Coherence-0.6.6.2/0000755000000000000000000000000011317673117012263 5ustar rootrootCoherence-0.6.6.2/README0000644000175000017500000000531311317660741012564 0ustar devdevCoherence is a framework written in Python, providing several UPnP MediaServers and MediaRenderers, and enabling your application to participate in digital living networks. It is licenced under the MIT licence. Coherence is known to work with various clients - Sony Playstation 3 - XBox360 - Denon AV Receivers - WD HD Live MediaPlayers - Samsung TVs - Sony Bravia TVs and much more... http://coherence-project.org/wiki/SupportedDevices As time evolves you will find in this file more detailed installation and basic configuration instructions. For now please pardon the inconvenience and have a look @ http://coherence-project.org Installation from source ======================== After downloading and extracting the archive or having done a svn checkout, move into the freshly created 'Coherence' folder and install the files with sudo python ./setup.py install This will copy the Python module files into your local Python package folder and the coherence executable to '/usr/bin/coherence'. http://coherence-project.org/wiki/DocumentationDepartment Quickstart ========== To just export some files on your hard-disk fire up Coherence with an UPnP MediaServer with a file-system backend enabled. coherence --plugin=backend:FSStore,content:/path/to/your/media/files A list of all available backends will get printed with coherence --help More information about the backends and their specific configuration: http://coherence-project.org/wiki/Backends For a continuous operation the use of a config file is highly recommended. http://coherence-project.org/wiki/XMLConfig The config file can be placed anywhere, coherence looks by default for $HOME/.coherence, but you can pass the path via the commandline option '-c' to it too. coherence -o /path/to/config/file -more -options Troubleshooting =============== If your MediaServer doesn't show up on your client most of the time networking issue are responsible for that. - Your system has more than one network interface? Specify the network interface to use in the config file. - Add a multicast route, pointing to the proper network interface route add -net 239.0.0.0 netmask 255.0.0.0 eth0 - Any firewall on your system? Support ======= First there is our wiki at http://coherence-project.org/. If you find an error there or think there is some information missing please get yourself an account and correct that issue. Thx! Then we have a mailinglist coherence-dev@lists.beebits.net. You add yourself here: http://lists.beebits.net/cgi-bin/mailman/listinfo/coherence-dev And last but not least, there is our irc channel irc://irc.freenode.net/#coherence where a lot of other users and most of the developers are around. Coherence-0.6.6.2/LICENCE0000644000175000017500000000205711317660741012673 0ustar devdevCopyright (c) <2006> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.Coherence-0.6.6.2/setup.py0000644000175000017500000001473011317672626013426 0ustar devdev# -*- coding: utf-8 -*- from coherence import __version__ try: from setuptools import setup, find_packages packages = find_packages() haz_setuptools = True except: from distutils.core import setup import os packages = ['coherence',] def find_packages(path): for f in os.listdir(path): if f[0] == '.': continue if os.path.isdir(os.path.join(path,f)) == True: next_path = os.path.join(path,f) if '__init__.py' in os.listdir(next_path): packages.append(next_path.replace(os.sep,'.')) find_packages(next_path) find_packages('coherence') haz_setuptools = False packages.append('misc') setup_args = { 'name':"Coherence", 'version':__version__, 'description':"""Coherence - DLNA/UPnP framework for the digital living""", 'long_description':"""Coherence is a framework written in Python, providing a variety of UPnP MediaServer and UPnP MediaRenderer implementations for instant use. It includes an UPnP ControlPoint, which is accessible via D-Bus too. Furthermore it enables your application to participate in digital living networks, at the moment primarily the DLNA/UPnP universe. Its objective and demand is to relieve your application from all the membership/the UPnP related tasks as much as possible. New in this %s - the Red-Nosed Reindeer Revolutions - release * new MediaServer backends that allow access to * Banshee - exports audio and video files from Banshees media db (http://banshee-project.org/) * FeedStore - a MediaServer serving generic RSS feeds * Playlist - exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...) * YAMJ - serves the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library (http://code.google.com/p/moviejukebox/) * updates on Mirabeau - our "UPnP over XMPP" bridge * simplifications in the D-Bus API * a first implementation of an JSON/REST API * advancements of the GStreamer MediaRenderer, supporting now GStreamers playbin2 * upgrade of the DVB-Daemon MediaServer * refinements in the transcoding section, having now the choice to use GStreamer pipelines or external processes like mencoder * more 'compatibility' improvements for different devices (e.g. Samsung TVs or Apache Felix) * and - as every time - the usual bugfixes and enhancements Kudos go to: * Benjamin (lightyear) Kampmann, * Charlie (porthose) Smotherman * Dominik (schrei5) Ruf, * Frank (dev) Scholz, * Friedrich (frinring) Kossebau, * Jean-Michel (jmsizun) Sizun, * Philippe (philn) Normand, * Sebastian (sebp) Poelsterl, * Zaheer (zaheerm) Merali """ % __version__, 'author':"Frank Scholz", 'author_email':'dev@coherence-project.org', 'license' : "MIT", 'packages':packages, 'scripts' : ['bin/coherence','misc/Desktop-Applet/applet-coherence'], 'url' : "http://coherence-project.org", 'download_url' : 'http://coherence-project.org/download/Coherence-%s.tar.gz' % __version__, 'keywords':['UPnP', 'DLNA', 'multimedia', 'gstreamer'], 'classifiers' : ['Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: Web Environment', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', ], 'package_data' : { 'coherence': ['upnp/core/xml-service-descriptions/*.xml', 'ui/icons/*.png', 'web/static/*.css','web/static/*.js'], 'misc': ['Desktop-Applet/*.png', 'device-icons/*.png'], }, } if haz_setuptools == True: setup_args['install_requires'] = [] try: from configobj import ConfigObj except ImportError: setup_args['install_requires'].append('ConfigObj >= 4.3') try: import netifaces except ImportError: import sys if sys.platform in ('win32','sunos5'): setup_args['install_requires'].append('Netifaces >= 0.4') setup_args['entry_points'] = """ [coherence.plugins.backend.media_server] FSStore = coherence.backends.fs_storage:FSStore MediaStore = coherence.backends.mediadb_storage:MediaStore ElisaMediaStore = coherence.backends.elisa_storage:ElisaMediaStore FlickrStore = coherence.backends.flickr_storage:FlickrStore AxisCamStore = coherence.backends.axiscam_storage:AxisCamStore BuzztardStore = coherence.backends.buzztard_control:BuzztardStore IRadioStore = coherence.backends.iradio_storage:IRadioStore LastFMStore = coherence.backends.lastfm_storage:LastFMStore AmpacheStore = coherence.backends.ampache_storage:AmpacheStore TrackerStore = coherence.backends.tracker_storage:TrackerStore DVBDStore = coherence.backends.dvbd_storage:DVBDStore AppleTrailersStore = coherence.backends.appletrailers_storage:AppleTrailersStore LolcatsStore = coherence.backends.lolcats_storage:LolcatsStore TEDStore = coherence.backends.ted_storage:TEDStore BBCStore = coherence.backends.bbc_storage:BBCStore SWR3Store = coherence.backends.swr3_storage:SWR3Store Gallery2Store = coherence.backends.gallery2_storage:Gallery2Store YouTubeStore = coherence.backends.youtube_storage:YouTubeStore MiroGuideStore = coherence.backends.miroguide_storage:MiroGuideStore ITVStore = coherence.backends.itv_storage:ITVStore PicasaStore = coherence.backends.picasa_storage:PicasaStore TestStore = coherence.backends.test_storage:TestStore PlaylistStore = coherence.backends.playlist_storage:PlaylistStore YamjStore = coherence.backends.yamj_storage:YamjStore BansheeStore = coherence.backends.banshee_storage:BansheeStore FeedStore = coherence.backends.feed_storage:FeedStore [coherence.plugins.backend.media_renderer] ElisaPlayer = coherence.backends.elisa_renderer:ElisaPlayer GStreamerPlayer = coherence.backends.gstreamer_renderer:GStreamerPlayer BuzztardPlayer = coherence.backends.buzztard_control:BuzztardPlayer [coherence.plugins.backend.binary_light] SimpleLight = coherence.backends.light:SimpleLight [coherence.plugins.backend.dimmable_light] BetterLight = coherence.backends.light:BetterLight """ setup(**setup_args) Coherence-0.6.6.2/Coherence.egg-info/0000755000000000000000000000000011317673117015650 5ustar rootrootCoherence-0.6.6.2/Coherence.egg-info/top_level.txt0000644000175000017500000000001711317673117020020 0ustar devdevcoherence misc Coherence-0.6.6.2/Coherence.egg-info/entry_points.txt0000644000175000017500000000442111317673117020567 0ustar devdev [coherence.plugins.backend.media_server] FSStore = coherence.backends.fs_storage:FSStore MediaStore = coherence.backends.mediadb_storage:MediaStore ElisaMediaStore = coherence.backends.elisa_storage:ElisaMediaStore FlickrStore = coherence.backends.flickr_storage:FlickrStore AxisCamStore = coherence.backends.axiscam_storage:AxisCamStore BuzztardStore = coherence.backends.buzztard_control:BuzztardStore IRadioStore = coherence.backends.iradio_storage:IRadioStore LastFMStore = coherence.backends.lastfm_storage:LastFMStore AmpacheStore = coherence.backends.ampache_storage:AmpacheStore TrackerStore = coherence.backends.tracker_storage:TrackerStore DVBDStore = coherence.backends.dvbd_storage:DVBDStore AppleTrailersStore = coherence.backends.appletrailers_storage:AppleTrailersStore LolcatsStore = coherence.backends.lolcats_storage:LolcatsStore TEDStore = coherence.backends.ted_storage:TEDStore BBCStore = coherence.backends.bbc_storage:BBCStore SWR3Store = coherence.backends.swr3_storage:SWR3Store Gallery2Store = coherence.backends.gallery2_storage:Gallery2Store YouTubeStore = coherence.backends.youtube_storage:YouTubeStore MiroGuideStore = coherence.backends.miroguide_storage:MiroGuideStore ITVStore = coherence.backends.itv_storage:ITVStore PicasaStore = coherence.backends.picasa_storage:PicasaStore TestStore = coherence.backends.test_storage:TestStore PlaylistStore = coherence.backends.playlist_storage:PlaylistStore YamjStore = coherence.backends.yamj_storage:YamjStore BansheeStore = coherence.backends.banshee_storage:BansheeStore FeedStore = coherence.backends.feed_storage:FeedStore [coherence.plugins.backend.media_renderer] ElisaPlayer = coherence.backends.elisa_renderer:ElisaPlayer GStreamerPlayer = coherence.backends.gstreamer_renderer:GStreamerPlayer BuzztardPlayer = coherence.backends.buzztard_control:BuzztardPlayer [coherence.plugins.backend.binary_light] SimpleLight = coherence.backends.light:SimpleLight [coherence.plugins.backend.dimmable_light] BetterLight = coherence.backends.light:BetterLight Coherence-0.6.6.2/Coherence.egg-info/SOURCES.txt0000644000000000000000000001674111317673117017545 0ustar rootrootChangeLog LICENCE MANIFEST.in NEWS README setup.py Coherence.egg-info/PKG-INFO Coherence.egg-info/SOURCES.txt Coherence.egg-info/dependency_links.txt Coherence.egg-info/entry_points.txt Coherence.egg-info/top_level.txt bin/coherence coherence/__init__.py coherence/backend.py coherence/base.py coherence/dbus_constants.py coherence/dbus_service.py coherence/dispatcher.py coherence/json.py coherence/log.py coherence/mirabeau.py coherence/transcoder.py coherence/tube_service.py coherence/backends/__init__.py coherence/backends/ampache_storage.py coherence/backends/appletrailers_storage.py coherence/backends/axiscam_storage.py coherence/backends/banshee_storage.py coherence/backends/bbc_storage.py coherence/backends/buzztard_control.py coherence/backends/dvbd_storage.py coherence/backends/elisa_renderer.py coherence/backends/elisa_storage.py coherence/backends/feed_storage.py coherence/backends/flickr_storage.py coherence/backends/fs_storage.py coherence/backends/gallery2_storage.py coherence/backends/gstreamer_renderer.py coherence/backends/iradio_storage.py coherence/backends/itv_storage.py coherence/backends/lastfm_storage.py coherence/backends/light.py coherence/backends/lolcats_storage.py coherence/backends/mediadb_storage.py coherence/backends/miroguide_storage.py coherence/backends/picasa_storage.py coherence/backends/playlist_storage.py coherence/backends/swr3_storage.py coherence/backends/ted_storage.py coherence/backends/test_storage.py coherence/backends/tracker_storage.py coherence/backends/yamj_storage.py coherence/backends/youtube_storage.py coherence/extern/__init__.py coherence/extern/config.py coherence/extern/covers_by_amazon.py coherence/extern/db_row.py coherence/extern/et.py coherence/extern/inotify.py coherence/extern/logger.py coherence/extern/louie.py coherence/extern/simple_config.py coherence/extern/simple_plugin.py coherence/extern/test_inotify.py coherence/extern/xdg.py coherence/extern/galleryremote/__init__.py coherence/extern/galleryremote/gallery.py coherence/extern/log/ChangeLog coherence/extern/log/README coherence/extern/log/__init__.py coherence/extern/log/log.py coherence/extern/log/termcolor.py coherence/extern/log/test_log.py coherence/extern/telepathy/__init__.py coherence/extern/telepathy/client.py coherence/extern/telepathy/connect.py coherence/extern/telepathy/mirabeau_tube_consumer.py coherence/extern/telepathy/mirabeau_tube_publisher.py coherence/extern/telepathy/stream.py coherence/extern/telepathy/tube.py coherence/extern/telepathy/tubeconn.py coherence/extern/uuid/__init__.py coherence/extern/uuid/uuid.README.txt coherence/extern/uuid/uuid.html coherence/extern/uuid/uuid.py coherence/extern/youtubedl/__init__.py coherence/extern/youtubedl/youtubedl.py coherence/test/__init__.py coherence/test/test_base.py coherence/test/test_dbus.py coherence/test/test_dispatching.py coherence/test/test_transcoder.py coherence/ui/__init__.py coherence/ui/av_widgets.py coherence/ui/icons/audio-x-generic.png coherence/ui/icons/emblem-new.png coherence/ui/icons/emblem-shared.png coherence/ui/icons/emblem-unreadable.png coherence/ui/icons/folder.png coherence/ui/icons/image-x-generic.png coherence/ui/icons/network-server.png coherence/ui/icons/video-x-generic.png coherence/upnp/__init__.py coherence/upnp/core/DIDLLite.py coherence/upnp/core/__init__.py coherence/upnp/core/action.py coherence/upnp/core/device.py coherence/upnp/core/dlna.py coherence/upnp/core/event.py coherence/upnp/core/msearch.py coherence/upnp/core/service.py coherence/upnp/core/soap_lite.py coherence/upnp/core/soap_proxy.py coherence/upnp/core/soap_service.py coherence/upnp/core/ssdp.py coherence/upnp/core/utils.py coherence/upnp/core/uuid.py coherence/upnp/core/variable.py coherence/upnp/core/test/__init__.py coherence/upnp/core/test/test_didl.py coherence/upnp/core/test/test_utils.py coherence/upnp/core/xml-service-descriptions/AVTransport1.xml coherence/upnp/core/xml-service-descriptions/AVTransport2.xml coherence/upnp/core/xml-service-descriptions/ConnectionManager1.xml coherence/upnp/core/xml-service-descriptions/ConnectionManager2.xml coherence/upnp/core/xml-service-descriptions/ContentDirectory1.xml coherence/upnp/core/xml-service-descriptions/ContentDirectory2.xml coherence/upnp/core/xml-service-descriptions/Dimming1.xml coherence/upnp/core/xml-service-descriptions/MediaRenderer2.xml coherence/upnp/core/xml-service-descriptions/MediaServer2.xml coherence/upnp/core/xml-service-descriptions/RenderingControl1.xml coherence/upnp/core/xml-service-descriptions/RenderingControl2.xml coherence/upnp/core/xml-service-descriptions/ScheduledRecording1.xml coherence/upnp/core/xml-service-descriptions/SwitchPower1.xml coherence/upnp/core/xml-service-descriptions/X_MS_MediaReceiverRegistrar1.xml coherence/upnp/devices/__init__.py coherence/upnp/devices/basics.py coherence/upnp/devices/binary_light.py coherence/upnp/devices/binary_light_client.py coherence/upnp/devices/control_point.py coherence/upnp/devices/dimmable_light.py coherence/upnp/devices/dimmable_light_client.py coherence/upnp/devices/media_renderer.py coherence/upnp/devices/media_renderer_client.py coherence/upnp/devices/media_server.py coherence/upnp/devices/media_server_client.py coherence/upnp/services/__init__.py coherence/upnp/services/clients/__init__.py coherence/upnp/services/clients/av_transport_client.py coherence/upnp/services/clients/connection_manager_client.py coherence/upnp/services/clients/content_directory_client.py coherence/upnp/services/clients/dimming_client.py coherence/upnp/services/clients/rendering_control_client.py coherence/upnp/services/clients/switch_power_client.py coherence/upnp/services/clients/test/__init__.py coherence/upnp/services/clients/test/test_switch_power_client.py coherence/upnp/services/servers/__init__.py coherence/upnp/services/servers/av_transport_server.py coherence/upnp/services/servers/connection_manager_server.py coherence/upnp/services/servers/content_directory_server.py coherence/upnp/services/servers/dimming_server.py coherence/upnp/services/servers/media_receiver_registrar_server.py coherence/upnp/services/servers/rendering_control_server.py coherence/upnp/services/servers/scheduled_recording_server.py coherence/upnp/services/servers/switch_power_server.py coherence/upnp/services/servers/test/__init__.py coherence/upnp/services/servers/test/test_content_directory_server.py coherence/web/__init__.py coherence/web/ui.py docs/coherence.conf.example docs/mirabeau.xml docs/test-store-example.xml docs/man/coherence.1 misc/coherence-initscript.sh misc/media_server_observer.py misc/org.Coherence.service misc/upnp-tester.py misc/Desktop-Applet/applet-coherence misc/Desktop-Applet/applet-coherence.1 misc/Desktop-Applet/tango-system-file-manager-32x32.png misc/Desktop-Applet/tango-system-file-manager.png misc/EOG-Plugin/upnp-coherence.eog-plugin misc/EOG-Plugin/upnp-coherence.py misc/Nautilus/coherence_upnp_export_extension.py misc/Nautilus/coherence_upnp_play_extension.py misc/Nautilus/coherence_upnp_upload_extension.py misc/Rhythmbox-Plugin/Makefile.am misc/Rhythmbox-Plugin/coherence.rb-plugin.in misc/Rhythmbox-Plugin/upnp_coherence/Makefile.am misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py misc/Rhythmbox-Plugin/upnp_coherence/MediaStore.py misc/Rhythmbox-Plugin/upnp_coherence/UpnpSource.py misc/Rhythmbox-Plugin/upnp_coherence/__init__.py misc/Totem-Plugin/upnp-coherence.py misc/Totem-Plugin/upnp-coherence.totem-plugin misc/device-icons/ampache-icon.png misc/device-icons/coherence-icon.png misc/device-icons/elisa-icon.png misc/device-icons/flickr-icon.png misc/device-icons/youtube-icon.png tests/rpc_client.pyCoherence-0.6.6.2/Coherence.egg-info/dependency_links.txt0000644000175000017500000000000111317673117021336 0ustar devdev Coherence-0.6.6.2/Coherence.egg-info/PKG-INFO0000644000175000017500000000540211317673117016366 0ustar devdevMetadata-Version: 1.0 Name: Coherence Version: 0.6.6.2 Summary: Coherence - DLNA/UPnP framework for the digital living Home-page: http://coherence-project.org Author: Frank Scholz Author-email: dev@coherence-project.org License: MIT Download-URL: http://coherence-project.org/download/Coherence-0.6.6.2.tar.gz Description: Coherence is a framework written in Python, providing a variety of UPnP MediaServer and UPnP MediaRenderer implementations for instant use. It includes an UPnP ControlPoint, which is accessible via D-Bus too. Furthermore it enables your application to participate in digital living networks, at the moment primarily the DLNA/UPnP universe. Its objective and demand is to relieve your application from all the membership/the UPnP related tasks as much as possible. New in this 0.6.6.2 - the Red-Nosed Reindeer Revolutions - release * new MediaServer backends that allow access to * Banshee - exports audio and video files from Banshees media db (http://banshee-project.org/) * FeedStore - a MediaServer serving generic RSS feeds * Playlist - exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...) * YAMJ - serves the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library (http://code.google.com/p/moviejukebox/) * updates on Mirabeau - our "UPnP over XMPP" bridge * simplifications in the D-Bus API * a first implementation of an JSON/REST API * advancements of the GStreamer MediaRenderer, supporting now GStreamers playbin2 * upgrade of the DVB-Daemon MediaServer * refinements in the transcoding section, having now the choice to use GStreamer pipelines or external processes like mencoder * more 'compatibility' improvements for different devices (e.g. Samsung TVs or Apache Felix) * and - as every time - the usual bugfixes and enhancements Kudos go to: * Benjamin (lightyear) Kampmann, * Charlie (porthose) Smotherman * Dominik (schrei5) Ruf, * Frank (dev) Scholz, * Friedrich (frinring) Kossebau, * Jean-Michel (jmsizun) Sizun, * Philippe (philn) Normand, * Sebastian (sebp) Poelsterl, * Zaheer (zaheerm) Merali Keywords: UPnP,DLNA,multimedia,gstreamer Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Web Environment Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Coherence-0.6.6.2/misc/0000755000000000000000000000000011317673117013216 5ustar rootrootCoherence-0.6.6.2/misc/media_server_observer.py0000644000175000017500000000356511317660741017574 0ustar devdev from twisted.internet import reactor from coherence.base import Coherence from coherence.upnp.devices.control_point import ControlPoint from coherence.upnp.core import DIDLLite # browse callback def process_media_server_browse(result, client): print "browsing root of", client.device.get_friendly_name() print "result contains %d out of %d total matches" % \ (int(result['NumberReturned']), int(result['TotalMatches'])) elt = DIDLLite.DIDLElement.fromString(result['Result']) for item in elt.getItems(): if item.upnp_class.startswith("object.container"): print " container %s (%s) with %d items" % \ (item.title,item.id, item.childCount) if item.upnp_class.startswith("object.item"): print " item %s (%s)" % (item.title, item.id) # called for each media server found def media_server_found(client, udn): print "media_server_found", client print "media_server_found", client.device.get_friendly_name() d = client.content_directory.browse(0, browse_flag='BrowseDirectChildren', process_result=False, backward_compatibility=False) d.addCallback(process_media_server_browse, client) # sadly they sometimes get removed as well :( def media_server_removed(udn): print "media_server_removed", udn def start(): control_point = ControlPoint(Coherence({'logmode':'warning'}), auto_client=['MediaServer']) control_point.connect(media_server_found, 'Coherence.UPnP.ControlPoint.MediaServer.detected') control_point.connect(media_server_removed, 'Coherence.UPnP.ControlPoint.MediaServer.removed') # now we should also try to discover the ones that are already there: for device in control_point.coherence.devices: print device if __name__ == "__main__": reactor.callWhenRunning(start) reactor.run() Coherence-0.6.6.2/misc/Nautilus/0000755000000000000000000000000011317673117015022 5ustar rootrootCoherence-0.6.6.2/misc/Nautilus/coherence_upnp_play_extension.py0000644000175000017500000000646311317660741023142 0ustar devdev#!/usr/bin/python # -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz """ Coherence and Nautilus bridge to play a file with a DLNA/UPnP MediaRenderer usable as Nautilus Extension for use an extension, copy it to ~/.nautilus/python-extensions or for a system-wide installation to /usr/lib/nautilus/extensions-2.0/python connection to Coherence is established via DBus """ import os import time from urllib import unquote import nautilus import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import dbus.service # dbus defines BUS_NAME = 'org.Coherence' OBJECT_PATH = '/org/Coherence' class CoherencePlayExtension(nautilus.MenuProvider): def __init__(self): print "CoherencePlayExtension", os.getpid() self.coherence = None try: self.init_controlpoint() except: print "can't setup Coherence connection" def init_controlpoint(self): self.bus = dbus.SessionBus() self.coherence = self.bus.get_object(BUS_NAME,OBJECT_PATH) def get_file_items(self, window, files): if self.coherence == None: return if len(files) == 0: return for file in files: if file.is_directory() or file.get_uri_scheme() != 'file': return #pin = self.coherence.get_pin('Nautilus::MediaServer::%d'%os.getpid()) #print 'Pin:',pin #if pin == 'Coherence::Pin::None': # return devices = self.coherence.get_devices(dbus_interface=BUS_NAME) i=0 menuitem = None for device in devices: print device['friendly_name'],device['device_type'] if device['device_type'].split(':')[3] == 'MediaRenderer': if i == 0: menuitem = nautilus.MenuItem('CoherencePlayExtension::Play', 'Play on MediaRenderer', 'Play the selected file(s) on a DLNA/UPnP MediaRenderer') submenu = nautilus.Menu() menuitem.set_submenu(submenu) item = nautilus.MenuItem('CoherencePlayExtension::Play%d' %i, device['friendly_name'], '') for service in device['services']: service_type = service.split('/')[-1] if service_type == 'AVTransport': item.connect('activate', self.play,service, device['path'], files) break submenu.append_item(item) i += 1 if i == 0: return return menuitem, def play(self,menu,service,uuid,files): print "play",uuid,service,files #pin = self.coherence.get_pin('Nautilus::MediaServer::%d'%os.getpid()) #if pin == 'Coherence::Pin::None': # return file = unquote(files[0].get_uri()[7:]) file = os.path.abspath(file) uri = self.coherence.create_oob(file) #result = self.coherence.call_plugin(pin,'get_url_by_name',{'name':file}) #print 'result', result print uri s = self.bus.get_object(BUS_NAME+'.service',service) print s s.action('stop','') s.action('set_av_transport_uri',{'current_uri':uri}) s.action('play','') Coherence-0.6.6.2/misc/Nautilus/coherence_upnp_export_extension.py0000755000175000017500000000654411317660741023521 0ustar devdev#!/usr/bin/python # -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz """ Coherence and Nautilus bridge to export folders as a DLNA/UPnP MediaServer usable as Nautilus Extension or a Script for use an extension, copy it to ~/.nautilus/python-extensions or for a system-wide installation to /usr/lib/nautilus/extensions-2.0/python for us as a script put it into ~/.gnome2/nautilus-scripts with a describing name of maybe "export as UPnP MediaServer" connection to Coherence is established via DBus when used as a script it will export every folder as a separate MediaServer the extension will use the same MediaServer over the lifetime of Nautilus and just add new folders """ import sys import os import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import dbus.service BUS_NAME = 'org.Coherence' OBJECT_PATH = '/org/Coherence' def do_export(name,directories): bus = dbus.SessionBus() coherence = bus.get_object(BUS_NAME,OBJECT_PATH) r = coherence.add_plugin('FSStore', {'name': name, 'content':','.join(directories)}, dbus_interface=BUS_NAME) return r try: import nautilus from urllib import unquote class CoherenceExportExtension(nautilus.MenuProvider): def __init__(self): print "CoherenceExportExtension", os.getpid() try: from coherence.ui.av_widgets import DeviceExportWidget self.ui = DeviceExportWidget(standalone=False) self.ui_create() except: print "can't setup Coherence connection" self.ui = None def ui_destroy(self,*args): self.window = None def ui_create(self): import pygtk pygtk.require("2.0") import gtk self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_default_size(350, 300) self.window.set_title('Coherence DLNA/UPnP Share') self.window.connect("delete_event", self.ui_destroy) self.window.add(self.ui.build_ui(root=self.window)) def get_file_items(self, window, files): if self.ui == None: return if len(files) == 0: return for file in files: if not file.is_directory(): return item = nautilus.MenuItem('CoherenceExportExtension::export_resources', 'Sharing as a MediaServer...', 'Share the selected folders as a DLNA/UPnP MediaServer') item.connect('activate', self.export_resources, files) return item, def export_resources(self, menu, files): if len(files) == 0: return self.build() if self.window == None: self.ui_create() self.ui.add_files([unquote(file.get_uri()[7:]) for file in files]) self.window.show_all() except ImportError: pass if __name__ == '__main__': import os.path files = [x for x in sys.argv[1:] if os.path.isdir(x)] do_export('Nautilus',files) Coherence-0.6.6.2/misc/Nautilus/coherence_upnp_upload_extension.py0000755000175000017500000000661411317660741023462 0ustar devdev#!/usr/bin/python # -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz """ Coherence and Nautilus bridge to upload files into a DLNA/UPnP MediaServer usable as Nautilus Extension or a Script for use an extension, copy it to ~/.nautilus/python-extensions or for a system-wide installation to /usr/lib/nautilus/extensions-2.0/python for us as a script put it into ~/.gnome2/nautilus-scripts with a describing name of maybe "upload to UPnP MediaServer" connection to Coherence is established via DBus """ import sys import os import pygtk pygtk.require("2.0") import gtk from coherence.ui.av_widgets import DeviceImportWidget def show_upload_widget(files,standalone=True): window = gtk.Window(gtk.WINDOW_TOPLEVEL) if standalone: window.connect("delete_event", gtk.main_quit) window.set_default_size(350, 300) window.set_title('Coherence DLNA/UPnP Upload') ui=DeviceImportWidget(standalone=standalone,root=window) window.add(ui.window) for filename in files: ui.add_file(filename) window.show_all() try: import nautilus from urllib import unquote import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import dbus.service # dbus defines BUS_NAME = 'org.Coherence' OBJECT_PATH = '/org/Coherence' def log(t): return f = open('/tmp/coherence.log','a') f.write(t+'\n') f.close() class CoherenceUploadExtension(nautilus.MenuProvider): def __init__(self): print "CoherenceUploadExtension", os.getpid() log("CoherenceUploadExtension %r" % os.getpid()) self.coherence = None try: self.init_controlpoint() except: import traceback log("can't setup %r" % traceback.format_exc()) print "can't setup Coherence connection" def init_controlpoint(self): self.bus = dbus.SessionBus() self.coherence = self.bus.get_object(BUS_NAME,OBJECT_PATH) def get_file_items(self, window, files): log("get_file_items") log("coherence %r" % self.coherence) if self.coherence == None: return log("files %d" % len(files)) if len(files) == 0: return log("get_file_items 2") for file in files: log("get_file_items 3 %r" % file) if file.is_directory() or file.get_uri_scheme() != 'file': return item = nautilus.MenuItem('CoherenceUploadExtension::import_resources', 'Upload to MediaServer...', 'Upload the selected files to a DLNA/UPnP MediaServer') item.connect('activate', self.import_resources, files) return item, def import_resources(self, menu, files): if len(files) == 0: return show_upload_widget([unquote(file.get_uri()[7:]) for file in files],standalone=False) except ImportError: pass if __name__ == '__main__': import os.path files = [x for x in sys.argv[1:] if not os.path.isdir(x)] show_upload_widget(files) gtk.gdk.threads_init() gtk.main() Coherence-0.6.6.2/misc/upnp-tester.py0000644000175000017500000001577111317660741015510 0ustar devdev#!/usr/bin/env python # -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz # upnp-tester.py # # very basic atm # # provides these functions: # # list - display all devices # extract - extract device and service xml files and put them in a # /tmp/ directory # send - pack the before extracted xml files in a tar.gz and # send them via email to the Coherence googlemail account # import os from sets import Set from twisted.internet import stdio from twisted.protocols import basic from twisted.internet import protocol try: from twisted.mail import smtp from twisted.names import client as namesclient from twisted.names import dns import StringIO class SMTPClient(smtp.ESMTPClient): """ build an email message and send it to our googlemail account """ def __init__(self, mail_from, mail_to, mail_subject, mail_file, *args, **kwargs): smtp.ESMTPClient.__init__(self, *args, **kwargs) self.mailFrom = mail_from self.mailTo = mail_to self.mailSubject = mail_subject self.mail_file = mail_file self.mail_from = mail_from def getMailFrom(self): result = self.mailFrom self.mailFrom = None return result def getMailTo(self): return [self.mailTo] def getMailData(self): from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart msg = MIMEMultipart() msg['Subject'] = self.mailSubject msg['From'] = self.mail_from msg['To'] = self.mailTo fp = open(self.mail_file, 'rb') tar = MIMEApplication(fp.read(),'x-tar') fp.close() tar.add_header('Content-Disposition', 'attachment', filename=os.path.basename(self.mail_file)) msg.attach(tar) return StringIO.StringIO(msg.as_string()) def sentMail(self, code, resp, numOk, addresses, log): print 'Sent', numOk, 'messages' class SMTPClientFactory(protocol.ClientFactory): protocol = SMTPClient def __init__(self, mail_from, mail_to, mail_subject, mail_file, *args, **kwargs): self.mail_from = mail_from self.mail_to = mail_to self.mail_subject = mail_subject self.mail_file = mail_file def buildProtocol(self, addr): return self.protocol(self.mail_from, self.mail_to, self.mail_subject, self.mail_file, secret=None, identity='localhost') except ImportError: pass from twisted.internet import reactor, defer from twisted.web import client from coherence.base import Coherence class UI(basic.LineReceiver): from os import linesep as delimiter def connectionMade(self): self.print_prompt() def lineReceived(self, line): args = line.strip().split() if args: cmd = args[0].lower() if hasattr(self, 'cmd_%s' % cmd): getattr(self, 'cmd_%s' % (cmd))(args[1:]) elif cmd == "?": self.cmd_help(args[1:]) else: self.transport.write("""Unknown command '%s'\n"""%(cmd)) self.print_prompt() def cmd_help(self,args): "help -- show help" methods = Set([ getattr(self, x) for x in dir(self) if x[:4] == "cmd_" ]) self.transport.write("Commands:\n") for method in methods: if hasattr(method, '__doc__'): self.transport.write("%s\n"%(method.__doc__)) def cmd_list(self, args): "list -- list devices" self.transport.write("Devices:\n") for d in self.coherence.get_devices(): self.transport.write(str("%s %s [%s/%s/%s]\n" % (d.friendly_name, ':'.join(d.device_type.split(':')[3:5]), d.st, d.usn.split(':')[1], d.host))) def cmd_extract(self, args): "extract -- download xml files from device" device = self.coherence.get_device_with_id(args[0]) if device == None: self.transport.write("device %s not found - aborting\n" % args[0]) else: self.transport.write(str("extracting from %s @ %s\n" % (device.friendly_name, device.host))) try: l = [] def device_extract(workdevice, path): tmp_dir = os.path.join(path,workdevice.get_uuid()) os.mkdir(tmp_dir) d = client.downloadPage(workdevice.get_location(),os.path.join(tmp_dir,'device-description.xml')) l.append(d) for service in workdevice.services: d = client.downloadPage(service.get_scpd_url(),os.path.join(tmp_dir,'%s-description.xml'%service.service_type.split(':',3)[3])) l.append(d) for ed in workdevice.devices: device_extract(ed, tmp_dir) def finished(result): self.transport.write(str("\nextraction of device %s finished\nfiles have been saved to /tmp/%s\n" %(args[0],args[0]))) self.print_prompt() device_extract(device,'/tmp') dl = defer.DeferredList(l) dl.addCallback(finished) except Exception, msg: self.transport.write(str("problem creating download directory %s\n" % msg)) def cmd_send(self, args): "send -- send before extracted xml files to the Coherence home base" if os.path.isdir(os.path.join('/tmp',args[0])) == 1: cwd = os.getcwd() os.chdir('/tmp') import tarfile tar = tarfile.open(os.path.join('/tmp',args[0]+'.tgz'), "w:gz") for file in os.listdir(os.path.join('/tmp',args[0])): tar.add(os.path.join(args[0],file)) tar.close() os.chdir(cwd) def got_mx(result): mx_list = result[0] mx_list.sort(lambda x, y: cmp(x.payload.preference, y.payload.preference)) if len(mx_list) > 0: import posix, pwd import socket reactor.connectTCP(str(mx_list[0].payload.name), 25, SMTPClientFactory('@'.join((pwd.getpwuid(posix.getuid())[0],socket.gethostname())), 'upnp.fingerprint@googlemail.com', 'xml-files', os.path.join('/tmp',args[0]+'.tgz'))) mx = namesclient.lookupMailExchange('googlemail.com') mx.addCallback(got_mx) def cmd_quit(self, args): "quit -- quits this program" reactor.stop() cmd_exit = cmd_quit def print_prompt(self): self.transport.write('>>> ') if __name__ == '__main__': c = Coherence({'logmode':'none'}) ui = UI() ui.coherence = c stdio.StandardIO(ui) reactor.run() Coherence-0.6.6.2/misc/Rhythmbox-Plugin/0000755000000000000000000000000011317673117016436 5ustar rootrootCoherence-0.6.6.2/misc/Rhythmbox-Plugin/Makefile.am0000644000175000017500000000100711317660741020107 0ustar devdev# Coherence UPNP Python Plugin SUBDIRS = upnp_coherence plugindir = $(PLUGINDIR)/upnp_coherence %.rb-plugin: %.rb-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache plugin_DATA = $(plugin_in_files:.rb-plugin.in=.rb-plugin) plugin_in_files = coherence.rb-plugin.in glade_DATA = gladedir = $(plugindir) EXTRA_DIST = $(plugin_in_files) $(glade_DATA) CLEANFILES = $(plugin_DATA) DISTCLEANFILES = $(plugin_DATA) Coherence-0.6.6.2/misc/Rhythmbox-Plugin/upnp_coherence/0000755000000000000000000000000011317673117021433 5ustar rootrootCoherence-0.6.6.2/misc/Rhythmbox-Plugin/upnp_coherence/Makefile.am0000644000175000017500000000027011317660741023105 0ustar devdev# Coherence UPnP Python Plugin plugindir = $(PLUGINDIR)/upnp_coherence plugin_PYTHON = \ UpnpSource.py \ MediaStore.py \ MediaPlayer.py \ __init__.py Coherence-0.6.6.2/misc/Rhythmbox-Plugin/upnp_coherence/MediaStore.py0000644000175000017500000004123011317660741023460 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # # Copyright 2007, James Livingston # Copyright 2007, Frank Scholz import os.path import rhythmdb import coherence.extern.louie as louie import urllib from coherence import __version_info__ from coherence.upnp.core import DIDLLite from coherence.backend import BackendItem, BackendStore ROOT_CONTAINER_ID = 0 AUDIO_CONTAINER = 100 AUDIO_ALL_CONTAINER_ID = 101 AUDIO_ARTIST_CONTAINER_ID = 102 AUDIO_ALBUM_CONTAINER_ID = 103 CONTAINER_COUNT = 10000 TRACK_COUNT = 1000000 # most of this class is from Coherence, originally under the MIT licence class Container(BackendItem): logCategory = 'rb_media_store' def __init__(self, id, parent_id, name, children_callback=None,store=None,play_container=False): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.store = store self.play_container = play_container self.update_id = 0 if children_callback != None: self.children = children_callback else: self.children = [] def add_child(self, child): self.children.append(child) def get_children(self,start=0,request_count=0): if callable(self.children): children = self.children(self.id) else: children = self.children self.info("Container get_children %r (%r,%r)", children, start, request_count) if request_count == 0: return children[start:] else: return children[start:request_count] def get_child_count(self): return len(self.get_children()) def get_item(self, parent_id=None): item = DIDLLite.Container(self.id,self.parent_id,self.name) item.childCount = self.get_child_count() if self.store and self.play_container == True: if item.childCount > 0: res = DIDLLite.PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=str(TRACK_COUNT + int(self.get_children()[0].get_id()))) item.res.append(res) return item def get_name(self): return self.name def get_id(self): return self.id class Album(BackendItem): logCategory = 'rb_media_store' def __init__(self, store, title, id, parent_id): self.id = id self.title = title self.store = store query = self.store.db.query_new() self.store.db.query_append(query,[rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_TYPE, self.store.db.entry_type_get_by_name('song')], [rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_ALBUM, self.title]) self.tracks_per_album_query = self.store.db.query_model_new(query) #self.tracks_per_album_query.set_sort_order(rhythmdb.rhythmdb_query_model_track_sort_func) self.store.db.do_full_query_async_parsed(self.tracks_per_album_query, query) def get_children(self,start=0,request_count=0): children = [] def track_sort(x,y): entry = self.store.db.entry_lookup_by_id (x.id) x_track = self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER) entry = self.store.db.entry_lookup_by_id (y.id) y_track = self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER) return cmp(x_track,y_track) def collate (model, path, iter): self.info("Album get_children %r %r %r" %(model, path, iter)) id = model.get(iter, 0)[0] children.append(Track(self.store,id,self.id)) self.tracks_per_album_query.foreach(collate) children.sort(cmp=track_sort) if request_count == 0: return children[start:] else: return children[start:request_count] def get_child_count(self): return len(self.get_children()) def get_item(self, parent_id = AUDIO_ALBUM_CONTAINER_ID): item = DIDLLite.MusicAlbum(self.id, parent_id, self.title) if __version_info__ >= (0,6,4): if self.get_child_count() > 0: res = DIDLLite.PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=str(TRACK_COUNT+int(self.get_children()[0].get_id()))) item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.title def get_cover(self): return self.cover class Artist(BackendItem): logCategory = 'rb_media_store' def __init__(self, store, name, id, parent_id): self.id = id self.name = name self.store = store query = self.store.db.query_new() self.store.db.query_append(query,[rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_TYPE, self.store.db.entry_type_get_by_name('song')], [rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_ARTIST, self.name]) self.tracks_per_artist_query = self.store.db.query_model_new(query) self.store.db.do_full_query_async_parsed(self.tracks_per_artist_query, query) self.albums_per_artist_query = self.store.db.property_model_new(rhythmdb.PROP_ALBUM) self.albums_per_artist_query.props.query_model = self.tracks_per_artist_query def get_artist_all_tracks(self,id): children = [] def collate (model, path, iter): id = model.get(iter, 0)[0] print id children.append(Track(self.store,id,self.id)) self.tracks_per_artist_query.foreach(collate) return children def get_children(self,start=0,request_count=0): children = [] def collate (model, path, iter): name = model.get(iter, 0)[0] priority = model.get(iter, 1)[0] self.info("get_children collate %r %r", name, priority) if priority is False: try: album = self.store.albums[name] children.append(album) except: self.warning("hmm, a new album %r, that shouldn't happen", name) self.albums_per_artist_query.foreach(collate) if len(children): all_id = 'artist_all_tracks_%d' % (self.id) if all_id not in self.store.containers: self.store.containers[all_id] = \ Container( all_id, self.id, 'All tracks of %s' % self.name, children_callback=self.get_artist_all_tracks, store=self.store,play_container=True) children.insert(0,self.store.containers[all_id]) if request_count == 0: return children[start:] else: return children[start:request_count] def get_child_count(self): return len(self.get_children()) def get_item(self, parent_id = AUDIO_ARTIST_CONTAINER_ID): item = DIDLLite.MusicArtist(self.id, parent_id, self.name) return item def get_id(self): return self.id def get_name(self): return self.name class Track(BackendItem): logCategory = 'rb_media_store' def __init__(self, store, id, parent_id): self.store = store if type(id) == int: self.id = id else: self.id = self.store.db.entry_get (id, rhythmdb.PROP_ENTRY_ID) self.parent_id = parent_id def get_children(self, start=0, request_count=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.info("Track get_item %r @ %r" %(self.id,self.parent_id)) host = "" # load common values entry = self.store.db.entry_lookup_by_id(self.id) # Bitrate is in bytes/second, not kilobits/second bitrate = self.store.db.entry_get(entry, rhythmdb.PROP_BITRATE) * 1024 / 8 # Duration is in HH:MM:SS format seconds = self.store.db.entry_get(entry, rhythmdb.PROP_DURATION) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 duration = ("%02d:%02d:%02d") % (hours, minutes, seconds) location = self.get_path(entry) mimetype = self.store.db.entry_get(entry, rhythmdb.PROP_MIMETYPE) # This isn't a real mime-type if mimetype == "application/x-id3": mimetype = "audio/mpeg" size = self.store.db.entry_get(entry, rhythmdb.PROP_FILE_SIZE) album = self.store.db.entry_get(entry, rhythmdb.PROP_ALBUM) if self.parent_id == None: try: self.parent_id = self.store.albums[album].id except: pass # create item item = DIDLLite.MusicTrack(self.id + TRACK_COUNT,self.parent_id) item.album = album item.artist = self.store.db.entry_get(entry, rhythmdb.PROP_ARTIST) #item.date = item.genre = self.store.db.entry_get(entry, rhythmdb.PROP_GENRE) item.originalTrackNumber = str(self.store.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER)) item.title = self.store.db.entry_get(entry, rhythmdb.PROP_TITLE) # much nicer if it was entry.title cover = self.store.db.entry_request_extra_metadata(entry, "rb:coverArt-uri") #self.warning("cover for %r is %r", item.title, cover) if cover != None: _,ext = os.path.splitext(cover) item.albumArtURI = ''.join((self.get_url(),'?cover',ext)) # add http resource res = DIDLLite.Resource(self.get_url(), 'http-get:*:%s:*' % mimetype) if size > 0: res.size = size if duration > 0: res.duration = str(duration) if bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) # add internal resource res = DIDLLite.Resource('track-%d' % self.id, 'rhythmbox:%s:%s:*' % (self.store.server.coherence.hostname, mimetype)) if size > 0: res.size = size if duration > 0: res.duration = str(duration) if bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) return item def get_id(self): return self.id def get_name(self): entry = self.store.db.entry_lookup_by_id (self.id) return self.store.db.entry_get(entry, rhythmdb.PROP_TITLE) def get_url(self): return self.store.urlbase + str(self.id + TRACK_COUNT) def get_path(self, entry = None): if entry is None: entry = self.store.db.entry_lookup_by_id (self.id) uri = self.store.db.entry_get(entry, rhythmdb.PROP_LOCATION) self.info("Track get_path uri = %r", uri) location = None if uri.startswith("file://"): location = unicode(urllib.unquote(uri[len("file://"):])) self.info("Track get_path location = %r", location) return location def get_cover(self): entry = self.store.db.entry_lookup_by_id(self.id) cover = self.store.db.entry_request_extra_metadata(entry, "rb:coverArt-uri") return cover class MediaStore(BackendStore): logCategory = 'rb_media_store' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.warning("__init__ MediaStore %r", kwargs) self.db = kwargs['db'] self.plugin = kwargs['plugin'] self.wmc_mapping.update({'4': lambda : self.get_by_id(AUDIO_ALL_CONTAINER_ID), # all tracks '7': lambda : self.get_by_id(AUDIO_ALBUM_CONTAINER_ID), # all albums '6': lambda : self.get_by_id(AUDIO_ARTIST_CONTAINER_ID), # all artists }) self.next_id = CONTAINER_COUNT self.albums = None self.artists = None self.tracks = None self.urlbase = kwargs.get('urlbase','') if( len(self.urlbase) > 0 and self.urlbase[len(self.urlbase)-1] != '/'): self.urlbase += '/' try: self.name = kwargs['name'] except KeyError: self.name = "Rhythmbox on %s" % self.server.coherence.hostname query = self.db.query_new() self.info(query) self.db.query_append(query, [rhythmdb.QUERY_PROP_EQUALS, rhythmdb.PROP_TYPE, self.db.entry_type_get_by_name('song')]) qm = self.db.query_model_new(query) self.db.do_full_query_async_parsed(qm, query) self.album_query = self.db.property_model_new(rhythmdb.PROP_ALBUM) self.album_query.props.query_model = qm self.artist_query = self.db.property_model_new(rhythmdb.PROP_ARTIST) self.artist_query.props.query_model = qm self.containers = {} self.containers[ROOT_CONTAINER_ID] = \ Container( ROOT_CONTAINER_ID,-1, "Rhythmbox on %s" % self.server.coherence.hostname) self.containers[AUDIO_ALL_CONTAINER_ID] = \ Container( AUDIO_ALL_CONTAINER_ID,ROOT_CONTAINER_ID, 'All tracks', children_callback=self.children_tracks, store=self,play_container=True) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALL_CONTAINER_ID]) self.containers[AUDIO_ALBUM_CONTAINER_ID] = \ Container( AUDIO_ALBUM_CONTAINER_ID,ROOT_CONTAINER_ID, 'Albums', children_callback=self.children_albums) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALBUM_CONTAINER_ID]) self.containers[AUDIO_ARTIST_CONTAINER_ID] = \ Container( AUDIO_ARTIST_CONTAINER_ID,ROOT_CONTAINER_ID, 'Artists', children_callback=self.children_artists) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ARTIST_CONTAINER_ID]) louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def get_by_id(self,id): self.info("looking for id %r", id) if isinstance(id, basestring) and id.startswith('artist_all_tracks_'): try: return self.containers[id] except: return None id = id.split('@',1) item_id = id[0] item_id = int(item_id) if item_id < TRACK_COUNT: try: item = self.containers[item_id] except KeyError: item = None else: item = Track(self, (item_id - TRACK_COUNT),None) return item def get_next_container_id(self): ret = self.next_id self.next_id += 1 return ret def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', [ 'rhythmbox:%s:*:*' % self.server.coherence.hostname, 'http-get:*:audio/mpeg:*', ]) self.warning("__init__ MediaStore initialized") def children_tracks(self, parent_id): tracks = [] def track_cb (entry): if self.db.entry_get (entry, rhythmdb.PROP_HIDDEN): return id = self.db.entry_get (entry, rhythmdb.PROP_ENTRY_ID) track = Track(self, id, parent_id) tracks.append(track) self.db.entry_foreach_by_type (self.db.entry_type_get_by_name('song'), track_cb) return tracks def children_albums(self,parent_id): albums = {} self.info('children_albums') def album_sort(x,y): r = cmp(x.title,y.title) self.info("sort %r - %r = %r", x.title, y.title, r) return r def collate (model, path, iter): name = model.get(iter, 0)[0] priority = model.get(iter, 1)[0] self.info("children_albums collate %r %r", name, priority) if priority is False: id = self.get_next_container_id() album = Album(self, name, id,parent_id) self.containers[id] = album albums[name] = album if self.albums is None: self.album_query.foreach(collate) self.albums = albums albums = self.albums.values() #.sort(cmp=album_sort) albums.sort(cmp=album_sort) return albums def children_artists(self,parent_id): artists = [] def collate (model, path, iter): name = model.get(iter, 0)[0] priority = model.get(iter, 1)[0] if priority is False: id = self.get_next_container_id() artist = Artist(self,name, id,parent_id) self.containers[id] = artist artists.append(artist) if self.artists is None: self.artist_query.foreach(collate) self.artists = artists return self.artists Coherence-0.6.6.2/misc/Rhythmbox-Plugin/upnp_coherence/__init__.py0000644000175000017500000002707111317664660023176 0ustar devdev# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- # # Copyright 2008, Frank Scholz # Copyright 2008, James Livingston # # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php import rhythmdb, rb import gobject gobject.threads_init() import gconf import coherence.extern.louie as louie from coherence import log # for the icon import os.path, urllib, gio, gtk.gdk # the gconf configuration gconf_keys = { 'port': "/apps/rhythmbox/plugins/coherence/port", 'interface': "/apps/rhythmbox/plugins/coherence/interface", # DMS 'dms_uuid': "/apps/rhythmbox/plugins/coherence/dms/uuid", 'dms_active': "/apps/rhythmbox/plugins/coherence/dms/active", 'dms_version': "/apps/rhythmbox/plugins/coherence/dms/version", 'dms_name': "/apps/rhythmbox/plugins/coherence/dms/name", # DMR 'dmr_uuid': "/apps/rhythmbox/plugins/coherence/dmr/uuid", 'dmr_active': "/apps/rhythmbox/plugins/coherence/dmr/active", 'dmr_version': "/apps/rhythmbox/plugins/coherence/dmr/version", 'dmr_name': "/apps/rhythmbox/plugins/coherence/dmr/name", # DMC 'dmc_active': "/apps/rhythmbox/plugins/coherence/dmc/active", } class CoherencePlugin(rb.Plugin, log.Loggable): logCategory = 'rb_coherence_plugin' def __init__(self): rb.Plugin.__init__(self) self.coherence = None self.config = gconf.client_get_default() if self.config.get(gconf_keys['dmc_active']) is None: # key not yet found represented by "None" self._set_defaults() def _set_defaults(self): for a in ('r', 's'): self.config.set_bool(gconf_keys['dm%s_active' % a], True) self.config.set_int(gconf_keys['dm%s_version' % a], 2) self.config.set_bool(gconf_keys['dmc_active'], True) def activate(self, shell): from twisted.internet import gtk2reactor try: gtk2reactor.install() except AssertionError, e: # sometimes it's already installed self.warning("gtk2reactor already installed %r" % e) self.coherence = self.get_coherence() if self.coherence is None: self.warning("Coherence is not installed or too old, aborting") return self.warning("Coherence UPnP plugin activated") self.shell = shell self.sources = {} # Set up our icon the_icon = None face_path = os.path.join(os.path.expanduser('~'), ".face") if os.path.exists(face_path): file = gio.File(file=face_path); url = file.get_uri(); info = file.query_info("standard::fast-content-type"); mimetype = info.get_attribute_as_string("standard::fast-content-type"); pixbuf = gtk.gdk.pixbuf_new_from_file(face_path) width = "%s" % pixbuf.get_width() height = "%s" % pixbuf.get_height() depth = '24' the_icon = { 'url': url, 'mimetype': mimetype, 'width': width, 'height': height, 'depth': depth } if self.config.get_bool(gconf_keys['dms_active']): # create our own media server from coherence.upnp.devices.media_server import MediaServer from MediaStore import MediaStore kwargs = { 'version': self.config.get_int(gconf_keys['dms_version']), 'no_thread_needed': True, 'db': self.shell.props.db, 'plugin': self} if the_icon: kwargs['icon'] = the_icon dms_uuid = self.config.get_string(gconf_keys['dms_uuid']) if dms_uuid: kwargs['uuid'] = dms_uuid name = self.config.get_string(gconf_keys['dms_name']) if name: kwargs['name'] = name self.server = MediaServer(self.coherence, MediaStore, **kwargs) if dms_uuid is None: self.config.set_string(gconf_keys['dms_uuid'], str(self.server.uuid)) self.warning("Media Store available with UUID %s" % str(self.server.uuid)) if self.config.get_bool(gconf_keys['dmr_active']): # create our own media renderer # but only if we have a matching Coherence package installed if self.coherence_version < (0, 5, 2): print "activation faild. Coherence is older than version 0.5.2" else: from coherence.upnp.devices.media_renderer import MediaRenderer from MediaPlayer import RhythmboxPlayer kwargs = { "version": self.config.get_int(gconf_keys['dmr_version']), "no_thread_needed": True, "shell": self.shell, 'rb_mediaserver': self.server, } if the_icon: kwargs['icon'] = the_icon dmr_uuid = self.config.get_string(gconf_keys['dmr_uuid']) if dmr_uuid: kwargs['uuid'] = dmr_uuid name = self.config.get_string(gconf_keys['dmr_name']) if name: kwargs['name'] = name self.renderer = MediaRenderer(self.coherence, RhythmboxPlayer, **kwargs) if dmr_uuid is None: self.config.set_string(gconf_keys['dmr_uuid'], str(self.renderer.uuid)) self.warning("Media Renderer available with UUID %s" % str(self.renderer.uuid)) if self.config.get_bool(gconf_keys['dmc_active']): self.warning("start looking for media servers") # watch for media servers louie.connect(self.detected_media_server, 'Coherence.UPnP.ControlPoint.MediaServer.detected', louie.Any) louie.connect(self.removed_media_server, 'Coherence.UPnP.ControlPoint.MediaServer.removed', louie.Any) def deactivate(self, shell): self.info("Coherence UPnP plugin deactivated") if self.coherence is None: return self.coherence.shutdown() try: louie.disconnect(self.detected_media_server, 'Coherence.UPnP.ControlPoint.MediaServer.detected', louie.Any) except louie.error.DispatcherKeyError: pass try: louie.disconnect(self.removed_media_server, 'Coherence.UPnP.ControlPoint.MediaServer.removed', louie.Any) except louie.error.DispatcherKeyError: pass del self.shell del self.coherence for usn, source in self.sources.iteritems(): source.delete_thyself() del self.sources # uninstall twisted reactor? probably not, since other things may have used it def get_coherence (self): coherence_instance = None required_version = (0, 5, 7) try: from coherence.base import Coherence from coherence import __version_info__ except ImportError, e: print "Coherence not found" return None if __version_info__ < required_version: required = '.'.join([str(i) for i in required_version]) found = '.'.join([str(i) for i in __version_info__]) print "Coherence %s required. %s found. Please upgrade" % (required, found) return None self.coherence_version = __version_info__ coherence_config = { #'logmode': 'info', 'controlpoint': 'yes', 'plugins': {}, } serverport = self.config.get_int(gconf_keys['port']) if serverport: coherence_config['serverport'] = serverport interface = self.config.get_string(gconf_keys['interface']) if interface: coherence_config['interface'] = interface coherence_instance = Coherence(coherence_config) return coherence_instance def removed_media_server(self, udn): self.info("upnp server went away %s" % udn) if self.sources.has_key(udn): self.sources[udn].delete_thyself() del self.sources[udn] def detected_media_server(self, client, udn): self.info("found upnp server %s (%s)" % (client.device.get_friendly_name(), udn)) if self.server and client.device.get_id() == str(self.server.uuid): """ don't react on our own MediaServer""" return db = self.shell.props.db group = rb.rb_source_group_get_by_name("shared") entry_type = db.entry_register_type("CoherenceUpnp:%s" % client.device.get_id()[5:]) from UpnpSource import UpnpSource source = gobject.new (UpnpSource, shell=self.shell, entry_type=entry_type, source_group=group, plugin=self, client=client, udn=udn) self.sources[udn] = source self.shell.append_source (source, None) def create_configure_dialog(self, dialog=None): if dialog is None: def store_config(dialog,port_spinner,interface_entry): port = port_spinner.get_value_as_int() self.config.set_int(gconf_keys['port'],port) interface = interface_entry.get_text() if len(interface) != 0: self.config.set_string(gconf_keys['interface'],interface) dialog.hide() dialog = gtk.Dialog(title='DLNA/UPnP Configuration', parent=None,flags=0,buttons=None) dialog.set_default_size(400,350) table = gtk.Table(rows=2, columns=2, homogeneous=True) dialog.vbox.pack_start(table, False, False, 0) label = gtk.Label("Port :") label.set_alignment(0,0.5) table.attach(label, 0, 1, 0, 1) value = 0 if self.config.get_int(gconf_keys['port']) != None: value = self.config.get_int(gconf_keys['port']) adj = gtk.Adjustment(value, 0, 65535, 1, 100, 0) port_spinner = gtk.SpinButton(adj, 0, 0) port_spinner.set_wrap(True) port_spinner.set_numeric(True) table.attach(port_spinner, 1, 2, 0, 1, xoptions=gtk.FILL|gtk.EXPAND,yoptions=gtk.FILL|gtk.EXPAND,xpadding=5,ypadding=5) label = gtk.Label("Interface :") label.set_alignment(0,0.5) table.attach(label, 0, 1, 1, 2) interface_entry = gtk.Entry() interface_entry.set_max_length(16) if self.config.get_string(gconf_keys['interface']) != None: interface_entry.set_text(self.config.get_string(gconf_keys['interface'])) else: interface_entry.set_text('') table.attach(interface_entry, 1, 2, 1, 2, xoptions=gtk.FILL|gtk.EXPAND,yoptions=gtk.FILL|gtk.EXPAND,xpadding=5,ypadding=5) button = gtk.Button(stock=gtk.STOCK_CANCEL) dialog.action_area.pack_start(button, True, True, 5) button.connect("clicked", lambda w: dialog.hide()) button = gtk.Button(stock=gtk.STOCK_OK) button.connect("clicked", lambda w: store_config(dialog,port_spinner,interface_entry)) dialog.action_area.pack_start(button, True, True, 5) dialog.show_all() dialog.present() return dialog Coherence-0.6.6.2/misc/Rhythmbox-Plugin/upnp_coherence/UpnpSource.py0000644000175000017500000001343311317660741023533 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # # Copyright 2007, James Livingston # Copyright 2007,2008 Frank Scholz import rb, rhythmdb import gobject, gtk from coherence import __version_info__ as coherence_version from coherence import log from coherence.upnp.core import DIDLLite class UpnpSource(rb.BrowserSource,log.Loggable): logCategory = 'rb_media_store' __gproperties__ = { 'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY), 'client': (gobject.TYPE_PYOBJECT, 'client', 'client', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY), 'udn': (gobject.TYPE_PYOBJECT, 'udn', 'udn', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY), } def __init__(self): rb.BrowserSource.__init__(self) self.__db = None self.__activated = False self.container_watch = [] if coherence_version < (0,5,1): self.process_media_server_browse = self.old_process_media_server_browse else: self.process_media_server_browse = self.new_process_media_server_browse def do_set_property(self, property, value): if property.name == 'plugin': self.__plugin = value elif property.name == 'client': self.__client = value self.props.name = self.__client.device.get_friendly_name() elif property.name == 'udn': self.__udn = value else: raise AttributeError, 'unknown property %s' % property.name def do_impl_activate(self): if not self.__activated: print "activating upnp source" self.__activated = True shell = self.get_property('shell') self.__db = shell.get_property('db') self.__entry_type = self.get_property('entry-type') # load upnp db self.load_db(0) self.__client.content_directory.subscribe_for_variable('ContainerUpdateIDs', self.state_variable_change) self.__client.content_directory.subscribe_for_variable('SystemUpdateID', self.state_variable_change) def load_db(self, id): d = self.__client.content_directory.browse(id, browse_flag='BrowseDirectChildren', process_result=False, backward_compatibility=False) d.addCallback(self.process_media_server_browse, self.__udn) def state_variable_change(self, variable, udn=None): self.info("%s changed from >%s< to >%s<", variable.name, variable.old_value, variable.value) if variable.old_value == '': return if variable.name == 'SystemUpdateID': self.load_db(0) elif variable.name == 'ContainerUpdateIDs': changes = variable.value.split(',') while len(changes) > 1: container = changes.pop(0).strip() update_id = changes.pop(0).strip() if container in self.container_watch: self.info("we have a change in %r, container needs a reload", container) self.load_db(container) def new_process_media_server_browse(self, results, udn): didl = DIDLLite.DIDLElement.fromString(results['Result']) for item in didl.getItems(): self.info("process_media_server_browse %r %r", item.id, item) if item.upnp_class.startswith('object.container'): self.load_db(item.id) if item.upnp_class.startswith('object.item.audioItem'): url = None duration = None size = None bitrate = None for res in item.res: remote_protocol,remote_network,remote_content_format,remote_flags = res.protocolInfo.split(':') self.info("%r %r %r %r",remote_protocol,remote_network,remote_content_format,remote_flags) if remote_protocol == 'http-get': url = res.data duration = res.duration size = res.size bitrate = res.bitrate break if url is not None: self.info("url %r %r",url,item.title) entry = self.__db.entry_lookup_by_location (url) if entry == None: entry = self.__db.entry_new(self.__entry_type, url) self.__db.set(entry, rhythmdb.PROP_TITLE, item.title) try: if item.artist is not None: self.__db.set(entry, rhythmdb.PROP_ARTIST, item.artist) except AttributeError: pass try: if item.album is not None: self.__db.set(entry, rhythmdb.PROP_ALBUM, item.album) except AttributeError: pass try: self.info("%r %r", item.title,item.originalTrackNumber) if item.originalTrackNumber is not None: self.__db.set(entry, rhythmdb.PROP_TRACK_NUMBER, int(item.originalTrackNumber)) except AttributeError: pass if duration is not None: h,m,s = duration.split(':') seconds = int(h)*3600 + int(m)*60 + float(s) self.info("%r %r:%r:%r %r" % (duration,h,m,s,seconds)) self.__db.set(entry, rhythmdb.PROP_DURATION, int(seconds)) if size is not None: self.__db.set(entry, rhythmdb.PROP_FILE_SIZE,int(size)) self.__db.commit() gobject.type_register(UpnpSource) Coherence-0.6.6.2/misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py0000644000175000017500000005526511317660741023635 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz import os.path import urllib from twisted.python import failure import rhythmdb from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import DIDLLite import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin from coherence import log TRACK_COUNT = 1000000 class RhythmboxPlayer(log.Loggable): """ a backend to the Rhythmbox """ logCategory = 'rb_media_renderer' implements = ['MediaRenderer'] vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'}, 'AVTransport': {'A_ARG_TYPE_SeekMode':('ABS_TIME','REL_TIME','TRACK_NR')}} vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}} def __init__(self, device, **kwargs): self.warning("__init__ RhythmboxPlayer %r", kwargs) self.shell = kwargs['shell'] self.server = device self.rb_mediaserver = kwargs['rb_mediaserver'] self.player = None self.entry = None self.metadata = None try: self.name = kwargs['name'] except KeyError: self.name = "Rhythmbox on %s" % self.server.coherence.hostname self.player = self.shell.get_player() louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) self.playing = False self.state = None self.duration = None self.volume = 1.0 self.muted_volume = None self.view = [] self.tags = {} def __repr__(self): return str(self.__class__).split('.')[-1] def volume_changed(self, player, parameter): self.volume = self.player.props.volume self.info('volume_changed to %r', self.volume) if self.volume > 0: rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.server.rendering_control_server.set_variable(rcs_id, 'Volume', self.volume*100) def playing_song_changed(self, player, entry): self.info("playing_song_changed %r", entry) if self.server != None: connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) if entry == None: self.update('STOPPED') self.playing = False #self.entry = None self.metadata = None self.duration = None else: id = self.shell.props.db.entry_get (entry, rhythmdb.PROP_ENTRY_ID) bitrate = self.shell.props.db.entry_get(entry, rhythmdb.PROP_BITRATE) * 1024 / 8 # Duration is in HH:MM:SS format seconds = self.shell.props.db.entry_get(entry, rhythmdb.PROP_DURATION) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 self.duration = "%02d:%02d:%02d" % (hours, minutes, seconds) mimetype = self.shell.props.db.entry_get(entry, rhythmdb.PROP_MIMETYPE) # This isn't a real mime-type if mimetype == "application/x-id3": mimetype = "audio/mpeg" size = self.shell.props.db.entry_get(entry, rhythmdb.PROP_FILE_SIZE) # create item item = DIDLLite.MusicTrack(id + TRACK_COUNT,'101') item.album = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ALBUM) item.artist = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ARTIST) item.genre = self.shell.props.db.entry_get(entry, rhythmdb.PROP_GENRE) item.originalTrackNumber = str(self.shell.props.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER)) item.title = self.shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE) # much nicer if it was entry.title cover = self.shell.props.db.entry_request_extra_metadata(entry, "rb:coverArt-uri") if cover != None: _,ext = os.path.splitext(cover) item.albumArtURI = ''.join((self.server.coherence.urlbase+str(self.rb_mediaserver.uuid)[5:]+'/'+ str(int(id) + TRACK_COUNT),'?cover',ext)) item.res = [] location = self.shell.props.db.entry_get(entry, rhythmdb.PROP_LOCATION) if location.startswith("file://"): location = unicode(urllib.unquote(location[len("file://"):])) uri = ''.join((self.server.coherence.urlbase+str(self.rb_mediaserver.uuid)[5:]+'/'+ str(int(id) + TRACK_COUNT))) res = DIDLLite.Resource(uri, 'http-get:*:%s:*' % mimetype) if size > 0: res.size = size if self.duration > 0: res.duration = self.duration if bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) # add internal resource res = DIDLLite.Resource('track-%d' % id, 'rhythmbox:%s:%s:*' % (self.server.coherence.hostname, mimetype)) if size > 0: res.size = size if self.duration > 0: res.duration = str(self.duration) if bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) elt = DIDLLite.DIDLElement() elt.addItem(item) self.metadata = elt.toString() self.entry = entry if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata) self.info("playing_song_changed %r", self.metadata) if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','PLAY,STOP,PAUSE,SEEK,NEXT,PREVIOUS') self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '00:00:00') self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '00:00:00') def playing_changed(self, player, state): self.info("playing_changed", state) if state is True: transport_state = 'PLAYING' else: if self.playing is False: transport_state = 'STOPPED' else: transport_state = 'PAUSED_PLAYBACK' self.update(transport_state) try: position = player.get_playing_time() except: position = None try: duration = player.get_playing_song_duration() except: duration = None self.update_position(position,duration) self.info("playing_changed %r %r ", position, duration) def elapsed_changed(self, player, time): self.info("elapsed_changed %r %r", player, time) try: duration = player.get_playing_song_duration() except: duration = None self.update_position(time,duration) def update(self, state): self.info("update %r", state) if state in ('STOPPED','READY'): transport_state = 'STOPPED' if state == 'PLAYING': transport_state = 'PLAYING' if state == 'PAUSED_PLAYBACK': transport_state = 'PAUSED_PLAYBACK' if self.state != transport_state: self.state = transport_state if self.server != None: connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.server.av_transport_server.set_variable(connection_id, 'TransportState', transport_state) def update_position(self, position,duration): self.info("update_position %r %r", position,duration) if self.server != None: connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack', 1) if position is not None: m,s = divmod( position, 60) h,m = divmod(m,60) if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '%02d:%02d:%02d' % (h,m,s)) self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '%02d:%02d:%02d' % (h,m,s)) if duration <= 0: duration = None if duration is not None: m,s = divmod( duration, 60) h,m = divmod(m,60) if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackDuration', '%02d:%02d:%02d' % (h,m,s)) self.server.av_transport_server.set_variable(connection_id, 'CurrentMediaDuration', '%02d:%02d:%02d' % (h,m,s)) if self.duration is None: if self.metadata is not None: self.info("update_position %r", self.metadata) elt = DIDLLite.DIDLElement.fromString(self.metadata) for item in elt: for res in item.findall('res'): res.attrib['duration'] = "%d:%02d:%02d" % (h,m,s) self.metadata = elt.toString() if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata) self.duration = duration def load( self, uri, metadata): self.info("player load %r %r", uri, metadata) #self.shell.load_uri(uri,play=False) self.duration = None self.metadata = metadata self.tags = {} was_playing = self.playing if was_playing == True: self.stop() if len(metadata)>0: elt = DIDLLite.DIDLElement.fromString(metadata) if elt.numItems() == 1: item = elt.getItems()[0] if uri.startswith('track-'): self.entry = self.shell.props.db.entry_lookup_by_id(int(uri[6:])) else: self.entry = self.shell.props.db.entry_lookup_by_location(uri) self.info("check for entry %r %r %r", self.entry,item.server_uuid,uri) if self.entry == None: if item.server_uuid is not None: entry_type = self.shell.props.db.entry_register_type("CoherenceUpnp:" + item.server_uuid) self.entry = self.shell.props.db.entry_new(entry_type, uri) self.info("create new entry %r", self.entry) else: entry_type = self.shell.props.db.entry_register_type("CoherencePlayer") self.entry = self.shell.props.db.entry_new(entry_type, uri) self.info("load and check for entry %r", self.entry) duration = None size = None bitrate = None for res in item.res: if res.data == uri: duration = res.duration size = res.size bitrate = res.bitrate break self.shell.props.db.set(self.entry, rhythmdb.PROP_TITLE, item.title) try: if item.artist is not None: self.shell.props.db.set(self.entry, rhythmdb.PROP_ARTIST, item.artist) except AttributeError: pass try: if item.album is not None: self.shell.props.db.set(self.entry, rhythmdb.PROP_ALBUM, item.album) except AttributeError: pass try: self.info("%r %r", item.title,item.originalTrackNumber) if item.originalTrackNumber is not None: self.shell.props.db.set(self.entry, rhythmdb.PROP_TRACK_NUMBER, int(item.originalTrackNumber)) except AttributeError: pass if duration is not None: h,m,s = duration.split(':') seconds = int(h)*3600 + int(m)*60 + int(s) self.info("%r %r:%r:%r %r", duration, h, m , s, seconds) self.shell.props.db.set(self.entry, rhythmdb.PROP_DURATION, seconds) if size is not None: self.shell.props.db.set(self.entry, rhythmdb.PROP_FILE_SIZE,int(size)) else: if uri.startswith('track-'): self.entry = self.shell.props.db.entry_lookup_by_id(int(uri[6:])) else: #self.shell.load_uri(uri,play=False) #self.entry = self.shell.props.db.entry_lookup_by_location(uri) entry_type = self.shell.props.db.entry_register_type("CoherencePlayer") self.entry = self.shell.props.db.entry_new(entry_type, uri) self.playing = False self.metadata = metadata connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','PLAY,STOP,PAUSE,SEEK,NEXT,PREVIOUS') self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',1) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',metadata) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',metadata) if was_playing == True: self.play() def start(self, uri): self.load(uri) self.play() def stop(self): self.info("player stop") self.player.stop() self.playing = False #self.server.av_transport_server.set_variable( \ # self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\ # 'TransportState', 'STOPPED') def play(self): self.info("player play") if self.playing == False: if self.entry: self.player.play_entry(self.entry) else: self.player.playpause() self.playing = True else: self.player.playpause() #self.server.av_transport_server.set_variable( \ # self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\ # 'TransportState', 'PLAYING') def pause(self): self.player.pause() #self.server.av_transport_server.set_variable( \ # self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\ # 'TransportState', 'PAUSED_PLAYBACK') def seek(self, location, old_state): """ @param location: +nL = relative seek forward n seconds -nL = relative seek backwards n seconds """ self.info("player seek %r", location) self.player.seek(location) self.server.av_transport_server.set_variable(0, 'TransportState', old_state) def mute(self): self.muted_volume = self.volume self.player.set_volume(0) rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'True') def unmute(self): if self.muted_volume is not None: self.player.set_volume(self.muted_volume) self.muted_volume = None self.player.set_mute(False) rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'False') def get_mute(self): return self.player.get_mute() def get_volume(self): self.volume = self.player.get_volume() self.info("get_volume %r", self.volume) return self.volume * 100 def set_volume(self, volume): self.info("set_volume %r", volume) volume = int(volume) if volume < 0: volume=0 if volume > 100: volume=100 self.player.set_volume(float(volume/100.0)) def upnp_init(self): self.player.connect ('playing-song-changed', self.playing_song_changed), self.player.connect ('playing-changed', self.playing_changed) self.player.connect ('elapsed-changed', self.elapsed_changed) self.player.connect("notify::volume", self.volume_changed) self.current_connection_id = None self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo', ['rhythmbox:%s:audio/mpeg:*' % self.server.coherence.hostname, 'http-get:*:audio/mpeg:*', 'rhythmbox:%s:application/ogg:*' % self.server.coherence.hostname, 'http-get:*:application/ogg:*', 'rhythmbox:%s:audio/ogg:*' % self.server.coherence.hostname, 'http-get:*:audio/ogg:*', 'rhythmbox:%s:audio/x-flac:*' % self.server.coherence.hostname, 'http-get:*:audio/x-flac:*', 'rhythmbox:%s:audio/flac:*' % self.server.coherence.hostname, 'http-get:*:audio/flac:*', 'rhythmbox:%s:audio/x-wav:*' % self.server.coherence.hostname, 'http-get:*:audio/x-wav:*', 'rhythmbox:%s:audio/L16;rate=44100;channels=2:*' % self.server.coherence.hostname, 'http-get:*:audio/L16;rate=44100;channels=2:*', 'rhythmbox:%s:audio/x-m4a:*' % self.server.coherence.hostname, 'http-get:*:audio/x-m4a:*'], default=True) self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True) self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True) self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True) self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True) self.server.rendering_control_server.set_variable(0, 'Volume', self.get_volume()) self.server.rendering_control_server.set_variable(0, 'Mute', self.get_mute()) def upnp_Play(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Speed = int(kwargs['Speed']) self.play() return {} def upnp_Previous(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.player.do_previous() return {} def upnp_Next(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.player.do_next() return {} def upnp_Pause(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.pause() return {} def upnp_Stop(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.stop() return {} def upnp_Seek(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Unit = kwargs['Unit'] Target = kwargs['Target'] if Unit in ['ABS_TIME','REL_TIME']: old_state = self.server.av_transport_server.get_variable('TransportState').value self.server.av_transport_server.set_variable(0, 'TransportState', 'TRANSITIONING') sign = 1 if Target[0] == '+': Target = Target[1:] if Target[0] == '-': Target = Target[1:] sign = -1 h,m,s = Target.split(':') seconds = int(h)*3600 + int(m)*60 + int(s) if Unit == 'ABS_TIME': position = self.player.get_playing_time() self.seek(seconds-position, old_state) elif Unit == 'REL_TIME': self.seek(seconds*sign, old_state) return {} def upnp_Next(self,*args,**kwargs): self.player.do_next() return {} def upnp_Previous(self,*args,**kwargs): self.player.do_previous() return {} def upnp_SetAVTransportURI(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) CurrentURI = kwargs['CurrentURI'] CurrentURIMetaData = kwargs['CurrentURIMetaData'] local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',') #print '>>>', local_protocol_infos if len(CurrentURIMetaData)==0: self.load(CurrentURI,CurrentURIMetaData) return {} else: elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData) #import pdb; pdb.set_trace() if elt.numItems() == 1: item = elt.getItems()[0] res = item.res.get_matching(local_protocol_infos, protocol_type='rhythmbox') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') self.load(res.data,CurrentURIMetaData) return {} return failure.Failure(errorCode(714)) def upnp_SetMute(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredMute = kwargs['DesiredMute'] if DesiredMute in ['TRUE', 'True', 'true', '1','Yes','yes']: self.mute() else: self.unmute() return {} def upnp_SetVolume(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredVolume = int(kwargs['DesiredVolume']) self.set_volume(DesiredVolume) return {} Coherence-0.6.6.2/misc/Rhythmbox-Plugin/coherence.rb-plugin.in0000644000175000017500000000074011317660741022237 0ustar devdev[RB Plugin] Loader=python Module=upnp_coherence IAge=1 _Name=DLNA/UPnP sharing and control support _Description=Adds support for playing media from and sending media to DLNA/UPnP network devices, and enables Rhythmbox to be controlled by a DLNA/UPnP ControlPoint Authors=James Livingston , Frank Scholz Copyright=Copyright © 2007,2008,2009 James Livingston & Frank Scholz Website=http://www.rhythmbox.org/ - http://coherence-project.org Coherence-0.6.6.2/misc/org.Coherence.service0000644000175000017500000000013711317660741016701 0ustar devdev[D-BUS Service] Name=org.Coherence Exec=/usr/bin/coherence -o use_dbus:yes -o controlpoint:yes Coherence-0.6.6.2/misc/Totem-Plugin/0000755000000000000000000000000011317673117015542 5ustar rootrootCoherence-0.6.6.2/misc/Totem-Plugin/upnp-coherence.totem-plugin0000644000175000017500000000042411317660741022442 0ustar devdev[Totem Plugin] Loader=python Module=upnp-coherence IAge=1 Name=Coherence DLNA/UPnP Client Description=A DLNA/UPnP client for Totem powered by Coherence Authors=Frank Scholz Copyright=Copyright © 2008 Frank Scholz Website=http://coherence.beebits.net/ Coherence-0.6.6.2/misc/Totem-Plugin/upnp-coherence.py0000644000175000017500000001141711317660741020452 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz import pygtk pygtk.require("2.0") import gtk from coherence.ui.av_widgets import TreeWidget from coherence.ui.av_widgets import UDN_COLUMN,UPNP_CLASS_COLUMN,SERVICE_COLUMN import totem class UPnPClient(totem.Plugin): def __init__ (self): totem.Plugin.__init__(self) self.ui = TreeWidget() self.ui.cb_item_right_click = self.button_pressed self.ui.window.show_all() selection = self.ui.treeview.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) def button_pressed(self, widget, event): if event.button == 3: x = int(event.x) y = int(event.y) try: row_path,column,_,_ = self.ui.treeview.get_path_at_pos(x, y) selection = self.ui.treeview.get_selection() if not selection.path_is_selected(row_path): self.ui.treeview.set_cursor(row_path,column,False) print "button_pressed", row_path, (row_path[0],) iter = self.ui.store.get_iter((row_path[0],)) udn, = self.ui.store.get(iter,UDN_COLUMN) iter = self.ui.store.get_iter(row_path) upnp_class,url = self.ui.store.get(iter,UPNP_CLASS_COLUMN,SERVICE_COLUMN) print udn, upnp_class, url if(not upnp_class.startswith('object.container') and not upnp_class == 'root'): self.create_item_context(has_delete=self.ui.device_has_action(udn,'ContentDirectory','DestroyObject')) self.context.popup(None,None,None,event.button,event.time) return 1 except TypeError: pass return 1 def create_item_context(self,has_delete=False): """ create context menu for right click in treeview item""" def action(menu, text): selection = self.ui.treeview.get_selection() model, selected_rows = selection.get_selected_rows() if text == 'item.delete': for row_path in selected_rows: self.ui.destroy_object(row_path) return if(len(selected_rows) > 0 and text ==' item.play'): row_path = selected_rows.pop(0) iter = self.ui.store.get_iter(row_path) url, = self.ui.store.get(iter,SERVICE_COLUMN) self.totem_object.action_remote(totem.REMOTE_COMMAND_REPLACE,url) self.totem_object.action_remote(totem.REMOTE_COMMAND_PLAY,url) for row_path in selected_rows: iter = self.ui.store.get_iter(row_path) url, = self.ui.store.get(iter,SERVICE_COLUMN) self.totem_object.action_remote(totem.REMOTE_COMMAND_ENQUEUE,url) self.totem_object.action_remote(totem.REMOTE_COMMAND_PLAY,url) if not hasattr(self, 'context_no_delete'): self.context_no_delete = gtk.Menu() play_menu = gtk.MenuItem("Play") play_menu.connect("activate", action, 'item.play') enqueue_menu = gtk.MenuItem("Enqueue") enqueue_menu.connect("activate", action, 'item.enqueue') self.context_no_delete.append(play_menu) self.context_no_delete.append(enqueue_menu) self.context_no_delete.show_all() if not hasattr(self, 'context_with_delete'): self.context_with_delete = gtk.Menu() play_menu = gtk.MenuItem("Play") play_menu.connect("activate", action, 'item.play') enqueue_menu = gtk.MenuItem("Enqueue") enqueue_menu.connect("activate", action, 'item.enqueue') self.context_with_delete.append(play_menu) self.context_with_delete.append(enqueue_menu) self.context_with_delete.append(gtk.SeparatorMenuItem()) menu = gtk.MenuItem("Delete") menu.connect("activate", action, 'item.delete') self.context_with_delete.append(menu) self.context_with_delete.show_all() if has_delete: self.context = self.context_with_delete else: self.context = self.context_no_delete def activate (self, totem_object): totem_object.add_sidebar_page ("upnp-coherence", _("Coherence DLNA/UPnP Client"), self.ui.window) self.totem_object = totem_object def load_and_play(url): totem_object.action_remote(totem.REMOTE_COMMAND_REPLACE,url) totem_object.action_remote(totem.REMOTE_COMMAND_PLAY,url) self.ui.cb_item_dbl_click = load_and_play def deactivate (self, totem_object): totem_object.remove_sidebar_page ("upnp-coherence")Coherence-0.6.6.2/misc/coherence-initscript.sh0000644000175000017500000000304111317660741017310 0ustar devdev#!/bin/bash # # /etc/rc.d/init.d/coherence # # Starts the coherence server # # chkconfig: 345 90 56 # description: An UPnP/DLNA MediaServer # processname: coherence # securlevel: 80 # ### BEGIN INIT INFO # Provides: coherence # Default-Start: 3 4 5 # Required-Start: $network messagebus # Required-Stop: $network messagebus # Short-Description: Starts the coherence server # Description: An UPnP/DLNA MediaServer ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions PROGNAME=coherence CONFIGFILE=/etc/coherence/coherence.conf LOGFILE=/var/log/coherence test -x /usr/bin/$PROGNAME || exit 0 RETVAL=0 # # See how we were called. # start() { # Check if it is already running if [ ! -f /var/lock/subsys/$PROGNAME ]; then gprintf "Starting %s daemon: " "$DAEMON" daemon python /usr/bin/$PROGNAME -d -c $CONFIGFILE -l $LOGFILE RETVAL=$? [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$PROGNAME echo fi return $RETVAL } stop() { gprintf "Stopping %s daemon: " "$DAEMON" killproc python /usr/bin/$PROGNAME RETVAL=$? [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$PROGNAME echo return $RETVAL } restart() { $0 stop $0 start } reload() { trap "" SIGHUP killall -HUP $PROGNAME } case "$1" in start) start ;; stop) stop ;; reload) reload ;; restart) restart ;; condrestart) if [ -f /var/lock/subsys/$PROGNAME ]; then restart fi ;; status) status $PROGNAME ;; *) INITNAME=`basename $0` gprintf "Usage: %s {start|stop|restart|condrestart|status}\n" "$INITNAME" exit 1 esac exit $RETVAL Coherence-0.6.6.2/misc/Desktop-Applet/0000755000000000000000000000000011317673117016052 5ustar rootrootCoherence-0.6.6.2/misc/Desktop-Applet/tango-system-file-manager.png0000644000175000017500000000074211317660741023161 0ustar devdev‰PNG  IHDRÄ´l;bKGDˆŠ… ¥©Õ pHYs × ×B(›xtIMEÖ ŽÀü*oIDAT8˵•=KÃP†Ÿ|X´E\ÔAœý#.®‚ BµP7qqÑEAqrÁ:‚àäæqth«Ô¦iîG]Úz“´šÖôÀI8'Ésrßû†ÀˆÂ2‹­âºeQÔj±q~ñxw ‡ÛáÓjßèí,›£³“Üþ¹Hi·¸ƒçÕÂG )›Ý¤þÜM)š¡Aƒ½|sµntúÓýþPšÎ/‡êø¡:E9È Í"½¸dX›« ¾~™Áo|„zvšsݱôÁ¶ãþ.E¿¥š‘D*·×FDA Ê9­$RŠþàÅé÷tô6‹ ð†¶Ûê¨ì¦´ÝzÝ4íÜ9Ø@@+™†Ý²ÀWü»1 x! 2Á+Ë›¡‡´RØŽƒV‘Íi÷lÇ`\ÔbzhÀ«¼êËÓÛ»‚ù½›aš¿_”«êø´e È9`pºF·Ï huÀ¤qˆù&´ÚµøûG¥´' Ù…IEND®B`‚Coherence-0.6.6.2/misc/Desktop-Applet/applet-coherence0000644000175000017500000000617111317660741020637 0ustar devdev#! /usr/bin/python # -*- coding: utf-8 -*- # Applet for Coherence # Copyright (C) 2008 Nicolas Lécureuil # Copyright (C) 2008 Helio Chissini de Castro # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # TODO : Add a configuration windows to allow to change name or plugins before launching # the server and save a configuration file in ~/.coherence # Use the name and the plugin given as commandline arguments # import time import os import subprocess import signal import sys import socket from pkg_resources import resource_filename icon = resource_filename(__name__, '../../misc/Desktop Applet/tango-system-file-manager.png') # this ../.. is evil, I know :-( # there must be a better way if not os.path.exists(icon): icon = "/usr/share/icons/coherence/tango-system-file-manager.png" from PyQt4.QtGui import * from PyQt4.QtCore import * from PyQt4 import QtCore proc = None host = None user = None confFile = None bool = False def startCoherence(): global proc global host global user global confFile global bool host = socket.gethostname() user = os.environ["USER"] if ( confFile ): if os.path.isfile( confFile ): proc = subprocess.Popen(["/usr/bin/coherence"]) else: proc = subprocess.Popen(["/usr/bin/coherence","--plugin=backend:FSStore,name:%s@%s" % ( user, host )]) startAction.setDisabled(1) stopAction.setDisabled(0) bool = True def stopCoherence(): global bool os.kill( proc.pid, signal.SIGTERM ) stopAction.setDisabled(1) startAction.setDisabled(0) bool = False def quitApplet(): if bool == True: stopCoherence() if __name__ == "__main__": app = QApplication(sys.argv) menu = QMenu() startAction = menu.addAction('Start Coherence Server') stopAction = menu.addAction('Stop Coherence Server') quitAction = menu.addAction('Quit') systrayIcon = QString(icon) icon = QIcon(systrayIcon) quitAction.connect( quitAction, SIGNAL("triggered()"), quitApplet) quitAction.connect( quitAction, SIGNAL("triggered()"), app, QtCore.SLOT("quit()")) startAction.connect( startAction, SIGNAL("triggered()"), startCoherence) stopAction.connect( stopAction, SIGNAL("triggered()"), stopCoherence) stopAction.setDisabled(1) tray = QSystemTrayIcon(icon) if (tray.isSystemTrayAvailable()): tray.setContextMenu(menu) tray.show() tray.setToolTip("Coherence control Applet") sys.exit(app.exec_()) Coherence-0.6.6.2/misc/Desktop-Applet/applet-coherence.10000644000175000017500000000131011317660741020764 0ustar devdev.TH "python-coherence-applet" "1" "0.6.2" "Frank Scholz" "Python UPnP Framework" .SH "NAME" .LP .LP Coherence \- Is a Python UPnP framework which enabling your application to participate in digital living networks, at the moment primarily the UPnP universe. Its goal is to relieve your application from all the membership and UPnP related tasks as much as possible. The core of Coherence provides a (hopefully complete) implementation of: * a SSDP server, * a MSEARCH client, * server and client for HTTP/SOAP requests, and * server and client for Event Subscription and Notification (GENA). .SH "OVERVIEW" .LP There are no command line options for applet\-coherence. .SH "SEE ALSO" .LP coherence Coherence-0.6.6.2/misc/Desktop-Applet/tango-system-file-manager-32x32.png0000644000175000017500000000123311317660741023734 0ustar devdev‰PNG  IHDR szzôsBIT|dˆRIDATX…í”ÍkQÅ™$ÚT0AФJºlì"KA”nüÖ¥€¥ àÎV]ˆ.\ºŠô_(]몈Up£‚ A\hëÂ……–šj¬ùèdæ½ëb&¦&Éd&Ð…x0ÃÜóî¹wî=ðÿ:bÝ.]Èß ž|”Œw í€ii Q·—>͆Rw±863sõl]™¿D´-Z5DÛ¦h«.ºQÕ¨ˆ2·DmÿUÿ!vmSìZIìê†Ø•uÙ.}–ë“ãõ‰â‰Û^9<Ëš(æïdß›[´6ÞSý²Ôs©£§0Òǘ¾s³¾úmóþÓgË 8nLŸ¼Ä\{K-Dò&Žœ$–>Îå©)ž¿øØ‘/áG6’)ÞÍ_ ÜÁ<…ÉÇž_}hÛàÃW›µ²î9õpÆ K ªR€²X/kN&»¯ÌðfÅ¢ÃW€á{‰Kìkb.Z…„àMì¡; }ˆ½Àïž@C¸^­tvçÌhÒãžš¿ ù?Ûç ?Týó¼\:ð7wG|„-pˆÙŒÁë]*nOÚÞ•áŒá ðž¥@(är~‘þ=ÍÖE5¢ðCè÷̈tߌ(¢F…ßvé@Ÿœ0´ø{2¢¨CÕˆ"¯a(Ôƒ„–ò>ÑPJ€òÒà(ÏO#¢@+D4¢ˆB´{D»ßšï ´vb›¼–€,Îà[Àw/q÷píÁBø²wǾöœ^þr2À]Ö5L7q§ý«€öÎö÷A€í&Ü*´LrïñOY¥ê*ìBIEND®B`‚Coherence-0.6.6.2/misc/EOG-Plugin/0000755000000000000000000000000011317673117015064 5ustar rootrootCoherence-0.6.6.2/misc/EOG-Plugin/upnp-coherence.eog-plugin0000644000175000017500000000042011317660741021402 0ustar devdev[Eog Plugin] Loader=python Module=upnp-coherence IAge=2 Name=Coherence DLNA/UPnP Client Description=A DLNA/UPnP client for EOG powered by Coherence Authors=Frank Scholz Copyright=Copyright © 2008 Frank Scholz Website=http://coherence.beebits.net/ Coherence-0.6.6.2/misc/EOG-Plugin/upnp-coherence.py0000644000175000017500000001051511317660741017772 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz import pygtk pygtk.require("2.0") import gtk from coherence.ui.av_widgets import TreeWidget from coherence.ui.av_widgets import UDN_COLUMN,UPNP_CLASS_COLUMN,SERVICE_COLUMN import eog class UPnPClient(eog.Plugin): def __init__ (self): eog.Plugin.__init__(self) def button_pressed(self, widget, event): if event.button == 3: x = int(event.x) y = int(event.y) try: row_path,column,_,_ = self.ui.treeview.get_path_at_pos(x, y) selection = self.ui.treeview.get_selection() if not selection.path_is_selected(row_path): self.ui.treeview.set_cursor(row_path,column,False) print "button_pressed", row_path, (row_path[0],) iter = self.ui.store.get_iter((row_path[0],)) udn, = self.ui.store.get(iter,UDN_COLUMN) iter = self.ui.store.get_iter(row_path) upnp_class,url = self.ui.store.get(iter,UPNP_CLASS_COLUMN,SERVICE_COLUMN) print udn, upnp_class, url if(not upnp_class.startswith('object.container') and not upnp_class == 'root'): self.create_item_context(has_delete=self.ui.device_has_action(udn,'ContentDirectory','DestroyObject')) self.context.popup(None,None,None,event.button,event.time) return 1 except TypeError: pass return 1 def create_item_context(self,has_delete=False): """ create context menu for right click in treeview item""" def action(menu, text): selection = self.ui.treeview.get_selection() model, selected_rows = selection.get_selected_rows() if text == 'item.delete': for row_path in selected_rows: self.ui.destroy_object(row_path) return if(len(selected_rows) > 0 and text ==' item.play'): row_path = selected_rows.pop(0) iter = self.ui.store.get_iter(row_path) url, = self.ui.store.get(iter,SERVICE_COLUMN) app = eog.eog_application_get_instance() app.open_uri_list((url,)) for row_path in selected_rows: iter = self.ui.store.get_iter(row_path) url, = self.ui.store.get(iter,SERVICE_COLUMN) app = eog.eog_application_get_instance() app.open_uri_list((url,)) if not hasattr(self, 'context_no_delete'): self.context_no_delete = gtk.Menu() play_menu = gtk.MenuItem("Play") play_menu.connect("activate", action, 'item.play') self.context_no_delete.append(play_menu) self.context_no_delete.show_all() if not hasattr(self, 'context_with_delete'): self.context_with_delete = gtk.Menu() play_menu = gtk.MenuItem("Display") play_menu.connect("activate", action, 'item.play') self.context_with_delete.append(play_menu) self.context_with_delete.append(gtk.SeparatorMenuItem()) menu = gtk.MenuItem("Delete") menu.connect("activate", action, 'item.delete') self.context_with_delete.append(menu) self.context_with_delete.show_all() if has_delete: self.context = self.context_with_delete else: self.context = self.context_no_delete def activate (self, window): self.eog_object = window print "activate", window self.ui = TreeWidget() self.ui.cb_item_right_click = self.button_pressed self.ui.window.show_all() selection = self.ui.treeview.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) sidebar = self.eog_object.get_sidebar() sidebar.add_page("Coherence DLNA/UPnP Client", self.ui.window) sidebar.show_all() def load_and_play(url): app = eog.eog_application_get_instance() app.open_uri_list((url,)) self.ui.cb_item_dbl_click = load_and_play def deactivate (self, window): #totem_object.remove_sidebar_page ("upnp-coherence") print "deactivate", window Coherence-0.6.6.2/misc/device-icons/0000755000000000000000000000000011317673117015566 5ustar rootrootCoherence-0.6.6.2/misc/device-icons/ampache-icon.png0000644000175000017500000000436311317660741020245 0ustar devdev‰PNG  IHDR*2˜¹ŸsRGB®ÎéPLTE!" $& +$($#/#0$'&)/(#7)-+.4,"?/536E3A5*F5#<87;9fŽ…LЋ®^SÔ åš ]ÒËÑ%šÍð•¢aŠï*SÓ|Pjh’2EØÆ½slÓO|:lçœß÷žóûýîïqÜÿ‚Öų…³[ê=Q‹éÌ¥=¸º²m«6žxpë÷²3jN êÔgT¹|wv‚†4ŒÆâ#§2Ë›ÑÛ}‚¦˜*Ê^—´ggS ?€¡xÏôr'Z!–ÕØëž›íaþ· ½ ¯˜q cÊ‚:„Ú#zÕØ¥-™³³á®XfáQÓ®ý éLІ¿ß ªDè%q¦eÛÜq¹£c×|-ö—™‘EG NRC Ô8ÔŒÐLÿëk.Ë2jÞ?¡W„*¿ºÐI’ il¼ú ¦ú—Ž58¯£&¿"›\(“:øg8÷0íDM½0ï7 †"u`èÈ”ôcœy±ŽGÕ¤^¥ÀpõTI½€3GBïP=©Ài†ÊÙº#¹†³çÍD$­¤@Ì„ýŠ/lª€Î:ã/„:{Çzš‹K¾ÿè Ç•Å#I5 2f¬»r~Ó%;D 0ˆ :(ÉE{V2ô°ˆi)Õð¥ÎAÇNÖÒ†Ã\,8X^Ϲ¡ó?“¥g3`yê®\+´õæ@è£ñÚpÃÆÖÕ;ÃÅ$B:«´écöBtáŠèè¼:(™¾Œap½ùœµ%k> …íú‰š ÈeòpÉÊ}× äºú.’úéÛ‚:Nö  Ñðkr‘(\~ƒ[_¸#¥ »PÎ:<ÇÕ$ÿn”¸TJ€³UP¾7F&ÿ²¹eAÚ\ˆIÐ9zhlÿéʽž•ÞipC«ß…Ü$HÍûbÇúŒØ‰ÿZ„L~ÝJ6‹PÛÉA#4¹Ž–¬[˜~¬2Õ7•ãž"T¡Ú|¶Ö- mÕ{'ª‘ž°jÛMÓ,_,Ì•óÚ¿g8W둱O6fQ5EŒOÊ+Ë jƒu+áfÉùP“ìkvm,Z¼¡Ô8M}»´@ÖF†ùfrrxÿþñ©G0Cé¡SgPQ+øØ„yÉJ’ù4 •:<в÷0ƒ¥Â5:|µÅç´¢C|°ƒ 7=«Ë–8‡~ÕÉ5úÞ! Cýâ¯<9aäÑ­•(›Œ« óê Nêê§9pz+¯ØmwG¥±„’N”ÖéÈ€W{ y÷œ’ä,Y7”=5•&”\€Q]0çG9 ÖW¹UBëùx Zh…LSû5¡~z䀉 .Ôv:ư>F¯'´‘R’n‹a>í;¢Qk.Ôš«B7Q¥Óvoß5PÔ1Tª”ú@´ƒ“–ZÜ üßͨ¾;·u^"áÜ«wô5^P…P+Ðx=Öe¥³tI qXço{ Õñ< Ô‘Õoe9+e?¤l§¬èóÕ(>Ø û;Qåäp¸ê°ÜáÙŒ.& ¸Ð…xAC$äw&k¬qH; k3å…¾eNž¥›ã½DÁªñ:TÎ3²s?};ªÑm<>ÖRJÓ¯{¨– fÆ©Õj’ÊÙo‘սРíÇÇåçŒVâÐ÷‡oýyy÷"B‘õËœn˜×û‹“->hM>ŒpÈÆ±6ëýB‘”òËþ‡¬/õXÖñôr>M©cïñ›¶…ÚÁ§Ý{Z˾܎mÖ[æk£Ö\o2äQñáûÿÙ¹U?§|¸ünÓŠ­}dm¥É7V=ú¼eÅ:Zÿ`[û6øîäpöçãHIEND®B`‚Coherence-0.6.6.2/misc/device-icons/youtube-icon.png0000644000175000017500000002473211317660741020345 0ustar devdev‰PNG  IHDRxA•s÷àsBITÛáOà IDATxœí|w˜]WuïZ{ŸvûôzÕ‘¬bË–lcŠ1Æ-ÀHˆ'Ä@(0å^¾˜„÷x”¼/,9‰än‚«(rA.êVõ2š^n;mïµÞçÞ;wîÌÈ8|þãm]I§ìú[k¯½ÖÚkTJÁ›5!0" 3‡Š!ºfß×EDàrQÐ!{(B¬<¬ªyìªü’„@'Φ1–ˆ °¬è¡a¡eG•¦d&dd@˜¤A0~W0þëR¢ÊŽÐ… ao¯wö +ÖÐ0÷ö0""±ìí±Îw3b¥)ŠãB/ºE˜tܬ™«nˆÙ2u[§R@ ÌŒÑë0ðæÏƒ–VR*"‹hl ZšF|þ|ÙØ,¦Ï°ÒɈfµÍ½99‘|·°k·~üñø/~i]èf€ˆmD”K !réfŽ®@Í m Y& ÖÀ‚ ‰DH¤„Ô†e¢b` C2!3 33 ¥˜}B­êr7Þl¿ÿ}Ö’¥†ã@i†”š~3ˆPöÜ’úîwÍÁa´mPÅxX=ý#”Æ¥Òø¸æñø,ˆÔØHB€e3+ˆLŠ…`Ó,†_ßè$clZ3%“Ú¶Q 5¡e€bht(/ô˜=½â³¿â©âÇïL}øa;P&9¼ .±¤Òç¾ûúïý ¥Y‘¸ƒLòl9’•¬ 0…Ðk €™%7ÖJ¶-d²í¨LF‚`FL‹ëd,.,i0èD’,“ C64‚!ÑvHn¨'ÛdišÏ<›Ü²Y*ûÅ»“ø O2PI8½Ù€DD‘ûÍóÎçî6OŸfÇ€e¾ŒXk¹*˜.Žíøb¢D¡5™¥3,%86s¨8æP2…À!à ÚÚ Û±À:™4È2Dc Ëb!U]†LŽ«ßò 8{®ð­o¦Þÿ‡Èe µÖ5Íóx‰6~–F9€q2ÿ;'Dd•õîùJê‡Ð²A­ €ˆyl–Y¸ÄÃÌ Ri˜HÄ5+¥ÿã¢ÖšH#!BH2 ®Ë ˆÅ@kP!Ä⪾^1bYÃÌÄì0ž°M‘)‘"`Û˜Iƒ0„m³ÜÒ*÷ïKûÝu—[?ú©‘ˆGmPt]ß÷,Ëbf"NÄãRÊÊzDäzn4L¥•cÙ¶cÿ~ñ­¾öwì¶^ÙB€FýþPÇIÜòòÇ‘ÀP ê„XbL4#B™s#Ž/3â…ÀëUš$s»”m©”ÐýCÂ6´Öhs äè(ÅœŒÓ†!r#CN,ßÜŠ¦™(d š™@ȤT*å­è´ÊoûeÝ 7D1`Ͼ½>ô )‹Å‡þøCW^qeeðJ…=üЮݻˆˆˆš2™ßõIÛv^OþgPŽ’8wÆè)IÏšÕwÍ5”Î`Fĉ"eX`îìÙüO¤ ¼rŽË‹Ñ}:+àÁ•« ë×i ¥ ¯íå—_DJÉ¡!¶m°l •QÖñ˜°·B=48"æÁ4â…¢fœÏs2‰LhÛAçŠØ®ÝÆŽðî÷D  Âð•í/wírb±\>×ÒÒ¶zåê˜c`ô<ÿ_~òÓý¯íGH¼vÍ*Ë´¦ÐÊ(Ž‹|‘‡Q“\°põg>“jn(ÃÆ3#fwîòŸ!™ÍF"&ˆÁêÈ¿êªeŸû¬iZ<ú裸ýe0%2# cJ×§¸#œ96†¡,ºÆÉ0´†X 0â âºhªhDÖšc1a[ ¤ŒŒ.`0`õªÕ«×¬9yêT:6L£ëèÑsçÎ/\° Ù\v`` ™J )LÓ¼æ]ï2Mëw‡xªDDº·”*…ÈŠÐÌÌS k],’Ö€P+£§jËuµ¦E¡R…"0C¤˜WZ‘Bp]ˆÇѲ9î ïù¢Ö!H£!,rù@ÓH:™tl£àjÒ!Çb88H¤P,€9•L._љɤ•V†e9~ldp€@k½í¹mD$¥`"xǵo·L¦`Æ7”jk@DÄÀuÃá!0d Ž×eœLš¡" ÊÂ#ú‘Fâ×L'ãŽÌ85ÊåòÕWÞ¥ûR&QîI‰¾/F‡Åð€mŠ¢+FF!›Ãl i ™z°w ë{¢X¹º¢Pù¼;šED`6¢•dùòåóçÏ?tè°í8}½ÇOžX³ör)„ÒzçÎa€bþ‚--ÍQÉJ¿§žž¿¸ã îï7†@Ȩcd`Ó‘#ê›ßôm€8øà%—u )0ìëÕßù_BF`khÀð½ˆ+'¨OµM—ñ­º­ÎÉ d€àùbd¤¤tšm[]`Ò(°>•ÔþS,r<–­««RÉ”,ÔÀ642€!Õ¹¬sÁ‚ŽÝ»öÄcN`çÞ½ïxÇuMMA)‹¤) Ãdf¨ü€´Š ³l‡ÅŸ"2·P‚Q81;îØ¤©”“ËÙlËò}YˆºDZš)ð…fЄBš) 4F–½wÌ|ùå—ÏŸ?ÿþýŽã:pðÜùó ¼òÊ«¡RBžÞ>}Á¼yRˆ2g€ç{»÷ìéëëàd2¹jåªúºúq¬„Ø7П/€™ÓétC]="†axöìY‚€ )[ššÇ) ¸»ÛºÐQ+ ˆ£:¡Ì•“'ÖTx*S/5Êö0hŽÆ©èB<6,MIL‚;¼À)˃ à Dåg²ÙâÂ…rÞ\ÈððÁÖ¡á†ú®¶éOõôegÎ4:D}Ëöôȇ¦7ÖCk+z®dd€cË4úÄñ¼á-Xí&]ºhÉìÙsvìØÇsnîøñ®«7\uðàÁÀ¢ÕcÉÒE1'VÄ»v¼úÏÿôÃçÎAa:cúôüá®{×u–iA)mú·M¿Üú+ÒšˆÞsã¾í¶d"yþB÷Çÿü†aA0cÆŒ¯ík³fÎ*õ#Ÿ“…™ÚÈÀËóYј¿@”y-’È!`îúZ>ôG‚š›ÿþu3$œÑ˜#?ó™æÎNAL:Ä/~ÏžgCr™­€ç»½ñ¸ñÅϵ^sÓÐDAèîÙ¥þá‚3g­Xœ‰˜UÞuWëïqš›%£ÃÞ˯œý鯦sgí™3<_† }O†¾F`@4*6 ¹tÙÒçžkö}ß¶íýû÷ ;v,Ë4ñºë®‹Åbû¿òÊËwþó}=½,…@ÁÀÌ|áÂ…ƒ‡õ Ü~ÛmRÈ‘Á¾þ®£Gƒ ¢µ—_NÄàûþ‘#G¤Aà‡a ”ªð)J Ò`Àò²?fʽNb¦™ê˜›^{90‚zþ9Ö ´¶ ë’U±5«€=OÙ1f +EÉ÷¹©%õßn¹î]¦cG„ËÌ™;Ð?àþý½&3°ƒï}úÓ³îü˜LUpCž£:—t.ëý—æ\èËäú:`AHR°4ÄX7®\¿~Îܹa¦qhÏží/uòÞ¿ý湞 fÜ1MS“&"Ã0lÛÞ¼eó®Ý»*¸„¡²L3‹ÅãqË2¢Þ –e9ñ˜‹Y¶Uöæóè"ws$ò±âFŠ\qtŒMçÒ_‰hDа` 0,‰}!„VX² HAi#¤Ô®ˆ9­W_Óþž›$ ­T‰¸R$ÞöNcõ*|”è «?ú`Ój'ÓÀÈš(XiFÒh¹b}úK_ðF‡Ñ-о^1šc)Ë 0V€f ¢Å‹Ïš93CÊ O<õT>Ÿ—RÀš5k’É3‡*xeÇ«¯>’L$T¨ïºë®»ï¾{þüùžç¥Óé#GŽlé¥ “ÆbŽ"BM+U’egféWÑpxDŒŒ¢SH"K%Zù§T%Çø2%¨š$Ñ{ЬCÉBH6DñáÍ£ŸùôÙûï§Ð€,š›t[+j ŠdûôÌ;Þi'“ÀZ>ýDñK_ýéOt!ÀBˆøªÕýk×"‘QÌãhVJŽ GíÉhD4¤ìèè¨k¨WAÒØ±k§Ö$„`æë¯¿>™H"@±PØ÷Ú~0Lf–†|ë5×ÜqǦa$“É{ïý{ß„;wî<}æôüyóÁ÷|"F!pL%kJ/Û Lš‰Em‡%ŽžT—„’º[Kž’4 ÂbŠ*&€Â‹/&ïù†ãÌŠóæ¥6\Í„  ‘´ê›3Ûim0Cîä1û¿;v\?ùt!“IÞt Z¦“©K_óÚ»O’%¹÷÷G½k˜Ö¯_?sz{*iêJ/;—uZ–ˆ·ØÕuÔ”‚Õ×ׯZµ2O8¶³`þ¼éÓ§+¥,ËêïïŽ ”0Sæ«T=æqàaÉYÊ8ö+YÕà­¹~c¦*F # köö`.+@ààà`WWÄv:eµ¶1(_¶41k+ý‹æ…^Fnwî ß°lšÖ*¢²€ZGL$jZï\º¬µ¥exh( ÂB>¯•ÎŽŽ.[¶¬¹¹9‚I+•ÍfµÖ##ÃQʒصm\×õ=ÿBww±X, GÈŽŒ²¦ŠY‹€D”Ëæ”R…bqRͺS¾ø¶ÔT`ÖÔ3…iñh „­Âúá¡È}‚e< DÈ€–%„í Ïcd`Á(ÌãÇ9Jõ@WIªÊ‚R³ Î`ÛÎì¶iqËÍ @f˜³rÕÊHß(a¤T.›UZg³9@``Â)%Ýb]&]™ 0—Íj­G³£Ä\k* ~ÌqŠ®;N§½HšÊ©ÀX+$8"Vn«ò”ALÇr6Á`030#c$z™™¨Ôœ! !a b6ç6im C¦/fâªýˆÊšãF€Î5kž|êü¹ó–mQ¡Xx۵צ’ÉRBf’I¥BÏs±ä ¾ïÝ¢)Í/°Ö”ÉdF³Yß÷Ë’‰Y¢•òí)­¡:H‡Xií…¾‹˜ !iS¡V–4üÀ¯Ö"!$M(5è0 a„JUæ1U(´*=Q±)•¦ ê¦?ýÀè“JA™ÙÖ¨…¤#b´hG´å7O»û ¶ÐÛ»Û¿áF%Ñ’†qÛ.KRž\ëÐZk­ƒ ð<&Y¡D·P´lÛsÝRƒVÊ-ÏÀgDišQføa(MAà3é±-#MÅbQx~4?J²6ó C1+Ö •B@W…¢ÊÖf`"ö•v³†H¤HÇ,»<´*_`ܰ"áÂÀEReQ…>ñÒjÆH¤ƒP¶Q`Ì”…9‡aH„ÀÄD%qdHWk`pµ.Š1MBJ­Tnÿ~[+”–§58•ˆ†I8‚ Òð}o¢%f˜f¦¾Ž˜]ו†ìîîŽÈÓß×wþÜy­5iÝÒÒ’J¥¢çA¡(…È]lÛ¶iš¤i箞ç€ïûU„ÓÊdøìy0˜  DÅl ™PiL€¨5C.ð ! Q3¹ÅB ˜-L ½‚Òº+䜙aFk<0(&‰bHŠ +ÅLÂvØ÷}dÊ¤í… )åóà°B ð†† #Ã"­Ä⥮Ò•ÛÒ d­™ØˆÅXÍl ÐL†“ Æ¦ˆ¾5&8—ô<×´Ê2Uh§Ré•—¬úùcOHCrùÇ{¼£cQ:~ðÁ‡z{ûLÓt•š?~{{{es &b„ƒûîÙ»ûÀ?üþT"Šj›¸$à¥Ð]R’MbÀÄ$Q(` ´¶CƒBi2+V¤ Fh­<ÏV8g&¬X.Y\÷黬æV×óÀ@ ‰5±E,iø€ÂX¾"¼áúðÌ)cåêÌ¥—¹¾'u¾¨\—‰Ð6Ãý3gcÍ­aà+–«w¾­ðÒËÆôéïºN ¡½@«0úLL³M­-Ë©ôd¾Ž*Ä£Ëá±\Òõ O¼uÃÕÿ'ñ\±( ã¥ßlÿÔ§>™Édî? ¥ÔZÛ¶}Ù¥—654–p6 ŒB»…xæ™gúúûöíÝW(Û©1OJÒ£©Ùk¨g­3ëh™BÌ>0ˆ’|B Dð 0¢(†¾*d›QP¿áX¶ÜéX  #Ìå¥cGë·" €G[Ü(™ÈZ¼ØþÁ?…‡‰é3 ¥E>˜¦ô}*€Ò<|48ô]z™!M@Î|ù«C‡efÍ1[[Yia ßÓ/¶<H–ZkTTH¦ÒsfGöHÆZ££• æ ÓEÊøâÅ‹n¿ýOÂ0 Cešæù³ç¼¶ß4M" ‚à†Þý¶k¯­ÔÑ:c†iÛ‘)Dš¶¿¸Ý0Œ|ô£BŠIReS#54"‘ `d1TÅbø¾ïùa*…ÄÙ½(Áu©·G33ëBaÔLĬK/Sšò?þ‘:qB!{¾çk™´*ðU¨UXØ»'èëã5—餗Ëú^ ó]GøÀA´-@æOî vlצáù>&âuk.¥º:C…ä‹£÷ýÔ9qÑ,;Áш9‰ú†h”ãF€P©|>¯”ò<7Ãl6KeÅ#²]›?ýù/Üùñ?knmv]WkMD®ëÆâñ?þÐùž{æÎ©Cˆø®w]?­½=Ïó‚ ˜9­í wþ“Ÿü$0xž†¡[,Fê2³a;V}”Co 8R †Ð0•e“ãhË ÓÑÜCKëhWÿîÝäÄÙŠ…†9thä«_áûþ-ÈçȲuÌa!HѰ9– ÓaÛ!Óò÷ìÎþsÃÛ~¥ÑP–£±l÷¹‘ŸnÔgÎËB@´¹ÿ€ÿ×÷ôþë¦Psh˜>³–BYfþäéÁ{ï•?ú±oJR "C*e&S‘0˜Dt´µµ}êSŸêïí3 ƒ˜W¬X‘L$Ê(—,—i-­÷·ßºzƯ¾208€ˆ‰xâ’•«Þsà m­mÕ–IçÒe÷|åž§Ÿ}:ð½¦ÆÆË×\zó-ïÍò÷|õfФÛÛÛ+õ Ä “Q(Ak!DHÚávPº*°¥(nº^xÎÓ:nZÞè°#ÁÕÚ’2Ôʲluü„þÚ=Þ[ÞOf‚þžâö—×ö)DøÑ?>ö°E :}.‚ÀÏ<]¸Ðí“rPx]]ñ;ÝÁ!ãÊu4svxÁŽ]ö¶m¨‰QéDRïÞ-†††·ýÚ^²Ì°m îëíI:Ì/¿dhra*°…ô¬¦F!ÊŠRMÄÿ¤ÁGe]ëcDD­õèè("& ˲*ùÇÙ òù Òét´YÓbuýˆxæ¾ûFïùŠ‘Ë‚ii!1 !•Ö–a¡`L–ÀP&Z“”RI¬C¡I™¦ŒC¶ø®ˆÅ4³áyšÈTÀf<Bh·hhRÀ %ЈÅU±hRÖ7n‘Ü¢0L’$ ±–(4³¡I¹ž™I£i³Ÿ1‰”! ÃÒZ !´Ö&C`Î_}rþÿ:²lk9z*LkÌçÊ­¢¾¾¾æaõudF'‰D"QÊ41g%Õ-Xà·µ¹ƒƒ( @ Ò€¨µBÄ€âq—Az@ˆ ‰P‘FDM„ÐBiw4ËE#Âí^èšÓoôÄJMUP4OqÖ""mé¥ß¼ùÛo"Ÿ´*(sI‡/ÊLD~àÛ¦åŠsçνvàÀUW^Y—É@6—{ùÕWêëW¯\…c.ȱթºÃšUnËÚÅX©š!3ó‹Û·oÿÍ‹¹ã£MMMÛ_Úþè£Ñg?ûY·X|âÉ'ëëë3™L:¶,³«ëØí·Ý>¥ÍÌû÷ïß¶m›ÖZ)õØcýø'ÿR(jŪTs;n‹vÂõÄ™TSCY“×Jt›ÏçŸ|ê)?ð+»¨ °{÷®í/m:¶k÷îßüæ71Ç)u£YÏ@+cg”–oKmU,“ÉúÉDžëÀàÀÀÆûï¿ù替ò寴45OoŸ¾nݺåË—3óÑ£G{{{UâÅ?õCDñxü-W_ÝÒÜrùÚµ_ÿß8¡{îœ9ýýÍMM}}}mmm†a  1qccCcC#¸®;84Tt‹RÊöií1Çîëí͵ŸJë †IDATf-Û"¢ lZëž¾Þ\.oFKK‹mÛ…B¶¶¶&Ɉ*ÝÝݾï74481gçî]O<ùäÜ9s[[[ÛZ[`ÖÌ™k×®ýů~uÕú+¥”»wíœ9}Æ¢E‹]×íïï÷|?•Jµ47K)Ã0ìíímii±,Kk=4<ä8N"žÈòBHÏó ±±qhhh`` žˆ775Û¶MD½ý}¹\.•H657 €hÙ¶Öúh×Q&ªËÔI),Ë Â :-XWW×ÕÕuìX×»¯7È{î¹g* O>}äÈ‘ÎeË„!OŸ>}êÔÉk¯yëÐÐðW¿þ5;æüìçÿ±dÉ’žžž›6nÝúìsÏo;yêtkk[cCá#‡xèÁ­[·>÷ Bâ¬Y³¥»vïÚ¸iÓÖ­[>\(†¯{ç;`çî7Ýÿ̳Ïìß¿¿¥¥Å0Œ-l~ú™gžá…¢ëÎ3Ç4Íóâ}›6þê׿b ˲¶lÙ2<<|øÈ‘‘‘‘Ë—K)щ9§NžÍf=Ï;ÒÕuë-·ñÓO?õÈcýzÛ¯÷íÛ—J¥Z[[ÏuŸÿÆ7¿±bÅŠ†ú†Ñìèÿé~Nooò™§÷½öÚK¯¼<44ØÔÜ´ñþM<þȹ³gfÍš]__¿oÿk=ôгÏ>»gïžææææ––3gΜãáÇÝxÿ¦††ÆX<&¥X’™‘¯YJY(÷ïÛwíµ×n¸jó[Ÿ½ÐÝýí{ÿÁÂ2­§N>ñä“W®[·ö²µ=úÈ#=6­mšaH!Ĝٳïøð‡¿÷ýïÿÍ—þº±¾ARJ¹bùŠHoYºx‘6¤ÁÑ«1K)›››fÍœ1wÎìíÛ_ê ¢T2qÓM7ÎhŸÞÝÝËeoÿ“?ɤÓ-ÍÍpëÍ|üø±X,ÖÕÕõäÓO:}Ú-ðĉ†iÞxà ©TjÆô7½ç= @Ì{öí«oh¼zÃÕñX,ÇãqÛ¶ÏwŸú™g:T,ýÀ—BΜ9sï¾}û°,!Ò1”!EµR€Ë:››[zzzÞñŽwôôöž=wfÃÕ.èˆÇãïßû‰Ä‰“'HS•ÜÅè|j$(§µ·¿ýmoK§RõõuJ‡ÛžÛV(mÛ~ñÅ …¢Ø÷Ú¾¶¶–cÇŽöôö ª&¥C!PÑGN"4¤´L+ªÿ¢ŸccŽÅb—]º¶½}Úú+ÖßýÅ/ìØ¹£³³S &“IfV* U˜H$¢ìŽã¸n1—ÏÿzÛ¶Çöø¢E‹æÍ›çØ6û¾ßÚÖ æÈý¯µ* MÑmÔ¡í/½ôàÃÍš9³³³3™H‘mÛw~ìc<òÈæ-›OžýÄSOF~ý™³f×Õ7Œf³RJ€È<©V˜ÆñÕX¾ÎwïˆHJ‰(F³YÃ4c±3”œuuu¶{õÕW¯{ç;™ù•¯j­6mú×kÞúÖ?¸å–S§O¢Öº¥µ¥··÷ܹ³ ~œ8qMì««ÛºõÙžÞžémÓ X,nù¥ üÅ'þ|phðçO<ÁÄZëL:ó‘;>òêÎW¿óכּb½B+­5M4˜K¡7õõõxèðáËÖ\æÄb{öìÉçòííí©d‚ˆ†††æÍ™›Ëesù\àû‘:¯”b†h ô-®^yÉ%ßú»¿Ûöüs vüÙÖØØ5†ˆ]]G•VåC`“|İFÙè(“Ò:›Íþâ—¿ˆÇãÝÝÝm­­Ë;—Œ »žGš`öìÙkV¯~ú™g|ß/ {vïݰ᪅ Íž=ûÈ‘#O<ù¤Ö:_(a¸tÁ‚E[xàÄÉ“ÌÜÓÛã‹Ä¼ö²ËöíÛ³yóæ¹sæZ¶5o»vïúŸÿ‡iš¹l–˜ ů·m€3gN_ºfM&6¤LÄ?âç+–/_yÉÊÊö3û¾ïû>07Ô7\ºúÒ_üò›ØR—©Û¹k窕«::&‰ŽŽŽÇö³îîî|>„¡í8Äìû¾Ò´Ö{^ÛwæÌ­T:•š?oÞ’ÅKN>¹å-óçÏ€†ú†•—\¢µ‰µÒ®ëFßû¹Hº˜Öª°P(t_èîè[¸°ã½·¾·©±Q+](º«V®L&¦avttض³ÿÀáá‘k¯½öí×¾Ý4Í9sæœ=wþÄÉ“—_¶6‘L,Y¼¤¥©yÅŠ½ý½Çu CÜzó-A®ºdesSsçòÎþþ×ì÷|é’%+W\24sæìÙs .\¿~]&S×ÒÜây^&“îèèBôööîÙ½{hxøª+¯\wùñXlþ¼y=}}‡jnnš5k–RJ .Y²TH¨pͪÕÖE¿s1¼Æ®›ÔMQc×M¼hq]üùIJP%ïjna XMÁš&ªK½nW'Å¡šTyøúß&­0Œ_&vbâø+¶V (•ëIÇ\y>±æšð"éusÖ4=‘üÝ5&îD~ªi¢B‰7öØšŠj°˜”û.R°ºgážÈ¼[„É(TaR°.Þ™ê·I5)m&v`bš\tü–,óÿÓo™j9úujêTC˜×³Q“thbUiå·ïÏEóLdÛIºWÍèS·;eú6VÞ=)T®IEND®B`‚Coherence-0.6.6.2/misc/device-icons/coherence-icon.png0000644000175000017500000000662511317660741020605 0ustar devdev‰PNG  IHDRxjwz•BsBIT|dˆ pHYsÈÈ'SsÿtEXtSoftwarewww.inkscape.org›î< IDATxœíytTÕÇ?o2Y™„L[­Ä€Œ €ÑÚÖEŠ€¶µ¶ÖVO5=­Ç¶¶ñx°§›­¶à¸µu¥ZOÛQ\ Öž¢B•°Ù$@FV³‡†@ȾLÈÜþ1 Æ$“ÌdÞ:“Ï9ïœäû~÷7óû»÷wß»ïJ¿¿uÁê«_+¼ÄÒÍAÀ”e!ÚÃôXÿ=á{žÚT?}éÇrÓ*{R{Ïû‹$©Œ€àýRY¿^ÎŽ>V_{ªõо›âyÓm÷HëÉ Üí p~ïqS¿òE’t€ÂËBx5÷|Œ°xΟvnÎ;ÖâûI>vuâ©Ëó›2“†l¨ÉÀ‚Þã,E’Tϧá½Oøca^¤wýc3pÝø)å—|õÏéñ­¢3wUsÒÔ÷»"±íÊùTð>ñǼ†XûþhúdFzCål2Î;D·MJúàOã™ùF‡˜¿¦UŠë£±mfö7ö;ßQ$I}ºþaþøè?ÆÁ8Û‚’ÒNùÞö‡ÉÒs¶€­¦ÇÙý͖̺Õö¥/Ì÷?ÈB´©]q4ó²®øÓ.yÿ³…z`îsm\ø|’¶ÁUóýûö¾0ß3Ü…c°D!mRó¾þG?ÄðH®÷³èÍLÞiÊtJ/:Ì·„c$df_ëæœ ŠÂuô,³^éà’ÇZ‰ë2U:e$PÅà¹ù£ÁÂüˆƒ¬þ”ï\Fæ qñ£k‰G¿™ÌñEñ\¶ªû!Ó§Sz çõËûï*’¤Ã xèB¢ÖŽõ®ÖñÔ]‘‡Í3¬lqgpðÎDXµ1 ‰ÀÅÀ·5À&àX‘$ý-쯸¦øJ:[ìyã·ÂþÙxïE;mSâ"²5FP<ÀÝa ìï±R^°|ä‚!P??žModPqC’"öÆ8Ë àYˆ–QÉúÒ‹iªÍRÄ“n›Äî‡Ò(xt<¾ñc1[º€e!ª ðÜÔ¨(ݾ„¤˜W5‹Ù¸!ƒã¹ ŠÙŒQî’…ØÙ÷Ϩn­ŸJÝÁEʸÔKÇD ÛžM§ø~=‰ÊýxbˆßËBü½ÿ‰ˆbbÅ®¥ôøî?%8rk o¯³s:;¬,.Öù'ðó'#¸»ÃFÕÿGb"(MYV¶¸íºc, }Àm²ƒf"þêŽíÿ"#53$þx‰}÷ÚØú;í“ÇÒ© '0bò¹³ˆ=q”m_©™a9± žë3¨\6–N  X! q,XE‚ß©Ê9œ®¾@ SAé¶Iìz8kÒð¥ Àz¹CâÃá (Ö»•n_‰ð«ßYV/IbÓúL¼‹b>zPbÝH…S¤½ÁÁ'žË•27|] [ŸO§ø>þ„˜lͯ¿¥ ¢M®rϺ;Ç)i28¹-…Íëì4Ί©tª¸}¨óP(*ð™®d*÷„tkY1šfZyûe;‡¿›¸™ÝÔT…¼¬DñN³Îsm§&+mvXü {fcëÚtÚ'Em:Õ,÷QÅ¢zÚ ïÂ6­Ï êú¨K§ðYˆâp/TeØ{ºf'ËçªazD|©;W§±su¾Ô¨‰Ù¿…X?š UËkÊ V zô —U×'±i}Þ…¦O§Ü²öbÕîhÊäØþ/©e>$Ú'űum:{jÃoÊÖ¼ø^$T™¨úßb|í©#T ßžÂÛ¯ØišiªtªX) Ñ›pT¸Ç—HÅ®¥jV2³¬l^gçÈm¦H§Z Ü@ˆøýcªÏ-?´–ÓÔ®&$ü Å÷ÙØú|:çö¤¸U"øºÝ0PÿS ‰²n¹œ†x%°qC&ÕK ™N­’…xK)cšüŒ›ê¦sâ輑 jˆ/MbÇš4v=œF·Í01û%Yˆ5JÔ,N•ï¸ÿ™x­ª ™ÊeIlz#ƒ²î¾ßWÚ¨fwµ¦SS|•VÕ…EÛ”8¶¾`gß½º¥SuU|už¦#ê¢+éjMײÊ8tG [Üvš²4M§š Œ˜Oªa\Sýg(ß±LË*Ãæt¶•·×Ù9zk²éTp‹,ĵ*ÐMuÓµ®6,z%ŠîOeÛ³étLTõ+Ê—…ؤfº$ƒ´É0#× ÏM`ㆠj'ªa~­,Äjî.·œ˜ÆñC9zT6¾ñ Ïîß)šNmîVÊØpè6S±ó+ôøTiªP±3b:õ”,Äð¯îU Ý(Û¾aâ%„u—ö«¹fÈAã;À5vé,†øVÛNM¦î£\½Ýˆˆ®t gσit;›N&ÐïêöÒNC P¹{ gºBîÏ KùÊ$6¿žA½ß7Çܨ§?†¸»s•{–èí†"´N‹ãÝí¶WJι=ßãÐõA0à ð‰çrÚz»¡ ¯TØ•ïq¨»¶v %°ðë·*BEÅùÇô¨ÜP4TgsªrŽÞn(M ðt¾ÇñŸ|CÓe8ʶ¯@ø£rÙRÀ“ïqh¦ )pGãDj÷_¡·j1x3ßãX›ïqØÔ®ÌT}x-ݪ~=¹Ø—ïq\ªf%†øŒ/É0«"T$ (È÷8~£V:eXê.¢µ~ªÞn¨MðK`G¾Ç¡ø¤¼¡FH—žÆyù‡¢Ï2~¹]Smõ¥3qæ~½]Q‹&àiàI—Óñb³^`èÝboú·Ø3uÀãÀs.§wä]ÇF‰)îl±SS|%ŸÏÑü~¹| <üÍåôF´ö7L!0@MQ`‹½D[“Þ®Œ–B`5°Áåôj¶K˜iîéN bç2²¯uëíJ¸¼ üÁåô¾§Gå¦À{d>S.* mR•Þ®Œ„X¬v9½£ßILŒ& B¢ÔØ«"º€µ@¶Ëé½YoqÁd- Å{.ÞÃ2ŽìB½]éO3ð,ð„Ëé5Ôñ¦[ìMÈ*õ{ âžžq9½†ý™R`_[Õ…×0=w£^.”.à%—Ókè ’M)0À±½_fò…»IJÓlÀ^©Î.§×Û››Và¾Us–¾¤EuÛ¤:[´¨LIL+0@}ÙE4ÖÎ$}j©æýÀ›Ra÷E02&K“Sö²[ì>àE`ŽËéýª™Å“·`€Ö“S¨;p)“çîŠØðð¸Ëé­Ü3c`z*v_ÏÄY{±&Œj@[< <írzO+ë™þD…ÀÝ6ªö\GÖÿ ç² àQàE—Óòf#*¨-ù“çî"Å~b¤¢%RWÍ’êDBÔ,üq”¬ÀyÃÚ`E> ê¨úÚ"£54TΦ¡*›ŒÏî;%€·¤:ÂÌHT ÅäöÏ­é–,þ—5.§÷ Þ>éIÔ Ü~ú>üûmwÝó½}1¦ŸèŠÎ挟ää¹'è퇈Jtà·z;a¢U`€»ròÜN½Ð›h8ŽÀÍø˜&š¸*'Ïm¬A4&Úpåä¹ÍóÖS…‰g÷êí„^Ä‚À?ÏÉsk»©±AˆmÀÃz;¡±"0ÀwsòÜæxͼ‚Ä’Àû1E, ›“çþ–ÞNhI¬ °:'Ï¢·Zñ¬¸‰ÄÝ ëIEND®B`‚Coherence-0.6.6.2/misc/device-icons/elisa-icon.png0000644000175000017500000000147311317660741017743 0ustar devdev‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ †¡dp»IDAT8Ë’[HqÆÏ\wfwug d©^"²±"puÒ|Yª‡^"W…Bê%JHÊ.ŠÅ&e$«/–ÛÍK`±”kF¥YO‘ÛBA¶n³³;3;—ÿLOŠ˜Fçñã;?øÎùV™`8© ?ä—kÕ­¹šplÿJ/¾/ód /Y®ëÜŒ¡ø•^rU@y‘š×qè¥Õ(ñ=ÇÛ,ݲ*à~°:jüÊpXAïiL~M^V 8ˆwßí$5û=¥8àÐtÿøåºþ¥˜C<„- ‰ºú‰t2%Ð&Þ:{ï“·7iF¡ËãóEt97kzÉ‹@“'ŒvZ”V¸W}ª–nàg˜®R? ND¼>HøÜ³˜†Ü–¦“,óÁK°#lÆ\2(Ý‚ Z°åyR¡}©o)a:°;o8G J#ð3E´Y`‡,6~fæEcUrq t1 õú}ï秃¦,Ò´w8îÛ5ðq›8ï*玺ÖssF6)Ï:’cZâ˦³X0‹%*fMés·U~ü–Ë6Ôüœ“ž<ïGC7g* y„æ<’c ¾@"ÉÎjb¢sÏìR!òº/ñœ  àI`f”¶Œ;hëæU#«œöT”eÜ®Ê#YkÒµBÀ–´.lå_®¼i Yz#YÄöê²"±%¾Z6·™Æ©Ã*ëtÇ›ù k6±þÔ³ G3Æ-°{Â’ µ[’Ú‰\¸jå´$äôókV¹¶åQ³ŽÌ€Á@—îÅÅxx»ß•µ¶˜ª&GM#YM8Ö¬²­è6ªœì å5+«òV)“tÒj ©F Ù–püúTßí¶`ë_€©Ø¡þ•tCӓΘl¯•„ðƒ­® ®þwÎÝyÛ ÇüÿòüGl;øY ²;IEND®B`‚Coherence-0.6.6.2/misc/device-icons/flickr-icon.png0000644000175000017500000000222611317660741020115 0ustar devdev‰PNG  IHDRb{ ysRGB®Îé€PLTEáíûf¡êÔÔÔ<ˆäK‘æäääÿ–Ì3‚ã™ÁñðððàààØØØíôýx¬ìÿˆÆÿfµèèèÜÜÜgÝ÷÷÷c êÅÅÅÑÑÑÿ™Îÿ»ÞÕåùûûûðöý“½ð6„ãüýÿóóóçðü‡¶ïÀØö¥ÈóuàT–èììì jÞo§ë?ŠåóøýŸÄò´Ñõÿ÷û«ÌónÞEåÿªÖÿÝïÿÌæÿÀáHæÿÕë®Íô~°íºÕöÆÜ÷pßÉÉÉÌàøi£êöùþeÜÉÞø!wáÿ’B‹åŠ·ï»ðrßÿî÷ØçúÏâøZšèÿùüÿŽÿ?¢êòüÿoºÿw½ÿøülÞÿÿr»ÿ‘'{áÍÍÍ`žéÿNª¢Æòu«ìÿ6žÿÛîÿíötàÿE¥$yáÿH§ÿÈQ•çÿx¾ÿŠÇÿÞï²îÛéúÿU­äîûN“çÿ¥Ôÿ¨Õ*}âÞëú]œé·Óõÿîö0€ãÿ'—ÿD¥ÿT­r©ìÒãùÿc´–¿ñÿ„cÜÿÿÿêLäæ pHYs  šœtIME×  6O —œIDATHǵև_Ú@ðä Aµ€«n«¶ÎVmíÞ{ï½w•ß¿îË»G΍ïÃ'y\îò%¹w÷AC5ær8ŒÐœóÀr±D1ä¼vôR[»ÏŽö:£âÜ'A¹Gxà±`ø ! €HÁáæ¬ËrˆL¾$ƒî¬qÒL­ëœ­×!šøŠ†Vˆx,Ù”ðŠ±¸Ù/àñ8D—#”ºöLÐý"ÕêO@$ ³]¦Çáf•8-bi`ÏD„^“½û)ºMGº/7g0bGœøô«Ä¿ñ¡ÿˆD «5f?E–æ#Å„l6ô•‰o<â¾Sbû(»¯¨ÑqhDÏçç¨B—TÂU´3Óqo§þg0£iîã»k£øHçËÏ.¾|Œ'“gÆ@ GJJ·îŽJ¿;*!ÝÊZ6(Ná¹}úùˆprôÚB»%̾jŸ^…ää¤Y!>ÙÇG©!„dx,´[b¡SéÓU%L®öèÊÄ…›L<‘Äh2>> from coherence.base import Plugins >>> p = Plugins() >>> p['FSStore'].options [{'default': 'my media', 'type': 'string', 'key': 'name'}, ...] }}} The Plugins class is a dict, so p.items() returns the backend identifiers as keys and their classes as values. 2009-10-13 12:28 dev * coherence/base.py: fixing a typo in the Plugins class and some more logging 2009-10-13 08:31 dev * coherence/backends/gstreamer_renderer.py: allow activation too when ''glib'' option is set 2009-10-12 18:33 dev * coherence/dbus_service.py, coherence/upnp/core/device.py: adding ''presentation_url'' and ''parent_udn'' to the D-Bus get_devices response 2009-10-10 13:47 dev * coherence/dbus_service.py: change the out-signature of the CDS Browse and Search actions to 'aa{sv}iii' to ease de-marshaling 2009-10-10 12:00 philn * coherence/backends/gstreamer_renderer.py: re-reworked gst renderer (-> playbin2), better support for mute and pictures display, fixes #251, #153 2009-10-10 11:02 dev * coherence/backends/flickr_storage.py: fix the FlickrStore when used without an user account 2009-10-10 10:26 philn * Coherence.egg-info/entry_points.txt: feed store in egg metadata 2009-10-09 08:39 dev * coherence/backends/gstreamer_renderer.py: reverting [1435], addresses #251 and #153 2009-10-07 18:49 philn * coherence/backends/gstreamer_renderer.py: reworked the gstreamer media renderer to use playbin2, depend on the glib main loop and properly deal with volume properties, fixes #153 2009-10-07 17:24 sebp * coherence/backends/dvbd_storage.py: Added process_DeleteTimer_result callback function 2009-10-07 17:14 sebp * coherence/backends/dvbd_storage.py: Fixed bug when there are no channels or recordings. Added initial version of DVBDScheduledRecording class. 2009-10-06 20:21 dev * coherence/dbus_service.py: more cleaning-up the DBus api 2009-10-06 19:12 dev * coherence/json.py: adding first JSON calls: * `http://host:port/json/devices` returns a list of device dicts * `!http://host:port/json///?arguments` returns the result of the action call as a dict of the out arguments[[BR]] e.g. `!http://host:port/json///ContentDirectory/Browse?ObjectID=0&BrowseFlag=BrowseDirectChildren&Filter=*&StartingIndex=0&RequestedCount=0&SortCriteria=` 2009-10-06 16:36 dev * coherence/upnp/core/device.py: necessary helper adjustments to enable embedded devices in the Inspector 2009-10-06 08:48 dev * coherence/base.py, coherence/upnp/core/ssdp.py: using proper interface on multihomed hosts for the SSDP multicast group, closes #250 2009-10-04 16:27 dev * coherence/dbus_service.py: emitting proper D-Bus signal 2009-10-04 15:20 dev * coherence/dbus_service.py: fixing the fix 2009-10-04 15:18 zaheerm * coherence/transcoder.py: fix typo 2009-10-04 15:02 sebp * coherence/backends/dvbd_storage.py: dvdb_storage: Added support for channel groups 2009-10-04 14:45 dev * coherence/dbus_service.py: refining the new generic device signal 2009-10-04 14:09 dev * coherence/dbus_service.py: adding generic device detected and removed signals 2009-10-04 11:09 dev * coherence/dbus_service.py: of course add the resources only when needed 2009-10-04 11:05 dev * coherence/dbus_service.py: now with the missing res array 2009-10-04 10:53 dev * coherence/dbus_service.py: fix D-Bus items structure 2009-10-04 10:44 dev * coherence/dbus_service.py: simplifying the D-Bus items structure 2009-10-04 09:27 dev * coherence/upnp/devices/control_point.py: fixing logging error 2009-10-04 08:48 dev * coherence/dbus_service.py: add XML to DBus dict parsing on our side for the CDS.Browse action 2009-10-03 14:52 sebp * coherence/backends/dvbd_storage.py: Adjusted DVB Daemon backend to API changes 2009-10-03 14:28 dev * coherence/json.py: importing the log from the proper place 2009-10-03 12:21 dev * coherence/base.py, coherence/json.py, coherence/upnp/core/action.py, coherence/upnp/core/device.py, coherence/upnp/core/service.py: laying the foundation for JSON "based" access to the devices/services and actions 2009-10-03 10:47 philn * coherence/backend.py: chain up to Backend class constructor 2009-09-19 13:53 schrei5 * Coherence.egg-info/entry_points.txt, coherence/backends/feed_storage.py: adding my feed storage backend to use it add something like the following to your configuration FeedStore http://www.swr3.de/rdf-feed/podcast/ 426e6b31-fe2d-46ef-a69a-f0733ff4914f SWR3 OPML FeedStore b51ddca7-79ba-47bd-b558-2e830d1e2566 http://podcast.wdr.de/quarks.xml Quarks 2009-09-17 21:29 dev * coherence/backends/fs_storage.py, coherence/upnp/core/DIDLLite.py: implementing the missing parts from the buried patch out of #237 - addresses #248 and closes #237 2009-09-14 16:58 dev * setup.py: add netifaces as a dependency when doing a setuptools install on OpenSolaris and Windows, addresses #242 2009-09-12 13:08 dev * coherence/upnp/devices/media_server.py: sending out a ''contentFeatures.dlna.org'' header whenever it is requested 2009-09-09 21:05 dev * coherence/backends/fs_storage.py, coherence/upnp/devices/media_server.py: support for subtitles - Samsung style - addresses #248 Works so far with the FSStore backend, if there is a filename.srt file we'll add that as an additional resource (maybe we can use that one day for some other device) and make it accessible via an attachment url 2009-09-09 17:41 lightyear * coherence/transcoder.py: mimetype->content_type 2009-09-09 17:41 lightyear * coherence/transcoder.py: disallow non-ascii in name 2009-09-09 17:41 lightyear * coherence/test/test_transcoder.py: bad name test 2009-09-09 17:41 lightyear * coherence/test/test_transcoder.py: add another anti-regression test 2009-09-09 17:41 lightyear * coherence/test/test_transcoder.py, coherence/transcoder.py: fix the implementation: level the API, spacing, pep8-ing the code, FIXMEs and basic clean up 2009-09-09 17:41 lightyear * coherence/test/test_transcoder.py: fix the tests 2009-09-09 17:41 lightyear * coherence/transcoder.py: fix the placeholder missing bug 2009-09-09 17:40 lightyear * coherence/test/test_transcoder.py: add test for placeholder missing bug 2009-09-09 17:40 lightyear * coherence/transcoder.py: make it bulletproof for 2.4 as well 2009-09-09 17:40 lightyear * coherence/test/test_transcoder.py, coherence/transcoder.py: fix this fucking implementation 2009-09-09 17:40 lightyear * coherence/test/test_transcoder.py, coherence/transcoder.py: more transcoder manager test stuff 2009-09-09 17:40 lightyear * coherence/test/test_transcoder.py, coherence/transcoder.py: more transcoder tests 2009-09-09 17:40 lightyear * coherence/test/test_transcoder.py: add first small and simple tests about the transcoder manager 2009-09-09 16:04 dev * coherence/upnp/core/DIDLLite.py: use the default DLNA.ORG_PN value for transcoded mpeg files 2009-09-09 11:41 dev * bin/coherence: making daemon mode work again, closes #230 Nevertheless we should consider using http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ 2009-09-09 10:34 philn * bin/coherence, coherence/__init__.py, setup.py: check twisted{,.web} versions at runtime, fixes #243 2009-09-08 20:09 philn * setup.py: put coherence base root package in packages to ship, fixes #244 2009-09-06 19:17 dev * coherence/transcoder.py: now with the ExternalProcessPipeline 2009-09-06 11:04 dev * coherence/transcoder.py, coherence/upnp/core/DIDLLite.py: fixing more typos 2009-09-05 15:16 dev * coherence/upnp/core/DIDLLite.py: typo fix 2009-09-05 14:59 dev * coherence/transcoder.py: there might be none transcoders defined in the config at all, thx Crys 2009-09-05 13:14 dev * coherence/transcoder.py: fixing some leftovers 2009-09-05 10:43 dev * coherence/upnp/devices/media_server.py: remove the direct use to the JPEGTranscoder 2009-09-05 09:58 dev * coherence/base.py, coherence/transcoder.py, coherence/upnp/devices/media_server.py: creating a TranscoderManager and removing the selector from the MediaServer 2009-08-28 07:54 zaheerm * ChangeLog, coherence/transcoder.py: * coherence/transcoder.py: Proper pipeline for mpeg2 ts inserted. 2009-08-27 20:27 dev * coherence/upnp/core/DIDLLite.py: more stubs for the mpegts video transcoding - not that smart :-/ 2009-08-27 19:38 dev * coherence/transcoder.py, coherence/upnp/devices/media_renderer.py, coherence/upnp/devices/media_server.py: * stub for mpegts video transcoding - intermediate solution to get the whole thing up * we need a (singleton) class that collects all available transcoders, does the proper selection so we can move that switch out of the MediaServer, and provides the available urls for the DIDLLite Resource creation 2009-08-27 18:24 dev * Coherence.egg-info/entry_points.txt, coherence/dispatcher.py, coherence/extern/simple_config.py: do a lazy import of the reactor to not have the default reactor installed before we have the chance to change that 2009-08-23 15:28 philn * coherence/dbus_service.py: dbus_device: get_markup_name() method, needed since [1374] 2009-08-04 09:19 dev * setup.py: make a setuptools based install more flexible and detect distro versions of ConfigObj, Twisted and TwistedWeb 2009-08-03 14:39 dev * coherence/upnp/core/action.py, coherence/upnp/core/soap_proxy.py: correcting [1372] and make the whole thing work below Python 2.6 again - thx lightyear 2009-07-30 17:38 lightyear * coherence/base.py, coherence/dbus_service.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/tube_service.py, coherence/upnp/core/device.py, coherence/upnp/devices/binary_light_client.py, coherence/upnp/devices/control_point.py, coherence/upnp/devices/dimmable_light_client.py, coherence/upnp/devices/media_renderer_client.py, coherence/upnp/devices/media_server_client.py, coherence/web/ui.py, mirabeau_client.py, tw_mirabeau_test.py: nice device type and version helpers From ea4155ed98daef846fe9895cdefdcc98a8f089b2 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 21 Jul 2009 20:42:49 +0200 Subject: [PATCH] add - friendly_device_type to device - device_type_version - get_device_markup --- coherence/upnp/core/device.py | 20 +++++++++++++++++++- coherence/web/ui.py | 34 ++++++++++++++++++------------- 2009-07-27 18:46 jmsizun * coherence/backends/playlist_storage.py: playlist: differentiate audio and video items 2009-07-26 12:03 dev * coherence/upnp/core/action.py: preserve the order of input arguments when doing a client-side action call - thanks again David Liu for spotting this 2009-07-21 20:25 philn * coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/tube.py: mirabeau: pass initiator address in tube params and reimplemented roster filter (on consumer side) 2009-07-20 17:44 zaheerm * coherence/transcoder.py: Don't reuse pipeline after setting state to NULL 2009-07-20 17:27 zaheerm * coherence/transcoder.py: Handle eos to finish requests and cleanup pipeline 2009-07-20 16:41 zaheerm * Coherence.egg-info/entry_points.txt, setup.py: Revert my local changes that were not meant to be committed 2009-07-20 16:35 zaheerm * Coherence.egg-info/entry_points.txt, coherence/transcoder.py, coherence/upnp/devices/control_point.py, setup.py: Move GStreamerPipeline to use appsink instead of custom sink. Allow multiple requests to be served by GStreamerPipeline. Create pipeline in constructor of GStreamerPipeline rather than in start. 2009-07-15 10:59 dev * coherence/backends/fs_storage.py: add a mimetype for Matroska containers with a .mkv extension 2009-07-14 21:19 jmsizun * coherence/backends/dvbd_storage.py: DVBD: added items for live tv channels (exposed via rtsp by dvb-daemon) 2009-07-12 17:05 dev * coherence/dbus_service.py: fix unicode error in dbus pontoon, thx Alfonso - closes #237 2009-07-12 17:01 dev * coherence/upnp/core/event.py: workaround for devices that won't let us subscribe to events, closes #210 2009-07-04 19:42 philn * docs/mirabeau.xml: sample mirabeau config file 2009-07-02 18:57 philn * coherence/extern/telepathy/client.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py: mirabeau: refactored publisher/consumer in mixins, code is now more reusable 2009-06-30 07:54 dev * coherence/dbus_service.py, coherence/tube_service.py: correcting some xml-namespace issue due to the fromString/toString cycle 2009-06-29 22:13 jmsizun * coherence/backends/itv_storage.py: ITV: return of "why not having this backend to work, for a change..." 2009-06-29 21:55 jmsizun * coherence/backends/dvbd_storage.py: dvbd: in my config, dvb_daemon returns file URIs for recordings instead of local pathes 2009-06-28 21:26 philn * misc/Rhythmbox-Plugin/upnp_coherence/__init__.py: rhythmbox plugin: use interface option when starting coherence 2009-06-28 14:29 philn * coherence/extern/telepathy/client.py, coherence/extern/telepathy/connect.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py, coherence/extern/telepathy/tubeconn.py: merged the tube publisher/consumer inside a single client instead of mixing 2 clients 2009-06-27 09:53 philn * coherence/dbus_service.py: dbus_pontoon: implement get_devices_async with a coiterator 2009-06-27 09:48 philn * coherence/base.py: fix typo 2009-06-26 21:53 jmsizun * coherence/backends/iradio_storage.py: iRadio: replaced prints by proper logs and other small fixes 2009-06-26 21:26 jmsizun * coherence/backends/itv_storage.py: ITV: why not having this backend work, for a change... 2009-06-26 20:47 jmsizun * coherence/backends/playlist_storage.py: playlist backend: updated list of potential source protocols 2009-06-26 19:58 dev * coherence/base.py, coherence/dbus_service.py, coherence/tube_service.py, coherence/upnp/core/DIDLLite.py, coherence/upnp/devices/control_point.py: adding the missing pieces to allow access from outside the local lan to MediaServer items exported via MiraBeau For now we need an element ''ip:port'' in the mirabeau config section. This will in a next phase be extended with UPnP and ICE NAT-traversal techniques. 2009-06-26 19:53 dev * coherence/extern/telepathy/client.py: create the muc_id right when using local-xmpp 2009-06-25 21:32 philn * coherence/extern/telepathy/client.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py: mirabeau: complete support for muc p2p media-server browsing 2009-06-25 19:50 philn * coherence/base.py, coherence/extern/telepathy/client.py, coherence/extern/telepathy/connect.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py, coherence/extern/telepathy/tubeconn.py: mirabeau: various adaptations to latest Telepathy interface requests API 2009-06-23 22:14 jmsizun * coherence/upnp/core/device.py: clarified warning message 2009-06-23 22:13 jmsizun * coherence/backends/youtube_storage.py: fix string name for backend store class 2009-06-23 22:10 jmsizun * coherence/backends/playlist_storage.py: publish resource with protocol http-get instead of rtsp-rtp-udp when relevant 2009-06-23 22:08 jmsizun * coherence/backends/miroguide_storage.py: fix string name for backend store class 2009-06-23 22:07 jmsizun * coherence/backends/itv_storage.py: ITV: retry later when connection to Shoutcast server fails 2009-06-23 20:05 jmsizun * coherence/backends/itv_storage.py: ITV: replaced prints by proper logs 2009-06-23 14:25 philn * coherence/extern/louie.py: extern.louie: make the dispatcher a global variable and the deprecation warning more developer friendly 2009-06-21 20:29 philn * coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/tube.py, coherence/extern/telepathy/tubeconn.py: mirabeau: various code cleanups and debug msgs fixes 2009-06-21 20:26 philn * coherence/dbus_service.py: mirabeau: fix StateVariableChanged in dbus service object (take 2) 2009-06-21 20:06 philn * coherence/dbus_service.py: mirabeau: fix StateVariableChanged in dbus service object 2009-06-21 15:58 philn * coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py, coherence/tube_service.py: mirabeau: some jabber related fixes 2009-06-21 15:07 philn * coherence/extern/telepathy/client.py: mirabeau client: fixed text messages acknoledgement 2009-06-21 14:23 philn * coherence/base.py: mirabeau: plug found remote devices to tube proxies 2009-06-21 13:06 dev * Coherence.egg-info/entry_points.txt, coherence/dbus_service.py, coherence/tube_service.py, coherence/upnp/core/service.py, mirabeau_client.py, tw_mirabeau_test.py: exposing now TubeDevice and TubeService proxies, plain communication is possible now 2009-06-20 16:02 philn * coherence/base.py, coherence/extern/telepathy/client.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py, coherence/extern/telepathy/tubeconn.py, mirabeau_client.py: mirabeau: Coherence can now publish tubes and listen for tubes from other peers within the same instance 2009-06-20 12:34 lightyear * coherence/extern/louie.py: using save_emit instead of emit because we rely on the bad reactor based implementation louie had before. 2009-06-20 11:57 lightyear * coherence/extern/louie.py: small syntax fix in the louie wrapper 2009-06-20 11:38 dev * coherence/upnp/core/service.py, coherence/upnp/services/servers/av_transport_server.py: * added ''register_vendor_action'' and ''register_vendor_variable'' methods * delayed creation of the service XML file 2009-06-20 11:36 dev * coherence/upnp/core/variable.py: rearrangements and naming correction 2009-06-20 11:35 dev * coherence/backends/gstreamer_renderer.py: small fix re 'Next' TransportAction 2009-06-20 11:33 dev * coherence/backends/flickr_storage.py: add lazy DIDLLite item creation and dlna-playcontainer support 2009-06-20 11:23 philn * coherence/backends/lastfm_storage.py: use hashlib if available 2009-06-20 10:36 lightyear * coherence/dispatcher.py, coherence/extern/louie.py, coherence/test/test_dispatching.py: You shall not commit when you are not done adding: Replacing louie with the new signaling dispatching system of pure AWESOMENESS 2009-06-20 10:34 lightyear * coherence/base.py, coherence/extern/louie, coherence/upnp/core/device.py: Re 2009-06-17 21:38 jmsizun * Coherence.egg-info/entry_points.txt, coherence/backends/playlist_storage.py, setup.py: Added new backend for web tv playlists (used in France by Free and Neuf ISPs) 2009-06-17 21:35 jmsizun * Coherence.egg-info/entry_points.txt, coherence/backends/miro_storage.py, coherence/backends/miroguide_storage.py, setup.py: rename Miro Backend into MiroGuide backend 2009-06-17 21:33 jmsizun * coherence/backends/miroguide_storage.py: renamed backend 2009-06-17 21:04 jmsizun * coherence/backends/miro_storage.py: add thumbnail as albumArtURI 2009-06-13 18:06 philn * coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/tube.py: mirabeau: don't accept muc tubes if they are not offered by a member of my roster 2009-06-13 18:03 philn * coherence/extern/telepathy/client.py: mirabeau telepathy client: roster retrieval support 2009-06-10 19:08 dev * coherence/upnp/core/event.py, coherence/upnp/core/ssdp.py: fix the wrong creation of a rfc-1123 date, e.g. the date-string was localized ''DATE: å…­, 06 6月 2009 05:50:58 GMT'' as reported by liuran - thx for spotting this! 2009-06-10 17:05 dev * coherence/upnp/core/service.py, coherence/upnp/core/xml-service-descriptions/AVTransport1.xml, coherence/upnp/core/xml-service-descriptions/AVTransport2.xml: add the necessary code to enforce that a backend has implemented all actions that Coherence can't handle itself 2009-06-10 16:48 dev * coherence/upnp/core/DIDLLite.py: modify the find-matching-res-entries method and allow check against a partial localcontent-format string ('image/' or 'audio/') only 2009-06-10 16:44 dev * coherence/extern/telepathy/tube.py, mirabeau_client.py: cosmetic changes 2009-06-10 16:40 dev * coherence/dbus_service.py: in an object coming from the tubes the ''device'' parameter is None 2009-06-10 16:36 dev * coherence/backends/test_storage.py: document 'title' and added a new 'extension' attribute to an item in the TestStore 2009-06-10 16:31 dev * coherence/base.py: reverting [1303] - this doesn't work here, and we need to find the root cause anyway, addresses #112 2009-06-04 20:58 dev * misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py: added a missing import 2009-06-03 22:03 jmsizun * coherence/base.py: #112 : return 404 error when requesting an URI with erroneous device UUID. This includes a hack to overcome some strange behavior with Twisted and HTTP request with non-standard methods. 2009-06-03 08:59 lightyear * misc/Rhythmbox-Plugin/upnp_coherence/__init__.py: cleanup, simplification and pep8-ing 2009-06-02 18:51 dev * coherence/dbus_service.py: * explicitly remove DBus object, currently they seem to stay around without that, maybe the objects have some references lingering around 2009-06-02 18:25 philn * coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py: mirabeau: use of logging system and other cleanups after dev :) 2009-06-02 18:23 philn * coherence/base.py: comments about telepathy account 2009-06-02 10:12 dev * coherence/base.py, coherence/dbus_service.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, mirabeau_client.py: mirabeau related cleanups 2009-06-02 10:11 dev * coherence/transcoder.py: typo fix 2009-06-01 19:20 dev * misc/Rhythmbox-Plugin/coherence.rb-plugin.in, misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py, misc/Rhythmbox-Plugin/upnp_coherence/MediaStore.py, misc/Rhythmbox-Plugin/upnp_coherence/__init__.py: * merging mine and lightyears gconf changes * an initial version of a configuration dialog * seeking fixes in the Rhythmbox MediaRenderer * better StateVariable settings for local initiated playback in the Rhythmbox MediaRenderer * first steps to dlna-playcontainer support in the Rhythmbox MediaServer 2009-06-01 19:10 dev * coherence/upnp/devices/media_renderer.py, coherence/upnp/devices/media_server.py: some fixes re icon handling 2009-06-01 19:07 dev * coherence/upnp/devices/basics.py: some fixes re icon handling 2009-06-01 19:06 dev * coherence/upnp/core/variable.py: one more detail for the Inspector 2009-06-01 19:02 dev * coherence/backends/gstreamer_renderer.py: fix re 'REL_TIME' seeking 2009-06-01 18:57 dev * coherence/upnp/core/xml-service-descriptions/AVTransport1.xml: fix re vendor values 2009-06-01 14:19 philn * coherence/extern/telepathy/mirabeau_tube_publisher.py: mirabeau: react on pontoon MS detected/removed signals 2009-06-01 14:18 philn * coherence/dbus_service.py: dbus: remove devices from cache after _removed signals have been called, it'd be better to hold the device object during signal emission. for now wait a second and clean cache 2009-05-30 18:15 philn * coherence/base.py, coherence/extern/telepathy/mirabeau_tube_publisher.py: mirabeau: uuid based devices filtering (on tube publisher side) 2009-05-30 18:15 philn * coherence/dbus_service.py: new dbus device attribute: uuid 2009-05-30 10:26 jmsizun * coherence/backends/youtube_storage.py: add youtube thumbnail as AlbumArtURI 2009-05-29 23:08 jmsizun * coherence/backends/itv_storage.py: fixed iTV items beeing erroneously advertised as audio items instead of video items 2009-05-29 23:04 jmsizun * coherence/backends/flickr_storage.py: #234 : authenticate call to flickr.photos.getSizes 2009-05-29 13:06 lightyear * misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py, misc/Rhythmbox-Plugin/upnp_coherence/MediaStore.py, misc/Rhythmbox-Plugin/upnp_coherence/__init__.py: clean up, typo and name-setting support 2009-05-29 12:48 lightyear * misc/Rhythmbox-Plugin/upnp_coherence/__init__.py: use gconf so save uuid and allow settings, refs #232 2009-05-28 13:18 lightyear * misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py: ref #189 : adding Next/Previous to rb player 2009-05-28 12:48 lightyear * misc/Rhythmbox-Plugin/upnp_coherence/MediaPlayer.py: small fix for rb-plugin: allow to start playback when rb didn't play before 2009-05-28 12:13 lightyear * misc/Rhythmbox-Plugin/upnp_coherence/MediaStore.py: fix the rb-media-store: backend store wasn't initialized 2009-05-24 08:03 philn * coherence/extern/telepathy/client.py, coherence/extern/telepathy/stream.py, coherence/extern/telepathy/tube.py: stream tubes support in Tube{Publisher,Consumer} 2009-05-20 23:15 lightyear * misc/media_server_observer.py: example script as test helper 2009-05-17 15:22 philn * coherence/base.py: mirabeau: store settings in config 2009-05-12 21:35 philn * coherence/base.py, coherence/dbus_constants.py, coherence/dbus_service.py, coherence/extern/telepathy, coherence/extern/telepathy/__init__.py, coherence/extern/telepathy/client.py, coherence/extern/telepathy/connect.py, coherence/extern/telepathy/mirabeau_tube_consumer.py, coherence/extern/telepathy/mirabeau_tube_publisher.py, coherence/extern/telepathy/tube.py, coherence/extern/telepathy/tubeconn.py, mirabeau_client.py: initial Mirabeau super-bridge bases checkin 2009-05-12 20:45 dev * Coherence.egg-info/PKG-INFO, coherence/__init__.py: bumping version to 0.6.5 2009-05-12 20:37 dev * ChangeLog, NEWS, coherence/__init__.py, setup.py: New in this - Pont Mirabeau - release: * new MediaServer backends that allow access to * Picasa Web Albums (http://picasa.google.com) * a TestServer to easily serve and test interaction with * one or more items and adjust 'upnp_class', 'mimetype' and 'DLNA-flags', * items that are a GStreamer pipeline or an external program * a new - used in parallel - D-Bus API with an 'org.DLNA' interface with the goal to create a common API for all UPnP/DNLA frameworks * support for the dlna-playcontainer URI (http://netzflocken.de/2009/4/23/media-collection-playing-the-dlna-way) * enchancements to the GStreamer MediaRenderer, supporting now dlna-playcontainer and SetNextAVTransportURI, and jumping to previous and next tracks * support for video items served by Ampache (http://ampache.org) * base classes for a ScheduledRecording service * some 'compatibility' adjustments for different devices * and - as every time - the usual bugfixes and enhancements Kudos go to jmsizun, Stecchino, cjsmo, chewi, and lightyear. 2009-05-12 20:08 dev * coherence/backends/test_storage.py: small typo in docstring 2009-05-12 20:08 dev * coherence/backends/ampache_storage.py: * make ''password'' and ''key'' equivalent in the config * some preparations for dlna-playcontainer uri 2009-05-12 20:03 dev * coherence/upnp/devices/media_server.py: rearranging the order of the device description elements for UPnP 1.1 2009-05-11 09:14 dev * coherence/dbus_service.py: more updates on the org.DLNA DBus API, now with all methods for the DMS.CDS interface (no checking for not implemented optional methods yet) 2009-05-10 15:30 dev * coherence/dbus_service.py: added the org.DLNA interfaces 2009-05-10 09:41 dev * coherence/dbus_service.py: fixing a DBus error, returning the object path now, not the object 2009-05-09 22:12 jmsizun * coherence/backends/itv_storage.py: small corrections 2009-05-09 13:12 dev * coherence/base.py: activate the ControlPoint automatically if the DBus interface is used 2009-05-09 10:29 lightyear * coherence/backends/lolcats_storage.py: fix the lolcats example plugin 2009-05-04 19:21 dev * coherence/backends/gstreamer_renderer.py, coherence/upnp/core/DIDLLite.py, coherence/upnp/services/servers/connection_manager_server.py: * support for skipping backward and forward, and seeking inside the track-list * adding the dlna-playcontainer flag to the SinkProtocolnfo values 2009-04-28 20:54 dev * coherence/backends/test_storage.py: don't make a trancoder ImportError let the TestStore fail 2009-04-27 09:23 dev * coherence/upnp/core/utils.py: hmm, no callback/errback attached 2009-04-27 09:13 dev * coherence/upnp/devices/basics.py, coherence/upnp/devices/binary_light.py, coherence/upnp/devices/dimmable_light.py, coherence/upnp/devices/media_renderer.py, coherence/upnp/devices/media_server.py: * making device xml generation more flexible - thx Caleb * check for icon file existence before announcing and registering it 2009-04-26 15:44 dev * coherence/backends/gstreamer_renderer.py: moving a lot of noisy prints into log messages 2009-04-26 12:03 dev * coherence/backends/gstreamer_renderer.py, coherence/backends/mediadb_storage.py, coherence/upnp/core/DIDLLite.py, coherence/upnp/devices/basics.py, coherence/upnp/devices/media_renderer.py: adding support for ''dlna-playcontainer://'' to the MediaDB MediaServer and the GStreamer MediaRenderer backend 2009-04-20 19:50 dev * coherence/upnp/core/soap_proxy.py: adding debug output to find xml parsing issues 2009-04-19 19:56 dev * coherence/upnp/core/service.py: expose the ServiceId to the Inspector 2009-04-19 17:38 dev * coherence/backends/gstreamer_renderer.py: changes for SetNextAVTransportURI support (thx chewi) - addresses #197 2009-04-18 16:00 dev * coherence/backends/fs_storage.py: ignore FIFO files, closes #184 2009-04-18 10:49 dev * coherence/backend.py, coherence/backends/ampache_storage.py, coherence/backends/appletrailers_storage.py, coherence/backends/axiscam_storage.py, coherence/backends/bbc_storage.py, coherence/backends/dvbd_storage.py, coherence/backends/flickr_storage.py, coherence/backends/fs_storage.py, coherence/backends/gallery2_storage.py, coherence/backends/iradio_storage.py, coherence/backends/itv_storage.py, coherence/backends/lastfm_storage.py, coherence/backends/lolcats_storage.py, coherence/backends/mediadb_storage.py, coherence/backends/swr3_storage.py, coherence/backends/ted_storage.py, coherence/backends/test_storage.py, coherence/backends/tracker_storage.py: cleaning up the backend inheritage a bit and moving wmc_mapping from class to instance 2009-04-14 21:37 dev * coherence/backends/ampache_storage.py: more work on the Ampache video items, re #201 2009-04-14 19:43 dev * coherence/upnp/core/DIDLLite.py: a little helper method 2009-04-14 11:36 dev * coherence/backends/fs_storage.py, coherence/backends/gstreamer_renderer.py, docs/test-store-example.xml: inquire the ignore pattern first before reacting upon an inotify event and adding a new file closes #203 2009-04-14 11:32 dev * coherence/base.py: move the warning message to the proper place 2009-04-14 10:38 dev * coherence/base.py, coherence/web/static/Coherence.Base.js, coherence/web/static/Coherence.Devices.js, coherence/web/static/Coherence.Logging.js, coherence/web/static/Coherence.js, coherence/web/static/MochiKit.js, coherence/web/static/main.css: disabling Web UI for the moment and removing obsolete JS files and MochiKit library 2009-04-13 11:59 dev * coherence/backends/test_storage.py, docs/test-store-example.xml: some fixes for the TestStore and a configurable fourth_field of the protocolInfo element 2009-04-10 20:10 dev * docs/man, docs/man/coherence.1, misc/Desktop-Applet/applet-coherence.1: man-pages for /usr/bin/coherence and /usr/bin/applet-coherence - thx cjsmo! The is a new docs/man folder for the coherence.1 file, maybe we'll have some time some more there. The applet-coherence.1 file is included in the misc/Desktop-Applet folder, doesn't make sense to install it without the applet itself. Closes #199 2009-04-09 20:31 jmsizun * coherence/backends/picasa_storage.py: corrected problem with get_item 2009-04-06 09:15 dev * coherence/backends/miro_storage.py, coherence/backends/picasa_storage.py, coherence/backends/youtube_storage.py: establishing parent<->child relationship again 2009-04-06 09:11 dev * coherence/base.py, coherence/upnp/core/utils.py: switching in utils.get_host_address from popen to getProcessOutput and handle the deferred properly 2009-04-05 14:31 jmsizun * coherence/backends/miro_storage.py, coherence/backends/picasa_storage.py, coherence/backends/youtube_storage.py: partial fix to problem with ids and parent ids in UpnP XML items 2009-04-02 07:50 dev * coherence/backends/test_storage.py: fixed typo in docstring and extended it a bit 2009-03-31 21:09 dev * coherence/backends/ampache_storage.py: requesting Ampache video files, not looking at the tags yet, addresses #201 2009-03-26 22:21 jmsizun * coherence/backend.py: forgot this commit yesterday 2009-03-25 22:46 jmsizun * coherence/backends/gallery2_storage.py, coherence/backends/itv_storage.py: Updated gallery2/iTV backend to use factorized ReversedProxyUriResource class 2009-03-25 22:01 jmsizun * coherence/backends/picasa_storage.py, coherence/backends/youtube_storage.py, coherence/upnp/core/utils.py: Factorize Uri splitting code from ReverseProxyResource subclasses into a common ReverseProxyUriResource class 2009-03-25 21:05 jmsizun * coherence/backends/test_storage.py: Remove useless import for coherence.upnp.core.utils.ReverseProxyResource 2009-03-25 19:18 jmsizun * coherence/backends/miro_storage.py, coherence/backends/picasa_storage.py, coherence/backends/youtube_storage.py: Factorize common backendStore functionnalities into ABstractBackendStore for Picasa/Youtube/Miro backends 2009-03-23 23:54 jmsizun * coherence/upnp/devices/media_server.py: change prints into log messages 2009-03-23 23:45 jmsizun * coherence/backends/picasa_storage.py: change prints into log messages 2009-03-22 18:16 jmsizun * coherence/backends/itv_storage.py, coherence/backends/miro_storage.py, coherence/backends/picasa_storage.py, coherence/backends/youtube_storage.py: Update listings after "refresh" minutes - phase 2 2009-03-20 11:01 dev * coherence/upnp/services/servers/scheduled_recording_server.py: the missing file 2009-03-19 16:08 dev * coherence/base.py: a fix for the wrong fix in [1209] 2009-03-17 19:20 dev * coherence/upnp/core/service.py, coherence/upnp/core/xml-service-descriptions/ScheduledRecording1.xml, coherence/upnp/devices/media_server.py, coherence/upnp/services/servers/media_receiver_registrar_server.py: the base classes needed to implement a MediaServer with a ScheduledRecording service 2009-03-17 14:00 dev * coherence/base.py: fixing an error with the logfile directive in the XMLConfig 2009-03-15 18:51 dev * coherence/backends/test_storage.py: now with the actual backend 2009-03-15 18:49 dev * Coherence.egg-info/entry_points.txt, bin/coherence, coherence/dbus_service.py, coherence/transcoder.py, coherence/upnp/devices/media_server.py, setup.py: new TestStore backend 2009-03-13 12:19 dev * misc/Rhythmbox-Plugin/upnp_coherence/__init__.py: change UPnP version to :1 for the Rhythmbox MediaServer and the MediaRenderer, so they are detected by clients like the Nokia NSeries phones or XBMC 2009-03-13 09:45 dev * coherence/upnp/core/DIDLLite.py: fix for XML parser oddness is Python2.4 2009-03-11 17:58 dev * coherence/upnp/core/utils.py: trying to find a more generic solution for detection of the IP address to use, re #194 2009-03-09 09:13 dev * coherence/backends/gstreamer_renderer.py: update GStreamer MediaRenderer to not propagate Seeking-capability when handling a http-stream 2009-03-05 19:01 jmsizun * coherence/backends/miro_storage.py: Update listings after "refresh" minutes 2009-02-25 23:25 jmsizun * coherence/backends/miro_storage.py: small corrections to MiroGuide backend 2009-02-25 23:14 jmsizun * Coherence.egg-info/entry_points.txt, coherence/backends/picasa_storage.py, setup.py: New backend for Picasa Web Albums 2009-02-24 17:34 dev * Coherence.egg-info/PKG-INFO, Coherence.egg-info/entry_points.txt, coherence/__init__.py: bump up version number to 0.6.3 2009-02-23 20:35 dev * ChangeLog, Coherence.egg-info/PKG-INFO, MANIFEST.in, NEWS, coherence/__init__.py, setup.py: New in this 0.6.2 - Rosenmontag - release * new MediaServer backends that allow access to * YouTube videos (http://youtube.com) * the MiroGuide for online videos (https://www.miroguide.com) * the videos provided by Shoutcast TV (http://www.shoutcast.com) * the SWR3 podcasts, a German radio station (http://swr3.de) * adjustments to the Ampache backend to work with newer Ampache versions (http://ampache.org) * a lot of 'compatibility' enhancements for different devices * a 'port' to the OpenEmbedded platform (http://www.openembedded.org/), bringing Coherence to the BeagleBoard (http://beagleboard.org/) * and - as every time - the usual bugfixes and enhancements Kudos go especially to jmsizun for his work on the new backends! 2009-02-23 19:54 dev * coherence/backends/axiscam_storage.py, coherence/backends/swr3_storage.py: some minor adjustments 2009-02-23 19:52 dev * coherence/backends/youtube_storage.py, coherence/extern/youtubedl/youtubedl.py, coherence/upnp/core/utils.py: * some last-minute YouTube related fixes, where we had GET instead of HEAD requests :-/ * changing prints to log-output 2009-02-19 21:06 jmsizun * coherence/backends/miro_storage.py: MiroGuide: added new directories (Top Rated, Most Popular, Recent Videos) 2009-02-18 23:44 jmsizun * coherence/backends/miro_storage.py, coherence/backends/youtube_storage.py: connect Miro backend to correct proxy class 2009-02-18 21:34 jmsizun * coherence/backends/miro_storage.py, coherence/backends/youtube_storage.py: LazyContainer/MiroGuide: allow retrieving full listings instead of only first page 2009-02-18 19:28 jmsizun * coherence/backends/miro_storage.py, coherence/backends/youtube_storage.py: Removed children classes from LazyContainer 2009-02-17 23:12 jmsizun * coherence/backends/miro_storage.py: MiroGuide: correctly retrieve list of channels for categories with space and other special characters 2009-02-17 14:27 dev * coherence/backends/youtube_storage.py, coherence/upnp/core/utils.py, docs/coherence.conf.example: * more cleanups, seems to work now in 'buffer' mode with Totem and the PS3 * there are probably still some unnecessary code pieces in there, need to get rid of them in the next step 2009-02-16 21:36 dev * coherence/backends/youtube_storage.py, coherence/upnp/core/utils.py: some more cleanups 2009-02-16 20:30 dev * coherence/backends/youtube_storage.py, coherence/upnp/core/utils.py: cleaning up things a bit 2009-02-16 17:35 dev * MANIFEST.in, setup.py: and the corresponding changes in the setup/dist related files, re #190 2009-02-16 17:31 dev * coherence/upnp/devices/media_server.py, misc/Desktop Applet, misc/Desktop-Applet, misc/EOG Plugin, misc/EOG-Plugin, misc/Rhythmbox Plugin, misc/Rhythmbox-Plugin, misc/Totem Plugin, misc/Totem-Plugin, misc/device icons, misc/device-icons: replace 'space' characters in folder names with dashes, addresses #190 2009-02-15 14:29 dev * coherence/backends/youtube_storage.py: making the YouTubeStore a bit more XBox friendly 2009-02-15 11:57 dev * coherence/backends/youtube_storage.py, coherence/upnp/core/utils.py: rework of the YouTube VideoProxyItem, now works with the PS3 but we seem to have some severe memory leakage 2009-02-14 12:04 dev * coherence/backends/itv_storage.py: fixing some UPnP related lapses 2009-02-13 23:25 jmsizun * coherence/backends/youtube_storage.py: Corrected cache purge method (took most recent files first) 2009-02-13 23:24 jmsizun * coherence/backends/youtube_storage.py: Youtube entries: Add mp4 extension to URL 2009-02-13 22:58 jmsizun * Coherence.egg-info/entry_points.txt, coherence/backends/itv_storage.py, setup.py: Added new backend (ITVStore) to stream Shoutcast TV 2009-02-13 22:57 jmsizun * coherence/upnp/core/DIDLLite.py: Corrected syntax for AudioItem parameter 2009-02-13 20:44 dev * coherence/backends/miro_storage.py: minor fix 2009-02-13 20:40 dev * Coherence.egg-info/entry_points.txt, setup.py: putting the MiroStore entrypoint at the right place 2009-02-13 20:35 dev * setup.py: fixing not-having-setuptools, this time hopefully for real 2009-02-13 17:40 jmsizun * Coherence.egg-info/entry_points.txt: removed erroneous "w" character 2009-02-12 22:18 jmsizun * Coherence.egg-info/entry_points.txt, coherence/backends/miro_storage.py: New media backend: MIRO Guide Catalog for on-line videos 2009-02-12 22:16 jmsizun * coherence/backends/youtube_storage.py: minor corrections to Proxy class 2009-02-12 22:14 jmsizun * coherence/backend.py: small syntax correction in commentary message 2009-02-10 23:36 jmsizun * coherence/backends/youtube_storage.py: Added new option "standard_feeds" to show/hide standard feeds 2009-02-10 23:30 jmsizun * coherence/upnp/core/utils.py: Avoid error in Windows as iface is actually a dict object 2009-02-10 23:28 jmsizun * coherence/upnp/core/utils.py: postpone seek beyond current size of the buffer file 2009-02-10 19:02 jmsizun * coherence/upnp/core/utils.py: Oups: missing class again (edited directly in Twisted) 2009-02-10 17:34 dev * coherence/backends/fs_storage.py: more debug output to catch issues during folder walk 2009-02-10 07:27 jmsizun * coherence/upnp/core/utils.py: Missing file for "youtube proxy: manage file download to cache & buffering" 2009-02-09 21:18 jmsizun * coherence/backends/youtube_storage.py: youtube proxy: manage file download to cache & buffering 2009-02-09 20:30 dev * coherence/backends/fs_storage.py: cushion the non-existence of ctypes 2009-02-06 10:48 dev * coherence/upnp/core/action.py: be sure our InstanceID is an int 2009-02-05 16:05 dev * coherence/transcoder.py, coherence/upnp/devices/media_server.py: a little more of logging 2009-02-05 16:04 dev * coherence/upnp/core/device.py: prepare device icons for the inspector 2009-02-03 18:21 jmsizun * coherence/backends/youtube_storage.py: Added param "proxy-mode": foreseen values are redirect, cache, buffered-cache, ... 2009-02-02 23:59 jmsizun * coherence/backends/youtube_storage.py: Moved Youtube specific code (extraction of video from web page) out of Proxy class (to get a generic Proxy class) 2009-02-02 10:16 dev * coherence/upnp/core/DIDLLite.py, coherence/upnp/core/soap_service.py: treat the Philips-TV a little bit different 2009-02-02 00:11 jmsizun * coherence/backends/youtube_storage.py, coherence/extern/youtubedl/youtubedl.py: Youtube: added support for HD video + merged youtube-dl version 09.01.31 2009-01-31 13:58 dev * coherence/backends/mediadb_storage.py: return path as utf-8 2009-01-31 12:35 dev * coherence/backends/mediadb_storage.py: appending the extension to the url 2009-01-31 12:34 dev * coherence/upnp/core/DIDLLite.py: removing duplicate element and fixing DLNA flags bug 2009-01-31 12:27 dev * coherence/transcoder.py: don't just stop without a reason 2009-01-31 11:03 dev * coherence/upnp/core/device.py, coherence/upnp/devices/media_server.py: more url related corrections 2009-01-30 23:30 jmsizun * coherence/backends/youtube_storage.py: Youtube: added support for playlists, subscriptions and localisation of standard feeds. 2009-01-30 22:29 jmsizun * coherence/upnp/devices/media_server.py: small protection when an item children list is None 2009-01-30 21:43 dev * coherence/upnp/core/device.py, coherence/upnp/core/service.py, coherence/upnp/devices/basics.py, coherence/upnp/devices/media_server.py: cleaning up some url issues 2009-01-30 20:06 dev * coherence/backends/fs_storage.py: fixing reaction upon inotify re-/moves 2009-01-30 20:05 dev * coherence/extern/inotify.py: just for convenience 2009-01-30 20:03 dev * bin/coherence: ignore any config file when called with the new '--noconfig' option 2009-01-29 20:44 dev * coherence/backends/flickr_storage.py: adding a 'limit' option for images per folder, 100 is the default 2009-01-29 16:52 dev * coherence/backends/ampache_storage.py: fixing cut 'n paste error, new try 2009-01-28 21:55 dev * coherence/extern/youtubedl/__init__.py, coherence/extern/youtubedl/youtubedl.py: missing files 2009-01-28 21:29 dev * coherence/backends/ampache_storage.py: using the proper element attribute 2009-01-28 09:16 dev * coherence/backends/youtube_storage.py: fix for empty containers 2009-01-28 09:16 dev * coherence/backends/fs_storage.py: warn about having a non-existent content path 2009-01-25 18:13 dev * coherence/backends/ampache_storage.py: get the mimetype from ampache when provided 2009-01-25 17:23 dev * misc/device icons/youtube-icon.png: a device-icon for the new YouTube backend 2009-01-25 15:38 dev * coherence/backends/ampache_storage.py: try a bit harder for the proper mimetype 2009-01-25 12:39 dev * coherence/upnp/core/DIDLLite.py: let's remove the irritating DLNA tags for 'video/*' 2009-01-25 11:37 dev * coherence/backends/youtube_storage.py: do the YouTube redirect retrieval now on our side and cache the final location 2009-01-25 11:34 dev * coherence/transcoder.py: make the lpcm transcoder read from an http uri to 2009-01-24 22:06 dev * coherence/backends/youtube_storage.py: 'userid' or 'login' - we need to find a consistent language 2009-01-24 21:21 dev * coherence/backends/youtube_storage.py: one more typo 2009-01-24 20:30 dev * coherence/backends/youtube_storage.py: typo fix 2009-01-24 18:38 dev * coherence/backends/youtube_storage.py: proper 'we-have-a-user-login' detection 2009-01-24 16:23 dev * Coherence.egg-info/entry_points.txt, coherence/backends/youtube_storage.py, coherence/extern/youtubedl, coherence/upnp/core/utils.py, setup.py: a modified version of [ticket:173 jmsizuns YouTube backend], only partial working atm needs a current version of [http://code.google.com/p/gdata-python-client Google Data APIs Python Client Library] 2009-01-24 16:18 dev * coherence/upnp/core/msearch.py, coherence/upnp/core/ssdp.py: something for the Inspector 2009-01-24 16:18 dev * coherence/base.py: precaution 2009-01-24 16:16 dev * bin/coherence, misc/Desktop Applet/applet-coherence: fix regarding distutils/setuptools 2009-01-23 17:29 dev * bin/coherence, coherence/base.py, misc/Desktop Applet/applet-coherence, setup.py: enable installation without setuptools again 2009-01-23 13:26 dev * coherence/backends/axiscam_storage.py: bug-fix - thx tororo 2009-01-22 13:41 dev * coherence/backend.py, coherence/backends/swr3_storage.py: updated version of the SWR3Store 2009-01-22 11:43 dev * coherence/base.py: a stupid one 2009-01-22 10:08 dev * bin/coherence, coherence/base.py, coherence/extern/simple_config.py: solving improper placement of logging related options in the new XMLConfig 2009-01-21 18:12 dev * coherence/upnp/core/DIDLLite.py: of course _only_ for containers 2009-01-21 18:09 dev * coherence/backends/lolcats_storage.py: fixing id extraction when called from the XBox 2009-01-21 16:17 dev * Coherence.egg-info/requires.txt, setup.py: adding Twisted to the dependencies in setup.py, it will be now downloaded automatically from pypi.python.org 2009-01-21 16:12 dev * coherence/upnp/devices/basics.py, coherence/upnp/services/servers/media_receiver_registrar_server.py: hiding the MediaReceiverRegistrar a bit more 2009-01-21 16:11 dev * coherence/upnp/core/DIDLLite.py: this does look better 2009-01-18 13:16 dev * coherence/upnp/core/DIDLLite.py: and again 2009-01-18 12:44 dev * coherence/upnp/core/DIDLLite.py: and again one for the XBox 2009-01-18 12:36 dev * coherence/backends/fs_storage.py: once more the wmc-hints 2009-01-18 12:19 dev * coherence/upnp/core/DIDLLite.py: more XBox adjustments 2009-01-18 12:07 dev * coherence/backends/flickr_storage.py: adjusting wrong wmc-hint 2009-01-18 11:22 dev * coherence/upnp/core/DIDLLite.py: fixing inconsistency re root-container id 2009-01-18 11:21 dev * coherence/backends/appletrailers_storage.py, coherence/backends/lolcats_storage.py: adding wmc-hints 2009-01-17 14:41 dev * coherence/upnp/core/DIDLLite.py: more DLNA related cleanups 2009-01-17 14:01 dev * coherence/upnp/core/DIDLLite.py, coherence/upnp/devices/media_server.py: more DLNA flag related adjustments 2009-01-16 21:01 dev * coherence/backends/mediadb_storage.py: react on more filetypes and put them at one place 2009-01-15 09:30 dev * coherence/backends/fs_storage.py: adding the filename extension to the resource data to test some client behaviour 2009-01-14 20:04 dev * coherence/upnp/core/event.py: apply the 'right' empty value to an evented StateVariable - thx hugolp for spotting this 2009-01-13 19:22 dev * coherence/extern/inotify.py, coherence/extern/test_inotify.py: revert 'flag_to_human' change that broke the API 2009-01-13 15:42 dev * coherence/extern/test_inotify.py: the trial tests for inotify.py 2009-01-13 15:40 dev * coherence/extern/inotify.py: wrap the callback loop in a try-except clause, so it isn't aborted by a faulty callback 2009-01-13 15:36 dev * coherence/extern/inotify.py: incorporating dialtones changes and tests - thx! Closes #180 2009-01-13 15:33 dev * coherence/upnp/devices/control_point.py: just for safety reasons 2009-01-12 10:59 dev * coherence/upnp/devices/media_server.py: only add the 'X_MS_MediaReceiverRegistrar' to the device description.xml when we talk to an XBox 2009-01-12 10:57 dev * coherence/backends/appletrailers_storage.py, coherence/backends/fs_storage.py, coherence/upnp/core/DIDLLite.py: more 'ordering' related fixes 2009-01-11 16:42 dev * coherence/extern/inotify.py: missing '()'s 2009-01-11 16:38 dev * coherence/backends/fs_storage.py, coherence/extern/inotify.py: only use fallback of inotify system-call when we are on a Linux kernel 2009-01-11 14:27 dev * Coherence.egg-info/PKG-INFO, coherence/__init__.py, coherence/upnp/core/DIDLLite.py, coherence/upnp/core/service.py, coherence/upnp/core/soap_lite.py, coherence/upnp/devices/control_point.py, coherence/upnp/devices/media_server.py: * merging all Coherence-Samsung changes 1073:1081 back into trunk 2009-01-04 13:03 dev * ., ChangeLog, Coherence.egg-info, LICENCE, MANIFEST.in, NEWS, README, bin, coherence, docs, misc, setup.py, tests: rearranging layout Coherence-0.6.6.2/setup.cfg0000644000000000000000000000007311317673117014104 0ustar rootroot[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 Coherence-0.6.6.2/tests/0000755000000000000000000000000011317673117013425 5ustar rootrootCoherence-0.6.6.2/tests/rpc_client.py0000755000175000017500000000420011317660732015537 0ustar devdev#!/usr/bin/env python # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007 Frank Scholz import getopt, sys, string import xmlrpclib command = "status" device = '' volume = 50 uri = '' id = 1000 arguments = {} try: optlist, args = getopt.getopt( sys.argv[1:], "c:d:i:v:u:", ['command=', 'device=', 'id=', 'volume=', 'uri=']) except getopt.GetoptError: print "falsche parameter" sys.exit(1) for option, param in optlist: if option in( '-c', '--command'):command=param if option in( '-d', '--device'):device=param if option in( '-i', '--id'):id=param if option in( '-v', '--volume'):volume=param if option in( '-u', '--uri'):uri=param skip = False for p in sys.argv[1:]: if skip: skip = False continue if p.startswith('-'): skip = True continue k, v = p.split('=') arguments[k] = v #print 'Arguments', arguments s = xmlrpclib.Server('http://127.0.0.1:30020/RPC2') if( command == "ping"): r=s.ping() if( command == "list_devices"): r=s.list_devices() if( command == "mute" and device != ''): r=s.mute_device(device) if( command == "unmute" and device != ''): r=s.unmute_device(device) if( command == "set_volume" and device != ''): r=s.set_volume(device, volume) if( command == "play" and device != ''): r=s.play(device) if( command == "pause" and device != ''): r=s.pause(device) if( command == "stop" and device != ''): r=s.stop(device) if( command == "next" and device != ''): r=s.next(device) if( command == "previous" and device != ''): r=s.previous(device) if( command == "set_av_transport_uri" and device != '' and uri != ''): r=s.set_av_transport_uri(device, uri) if( command == "shutdown"): r=s.shutdown() if(command == "create_object" and device != ''): r = s.create_object(device, id, arguments) if(command == "import_resource" and device != ''): r = s.import_resource(device, arguments['source_uri'], arguments['destination_uri']) if(command == "put_resource"): r = s.put_resource( arguments['url'], arguments['path']) print r Coherence-0.6.6.2/MANIFEST.in0000644000175000017500000000136211317663700013440 0ustar devdevrecursive-exclude debian * include ChangeLog include LICENCE include MANIFEST.in include NEWS include README recursive-include docs * recursive-include tests * recursive-include coherence/extern/log * recursive-include coherence/extern/uuid * recursive-include coherence/ui/icons * recursive-include coherence/upnp/core/xml-service-descriptions * recursive-include coherence/web/static * include misc/coherence-initscript.sh include misc/media_server_observer.py include misc/org.Coherence.service include misc/upnp-tester.py recursive-include misc/device-icons * recursive-include misc/Desktop-Applet * recursive-include misc/EOG-Plugin * recursive-include misc/Nautilus * recursive-include misc/Rhythmbox-Plugin * recursive-include misc/Totem-Plugin * Coherence-0.6.6.2/NEWS0000644000175000017500000002406311317670201012376 0ustar devdev0.6.6.2 - Red-Nosed Reindeer Revolutions - 20100102 * fixing the setup configuration to compensate for the gone 'auto-include-all-files-under-version-control' setuptools feature * generic 'genre' handling in DIDLLite - thx Caleb * fix for broken .face-icon handling in the Rhythmbox plugin 0.6.6.1 - Red-Nosed Reindeer Reloaded - 20091222 * fixing the broken tarball of the 0.6.6 release and adding a missing import that made some backends unfeasible 0.6.6 - Red-Nosed Reindeer - 20091220 * new MediaServer backends that allow access to * Banshee - exports audio and video files from Banshees media db (http://banshee-project.org/) * FeedStore - a MediaServer serving generic RSS feeds * Playlist - exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...) * YAMJ - serves the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library (http://code.google.com/p/moviejukebox/) * updates on Mirabeau - our "UPnP over XMPP" bridge * simplifications in the D-Bus API * a first implementation of an JSON/REST API * advancements of the GStreamer MediaRenderer, supporting now GStreamers playbin2 * upgrade of the DVB-Daemon MediaServer * refinements in the transcoding section, having now the choice to use GStreamer pipelines or external processes like mencoder * more 'compatibility' improvements for different devices (e.g. Samsung TVs or Apache Felix) * and - as every time - the usual bugfixes and enhancements Kudos go to Benjamin (lightyear) Kampmann, Dominik (schrei5) Ruf, Frank (dev) Scholz, Friedrich (frinring) Kossebau, Jean-Michel (jmsizun) Sizun, Philippe (philn) Normand, Sebastian (sebp) Pölsterl, Zaheer (zaheerm) Merali 0.6.4 - Pont Mirabeau - 20090512 * new MediaServer backends that allow access to * Picasa Web Albums (http://picasa.google.com) * a TestServer to easily serve and test interaction with * one or more items and adjust 'upnp_class', 'mimetype' and 'DLNA-flags', * items that are a GStreamer pipeline or an external program * a new - used in parallel - D-Bus API with an 'org.DLNA' interface with the goal to create a common API for all UPnP/DNLA frameworks * support for the dlna-playcontainer URI (http://netzflocken.de/2009/4/23/media-collection-playing-the-dlna-way) * enchancements to the GStreamer MediaRenderer, supporting now dlna-playcontainer and SetNextAVTransportURI, and jumping to previous and next tracks * support for video items served by Ampache (http://ampache.org) * base classes for a ScheduledRecording service * some 'compatibility' adjustments for different devices * and - as every time - the usual bugfixes and enhancements Kudos go to jmsizun, Stecchino, cjsmo, chewi, and lightyear. 0.6.2 - Rosenmontag - 20090223 * new MediaServer backends that allow access to * YouTube videos (http://youtube.com) * the MiroGuide for online videos (https://www.miroguide.com) * the videos provided by Shoutcast TV (http://www.shoutcast.com) * the SWR3 podcasts, a German radio station (http://swr3.de) * adjustments to the Ampache backend to work with newer Ampache versions (http://ampache.org) * a lot of 'compatibility' enhancements for different devices * a 'port' to the OpenEmbedded platform (http://www.openembedded.org/), bringing Coherence to the BeagleBoard (http://beagleboard.org/) * and - as every time - the usual bugfixes and enhancements Kudos go especially to jmsizun for his work on the new backends! 0.6.0 - The late Pumpkin - 20081231 * new MediaServer backends that allow access to * movie trailers from the Apple HD trailers site (http://www.apple.com/trailers) * images hosted at a Gallery site - an open source web based photo album organizer (http://gallery.menalto.com) * Lolcats images from http://icanhascheezburger.com * podcasts from the BBC (http://open.bbc.co.uk/labs/) * videos from TED (http://www.ted.com) * an extended Flickr MediaServer backend * enables user-authenticated access to your Flickr account * access to your images and the one of your frieds via an UPnP device * upload an image directly from an UPnP device to your Flickr account * transcoding of audio files based on GStreamer for DLNA devices like the PS3, and even XBox 360 * several plugins for the Nautlilus filemanager (http://www.gnome.org/projects/nautilus) * sharing folders from within Nautilus * upload files from Nautilus to UPnP A/V MediaServers * play files from Nautilus on an UPnP A/V MediaRenderer * an experimental plugin for EOG - the Gnome Image Viewer (http://projects.gnome.org/eog/) * greatly improved XBox 360 support, including audio transcoding * and the usual bugfixes and enhancements Kudos go especially to jmsizun, lightyear, superdump for their work on the backends and their patient debugging sessions! 0.5.8 - Trix and Flix - 20080630 * a MediaServer backend for DVB-Daemon (http://www.k-d-w.org/node/42) * exporting atm the stored recordings * allowing to delete recordings from within a UPnP client, when enabled on the backend * will export EPG data and allow scheduling via UPnP in the future * client device and service implementations for BinaryLight and DimmableLight devices * rework of the D-Bus support * should now be usable from other languages (C,Perl,..) too * support for activating/deactivation a backend via D-Bus, allowing for instance to start a MediaServer backend via D-Bus * a plugin for Totem (http://www.gnome.org/projects/totem/) * enabling Totem to detect and browse UPnP A/V MediaServers * using only D-Bus to communicate with a Coherence instance * a basic reusable PyGTK based UPnP A/V ControlPoint widget, used in the Totem plugin * rework (again) of the XBox 360 support - getting closer * our first set of unit tests * include a copy of Louie (http://pylouie.org) to solve a setuptools runtime dependency issue and make the life of distribution packagers a bit easier * and the usual bugfixes and enhancements 0.5.6 - Walpurgisnacht - 20080430 * a MediaServer backend for Meta Tracker (http://www.gnome.org/projects/tracker) * simplify the IP-address detection on Windows with the help of the optional Python package netifaces (http://alastairs-place.net/netifaces) * proper handling of filename encoding issues in the filesystem based MediaServer * and a lot of community driven fixes and enhancements, kudos given representatively to Lawrence and veerz 0.5.4 - Fools Garden - 20080401 * a DesktopApplet to easily start a Coherence instance from your desktops panel Thx to Erwan Velu, Helio Chissini de Castro and Nicolas Lécureuil! * more efforts to simplify the ordinary user experience * allow now the backend definition via commandline, to just start up a MediaServer or anything else, without bothering oneself with the config file * specify logfile location and daemonization on the commandline too * a bit more usable --help output Thx again Erwan Velu! * a MediaServer backend for Ampache - a Web-based Audio file manager (http://ampache.org) Thx to the awesome help of Karl Vollmer! * device implementations for BinaryLight and DimmableLight * a little helper to extract device and service xml files and send them to us - a beginning of our UPnP device fingerprint program * and the usual bugfixes and enhancements 0.5.2 - Little Leap - 20080229 * rework of the XBox 360 support * refinements and improvements on the client side API, incorporating things we learned on extending the Rhythmbox UPnP plugin * some efforts on smoothing the "just use it as a MediaServer/-Renderer" user experience * rearrangement in the way multiple res elements are ordered, fixes issues which we had with some UPnP stacks and their 'wayward' interpretation of the specs * and a lot more of the usual bugfixes and enhancements 0.5.0 - 20071231 * better DLNA support, in particular for the Sony Playstation 3 * a MediaServer backend for Shoutcast internet radio streams * an experimental last.fm MediaServer backend for the last.fm service * provide methods to remove local devices from a Coherence instance * slow move to an XML based configuration file * support for BSD systems - thx kraft! * an emerging D-Bus interface * more platform independency for our Twisted inotify module, using libc when possible * and a lot more of the usual bugfixes and enhancements 0.4.0 - 20070731 * integration of a new logging module logging can now be configured via the config file or through an environment variable COHERENCE_DEBUG, which overrides the config values. Usage is like COHERENCE_DEBUG=*:3 emit INFO level messages from all modules COHERENCE_DEBUG=*:2,ssdp:4 WARNING level messages from all modules, plus debug level for the ssdp module * removed the dependency for SOAPpy, now using own methods and ElementTree only * start reworking the client API, to make things there easier too, see as an example https://coherence.beebits.net/wiki/CoherenceMediaRenderer * serving cover art now to DLNA MediaRenderers * refinements on the object creation and the import into the MediaServers * an installable package for the Nokia Maemo platform on the N800, complete with all dependencies, thanks to Rob Taylor of http://codethink.co.uk * and a lot more of the usual bugfixes and enhancements 0.3.0 - 20070611 * better DLNA support, especially for the PlayStation 3 * cover art in the MediaServers * object creation and import in the MediaServers * a new experimental MediaServer with an All, Artist, Album based structure * support for deployment on the Nokia N800 - notably a working GStreamer UPnP MediaRenderer there, with mp3 and ogg playback * an album art (helper) module to fetch the album covers from the Amazon WebService * icon support in the UPnP device description * the usual bugfixes 0.2.1 - 20070403 * bugfix release 0.2.0 - 200070401 * fixes,fixes and more fixes 0.1.0 - 20070215 * first public release Coherence-0.6.6.2/PKG-INFO0000644000000000000000000000540211317673117013361 0ustar rootrootMetadata-Version: 1.0 Name: Coherence Version: 0.6.6.2 Summary: Coherence - DLNA/UPnP framework for the digital living Home-page: http://coherence-project.org Author: Frank Scholz Author-email: dev@coherence-project.org License: MIT Download-URL: http://coherence-project.org/download/Coherence-0.6.6.2.tar.gz Description: Coherence is a framework written in Python, providing a variety of UPnP MediaServer and UPnP MediaRenderer implementations for instant use. It includes an UPnP ControlPoint, which is accessible via D-Bus too. Furthermore it enables your application to participate in digital living networks, at the moment primarily the DLNA/UPnP universe. Its objective and demand is to relieve your application from all the membership/the UPnP related tasks as much as possible. New in this 0.6.6.2 - the Red-Nosed Reindeer Revolutions - release * new MediaServer backends that allow access to * Banshee - exports audio and video files from Banshees media db (http://banshee-project.org/) * FeedStore - a MediaServer serving generic RSS feeds * Playlist - exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...) * YAMJ - serves the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library (http://code.google.com/p/moviejukebox/) * updates on Mirabeau - our "UPnP over XMPP" bridge * simplifications in the D-Bus API * a first implementation of an JSON/REST API * advancements of the GStreamer MediaRenderer, supporting now GStreamers playbin2 * upgrade of the DVB-Daemon MediaServer * refinements in the transcoding section, having now the choice to use GStreamer pipelines or external processes like mencoder * more 'compatibility' improvements for different devices (e.g. Samsung TVs or Apache Felix) * and - as every time - the usual bugfixes and enhancements Kudos go to: * Benjamin (lightyear) Kampmann, * Charlie (porthose) Smotherman * Dominik (schrei5) Ruf, * Frank (dev) Scholz, * Friedrich (frinring) Kossebau, * Jean-Michel (jmsizun) Sizun, * Philippe (philn) Normand, * Sebastian (sebp) Poelsterl, * Zaheer (zaheerm) Merali Keywords: UPnP,DLNA,multimedia,gstreamer Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Web Environment Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Coherence-0.6.6.2/docs/0000755000000000000000000000000011317673117013213 5ustar rootrootCoherence-0.6.6.2/docs/mirabeau.xml0000644000175000017500000000215011317660733015140 0ustar devdev 30020 yes yes Mirabeau gabble jabber myjabberaccount@gmail.com mypassword talk.google.com 5223 True True conference.jabber.org 81.38.191.92:30020 Coherence-0.6.6.2/docs/test-store-example.xml0000644000175000017500000000060011317660733017113 0ustar devdev 30020 video/mpeg /tmp/movie.mpg object.item.videoItem DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0 Test TestStore Coherence-0.6.6.2/docs/coherence.conf.example0000644000175000017500000000731011317660733017070 0ustar devdev# config file for coherence # # keywords are case-sensitive logmode = warning # none, error, warning, info, debug, log #logfile = coherence.log #interface = eth0 serverport = 30020 # if not specified or set to 0 # coherence will let the OS choose the port controlpoint = yes # if set to yes coherence will activate its # internal ControlPoint web-ui = no # set this to yes to enable some interactive # Web-UI #use_dbus = yes # set this to yes to let Coherence signal # devices and services via dbus and provide # access to them # only useful when controlpoint is enabled too [subsystem_log] #coherence = info #WebUI = info #SSDP = info #MSEARCH = info #SOAP = info #Action = info #Variable = info #Event = info #Service_Server = info #Service_Client = info #Device = info #Connection_Manager_Server = info #MediaServer= info #MediaRenderer= log #ControlPoint = info #MS_Client = info #flickr_storage = info #Buzztard=info [plugins] [[FSStore]] # content = tests/content/audio,tests/content/video # append more directories separated by commas # version = 1 # #content = /data/audio/music name = Coherence Test Content # [[[icon]]] # mimetype = image/png # width = 120 # height = 106 # depth = 24 # url = file:///home/dev/Projects/Coherence/tests/coherence-icon.png #[[GStreamerPlayer]] # name = GStreamer Audio Player #[[AmpacheStore]] # name = Ampache # url = http://localhost/ampache/server/xml.server.php # key = your password # not your normal ampache user password, # this is for the XML-API # user = your user # optional #[[TrackerStore]] # name = Tracker #[[ElisaPlayer]] # name = Elisa # host = localhost #[[IRadioStore]] # name = iRadio [[FlickrStore]] name = Flickr Images # refresh = 60 # check every 60 minutes with Flickr for new images in the set # proxy = yes # if set to yes coherence will fetch the images # # from Flickr for your client # [[[icon]]] # mimetype = image/png # width = 98 # height = 26 # depth = 24 # url = file:///home/dev/Projects/Coherence/tests/flickr-icon.png #[[MediaStore]] # medialocation = /data/audio/music # append more directories seperated by commas # #medialocation = tests/content/audio # coverlocation = /data/audio/covers # optional # mediadb= /tmp/media.db # name = Coherence MediaStore # [[[icon]]] # mimetype = image/png # height = 48 # width = 48 # depth = 24 # url = file:///home/dev/elisa/elisa.png #[[AxisCamStore]] # name = Elisa is watching you # [[[Cam 1]]] # url = http://192.168.1.222:554/mpeg4/1/media.amp # protocol = rtsp-rtp-udp:*:video/MP4V-ES:* # [[[Cam 2]]] # url = http://192.168.1.222:554/mpeg4/2/media.amp # protocol = rtsp-rtp-udp:*:video/MP4V-ES:* #[[BuzztardStore]] # name = Buzztard # host = localhost # port = 7654 #[[BuzztardPlayer]] # name = Buzztard # host = localhost # port = 7654 Coherence-0.6.6.2/docs/man/0000755000000000000000000000000011317673117013766 5ustar rootrootCoherence-0.6.6.2/docs/man/coherence.10000644000175000017500000000366711317660733015437 0ustar devdev.TH "coherence" "1" "Thur Mar 19 2009" "Frank Scholz" "Python UPnP Framwork" .SH "NAME" .LP Coherence \- Is a Python UPnP framework which enabling your application to participate in digital living networks, at the moment primarily the UPnP universe. Its goal is to relieve your application from all the membership and UPnP related tasks as much as possible. The core of Coherence provides a (hopefully complete) implementation of: * a SSDP server, * a MSEARCH client, * server and client for HTTP/SOAP requests, and * server and client for Event Subscription and Notification (GENA). .SH "SYNTAX" .LP coherence \-\-<\fIOPTION\fP> : <\fISTORE_TYPE\fP>, url:<\fIURL\fP>, key:<\fIKEY\fP> .SH "OPTIONS" .LP .TP \fB\-d\fR \fB\-\-daemon\fR daemonize .TP \fB\-v\fR \fB\-\-version\fR print version .TP \fB\-c\fR \fB\-\-configfile=\fR<\fIPATH\fP> Path to config file .TP \fB \fR \fB\-\-noconfig\fR ignore any config file found .TP \fB\-o\fR \fB\-\-option=\fR activate option .TP \fB\-l\fR \fB\-\-logfile=<\fIPATH\fP> Path to log file. .TP \fB \fR \fB\-\-help Displays coherence options. .SH "AVAILABLE STORES" BetterLight, AmpacheStore, FlickrStore, MiroStore, ElisaPlayer, .br ElisaMediaStore, Gallery2Store, DVBDStore, FSStore, .br BuzztardPlayer, BuzztardStore, GStreamerPlayer, SimpleLight, .br ITVStore, SWR3Store, TrackerStore, LolcatsStore, BBCStore, .br MediaStore, AppleTrailerStore, LastFMStore, AxisCamStore, .br YouTubeStore, TEDStore, IRadioStore .SH "FILES" .LP \fI/home/name/.coherence\fP .SH "ENVIRONMENT VARIABLES" .LP .TP \fBCOHERENCE_DEBUG=<\fISTORE\fP>\fP Supplies debug information pertaining to the named store. .SH "EXAMPLES" .LP coherence \-\-plugin=backend:FSStore,name:MyCoherence .SH "AUTHORS" .LP Coherence was written by Frank Scholz . This man page was created by Charlie Smotherman for Frank Scholz and the Debian Project. .SH "SEE ALSO" .LP http://coherence.beebits.net/ Coherence-0.6.6.2/bin/0000755000000000000000000000000011317673117013033 5ustar rootrootCoherence-0.6.6.2/bin/coherence0000755000175000017500000002550311317660733014341 0ustar devdev#! /usr/bin/python # # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007,2008 Frank Scholz """ Coherence is a framework to host DLNA/UPnP devices For more information about it and its available backends point your browser to: http://coherence-project.org """ import os, sys import string from coherence import __version__ from coherence.extern.simple_config import Config,ConfigItem from twisted.python import usage, text """ thankfully taken from twisted.scripts._twistd_unix.py """ def daemonize(): # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16 if os.fork(): # launch child and... os._exit(0) # kill off parent os.setsid() if os.fork(): # launch child and... os._exit(0) # kill off parent again. os.umask(077) null=os.open('/dev/null', os.O_RDWR) for i in range(3): try: os.dup2(null, i) except OSError, e: if e.errno != errno.EBADF: raise os.close(null) """ taken with minor adjustments from twisted.python.text.py """ def greedyWrap(inString, width=80): """Given a string and a column width, return a list of lines. Caveat: I'm use a stupid greedy word-wrapping algorythm. I won't put two spaces at the end of a sentence. I don't do full justification. And no, I've never even *heard* of hypenation. """ outLines = [] #eww, evil hacks to allow paragraphs delimited by two \ns :( if inString.find('\n\n') >= 0: paragraphs = inString.split('\n\n') for para in paragraphs: outLines.extend(greedyWrap(para, width) + ['']) return outLines inWords = inString.split() column = 0 ptr_line = 0 while inWords: column = column + len(inWords[ptr_line]) ptr_line = ptr_line + 1 if (column > width): if ptr_line == 1: # This single word is too long, it will be the whole line. pass else: # We've gone too far, stop the line one word back. ptr_line = ptr_line - 1 (l, inWords) = (inWords[0:ptr_line], inWords[ptr_line:]) outLines.append(string.join(l,' ')) ptr_line = 0 column = 0 elif not (len(inWords) > ptr_line): # Clean up the last bit. outLines.append(' '.join(inWords)) del inWords[:] else: # Space column = column + 1 # next word return outLines """ taken with minor adjustments from twisted.python.usage.py """ def docMakeChunks(optList, width=80): """ Makes doc chunks for option declarations. Takes a list of dictionaries, each of which may have one or more of the keys 'long', 'short', 'doc', 'default', 'optType'. Returns a list of strings. The strings may be multiple lines, all of them end with a newline. """ # XXX: sanity check to make sure we have a sane combination of keys. maxOptLen = 0 for opt in optList: optLen = len(opt.get('long', '')) if optLen: if opt.get('optType', None) == "parameter": # these take up an extra character optLen = optLen + 1 maxOptLen = max(optLen, maxOptLen) colWidth1 = maxOptLen + len(" -s, -- ") colWidth2 = width - colWidth1 # XXX - impose some sane minimum limit. # Then if we don't have enough room for the option and the doc # to share one line, they can take turns on alternating lines. colFiller1 = " " * colWidth1 optChunks = [] seen = {} for opt in optList: if opt.get('short', None) in seen or opt.get('long', None) in seen: continue for x in opt.get('short', None), opt.get('long', None): if x is not None: seen[x] = 1 optLines = [] comma = " " if opt.get('short', None): short = "-%c" % (opt['short'],) else: short = '' if opt.get('long', None): long = opt['long'] if opt.get("optType", None) == "parameter": long = long + '=' long = "%-*s" % (maxOptLen, long) if short: comma = "," else: long = " " * (maxOptLen + len('--')) if opt.get('optType', None) == 'command': column1 = ' %s ' % long else: column1 = " %2s%c --%s " % (short, comma, long) if opt.get('doc', ''): doc = opt['doc'].strip() else: doc = '' if (opt.get("optType", None) == "parameter") \ and not (opt.get('default', None) is None): doc = "%s [default: %s]" % (doc, opt['default']) if (opt.get("optType", None) == "parameter") \ and opt.get('dispatch', None) is not None: d = opt['dispatch'] if isinstance(d, usage.CoerceParameter) and d.doc: doc = "%s. %s" % (doc, d.doc) if doc: column2_l = greedyWrap(doc, colWidth2) else: column2_l = [''] optLines.append("%s%s\n" % (column1, column2_l.pop(0))) for line in column2_l: optLines.append("%s%s\n" % (colFiller1, line)) optChunks.append(''.join(optLines)) return optChunks usage.docMakeChunks = docMakeChunks def setConfigFile(): def findConfigDir(): try: configDir = os.path.expanduser('~') except: configDir = os.getcwd() return configDir return os.path.join( findConfigDir(), '.coherence') class Options(usage.Options): optFlags = [['daemon','d', 'daemonize'], ['noconfig', None, 'ignore any configfile found'], ['version','v', 'print out version'] ] optParameters = [['configfile', 'c', setConfigFile(), 'configfile'], ['logfile', 'l', None, 'logfile'], ['option', 'o', None, 'activate option'], ['plugin', 'p', None, 'activate plugin'], ] def __init__(self): usage.Options.__init__(self) self['plugins'] = [] self['options'] = {} def opt_version(self): print "Coherence version:", __version__ sys.exit(0) def opt_help(self): sys.argv.remove('--help') from coherence.base import Plugins p = Plugins() for opt,doc in self.docs.items(): if opt == 'plugin': self.docs[opt] = doc + '\n\nExample: --plugin=backend:FSStore,name:MyCoherence\n\nAvailable backends are:\n\n' self.docs[opt] += ', '.join(p.keys()) print self.__str__() sys.exit(0) def opt_plugin(self,option): self['plugins'].append(option) def opt_option(self,option): try: key,value = option.split(':') self['options'][key] = value except: pass def main(config): from coherence.base import Coherence c = Coherence(config) #c = Coherence(plugins={'FSStore': {'content_directory':'tests/content'}, # 'Player': {}) #c.add_plugin('FSStore', content_directory='tests/content', version=1) if __name__ == '__main__': options = Options() try: options.parseOptions() except usage.UsageError, errortext: print '%s: %s' % (sys.argv[0], errortext) print '%s: Try --help for usage details.' % (sys.argv[0]) sys.exit(0) try: if options['daemon'] == 1: daemonize() except: print traceback.format_exc() config = {} if options['noconfig'] != 1: try: config = Config(options['configfile'],root='config').config except SyntaxError: import traceback #print traceback.format_exc() try: from configobj import ConfigObj config = ConfigObj(options['configfile']) except: print "hmm, seems we are in trouble reading in any sort of config file" print traceback.format_exc() except IOError: print "no config file %r found" % options['configfile'] pass if options['logfile'] != None: if isinstance(config,(ConfigItem,dict)): if 'logging' not in config: config['logging'] = {} config['logging']['logfile'] = options['logfile'] else: config['logfile'] = options['logfile'] for k,v in options['options'].items(): if k == 'logfile': continue config[k] = v if options['daemon'] == 1: if isinstance(config,(ConfigItem,dict)): if config.get('logging',None) == None: config['logging'] = {} if config['logging'].get('logfile',None) == None: config['logging']['level'] = 'none' try: del config['logging']['logfile'] except KeyError: pass else: if config.get('logfile',None) == None: config.set('logmode','none') try: del config['logfile'] except KeyError: pass if(config.get('use_dbus', 'no') == 'yes' or config.get('glib', 'no') == 'yes' or config.get('transcoding', 'no') == 'yes'): try: from twisted.internet import glib2reactor glib2reactor.install() except AssertionError: print "error installing glib2reactor" if len(options['plugins']) > 0: plugins = config.get('plugin') if isinstance(plugins,dict): config['plugin']=[plugins] if plugins is None: plugins = config.get('plugins',None) if plugins == None: config['plugin'] = [] plugins = config['plugin'] while len(options['plugins']) > 0: p = options['plugins'].pop() plugin = {} plugin_conf = p.split(',') for pair in plugin_conf: pair = pair.split(':',1) if len(pair) == 2: pair[0] = pair[0].strip() if pair[0] in plugin: if not isinstance(plugin[pair[0]],list): new_list = [plugin[pair[0]]] plugin[pair[0]] = new_list plugin[pair[0]].append(pair[1]) else: plugin[pair[0]] = pair[1] try: plugins.append(plugin) except AttributeError: print "mixing commandline plugins and configfile does not work with the old config file format" from twisted.internet import reactor reactor.callWhenRunning(main, config) reactor.run() Coherence-0.6.6.2/coherence/0000755000000000000000000000000011317673117014216 5ustar rootrootCoherence-0.6.6.2/coherence/ui/0000755000000000000000000000000011317673117014633 5ustar rootrootCoherence-0.6.6.2/coherence/ui/av_widgets.py0000644000175000017500000010017611317660740016764 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ simple and hopefully reusable widgets to ease the creation of UPnP UI applications icons taken from the Tango Desktop Project """ import os.path import urllib import traceback import pygtk pygtk.require("2.0") import gtk import gobject import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import dbus.service import mimetypes mimetypes.init() # dbus defines BUS_NAME = 'org.Coherence' OBJECT_PATH = '/org/Coherence' # gtk store defines NAME_COLUMN = 0 ID_COLUMN = 1 UPNP_CLASS_COLUMN = 2 CHILD_COUNT_COLUMN = 3 UDN_COLUMN = 4 SERVICE_COLUMN = 5 ICON_COLUMN = 6 DIDL_COLUMN = 7 TOOLTIP_ICON_COLUMN = 8 from pkg_resources import resource_filename class ControlPoint(object): _instance_ = None # Singleton def __new__(cls, *args, **kwargs): obj = getattr(cls, '_instance_', None) if obj is not None: return obj else: obj = super(ControlPoint, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj obj._connect(*args, **kwargs) return obj def __init__(self): pass def _connect(self): self.bus = dbus.SessionBus() self.coherence = self.bus.get_object(BUS_NAME,OBJECT_PATH) class DeviceExportWidget(object): def __init__(self,name='Nautilus',standalone=True,root=None): self.root=root self.uuid = None self.name = name self.standalone=standalone icon = resource_filename(__name__, os.path.join('icons','emblem-new.png')) self.new_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','emblem-shared.png')) self.shared_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','emblem-unreadable.png')) self.unshared_icon = gtk.gdk.pixbuf_new_from_file(icon) self.filestore = gtk.ListStore(str,gtk.gdk.Pixbuf) self.coherence = ControlPoint().coherence def build_ui(self,root=None): if root != None: self.root = root self.window = gtk.VBox(homogeneous=False, spacing=0) self.fileview = gtk.TreeView(self.filestore) column = gtk.TreeViewColumn('Folders to share') self.fileview.append_column(column) icon_cell = gtk.CellRendererPixbuf() text_cell = gtk.CellRendererText() column.pack_start(icon_cell, False) column.pack_start(text_cell, True) column.set_attributes(text_cell, text=0) column.add_attribute(icon_cell, "pixbuf",1) self.window.pack_start(self.fileview,expand=True,fill=True) buttonbox = gtk.HBox(homogeneous=False, spacing=0) button = gtk.Button(stock=gtk.STOCK_ADD) button.set_sensitive(False) button.connect("clicked", self.new_files) buttonbox.pack_start(button, expand=False,fill=False, padding=2) button = gtk.Button(stock=gtk.STOCK_REMOVE) #button.set_sensitive(False) button.connect("clicked", self.remove_files) buttonbox.pack_start(button, expand=False,fill=False, padding=2) button = gtk.Button(stock=gtk.STOCK_CANCEL) button.connect("clicked", self.share_cancel) buttonbox.pack_start(button, expand=False,fill=False, padding=2) button = gtk.Button(stock=gtk.STOCK_APPLY) button.connect("clicked", self.share_files) buttonbox.pack_start(button, expand=False,fill=False, padding=2) self.window.pack_end(buttonbox,expand=False,fill=False) return self.window def share_cancel(self,button): for row in self.filestore: print row if row[1] == self.new_icon: del row continue if row[1] == self.unshared_icon: row[1] = self.shared_icon if self.standalone: gtk.main_quit() else: self.root.hide() def share_files(self,button): print "share_files with", self.uuid folders = [] for row in self.filestore: if row[1] == self.unshared_icon: del row continue folders.append(row[0]) if self.uuid == None: if len(folders) > 0: self.uuid = self.coherence.add_plugin('FSStore', {'name': self.name, 'version':'1', 'create_root': 'yes', 'import_folder': '/tmp/UPnP Imports', 'content':','.join(folders)}, dbus_interface=BUS_NAME) #self.coherence.pin('Nautilus::MediaServer::%d'%os.getpid(),self.uuid) else: result = self.coherence.call_plugin(self.uuid,'update_config',{'content':','.join(folders)}) if result != self.uuid: print "something failed", result for row in self.filestore: row[1] = self.shared_icon self.root.hide() def add_files(self,files): print "add_files", files for filename in files: for row in self.filestore: if os.path.abspath(filename) == row[0]: break else: self.add_file(filename) def add_file(self,filename): self.filestore.append([os.path.abspath(filename),self.new_icon]) def new_files(self,button): print "new_files" def remove_files(self,button): print "remove_files" selection = self.fileview.get_selection() print selection model, selected_rows = selection.get_selected_rows() for row_path in selected_rows: #model.remove(model.get_iter(row_path)) row = model[row_path] row[1] = self.unshared_icon class DeviceImportWidget(object): def __init__(self,standalone=True,root=None): self.standalone=standalone self.root=root self.build_ui() self.init_controlpoint() def build_ui(self): self.window = gtk.VBox(homogeneous=False, spacing=0) self.combobox = gtk.ComboBox() self.store = gtk.ListStore(str, # 0: friendly name str, # 1: device udn gtk.gdk.Pixbuf) icon = resource_filename(__name__, os.path.join('icons','network-server.png')) self.device_icon = gtk.gdk.pixbuf_new_from_file(icon) # create a CellRenderers to render the data icon_cell = gtk.CellRendererPixbuf() text_cell = gtk.CellRendererText() self.combobox.pack_start(icon_cell, False) self.combobox.pack_start(text_cell, True) self.combobox.set_attributes(text_cell, text=0) self.combobox.add_attribute(icon_cell, "pixbuf",2) self.combobox.set_model(self.store) item = self.store.append(None) self.store.set_value(item, 0, 'Select a MediaServer...') self.store.set_value(item, 1, '') self.store.set_value(item, 2, None) self.combobox.set_active(0) self.window.pack_start(self.combobox,expand=False,fill=False) self.filestore = gtk.ListStore(str) self.fileview = gtk.TreeView(self.filestore) column = gtk.TreeViewColumn('Files') self.fileview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, True) column.set_attributes(text_cell, text=0) self.window.pack_start(self.fileview,expand=True,fill=True) buttonbox = gtk.HBox(homogeneous=False, spacing=0) button = gtk.Button(stock=gtk.STOCK_ADD) button.set_sensitive(False) button.connect("clicked", self.new_files) buttonbox.pack_start(button, expand=False,fill=False, padding=2) button = gtk.Button(stock=gtk.STOCK_REMOVE) button.set_sensitive(False) button.connect("clicked", self.remove_files) buttonbox.pack_start(button, expand=False,fill=False, padding=2) button = gtk.Button(stock=gtk.STOCK_CANCEL) if self.standalone: button.connect("clicked", gtk.main_quit) else: button.connect("clicked", lambda x: self.root.destroy()) buttonbox.pack_start(button, expand=False,fill=False, padding=2) button = gtk.Button(stock=gtk.STOCK_APPLY) button.connect("clicked", self.import_files) buttonbox.pack_start(button, expand=False,fill=False, padding=2) self.window.pack_end(buttonbox,expand=False,fill=False) def add_file(self,filename): self.filestore.append([os.path.abspath(filename)]) def new_files(self,button): print "new_files" def remove_files(self,button): print "remove_files" def import_files(self,button): print "import_files" active = self.combobox.get_active() if active <= 0: print "no MediaServer selected" return None friendlyname, uuid,_ = self.store[active] try: row = self.filestore[0] print 'import to', friendlyname,os.path.basename(row[0]) def success(r): print 'success',r self.filestore.remove(self.filestore.get_iter(0)) self.import_files(None) def reply(r): print 'reply',r['Result'], r['ObjectID'] from coherence.upnp.core import DIDLLite didl = DIDLLite.DIDLElement.fromString(r['Result']) item = didl.getItems()[0] res = item.res.get_matching(['*:*:*:*'], protocol_type='http-get') if len(res) > 0: print 'importURI',res[0].importUri self.coherence.put_resource(res[0].importUri,row[0], reply_handler=success, error_handler=self.handle_error) mimetype,_ = mimetypes.guess_type(row[0], strict=False) if mimetype.startswith('image/'): upnp_class = 'object.item.imageItem' elif mimetype.startswith('video/'): upnp_class = 'object.item.videoItem' elif mimetype.startswith('audio/'): upnp_class = 'object.item.audioItem' else: upnp_class = 'object.item' self.coherence.create_object(uuid,'DLNA.ORG_AnyContainer', {'parentID':'DLNA.ORG_AnyContainer','upnp_class':upnp_class,'title':os.path.basename(row[0])}, reply_handler=reply, error_handler=self.handle_error) except IndexError: pass def handle_error(self,error): print error def handle_devices_reply(self,devices): for device in devices: if device['device_type'].split(':')[3] == 'MediaServer': self.media_server_found(device) def init_controlpoint(self): cp = ControlPoint() self.bus = cp.bus self.coherence = cp.coherence self.coherence.get_devices(dbus_interface=BUS_NAME, reply_handler=self.handle_devices_reply, error_handler=self.handle_error) self.coherence.connect_to_signal('UPnP_ControlPoint_MediaServer_detected', self.media_server_found, dbus_interface=BUS_NAME) self.coherence.connect_to_signal('UPnP_ControlPoint_MediaServer_removed', self.media_server_removed, dbus_interface=BUS_NAME) self.devices = {} def media_server_found(self,device,udn=None): for service in device['services']: service_type = service.split('/')[-1] if service_type == 'ContentDirectory': def got_icons(r,udn,item): print 'got_icons', r for icon in r: ###FIXME, we shouldn't just use the first icon icon_loader = gtk.gdk.PixbufLoader() icon_loader.write(urllib.urlopen(str(icon['url'])).read()) icon_loader.close() icon = icon_loader.get_pixbuf() icon = icon.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR) self.store.set_value(item, 2, icon) break def reply(r,udn): if 'CreateObject' in r: self.devices[udn] = {'ContentDirectory':{}} self.devices[udn]['ContentDirectory']['actions'] = r item = self.store.append(None) self.store.set_value(item, 0, str(device['friendly_name'])) self.store.set_value(item, 1, str(device['udn'])) self.store.set_value(item, 2, self.device_icon) d = self.bus.get_object(BUS_NAME+'.device',device['path']) d.get_device_icons(reply_handler=lambda x : got_icons(x,str(device['udn']),item),error_handler=self.handle_error) s = self.bus.get_object(BUS_NAME+'.service',service) s.get_available_actions(reply_handler=lambda x : reply(x,str(device['udn'])),error_handler=self.handle_error) def media_server_removed(self,udn): row_count = 0 for row in self.store: if udn == row[1]: self.store.remove(self.store.get_iter(row_count)) del self.devices[str(udn)] break row_count += 1 class TreeWidget(object): def __init__(self,cb_item_dbl_click=None, cb_resource_chooser=None): self.cb_item_dbl_click = cb_item_dbl_click self.cb_item_right_click = None self.cb_resource_chooser = cb_resource_chooser self.build_ui() self.init_controlpoint() def build_ui(self): self.window = gtk.ScrolledWindow() self.window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) icon = resource_filename(__name__, os.path.join('icons','network-server.png')) self.device_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','folder.png')) self.folder_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','audio-x-generic.png')) self.audio_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','video-x-generic.png')) self.video_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','image-x-generic.png')) self.image_icon = gtk.gdk.pixbuf_new_from_file(icon) self.store = gtk.TreeStore(str, # 0: name or title str, # 1: id, '0' for the device str, # 2: upnp_class, 'root' for the device int, # 3: child count, -1 if not available str, # 4: device udn, '' for an item str, # 5: service path, '' for a non container item gtk.gdk.Pixbuf, str, # 7: DIDLLite fragment, '' for a non upnp item gtk.gdk.Pixbuf ) self.treeview = gtk.TreeView(self.store) self.column = gtk.TreeViewColumn('MediaServers') self.treeview.append_column(self.column) # create a CellRenderers to render the data icon_cell = gtk.CellRendererPixbuf() text_cell = gtk.CellRendererText() self.column.pack_start(icon_cell, False) self.column.pack_start(text_cell, True) self.column.set_attributes(text_cell, text=0) self.column.add_attribute(icon_cell, "pixbuf",6) #self.column.set_cell_data_func(self.cellpb, get_icon) #self.treeview.insert_column_with_attributes(-1, 'MediaServers', cell, text=0) self.treeview.connect("row-activated", self.browse) self.treeview.connect("row-expanded", self.row_expanded) self.treeview.connect("button_press_event", self.button_action) self.treeview.set_property("has-tooltip", True) self.treeview.connect("query-tooltip", self.show_tooltip) self.tooltip_path = None self.we_are_scrolling = None def end_scrolling(): self.we_are_scrolling = None def start_scrolling(w,e): if self.we_are_scrolling != None: gobject.source_remove(self.we_are_scrolling) self.we_are_scrolling = gobject.timeout_add(800, end_scrolling) self.treeview.connect('scroll-event', start_scrolling) self.window.add(self.treeview) def show_tooltip(self, widget, x, y, keyboard_mode, tooltip): if self.we_are_scrolling != None: return False ret = False try: path = self.treeview.get_dest_row_at_pos(x, y) iter = self.store.get_iter(path[0]) title,object_id,upnp_class,item = self.store.get(iter,NAME_COLUMN,ID_COLUMN,UPNP_CLASS_COLUMN,DIDL_COLUMN) from coherence.upnp.core import DIDLLite if upnp_class == 'object.item.videoItem': self.tooltip_path = object_id item = DIDLLite.DIDLElement.fromString(item).getItems()[0] tooltip_icon, = self.store.get(iter,TOOLTIP_ICON_COLUMN) if tooltip_icon != None: tooltip.set_icon(tooltip_icon) else: tooltip.set_icon(self.video_icon) for res in item.res: protocol,network,content_format,additional_info = res.protocolInfo.split(':') if(content_format == 'image/jpeg' and 'DLNA.ORG_PN=JPEG_TN' in additional_info.split(';')): icon_loader = gtk.gdk.PixbufLoader() icon_loader.write(urllib.urlopen(str(res.data)).read()) icon_loader.close() icon = icon_loader.get_pixbuf() tooltip.set_icon(icon) self.store.set_value(iter, TOOLTIP_ICON_COLUMN, icon) #print "got poster", icon break title = title.replace('&','&') try: director = item.director.replace('&','&') except AttributeError: director = "" try: description = item.description.replace('&','&') except AttributeError: description = "" tooltip.set_markup("%s\n" "Director: %s\n" "Description: %s" % (title, director, description)) ret = True except TypeError: #print traceback.format_exc() pass except Exception: #print traceback.format_exc() #print "something wrong" pass return ret def button_action(self, widget, event): #print "button_action", widget, event, event.button if self.cb_item_right_click != None: return self.cb_item_right_click(widget, event) return 0 def handle_error(self,error): print error def handle_devices_reply(self,devices): for device in devices: if device['device_type'].split(':')[3] == 'MediaServer': self.media_server_found(device) def init_controlpoint(self): cp = ControlPoint() self.bus = cp.bus self.coherence = cp.coherence self.hostname = self.coherence.hostname(dbus_interface=BUS_NAME) self.coherence.get_devices(dbus_interface=BUS_NAME, reply_handler=self.handle_devices_reply, error_handler=self.handle_error) self.coherence.connect_to_signal('UPnP_ControlPoint_MediaServer_detected', self.media_server_found, dbus_interface=BUS_NAME) self.coherence.connect_to_signal('UPnP_ControlPoint_MediaServer_removed', self.media_server_removed, dbus_interface=BUS_NAME) self.devices = {} def device_has_action(self,udn,service,action): try: self.devices[udn][service]['actions'].index(action) return True except: return False def state_variable_change( self, udn, service, variable, value): #print "state_variable_change", udn, service, variable, 'changed to', value if variable == 'ContainerUpdateIDs': changes = value.split(',') while len(changes) > 1: container = changes.pop(0).strip() update_id = changes.pop(0).strip() def match_func(model, iter, data): column, key = data # data is a tuple containing column number, key value = model.get_value(iter, column) return value == key def search(model, iter, func, data): #print "search", model, iter, data while iter: if func(model, iter, data): return iter result = search(model, model.iter_children(iter), func, data) if result: return result iter = model.iter_next(iter) return None row_count = 0 for row in self.store: if udn == row[UDN_COLUMN]: iter = self.store.get_iter(row_count) match_iter = search(self.store, self.store.iter_children(iter), match_func, (ID_COLUMN, container)) if match_iter: print "heureka, we have a change in ", container, ", container needs a reload" path = self.store.get_path(match_iter) expanded = self.treeview.row_expanded(path) child = self.store.iter_children(match_iter) while child: self.store.remove(child) child = self.store.iter_children(match_iter) self.browse(self.treeview,path,None, starting_index=0,requested_count=0,force=True,expand=expanded) break row_count += 1 def media_server_found(self,device,udn=None): #print "media_server_found", device['friendly_name'] item = self.store.append(None) self.store.set_value(item, NAME_COLUMN, device['friendly_name']) self.store.set_value(item, ID_COLUMN, '0') self.store.set_value(item, UPNP_CLASS_COLUMN, 'root') self.store.set_value(item, CHILD_COUNT_COLUMN, -1) self.store.set_value(item, UDN_COLUMN, str(device['udn'])) self.store.set_value(item, ICON_COLUMN, self.device_icon) self.store.set_value(item, DIDL_COLUMN, '') self.store.set_value(item, TOOLTIP_ICON_COLUMN, None) self.store.append(item, ('...loading...','','placeholder',-1,'','',None,'',None)) self.devices[str(device['udn'])] = {'ContentDirectory':{}} for service in device['services']: service_type = service.split('/')[-1] if service_type == 'ContentDirectory': self.store.set_value(item, SERVICE_COLUMN, service) self.devices[str(device['udn'])]['ContentDirectory'] = {} def reply(r,udn): self.devices[udn]['ContentDirectory']['actions'] = r def got_icons(r,udn,item): #print 'got_icons', r for icon in r: ###FIXME, we shouldn't just use the first icon icon_loader = gtk.gdk.PixbufLoader() icon_loader.write(urllib.urlopen(str(icon['url'])).read()) icon_loader.close() icon = icon_loader.get_pixbuf() icon = icon.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR) self.store.set_value(item, ICON_COLUMN, icon) break def reply_subscribe(udn, service, r): for k,v in r.iteritems(): self.state_variable_change(udn,service,k,v) s = self.bus.get_object(BUS_NAME+'.service',service) s.connect_to_signal('StateVariableChanged', self.state_variable_change, dbus_interface=BUS_NAME+'.service') s.get_available_actions(reply_handler=lambda x : reply(x,str(device['udn'])),error_handler=self.handle_error) s.subscribe(reply_handler=reply_subscribe,error_handler=self.handle_error) d = self.bus.get_object(BUS_NAME+'.device',device['path']) d.get_device_icons(reply_handler=lambda x : got_icons(x,str(device['udn']),item),error_handler=self.handle_error) def media_server_removed(self,udn): #print "media_server_removed", udn row_count = 0 for row in self.store: if udn == row[UDN_COLUMN]: self.store.remove(self.store.get_iter(row_count)) del self.devices[str(udn)] break row_count += 1 def row_expanded(self,view,iter,row_path): #print "row_expanded", view,iter,row_path child = self.store.iter_children(iter) if child: upnp_class, = self.store.get(child,UPNP_CLASS_COLUMN) if upnp_class == 'placeholder': self.browse(view,row_path,None) def browse(self,view,row_path,column,starting_index=0,requested_count=0,force=False,expand=False): #print "browse", view,row_path,column,starting_index,requested_count,force iter = self.store.get_iter(row_path) child = self.store.iter_children(iter) if child: upnp_class, = self.store.get(child,UPNP_CLASS_COLUMN) if upnp_class != 'placeholder': if force == False: if view.row_expanded(row_path): view.collapse_row(row_path) else: view.expand_row(row_path, False) return title,object_id,upnp_class = self.store.get(iter,NAME_COLUMN,ID_COLUMN,UPNP_CLASS_COLUMN) if(not upnp_class.startswith('object.container') and not upnp_class == 'root'): url, = self.store.get(iter,SERVICE_COLUMN) if url == '': return print "request to play:", title,object_id,url if self.cb_item_dbl_click != None: self.cb_item_dbl_click(url) return def reply(r): #print "browse_reply - %s of %s returned" % (r['NumberReturned'],r['TotalMatches']) from coherence.upnp.core import DIDLLite child = self.store.iter_children(iter) if child: upnp_class, = self.store.get(child,UPNP_CLASS_COLUMN) if upnp_class == 'placeholder': self.store.remove(child) title, = self.store.get(iter,NAME_COLUMN) try: title = title[:title.rindex('(')] self.store.set_value(iter,NAME_COLUMN, "%s(%d)" % (title,int(r['TotalMatches']))) except ValueError: pass didl = DIDLLite.DIDLElement.fromString(r['Result']) for item in didl.getItems(): #print item.title, item.id, item.upnp_class if item.upnp_class.startswith('object.container'): icon = self.folder_icon service, = self.store.get(iter,SERVICE_COLUMN) child_count = item.childCount try: title = "%s (%d)" % (item.title,item.childCount) except TypeError: title = "%s (n/a)" % item.title child_count = -1 else: icon=None service = '' if callable(self.cb_resource_chooser): service = self.cb_resource_chooser(item.res) else: res = item.res.get_matching(['*:%s:*:*' % self.hostname], protocol_type='internal') if len(res) == 0: res = item.res.get_matching(['*:*:*:*'], protocol_type='http-get') if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') service = res.data child_count = -1 title = item.title if item.upnp_class.startswith('object.item.audioItem'): icon = self.audio_icon elif item.upnp_class.startswith('object.item.videoItem'): icon = self.video_icon elif item.upnp_class.startswith('object.item.imageItem'): icon = self.image_icon stored_didl = DIDLLite.DIDLElement() stored_didl.addItem(item) new_iter = self.store.append(iter, (title,item.id,item.upnp_class,child_count,'',service,icon,stored_didl.toString(),None)) if item.upnp_class.startswith('object.container'): self.store.append(new_iter, ('...loading...','','placeholder',-1,'','',None,'',None)) if((int(r['TotalMatches']) > 0 and force==False) or expand==True): view.expand_row(row_path, False) if(requested_count != int(r['NumberReturned']) and int(r['NumberReturned']) < (int(r['TotalMatches'])-starting_index)): print "seems we have been returned only a part of the result" print "requested %d, starting at %d" % (requested_count,starting_index) print "got %d out of %d" % (int(r['NumberReturned']), int(r['TotalMatches'])) print "requesting more starting now at %d" % (starting_index+int(r['NumberReturned'])) self.browse(view,row_path,column, starting_index=starting_index+int(r['NumberReturned']), force=True) service, = self.store.get(iter,SERVICE_COLUMN) if service == '': return s = self.bus.get_object(BUS_NAME+'.service',service) s.action('browse', {'object_id':object_id,'process_result':'no', 'starting_index':str(starting_index),'requested_count':str(requested_count)}, reply_handler=reply,error_handler=self.handle_error) def destroy_object(self, row_path): #print "destroy_object", row_path iter = self.store.get_iter(row_path) object_id, = self.store.get(iter,ID_COLUMN) parent_iter = self.store.iter_parent(iter) service, = self.store.get(parent_iter,SERVICE_COLUMN) if service == '': return def reply(r): #print "destroy_object reply", r pass s = self.bus.get_object(BUS_NAME+'.service',service) s.action('destroy_object', {'object_id':object_id}, reply_handler=reply,error_handler=self.handle_error) if __name__ == '__main__': ui=TreeWidget() window = gtk.Window() window.connect("delete_event", gtk.main_quit) window.set_default_size(350, 550) window.add(ui.window) window.show_all() gtk.gdk.threads_init() gtk.main()Coherence-0.6.6.2/coherence/ui/__init__.py0000644000175000017500000000000011317660740016350 0ustar devdevCoherence-0.6.6.2/coherence/ui/icons/0000755000000000000000000000000011317673117015746 5ustar rootrootCoherence-0.6.6.2/coherence/ui/icons/emblem-shared.png0000644000175000017500000000110411317660740020573 0ustar devdev‰PNG  IHDRóÿasBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<ÁIDAT8íÒ=hSQÆñÿ¹çÞä¦Iª -TÐ8ÝD"%‹“ŸàPÐÅÁ¥à$¸‹¡ˆ‹] º«ƒAt*ÒR¤ I¡ m¤ æCï½æÞ{rÎq°Q¡Ž>ðïðüà…WXkÙMœ]µÿÿ€•R2}ç6'ÔBà2Bˆ.P³FW+Õ¹wŽŸ°+K‹´Z›7zgzÙì™ñ+z0HŒJ×°æ½Ñz hlχ‹3óáH­3ÖÚÌß>ÏÏù€XÊbrÊv7IEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/network-server.png0000644000175000017500000000120211317660740021062 0ustar devdev‰PNG  IHDRóÿabKGD```efJ pHYs  ÒÝ~ütIMEÕ ) áE§9>tEXtCommentCreated with The GIMP (c) 2003 Jakub 'jimmac' Steiner'3ïXÅIDAT8Ë’½Š"Q…¿ë6Úb¬ ‘þ±Ââ>ÀÆF÷Œ† |Àö6d7‘55‘ö/1Ñ‹Mw×;Ý«£2{àBqï=§êT•âOZëß|€Á`ðøÅ²,—Kñ}_|ßÏóÄu]q]WŒ1bŒ‘ñx,Zk¾^±µÖr>Ÿ¯È÷N§“ì÷ûPëRD)Åh4úÈ­V+Š­÷§Ó‰r¹üŸ§T*±Ýnñ}?ª@DÈf³á>|‹½o’ã8‹Ef³J)âñ8…B!:—n¦ " &“ ívÛ¶q]—ÅbU`Û6‡Ãáñëõ:Õj5ê|.—»ÛÌ7|º±àyADÞÇÁó<LjFø†DìÑÂìv;6› ‰D‚õzM,‹66Ü»+ ‡ÃL&@¥R!•JÑl6I¥RÔj5ÒéôM¢Kç———ü?žþ|Pþús“IEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/audio-x-generic.png0000644000175000017500000000064311317660740021055 0ustar devdev‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs × ×B(›xtIMEÕ 69»$ŸG0IDAT8Ëc` 0b,­5íg`úZ@H3Ç^ì2_ ‚ƒ Ú¾vÝzg&lÿÿ3~#Ö ,lÀ*þÿÿ@&‚0000t7^ç’:íï  Ldþ?xpaÆÿ\x£Iž‰Òt@V, ˳0000dZH2³ü‰ýñý»“€ðw†ÿ ¿þ|†kÀ'ÏÂÀÀÀðùÓ§:5-¶ 3U~^u~ne†{O×2ˆ‰s2’ga```aȰ··fø÷ï33;ÃÝ'k.ýÿÍëÿ[ñÉ¿zÁÔO÷žgàà`aàç{Ëpóú[†woÿ^<}ìC>ù³'ßî‡'’ÄLµÛ"b,* ·¯ÿ\´aÕÝxä€C——W”¨˜Øyô9¥±È½u‰ùo)JžIEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/image-x-generic.png0000644000175000017500000000105611317660740021035 0ustar devdev‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs × ×B(›xtIMEÕ 6”²¶»IDAT8ËÓMkAÀñÿìÛ4ݘÔ¡¥¡à¥…\Ä¢SèɃŸ@èg(žë1ƒõ Þzõ Ò@µØ ÕžTD*ÒC"•&›n·Ù™éaéêb !óú{f†gD½Q[î3ZyB½Q3Zë‘j½Q3€1†ž²µón¨°KÕVW–p„ìîfîæ,˵…‚â÷‘M¢4‰Ò(¥H”akçM0ÆàØ‚ ì33s·Z$(lì~çí{?CR@Óþç4Îyò,.ù’ó1³S‹árûz—o§D'¥S@)“»Î_@Jã’Nh'-\»ÌŸ£.¾œ@: Ï} €bÁ£}x…×{_ʧ|=(Ž{hmhÿøÄäÕ9ô…€%_‚ôŽ}z=ƒç€ë§oôjï%…1™ÙùÁ@Òïus“´I£îo?§GƒN7äÙú‹ô.én´Ö„ÇaQ™N‡›Í&x—3ÀÊižÄv$‡¾ĉÁqǨLŸdk¦&|€Å[Z? Ƚk¹^ëÃÇÿ¥ê›ÛC§rB°º²œ¥ç0EJ™ëÇïúϵXá–‹B«IEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/video-x-generic.png0000644000175000017500000000146711317660740021067 0ustar devdev‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ :'3y5tEXtComment(c) 2004 Jakub Steiner Created with The GIMPÙ‹oƒIDAT8Ë“ßKSaÇ?çœéœgÆ6Ä› ÍbºcZjj*D ý ÖôÚ«.êZ©;AüBŒîl¢ø+Í@½’’г¨ õÂi1¥ãvæ¦]¸IÚMõܼï Ïóáy¿ßç8½}=@¨\@X‚]ݧó…ß /ý5ÕµWEAΗ“Œ=UUù´üqxÔÕÙýù S<ð·Ù ìèF 3™ }F%¬¹yØåô˜NðÍëm 5 3 þ€¿­H ¤­°¾±†™40“&ëk„´ þ¶" ?ÛÔÛ×ÓQS]û¸¸¤˜¯ß¾°¿¿O(¤ÖÂx˼LNLîàr9‰é1Ün‚§²ªb}fzvYŠ¢ 14-Ìü܆ný_7˜Ÿ[@ÓÂèF EQȨ“óe¶~üÄátÇQ÷Ü8>ãñ8§3™ÀqÆIÆ%,«H¤±X,ܹ{û„MV«•@ûÃãwú ½º²"F$Q"•J1>6AppÓ̈hš‡› •J!‰RÍ–Œ=kn»;»Øl6TUcdx€‘áQTUÃf³±»³‹57cÏ 3\ˆ@PUUìr>_9Í-MÈö£A’í2Í-Mø|åØåTUH3ӳ˕U·¼ç½‡ÓAL! WêE‘Òs¥D6#¬¯mÐØxd"É»÷3‹]ÝO$€›­7>„ÔU…ï‚ìv{(,,àððKeÞrêëH&’¼|5D¶ž--±†VÕÍÚ…©·“_··¿O™¦y˜^ÿ³vÿeà90úào#Ðî \ú.† QI2ò!IEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/folder.png0000644000175000017500000000076211317660740017352 0ustar devdev‰PNG  IHDRóÿabKGDùC» pHYs × ×B(›xtIMEÕ 9 ùÝpPIDAT8Ë¥‘MˆRQ†Ÿc*þ@¦‹ZA´‚™ šå,ãb´©ˆ ˆþ˜Ýlf¶³¢]P3Î*D¤eIP¶lF›ˆ"( ±{GÏýfqí âÍ—œÃ÷¼/ïQ—.—¶ŠÅâ6S2ÆçÀÜÚÛÝB„Ô{·åúÕ3µöx¶÷”^¯w¥Z©=”œ‹´>µ¢LxÛx3}õ¨Z©=ˆ¤ÓŽäò‘çB)pS ¥µÕû@0Æày.¯?öiv2Ì¥Ãgn:RHéÏa‚f'Ããu´±s1RÉCÜ}X?ð<€Ÿ¿5_Úçœ8𠦵&ž3ú Å\× üà_KM`­e‡¿ˆù "(¥DÀÁZah}ÌHŒ,C+ä²É Àó\Žçtû†]Dá#Œ‘1Y™~uÚœ>{Ž_ÿŒ—wð"aª•“ê/_‘×ßËêüµ†›8¶¶hyÙá·÷ïÊ›«,«‡´Ê§°âøIEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/emblem-unreadable.png0000644000175000017500000000156411317660740021441 0ustar devdev‰PNG  IHDRóÿasBIT|dˆ+IDAT8mÒékuðgfî{d7YK¤«D]mz⛪oŠxF¤¥â;mÂdìšÝ¤"âQÝ#©6ÿ‚¦H±PJª„¶biã‰/ ¾Å´{ÌÌnff7;3¿¯/L–}ÞøÖ“ÉŒ>D<ÿ-9î3•ÊÙñ?I¥Fœ—¿ ×ÝW.Ÿý ø 섫ǽ%yQÉ(mÅJFyB”¤Å£¯ú¶ וœ² ¸±±±Å ÿZ6=ÅúÑlê8ýÉ©†Ñn>S:óÓ •\6߉ôaeå/Tf‹Õ¶kð88´rìèÁvîâÖº‚ˆ§žÜ#Þºyc8‘¼4tpÿY,¾“-D%YÆêjýqƒAé×_~~‹w€}óó_èÍV>¯µ6‚Á²Ù‰˜Çç[öñÜÜx¾‡%QF£QÇ… _6ÇïíÍ79•S5]#Çq¨µÚ$Û±©ÙÒ©ÙÒ©kw©¡Ö¨Û]£Z½F…É\ý?{J¥F…©œ¦i*Ù¶Mš®’i™dZÕÕê:®Ra2ÛH¥FßpÜÖÏûÝÉ©÷|~¿–e‚ƒ(Hpl~ü~×µí½›ÏÌo.pyÞ%"Žã810b "¸Œ1æá8ÎÞlzÉä‰A9¸2™×+ "¬¶ ! B$Ö*dYF~bÊãKéôè#ÿ*H&O Š’üu&“‹…B!h-¡`Œ1úÂQÔU„Ca¤“™˜? ,)ãÊîõGzûQA–—ÆÓ¹h$ÖTE`>:õËqò'½¢(¡V¿ƒøÀv¨šŠÙÙrÃêvŸæy¯÷‡W^zµo ?½¥õð§¥i9îsÜýÅÒ´nY&úcqÜ­ÝÁŽí÷âÈ‘çc¢›<<þœz·º‚Ø=0 ÅÒ´Ö6­Ã•Êçߗ˟ݲ ó¹bé´nµÛ¸ÿ¾øãÏßqñâW5rÝ÷ÏùFÞ&ˆW‡‡_‹.,ÌëfÇxv¶8·¼yÛÉäÈQ/¿ðâËá…són§shffîvï”qe·´ìÂ=´o$V†è07133wþþÜŽDÄIEND®B`‚Coherence-0.6.6.2/coherence/ui/icons/emblem-new.png0000644000175000017500000000133711317660740020126 0ustar devdev‰PNG  IHDRóÿasBIT|dˆ–IDAT8uS¿oeœy»w»¶³öaû|Q‚6‰cYnh@BJCÄ_AEK…¨éH‡„¨ÓÓ¦!$Ò‰'ñòcû¸óÙwÞÛÛÛï Å%‘…©žôæôÞÌ#^ÂÎÎj²<›64m—#Ç% R0;“ªã,kž’?Tù|VH°Ngk9M}ðE )IÊ ëðð’ûCÎmw_`ýþæÛ1æÓÞ€‡yp6£ÅNz;÷åþW1¶Ÿ~Ý€ŠbýjLm ¸aî«N\t¹ž|~¶†ï <¹ p*­9tºqιín,½;•烛’¿iÄš¨uBoY´™Åµ ˆEïm„p¯ a†6ˆ=éÖOVÃ%_"uEÀ akQüát=ùR@¨%_ Š?x ¨¯AºèJ5ûýùXã°l†y°ÑLÓ;ïÐV’§›=‡YË’ô+üuùþû£Ñ§]ÿ ãEsã´\©ÔKBø±zyøEÜŒCøM$§#÷YDP€rt;¯ÆwÃÿ‡ê{ ?ëΉƒ1-rŽD çãòk•^%P–·†s C E0 Œõñ!§˜ø| œ 櫌3¹À¶€®ˆso[’ØG ýcvãÉ$š%Bõ]¨ª»ALRgm÷`Ñ`Ç3‹E/&·gg›;F5›’ÊÙP}›”å7A~8Ÿ×êŸu¶+b×äË’Gäîèi”oÅã¼³UJ7)­jZ™Nú:'ø¯Ä}#ÿ¬Ä_²ìáïääšx&ÒïwV#ø€%™æ(¥EcáŽ.#;*ËêQ£ñx„?¦‹h·¯gYfM•µf0%eîCÖ⃓“è¸ÕºŸ_äÿ·xVônÖ/ IEND®B`‚Coherence-0.6.6.2/coherence/json.py0000644000175000017500000000640611317660741015166 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php import simplejson as json from twisted.web import resource,static from twisted.internet import defer from coherence import log class JsonInterface(resource.Resource,log.Loggable): logCategory = 'json' #isLeaf = False def __init__(self, controlpoint): self.controlpoint = controlpoint self.controlpoint.coherence.add_web_resource('json', self) self.children = {} def render_GET(self,request): d = defer.maybeDeferred(self.do_the_render,request) return d def render_POST(self,request): d = defer.maybeDeferred(self.do_the_render,request) return d def getChildWithDefault(self,path,request): self.info('getChildWithDefault, %s, %s, %s %s %r' % (request.method, path, request.uri, request.client,request.args)) #return self.do_the_render(request) d = defer.maybeDeferred(self.do_the_render,request) return d def do_the_render(self,request): self.warning('do_the_render, %s, %s, %s %r %s' % (request.method, request.path,request.uri, request.args, request.client)) msg = "Houston, we've got a problem" path = request.path.split('/') path = path[2:] self.warning('path %r' % path) if request.method in ('GET','POST'): request.postpath = None if request.method == 'GET': if path[0] == 'devices': return self.list_devices(request) else: device = self.controlpoint.get_device_with_id(path[0]) if device != None: service = device.get_service_by_type(path[1]) if service != None: action = service.get_action(path[2]) if action != None: return self.call_action(action,request) else: msg = "action %r on service type %r for device %r not found" % (path[2],path[1],path[0]) else: msg = "service type %r for device %r not found" % (path[1],path[0]) else: msg = "device with id %r not found" % path[0] request.setResponseCode(404,message=msg) return static.Data("

%s

" % msg,'text/html') def list_devices(self,request): devices = [] for device in self.controlpoint.get_devices(): devices.append(device.as_dict()) return static.Data(json.dumps(devices),'application/json') def call_action(self,action,request): kwargs = {} for entry,value_list in request.args.items(): kwargs[entry] = unicode(value_list[0]) def to_json(result): self.warning("to_json") return static.Data(json.dumps(result),'application/json') def fail(f): request.setResponseCode(404) return static.Data("

Houston, we've got a problem

",'text/html') d = action.call(**kwargs) d.addCallback(to_json) d.addErrback(fail) return dCoherence-0.6.6.2/coherence/test/0000755000000000000000000000000011317673117015175 5ustar rootrootCoherence-0.6.6.2/coherence/test/test_dbus.py0000644000175000017500000000613611317660733017171 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ Test cases for L{dbus_service} """ import os from twisted.trial import unittest from twisted.internet import reactor from twisted.internet.defer import Deferred from coherence import __version__ from coherence.base import Coherence from coherence.upnp.core.uuid import UUID import coherence.extern.louie as louie BUS_NAME = 'org.Coherence' OBJECT_PATH = '/org/Coherence' class TestDBUS(unittest.TestCase): def setUp(self): louie.reset() self.coherence = Coherence({'unittest':'yes','logmode':'error','use_dbus':'yes','controlpoint':'yes'}) self.bus = dbus.SessionBus() self.coherence_service = self.bus.get_object(BUS_NAME,OBJECT_PATH) self.uuid = UUID() def tearDown(self): def cleaner(r): self.coherence.clear() return r dl = self.coherence.shutdown() dl.addBoth(cleaner) return dl def test_dbus_version(self): """ tests the version number request via dbus """ d = Deferred() def handle_version_reply(version): self.assertEqual(version,__version__) d.callback(version) def handle_error(err): d.errback(err) self.coherence_service.version(dbus_interface=BUS_NAME, reply_handler=handle_version_reply, error_handler=handle_error) return d def test_dbus_plugin_add_and_remove(self): """ tests creation and removal of a backend via dbus """ d = Deferred() def handle_error(err): d.errback(err) def handle_add_plugin_reply(uuid): uuid = str(uuid) self.assertEqual(str(self.uuid),uuid) def remove_it(uuid): def handle_remove_plugin_reply(uuid): self.assertEqual(str(self.uuid),uuid) d.callback(uuid) self.coherence_service.remove_plugin(uuid, dbus_interface=BUS_NAME, reply_handler=handle_remove_plugin_reply, error_handler=handle_error) reactor.callLater(2,remove_it,uuid) self.coherence_service.add_plugin('SimpleLight',{'name':'dbus-test-light-%d'%os.getpid(),'uuid':str(self.uuid)}, dbus_interface=BUS_NAME, reply_handler=handle_add_plugin_reply, error_handler=handle_error) return d if reactor.__class__.__name__ != 'Glib2Reactor': TestDBUS.skip = """This test needs a Glib2Reactor, pls start trial with the '-r glib2' option""" try: import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import dbus.service except ImportError: TestDBUS.skip = "Python dbus-bindings not available" Coherence-0.6.6.2/coherence/test/test_transcoder.py0000644000175000017500000001407111317660733020375 0ustar devdev# -*- coding: utf-8 -*- from twisted.trial.unittest import TestCase from coherence.transcoder import TranscoderManager, get_transcoder_name from coherence.transcoder import (PCMTranscoder, WAVTranscoder, MP3Transcoder, MP4Transcoder, MP2TSTranscoder, ThumbTranscoder, GStreamerTranscoder, ExternalProcessPipeline) known_transcoders = [PCMTranscoder, WAVTranscoder, MP3Transcoder, MP4Transcoder, MP2TSTranscoder, ThumbTranscoder] class TranscoderTestMixin(object): def setUp(self): self.manager = TranscoderManager() def tearDown(self): # as it is a singleton ensuring that we always get a clean # and fresh one is tricky and hacks the internals TranscoderManager._instance = None del self.manager class TestTranscoderManagerSingletony(TranscoderTestMixin, TestCase): def test_is_really_singleton(self): #FIXME: singleton tests should be outsourced some when old_id = id(self.manager) new_manager = TranscoderManager() self.assertEquals(old_id, id(new_manager)) class TestTranscoderAutoloading(TranscoderTestMixin, TestCase): class CoherenceStump(object): def __init__(self, **kwargs): self.config = kwargs failing_config = {'name': 'failing', 'pipeline': 'wrong', 'type': 'process', 'target': 'yay'} gst_config = {'name': 'supertest', 'pipeline': 'pp%spppl', 'type': 'gstreamer', 'target': 'yay'} process_config = {'name': 'megaprocess', 'pipeline': 'uiui%suiui', 'type': 'process', 'target': 'yay'} bad_name_config = {'name': u'so bäd', 'pipeline': 'fake %s', 'type': 'process', 'target': 'norway'} def setUp(self): self.manager = None def test_is_loading_all_known_transcoders(self): self.manager = TranscoderManager() self._check_for_transcoders(known_transcoders) def _check_for_transcoders(self, transcoders): for klass in transcoders: loaded_transcoder = self.manager.transcoders[get_transcoder_name(klass)] self.assertEquals(loaded_transcoder, klass) def test_is_loading_no_config(self): coherence = self.CoherenceStump() self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) def test_is_loading_one_gst_from_config(self): coherence = self.CoherenceStump(transcoder=self.gst_config) self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) my_pipe = self.manager.select('supertest', 'http://my_uri') self.assertTrue(isinstance(my_pipe, GStreamerTranscoder)) self._check_transcoder_attrs(my_pipe, pipeline='pp%spppl', uri="http://my_uri") def _check_transcoder_attrs(self, transcoder, pipeline=None, uri=None): # bahh... relying on implementation details of the basetranscoder here self.assertEquals(transcoder.pipeline_description, pipeline) self.assertEquals(transcoder.uri, uri) def test_is_loading_one_process_from_config(self): coherence = self.CoherenceStump(transcoder=self.process_config) self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) transcoder = self.manager.select('megaprocess', 'http://another/uri') self.assertTrue(isinstance(transcoder, ExternalProcessPipeline)) self._check_transcoder_attrs(transcoder, 'uiui%suiui', 'http://another/uri') def test_placeholdercheck_in_config(self): # this pipeline does not contain the '%s' placeholder and because # of that should not be created coherence = self.CoherenceStump(transcoder=self.failing_config) self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) self.assertRaises(KeyError, self.manager.select, 'failing', 'http://another/uri') def test_badname_in_config(self): # this pipeline does not contain the '%s' placeholder and because # of that should not be created coherence = self.CoherenceStump(transcoder=self.bad_name_config) self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) self.assertRaises(KeyError, self.manager.select, u'so bäd', 'http://another/uri') def test_is_loading_multiple_from_config(self): coherence = self.CoherenceStump(transcoder=[self.gst_config, self.process_config]) self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) # check the megaprocess transcoder = self.manager.select('megaprocess', 'http://another/uri') self.assertTrue(isinstance(transcoder, ExternalProcessPipeline)) self._check_transcoder_attrs(transcoder, 'uiui%suiui', 'http://another/uri') # check the gstreamer transcoder transcoder = self.manager.select('supertest', 'http://another/uri2') self.assertTrue(isinstance(transcoder, GStreamerTranscoder)) self._check_transcoder_attrs(transcoder, 'pp%spppl', 'http://another/uri2') def test_loaded_gst_always_new_instance(self): coherence = self.CoherenceStump(transcoder=self.gst_config) self.manager = TranscoderManager(coherence) self._check_for_transcoders(known_transcoders) transcoder_a = self.manager.select('supertest', 'http://my_uri') self.assertTrue(isinstance(transcoder_a, GStreamerTranscoder)) self._check_transcoder_attrs(transcoder_a, pipeline='pp%spppl', uri="http://my_uri") transcoder_b = self.manager.select('supertest', 'http://another/uri') self.assertTrue(isinstance(transcoder_b, GStreamerTranscoder)) self._check_transcoder_attrs(transcoder_b, pipeline='pp%spppl', uri="http://another/uri") self.assertNotEquals(transcoder_a, transcoder_b) self.assertNotEquals(id(transcoder_a), id(transcoder_b)) Coherence-0.6.6.2/coherence/test/test_dispatching.py0000644000175000017500000003641511317660733020534 0ustar devdev from twisted.trial import unittest from twisted.internet import defer from coherence.dispatcher import Dispatcher, UnknownSignal, Receiver, \ SignalingProperty, ChangedSignalingProperty, CustomSignalingProperty class TestDispatcher(Dispatcher): __signals__ = {'test': 'Test signal'} class SimpleTarget(object): def __init__(self): self.called = 0 self.called_a = 0 self.called_b = 0 self.called_c = 0 self.called_d = 0 def callback(self): self.called += 1 def updater(self, arg1, arg2, value, arg4, key_a='p', variable=None): setattr(self, variable, value) setattr(self, "%s_%s" % (variable, arg2), key_a) def plus(self, plus, variable=False): setattr(self, variable, getattr(self, variable) + plus) def fail_before(self, plus, variable=False): raise TypeError(':(') self.update(plus, variable=variable) class TestDispatching(unittest.TestCase): def setUp(self): self.called_counter = 0 self.dispatcher = TestDispatcher() self.target = SimpleTarget() def test_simple_emit(self): receiver = self.dispatcher.connect('test', self.target.callback) self.dispatcher.emit('test') self.assertEquals(self.target.called, 1) self.dispatcher.emit('test') self.assertEquals(self.target.called, 2) self.dispatcher.disconnect(receiver) self.dispatcher.emit('test') self.assertEquals(self.target.called, 2) def test_simple_deferred_emit(self): receiver = self.dispatcher.connect('test', self.target.callback) self.dispatcher.deferred_emit('test') self.assertEquals(self.target.called, 1) self.dispatcher.deferred_emit('test') self.assertEquals(self.target.called, 2) self.dispatcher.disconnect(receiver) self.dispatcher.deferred_emit('test') self.assertEquals(self.target.called, 2) def test_simple_save_emit(self): def call(res): return self.dispatcher.save_emit('test') def test(res, val): self.assertEquals(self.target.called, val) receiver = self.dispatcher.connect('test', self.target.callback) dfr = defer.succeed(None) dfr.addCallback(call) dfr.addCallback(test, 1) dfr.addCallback(call) dfr.addCallback(test, 2) dfr.addCallback(lambda x: self.dispatcher.disconnect(receiver)) dfr.addCallback(call) dfr.addCallback(test, 2) return dfr def test_connect_typo(self): self.assertRaises(UnknownSignal, self.dispatcher.connect, 'Test', None) def test_disconnect_none_receiver(self): """ trying to disconnect with None shouldn't fail, it is a valid use case """ self.dispatcher.disconnect(None) def test_disconnect_false_receiver(self): """ this receiver isn't coming from this dispatcher """ # this is REALLY constructed. you may *not* instantiate a Receiver yourself anyway rec = Receiver('test', None, None, None) self.dispatcher.disconnect(rec) def test_disconnect_wrong_signal_receiver(self): rec = Receiver('Test', None, None, None) self.assertRaises(UnknownSignal, self.dispatcher.disconnect, rec) def test_disconnect_not_receiver(self): self.assertRaises(TypeError, self.dispatcher.disconnect, 'test') def test_emit_false_signal(self): self.assertRaises(UnknownSignal, self.dispatcher.emit, False) def test_emit_without_receivers(self): self.dispatcher.emit('test') self.assertEquals(self.target.called, 0) def test_emit_with_multiple_receiver(self): rc1 = self.dispatcher.connect('test', self.target.updater, 1, 2, variable='va1') rc2 = self.dispatcher.connect('test', self.target.updater, 'value', 2, variable='variable') rc3 = self.dispatcher.connect('test', self.target.updater, 'other', 2, variable='one') self.dispatcher.emit('test', self, 'other', key_a='q') # check rc1 self.assertEquals(self.target.va1, 1) self.assertEquals(self.target.va1_other, 'q') #check rc2 self.assertEquals(self.target.variable, 'value') self.assertEquals(self.target.variable_other, 'q') # check rc3 self.assertEquals(self.target.one, 'other') self.assertEquals(self.target.one_other, 'q') # now removing the one in the middel self.dispatcher.disconnect(rc2) # and try again with other data self.dispatcher.emit('test', self, 'other', key_a='thistime') # check rc1 self.assertEquals(self.target.va1, 1) self.assertEquals(self.target.va1_other, 'thistime') #check rc2 self.assertEquals(self.target.variable, 'value') self.assertEquals(self.target.variable_other, 'q') # check rc3 self.assertEquals(self.target.one, 'other') self.assertEquals(self.target.one_other, 'thistime') # no keyword self.dispatcher.emit('test', self, 'a') # worked for rc1 and rc3 with the default value self.assertEquals(self.target.va1_a, 'p') self.assertEquals(self.target.one_a, 'p') # but not on rc2 self.assertFalse(hasattr(self.target, 'variable_a')) self.dispatcher.disconnect(rc1) self.dispatcher.disconnect(rc3) def test_emit_multiple_with_failing_in_between(self): rc1 = self.dispatcher.connect('test', self.target.plus, 1, variable='called_a') rc2 = self.dispatcher.connect('test', self.target.plus, 2, variable='called_b') rc3 = self.dispatcher.connect('test', self.target.fail_before, 3, variable='called_c') rc4 = self.dispatcher.connect('test', self.target.plus, 4, variable='called_d') self.dispatcher.emit('test') self.assertEquals(self.target.called_a, 1) self.assertEquals(self.target.called_b, 2) self.assertEquals(self.target.called_c, 0) self.assertEquals(self.target.called_d, 4) self.dispatcher.emit('test') self.assertEquals(self.target.called_a, 2) self.assertEquals(self.target.called_b, 4) self.assertEquals(self.target.called_c, 0) self.assertEquals(self.target.called_d, 8) self.dispatcher.disconnect(rc1) self.dispatcher.disconnect(rc2) self.dispatcher.disconnect(rc3) self.dispatcher.disconnect(rc4) # Receiver tests class TestReceiver(unittest.TestCase): def setUp(self): self.called = 0 def _callback(self, *args, **kw): self.called += 1 self.args = args self.kw = kw def test_simple_calling(self): rec = Receiver('test', self._callback, (), {}) self.assertEquals(rec.signal, 'test') rec() self.assertEquals(self.called, 1) self.assertEquals(self.args, ()) self.assertEquals(self.kw, {}) rec() self.assertEquals(self.called, 2) self.assertEquals(self.args, ()) self.assertEquals(self.kw, {}) rec() self.assertEquals(self.called, 3) self.assertEquals(self.args, ()) self.assertEquals(self.kw, {}) def test_calling_with_args(self): rec = Receiver('test', self._callback, (1, 2, 3), {'test': 'a'}) self.assertEquals(rec.signal, 'test') rec(0) self.assertEquals(self.called, 1) self.assertEquals(self.args, (0, 1, 2, 3)) self.assertEquals(self.kw, {'test': 'a'}) rec(-1) self.assertEquals(self.called, 2) self.assertEquals(self.args, (-1, 1, 2, 3)) self.assertEquals(self.kw, {'test': 'a'}) rec(-2) self.assertEquals(self.called, 3) self.assertEquals(self.args, (-2, 1, 2, 3)) self.assertEquals(self.kw, {'test': 'a'}) def test_calling_with_kw(self): rec = Receiver('test', self._callback, (1, 2, 3), {'test': 'a'}) self.assertEquals(rec.signal, 'test') rec(p='q') self.assertEquals(self.called, 1) self.assertEquals(self.args, (1, 2, 3)) self.assertEquals(self.kw, {'test': 'a', 'p': 'q'}) rec(other='wise') self.assertEquals(self.called, 2) self.assertEquals(self.args, (1, 2, 3)) self.assertEquals(self.kw, {'test': 'a', 'other': 'wise'}) rec(and_one='more') self.assertEquals(self.called, 3) self.assertEquals(self.args, (1, 2, 3)) self.assertEquals(self.kw, {'test': 'a', 'and_one': 'more'}) def test_calling_with_clashing_kw(self): rec = Receiver('test', self._callback, (1, 2, 3), {'test': 'a', 'p': 'a'}) self.assertEquals(rec.signal, 'test') rec(p='q') self.assertEquals(self.called, 1) self.assertEquals(self.args, (1, 2, 3)) self.assertEquals(self.kw, {'test': 'a', 'p': 'q'}) rec(other='wise') self.assertEquals(self.called, 2) self.assertEquals(self.args, (1, 2, 3)) self.assertEquals(self.kw, {'test': 'a', 'other': 'wise', 'p': 'a'}) def test_calling_with_clashing_kw_and_args(self): rec = Receiver('test', self._callback, (1, 2, 3), {'test': 'a', 'p': 'a'}) self.assertEquals(rec.signal, 'test') # without rec() self.assertEquals(self.called, 1) self.assertEquals(self.args, (1, 2, 3)) self.assertEquals(self.kw, {'test': 'a', 'p': 'a'}) rec(1, 2, 7, test='True', o='p') self.assertEquals(self.called, 2) self.assertEquals(self.args, (1, 2, 7, 1, 2, 3)) self.assertEquals(self.kw, {'test': 'True', 'o': 'p', 'p': 'a'}) def test_repr(self): rec = Receiver('test', 'callback', (0, 1, 2), {}) self.assertIn('%s' % id(rec), '%r' % rec) self.assertIn('test', '%r' % rec) self.assertIn('callback', '%r' % rec) self.assertIn('0, 1, 2', '%r' % rec) # Signal Descriptor test class SimpleSignaler(object): simple = SignalingProperty('simple') def __init__(self): self.emitted = [] def emit(self, signal, *values, **kw): self.emitted.append((signal, values, kw)) class DummySignaler(SimpleSignaler): simple_with_default = SignalingProperty('simple2', default=0) double_a = SignalingProperty('same-signal') double_b = SignalingProperty('same-signal') double_c = SignalingProperty('dif-var', var_name='_a') double_d = SignalingProperty('dif-var', var_name='_b') changer = ChangedSignalingProperty('state') changer_with_default = ChangedSignalingProperty('state2', default='off') def __init__(self): self.emitted = [] self._x = 0 self.x_get = 0 self.x_set = 0 def xget(self): self.x_get += 1 return self._x def xset(self, value): self.x_set += 1 self._x = value def xsq(self, value): self.x_set += 1 self._x = value * value x = CustomSignalingProperty('x-changed', xget, xset) x_square = CustomSignalingProperty('x-square', xget, xsq) class TestSignalingDescriptors(unittest.TestCase): def setUp(self): self.signaler = DummySignaler() def test_simple(self): self.signaler.simple = 'A' self._check(values=[('simple', ('A',), {})]) # empty self.signaler.emitted = [] self.signaler.simple = 'A' # stays empty self._check() def test_simple_with_default(self): self.signaler.simple_with_default = 'B' self._check(values=[('simple2', ('B',), {})]) # empty self.signaler.emitted = [] self.signaler.simple_with_default = 'B' # stays empty self._check() def test_changer(self): self.signaler.changer = 'Yes' self._check(values=[('state', ('Yes', None), {})]) # empty self.signaler.emitted = [] self.signaler.changer = 'Yes' # stays empty self._check() def test_changer_with_default(self): self.signaler.changer_with_default = 'another' self._check(values=[('state2', ('another', 'off'), {})]) # empty self.signaler.emitted = [] self.signaler.changer_with_default = 'another' # stays empty self._check() def test_double_same_var(self): self.signaler.double_a = 'A1' self.signaler.double_b = 'B2' self._check(values=[('same-signal', ('A1',), {}), ('same-signal', ('B2',), {})]) # empty self.signaler.emitted = [] # sending B2 over double a even thought it was changed by b self.signaler.double_a = 'B2' self.signaler.double_b = 'B2' # stays empty self._check() # but changing them different works self.signaler.double_a = 'B1' self.signaler.double_b = 'A2' self._check(values=[('same-signal', ('B1',), {}), ('same-signal', ('A2',), {})]) def test_double_differnt_var(self): self.signaler.double_c = 'A1' self.signaler.double_d = 'B2' self._check(values=[('dif-var', ('A1',), {}), ('dif-var', ('B2',), {})]) # empty self.signaler.emitted = [] self.signaler.double_c = 'A1' self.signaler.double_d = 'B2' # stays empty self._check() # but they still allow changes self.signaler.double_c = 'B1' self.signaler.double_d = 'A2' self._check(values=[('dif-var', ('B1',), {}), ('dif-var', ('A2',), {})]) def test_custom(self): self.signaler.x = 'Pocahontas' self._check(values=[('x-changed', ('Pocahontas',), {})], x='Pocahontas', x_get=2, x_set=1) self.assertEquals(self.signaler.x, 'Pocahontas') # settings again to the same value is boring me self.signaler.emitted = [] self.signaler.x_get = 0 self.signaler.x_set = 0 self.signaler.x = 'Pocahontas' self.assertEquals(self.signaler.emitted, []) self.assertEquals(self.signaler.x, 'Pocahontas') def test_custom_square(self): self.signaler.x_square = 10 self._check(values=[('x-square', (100,), {})], x=100, x_get=2, x_set=1) self.assertEquals(self.signaler.x, 100) def test_custom_square_nearly_the_same(self): self.signaler._x = 10 self.signaler.x_square = 10 self._check(values=[('x-square', (100,), {})], x=100, x_get=2, x_set=1) self.assertEquals(self.signaler.x, 100) def _check(self, values=[], x=0, x_set=0, x_get=0): self.assertEquals(self.signaler._x, x) self.assertEquals(self.signaler.x_set, x_set) self.assertEquals(self.signaler.x_get, x_get) self.assertEquals(self.signaler.emitted, values) class TestStayInObjectSignaling(unittest.TestCase): def setUp(self): self.foo = SimpleSignaler() self.bar = SimpleSignaler() def test_double_different_values(self): self.foo.simple = 'A' self.bar.simple = 'B' self.assertEquals(self.foo.simple, 'A') self.assertEquals(self.bar.simple, 'B') self.assertEquals(len(self.foo.emitted), 1) self.assertEquals(len(self.bar.emitted), 1) self.assertEquals(self.foo.emitted[0][1][0], 'A') self.assertEquals(self.bar.emitted[0][1][0], 'B') Coherence-0.6.6.2/coherence/test/__init__.py0000644000175000017500000000000011317660733016714 0ustar devdevCoherence-0.6.6.2/coherence/test/test_base.py0000644000175000017500000000235111317660733017141 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ Test cases for the L{Coherence base class} """ import time from twisted.trial import unittest from twisted.internet import reactor from twisted.internet.defer import Deferred from coherence.base import Coherence import coherence.extern.louie as louie class TestCoherence(unittest.TestCase): def setUp(self): louie.reset() self.coherence = Coherence({'unittest':'yes','logmode':'error'}) def tearDown(self): def cleaner(r): self.coherence.clear() return r dl = self.coherence.shutdown() dl.addBoth(cleaner) return dl def test_singleton(self): d = Deferred() c1 = Coherence({'unittest':'no','logmode':'error'}) c2 = Coherence({'unittest':'no','logmode':'error'}) c3 = Coherence({'unittest':'no','logmode':'error'}) def shutdown(r,instance): return instance.shutdown() d.addCallback(shutdown,c1) d.addCallback(shutdown,c2) d.addCallback(shutdown,c3) reactor.callLater(3, d.callback, None) return d Coherence-0.6.6.2/coherence/dbus_constants.py0000644000175000017500000000066511317660741017247 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007 - Frank Scholz DLNA_BUS_NAME = 'org.DLNA' # bus name for DLNA API BUS_NAME = 'org.Coherence' # the one with the dots OBJECT_PATH = '/org/Coherence' # the one with the slashes ;-) DEVICE_IFACE = '%s.device' % BUS_NAME SERVICE_IFACE = '%s.service' % BUS_NAME CDS_SERVICE = '%s.DMS.CDS' % DLNA_BUS_NAME Coherence-0.6.6.2/coherence/backend.py0000644000175000017500000004752111317660741015607 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007,, Frank Scholz import time from coherence.extern.simple_plugin import Plugin from coherence import log import coherence.extern.louie as louie from coherence.upnp.core.utils import getPage from coherence.extern.et import parse_xml from coherence.upnp.core import DIDLLite class Backend(log.Loggable,Plugin): """ the base class for all backends if there are any UPnP service actions, that can't be handled by the service classes itself, or need some special adjustments for the backend, they need to be defined here. Like maybe upnp_Browse for the CDS Browse action. """ implements = [] # list the device classes here # like [BinaryLight'] or ['MediaServer','MediaRenderer'] logCategory = 'backend' def __init__(self,server,**kwargs): """ the init method for a backend, should probably most of the time be overwritten when the init is done, send a signal to its device the device will then setup and announce itself, after that it calls the backends upnp_init method """ self.config = kwargs self.server = server # the UPnP device that's hosting that backend """ do whatever is necessary with the stuff we can extract from the config dict, connect maybe to an external data-source and start up the backend after that's done, tell Coherence about it """ log.Loggable.__init__(self) Plugin.__init__(self) """ this has to be done in the actual backend, maybe it has to wait for an answer from an external data-source first """ #self.init_completed() def init_completed(self, *args, **kwargs): """ inform Coherence that this backend is ready for announcement this method just accepts any form of arguments as we don't under which circumstances it is called """ louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def upnp_init(self): """ this method gets called after the device is fired, here all initializations of service related state variables should happen, as the services aren't available before that point """ pass class BackendStore(Backend): """ the base class for all MediaServer backend stores """ logCategory = 'backend_store' def __init__(self,server,*args,**kwargs): """ the init method for a MediaServer backend, should probably most of the time be overwritten when the init is done, send a signal to its device the device will then setup and announce itself, after that it calls the backends upnp_init method """ Backend.__init__(self, server, *args) self.config = kwargs self.server = server # the UPnP device that's hosting that backend self.update_id = 0 """ do whatever is necessary with the stuff we can extract from the config dict """ """ in case we want so serve something via the MediaServer web backend the BackendItem should pass an URI assembled of urlbase + '/' + id to the DIDLLite.Resource """ self.urlbase = kwargs.get('urlbase','') if not self.urlbase.endswith('/'): self.urlbase += '/' self.wmc_mapping = {'4':'4', '5':'5', '6':'6','7':'7','14':'14','F':'F', '11':'11','16':'16','B':'B','C':'C','D':'D', '13':'13', '17':'17', '8':'8', '9':'9', '10':'10', '15':'15', 'A':'A', 'E':'E'} self.wmc_mapping.update({'4':lambda: self._get_all_items(0), '8':lambda: self._get_all_items(0), 'B':lambda: self._get_all_items(0), }) """ and send out the signal when ready """ #louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def release(self): """ if anything needs to be cleaned up upon shutdown of this backend, this is the place for it """ pass def _get_all_items(self,id): """ a helper method to get all items as a response to some XBox 360 UPnP Search action probably never be used as the backend will overwrite the wmc_mapping with more appropriate methods """ items = [] item = self.get_by_id(id) if item is not None: containers = [item] while len(containers)>0: container = containers.pop() if container.mimetype not in ['root', 'directory']: continue for child in container.get_children(0,0): if child.mimetype in ['root', 'directory']: containers.append(child) else: items.append(child) return items def get_by_id(self,id): """ called by the CDS or the MediaServer web id is the id property of our DIDLLite item if this MediaServer implements containers, that can share their content, like 'all tracks', 'album' and 'album_of_artist' - they all have the same track item as content - then the id may be passed by the CDS like this: 'id@container' or 'id@container@container@container...' therefore a if isinstance(id, basestring): id = id.split('@',1) id = id[0] may be appropriate as the first thing to do when entering this method should return - None when no matching item for that id is found, - a BackendItem, - or a Deferred """ return None class BackendItem(log.Loggable): """ the base class for all MediaServer backend items """ logCategory = 'backend_item' def __init__(self, *args, **kwargs): """ most of the time we collect the necessary data for an UPnP ContentDirectoryService Container or Object and instantiate it here self.item = DIDLLite.Container(id,parent_id,name,...) or self.item = DIDLLite.MusicTrack(id,parent_id,name,...) To make that a valid UPnP CDS Object it needs one or more DIDLLite.Resource(uri,protocolInfo) self.item.res = [] res = DIDLLite.Resource(url, 'http-get:*:%s:*' % mimetype) url : the urlbase of our backend + '/' + our id res.size = size self.item.res.append(res) """ self.name = u'my_name' # the basename of a file, the album title, # the artists name,... # is expected to be unicode self.item = None self.update_id = 0 # the update id of that item, # when an UPnP ContentDirectoryService Container # this should be incremented on every modification self.location = None # the filepath of our media file, or alternatively # a FilePath or a ReverseProxyResource object self.cover = None # if we have some album art image, let's put # the filepath or link into here def get_children(self,start=0,end=0): """ called by the CDS and the MediaServer web should return - a list of its childs[start:end] - or a Deferred if end == 0, the request is for all childs after start - childs[start:] """ pass def get_child_count(self): """ called by the CDS should return - the number of its childs - len(childs) - or a Deferred """ def get_item(self): """ called by the CDS and the MediaServer web should return - an UPnP ContentDirectoryServer DIDLLite object - or a Deferred """ return self.item def get_name(self): """ called by the MediaServer web should return - the name of the item, it is always expected to be in unicode """ return self.name def get_path(self): """ called by the MediaServer web should return - the filepath where to find the media file that this item does refer to """ return self.location def get_cover(self): """ called by the MediaServer web should return - the filepath where to find the album art file only needed when we have created for that item an albumArtURI property that does point back to us """ return self.cover class BackendRssMixin: def update_data(self,rss_url,container=None,encoding="utf-8"): """ creates a deferred chain to retrieve the rdf file, parse and extract the metadata and reschedule itself """ def fail(f): self.info("fail %r", f) self.debug(f.getTraceback()) return f dfr = getPage(rss_url) dfr.addCallback(parse_xml, encoding=encoding) dfr.addErrback(fail) dfr.addCallback(self.parse_data,container) dfr.addErrback(fail) dfr.addBoth(self.queue_update,rss_url,container) return dfr def parse_data(self,xml_data,container): """ extract media info and create BackendItems """ pass def queue_update(self, error_or_failure,rss_url,container): from twisted.internet import reactor reactor.callLater(self.refresh, self.update_data,rss_url,container) class Container(BackendItem): def __init__(self, parent, title): BackendItem.__init__(self) self.parent = parent if self.parent is not None: self.parent_id = self.parent.get_id() else: self.parent_id = -1 self.store = None self.storage_id = None self.name = title self.mimetype = 'directory' self.children = [] self.children_ids = {} self.children_by_external_id = {} self.update_id = 0 self.item = None self.sorted = False def register_child(self, child, external_id = None): id = self.store.append_item(child) child.url = self.store.urlbase + str(id) child.parent = self if external_id is not None: child.external_id = external_id self.children_by_external_id[external_id] = child def add_child(self, child, external_id = None, update=True): id = self.register_child(child, external_id) if self.children is None: self.children = [] self.children.append(child) self.sorted = False if update == True: self.update_id += 1 def remove_child(self, child, external_id = None, update=True): self.children.remove(child) self.store.remove_item(child) if update == True: self.update_id += 1 if external_id is not None: child.external_id = None del self.children_by_external_id[external_id] def get_children(self, start=0, end=0): if self.sorted == False: def childs_sort(x,y): r = cmp(x.name,y.name) return r self.children.sort(cmp=childs_sort) self.sorted = True if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): if self.children is None: return 0 return len(self.children) def get_path(self): return self.store.urlbase + str(self.storage_id) def get_item(self): if self.item is None: self.item = DIDLLite.Container(self.storage_id, self.parent_id, self.name) self.item.childCount = len(self.children) return self.item def get_name(self): return self.name def get_id(self): return self.storage_id def get_update_id(self): return self.update_id class LazyContainer(Container, log.Loggable): logCategory = 'lazyContainer' def __init__(self, parent, title, external_id=None, refresh=0, childrenRetriever=None, **kwargs): Container.__init__(self, parent, title) self.childrenRetrievingNeeded = False self.childrenRetrievingDeferred = None self.childrenRetriever = childrenRetriever self.children_retrieval_campaign_in_progress = False self.childrenRetriever_params = kwargs self.childrenRetriever_params['parent']=self self.has_pages = (self.childrenRetriever_params.has_key('per_page')) self.external_id = None self.external_id = external_id self.retrieved_children = {} self.last_updated = 0 self.refresh = refresh def replace_by(self, item): if self.external_id is not None and item.external_id is not None: return (self.external_id == item.external_id) return True def add_child(self, child, external_id = None, update=True): if self.children_retrieval_campaign_in_progress is True: self.retrieved_children[external_id] = child else: Container.add_child(self, child, external_id=external_id, update=update) def update_children(self, new_children, old_children): children_to_be_removed = {} children_to_be_replaced = {} children_to_be_added = {} # Phase 1 # let's classify the item between items to be removed, # to be updated or to be added self.debug("Refresh pass 1:%d %d" % (len(new_children), len(old_children))) for id,item in old_children.items(): children_to_be_removed[id] = item for id,item in new_children.items(): if old_children.has_key(id): #print(id, "already there") children_to_be_replaced[id] = old_children[id] del children_to_be_removed[id] else: children_to_be_added[id] = new_children[id] # Phase 2 # Now, we remove, update or add the relevant items # to the list of items self.debug("Refresh pass 2: %d %d %d" % (len(children_to_be_removed), len(children_to_be_replaced), len(children_to_be_added))) # Remove relevant items from Container children for id,item in children_to_be_removed.items(): self.remove_child(item, external_id=id, update=False) # Update relevant items from Container children for id,item in children_to_be_replaced.items(): old_item = item new_item = new_children[id] replaced = False if self.replace_by: #print "Replacement method available: Try" replaced = old_item.replace_by(new_item) if replaced is False: #print "No replacement possible: we remove and add the item again" self.remove_child(old_item, external_id=id, update=False) self.add_child(new_item, external_id=id, update=False) # Add relevant items to COntainer children for id,item in children_to_be_added.items(): self.add_child(item, external_id=id, update=False) self.update_id += 1 def start_children_retrieval_campaign(self): #print "start_update_campaign" self.last_updated = time.time() self.retrieved_children = {} self.children_retrieval_campaign_in_progress = True def end_children_retrieval_campaign(self, success=True): #print "end_update_campaign" self.children_retrieval_campaign_in_progress = False if success is True: self.update_children(self.retrieved_children, self.children_by_external_id) self.update_id += 1 self.last_updated = time.time() self.retrieved_children = {} def retrieve_children(self, start=0): def items_retrieved(result, source_deferred): childrenRetrievingOffset = len(self.retrieved_children) if self.childrenRetrievingNeeded is True: return self.retrieve_children(childrenRetrievingOffset) return self.retrieved_children self.childrenRetrievingNeeded = False if self.has_pages is True: self.childrenRetriever_params['offset'] = start d = self.childrenRetriever(**self.childrenRetriever_params) d.addCallback(items_retrieved, d) return d def retrieve_all_children(self, start=0, request_count=0): def all_items_retrieved (result): #print "All items retrieved!" self.end_children_retrieval_campaign(True) return Container.get_children(self, start, request_count) def error_while_retrieving_items (error): #print "All items retrieved!" self.end_children_retrieval_campaign(False) return Container.get_children(self, start, request_count) # if first retrieval and refresh required # we start a looping call to periodically update the children #if ((self.last_updated == 0) and (self.refresh > 0)): # task.LoopingCall(self.retrieve_children,0,0).start(self.refresh, now=False) self.start_children_retrieval_campaign() if self.childrenRetriever is not None: d = self.retrieve_children(start) if start == 0: d.addCallbacks(all_items_retrieved, error_while_retrieving_items) return d else: self.end_children_retrieval_campaign() return self.children def get_children(self,start=0,request_count=0): # Check if an update is needed since last update current_time = time.time() delay_since_last_updated = current_time - self.last_updated period = self.refresh if (period > 0) and (delay_since_last_updated > period): self.info("Last update is older than %d s -> update data" % period) self.childrenRetrievingNeeded = True if self.childrenRetrievingNeeded is True: #print "children Retrieving IS Needed (offset is %d)" % start return self.retrieve_all_children() else: return Container.get_children(self, start, request_count) ROOT_CONTAINER_ID = 0 SEED_ITEM_ID = 1000 class AbstractBackendStore (BackendStore): def __init__(self, server, **kwargs): BackendStore.__init__(self, server, **kwargs) self.next_id = SEED_ITEM_ID self.store = {} def len(self): return len(self.store) def set_root_item(self, item): return self.append_item(item, storage_id = ROOT_CONTAINER_ID) def get_root_id(self): return ROOT_CONTAINER_ID def get_root_item(self): return self.get_by_id(ROOT_CONTAINER_ID) def append_item(self, item, storage_id=None): if storage_id is None: storage_id = self.getnextID() self.store[storage_id] = item item.storage_id = storage_id item.store = self return storage_id def remove_item(self, item): item.storage_id = -1 item.store = None del self.store[child.storage_id] def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0].split('.')[0] try: return self.store[int(id)] except (ValueError,KeyError): pass return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret Coherence-0.6.6.2/coherence/transcoder.py0000644000175000017500000006220511317660741016360 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ transcoder classes to be used in combination with a Coherence MediaServer using GStreamer pipelines for the actually work and feeding the output into a http response """ import pygst pygst.require('0.10') import gst import gobject gobject.threads_init() import os.path import urllib from twisted.web import resource, server from twisted.internet import protocol from coherence import log import struct def get_transcoder_name(transcoder): return transcoder.name class InternalTranscoder(object): """ just a class to inherit from and which we can look for upon creating our list of available transcoders """ class FakeTransformer(gst.Element, log.Loggable): logCategory = 'faker_datasink' _sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate", gst.PAD_SINK, gst.PAD_ALWAYS, gst.caps_new_any()) _srcpadtemplate = gst.PadTemplate ("srcpadtemplate", gst.PAD_SRC, gst.PAD_ALWAYS, gst.caps_new_any()) def __init__(self, destination=None, request=None): gst.Element.__init__(self) self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink") self.srcpad = gst.Pad(self._srcpadtemplate, "src") self.add_pad(self.sinkpad) self.add_pad(self.srcpad) self.sinkpad.set_chain_function(self.chainfunc) self.buffer = '' self.buffer_size = 0 self.proxy = False self.got_new_segment = False self.closed = False def get_fake_header(self): return struct.pack(">L4s", 32, 'ftyp') + \ "mp42\x00\x00\x00\x00mp42mp41isomiso2" def chainfunc(self, pad, buffer): if self.proxy: # we are in proxy mode already self.srcpad.push(buffer) return gst.FLOW_OK self.buffer = self.buffer + buffer.data if not self.buffer_size: try: self.buffer_size, a_type = struct.unpack(">L4s", self.buffer[:8]) except: return gst.FLOW_OK if len(self.buffer) < self.buffer_size: # we need to buffer more return gst.FLOW_OK buffer = self.buffer[self.buffer_size:] fake_header = self.get_fake_header() n_buf = gst.Buffer(fake_header + buffer) self.proxy = True self.srcpad.push(n_buf) return gst.FLOW_OK gobject.type_register(FakeTransformer) class DataSink(gst.Element, log.Loggable): logCategory = 'transcoder_datasink' _sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate", gst.PAD_SINK, gst.PAD_ALWAYS, gst.caps_new_any()) def __init__(self, destination=None, request=None): gst.Element.__init__(self) self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink") self.add_pad(self.sinkpad) self.sinkpad.set_chain_function(self.chainfunc) self.sinkpad.set_event_function(self.eventfunc) self.destination = destination self.request = request if self.destination is not None: self.destination = open(self.destination, 'wb') self.buffer = '' self.data_size = 0 self.got_new_segment = False self.closed = False def chainfunc(self, pad, buffer): if self.closed: return gst.FLOW_OK if self.destination is not None: self.destination.write(buffer.data) elif self.request is not None: self.buffer += buffer.data if len(self.buffer) > 200000: self.request.write(self.buffer) self.buffer = '' else: self.buffer += buffer.data self.data_size += buffer.size return gst.FLOW_OK def eventfunc(self, pad, event): if event.type == gst.EVENT_NEWSEGMENT: if not self.got_new_segment: self.got_new_segment = True else: self.closed = True elif event.type == gst.EVENT_EOS: if self.destination is not None: self.destination.close() elif self.request is not None: if len(self.buffer) > 0: self.request.write(self.buffer) self.request.finish() return True gobject.type_register(DataSink) class GStreamerPipeline(resource.Resource, log.Loggable): logCategory = 'gstreamer' addSlash = True def __init__(self, pipeline, content_type): self.pipeline_description = pipeline self.contentType = content_type self.requests = [] # if stream has a streamheader (something that has to be prepended # before any data), then it will be a tuple of GstBuffers self.streamheader = None self.parse_pipeline() resource.Resource.__init__(self) def parse_pipeline(self): self.pipeline = gst.parse_launch(self.pipeline_description) self.appsink = gst.element_factory_make("appsink", "sink") self.appsink.set_property('emit-signals', True) self.pipeline.add(self.appsink) enc = self.pipeline.get_by_name("enc") enc.link(self.appsink) self.appsink.connect("new-preroll", self.new_preroll) self.appsink.connect("new-buffer", self.new_buffer) self.appsink.connect("eos", self.eos) def start(self, request=None): self.info("GStreamerPipeline start %r %r", request, self.pipeline_description) self.requests.append(request) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished, request) def new_preroll(self, appsink): self.debug("new preroll") buffer = appsink.emit('pull-preroll') if not self.streamheader: # check caps for streamheader buffer caps = buffer.get_caps() s = caps[0] if s.has_key("streamheader"): self.streamheader = s["streamheader"] self.debug("setting streamheader") for r in self.requests: self.debug("writing streamheader") for h in self.streamheader: r.write(h.data) for r in self.requests: self.debug("writing preroll") r.write(buffer.data) def new_buffer(self, appsink): buffer = appsink.emit('pull-buffer') if not self.streamheader: # check caps for streamheader buffers caps = buffer.get_caps() s = caps[0] if s.has_key("streamheader"): self.streamheader = s["streamheader"] self.debug("setting streamheader") for r in self.requests: self.debug("writing streamheader") for h in self.streamheader: r.write(h.data) for r in self.requests: r.write(buffer.data) def eos(self, appsink): self.info("eos") for r in self.requests: r.finish() self.cleanup() def getChild(self, name, request): self.info('getChild %s, %s' % (name, request)) return self def render_GET(self, request): self.info('render GET %r' % (request)) request.setResponseCode(200) if hasattr(self, 'contentType'): request.setHeader('Content-Type', self.contentType) request.write('') headers = request.getAllHeaders() if('connection' in headers and headers['connection'] == 'close'): pass if self.requests: if self.streamheader: self.debug("writing streamheader") for h in self.streamheader: request.write(h.data) self.requests.append(request) else: self.parse_pipeline() self.start(request) return server.NOT_DONE_YET def render_HEAD(self, request): self.info('render HEAD %r' % (request)) request.setResponseCode(200) request.setHeader('Content-Type', self.contentType) request.write('') def requestFinished(self, result, request): self.info("requestFinished %r" % result) """ we need to find a way to destroy the pipeline here """ #from twisted.internet import reactor #reactor.callLater(0, self.pipeline.set_state, gst.STATE_NULL) self.requests.remove(request) if not self.requests: self.cleanup() def on_message(self, bus, message): t = message.type print "on_message", t if t == gst.MESSAGE_ERROR: #err, debug = message.parse_error() #print "Error: %s" % err, debug self.cleanup() elif t == gst.MESSAGE_EOS: self.cleanup() def cleanup(self): self.info("pipeline cleanup") self.pipeline.set_state(gst.STATE_NULL) self.requests = [] self.streamheader = None class BaseTranscoder(resource.Resource, log.Loggable): logCategory = 'transcoder' addSlash = True def __init__(self, uri, destination=None): self.info('uri %s %r' % (uri, type(uri))) if uri[:7] not in ['file://', 'http://']: uri = 'file://' + urllib.quote(uri) #FIXME self.uri = uri self.destination = destination resource.Resource.__init__(self) def getChild(self, name, request): self.info('getChild %s, %s' % (name, request)) return self def render_GET(self, request): self.info('render GET %r' % (request)) request.setResponseCode(200) if hasattr(self, 'contentType'): request.setHeader('Content-Type', self.contentType) request.write('') headers = request.getAllHeaders() if('connection' in headers and headers['connection'] == 'close'): pass self.start(request) return server.NOT_DONE_YET def render_HEAD(self, request): self.info('render HEAD %r' % (request)) request.setResponseCode(200) request.setHeader('Content-Type', self.contentType) request.write('') def requestFinished(self, result): self.info("requestFinished %r" % result) """ we need to find a way to destroy the pipeline here """ #from twisted.internet import reactor #reactor.callLater(0, self.pipeline.set_state, gst.STATE_NULL) gobject.idle_add(self.cleanup) def on_message(self, bus, message): t = message.type print "on_message", t if t == gst.MESSAGE_ERROR: #err, debug = message.parse_error() #print "Error: %s" % err, debug self.cleanup() elif t == gst.MESSAGE_EOS: self.cleanup() def cleanup(self): self.pipeline.set_state(gst.STATE_NULL) class PCMTranscoder(BaseTranscoder, InternalTranscoder): contentType = 'audio/L16;rate=44100;channels=2' name = 'lpcm' def start(self, request=None): self.info("PCMTranscoder start %r %r", request, self.uri) self.pipeline = gst.parse_launch( "%s ! decodebin ! audioconvert name=conv" % self.uri) conv = self.pipeline.get_by_name('conv') caps = gst.Caps("audio/x-raw-int,rate=44100,endianness=4321,channels=2,width=16,depth=16,signed=true") #FIXME: UGLY. 'filter' is a python builtin! filter = gst.element_factory_make("capsfilter", "filter") filter.set_property("caps", caps) self.pipeline.add(filter) conv.link(filter) sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) filter.link(sink) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class WAVTranscoder(BaseTranscoder, InternalTranscoder): contentType = 'audio/x-wav' name = 'wav' def start(self, request=None): self.info("start %r", request) self.pipeline = gst.parse_launch( "%s ! decodebin ! audioconvert ! wavenc name=enc" % self.uri) enc = self.pipeline.get_by_name('enc') sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) enc.link(sink) #bus = self.pipeline.get_bus() #bus.connect('message', self.on_message) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class MP3Transcoder(BaseTranscoder, InternalTranscoder): contentType = 'audio/mpeg' name = 'mp3' def start(self, request=None): self.info("start %r", request) self.pipeline = gst.parse_launch( "%s ! decodebin ! audioconvert ! lame name=enc" % self.uri) enc = self.pipeline.get_by_name('enc') sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) enc.link(sink) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class MP4Transcoder(BaseTranscoder, InternalTranscoder): """ Only works if H264 inside Quicktime/MP4 container is input Source has to be a valid uri """ contentType = 'video/mp4' name = 'mp4' def start(self, request=None): self.info("start %r", request) self.pipeline = gst.parse_launch( "%s ! qtdemux name=d ! queue ! h264parse ! mp4mux name=mux d. ! queue ! mux." % self.uri) mux = self.pipeline.get_by_name('mux') sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) mux.link(sink) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class MP2TSTranscoder(BaseTranscoder, InternalTranscoder): contentType = 'video/mpeg' name = 'mpegts' def start(self, request=None): self.info("start %r", request) ### FIXME mpeg2enc self.pipeline = gst.parse_launch( "mpegtsmux name=mux %s ! decodebin2 name=d ! queue ! ffmpegcolorspace ! mpeg2enc ! queue ! mux. d. ! queue ! audioconvert ! twolame ! queue ! mux." % self.uri) enc = self.pipeline.get_by_name('mux') sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) enc.link(sink) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class ThumbTranscoder(BaseTranscoder, InternalTranscoder): """ should create a valid thumbnail according to the DLNA spec neither width nor height must exceed 160px """ contentType = 'image/jpeg' name = 'thumb' def start(self, request=None): self.info("start %r", request) """ what we actually want here is a pipeline that calls us when it knows about the size of the original image, and allows us now to adjust the caps-filter with the calculated values for width and height new_width = 160 new_height = 160 if original_width > 160: new_heigth = int(float(original_height) * (160.0/float(original_width))) if new_height > 160: new_width = int(float(new_width) * (160.0/float(new_height))) elif original_height > 160: new_width = int(float(original_width) * (160.0/float(original_height))) """ try: type = request.args['type'][0] except: type = 'jpeg' if type == 'png': self.pipeline = gst.parse_launch( "%s ! decodebin2 ! videoscale ! video/x-raw-yuv,width=160,height=160 ! pngenc name=enc" % self.uri) self.contentType = 'image/png' else: self.pipeline = gst.parse_launch( "%s ! decodebin2 ! videoscale ! video/x-raw-yuv,width=160,height=160 ! jpegenc name=enc" % self.uri) self.contentType = 'image/jpeg' enc = self.pipeline.get_by_name('enc') sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) enc.link(sink) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class GStreamerTranscoder(BaseTranscoder): """ a generic Transcode based on GStreamer the pipeline which will be parsed upon calling the start method, as to be set as the attribute pipeline_description to the instantiated class same for the attribute contentType """ def start(self, request=None): self.info("start %r", request) self.pipeline = gst.parse_launch(self.pipeline_description % self.uri) enc = self.pipeline.get_by_name('mux') sink = DataSink(destination=self.destination, request=request) self.pipeline.add(sink) enc.link(sink) self.pipeline.set_state(gst.STATE_PLAYING) d = request.notifyFinish() d.addBoth(self.requestFinished) class ExternalProcessProtocol(protocol.ProcessProtocol): def __init__(self, caller): self.caller = caller def connectionMade(self): print "pp connection made" def outReceived(self, data): #print "outReceived with %d bytes!" % len(data) self.caller.write_data(data) def errReceived(self, data): #print "errReceived! with %d bytes!" % len(data) print "pp (err):", data.strip() def inConnectionLost(self): #print "inConnectionLost! stdin is closed! (we probably did it)" pass def outConnectionLost(self): #print "outConnectionLost! The child closed their stdout!" pass def errConnectionLost(self): #print "errConnectionLost! The child closed their stderr." pass def processEnded(self, status_object): print "processEnded, status %d" % status_object.value.exitCode print "processEnded quitting" self.caller.ended = True self.caller.write_data('') class ExternalProcessProducer(object): logCategory = 'externalprocess' def __init__(self, pipeline, request): self.pipeline = pipeline self.request = request self.process = None self.written = 0 self.data = '' self.ended = False request.registerProducer(self, 0) def write_data(self, data): if data: #print "write %d bytes of data" % len(data) self.written += len(data) # this .write will spin the reactor, calling .doWrite and then # .resumeProducing again, so be prepared for a re-entrant call self.request.write(data) if self.request and self.ended: print "closing" self.request.unregisterProducer() self.request.finish() self.request = None def resumeProducing(self): #print "resumeProducing", self.request if not self.request: return if self.process is None: argv = self.pipeline.split() executable = argv[0] argv[0] = os.path.basename(argv[0]) from twisted.internet import reactor self.process = reactor.spawnProcess(ExternalProcessProtocol(self), executable, argv, {}) def pauseProducing(self): pass def stopProducing(self): print "stopProducing", self.request self.request.unregisterProducer() self.process.loseConnection() self.request.finish() self.request = None class ExternalProcessPipeline(resource.Resource, log.Loggable): logCategory = 'externalprocess' addSlash = False def __init__(self, uri): self.uri = uri def getChildWithDefault(self, path, request): return self def render(self, request): print "ExternalProcessPipeline render" try: if self.contentType: request.setHeader('Content-Type', self.contentType) except AttributeError: pass ExternalProcessProducer(self.pipeline_description % self.uri, request) return server.NOT_DONE_YET def transcoder_class_wrapper(klass, content_type, pipeline): def create_object(uri): transcoder = klass(uri) transcoder.contentType = content_type transcoder.pipeline_description = pipeline return transcoder return create_object class TranscoderManager(log.Loggable): """ singleton class which holds information about all available transcoders they are put into a transcoders dict with their id as the key we collect all internal transcoders by searching for all subclasses of InternalTranscoder, the class will be the value transcoders defined in the config are parsed and stored as a dict in the transcoders dict in the config a transcoder description has to look like this: *** preliminary, will be extended and might even change without further notice *** %s ... gstreamer mpegts video/mpeg """ logCategory = 'transcoder_manager' _instance_ = None # Singleton def __new__(cls, *args, **kwargs): """ creates the singleton """ if cls._instance_ is None: obj = super(TranscoderManager, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj return cls._instance_ def __init__(self, coherence=None): """ initializes the class it should be called at least once with the main coherence class passed as an argument, so we have access to the config """ self.transcoders = {} for transcoder in InternalTranscoder.__subclasses__(): self.transcoders[get_transcoder_name(transcoder)] = transcoder if coherence is not None: self.coherence = coherence try: transcoders_from_config = self.coherence.config['transcoder'] if isinstance(transcoders_from_config, dict): transcoders_from_config = [transcoders_from_config] except KeyError: transcoders_from_config = [] for transcoder in transcoders_from_config: # FIXME: is anyone checking if all keys are given ? pipeline = transcoder['pipeline'] if not '%s' in pipeline: self.warning("Can't create transcoder %r:" " missing placehoder '%%s' in 'pipeline'", transcoder) continue try: transcoder_name = transcoder['name'].decode('ascii') except UnicodeEncodeError: self.warning("Can't create transcoder %r:" " the 'name' contains non-ascii letters", transcoder) continue transcoder_type = transcoder['type'].lower() if transcoder_type == 'gstreamer': wrapped = transcoder_class_wrapper(GStreamerTranscoder, transcoder['target'], transcoder['pipeline']) elif transcoder_type == 'process': wrapped = transcoder_class_wrapper(ExternalProcessPipeline, transcoder['target'], transcoder['pipeline']) else: self.warning("unknown transcoder type %r", transcoder_type) continue self.transcoders[transcoder_name] = wrapped #FIXME reduce that to info later self.warning("available transcoders %r" % self.transcoders) def select(self, name, uri, backend=None): # FIXME:why do we specify the name when trying to get it? if backend is not None: """ try to find a transcoder provided by the backend and return that here, if there isn't one continue with the ones provided by the config or the internal ones """ pass transcoder = self.transcoders[name](uri) return transcoder if __name__ == '__main__': t = Transcoder(None) Coherence-0.6.6.2/coherence/dbus_service.py0000644000175000017500000012241211317660741016666 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007,2008,2009 - Frank Scholz """ DBUS service class """ import time import urllib, urlparse #import gtk import dbus if dbus.__version__ < '0.82.2': raise ImportError, 'dbus-python module too old, pls get a newer one from http://dbus.freedesktop.org/releases/dbus-python/' from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import dbus.service #import dbus.gobject_service #import dbus.glib from coherence import __version__ from coherence.upnp.core import DIDLLite from coherence.dbus_constants import * from coherence.upnp.core.utils import parse_xml import coherence.extern.louie as louie from coherence import log from twisted.internet import reactor from twisted.internet import defer, task namespaces = {'{http://purl.org/dc/elements/1.1/}':'dc:', '{urn:schemas-upnp-org:metadata-1-0/upnp/}': 'upnp:', '{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}': 'DIDL-Lite:', '{urn:schemas-dlna-org:metadata-1-0}': 'dlna:', '{http://www.pv.com/pvns/}': 'pv:'} def un_namespace(text): for k,v in namespaces.items(): if text.startswith(k): return text.replace(k,v) return text class DBusCDSService(dbus.service.Object,log.Loggable): logCategory = 'dbus' NOT_FOR_THE_TUBES = True def __init__(self, service, dbus_device, bus): self.service = service self.dbus_device = dbus_device self.type = self.service.service_type.split(':')[3] # get the service name bus_name = dbus.service.BusName(CDS_SERVICE, bus) device_id = dbus_device.id self.path = OBJECT_PATH + '/devices/' + device_id + '/services/' + 'CDS' dbus.service.Object.__init__(self, bus, bus_name=bus_name, object_path=self.path) self.debug("DBusService %r %r", service, self.type) louie.connect(self.variable_changed, 'StateVariable.changed', sender=self.service) self.subscribeStateVariables() def _release_thyself(self): louie.disconnect(self.variable_changed, 'StateVariable.changed', sender=self.service) self.service = None self.dbus_device = None self.tube = None self.path = None self.remove_from_connection() del self def variable_changed(self,variable): self.StateVariableChanged(self.dbus_device.device.get_id(),self.type,variable.name, variable.value) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='s') def get_id(self): return self.service.id @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='s') def get_scpd_xml(self): return self.service.scpdXML @dbus.service.signal(CDS_SERVICE, signature='sssv') def StateVariableChanged(self, udn, service, variable, value): self.info("%s service %s signals StateVariable %s changed to %r" % (self.dbus_device.device.get_friendly_name(), self.type, variable, value)) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='as') def getAvailableActions(self): actions = self.service.get_actions() r = [] for name in actions.keys(): r.append(name) return r @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='ssv') def subscribeStateVariables(self): if not self.service: return notify = [v for v in self.service._variables[0].values() if v.send_events == True] if len(notify) == 0: return data = {} for n in notify: if n.name == 'LastChange': lc = {} for instance, vdict in self.service._variables.items(): v = {} for variable in vdict.values(): if( variable.name != 'LastChange' and variable.name[0:11] != 'A_ARG_TYPE_' and variable.never_evented == False): if hasattr(variable, 'dbus_updated') == False: variable.dbus_updated = None if len(v) > 0: lc[str(instance)] = v if len(lc) > 0: data[unicode(n.name)] = lc else: data[unicode(n.name)] = unicode(n.value) return self.dbus_device.device.get_id(), self.type, dbus.Dictionary(data,signature='sv',variant_level=3) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='s', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def GetSearchCapabilites(self,dbus_async_cb,dbus_async_err_cb): r = self.callAction('GetSearchCapabilites',{}) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['SearchCaps'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='s', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def GetSortCapabilities(self,dbus_async_cb,dbus_async_err_cb): r = self.callAction('GetSortCapabilities',{}) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['SortCaps'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='s', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def GetSortExtensionCapabilities(self,dbus_async_cb,dbus_async_err_cb): r = self.callAction('GetSortExtensionCapabilities',{}) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['SortExtensionCaps'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='s', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def GetFeatureList(self,dbus_async_cb,dbus_async_err_cb): r = self.callAction('GetFeatureList',{}) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['FeatureList'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='',out_signature='i', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def GetSystemUpdateID(self,dbus_async_cb,dbus_async_err_cb): r = self.callAction('GetSystemUpdateID',{}) if r == '': return r def convert_reply(data): dbus_async_cb(int(data['Id'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='sssiis',out_signature='aa{sv}iii', # was viii async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def Browse(self,ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount,SortCriteria, dbus_async_cb,dbus_async_err_cb): arguments = {'ObjectID':unicode(ObjectID), 'BrowseFlag':unicode(BrowseFlag), 'Filter':unicode(Filter), 'StartingIndex':int(StartingIndex), 'RequestedCount':int(RequestedCount), 'SortCriteria':unicode(SortCriteria)} r = self.callAction('Browse',arguments) if r == '': return r def convert_reply(data): et = parse_xml(data['Result'], 'utf-8') et = et.getroot() items = dbus.Array([],signature='v') def append(item): i = dbus.Dictionary({},signature='sv') for k,v in item.attrib.items(): i[un_namespace(k)] = v res = dbus.Array([],signature='v') for child in item: if un_namespace(child.tag) == 'DIDL-Lite:res': res_dict = dbus.Dictionary({},signature='sv') res_dict['url'] = unicode(child.text) for k,v in child.attrib.items(): res_dict[un_namespace(k)] = v res.append(res_dict) else: i[un_namespace(child.tag)] = child.text if len(res): i['res'] = res items.append(i) for item in et: append(item) dbus_async_cb(items,int(data['NumberReturned']),int(data['TotalMatches']),int(data['UpdateID'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='sssiis',out_signature='aa{sv}iii', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def Search(self,ContainerID,SearchCriteria,Filter,StartingIndex,RequestedCount,SortCriteria, dbus_async_cb,dbus_async_err_cb): arguments = {'ContainerID':unicode(ContainerID), 'SearchCriteria':unicode(SearchCriteria), 'Filter':unicode(Filter), 'StartingIndex':int(StartingIndex), 'RequestedCount':int(RequestedCount), 'SortCriteria':unicode(SortCriteria)} r = self.callAction('Search',arguments) if r == '': return r def convert_reply(data): et = parse_xml(data['Result'], 'utf-8') et = et.getroot() items = dbus.Array([],signature='v') def append(item): i = dbus.Dictionary({},signature='sv') for k,v in item.attrib.items(): i[un_namespace(k)] = v res = dbus.Array([],signature='v') for child in item: if un_namespace(child.tag) == 'DIDL-Lite:res': res_dict = dbus.Dictionary({},signature='sv') res_dict['url'] = unicode(child.text) for k,v in child.attrib.items(): res_dict[un_namespace(k)] = v res.append(res_dict) else: i[un_namespace(child.tag)] = child.text if len(res): i['res'] = res items.append(i) for item in et: append(item) dbus_async_cb(items,int(data['NumberReturned']),int(data['TotalMatches']),int(data['UpdateID'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='ss',out_signature='ss', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def CreateObject(self,ContainerID,Elements, dbus_async_cb,dbus_async_err_cb): arguments = {'ContainerID':unicode(ContainerID), 'Elements':unicode(Elements)} r = self.callAction('CreateObject',arguments) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['ObjectID']),unicode(data['Result'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='s',out_signature='', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def DestroyObject(self,ObjectID, dbus_async_cb,dbus_async_err_cb): arguments = {'ObjectID':unicode(ObjectID)} r = self.callAction('DestroyObject',arguments) if r == '': return r def convert_reply(data): dbus_async_cb() r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='sss',out_signature='', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def UpdateObject(self,ObjectID,CurrentTagValue,NewTagValue, dbus_async_cb,dbus_async_err_cb): arguments = {'ObjectID':unicode(ObjectID), 'CurrentTagValue':unicode(CurrentTagValue), 'NewTagValue':NewTagValue} r = self.callAction('UpdateObject',arguments) if r == '': return r def convert_reply(data): dbus_async_cb() r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='ss',out_signature='s', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def MoveObject(self,ObjectID,NewParentID, dbus_async_cb,dbus_async_err_cb): arguments = {'ObjectID':unicode(ObjectID), 'NewParentID':unicode(NewParentID)} r = self.callAction('MoveObject',arguments) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['NewObjectID'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='ss',out_signature='i', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def ImportResource(self,SourceURI,DestinationURI, dbus_async_cb,dbus_async_err_cb): arguments = {'SourceURI':unicode(SourceURI), 'DestinationURI':unicode(DestinationURI)} r = self.callAction('ImportResource',arguments) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['TransferID'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='ss',out_signature='i', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def ExportResource(self,SourceURI,DestinationURI, dbus_async_cb,dbus_async_err_cb): arguments = {'SourceURI':unicode(SourceURI), 'DestinationURI':unicode(DestinationURI)} r = self.callAction('ExportResource',arguments) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['TransferID'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='s',out_signature='', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def DeleteResource(self,ResourceURI, dbus_async_cb,dbus_async_err_cb): arguments = {'ResourceURI':unicode(ResourceURI)} r = self.callAction('DeleteResource',arguments) if r == '': return r def convert_reply(data): dbus_async_cb() r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='i',out_signature='', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def StopTransferResource(self,TransferID, dbus_async_cb,dbus_async_err_cb): arguments = {'TransferID':unicode(TransferID)} r = self.callAction('StopTransferResource',arguments) if r == '': return r def convert_reply(data): dbus_async_cb() r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='i',out_signature='sss', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def GetTransferProgress(self,TransferID, dbus_async_cb,dbus_async_err_cb): arguments = {'TransferID':unicode(TransferID)} r = self.callAction('GetTransferProgress',arguments) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['TransferStatus']),unicode(data['TransferLength']),unicode(data['TransferTotal'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) @dbus.service.method(CDS_SERVICE,in_signature='ss',out_signature='s', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def CreateReference(self,ContainerID,ObjectID, dbus_async_cb,dbus_async_err_cb): arguments = {'ContainerID':unicode(ContainerID), 'ObjectID':unicode(ObjectID)} r = self.callAction('CreateReference',arguments) if r == '': return r def convert_reply(data): dbus_async_cb(unicode(data['NewID'])) r.addCallback(convert_reply) r.addErrback(dbus_async_err_cb) def callAction(self,name,arguments): action = self.service.get_action(name) if action != None: d = action.call(**arguments) return d return '' class DBusService(dbus.service.Object,log.Loggable): logCategory = 'dbus' SUPPORTS_MULTIPLE_CONNECTIONS = True def __init__(self, service, dbus_device, bus): self.service = service self.dbus_device = dbus_device if self.service is not None: self.type = self.service.service_type.split(':')[3] # get the service name else: self.type = "from_the_tubes" try: bus_name = dbus.service.BusName(SERVICE_IFACE, bus) except: bus_name = None self.tube = bus else: self.tube = None if self.dbus_device is not None: self.device_id = self.dbus_device.id else: self.device_id = "dev_from_the_tubes" self.path = OBJECT_PATH + '/devices/' + self.device_id + '/services/' + self.type dbus.service.Object.__init__(self, bus, bus_name=bus_name, object_path=self.path) self.debug("DBusService %r %r", service, self.type) louie.connect(self.variable_changed, 'Coherence.UPnP.StateVariable.changed', sender=self.service) self.subscribe() #interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__] #for (name, funcs) in interfaces.iteritems(): # print name, funcs # if funcs.has_key('destroy_object'): # print """removing 'destroy_object'""" # del funcs['destroy_object'] # for func in funcs.values(): # if getattr(func, '_dbus_is_method', False): # print self.__class__._reflect_on_method(func) #self._get_service_methods() def _release_thyself(self): louie.disconnect(self.variable_changed, 'Coherence.UPnP.StateVariable.changed', sender=self.service) self.service = None self.dbus_device = None self.tube = None self.path = None self.remove_from_connection() del self def _get_service_methods(self): '''Returns a list of method descriptors for this object''' methods = [] for func in dir(self): func = getattr(self,func) if callable(func) and hasattr(func, '_dbus_is_method'): print func, func._dbus_interface, func._dbus_is_method if hasattr(func, 'im_func'): print func.im_func def variable_changed(self,variable): #print self.service, "got signal for change of", variable #print variable.name, variable.value #print type(variable.name), type(variable.value) self.StateVariableChanged(self.device_id,self.type,variable.name, variable.value) @dbus.service.signal(SERVICE_IFACE, signature='sssv') def StateVariableChanged(self, udn, service, variable, value): self.info("%s service %s signals StateVariable %s changed to %r", self.device_id, self.type, variable, value) @dbus.service.method(SERVICE_IFACE,in_signature='',out_signature='s') def get_scpd_xml(self): return self.service.get_scpdXML() @dbus.service.method(SERVICE_IFACE,in_signature='',out_signature='as') def get_available_actions(self): actions = self.service.get_actions() r = [] for name in actions.keys(): r.append(name) return r @dbus.service.method(SERVICE_IFACE,in_signature='',out_signature='s') def get_id(self): return self.service.id @dbus.service.method(SERVICE_IFACE,in_signature='sv',out_signature='v', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def action(self,name,arguments,dbus_async_cb,dbus_async_err_cb): #print "action", name, arguments def reply(data): dbus_async_cb(dbus.Dictionary(data,signature='sv',variant_level=4)) if self.service.client is not None: #print "action", name func = getattr(self.service.client,name,None) #print "action", func if callable(func): kwargs = {} try: for k,v in arguments.items(): kwargs[str(k)] = unicode(v) except: pass d = func(**kwargs) d.addCallback(reply) d.addErrback(dbus_async_err_cb) return '' @dbus.service.method(SERVICE_IFACE,in_signature='sa{ss}',out_signature='v', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb',), sender_keyword='sender',connection_keyword='connection') def call_action(self,name,arguments,dbus_async_cb,dbus_async_err_cb,sender=None,connection=None): print "call_action called by ", sender, connection, self.type, self.tube def reply(data,name,connection): if hasattr(connection,'_tube') == True: if name == 'Browse': didl = DIDLLite.DIDLElement.fromString(data['Result']) changed = False for item in didl.getItems(): new_res = DIDLLite.Resources() for res in item.res: remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') if remote_protocol == 'http-get' and remote_network == '*': quoted_url = 'mirabeau' + '/' + urllib.quote_plus(res.data) #print "modifying", res.data res.data = urlparse.urlunsplit(('http', self.service.device.client.coherence.external_address,quoted_url,"","")) #print "--->", res.data new_res.append(res) changed = True item.res = new_res if changed == True: didl.rebuild() ### FIXME this is not the proper way to do it data['Result'] = didl.toString().replace(' 0: lc[str(instance)] = v if len(lc) > 0: data[unicode(n.name)] = lc else: data[unicode(n.name)] = unicode(n.value) return self.dbus_device.device.get_id(), self.type, dbus.Dictionary(data,signature='sv',variant_level=3) class DBusDevice(dbus.service.Object,log.Loggable): logCategory = 'dbus' SUPPORTS_MULTIPLE_CONNECTIONS = True def __init__(self,device, bus): if device is not None: self.uuid = device.get_id()[5:] self.id = self.uuid.replace('-','') # we shouldn't need to do this, but ... self.id = self.id.replace('+','') else: self.id = "from_the_tubes" try: bus_name = dbus.service.BusName(DEVICE_IFACE, bus) except: bus_name = None self.tube = bus else: self.tube = None dbus.service.Object.__init__(self, bus, bus_name=bus_name, object_path=self.path()) self.services = [] self.device = device self.debug("DBusDevice %r %r", device, self.id) if device is not None: for service in device.get_services(): self.services.append(DBusService(service,self,bus)) if service.service_type.split(':')[3] == 'ContentDirectory': self.services.append(DBusCDSService(service,self,bus)) def _release_thyself(self): for service in self.services: service._release_thyself() self.services = None self.device = None self.tube = None self.path = None self.remove_from_connection() del self def path(self): return OBJECT_PATH + '/devices/' + self.id @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='v') def get_info(self): services = [x.path for x in self.services if getattr(x, "NOT_FOR_THE_TUBES", False) == False] r = {'path': self.path(), 'device_type': self.device.get_device_type(), 'friendly_name': self.device.get_friendly_name(), 'udn': self.device.get_id(), 'uri': list(urlparse.urlsplit(self.device.get_location())), 'presentation_url': self.device.get_presentation_url(), 'parent_udn': self.device.get_parent_id(), 'services': services} return dbus.Dictionary(r,signature='sv',variant_level=2) @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='s') def get_markup_name(self): return self.device.get_markup_name() @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='s') def get_friendly_name(self): return self.device.get_friendly_name() @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='s') def get_friendly_device_type(self): return self.device.get_friendly_device_type() @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='i') def get_device_type_version(self): return int(self.device.get_device_type_version()) @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='s') def get_id(self): return self.device.get_id() @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='s') def get_device_type(self): return self.device.get_device_type() @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='s') def get_usn(self): return self.device.get_usn() @dbus.service.method(DEVICE_IFACE,in_signature='',out_signature='av') def get_device_icons(self): return dbus.Array(self.device.icons,signature='av',variant_level=2) class DBusPontoon(dbus.service.Object,log.Loggable): logCategory = 'dbus' SUPPORTS_MULTIPLE_CONNECTIONS = True def __init__(self,controlpoint, bus=None): self.bus = bus or dbus.SessionBus() try: bus_name = dbus.service.BusName(BUS_NAME, self.bus) except: bus_name = None self.tube = self.bus else: self.tube = None self.bus_name = bus_name dbus.service.Object.__init__(self, self.bus, bus_name=self.bus_name, object_path=OBJECT_PATH) self.debug("D-Bus pontoon %r %r %r" % (self, self.bus, self.bus_name)) self.devices = {} self.controlpoint = controlpoint self.pinboard = {} # i am a stub service if i have no control point if self.controlpoint is None: return for device in self.controlpoint.get_devices(): self.devices[device.get_id()] = DBusDevice(device,self.bus_name) #louie.connect(self.cp_ms_detected, 'Coherence.UPnP.ControlPoint.MediaServer.detected', louie.Any) #louie.connect(self.cp_ms_removed, 'Coherence.UPnP.ControlPoint.MediaServer.removed', louie.Any) #louie.connect(self.cp_mr_detected, 'Coherence.UPnP.ControlPoint.MediaRenderer.detected', louie.Any) #louie.connect(self.cp_mr_removed, 'Coherence.UPnP.ControlPoint.MediaRenderer.removed', louie.Any) #louie.connect(self.remove_client, 'Coherence.UPnP.Device.remove_client', louie.Any) louie.connect(self._device_detected, 'Coherence.UPnP.Device.detection_completed', louie.Any) louie.connect(self._device_removed, 'Coherence.UPnP.Device.removed', louie.Any) self.debug("D-Bus pontoon started") @dbus.service.method(BUS_NAME,in_signature='sv',out_signature='') def pin(self,key,value): self.pinboard[key] = value print self.pinboard @dbus.service.method(BUS_NAME,in_signature='s',out_signature='v') def get_pin(self,key): return self.pinboard.get(key,'Coherence::Pin::None') @dbus.service.method(BUS_NAME,in_signature='s',out_signature='') def unpin(self,key): del self.pinboard[key] @dbus.service.method(BUS_NAME,in_signature='s',out_signature='s') def create_oob(self,file): print 'create_oob' key = str(time.time()) self.pinboard[key] = file print self.pinboard return self.controlpoint.coherence.urlbase + 'oob?key=' + key def remove_client(self, usn, client): self.info("removed %s %s" % (client.device_type,client.device.get_friendly_name())) try: getattr(self,str('UPnP_ControlPoint_%s_removed' % client.device_type))(usn) except: pass def remove(self,udn): #print "DBusPontoon remove", udn #print "before remove", self.devices d = self.devices.pop(udn) d._release_thyself() del d #print "after remove", self.devices @dbus.service.method(BUS_NAME,in_signature='',out_signature='s') def version(self): return __version__ @dbus.service.method(BUS_NAME,in_signature='',out_signature='s') def hostname(self): return self.controlpoint.coherence.hostname @dbus.service.method(BUS_NAME,in_signature='',out_signature='av') def get_devices(self): r = [] for device in self.devices.values(): #r.append(device.path()) r.append(device.get_info()) return dbus.Array(r,signature='v',variant_level=2) @dbus.service.method(BUS_NAME,in_signature='',out_signature='av', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def get_devices_async(self,dbus_async_cb,dbus_async_err_cb): infos = [] def iterate_devices(devices): for device in devices: infos.append(device.get_info()) yield infos def done(generator): dbus_async_cb(dbus.Array(infos, signature='v', variant_level=2)) devices = self.devices.copy().values() dfr = task.coiterate(iterate_devices(devices)) dfr.addCallbacks(done, lambda failure: dbus_async_err_cb(failure.value)) @dbus.service.method(BUS_NAME,in_signature='s',out_signature='v') def get_device_with_id(self,id): for device in self.devices.values(): if id == device.device.get_id(): return device.get_info() @dbus.service.method(BUS_NAME,in_signature='sa{ss}',out_signature='s') def add_plugin(self,backend,arguments): kwargs = {} for k,v in arguments.iteritems(): kwargs[str(k)] = str(v) p = self.controlpoint.coherence.add_plugin(backend,**kwargs) return str(p.uuid) @dbus.service.method(BUS_NAME,in_signature='s',out_signature='s') def remove_plugin(self,uuid): return str(self.controlpoint.coherence.remove_plugin(uuid)) @dbus.service.method(BUS_NAME,in_signature='ssa{ss}',out_signature='s') def call_plugin(self,uuid,method,arguments): try: plugin = self.controlpoint.coherence.active_backends[uuid] except KeyError: self.warning("no backend with the uuid %r found" % uuid) return "" function = getattr(plugin.backend, method, None) if function == None: return "" kwargs = {} for k,v in arguments.iteritems(): kwargs[str(k)] = unicode(v) function(**kwargs) return uuid @dbus.service.method(BUS_NAME,in_signature='ssa{ss}',out_signature='v', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def create_object(self,device_id,container_id,arguments,dbus_async_cb,dbus_async_err_cb): device = self.controlpoint.get_device_with_id(device_id) if device != None: client = device.get_client() new_arguments = {} for k,v in arguments.items(): new_arguments[str(k)] = unicode(v) def reply(data): dbus_async_cb(dbus.Dictionary(data,signature='sv',variant_level=4)) d = client.content_directory.create_object(str(container_id), new_arguments) d.addCallback(reply) d.addErrback(dbus_async_err_cb) @dbus.service.method(BUS_NAME,in_signature='sss',out_signature='v', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def import_resource(self,device_id,source_uri, destination_uri,dbus_async_cb,dbus_async_err_cb): device = self.controlpoint.get_device_with_id(device_id) if device != None: client = device.get_client() def reply(data): dbus_async_cb(dbus.Dictionary(data,signature='sv',variant_level=4)) d = client.content_directory.import_resource(str(source_uri), str(destination_uri)) d.addCallback(reply) d.addErrback(dbus_async_err_cb) @dbus.service.method(BUS_NAME,in_signature='ss',out_signature='v', async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')) def put_resource(self,destination_uri,filepath,dbus_async_cb,dbus_async_err_cb): def reply(data): dbus_async_cb(200) d = self.controlpoint.put_resource(str(destination_uri),unicode(filepath)) d.addCallback(reply) d.addErrback(dbus_async_err_cb) def _device_detected(self,device): id = device.get_id() #print "new_device_detected",device.get_usn(),device.friendly_device_type,id if id not in self.devices: new_device = DBusDevice(device,self.bus) self.devices[id] = new_device #print self.devices, id info = new_device.get_info() self.device_detected(info,id) if device.get_friendly_device_type() == 'MediaServer': self.UPnP_ControlPoint_MediaServer_detected(info,id) elif device.get_friendly_device_type() == 'MediaRenderer': self.UPnP_ControlPoint_MediaRenderer_detected(info,id) def _device_removed(self,usn=''): #print "_device_removed", usn id = usn.split('::')[0] device = self.devices[id] self.device_removed(id) #print device.get_friendly_device_type() if device.get_friendly_device_type() == 'MediaServer': self.UPnP_ControlPoint_MediaServer_removed(id) if device.get_friendly_device_type() == 'MediaRenderer': self.UPnP_ControlPoint_MediaServer_removed(id) reactor.callLater(1, self.remove, id) def cp_ms_detected(self,client,udn=''): #print "cp_ms_detected", udn if client.device.get_id() not in self.devices: new_device = DBusDevice(client.device,self.bus) self.devices[client.device.get_id()] = new_device self.UPnP_ControlPoint_MediaServer_detected(new_device.get_info(),udn) def cp_mr_detected(self,client,udn=''): if client.device.get_id() not in self.devices: new_device = DBusDevice(client.device,self.bus) self.devices[client.device.get_id()] = new_device self.UPnP_ControlPoint_MediaRenderer_detected(new_device.get_info(),udn) def cp_ms_removed(self,udn): #print "cp_ms_removed", udn self.UPnP_ControlPoint_MediaServer_removed(udn) # schedule removal of device from our cache after signal has # been called. Let's assume one second is long enough... reactor.callLater(1, self.remove, udn) def cp_mr_removed(self,udn): #print "cp_mr_removed", udn self.UPnP_ControlPoint_MediaRenderer_removed(udn) # schedule removal of device from our cache after signal has # been called. Let's assume one second is long enough... reactor.callLater(1, self.remove, udn) @dbus.service.signal(BUS_NAME, signature='vs') def UPnP_ControlPoint_MediaServer_detected(self,device,udn): self.info("emitting signal UPnP_ControlPoint_MediaServer_detected") @dbus.service.signal(BUS_NAME, signature='s') def UPnP_ControlPoint_MediaServer_removed(self,udn): self.info("emitting signal UPnP_ControlPoint_MediaServer_removed") @dbus.service.signal(BUS_NAME, signature='vs') def UPnP_ControlPoint_MediaRenderer_detected(self,device,udn): self.info("emitting signal UPnP_ControlPoint_MediaRenderer_detected") @dbus.service.signal(BUS_NAME, signature='s') def UPnP_ControlPoint_MediaRenderer_removed(self,udn): self.info("emitting signal UPnP_ControlPoint_MediaRenderer_removed") @dbus.service.signal(BUS_NAME, signature='vs') def device_detected(self,device,udn): self.info("emitting signal device_detected") @dbus.service.signal(BUS_NAME, signature='s') def device_removed(self,udn): self.info("emitting signal device_removed") """ org.DLNA related methods and signals """ @dbus.service.method(DLNA_BUS_NAME+'.DMC',in_signature='',out_signature='av') def getDMSList(self): return dbus.Array(self._get_devices_of_type('MediaServer'), signature='v', variant_level=2) def _get_devices_of_type(self, typ): return [ device.get_info() for device in self.devices.itervalues() if device.get_friendly_device_type() == typ ] @dbus.service.method(DLNA_BUS_NAME + '.DMC', in_signature='', out_signature='av') def getDMRList(self): return dbus.Array(self._get_devices_of_type('MediaRenderer'), signature='v', variant_level=2) @dbus.service.signal(BUS_NAME, signature='vs') def DMS_added(self,device,udn): self.info("emitting signal DMS_added") @dbus.service.signal(BUS_NAME, signature='s') def DMS_removed(self,udn): self.info("emitting signal DMS_removed") @dbus.service.signal(BUS_NAME, signature='vs') def DMR_added(self,device,udn): self.info("emitting signal DMR_added") @dbus.service.signal(BUS_NAME, signature='s') def DMR_removed(self,udn): self.info("emitting signal DMR_detected") Coherence-0.6.6.2/coherence/log.py0000644000175000017500000000541011317660741014770 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Philippe Normand from coherence.extern.log import log as externlog from coherence.extern.log.log import * import os def human2level(levelname): levelname = levelname.lower() if levelname.startswith('none'): return 0 if levelname.startswith('error'): return 1 if levelname.startswith('warn'): return 2 if levelname.startswith('info'): return 3 if levelname.startswith('debug'): return 4 return 5 def customStderrHandler(level, object, category, file, line, message): """ A log handler that writes to stderr. @type level: string @type object: string (or None) @type category: string @type message: string """ if not isinstance(message, basestring): message = str(message) if isinstance(message, unicode): message = message.encode('utf-8') message = "".join(message.splitlines()) where = "(%s:%d)" % (file, line) formatted_level = getFormattedLevelName(level) formatted_time = time.strftime("%b %d %H:%M:%S") formatted = '%s %-27s %-15s ' % (formatted_level, category, formatted_time) safeprintf(sys.stderr, formatted) safeprintf(sys.stderr, ' %s %s\n', message, where) sys.stderr.flush() def init(logfile=None,loglevel='*:2'): externlog.init('COHERENCE_DEBUG', True) externlog.setPackageScrubList('coherence', 'twisted') if logfile is not None: outputToFiles(stdout=None, stderr=logfile) # log WARNINGS by default if not os.getenv('COHERENCE_DEBUG'): if loglevel.lower() != 'none': setDebug(loglevel) if externlog.stderrHandler in externlog._log_handlers_limited: externlog.removeLimitedLogHandler(externlog.stderrHandler) if os.getenv('COHERENCE_DEBUG') or loglevel.lower() != 'none': "print addLimitedLogHandler(customStderrHandler)" externlog.addLimitedLogHandler(customStderrHandler) def set_debug(loglevel): setDebug(loglevel) def show_levels(): print externlog._categories # Make Loggable a new-style object class Loggable(externlog.Loggable, object): def logFunction(self, *args): if len(args) > 1: format = args[0] arguments = args[1:] try: format % arguments except TypeError: format += " ".join(["%r" for i in arguments]) args = (format,) + arguments return args def critical(self, msg, *args): self.warning(msg, *args) #def error(self, msg, *args): # self.log(msg, *args) def msg(self, message, *args): self.info(message, *args) Coherence-0.6.6.2/coherence/__init__.py0000644000175000017500000000213511317663670015753 0ustar devdevimport platform import sys __version_info__ = (0,6,6,2) __version__ = '.'.join(map(str,__version_info__)) SERVER_ID = ','.join([platform.system(), platform.release(), 'UPnP/1.0,Coherence UPnP framework', __version__]) try: from twisted import version as twisted_version from twisted.web import version as twisted_web_version from twisted.python.versions import Version except ImportError, exc: # log error to stderr, might be useful for debugging purpose sys.stderr.write("Twisted >= 2.5 and Twisted.Web >= 2.5 are required. "\ "Please install them.\n") raise try: if twisted_version < Version("twisted", 2, 5, 0): raise ImportError("Twisted >= 2.5 is required. Please install it.") if twisted_web_version < Version("twisted.web", 2, 5, 0): raise ImportError("Twisted.Web >= 2.5 is required. Please install it") except ImportError, exc: # log error to stderr, might be useful for debugging purpose for arg in exc.args: sys.stderr.write("%s\n" % arg) raise Coherence-0.6.6.2/coherence/extern/0000755000000000000000000000000011317673117015523 5ustar rootrootCoherence-0.6.6.2/coherence/extern/covers_by_amazon.py0000644000175000017500000002702611317660736021067 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Frank Scholz """ Covers by Amazon methods to retrieve covers/album art via the Amazon E-Commerce WebService v4 http://docs.amazonwebservices.com/AWSECommerceService/2007-04-04/DG/ The licence agreement says something about only one request per second, so we need to serialize and delay the calls a bit. The AWSAccessKeyId supplied is _ONLY_ for the use in conjunction with Coherence, http://coherence.beebits.net If you use this library in your own software please apply for your own key @ http://www.amazon.com/webservices and follow the rules of their license. Especially you must add the following disclaimer in a place that is reasonably viewable by the user of your application: PLEASE KEEP IN MIND THAT SOME OF THE CONTENT THAT WE MAKE AVAILABLE TO YOU THROUGH THIS APPLICATION COMES FROM AMAZON WEB SERVICES. ALL SUCH CONTENT IS PROVIDED TO YOU "AS IS." THIS CONTENT AND YOUR USE OF IT ARE SUBJECT TO CHANGE AND/OR REMOVAL AT ANY TIME. Furthermore if you save any of the cover images you have to take care that they are stored no longer than a maximum of one month and requested then from Amazon again. """ import os import urllib import StringIO from twisted.internet import reactor from twisted.internet import defer from twisted.web import client from et import parse_xml aws_server = { 'de': 'de', 'jp': 'jp', 'ca': 'ca', 'uk': 'co.uk', 'fr': 'fr'} aws_artist_query = '&Operation=ItemSearch' \ '&SearchIndex=Music' aws_asin_query = '&Operation=ItemLookup' aws_response_group = '&ResponseGroup=Images' aws_ns = 'http://webservices.amazon.com/AWSECommerceService/2005-10-05' aws_image_size = { 'large': 'LargeImage', 'medium': 'MediumImage', 'small': 'SmallImage'} class WorkQueue(object): _instance_ = None # Singleton def __new__(cls, *args, **kwargs): obj = getattr(cls,'_instance_',None) if obj is not None: return obj else: obj = super(WorkQueue, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj obj.max_workers = kwargs.get('max_workers', 1) obj.queue = [] obj.workers = [] return obj def __init__(self, method, *args, **kwargs): self.queue.append((method,args,kwargs)) self.queue_run() def queue_run(self): if len(self.queue) == 0: return if len(self.workers) >= self.max_workers: #print "WorkQueue - all workers busy" return work = self.queue.pop() d = defer.maybeDeferred(work[0], *work[1],**work[2]) self.workers.append(d) d.addCallback(self.remove_from_workers, d) d.addErrback(self.remove_from_workers, d) def remove_from_workers(self, result, d): self.workers.remove(d) reactor.callLater(1,self.queue_run) # a very,very weak attempt class CoverGetter(object): """ retrieve a cover image for a given ASIN, a TITLE or an ARTIST/TITLE combo parameters are: filename: where to save a received image if NONE the image will be passed to the callback callback: a method to call with the filename or the image as a parameter after the image request and save was successful can be: - only a callable - a tuple with a callable, - optional an argument or a tuple of arguments - optional a dict with keyword arguments not_found_callback: a method to call when the search at Amazon failed can be: - only a callable - a tuple with a callable, - optional an argument or a tuple of arguments - optional a dict with keyword arguments locale: which Amazon Webservice Server to use, defaults to .com image_size: request the cover as large|medium|small image resolution seems to be in pixels for large: 500x500, medium: 160x160 and small: 75x75 asin: the Amazon Store Identification Number artist: the artists name title: the album title if the filename extension and the received image extension differ, the image is converted with PIL to the desired format http://www.pythonware.com/products/pil/index.htm """ def __init__(self, filename, aws_key, callback=None, not_found_callback=None, locale=None, image_size='large', title=None, artist=None, asin=None): self.aws_base_query = '/onca/xml?Service=AWSECommerceService' \ '&AWSAccessKeyId=%s' % aws_key self.filename = filename self.callback = callback self._errcall = not_found_callback self.server = 'http://ecs.amazonaws.%s' % aws_server.get(locale,'com') self.image_size = image_size def sanitize(s): if s is not None: s = unicode(s.lower()) s = s.replace(unicode(u'ä'),unicode('ae')) s = s.replace(unicode(u'ö'),unicode('oe')) s = s.replace(unicode(u'ü'),unicode('ue')) s = s.replace(unicode(u'ß'),unicode('ss')) if isinstance(s,unicode): s = s.encode('ascii','ignore') else: s = s.decode('utf-8').encode('ascii','ignore') return s if asin != None: query = aws_asin_query + '&ItemId=%s' % urllib.quote(asin) elif (artist is not None or title is not None): query = aws_artist_query if artist is not None: artist = sanitize(artist) query = '&'.join((query, 'Artist=%s' % urllib.quote(artist))) if title is not None: title = sanitize(title) query = '&'.join((query, 'Title=%s' % urllib.quote(title))) else: raise KeyError, "Please supply either asin, title or artist and title arguments" url = self.server+self.aws_base_query+aws_response_group+query WorkQueue(self.send_request, url) def send_request(self,url,*args,**kwargs): #print "send_request", url d= client.getPage(url) d.addCallback(self.got_response) d.addErrback(self.got_error, url) return d def got_image(self, result, convert_from='', convert_to=''): #print "got_image" if(len(convert_from) and len(convert_to)): #print "got_image %d, convert to %s" % (len(result), convert_to) try: import Image im = Image.open(StringIO.StringIO(result)) name,file_ext = os.path.splitext(self.filename) self.filename = name + convert_to im.save(self.filename) except ImportError: print "we need the Python Imaging Library to do image conversion" if self.filename == None: data = result else: data = self.filename if self.callback is not None: #print "got_image", self.callback if isinstance(self.callback,tuple): if len(self.callback) == 3: c,a,kw = self.callback if not isinstance(a,tuple): a = (a,) a=(data,) + a c(*a,**kw) if len(self.callback) == 2: c,a = self.callback if isinstance(a,dict): c(data,**a) else: if not isinstance(a,tuple): a = (a,) a=(data,) + a c(*a) if len(self.callback) == 1: c = self.callback c(data) else: self.callback(data) def got_response(self, result): convert_from = convert_to = '' result = parse_xml(result, encoding='utf-8') image_tag = result.find('.//{%s}%s' % (aws_ns,aws_image_size.get(self.image_size,'large'))) if image_tag != None: image_url = image_tag.findtext('{%s}URL' % aws_ns) if self.filename == None: d = client.getPage(image_url) else: _,file_ext = os.path.splitext(self.filename) if file_ext == '': _,image_ext = os.path.splitext(image_url) if image_ext != '': self.filename = ''.join((self.filename, image_ext)) else: _,image_ext = os.path.splitext(image_url) if image_ext != '' and file_ext != image_ext: #print "hmm, we need a conversion..." convert_from = image_ext convert_to = file_ext if len(convert_to): d = client.getPage(image_url) else: d = client.downloadPage(image_url, self.filename) d.addCallback(self.got_image, convert_from=convert_from, convert_to=convert_to) d.addErrback(self.got_error, image_url) else: if self._errcall is not None: if isinstance(self._errcall,tuple): if len(self._errcall) == 3: c,a,kw = self._errcall if not isinstance(a,tuple): a = (a,) c(*a,**kw) if len(self._errcall) == 2: c,a = self._errcall if isinstance(a,dict): c(**a) else: if not isinstance(a,tuple): a = (a,) c(*a) if len(self._errcall) == 1: c = self._errcall c() else: self._errcall() def got_error(self, failure, url): print "got_error", failure, url if __name__ == '__main__': from twisted.python import usage class Options(usage.Options): optParameters = [['artist', 'a', '', 'artist name'], ['title', 't', '', 'title'], ['asin', 's', '', 'ASIN'], ['filename', 'f', 'cover.jpg', 'filename'], ] options = Options() try: options.parseOptions() except usage.UsageError, errortext: import sys print '%s: %s' % (sys.argv[0], errortext) print '%s: Try --help for usage details.' % (sys.argv[0]) sys.exit(1) def got_it(filename, *args, **kwargs): print "Mylady, it is an image and its name is", filename, args, kwargs aws_key = '1XHSE4FQJ0RK0X3S9WR2' print options['asin'],options['artist'],options['title'] if len(options['asin']): reactor.callWhenRunning(CoverGetter,options['filename'],aws_key, callback=got_it,asin=options['asin']) elif len(options['artist']) and len(options['title']): reactor.callWhenRunning(CoverGetter,options['filename'],aws_key, callback=got_it,artist=options['artist'],title=options['title']) reactor.run() Coherence-0.6.6.2/coherence/extern/telepathy/0000755000000000000000000000000011317673117017522 5ustar rootrootCoherence-0.6.6.2/coherence/extern/telepathy/tubeconn.py0000644000175000017500000000726211317660736021343 0ustar devdev# Copyright (C) 2007 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU 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 # Modified by Philippe Normand. from dbus.connection import Connection from dbus import PROPERTIES_IFACE from telepathy.interfaces import CHANNEL_TYPE_DBUS_TUBE from coherence import log class TubeConnection(Connection, log.Loggable): logCategory = "tube_connection" def __new__(cls, conn, tube, address, group_iface=None, mainloop=None): self = super(TubeConnection, cls).__new__(cls, address, mainloop=mainloop) self._tube = tube self.participants = {} self.bus_name_to_handle = {} self._mapping_watches = [] if group_iface is None: method = conn.GetSelfHandle else: method = group_iface.GetSelfHandle method(reply_handler=self._on_get_self_handle_reply, error_handler=self._on_get_self_handle_error) return self def _on_get_self_handle_reply(self, handle): self.self_handle = handle tube_channel = self._tube[CHANNEL_TYPE_DBUS_TUBE] match = tube_channel.connect_to_signal('DBusNamesChanged', self._on_dbus_names_changed) self._tube[PROPERTIES_IFACE].Get(CHANNEL_TYPE_DBUS_TUBE, 'DBusNames', reply_handler=self._on_get_dbus_names_reply, error_handler=self._on_get_dbus_names_error) self._dbus_names_changed_match = match def _on_get_self_handle_error(self, e): self.warning('GetSelfHandle failed: %s', e) def close(self): self._dbus_names_changed_match.remove() self._on_dbus_names_changed({}, self.participants.keys()) super(TubeConnection, self).close() def _on_get_dbus_names_reply(self, names): self._on_dbus_names_changed(names, ()) def _on_get_dbus_names_error(self, e): self.warning('Get DBusNames property failed: %s', e) def _on_dbus_names_changed(self, added, removed): for handle, bus_name in added.items(): if handle == self.self_handle: # I've just joined - set my unique name self.set_unique_name(bus_name) self.participants[handle] = bus_name self.bus_name_to_handle[bus_name] = handle # call the callback while the removed people are still in # participants, so their bus names are available for callback in self._mapping_watches: callback(added, removed) for handle in removed: bus_name = self.participants.pop(handle, None) self.bus_name_to_handle.pop(bus_name, None) def watch_participants(self, callback): self._mapping_watches.append(callback) if self.participants: # GetDBusNames already returned: fake a participant add event # immediately added = list(self.participants.iteritems()) callback(added, []) Coherence-0.6.6.2/coherence/extern/telepathy/client.py0000644000175000017500000002264011317660736021001 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 Philippe Normand import time import dbus.glib from dbus import PROPERTIES_IFACE from telepathy.client import Channel from telepathy.interfaces import CONN_INTERFACE, CHANNEL_INTERFACE_GROUP, \ CHANNEL_TYPE_CONTACT_LIST, CHANNEL_TYPE_TEXT, CHANNEL_INTERFACE, \ CONNECTION_INTERFACE_REQUESTS, CHANNEL_INTERFACE_TUBE, CHANNEL_TYPE_DBUS_TUBE from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT, \ CONNECTION_HANDLE_TYPE_LIST, CONNECTION_HANDLE_TYPE_ROOM, \ CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, \ CONNECTION_STATUS_CONNECTING, TUBE_CHANNEL_STATE_LOCAL_PENDING, \ TUBE_CHANNEL_STATE_REMOTE_PENDING, TUBE_CHANNEL_STATE_OPEN, \ TUBE_CHANNEL_STATE_NOT_OFFERED from coherence.extern.telepathy.tubeconn import TubeConnection from coherence.extern.telepathy.connect import tp_connect from coherence import log TUBE_STATE = {TUBE_CHANNEL_STATE_LOCAL_PENDING : 'local pending', TUBE_CHANNEL_STATE_REMOTE_PENDING : 'remote pending', TUBE_CHANNEL_STATE_OPEN : 'open', TUBE_CHANNEL_STATE_NOT_OFFERED: 'not offered'} class Client(log.Loggable): logCategory = "tp_client" def __init__(self, manager, protocol, account, muc_id, existing_client=False): log.Loggable.__init__(self) self.account = account self.existing_client = existing_client self.channel_text = None self._unsent_messages = [] self._tube_conns = {} self._tubes = {} self._channels = [] self._pending_tubes = {} if self.existing_client: self.muc_id = self.existing_client.muc_id self.conn = self.existing_client.conn self.ready_cb(self.conn) else: if protocol == 'local-xmpp': self.muc_id = muc_id else: self.muc_id = "%s@%s" % (muc_id, self.account["fallback-conference-server"]) self.conn = tp_connect(manager, protocol, account, self.ready_cb) conn_obj = self.conn[CONN_INTERFACE] conn_obj.connect_to_signal('StatusChanged', self.status_changed_cb) conn_obj.connect_to_signal('NewChannels', self.new_channels_cb) self.joined = False def start(self): if not self.existing_client: self.conn[CONN_INTERFACE].Connect() def stop(self): if not self.existing_client: try: self.conn[CONN_INTERFACE].Disconnect() except: pass def ready_cb(self, conn): self.conn[CONNECTION_INTERFACE_REQUESTS].connect_to_signal("NewChannels", self.new_channels_cb) self.self_handle = self.conn[CONN_INTERFACE].GetSelfHandle() self.fill_roster() self.join_muc() def status_changed_cb(self, status, reason): self.debug("status changed to %r: %r", status, reason) if status == CONNECTION_STATUS_CONNECTING: self.info('connecting') elif status == CONNECTION_STATUS_CONNECTED: self.info('connected') elif status == CONNECTION_STATUS_DISCONNECTED: self.info('disconnected') def fill_roster(self): self.info("Filling up the roster") self.roster = {} conn_iface = self.conn[CONN_INTERFACE] for name in ('subscribe', 'publish', 'hide', 'allow', 'deny', 'known'): try: chan = self._request_list_channel(name) except dbus.DBusException: self.debug("'%s' channel is not available" % name) continue group_iface = chan[CHANNEL_INTERFACE_GROUP] current, local_pending, remote_pending = (group_iface.GetAllMembers()) for member in current: contact_id = conn_iface.InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [member])[0] self.roster[contact_id] = name self.debug("roster contents: %r", self.roster) def _request_list_channel(self, name): handle = self.conn[CONN_INTERFACE].RequestHandles( CONNECTION_HANDLE_TYPE_LIST, [name])[0] return self.conn.request_channel( CHANNEL_TYPE_CONTACT_LIST, CONNECTION_HANDLE_TYPE_LIST, handle, True) def join_muc(self): conn_obj = self.conn[CONN_INTERFACE] # workaround to be sure that the muc service is fully resolved in # Salut. if conn_obj.GetProtocol() == "local-xmpp": time.sleep(2) muc_id = self.muc_id self.info("joining MUC %r", muc_id) if self.existing_client: self.channel_text = self.existing_client.channel_text #self.new_channels_cb(self.existing_client._channels) self._tubes = self.existing_client._pending_tubes for path, tube in self._tubes.iteritems(): self.connect_tube_signals(tube) self.got_tube(tube) else: conn_iface = self.conn[CONNECTION_INTERFACE_REQUESTS] params = {CHANNEL_INTERFACE+".ChannelType": CHANNEL_TYPE_TEXT, CHANNEL_INTERFACE+".TargetHandleType": CONNECTION_HANDLE_TYPE_ROOM, CHANNEL_INTERFACE+ ".TargetID": muc_id} chan_path, props = conn_iface.CreateChannel(params) self.channel_text = Channel(self.conn.dbus_proxy.bus_name, chan_path) room_iface = self.channel_text[CHANNEL_INTERFACE_GROUP] self.self_handle = room_iface.GetSelfHandle() room_iface.connect_to_signal("MembersChanged", self.text_channel_members_changed_cb) if self.self_handle in room_iface.GetMembers(): self.joined = True self.muc_joined() def new_channels_cb(self, channels): self._channels.extend(channels) for path, props in channels: self.debug("new channel with path %r and props %r", path, props) if props[CHANNEL_INTERFACE + ".ChannelType"] == CHANNEL_TYPE_DBUS_TUBE: tube = Channel(self.conn.dbus_proxy.bus_name, path) self.connect_tube_signals(tube) tube.props = props self._tubes[path] = tube self.got_tube(tube) def connect_tube_signals(self, tube): tube_iface = tube[CHANNEL_INTERFACE_TUBE] state_changed = lambda state: self.tube_channel_state_changed_cb(tube, state) tube_iface.connect_to_signal("TubeChannelStateChanged", state_changed) channel_iface = tube[CHANNEL_INTERFACE] channel_iface.connect_to_signal("Closed", lambda: self.tube_closed(tube)) def got_tube(self, tube): props = tube.props self.debug("got tube with props %r", props) initiator_id = props[CHANNEL_INTERFACE + ".InitiatorID"] service = props[CHANNEL_TYPE_DBUS_TUBE + ".ServiceName"] state = tube[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_TUBE, 'State') self.info("new D-Bus tube offered by %s. Service: %s. State: %s", initiator_id, service, TUBE_STATE[state]) def tube_opened(self, tube): tube_path = tube.object_path state = tube[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_TUBE, 'State') self.info("tube %r opened (state: %s)", tube_path, TUBE_STATE[state]) group_iface = self.channel_text[CHANNEL_INTERFACE_GROUP] tube_address = tube.local_address tube_conn = TubeConnection(self.conn, tube, tube_address, group_iface=group_iface) self._tube_conns[tube_path] = tube_conn return tube_conn def received_cb(self, id, timestamp, sender, type, flags, text): channel_obj = self.channel_text[CHANNEL_TYPE_TEXT] channel_obj.AcknowledgePendingMessages([id]) conn_obj = self.conn[telepathy.CONN_INTERFACE] contact = conn_obj.InspectHandles(telepathy.HANDLE_TYPE_CONTACT, [sender])[0] self.info("Received message from %s: %s", contact, text) def tube_channel_state_changed_cb(self, tube, state): if state == TUBE_CHANNEL_STATE_OPEN: self.tube_opened(tube) def tube_closed(self, tube): tube_path = tube.object_path self.info("tube %r closed", tube_path) self._tube_conns[tube_path].close() del self._tube_conns[tube_path] del self._tubes[tube_path] def text_channel_members_changed_cb(self, message, added, removed, local_pending, remote_pending, actor, reason): if self.self_handle in added and not self.joined: self.joined = True self.muc_joined() def muc_joined(self): self.info("MUC joined") for msg in self._unsent_messages: self.send_text(msg) self._unsent_messages = [] def send_text(self, text): if self.channel_text: self.info("Sending text %r", text) channel_obj = self.channel_text[CHANNEL_TYPE_TEXT] channel_obj.Send(telepathy.CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, text) else: self.info("Queing text %r until muc is joined", text) self._unsent_messages.append(text) Coherence-0.6.6.2/coherence/extern/telepathy/tube.py0000644000175000017500000000671311317660736020465 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 Philippe Normand from telepathy.interfaces import CHANNEL_INTERFACE, CONNECTION_INTERFACE_REQUESTS, \ CHANNEL_TYPE_DBUS_TUBE from telepathy.constants import CONNECTION_HANDLE_TYPE_ROOM, \ SOCKET_ACCESS_CONTROL_CREDENTIALS from coherence.extern.telepathy.client import Client class TubePublisherMixin(object): def __init__(self, tubes_to_offer): self._tubes_to_offer = tubes_to_offer def muc_joined(self): self.info("muc joined. Offering the tubes") conn_iface = self.conn[CONNECTION_INTERFACE_REQUESTS] params = {CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_DBUS_TUBE, CHANNEL_INTERFACE + ".TargetHandleType": CONNECTION_HANDLE_TYPE_ROOM, CHANNEL_INTERFACE + ".TargetID": self.muc_id} for interface in self._tubes_to_offer.keys(): params[CHANNEL_TYPE_DBUS_TUBE + ".ServiceName"] = interface conn_iface.CreateChannel(params) def got_tube(self, tube): super(TubePublisherMixin, self).got_tube(tube) initiator_handle = tube.props[CHANNEL_INTERFACE + ".InitiatorHandle"] if initiator_handle == self.self_handle: self.finish_tube_offer(tube) def finish_tube_offer(self, tube): self.info("offering my tube located at %r", tube.object_path) service_name = tube.props[CHANNEL_TYPE_DBUS_TUBE + ".ServiceName"] params = self._tubes_to_offer[service_name] params["initiator"] = self.account["account"] address = tube[CHANNEL_TYPE_DBUS_TUBE].Offer(params, SOCKET_ACCESS_CONTROL_CREDENTIALS) tube.local_address = address self.info("local tube address: %r", address) class TubePublisher(TubePublisherMixin, Client): logCategory = "tube_publisher" def __init__(self, manager, protocol, account, muc_id, tubes_to_offer): TubePublisherMixin.__init__(self, tubes_to_offer) Client.__init__(self, manager, protocol, account, muc_id) class TubeConsumerMixin(object): logCategory = "tube_consumer" def __init__(self, found_peer_callback=None, disapeared_peer_callback=None): self.found_peer_callback = found_peer_callback self.disapeared_peer_callback = disapeared_peer_callback def got_tube(self, tube): super(TubeConsumerMixin, self).got_tube(tube) self.accept_tube(tube) def accept_tube(self, tube): if self.pre_accept_tube(tube): self.info("accepting tube %r", tube.object_path) tube_iface = tube[CHANNEL_TYPE_DBUS_TUBE] tube.local_address = tube_iface.Accept(SOCKET_ACCESS_CONTROL_CREDENTIALS) else: self.warning("tube %r not allowed", tube) def pre_accept_tube(self, tube): return True def tube_closed(self, tube): self.disapeared_peer_callback(tube) super(TubeConsumerMixin, self).tube_closed(tube) class TubeConsumer(TubeConsumerMixin, Client): logCategory = "tube_consumer" def __init__(self, manager, protocol, account, muc_id, found_peer_callback=None, disapeared_peer_callback=None): TubeConsumerMixin.__init__(self, found_peer_callback=found_peer_callback, disapeared_peer_callback=disapeared_peer_callback) Client.__init__(self, manager, protocol, account, muc_id) Coherence-0.6.6.2/coherence/extern/telepathy/mirabeau_tube_consumer.py0000644000175000017500000001347311317660736024246 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 Philippe Normand from dbus import PROPERTIES_IFACE from telepathy.interfaces import CHANNEL_TYPE_DBUS_TUBE, CONN_INTERFACE, \ CHANNEL_INTERFACE, CHANNEL_INTERFACE_TUBE from coherence.extern.telepathy import client, tube from coherence.dbus_constants import BUS_NAME, OBJECT_PATH, DEVICE_IFACE, SERVICE_IFACE from coherence import dbus_service class MirabeauTubeConsumerMixin(tube.TubeConsumerMixin): def __init__(self, found_peer_callback=None, disapeared_peer_callback=None, got_devices_callback=None): tube.TubeConsumerMixin.__init__(self, found_peer_callback=found_peer_callback, disapeared_peer_callback=disapeared_peer_callback) self.got_devices_callback = got_devices_callback self.info("MirabeauTubeConsumer created") self._coherence_tubes = {} def pre_accept_tube(self, tube): params = tube[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_TUBE, 'Parameters') initiator = params.get("initiator") can_accept = initiator and initiator in self.roster return can_accept def post_tube_accept(self, tube, tube_conn, initiator_handle): service = tube.props[CHANNEL_TYPE_DBUS_TUBE + ".ServiceName"] if service == BUS_NAME: tube.remote_object = dbus_service.DBusPontoon(None, tube_conn) elif service == DEVICE_IFACE: tube.remote_object = dbus_service.DBusDevice(None, tube_conn) elif service == SERVICE_IFACE: tube.remote_object = dbus_service.DBusService(None, None, tube_conn) else: self.info("tube %r is not coming from Coherence", service) return tube_conn if initiator_handle not in self._coherence_tubes: self._coherence_tubes[initiator_handle] = {} self._coherence_tubes[initiator_handle][service] = tube if len(self._coherence_tubes[initiator_handle]) == 3: self.announce(initiator_handle) def tube_closed(self, tube): self.disapeared_peer_callback(tube) super(MirabeauTubeConsumerMixin, self).tube_closed(tube) def announce(self, initiator_handle): service_name = BUS_NAME pontoon_tube = self._coherence_tubes[initiator_handle][service_name] def cb(participants, removed): if participants and initiator_handle in participants: initiator_bus_name = participants[initiator_handle] self.info("bus name %r for service %r", initiator_bus_name, service_name) if initiator_bus_name is not None: self.found_devices(initiator_handle, initiator_bus_name) for handle in removed: try: tube_channels = self._coherence_tubes[handle] except KeyError: self.debug("tube with handle %d not registered", handle) else: for service_iface_name, channel in tube_channels.iteritems(): channel[CHANNEL_INTERFACE].Close() del self._coherence_tubes[handle] pontoon_tube.remote_object.tube.watch_participants(cb) def found_devices(self, initiator_handle, initiator_bus_name): devices = [] tubes = self._coherence_tubes[initiator_handle] pontoon_tube = tubes[BUS_NAME].remote_object.tube device_tube = tubes[DEVICE_IFACE].remote_object.tube service_tube = tubes[SERVICE_IFACE].remote_object.tube self.info("using pontoon tube at %r", tubes[BUS_NAME].object_path) def got_devices(pontoon_devices): self.info("%r devices registered in remote pontoon", len(pontoon_devices)) for device_dict in pontoon_devices: device_path = device_dict["path"] self.info("getting object at %r from %r", device_path, initiator_bus_name) proxy = device_tube.get_object(initiator_bus_name, device_path) infos = proxy.get_info(dbus_interface=DEVICE_IFACE) service_proxies = [] for service_path in device_dict["services"]: service_proxy = service_tube.get_object(initiator_bus_name, service_path) service_proxies.append(service_proxy) proxy.services = service_proxies devices.append(proxy) self.got_devices_callback(devices) def got_error(exception): print ">>>", exception pontoon = pontoon_tube.get_object(initiator_bus_name, OBJECT_PATH) pontoon.get_devices_async(reply_handler=got_devices, error_handler=got_error) class MirabeauTubeConsumer(MirabeauTubeConsumerMixin, client.Client): logCategory = "mirabeau_tube_consumer" def __init__(self, manager, protocol, account, muc_id, found_peer_callback=None, disapeared_peer_callback=None, got_devices_callback=None): MirabeauTubeConsumerMixin.__init__(self, found_peer_callback=found_peer_callback, disapeared_peer_callback=disapeared_peer_callback, got_devices_callback=got_devices_callback) client.Client.__init__(self, manager, protocol, account, muc_id) def got_tube(self, tube): client.Client.got_tube(self, tube) self.accept_tube(tube) def tube_opened(self, tube): tube_conn = super(MirabeauTubePublisherConsumer, self).tube_opened(tube) self.post_tube_accept(tube, tube_conn) return tube_conn Coherence-0.6.6.2/coherence/extern/telepathy/mirabeau_tube_publisher.py0000644000175000017500000001374711317660736024414 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 Philippe Normand import gobject from dbus import PROPERTIES_IFACE from telepathy.interfaces import CHANNEL_INTERFACE, CONNECTION_INTERFACE_REQUESTS, \ CHANNEL_TYPE_DBUS_TUBE from telepathy.constants import CONNECTION_HANDLE_TYPE_ROOM, \ SOCKET_ACCESS_CONTROL_CREDENTIALS from telepathy.interfaces import CHANNEL_INTERFACE_TUBE, CHANNEL_TYPE_DBUS_TUBE, \ CHANNEL_INTERFACE from telepathy.constants import TUBE_CHANNEL_STATE_LOCAL_PENDING from coherence.extern.telepathy import client, tube, mirabeau_tube_consumer from coherence.dbus_constants import BUS_NAME, OBJECT_PATH, DEVICE_IFACE, SERVICE_IFACE from coherence import dbus_service from twisted.internet import task class MirabeauTubePublisherMixin(tube.TubePublisherMixin): def __init__(self, tubes_to_offer, application, allowed_devices): tube.TubePublisherMixin.__init__(self, tubes_to_offer) self.coherence = application self.allowed_devices = allowed_devices self.coherence_tube = None self.device_tube = None self.service_tube = None self.announce_done = False def _media_server_found(self, infos, udn): uuid = udn[5:] for device in self.coherence.dbus.devices.values(): if device.uuid == uuid: self._register_device(device) return def _register_device(self, device): if self.allowed_devices is not None and device.uuid not in self.allowed_devices: self.info("device not allowed: %r", device.uuid) return device.add_to_connection(self.device_tube, device.path()) self.info("adding device %s to connection: %s", device.get_markup_name(), self.device_tube) def iterate(): for service in device.services: if getattr(service,'NOT_FOR_THE_TUBES', False): continue yield service.add_to_connection(self.service_tube, service.path) dfr = task.coiterate(iterate()) return dfr def _media_server_removed(self, udn): for device in self.coherence.dbus.devices.values(): if udn == device.device.get_id(): if self.allowed_devices != None and device.uuid not in self.allowed_devices: # the device is not allowed, no reason to # disconnect from the tube to which it wasn't # connected in the first place anyway return device.remove_from_connection(self.device_tube, device.path()) self.info("remove_from_connection: %s" % device.get_friendly_name()) for service in device.services: if getattr(service,'NOT_FOR_THE_TUBES', False): continue service.remove_from_connection(self.service_tube, service.path) return def post_tube_offer(self, tube, tube_conn): service = tube.props[CHANNEL_TYPE_DBUS_TUBE + ".ServiceName"] if service == BUS_NAME: self.coherence.dbus.add_to_connection(tube_conn, OBJECT_PATH) self.coherence_tube = tube_conn elif service == DEVICE_IFACE: self.device_tube = tube_conn elif service == SERVICE_IFACE: self.service_tube = tube_conn if not self.announce_done and None not in (self.coherence_tube, self.device_tube, self.service_tube): self.announce_done = True def iterate(devices): for device in devices: yield self._register_device(device) def done(result): bus = self.coherence.dbus.bus bus.add_signal_receiver(self._media_server_found, "UPnP_ControlPoint_MediaServer_detected") bus.add_signal_receiver(self._media_server_removed, "UPnP_ControlPoint_MediaServer_removed") dfr = task.coiterate(iterate(self.coherence.dbus.devices.values())) dfr.addCallback(lambda gen: done) class MirabeauTubePublisherConsumer(MirabeauTubePublisherMixin, mirabeau_tube_consumer.MirabeauTubeConsumerMixin, client.Client): logCategory = "mirabeau_tube_publisher" def __init__(self, manager, protocol, account, muc_id, tubes_to_offer, application, allowed_devices, found_peer_callback=None, disapeared_peer_callback=None, got_devices_callback=None): MirabeauTubePublisherMixin.__init__(self, tubes_to_offer, application, allowed_devices) mirabeau_tube_consumer.MirabeauTubeConsumerMixin.__init__(self, found_peer_callback=found_peer_callback, disapeared_peer_callback=disapeared_peer_callback, got_devices_callback=got_devices_callback) client.Client.__init__(self, manager, protocol, account, muc_id) def got_tube(self, tube): client.Client.got_tube(self, tube) initiator_handle = tube.props[CHANNEL_INTERFACE + ".InitiatorHandle"] if initiator_handle == self.self_handle: self.finish_tube_offer(tube) else: self.accept_tube(tube) def tube_opened(self, tube): tube_conn = super(MirabeauTubePublisherConsumer, self).tube_opened(tube) initiator_handle = tube.props[CHANNEL_INTERFACE + ".InitiatorHandle"] if initiator_handle == self.self_handle: self.post_tube_offer(tube, tube_conn) else: self.post_tube_accept(tube, tube_conn, initiator_handle) return tube_conn Coherence-0.6.6.2/coherence/extern/telepathy/__init__.py0000644000175000017500000000000011317660736021244 0ustar devdevCoherence-0.6.6.2/coherence/extern/telepathy/connect.py0000644000175000017500000000230311317660736021146 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 Philippe Normand import telepathy from telepathy.interfaces import CONN_MGR_INTERFACE import dbus def to_dbus_account(account): for key, value in account.iteritems(): if value.lower() in ("false", "true"): value = bool(value) else: try: value = dbus.UInt32(int(value)) except: pass account[key] = value return account def tp_connect(manager, protocol, account, ready_handler=None): try: account = to_dbus_account(account) except: pass reg = telepathy.client.ManagerRegistry() reg.LoadManagers() mgr = reg.GetManager(manager) connection = mgr[CONN_MGR_INTERFACE].RequestConnection(protocol, account) conn_bus_name, conn_object_path = connection client_connection = telepathy.client.Connection(conn_bus_name, conn_object_path, ready_handler=ready_handler) return client_connection Coherence-0.6.6.2/coherence/extern/telepathy/stream.py0000644000175000017500000000317311317660736021016 0ustar devdevimport socket import gobject class TrivialStream: def __init__(self, socket_address=None): self.socket_address = socket_address def read_socket(self, s): try: data = s.recv(1024) if len(data) > 0: print "received:", data except socket.error, e: pass return True def write_socket(self, s, msg): print "send:", msg try: s = s.send(msg) except socket.error, e: pass return True class TrivialStreamServer(TrivialStream): def __init__(self): TrivialStream.__init__(self) self._socket = None def run(self): self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.setblocking(1) self._socket.settimeout(0.1) self._socket.bind(("127.0.0.1", 0)) self.socket_address = self._socket.getsockname() print "Trivial Server launched on socket", self.socket_address self._socket.listen(1) gobject.timeout_add(1000, self.accept_client, self._socket) def accept_client(self, s): try: s2, addr = s.accept() s2.setblocking(1) s2.setblocking(0.1) self.handle_client(s2) return True except socket.timeout: return True def handle_client(self, s): pass class TrivialStreamClient(TrivialStream): def connect(self): self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.connect(self.socket_address) print "Trivial client connected to", self.socket_address Coherence-0.6.6.2/coherence/extern/log/0000755000000000000000000000000011317673117016304 5ustar rootrootCoherence-0.6.6.2/coherence/extern/log/README0000644000175000017500000000236011317660746016611 0ustar devdevThis module does logging. Suggested use in your project (if you are using Subversion): - create myproject/extern directory - svn commit myproject/extern - svn propset svn:externals "log https://core.fluendo.com/svn/flumotion/trunk/flumotion/extern/log" myproject/extern - in your project: - call log.init('MY_PROJECT_DEBUG') (where MY_PROJECT_DEBUG is the name of the enviroment variable you want to have the debug output controlled by) - call log.setPackageScrubList('myproject') to have the filename logging be relative to your myproject package/module. - call log.setDebug(myDebugString) to change the logging levels. Alternatively, you can embed only the log.py file with proper svn:externals. If you are worried about stability, check out a specific revision by putting -r (revision number) in between log and https BUGS ---- The logging strings should be strings, not unicode. Unicode objects are encoded with UTF-8 before being logged. log.getExceptionMessage() is not always able to extract the stack properly; there doesn't seem to be a good way to do so. Instead, it will take the stack from the last raise context, so call this function before passing the exception down somewhere else where it could hit another raise first. Coherence-0.6.6.2/coherence/extern/log/test_log.py0000644000175000017500000002530111317660746020123 0ustar devdev# -*- Mode: Python; test-case-name: test_log -*- # vi:si:et:sw=4:sts=4:ts=4 # # Flumotion - a streaming media server # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). # All rights reserved. # This file may be distributed and/or modified under the terms of # the GNU General Public License version 2 as published by # the Free Software Foundation. # This file is distributed without any warranty; without even the implied # warranty of merchantability or fitness for a particular purpose. # See "LICENSE.GPL" in the source distribution for more information. # Licensees having purchased or holding a valid Flumotion Advanced # Streaming Server license may use this file in accordance with the # Flumotion Advanced Streaming Server Commercial License Agreement. # See "LICENSE.Flumotion" in the source distribution for more information. # Headers in this file shall remain intact. from twisted.trial import unittest import log __version__ = "$Rev: 8216 $" class LogTester(log.Loggable): logCategory = 'testlog' class LogFunctionTester(log.Loggable): def logFunction(self, format, *args): return (("override " + format), ) + args[1:] class TestLog(unittest.TestCase): def setUp(self): self.category = self.level = self.message = None self.tester = LogTester() # we want to remove the default handler so it doesn't show up stuff log.reset() # just test for parsing semi- or non-valid FLU_DEBUG variables def testSetDebug(self): log.setDebug(":5") log.setDebug("*") log.setDebug("5") def testGetLevelName(self): self.assertRaises(AssertionError, log.getLevelName, -1) self.assertRaises(AssertionError, log.getLevelName, 6) def testGetLevelInt(self): self.assertRaises(AssertionError, log.getLevelInt, "NOLEVEL") def testGetFormattedLevelName(self): self.assertRaises(AssertionError, log.getFormattedLevelName, -1) # FIXME: we're poking at internals here, but without calling this # no format levels are set at all. log._preformatLevels("ENVVAR") self.failUnless("LOG" in log.getFormattedLevelName(log.LOG)) def testGetFileLine(self): # test a function object (filename, line) = log.getFileLine(where=self.testGetFileLine) self.failUnless(filename.endswith('test_log.py')) self.assertEquals(line, 68) # test a lambda f = lambda x: x + 1 (filename, line) = log.getFileLine(where=f) self.failUnless(filename.endswith('test_log.py')) self.assertEquals(line, 75) # test an eval f = eval("lambda x: x + 1") (filename, line) = log.getFileLine(where=f) self.assertEquals(filename, '') self.assertEquals(line, 1) # test for adding a log handler def testEllipsize(self): self.assertEquals(log.ellipsize("*" * 1000), "'" + "*" * 59 + ' ... ' + "*" * 14 + "'") def handler(self, level, object, category, file, line, message): self.level = level self.object = object self.category = category self.file = file self.line = line self.message = message def testLimitInvisible(self): log.setDebug("testlog:3") log.addLimitedLogHandler(self.handler) # log 2 we shouldn't get self.tester.log("not visible") assert not self.category assert not self.level assert not self.message self.tester.debug("not visible") assert not self.category assert not self.level assert not self.message def testLimitedVisible(self): log.setDebug("testlog:3") log.addLimitedLogHandler(self.handler) # log 3 we should get self.tester.info("visible") assert self.category == 'testlog' assert self.level == log.INFO assert self.message == 'visible' self.tester.warning("also visible") assert self.category == 'testlog' assert self.level == log.WARN assert self.message == 'also visible' def testFormatStrings(self): log.setDebug("testlog:3") log.addLimitedLogHandler(self.handler) self.tester.info("%d %s", 42, 'the answer') assert self.category == 'testlog' assert self.level == log.INFO assert self.message == '42 the answer' def testLimitedError(self): log.setDebug("testlog:3") log.addLimitedLogHandler(self.handler) self.assertRaises(SystemExit, self.tester.error, "error") assert self.category == 'testlog' assert self.level == log.ERROR assert self.message == 'error' def testLogHandlerLimitedLevels(self): log.setDebug("testlog:3") log.addLimitedLogHandler(self.handler) # now try debug and log again too log.setDebug("testlog:5") self.tester.debug("debug") assert self.category == 'testlog' assert self.level == log.DEBUG assert self.message == 'debug' self.tester.log("log") assert self.category == 'testlog' assert self.level == log.LOG assert self.message == 'log' # test that we get all log messages def testLogHandler(self): log.setDebug("testlog:3") log.addLogHandler(self.handler) self.tester.log("visible") assert self.message == 'visible' self.tester.warning("also visible") assert self.message == 'also visible' def testAddLogHandlerRaises(self): self.assertRaises(TypeError, log.addLogHandler, 1) class TestOwnLogHandler(unittest.TestCase): def setUp(self): self.category = self.level = self.message = None self.tester = LogFunctionTester() log.reset() def handler(self, level, object, category, file, line, message): self.level = level self.object = object self.category = category self.file = file self.line = line self.message = message # test if our own log handler correctly mangles the message def testOwnLogHandlerLimited(self): log.setDebug("testlog:3") log.addLogHandler(self.handler) self.tester.log("visible") assert self.message == 'override visible' def testLogHandlerAssertion(self): self.assertRaises(TypeError, log.addLimitedLogHandler, None) class TestGetExceptionMessage(unittest.TestCase): def setUp(self): log.reset() def func3(self): self.func2() def func2(self): self.func1() def func1(self): raise TypeError("I am in func1") def testLevel2(self): try: self.func2() self.fail() except TypeError, e: self.verifyException(e) def testLevel3(self): try: self.func3() self.fail() except TypeError, e: self.verifyException(e) def verifyException(self, e): message = log.getExceptionMessage(e) self.failUnless("func1()" in message) self.failUnless("test_log.py" in message) self.failUnless("TypeError" in message) class TestLogSettings(unittest.TestCase): def testSet(self): old = log.getLogSettings() log.setDebug('*:5') self.assertNotEquals(old, log.getLogSettings()) log.setLogSettings(old) self.assertEquals(old, log.getLogSettings()) class TestWriteMark(unittest.TestCase): def handler(self, level, object, category, file, line, message): self.level = level self.object = object self.category = category self.file = file self.line = line self.message = message def testWriteMarkInDebug(self): loggable = log.Loggable() log.setDebug("4") log.addLogHandler(self.handler) marker = 'test' loggable.writeMarker(marker, log.DEBUG) self.assertEquals(self.message, marker) def testWriteMarkInWarn(self): loggable = log.Loggable() log.setDebug("2") log.addLogHandler(self.handler) marker = 'test' loggable.writeMarker(marker, log.WARN) self.assertEquals(self.message, marker) def testWriteMarkInInfo(self): loggable = log.Loggable() log.setDebug("3") log.addLogHandler(self.handler) marker = 'test' loggable.writeMarker(marker, log.INFO) self.assertEquals(self.message, marker) def testWriteMarkInLog(self): loggable = log.Loggable() log.setDebug("5") log.addLogHandler(self.handler) marker = 'test' loggable.writeMarker(marker, log.LOG) self.assertEquals(self.message, marker) def testWriteMarkInError(self): loggable = log.Loggable() log.setDebug("4") log.addLogHandler(self.handler) marker = 'test' self.assertRaises(SystemExit, loggable.writeMarker, marker, log.ERROR) self.assertEquals(self.message, marker) class TestLogNames(unittest.TestCase): def testGetLevelNames(self): self.assertEquals(['ERROR', 'WARN', 'INFO', 'DEBUG', 'LOG'], log.getLevelNames()) def testGetLevelCode(self): self.assertEquals(1, log.getLevelInt('ERROR')) self.assertEquals(2, log.getLevelInt('WARN')) self.assertEquals(3, log.getLevelInt('INFO')) self.assertEquals(4, log.getLevelInt('DEBUG')) self.assertEquals(5, log.getLevelInt('LOG')) def testGetLevelName(self): self.assertEquals('ERROR', log.getLevelName(1)) self.assertEquals('WARN', log.getLevelName(2)) self.assertEquals('INFO', log.getLevelName(3)) self.assertEquals('DEBUG', log.getLevelName(4)) self.assertEquals('LOG', log.getLevelName(5)) class TestLogUnicode(unittest.TestCase): def setUp(self): self.tester = LogTester() # add stderrHandler to fully test unicode handling log.addLogHandler(log.stderrHandler) def testUnicode(self): # Test with a unicode input self.tester.log(u'\xf3') def testUnicodeWithArgs(self): self.tester.log('abc: %s', u'\xf3') def testNonASCIIByteString(self): # Test with a non-ASCII bytestring self.tester.log('\xc3\xa4') def testNonASCIIByteStringWithArgs(self): self.tester.log('abc: %s', '\xc3\xa4') def testNonASCIIByteStringPlusUnicode(self): # This should fail since were trying to combine # a non-ascii string with a unicode string self.assertRaises(UnicodeDecodeError, self.tester.log, 'abc\xf3n%s:', u'a') def testASCIIFormatUnicodeArgs(self): self.tester.log('abc: %s', u'\xc3\xa4') if __name__ == '__main__': unittest.main() Coherence-0.6.6.2/coherence/extern/log/ChangeLog0000644000175000017500000000673611317660746017516 0ustar devdev2009-06-23 Thomas Vander Stichele * log.py: Fix various pychecker warnings. Rewrite writeMarker in less lines, although it's still ugly to special-case for errorObject, and possibly that should move to doLog instead. 2008-05-06 Thomas Vander Stichele * log.py: Provide exception message when a handler fails. 2007-11-07 Johan Dahlin * log.py: use range() for an enum 2007-11-07 Johan Dahlin * log.py (getLoggingSettings, setLoggingSettings): Add two new functions for setting and restoring the current log settings. 2007-10-31 Andy Wingo Patch by: Philippe Normand * log.py (reopenOutputFiles, outputToFiles): Allow redirection of e.g. stderr but not stdout. Fixes #748. 2007-07-24 Andy Wingo Patch by: Philippe Normand * log.py: Optionally support colorizing the log level. Fixes #642. Patch modified slightly. * termcolor.py: New file, imported only if we are enabling color debug output. 2007-06-25 Andy Wingo * log.py (safeprintf): New procedure. See #613. (stderrHandler, TwistedLogObserver.emit): Use safeprintf. 2007-06-11 Andy Wingo * log.py (outputToFiles): Store the previous SIGHUP handler, if any, and call it after rotating logs. 2007-05-09 Andy Wingo Patch by: Philippe Normand * log.py (init): Adapt to addLimitedLogHandler. (addLogHandler): Only add generic log handlers. (addLimitedLogHandler): New function, splits out management of limited log handlers. (removeLogHandler, removeLimitedLogHandler): New functions. * test_log.py (TestLog.testLimitInvisible) (TestLog.testLimitedVisible, TestLog.testFormatStrings) (TestLog.testLimitedError, TestLog.testLogHandlerLimitedLevels): Limited log handlers are now managed via addLimitedLogHandler. (TestLog.testLogHandler) (TestOwnLogHandler.testOwnLogHandlerLimited) (TestOwnLogHandler.testLogHandlerAssertion): No need for limited=False. 2007-04-30 Andy Wingo * log.py (TwistedLogObserver.emit): Write to stderr, like the stderrhandler, both for sane interleaving with normal logs and so that we don't cause 100% CPU usage if the disk is full. See #613. 2007-04-14 Thomas Vander Stichele * log.py: * test_log.py (TestLog.testSetDebug, TestLog.testLimitInvisible, TestLog.testLimitedVisible, TestLog.testFormatStrings, TestLog.testLimitedError, TestLog.testLogHandlerLimitedLevels, TestLog.testLogHandler, TestOwnLogHandler.testOwnLogHandlerLimited): Move over test and adapt. 2007-04-14 Thomas Vander Stichele * log.py (stderrHandler, init, setDebug, setPackageScrubList, reset, addLogHandler, error, log, getExceptionMessage, reopenOutputFiles, outputToFiles, sighup, Loggable, Loggable.warningFailure, Loggable.logObjectName, _getTheTwistedLogObserver, getFailureMessage, warningFailure, logTwisted, TwistedLogObserver, TwistedLogObserver.__init__, TwistedLogObserver.emit, TwistedLogObserver.ignoreErrors, TwistedLogObserver.clearIgnores): Reorder log file a little. 2007-04-14 Thomas Vander Stichele * README: added. 2007-04-14 Thomas Vander Stichele * log.py: copied and adapted from flumotion.common.log so outside projects can use it. Coherence-0.6.6.2/coherence/extern/log/log.py0000644000175000017500000006722011317660746017072 0ustar devdev# -*- Mode: Python; test-case-name: test_log -*- # vi:si:et:sw=4:sts=4:ts=4 # This file is released under the standard PSF license. """ Logging module. Five levels of log information are defined. These are, in order of decreasing verbosity: log, debug, info, warning, error. This module provides a Loggable class for objects, as well as various convenience methods for logging in general, and for logging with Twisted and failures in particular. Maintainer: U{Thomas Vander Stichele } """ import errno import sys import os import fnmatch import time import types import traceback # environment variables controlling levels for each category _DEBUG = "*:1" # name of the environment variable controlling our logging _ENV_VAR_NAME = None # package names we should scrub filenames for _PACKAGE_SCRUB_LIST = [] # dynamic dictionary of categories already seen and their level _categories = {} # log handlers registered _log_handlers = [] _log_handlers_limited = [] _initialized = False _stdout = None _stderr = None _old_hup_handler = None # public log levels (ERROR, WARN, INFO, DEBUG, LOG) = range(1, 6) COLORS = {ERROR: 'RED', WARN: 'YELLOW', INFO: 'GREEN', DEBUG: 'BLUE', LOG: 'CYAN'} _FORMATTED_LEVELS = [] _LEVEL_NAMES = ['ERROR', 'WARN', 'INFO', 'DEBUG', 'LOG'] def getLevelName(level): """ Return the name of a log level. @param level: The level we want to know the name @type level: int @return: The name of the level @rtype: str """ assert isinstance(level, int) and level > 0 and level < 6, \ TypeError("Bad debug level") return getLevelNames()[level - 1] def getLevelNames(): """ Return a list with the level names @return: A list with the level names @rtype: list of str """ return _LEVEL_NAMES def getLevelInt(levelName): """ Return the integer value of the levelName. @param levelName: The string value of the level name @type levelName: str @return: The value of the level name we are interested in. @rtype: int """ assert isinstance(levelName, str) and levelName in getLevelNames(), \ "Bad debug level name" return getLevelNames().index(levelName)+1 def getFormattedLevelName(level): assert isinstance(level, int) and level > 0 and level < 6, \ TypeError("Bad debug level") return _FORMATTED_LEVELS[level - 1] def registerCategory(category): """ Register a given category in the debug system. A level will be assigned to it based on previous calls to setDebug. """ # parse what level it is set to based on _DEBUG # example: *:2,admin:4 global _DEBUG global _levels global _categories level = 0 chunks = _DEBUG.split(',') for chunk in chunks: if not chunk: continue if ':' in chunk: spec, value = chunk.split(':') else: spec = '*' value = chunk # our glob is unix filename style globbing, so cheat with fnmatch # fnmatch.fnmatch didn't work for this, so don't use it if category in fnmatch.filter((category, ), spec): # we have a match, so set level based on string or int if not value: continue try: level = int(value) except ValueError: # e.g. *; we default to most level = 5 # store it _categories[category] = level def getCategoryLevel(category): """ @param category: string Get the debug level at which this category is being logged, adding it if it wasn't registered yet. """ global _categories if not category in _categories: registerCategory(category) return _categories[category] def setLogSettings(state): """Update the current log settings. This can restore an old saved log settings object returned by getLogSettings @param state: the settings to set """ global _DEBUG global _log_handlers global _log_handlers_limited (_DEBUG, categories, _log_handlers, _log_handlers_limited) = state for category in categories: registerCategory(category) def getLogSettings(): """Fetches the current log settings. The returned object can be sent to setLogSettings to restore the returned settings @returns: the current settings """ return (_DEBUG, _categories, _log_handlers, _log_handlers_limited) def _canShortcutLogging(category, level): if _log_handlers: # we have some loggers operating without filters, have to do # everything return False else: return level > getCategoryLevel(category) def scrubFilename(filename): ''' Scrub the filename to a relative path for all packages in our scrub list. ''' global _PACKAGE_SCRUB_LIST for package in _PACKAGE_SCRUB_LIST: i = filename.rfind(package) if i > -1: return filename[i:] return filename def getFileLine(where=-1): """ Return the filename and line number for the given location. If where is a negative integer, look for the code entry in the current stack that is the given number of frames above this module. If where is a function, look for the code entry of the function. @param where: how many frames to go back up, or function @type where: int (negative) or function @return: tuple of (file, line) @rtype: tuple of (str, int) """ co = None lineno = None if isinstance(where, types.FunctionType): co = where.func_code lineno = co.co_firstlineno elif isinstance(where, types.MethodType): co = where.im_func.func_code lineno = co.co_firstlineno else: stackFrame = sys._getframe() while stackFrame: co = stackFrame.f_code if not co.co_filename.endswith('log.py'): # wind up the stack according to frame while where < -1: stackFrame = stackFrame.f_back where += 1 co = stackFrame.f_code lineno = stackFrame.f_lineno break stackFrame = stackFrame.f_back if not co: return "", 0 return scrubFilename(co.co_filename), lineno def ellipsize(o): """ Ellipsize the representation of the given object. """ r = repr(o) if len(r) < 800: return r r = r[:60] + ' ... ' + r[-15:] return r def getFormatArgs(startFormat, startArgs, endFormat, endArgs, args, kwargs): """ Helper function to create a format and args to use for logging. This avoids needlessly interpolating variables. """ debugArgs = startArgs[:] for a in args: debugArgs.append(ellipsize(a)) for items in kwargs.items(): debugArgs.extend(items) debugArgs.extend(endArgs) debugFormat = startFormat \ + ', '.join(('%s', ) * len(args)) \ + (kwargs and ', ' or '') \ + ', '.join(('%s=%r', ) * len(kwargs)) \ + endFormat return debugFormat, debugArgs def doLog(level, object, category, format, args, where=-1, filePath=None, line=None): """ @param where: what to log file and line number for; -1 for one frame above log.py; -2 and down for higher up; a function for a (future) code object @type where: int or callable @param filePath: file to show the message as coming from, if caller knows best @type filePath: str @param line: line to show the message as coming from, if caller knows best @type line: int @return: dict of calculated variables, if they needed calculating. currently contains file and line; this prevents us from doing this work in the caller when it isn't needed because of the debug level """ ret = {} if args: message = format % args else: message = format # first all the unlimited ones if _log_handlers: if filePath is None and line is None: (filePath, line) = getFileLine(where=where) ret['filePath'] = filePath ret['line'] = line for handler in _log_handlers: try: handler(level, object, category, file, line, message) except TypeError, e: raise SystemError("handler %r raised a TypeError: %s" % ( handler, getExceptionMessage(e))) if level > getCategoryLevel(category): return ret if _log_handlers_limited: if filePath is None and line is None: (filePath, line) = getFileLine(where=where) ret['filePath'] = filePath ret['line'] = line for handler in _log_handlers_limited: # set this a second time, just in case there weren't unlimited # loggers there before try: handler(level, object, category, filePath, line, message) except TypeError: raise SystemError("handler %r raised a TypeError" % handler) return ret def errorObject(object, cat, format, *args): """ Log a fatal error message in the given category. This will also raise a L{SystemExit}. """ doLog(ERROR, object, cat, format, args) # we do the import here because having it globally causes weird import # errors if our gstreactor also imports .log, which brings in errors # and pb stuff if args: raise SystemExit(format % args) else: raise SystemExit(format) def warningObject(object, cat, format, *args): """ Log a warning message in the given category. This is used for non-fatal problems. """ doLog(WARN, object, cat, format, args) def infoObject(object, cat, format, *args): """ Log an informational message in the given category. """ doLog(INFO, object, cat, format, args) def debugObject(object, cat, format, *args): """ Log a debug message in the given category. """ doLog(DEBUG, object, cat, format, args) def logObject(object, cat, format, *args): """ Log a log message. Used for debugging recurring events. """ doLog(LOG, object, cat, format, args) def safeprintf(file, format, *args): """Write to a file object, ignoring errors. """ try: if args: file.write(format % args) else: file.write(format) except IOError, e: if e.errno == errno.EPIPE: # if our output is closed, exit; e.g. when logging over an # ssh connection and the ssh connection is closed os._exit(os.EX_OSERR) # otherwise ignore it, there's nothing you can do def stderrHandler(level, object, category, file, line, message): """ A log handler that writes to stderr. @type level: string @type object: string (or None) @type category: string @type message: string """ o = "" if object: o = '"' + object + '"' where = "(%s:%d)" % (file, line) # level pid object cat time # 5 + 1 + 7 + 1 + 32 + 1 + 17 + 1 + 15 == 80 safeprintf(sys.stderr, '%s [%5d] %-32s %-17s %-15s ', getFormattedLevelName(level), os.getpid(), o, category, time.strftime("%b %d %H:%M:%S")) try: safeprintf(sys.stderr, '%-4s %s %s\n', "", message, where) except UnicodeEncodeError: # this can happen if message is a unicode object, convert it back into # a string using the UTF-8 encoding message = message.encode('UTF-8') safeprintf(sys.stderr, '%-4s %s %s\n', "", message, where) sys.stderr.flush() def _preformatLevels(noColorEnvVarName): format = '%-5s' try: import termcolor except ImportError: # we don't need to catch this if termcolor is in same package as # log.py termcolor = None if (noColorEnvVarName is not None and termcolor is not None and (noColorEnvVarName not in os.environ or not os.environ[noColorEnvVarName])): t = termcolor.TerminalController() formatter = lambda level: ''.join((t.BOLD, getattr(t, COLORS[level]), format % (_LEVEL_NAMES[level-1], ), t.NORMAL)) else: formatter = lambda level: format % (_LEVEL_NAMES[level-1], ) for level in ERROR, WARN, INFO, DEBUG, LOG: _FORMATTED_LEVELS.append(formatter(level)) ### "public" useful API # setup functions def init(envVarName, enableColorOutput=False): """ Initialize the logging system and parse the environment variable of the given name. Needs to be called before starting the actual application. """ global _initialized if _initialized: return global _ENV_VAR_NAME _ENV_VAR_NAME = envVarName if enableColorOutput: _preformatLevels(envVarName + "_NO_COLOR") else: _preformatLevels(None) if envVarName in os.environ: # install a log handler that uses the value of the environment var setDebug(os.environ[envVarName]) addLimitedLogHandler(stderrHandler) _initialized = True def setDebug(string): """Set the DEBUG string. This controls the log output.""" global _DEBUG global _ENV_VAR_NAME global _categories _DEBUG = string debug('log', "%s set to %s" % (_ENV_VAR_NAME, _DEBUG)) # reparse all already registered category levels for category in _categories: registerCategory(category) def getDebug(): """ Returns the currently active DEBUG string. @rtype: str """ global _DEBUG return _DEBUG def setPackageScrubList(*packages): """ Set the package names to scrub from filenames. Filenames from these paths in log messages will be scrubbed to their relative file path instead of the full absolute path. @type packages: list of str """ global _PACKAGE_SCRUB_LIST _PACKAGE_SCRUB_LIST = packages def reset(): """ Resets the logging system, removing all log handlers. """ global _log_handlers, _log_handlers_limited, _initialized _log_handlers = [] _log_handlers_limited = [] _initialized = False def addLogHandler(func): """ Add a custom log handler. @param func: a function object with prototype (level, object, category, message) where level is either ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are strings or None. Use getLevelName(level) to get a printable name for the log level. @type func: a callable function @raises TypeError: if func is not a callable """ if not callable(func): raise TypeError("func must be callable") if func not in _log_handlers: _log_handlers.append(func) def addLimitedLogHandler(func): """ Add a custom log handler. @param func: a function object with prototype (level, object, category, message) where level is either ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are strings or None. Use getLevelName(level) to get a printable name for the log level. @type func: a callable function @raises TypeError: TypeError if func is not a callable """ if not callable(func): raise TypeError("func must be callable") if func not in _log_handlers_limited: _log_handlers_limited.append(func) def removeLogHandler(func): """ Remove a registered log handler. @param func: a function object with prototype (level, object, category, message) where level is either ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are strings or None. Use getLevelName(level) to get a printable name for the log level. @type func: a callable function @raises ValueError: if func is not registered """ _log_handlers.remove(func) def removeLimitedLogHandler(func): """ Remove a registered limited log handler. @param func: a function object with prototype (level, object, category, message) where level is either ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are strings or None. Use getLevelName(level) to get a printable name for the log level. @type func: a callable function @raises ValueError: if func is not registered """ _log_handlers_limited.remove(func) # public log functions def error(cat, format, *args): errorObject(None, cat, format, *args) def warning(cat, format, *args): warningObject(None, cat, format, *args) def info(cat, format, *args): infoObject(None, cat, format, *args) def debug(cat, format, *args): debugObject(None, cat, format, *args) def log(cat, format, *args): logObject(None, cat, format, *args) # public utility functions def getExceptionMessage(exception, frame=-1, filename=None): """ Return a short message based on an exception, useful for debugging. Tries to find where the exception was triggered. """ stack = traceback.extract_tb(sys.exc_info()[2]) if filename: stack = [f for f in stack if f[0].find(filename) > -1] #import code; code.interact(local=locals()) (filename, line, func, text) = stack[frame] filename = scrubFilename(filename) exc = exception.__class__.__name__ msg = "" # a shortcut to extract a useful message out of most exceptions # for now if str(exception): msg = ": %s" % str(exception) return "exception %(exc)s at %(filename)s:%(line)s: %(func)s()%(msg)s" \ % locals() def reopenOutputFiles(): """ Reopens the stdout and stderr output files, as set by L{outputToFiles}. """ if not _stdout and not _stderr: debug('log', 'told to reopen log files, but log files not set') return def reopen(name, fileno, *args): oldmask = os.umask(0026) try: f = open(name, 'a+', *args) finally: os.umask(oldmask) os.dup2(f.fileno(), fileno) if _stdout: reopen(_stdout, sys.stdout.fileno()) if _stderr: reopen(_stderr, sys.stderr.fileno(), 0) debug('log', 'opened log %r', _stderr) def outputToFiles(stdout=None, stderr=None): """ Redirect stdout and stderr to named files. Records the file names so that a future call to reopenOutputFiles() can open the same files. Installs a SIGHUP handler that will reopen the output files. Note that stderr is opened unbuffered, so if it shares a file with stdout then interleaved output may not appear in the order that you expect. """ global _stdout, _stderr, _old_hup_handler _stdout, _stderr = stdout, stderr reopenOutputFiles() def sighup(signum, frame): info('log', "Received SIGHUP, reopening logs") reopenOutputFiles() if _old_hup_handler: info('log', "Calling old SIGHUP hander") _old_hup_handler(signum, frame) try: import signal except ImportError: debug('log', 'POSIX signals not supported, unable to install' ' SIGHUP handler') else: debug('log', 'installing SIGHUP handler') handler = signal.signal(signal.SIGHUP, sighup) if handler == signal.SIG_DFL or handler == signal.SIG_IGN: _old_hup_handler = None else: _old_hup_handler = handler # base class for loggable objects class Loggable: """ Base class for objects that want to be able to log messages with different level of severity. The levels are, in order from least to most: log, debug, info, warning, error. @cvar logCategory: Implementors can provide a category to log their messages under. """ logCategory = 'default' def writeMarker(self, marker, level): """ Sets a marker that written to the logs. Setting this marker to multiple elements at a time helps debugging. @param marker: A string write to the log. @type marker: str @param level: The log level. It can be log.WARN, log.INFO, log.DEBUG, log.ERROR or log.LOG. @type level: int """ # errorObject specifically raises, so treat it specially if level == ERROR: self.error('%s', marker) doLog(level, self.logObjectName(), self.logCategory, '%s', marker) def error(self, *args): """Log an error. By default this will also raise an exception.""" if _canShortcutLogging(self.logCategory, ERROR): return errorObject(self.logObjectName(), self.logCategory, *self.logFunction(*args)) def warning(self, *args): """Log a warning. Used for non-fatal problems.""" if _canShortcutLogging(self.logCategory, WARN): return warningObject(self.logObjectName(), self.logCategory, *self.logFunction(*args)) def info(self, *args): """Log an informational message. Used for normal operation.""" if _canShortcutLogging(self.logCategory, INFO): return infoObject(self.logObjectName(), self.logCategory, *self.logFunction(*args)) def debug(self, *args): """Log a debug message. Used for debugging.""" if _canShortcutLogging(self.logCategory, DEBUG): return debugObject(self.logObjectName(), self.logCategory, *self.logFunction(*args)) def log(self, *args): """Log a log message. Used for debugging recurring events.""" if _canShortcutLogging(self.logCategory, LOG): return logObject(self.logObjectName(), self.logCategory, *self.logFunction(*args)) def doLog(self, level, where, format, *args, **kwargs): """ Log a message at the given level, with the possibility of going higher up in the stack. @param level: log level @type level: int @param where: how many frames to go back from the last log frame; or a function (to log for a future call) @type where: int (negative), or function @param kwargs: a dict of pre-calculated values from a previous doLog call @return: a dict of calculated variables, to be reused in a call to doLog that should show the same location @rtype: dict """ if _canShortcutLogging(self.logCategory, level): return {} args = self.logFunction(*args) return doLog(level, self.logObjectName(), self.logCategory, format, args, where=where, **kwargs) def warningFailure(self, failure, swallow=True): """ Log a warning about a Twisted Failure. Useful as an errback handler: d.addErrback(self.warningFailure) @param swallow: whether to swallow the failure or not @type swallow: bool """ if _canShortcutLogging(self.logCategory, WARN): if swallow: return return failure warningObject(self.logObjectName(), self.logCategory, *self.logFunction(getFailureMessage(failure))) if not swallow: return failure def logFunction(self, *args): """Overridable log function. Default just returns passed message.""" return args def logObjectName(self): """Overridable object name function.""" # cheat pychecker for name in ['logName', 'name']: if hasattr(self, name): return getattr(self, name) return None # Twisted helper stuff # private stuff _initializedTwisted = False # make a singleton __theTwistedLogObserver = None def _getTheTwistedLogObserver(): # used internally and in test global __theTwistedLogObserver if not __theTwistedLogObserver: __theTwistedLogObserver = TwistedLogObserver() return __theTwistedLogObserver # public helper methods def getFailureMessage(failure): """ Return a short message based on L{twisted.python.failure.Failure}. Tries to find where the exception was triggered. """ exc = str(failure.type) msg = failure.getErrorMessage() if len(failure.frames) == 0: return "failure %(exc)s: %(msg)s" % locals() (func, filename, line, some, other) = failure.frames[-1] filename = scrubFilename(filename) return "failure %(exc)s at %(filename)s:%(line)s: %(func)s(): %(msg)s" \ % locals() def warningFailure(failure, swallow=True): """ Log a warning about a Failure. Useful as an errback handler: d.addErrback(warningFailure) @param swallow: whether to swallow the failure or not @type swallow: bool """ warning('', getFailureMessage(failure)) if not swallow: return failure def logTwisted(): """ Integrate twisted's logger with our logger. This is done in a separate method because calling this imports and sets up a reactor. Since we want basic logging working before choosing a reactor, we need to separate these. """ global _initializedTwisted if _initializedTwisted: return debug('log', 'Integrating twisted logger') # integrate twisted's logging with us from twisted.python import log as tlog # this call imports the reactor # that is why we do this in a separate method from twisted.spread import pb # we don't want logs for pb.Error types since they # are specifically raised to be handled on the other side observer = _getTheTwistedLogObserver() observer.ignoreErrors([pb.Error, ]) tlog.startLoggingWithObserver(observer.emit, False) _initializedTwisted = True # we need an object as the observer because startLoggingWithObserver # expects a bound method class TwistedLogObserver(Loggable): """ Twisted log observer that integrates with our logging. """ logCategory = "logobserver" def __init__(self): self._ignoreErrors = [] # Failure types def emit(self, eventDict): method = log # by default, lowest level edm = eventDict['message'] if not edm: if eventDict['isError'] and 'failure' in eventDict: f = eventDict['failure'] for failureType in self._ignoreErrors: r = f.check(failureType) if r: self.debug("Failure of type %r, ignoring" % failureType) return self.log("Failure %r" % f) method = debug # tracebacks from errors at debug level msg = "A twisted traceback occurred." if getCategoryLevel("twisted") < WARN: msg += " Run with debug level >= 2 to see the traceback." # and an additional warning warning('twisted', msg) text = f.getTraceback() safeprintf(sys.stderr, "\nTwisted traceback:\n") safeprintf(sys.stderr, text + '\n') elif 'format' in eventDict: text = eventDict['format'] % eventDict else: # we don't know how to log this return else: text = ' '.join(map(str, edm)) msgStr = " [%(system)s] %(text)s\n" % { 'system': eventDict['system'], 'text': text.replace("\n", "\n\t")} # because msgstr can contain %, as in a backtrace, make sure we # don't try to splice it method('twisted', msgStr) def ignoreErrors(self, *types): for failureType in types: self._ignoreErrors.append(failureType) def clearIgnores(self): self._ignoreErrors = [] Coherence-0.6.6.2/coherence/extern/log/__init__.py0000644000175000017500000000000011317660746020027 0ustar devdevCoherence-0.6.6.2/coherence/extern/log/termcolor.py0000644000175000017500000002014311317660746020310 0ustar devdev# Copyright 2006 Edward Loper. May be distributed under the same terms # as Python itself. # # From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116 import re import sys class TerminalController: """ A class that can be used to portably generate formatted output to a terminal. `TerminalController` defines a set of instance variables whose values are initialized to the control sequence necessary to perform a given action. These can be simply included in normal output to the terminal: >>> term = TerminalController() >>> print 'This is '+term.GREEN+'green'+term.NORMAL Alternatively, the `render()` method can used, which replaces '${action}' with the string required to perform 'action': >>> term = TerminalController() >>> print term.render('This is ${GREEN}green${NORMAL}') If the terminal doesn't support a given action, then the value of the corresponding instance variable will be set to ''. As a result, the above code will still work on terminals that do not support color, except that their output will not be colored. Also, this means that you can test whether the terminal supports a given action by simply testing the truth value of the corresponding instance variable: >>> term = TerminalController() >>> if term.CLEAR_SCREEN: ... print 'This terminal supports clearning the screen.' Finally, if the width and height of the terminal are known, then they will be stored in the `COLS` and `LINES` attributes. """ # Cursor movement: BOL = '' #: Move the cursor to the beginning of the line UP = '' #: Move the cursor up one line DOWN = '' #: Move the cursor down one line LEFT = '' #: Move the cursor left one char RIGHT = '' #: Move the cursor right one char # Deletion: CLEAR_SCREEN = '' #: Clear the screen and move to home position CLEAR_EOL = '' #: Clear to the end of the line. CLEAR_BOL = '' #: Clear to the beginning of the line. CLEAR_EOS = '' #: Clear to the end of the screen # Output modes: BOLD = '' #: Turn on bold mode BLINK = '' #: Turn on blink mode DIM = '' #: Turn on half-bright mode REVERSE = '' #: Turn on reverse-video mode NORMAL = '' #: Turn off all modes # Cursor display: HIDE_CURSOR = '' #: Make the cursor invisible SHOW_CURSOR = '' #: Make the cursor visible # Terminal size: COLS = None #: Width of the terminal (None for unknown) LINES = None #: Height of the terminal (None for unknown) # Foreground colors: BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' # Background colors: BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' _STRING_CAPABILITIES = """ BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() def __init__(self, term_stream=sys.stdout): """ Create a `TerminalController` and initialize its attributes with appropriate values for the current terminal. `term_stream` is the stream that will be used for terminal output; if this stream is not a tty, then the terminal is assumed to be a dumb terminal (i.e., have no capabilities). """ # Curses isn't available on all platforms try: import curses except ImportError: return # If the stream isn't a tty, then assume it has no capabilities. if not term_stream.isatty(): return # Check the terminal type. If we fail, then assume that the # terminal has no capabilities. try: curses.setupterm() except: return # Look up numeric capabilities. self.COLS = curses.tigetnum('cols') self.LINES = curses.tigetnum('lines') # Look up string capabilities. for capability in self._STRING_CAPABILITIES: (attrib, cap_name) = capability.split('=') setattr(self, attrib, self._tigetstr(cap_name) or '') # Colors set_fg = self._tigetstr('setf') if set_fg: for i, color in zip(range(len(self._COLORS)), self._COLORS): setattr(self, color, curses.tparm(set_fg, i) or '') set_fg_ansi = self._tigetstr('setaf') if set_fg_ansi: for i, color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): setattr(self, color, curses.tparm(set_fg_ansi, i) or '') set_bg = self._tigetstr('setb') if set_bg: for i, color in zip(range(len(self._COLORS)), self._COLORS): setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '') set_bg_ansi = self._tigetstr('setab') if set_bg_ansi: for i, color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') def _tigetstr(self, cap_name): # String capabilities can include "delays" of the form "$<2>". # For any modern terminal, we should be able to just ignore # these, so strip them out. import curses cap = curses.tigetstr(cap_name) or '' return re.sub(r'\$<\d+>[/*]?', '', cap) def render(self, template): """ Replace each $-substitutions in the given template string with the corresponding terminal control string (if it's defined) or '' (if it's not). """ return re.sub(r'\$\$|\${\w+}', self._render_sub, template) def _render_sub(self, match): s = match.group() if s == '$$': return s else: return getattr(self, s[2:-1]) ####################################################################### # Example use case: progress bar ####################################################################### class ProgressBar: """ A 3-line progress bar, which looks like:: Header 20% [===========----------------------------------] progress message The progress bar is colored, if the terminal supports color output; and adjusts to the width of the terminal. """ BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n' HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' def __init__(self, term, header): self.term = term if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): raise ValueError("Terminal isn't capable enough -- you " "should use a simpler progress dispaly.") self.width = self.term.COLS or 75 self.bar = term.render(self.BAR) self.header = self.term.render(self.HEADER % header.center(self.width)) self.cleared = 1 #: true if we haven't drawn the bar yet. self.update(0, '') def update(self, percent, message): if self.cleared: sys.stdout.write(self.header) self.cleared = 0 n = int((self.width-10)*percent) sys.stdout.write( self.term.BOL + self.term.UP + self.term.CLEAR_EOL + (self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) + self.term.CLEAR_EOL + message.center(self.width)) def clear(self): if not self.cleared: sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + self.term.UP + self.term.CLEAR_EOL + self.term.UP + self.term.CLEAR_EOL) self.cleared = 1 if __name__ == '__main__': term = TerminalController() print term.render('${BOLD}${RED}Error:${NORMAL}'), 'paper is ripped' Coherence-0.6.6.2/coherence/extern/test_inotify.py0000644000175000017500000002203511317660736020242 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Adroll.com and Valentino Volonghi # 20090113 Frank Scholz # renamed watch() kwarg autoAdd back to auto_add, to not break # existing applications from twisted.internet import defer, reactor from twisted.python import filepath from twisted.trial import unittest import inotify class TestINotify(unittest.TestCase): def setUp(self): self.dirname = filepath.FilePath(self.mktemp()) self.dirname.createDirectory() self.inotify = inotify.INotify() def tearDown(self): self.inotify.release() self.inotify = None self.dirname.remove() def test_notifications(self): """ Test that a notification is actually delivered on a file creation. """ NEW_FILENAME = "new_file.file" EXTRA_ARG = "HELLO" checkMask = inotify.IN_CREATE | inotify.IN_CLOSE_WRITE calls = [] # We actually expect 2 calls here, one when we create # and one when we close the file after writing it. def _callback(wp, filename, mask, data): try: self.assertEquals(filename, NEW_FILENAME) self.assertEquals(data, EXTRA_ARG) calls.append(filename) if len(calls) == 2: self.assert_(mask & inotify.IN_CLOSE_WRITE) d.callback(None) elif len(calls) == 1: self.assert_(mask & inotify.IN_CREATE) except Exception, e: d.errback(e) self.inotify.watch( self.dirname, mask=checkMask, callbacks=(_callback, EXTRA_ARG) ) d = defer.Deferred() f = self.dirname.child(NEW_FILENAME).open('wb') f.write("hello darling") f.close() return d def test_simpleSubdirectoryAutoAdd(self): """ Test that when a subdirectory is added to a watched directory it is also added to the watched list. """ def _callback(wp, filename, mask, data): # We are notified before we actually process new # directories, so we need to defer this check. def _(): try: self.assert_(self.inotify.isWatched(SUBDIR.path)) d.callback(None) except Exception, e: d.errback(e) reactor.callLater(0, _) checkMask = inotify.IN_ISDIR | inotify.IN_CREATE self.inotify.watch( self.dirname, mask=checkMask, auto_add=True, callbacks=(_callback, None) ) SUBDIR = self.dirname.child('test') d = defer.Deferred() SUBDIR.createDirectory() return d def test_simpleDeleteDirectory(self): """ Test that when a subdirectory is added and then removed it is also removed from the watchlist """ calls = [] def _callback(wp, filename, mask, data): # We are notified before we actually process new # directories, so we need to defer this check. def _(): try: self.assert_(self.inotify.isWatched(SUBDIR.path)) SUBDIR.remove() except Exception, e: print e d.errback(e) def _eb(): # second call, we have just removed the subdir try: self.assert_(not self.inotify.isWatched(SUBDIR.path)) d.callback(None) except Exception, e: print e d.errback(e) if not calls: # first call, it's the create subdir calls.append(filename) reactor.callLater(0.1, _) else: reactor.callLater(0.1, _eb) checkMask = inotify.IN_ISDIR | inotify.IN_CREATE self.inotify.watch( self.dirname, mask=checkMask, auto_add=True, callbacks=(_callback, None) ) SUBDIR = self.dirname.child('test') d = defer.Deferred() SUBDIR.createDirectory() return d def test_ignoreDirectory(self): """ Test that ignoring a directory correctly removes it from the watchlist without removing it from the filesystem. """ self.inotify.watch( self.dirname, auto_add=True ) self.assert_(self.inotify.isWatched(self.dirname)) self.inotify.ignore(self.dirname) self.assert_(not self.inotify.isWatched(self.dirname)) def test_watchPoint(self): """ Test that Watch methods work as advertised """ w = inotify.Watch('/tmp/foobar') f = lambda : 5 w.addCallback(f) self.assert_(w.callbacks, [(f, None)]) def test_flagToHuman(self): """ Test the helper function """ for mask, value in inotify._FLAG_TO_HUMAN.iteritems(): self.assert_(inotify.flag_to_human(mask)[0], value) checkMask = inotify.IN_CLOSE_WRITE|inotify.IN_ACCESS|inotify.IN_OPEN self.assert_( len(inotify.flag_to_human(checkMask)), 3 ) def test_recursiveWatch(self): """ Test that a recursive watch correctly adds all the paths in the watched directory. """ SUBDIR = self.dirname.child('test') SUBDIR2 = SUBDIR.child('test2') SUBDIR3 = SUBDIR2.child('test3') SUBDIR3.makedirs() DIRS = [SUBDIR, SUBDIR2, SUBDIR3] self.inotify.watch(self.dirname, recursive=True) # let's even call this twice so that we test that nothing breaks self.inotify.watch(self.dirname, recursive=True) for d in DIRS: self.assert_(self.inotify.isWatched(d)) def test_noAutoAddSubdirectory(self): """ Test that if auto_add is off we don't add a new directory """ def _callback(wp, filename, mask, data): # We are notified before we actually process new # directories, so we need to defer this check. def _(): try: self.assert_(not self.inotify.isWatched(SUBDIR.path)) d.callback(None) except Exception, e: d.errback(e) reactor.callLater(0, _) checkMask = inotify.IN_ISDIR | inotify.IN_CREATE self.inotify.watch( self.dirname, mask=checkMask, auto_add=False, callbacks=(_callback, None) ) SUBDIR = self.dirname.child('test') d = defer.Deferred() SUBDIR.createDirectory() return d def test_complexSubdirectoryAutoAdd(self): """ Test that when we add one subdirectory with other new children and files we end up with the notifications for those files and with all those directories watched. This is basically the most critical testcase for inotify. """ calls = set() def _callback(wp, filename, mask, data): # We are notified before we actually process new # directories, so we need to defer this check. def _(): try: self.assert_(self.inotify.isWatched(SUBDIR.path)) self.assert_(self.inotify.isWatched(SUBDIR2.path)) self.assert_(self.inotify.isWatched(SUBDIR3.path)) CREATED = SOME_FILES.union( set([SUBDIR.basename(), SUBDIR2.basename(), SUBDIR3.basename() ]) ) self.assert_(len(calls), len(CREATED)) self.assertEquals(calls, CREATED) except Exception, e: d.errback(e) else: d.callback(None) if not calls: # Just some delay to be sure, given how the algorithm # works for this we know that there's a new extra cycle # every subdirectory reactor.callLater(0.1, _) calls.add(filename) checkMask = inotify.IN_ISDIR | inotify.IN_CREATE self.inotify.watch( self.dirname, mask=checkMask, auto_add=True, callbacks=(_callback, None) ) SUBDIR = self.dirname.child('test') SUBDIR2 = SUBDIR.child('test2') SUBDIR3 = SUBDIR2.child('test3') SOME_FILES = set(["file1.dat", "file2.dat", "file3.dat"]) d = defer.Deferred() SUBDIR3.makedirs() # Add some files in pretty much all the directories so that we # see that we process all of them. for i, filename in enumerate(SOME_FILES): if not i: S = SUBDIR if i == 1: S = SUBDIR2 else: S = SUBDIR3 S.child(filename).setContent(filename) return d Coherence-0.6.6.2/coherence/extern/et.py0000644000175000017500000001103611317660736016131 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007 Frank Scholz # # a little helper to get the proper ElementTree package import re import exceptions try: import cElementTree as ET import elementtree #print "we are on CET" except ImportError: try: from elementtree import ElementTree as ET import elementtree #print "simply using ET" except ImportError: """ this seems to be necessary with the python2.5 on the Maemo platform """ try: from xml.etree import cElementTree as ET from xml import etree as elementtree except ImportError: try: from xml.etree import ElementTree as ET from xml import etree as elementtree except ImportError: #print "no ElementTree module found, critical error" raise ImportError, "no ElementTree module found, critical error" utf8_escape = re.compile(eval(r'u"[&<>\"]+"')) escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) def encode_entity(text, pattern=escape): # map reserved and non-ascii characters to numerical entities def escape_entities(m, map=elementtree.ElementTree._escape_map): out = [] append = out.append for char in m.group(): t = map.get(char) if t is None: t = "&#%d;" % ord(char) append(t) return ''.join(out) try: return elementtree.ElementTree._encode(pattern.sub(escape_entities, text), 'ascii') except TypeError: elementtree.ElementTree._raise_serialization_error(text) def new_encode_entity(text, pattern=utf8_escape): # map reserved and non-ascii characters to numerical entities def escape_entities(m, map=elementtree.ElementTree._escape_map): out = [] append = out.append for char in m.group(): t = map.get(char) if t is None: t = "&#%d;" % ord(char) append(t) if type(text) == unicode: return ''.join(out) else: return u''.encode('utf-8').join(out) try: if type(text) == unicode: return elementtree.ElementTree._encode(escape.sub(escape_entities, text), 'ascii') else: return elementtree.ElementTree._encode(utf8_escape.sub(escape_entities, text.decode('utf-8')), 'utf-8') except TypeError: elementtree.ElementTree._raise_serialization_error(text) elementtree.ElementTree._encode_entity = new_encode_entity # it seems there are some ElementTree libs out there # which have the alias XMLParser and some that haven't. # # So we just use the XMLTreeBuilder method for now # if XMLParser isn't available. if not hasattr(ET, 'XMLParser'): def XMLParser(encoding='utf-8'): return ET.XMLTreeBuilder() ET.XMLParser = XMLParser def namespace_map_update(namespaces): #try: # from xml.etree import ElementTree #except ImportError: # from elementtree import ElementTree elementtree.ElementTree._namespace_map.update(namespaces) class ElementInterface(elementtree.ElementTree._ElementInterface): """ helper class """ def indent(elem, level=0): """ generate pretty looking XML, based upon: http://effbot.org/zone/element-lib.htm#prettyprint """ i = "\n" + level*" " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " for elem in elem: indent(elem, level+1) if not elem.tail or not elem.tail.strip(): elem.tail = i if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def parse_xml(data, encoding="utf-8",dump_invalid_data=False): try: p = ET.XMLParser(encoding=encoding) except exceptions.TypeError: p = ET.XMLParser() # my version of twisted.web returns page_infos as a dictionary in # the second item of the data list if isinstance(data, (list, tuple)): data, _ = data try: data = data.encode(encoding) except UnicodeDecodeError: pass # Guess from who we're getting this? data = data.replace('\x00','') try: p.feed(data) except Exception, error: if dump_invalid_data: print error, repr(data) p.close() raise Exception, error else: return ET.ElementTree(p.close()) Coherence-0.6.6.2/coherence/extern/simple_config.py0000644000175000017500000001533511317660736020345 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # based on: http://code.activestate.com/recipes/573463/ # Modified by Philippe Normand # Copyright 2008, Frank Scholz from coherence.extern.et import ET as ElementTree, indent, parse_xml class ConfigItem(object): """ the base """ class Config(ConfigItem): def __init__(self,filename,root=None,preamble=False): self.filename = filename self.db = parse_xml(open(self.filename).read()) self.config = self.db = ConvertXmlToDict(self.db.getroot()) self.preamble = '' if preamble == True: self.preamble = """\n""" if root != None: try: self.config = self.config[root] except KeyError: pass self.config.save = self.save def tostring(self): root = ConvertDictToXml(self.db) tree = ElementTree.ElementTree(root).getroot() indent(tree,0) xml = self.preamble + ElementTree.tostring(tree, encoding='utf-8') return xml def save(self, new_filename=None): if new_filename != None: self.filename = new_filename xml = self.tostring() f = open(self.filename, 'wb') f.write(xml) f.close() def get(self,key,default=None): print "get", key if key in self.config: item = self.config[key] try: if item['active'] == 'no': return default return item except (TypeError,KeyError): return item return default def set(self,key,value): self.config[key] = value class XmlDictObject(dict,ConfigItem): def __init__(self, initdict=None): if initdict is None: initdict = {} dict.__init__(self, initdict) self._attrs = {} def __getattr__(self, item): value = self.__getitem__(item) try: if value['active'] == 'no': raise KeyError except (TypeError,KeyError): return value return value def __setattr__(self, item, value): if item == '_attrs': object.__setattr__(self, item, value) else: self.__setitem__(item, value) def get(self,key,default=None): try: item = self[key] try: if item['active'] == 'no': return default return item except (TypeError,KeyError): return item except KeyError: pass return default def set(self,key,value): self[key] = value def __str__(self): if self.has_key('_text'): return self.__getitem__('_text') else: return '' def __repr__(self): return repr(dict(self)) @staticmethod def Wrap(x): if isinstance(x, dict): return XmlDictObject((k, XmlDictObject.Wrap(v)) for (k, v) in x.iteritems()) elif isinstance(x, list): return [XmlDictObject.Wrap(v) for v in x] else: return x @staticmethod def _UnWrap(x): if isinstance(x, dict): return dict((k, XmlDictObject._UnWrap(v)) for (k, v) in x.iteritems()) elif isinstance(x, list): return [XmlDictObject._UnWrap(v) for v in x] else: return x def UnWrap(self): return XmlDictObject._UnWrap(self) def _ConvertDictToXmlRecurse(parent, dictitem): assert type(dictitem) is not type([]) if isinstance(dictitem, dict): for (tag, child) in dictitem.iteritems(): if str(tag) == '_text': parent.text = str(child) ## elif str(tag) == '_attrs': ## for key, value in child.iteritems(): ## parent.set(key, value) elif type(child) is type([]): for listchild in child: elem = ElementTree.Element(tag) parent.append(elem) _ConvertDictToXmlRecurse(elem, listchild) else: if(not isinstance(dictitem, XmlDictObject) and not callable(dictitem)): attrs = dictitem dictitem = XmlDictObject() dictitem._attrs = attrs if tag in dictitem._attrs: parent.set(tag, child) elif not callable(tag) and not callable(child): elem = ElementTree.Element(tag) parent.append(elem) _ConvertDictToXmlRecurse(elem, child) else: if not callable(dictitem): parent.text = str(dictitem) def ConvertDictToXml(xmldict): roottag = xmldict.keys()[0] root = ElementTree.Element(roottag) _ConvertDictToXmlRecurse(root, xmldict[roottag]) return root def _ConvertXmlToDictRecurse(node, dictclass): nodedict = dictclass() ## if node.items(): ## nodedict.update({'_attrs': dict(node.items())}) if len(node.items()) > 0: # if we have attributes, set them attrs = dict(node.items()) nodedict.update(attrs) nodedict._attrs = attrs for child in node: # recursively add the element's children newitem = _ConvertXmlToDictRecurse(child, dictclass) if nodedict.has_key(child.tag): # found duplicate tag, force a list if type(nodedict[child.tag]) is type([]): # append to existing list nodedict[child.tag].append(newitem) else: # convert to list nodedict[child.tag] = [nodedict[child.tag], newitem] else: # only one, directly set the dictionary nodedict[child.tag] = newitem if node.text is None: text = '' else: text = node.text.strip() if len(nodedict) > 0: # if we have a dictionary add the text as a dictionary value (if there is any) if len(text) > 0: nodedict['_text'] = text else: # if we don't have child nodes or attributes, just set the text if node.text is not None: nodedict = node.text.strip() return nodedict def ConvertXmlToDict(root,dictclass=XmlDictObject): return dictclass({root.tag: _ConvertXmlToDictRecurse(root, dictclass)}) def main(): c = Config('config.xml',root='config') #print '%r' % c.config #c.save(new_filename='config.new.xml') print c.config['interface'] #for plugin in c.config.pluginlist.plugin: # if plugin.active != 'no': # print '%r' % plugin if __name__ == '__main__': main() Coherence-0.6.6.2/coherence/extern/inotify.py0000644000175000017500000004154311317660736017210 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006-2009 Frank Scholz # Modified by Colin Laplace, added is_watched() function # Copyright 2008 Adroll.com and Valentino Volonghi # Modified by Valentino Volonghi. # * Increased isWatched efficiency, now it's O(1) # * Code reorganization, added docstrings, Twisted coding style # * Introduced an hack to partially solve a race condition in auto_add. # * Removed code that didn't use libc 2.4 but magic hacks # -> reverted, as it might still be needed somewhere (fs) # * Used fdesc.readFromFD to read during doRead import os import struct try: import ctypes import ctypes.util except ImportError: raise SystemError("ctypes not detected on this system, can't use INotify") from twisted.internet import reactor from twisted.internet.abstract import FileDescriptor from twisted.internet import fdesc from twisted.python.filepath import FilePath # from /usr/src/linux/include/linux/inotify.h IN_ACCESS = 0x00000001L # File was accessed IN_MODIFY = 0x00000002L # File was modified IN_ATTRIB = 0x00000004L # Metadata changed IN_CLOSE_WRITE = 0x00000008L # Writtable file was closed IN_CLOSE_NOWRITE = 0x00000010L # Unwrittable file closed IN_OPEN = 0x00000020L # File was opened IN_MOVED_FROM = 0x00000040L # File was moved from X IN_MOVED_TO = 0x00000080L # File was moved to Y IN_CREATE = 0x00000100L # Subfile was created IN_DELETE = 0x00000200L # Subfile was delete IN_DELETE_SELF = 0x00000400L # Self was deleted IN_MOVE_SELF = 0x00000800L # Self was moved IN_UNMOUNT = 0x00002000L # Backing fs was unmounted IN_Q_OVERFLOW = 0x00004000L # Event queued overflowed IN_IGNORED = 0x00008000L # File was ignored IN_ONLYDIR = 0x01000000 # only watch the path if it is a directory IN_DONT_FOLLOW = 0x02000000 # don't follow a sym link IN_MASK_ADD = 0x20000000 # add to the mask of an already existing watch IN_ISDIR = 0x40000000 # event occurred against dir IN_ONESHOT = 0x80000000 # only send event once IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE # closes IN_MOVED = IN_MOVED_FROM | IN_MOVED_TO # moves IN_CHANGED = IN_MODIFY | IN_ATTRIB # changes IN_WATCH_MASK = IN_MODIFY | IN_ATTRIB | \ IN_CREATE | IN_DELETE | \ IN_DELETE_SELF | IN_MOVE_SELF | \ IN_UNMOUNT | IN_MOVED_FROM | IN_MOVED_TO _FLAG_TO_HUMAN = { IN_ACCESS: 'access', IN_MODIFY: 'modify', IN_ATTRIB: 'attrib', IN_CLOSE_WRITE: 'close_write', IN_CLOSE_NOWRITE: 'close_nowrite', IN_OPEN: 'open', IN_MOVED_FROM: 'moved_from', IN_MOVED_TO: 'moved_to', IN_CREATE: 'create', IN_DELETE: 'delete', IN_DELETE_SELF: 'delete_self', IN_MOVE_SELF: 'move_self', IN_UNMOUNT: 'unmount', IN_Q_OVERFLOW: 'queue_overflow', IN_IGNORED: 'ignored', IN_ONLYDIR: 'only_dir', IN_DONT_FOLLOW: 'dont_follow', IN_MASK_ADD: 'mask_add', IN_ISDIR: 'is_dir', IN_ONESHOT: 'one_shot' } # system call numbers are architecture-specific # see /usr/include/linux/asm/unistd.h and look for inotify _inotify_syscalls = { 'i386': (291,292,293), # FIXME, there has to be a better way for this 'i486': (291,292,293), 'i586': (291,292,293), 'i686': (291,292,293), 'x86_64': (253,254,255), # gotten from FC-6 and F-7 'armv6l':(316,317,318), # Nokia N800 'armv5tej1':(316,317,318), # Nokia N770 'ppc': (275,276,277), # PPC, like PS3 } def flag_to_human(mask): """ Auxiliary function that converts an hexadecimal mask into a series of human readable flags. """ s = [] for (k, v) in _FLAG_TO_HUMAN.iteritems(): if k & mask: s.append(v) return s class Watch(object): """ Watch object that represents a Watch point in the filesystem. @ivar path: The path over which this watch point is monitoring @ivar mask: The events monitored by this watchpoint @ivar auto_add: Flag that determines whether this watch point should automatically add created subdirectories @ivar callbacks: C{list} of C{tuples} of callbacks that should be called synchronously on the events monitored. """ def __init__(self, path, mask=IN_WATCH_MASK, auto_add=False, callbacks=[]): self.path = path self.mask = mask self.auto_add = auto_add self.callbacks = [] if not isinstance(callbacks, list): callbacks = [callbacks] self.callbacks = callbacks def addCallback(self, callback, args=None): """ Add a new callback to the list with the given auxiliary optional argument. """ self.callbacks.append((callback, args)) def notify(self, filename, events): """ Callback function used by L{INotify} to dispatch an event. """ for callback in self.callbacks: if callback is not None: #wrap that so our loop isn't aborted by a faulty callback try: callback[0](self, filename, events, callback[1]) except: import traceback traceback.print_exc() class INotify(FileDescriptor, object): """ The INotify file descriptor, it basically does everything related to INotify, from reading to notifying watch points. """ _instance_ = None # Singleton def __new__(cls, *args, **kwargs): obj = getattr(cls, '_instance_', None) if obj is not None: return obj else: obj = super(INotify, cls).__new__(cls, *args, **kwargs) # Check inotify support by checking for the required functions obj.libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c')) if len([function for function in "inotify_add_watch inotify_init inotify_rm_watch".split() if hasattr(obj.libc, function)]) == 3: obj.inotify_init = obj.libc.inotify_init obj.inotify_add_watch = obj.libc_inotify_add_watch obj.inotify_rm_watch = obj.libc_inotify_rm_watch else: print("inotify.py - can't use libc6, 2.4 or higher needed") import platform if platform.system() != 'Linux': raise SystemError, "unknown system '%r', INotify support disabled" % platform.uname() machine = platform.machine() try: obj._init_syscall_id = _inotify_syscalls[machine][0] obj._add_watch_syscall_id = _inotify_syscalls[machine][1] obj._rm_watch_syscall_id = _inotify_syscalls[machine][2] obj.inotify_init = obj._inotify_init obj.inotify_add_watch = obj._inotify_add_watch obj.inotify_rm_watch = obj._inotify_rm_watch except: raise SystemError, "unknown system '%s', INotify support disabled" % machine FileDescriptor.__init__(obj) obj._fd = obj.inotify_init() if obj._fd < 0: raise SystemError("INotify initialization error.") fdesc.setNonBlocking(obj._fd) reactor.addReader(obj) obj._buffer = '' # Mapping from wds to Watch objects obj._watchpoints = {} # Mapping from paths to wds obj._watchpaths = {} cls._instance_ = obj return obj def _addWatch(self, path, mask, auto_add, callbacks): """ Private helpers that abstract the use of ctypes and help managing state related to those calls. """ wd = self.inotify_add_watch( os.path.normpath(path), mask ) if wd < 0: raise IOError("Failed to add watch on '%r' - (%r)" % (path, wd)) iwp = Watch(path, mask, auto_add, callbacks) self._watchpoints[wd] = iwp self._watchpaths[path] = wd return wd def _rmWatch(self, wd): """ Private helpers that abstract the use of ctypes and help managing state related to those calls. """ self.inotify_rm_watch(wd) iwp = self._watchpoints.pop(wd) self._watchpaths.pop(iwp.path) del iwp def _inotify_init(self): return self.libc.syscall(self._init_syscall_id) def _inotify_add_watch(self, path, mask): if type(path) is unicode: path = path.encode('utf-8') self.libc.syscall.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_int] else: self.libc.syscall.argtypes = None return self.libc.syscall(self._add_watch_syscall_id, self._fd, path, mask) def _inotify_rm_watch(self, wd): return self.libc.syscall(self._rm_watch_syscall_id, self._fd, wd) def libc_inotify_add_watch(self, path, mask): if type(path) is unicode: path = path.encode('utf-8') self.libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int] else: self.libc.inotify_add_watch.argtypes = None return self.libc.inotify_add_watch(self._fd, path, mask) def libc_inotify_rm_watch(self, wd): return self.libc.inotify_rm_watch(self._fd, wd) def release(self): """ Release the inotify file descriptor and do the necessary cleanup """ reactor.removeReader(self) if hasattr(self, '_fd') and self._fd >= 0: try: os.close(self._fd) except OSError: pass if hasattr(INotify, '_instance_'): del INotify._instance_ # I'd rather not have this... __del__ = release def fileno(self): """ Get the underlying file descriptor from this inotify observer. """ return self._fd def notify(self, iwp, filename, mask, *args): """ A simple callback that you can use for tests """ print "event %s on %s %s" % ( ', '.join(flag_to_human(mask)), iwp.path, filename) def doRead(self): """ Read some data from the observed file descriptors """ fdesc.readFromFD(self._fd, self._doRead) def _doRead(self, in_): """ Work on the data just read from the file descriptor. """ self._buffer += in_ while True: if len(self._buffer) < 16: break wd, mask, cookie, size = struct.unpack("=LLLL", self._buffer[0:16]) if size: name = self._buffer[16:16+size].rstrip('\0') else: name = None self._buffer = self._buffer[16+size:] try: iwp = self._watchpoints[wd] except: continue # can this happen? path = iwp.path if name: path = os.path.join(path, name) iwp.notify(name, mask) else: iwp.notify(path, mask) if (iwp.auto_add and mask & IN_ISDIR and mask & IN_CREATE): # Note that this is a fricking hack... it's because we # cannot be fast enough in adding a watch to a directory # and so we basically end up getting here too late if # some operations have already been going on in the # subdir, we basically need to catchup. # This eventually ends up meaning that we generate # double events, your app must be resistant. def _addChildren(iwp): try: listdir = os.listdir(iwp.path) except OSError: # Somebody or something (like a test) # removed this directory while we were in the # callLater(0...) waiting. It doesn't make # sense to process it anymore return # note that it's true that listdir will only see # the subdirs inside path at the moment of the call # but path is monitored already so if something is # created we will receive an event. for f in listdir: inner = os.path.join(iwp.path, f) # It's a directory, watch it and then add its # children if os.path.isdir(inner): wd = self.watch( inner, mask=iwp.mask, auto_add=True, callbacks=iwp.callbacks ) iwp.notify(f, IN_ISDIR|IN_CREATE) # now inner is watched, we can add its children # the callLater is to avoid recursion reactor.callLater(0, _addChildren, self._watchpoints[wd]) # It's a file and we notify it. if os.path.isfile(inner): iwp.notify(f, IN_CREATE|IN_CLOSE_WRITE) if os.path.isdir(path): new_wd = self.watch( path, mask=iwp.mask, auto_add=True, callbacks=iwp.callbacks ) # This is very very very hacky and I'd rather # not do this but we have no other alternative # that is less hacky other than surrender # We use callLater because we don't want to have # too many events waiting while we process these # subdirs, we must always answer events as fast # as possible or the overflow might come. reactor.callLater(0, _addChildren, self._watchpoints[new_wd]) if mask & IN_DELETE_SELF: self._rmWatch(wd) def watch(self, path, mask=IN_WATCH_MASK, auto_add=None, callbacks=[], recursive=False): """ Watch the 'mask' events in given path. @param path: The path needing monitoring @type path: L{FilePath} or C{str} or C{unicode} @param mask: The events that should be watched @type mask: C{hex} @param auto_add: if True automatically add newly created subdirectories @type auto_add: C{boolean} @param callbacks: A list of callbacks that should be called when an event happens in the given path. @type callbacks: C{list} of C{tuples} @param recursive: Also add all the subdirectories in this path @type recursive: C{boolean} """ if isinstance(path, FilePath): path = path.path if type(path) is unicode: path = path.encode('utf-8') path = os.path.realpath(path) if recursive: for root, dirs, files in os.walk(path): self.watch(root, mask, auto_add, callbacks, False) else: wd = self.isWatched(path) if wd: return wd mask = mask | IN_DELETE_SELF return self._addWatch(path, mask, auto_add, callbacks) def ignore(self, path): """ Remove the watch point monitoring the given path @param path: The path that should be ignored @type path: L{FilePath} or C{unicode} or C{str} """ if isinstance(path, FilePath): path = path.path if type(path) is unicode: path = path.encode('utf-8') path = os.path.realpath(path) wd = self.isWatched(path) if wd: self._rmWatch(wd) def isWatched(self, path): """ Helper function that checks if the path is already monitored and returns its watchdescriptor if so. @param path: The path that should be checked @type path: L{FilePath} or C{unicode} or C{str} """ if isinstance(path, FilePath): path = path.path if type(path) is unicode: path = path.encode('utf-8') return self._watchpaths.get(path, False) def flag_to_human(self,mask): return flag_to_human(mask) if __name__ == '__main__': i = INotify() print i i.watch(unicode('/tmp'), auto_add=True, callbacks=(i.notify,None), recursive=True) i2 = INotify() print i2 i2.watch('/', auto_add=True, callbacks=(i2.notify,None), recursive=False) reactor.run() Coherence-0.6.6.2/coherence/extern/louie.py0000644000175000017500000000505011317660736016635 0ustar devdev""" Wrapper module for the louie implementation """ import warnings from coherence.dispatcher import Dispatcher class Any(object): pass class All(object): pass class Anonymous(object): pass # fake the API class Dummy(object): pass signal = Dummy() sender = Dummy() #senders sender.Anonymous = Anonymous sender.Any = Any #signals signal.All = All # a slightly less raise-y-ish implementation as louie was not so picky, too class GlobalDispatcher(Dispatcher): def connect(self, signal, callback, *args, **kw): if not signal in self.receivers: # ugly hack self.receivers[signal] = [] return Dispatcher.connect(self, signal, callback, *args, **kw) def _get_receivers(self, signal): try: return self.receivers[signal] except KeyError: return [] global _global_dispatcher _global_dispatcher = GlobalDispatcher() _global_receivers_pool = {} def _display_deprecation_warning(): warnings.warn("extern.louie will soon be deprecated in favor of coherence.dispatcher.") def connect(receiver, signal=All, sender=Any, weak=True): callback = receiver if signal in (Any, All): raise NotImplemented("This is not allowed. Signal HAS to be something") if sender not in (Any, All): _display_deprecation_warning() receiver = _global_dispatcher.connect(signal, callback) _global_receivers_pool[(callback, signal)] = receiver return receiver def disconnect(receiver, signal=All, sender=Any, weak=True): callback = receiver if signal in (Any, All): raise NotImplemented("This is not allowed. Signal HAS to be something") if sender not in (Any, All): _display_deprecation_warning() receiver = _global_receivers_pool.pop((callback, signal)) return _global_dispatcher.disconnect(receiver) def send(signal=All, sender=Anonymous, *arguments, **named): if signal in (Any, All): raise NotImplemented("This is not allowed. Signal HAS to be something") if sender not in (Anonymous, None): _display_deprecation_warning() # the first value of the callback shall always be the signal: return _global_dispatcher.save_emit(signal, *arguments, **named) def send_minimal(signal=All, sender=Anonymous, *arguments, **named): return send(signal, sender, *arguments, **named) def send_exact(signal=All, sender=Anonymous, *arguments, **named): return send(signal, sender, *arguments, **named) def send_robust(signal=All, sender=Anonymous, *arguments, **named): return send(signal, sender, *arguments, **named) Coherence-0.6.6.2/coherence/extern/simple_plugin.py0000644000175000017500000000463011317660736020372 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Frank Scholz """ real simple plugin system meant as a replacement when setuptools/pkg_resources are not available """ import os import sys class Plugin(object): """ a new style class that betrays all its sub-classes """ pass class Reception(object): """ singleton class which holds information about known plugins currently a singleton, and even a class, seems to be overkill for this, but maybe we'll add some more functionality later """ _instance_ = None # Singleton def __new__(cls, *args, **kwargs): """ creates the singleton """ obj = getattr(cls,'_instance_',None) if obj is not None: return obj else: obj = super(Reception, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj return obj def __init__(self,plugin_path=None,log=None): """ initializes the class and checks in if a path is provided """ self.log = log if plugin_path is not None: self.checkin(plugin_path) def checkin(self,plugin_path): """ import all valid files from plugin_path """ if not plugin_path in sys.path: sys.path.insert(0, plugin_path) for plugin in os.listdir(plugin_path): p = os.path.join(plugin_path, plugin) if plugin != '__init__.py' and os.path.isfile(p) and os.path.splitext(p)[1] == '.py': try: __import__(os.path.splitext(plugin)[0], None, None, ['']) except Exception, msg: if self.log is None: print "can't import %r - %s" % (os.path.splitext(plugin)[0], msg) else: self.log("can't import %r - %r" % (os.path.splitext(plugin)[0], msg)) def guestlist(self, plugin_class=Plugin): """ returns a list of all Plugin subclasses """ found = [] def get_subclass(klass, subclasses): if len(subclasses) == 0: found.append(klass) else: for k in subclasses: get_subclass(k,k.__subclasses__()) get_subclass(plugin_class, plugin_class.__subclasses__()) return foundCoherence-0.6.6.2/coherence/extern/uuid/0000755000000000000000000000000011317673117016471 5ustar rootrootCoherence-0.6.6.2/coherence/extern/uuid/uuid.html0000644000175000017500000005261711317660736017763 0ustar devdev Python: module uuid
 
 
uuid (version 1.30, 2006-06-12)
index
/Users/ping/dev/python/uuid.py

UUID objects (universally unique identifiers) according to RFC 4122.
 
This module provides immutable UUID objects (class UUID) and the functions
uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5
UUIDs as specified in RFC 4122.
 
If all you want is a unique ID, you should probably call uuid1() or uuid4().
Note that uuid1() may compromise privacy since it creates a UUID containing
the computer's network address.  uuid4() creates a random UUID.
 
Typical usage:
 
    >>> import uuid
 
    # make a UUID based on the host ID and current time
    >>> uuid.uuid1()
    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
 
    # make a UUID using an MD5 hash of a namespace UUID and a name
    >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
    UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')
 
    # make a random UUID
    >>> uuid.uuid4()
    UUID('16fd2706-8baf-433b-82eb-8c7fada847da')
 
    # make a UUID using a SHA-1 hash of a namespace UUID and a name
    >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
    UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')
 
    # make a UUID from a string of hex digits (braces and hyphens ignored)
    >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')
 
    # convert a UUID to a string of hex digits in standard form
    >>> str(x)
    '00010203-0405-0607-0809-0a0b0c0d0e0f'
 
    # get the raw 16 bytes of the UUID
    >>> x.bytes
    '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
 
    # make a UUID from a 16-byte string
    >>> uuid.UUID(bytes=x.bytes)
    UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
 
This module works with Python 2.3 or higher.

 
Classes
       
__builtin__.object
UUID

 
class UUID(__builtin__.object)
    Instances of the UUID class represent UUIDs as specified in RFC 4122.
UUID objects are immutable, hashable, and usable as dictionary keys.
Converting a UUID to a string with str() yields something in the form
'12345678-1234-1234-1234-123456789abc'.  The UUID constructor accepts
four possible forms: a similar string of hexadecimal digits, or a
string of 16 raw bytes as an argument named 'bytes', or a tuple of
six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and
48-bit values respectively) as an argument named 'fields', or a single
128-bit integer as an argument named 'int'.
 
UUIDs have these read-only attributes:
 
    bytes       the UUID as a 16-byte string
 
    fields      a tuple of the six integer fields of the UUID,
                which are also available as six individual attributes
                and two derived attributes:
 
        time_low                the first 32 bits of the UUID
        time_mid                the next 16 bits of the UUID
        time_hi_version         the next 16 bits of the UUID
        clock_seq_hi_variant    the next 8 bits of the UUID
        clock_seq_low           the next 8 bits of the UUID
        node                    the last 48 bits of the UUID
 
        time                    the 60-bit timestamp
        clock_seq               the 14-bit sequence number
 
    hex         the UUID as a 32-character hexadecimal string
 
    int         the UUID as a 128-bit integer
 
    urn         the UUID as a URN as specified in RFC 4122
 
    variant     the UUID variant (one of the constants RESERVED_NCS,
                RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE)
 
    version     the UUID version number (1 through 5, meaningful only
                when the variant is RFC_4122)
 
  Methods defined here:
__cmp__(self, other)
__hash__(self)
__init__(self, hex=None, bytes=None, fields=None, int=None, version=None)
Create a UUID from either a string of 32 hexadecimal digits,
a string of 16 bytes as the 'bytes' argument, a tuple of six
integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
the 'fields' argument, or a single 128-bit integer as the 'int'
argument.  When a string of hex digits is given, curly braces,
hyphens, and a URN prefix are all optional.  For example, these
expressions all yield the same UUID:
 
UUID('{12345678-1234-5678-1234-567812345678}')
UUID('12345678123456781234567812345678')
UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
UUID(bytes='\x12\x34\x56\x78'*4)
UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
UUID(int=0x12345678123456781234567812345678)
 
Exactly one of 'hex', 'bytes', 'fields', or 'int' must be given.
The 'version' argument is optional; if given, the resulting UUID
will have its variant and version number set according to RFC 4122,
overriding bits in the given 'hex', 'bytes', 'fields', or 'int'.
__int__(self)
__repr__(self)
__setattr__(self, name, value)
__str__(self)
get_bytes(self)
get_clock_seq(self)
get_clock_seq_hi_variant(self)
get_clock_seq_low(self)
get_fields(self)
get_hex(self)
get_node(self)
get_time(self)
get_time_hi_version(self)
get_time_low(self)
get_time_mid(self)
get_urn(self)
get_variant(self)
get_version(self)

Properties defined here:
bytes
get = get_bytes(self)
clock_seq
get = get_clock_seq(self)
clock_seq_hi_variant
get = get_clock_seq_hi_variant(self)
clock_seq_low
get = get_clock_seq_low(self)
fields
get = get_fields(self)
hex
get = get_hex(self)
node
get = get_node(self)
time
get = get_time(self)
time_hi_version
get = get_time_hi_version(self)
time_low
get = get_time_low(self)
time_mid
get = get_time_mid(self)
urn
get = get_urn(self)
variant
get = get_variant(self)
version
get = get_version(self)

Data and other attributes defined here:
__dict__ = <dictproxy object at 0xd6790>
dictionary for instance variables (if defined)
__weakref__ = <attribute '__weakref__' of 'UUID' objects>
list of weak references to the object (if defined)

 
Functions
       
getnode()
Get the hardware address as a 48-bit integer.  The first time this
runs, it may launch a separate program, which could be quite slow.  If
all attempts to obtain the hardware address fail, we choose a random
48-bit number with its eighth bit set to 1 as recommended in RFC 4122.
uuid1(node=None, clock_seq=None)
Generate a UUID from a host ID, sequence number, and the current time.
If 'node' is not given, getnode() is used to obtain the hardware
address.  If 'clock_seq' is given, it is used as the sequence number;
otherwise a random 14-bit sequence number is chosen.
uuid3(namespace, name)
Generate a UUID from the MD5 hash of a namespace UUID and a name.
uuid4()
Generate a random UUID.
uuid5(namespace, name)
Generate a UUID from the SHA-1 hash of a namespace UUID and a name.

 
Data
        NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')
RESERVED_FUTURE = 'reserved for future definition'
RESERVED_MICROSOFT = 'reserved for Microsoft compatibility'
RESERVED_NCS = 'reserved for NCS compatibility'
RFC_4122 = 'specified in RFC 4122'
__author__ = 'Ka-Ping Yee <ping@zesty.ca>'
__date__ = '2006-06-12'
__version__ = '1.30'

 
Author
        Ka-Ping Yee <ping@zesty.ca>
Coherence-0.6.6.2/coherence/extern/uuid/uuid.README.txt0000644000175000017500000000030211317660736020552 0ustar devdevThe uuid.py module here is part of the standard library for Python 2.5, distributed under the Python software license. You can also obtain it from http://python.org/pypi/uuid/. -- Ka-Ping Yee Coherence-0.6.6.2/coherence/extern/uuid/__init__.py0000644000175000017500000000000011317660736020213 0ustar devdevCoherence-0.6.6.2/coherence/extern/uuid/uuid.py0000644000175000017500000004261511317660736017444 0ustar devdevr"""UUID objects (universally unique identifiers) according to RFC 4122. This module provides immutable UUID objects (class UUID) and the functions uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5 UUIDs as specified in RFC 4122. If all you want is a unique ID, you should probably call uuid1() or uuid4(). Note that uuid1() may compromise privacy since it creates a UUID containing the computer's network address. uuid4() creates a random UUID. Typical usage: >>> import uuid # make a UUID based on the host ID and current time >>> uuid.uuid1() UUID('a8098c1a-f86e-11da-bd1a-00112444be1e') # make a UUID using an MD5 hash of a namespace UUID and a name >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org') UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e') # make a random UUID >>> uuid.uuid4() UUID('16fd2706-8baf-433b-82eb-8c7fada847da') # make a UUID using a SHA-1 hash of a namespace UUID and a name >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org') UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d') # make a UUID from a string of hex digits (braces and hyphens ignored) >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}') # convert a UUID to a string of hex digits in standard form >>> str(x) '00010203-0405-0607-0809-0a0b0c0d0e0f' # get the raw 16 bytes of the UUID >>> x.bytes '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' # make a UUID from a 16-byte string >>> uuid.UUID(bytes=x.bytes) UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') This module works with Python 2.3 or higher.""" __author__ = 'Ka-Ping Yee ' __date__ = '$Date: 2006/06/12 23:15:40 $'.split()[1].replace('/', '-') __version__ = '$Revision: 1.30 $'.split()[1] RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ 'reserved for NCS compatibility', 'specified in RFC 4122', 'reserved for Microsoft compatibility', 'reserved for future definition'] class UUID(object): """Instances of the UUID class represent UUIDs as specified in RFC 4122. UUID objects are immutable, hashable, and usable as dictionary keys. Converting a UUID to a string with str() yields something in the form '12345678-1234-1234-1234-123456789abc'. The UUID constructor accepts four possible forms: a similar string of hexadecimal digits, or a string of 16 raw bytes as an argument named 'bytes', or a tuple of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and 48-bit values respectively) as an argument named 'fields', or a single 128-bit integer as an argument named 'int'. UUIDs have these read-only attributes: bytes the UUID as a 16-byte string fields a tuple of the six integer fields of the UUID, which are also available as six individual attributes and two derived attributes: time_low the first 32 bits of the UUID time_mid the next 16 bits of the UUID time_hi_version the next 16 bits of the UUID clock_seq_hi_variant the next 8 bits of the UUID clock_seq_low the next 8 bits of the UUID node the last 48 bits of the UUID time the 60-bit timestamp clock_seq the 14-bit sequence number hex the UUID as a 32-character hexadecimal string int the UUID as a 128-bit integer urn the UUID as a URN as specified in RFC 4122 variant the UUID variant (one of the constants RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) version the UUID version number (1 through 5, meaningful only when the variant is RFC_4122) """ def __init__(self, hex=None, bytes=None, fields=None, int=None, version=None): """Create a UUID from either a string of 32 hexadecimal digits, a string of 16 bytes as the 'bytes' argument, a tuple of six integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version, 8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as the 'fields' argument, or a single 128-bit integer as the 'int' argument. When a string of hex digits is given, curly braces, hyphens, and a URN prefix are all optional. For example, these expressions all yield the same UUID: UUID('{12345678-1234-5678-1234-567812345678}') UUID('12345678123456781234567812345678') UUID('urn:uuid:12345678-1234-5678-1234-567812345678') UUID(bytes='\x12\x34\x56\x78'*4) UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678)) UUID(int=0x12345678123456781234567812345678) Exactly one of 'hex', 'bytes', 'fields', or 'int' must be given. The 'version' argument is optional; if given, the resulting UUID will have its variant and version number set according to RFC 4122, overriding bits in the given 'hex', 'bytes', 'fields', or 'int'. """ if [hex, bytes, fields, int].count(None) != 3: raise TypeError('need just one of hex, bytes, fields, or int') if hex is not None: hex = hex.replace('urn:', '').replace('uuid:', '') hex = hex.strip('{}').replace('-', '') if len(hex) != 32: raise ValueError('badly formed hexadecimal UUID string') int = long(hex, 16) if bytes is not None: if len(bytes) != 16: raise ValueError('bytes is not a 16-char string') int = long(('%02x'*16) % tuple(map(ord, bytes)), 16) if fields is not None: if len(fields) != 6: raise ValueError('fields is not a 6-tuple') (time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node) = fields if not 0 <= time_low < 1<<32L: raise ValueError('field 1 out of range (need a 32-bit value)') if not 0 <= time_mid < 1<<16L: raise ValueError('field 2 out of range (need a 16-bit value)') if not 0 <= time_hi_version < 1<<16L: raise ValueError('field 3 out of range (need a 16-bit value)') if not 0 <= clock_seq_hi_variant < 1<<8L: raise ValueError('field 4 out of range (need an 8-bit value)') if not 0 <= clock_seq_low < 1<<8L: raise ValueError('field 5 out of range (need an 8-bit value)') if not 0 <= node < 1<<48L: raise ValueError('field 6 out of range (need a 48-bit value)') clock_seq = (clock_seq_hi_variant << 8L) | clock_seq_low int = ((time_low << 96L) | (time_mid << 80L) | (time_hi_version << 64L) | (clock_seq << 48L) | node) if int is not None: if not 0 <= int < 1<<128L: raise ValueError('int is out of range (need a 128-bit value)') if version is not None: if not 1 <= version <= 5: raise ValueError('illegal version number') # Set the variant to RFC 4122. int &= ~(0xc000 << 48L) int |= 0x8000 << 48L # Set the version number. int &= ~(0xf000 << 64L) int |= version << 76L self.__dict__['int'] = int def __cmp__(self, other): if isinstance(other, UUID): return cmp(self.int, other.int) return NotImplemented def __hash__(self): return hash(self.int) def __int__(self): return self.int def __repr__(self): return 'UUID(%r)' % str(self) def __setattr__(self, name, value): raise TypeError('UUID objects are immutable') def __str__(self): hex = '%032x' % self.int return '%s-%s-%s-%s-%s' % ( hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:]) def get_bytes(self): bytes = '' for shift in range(0, 128, 8): bytes = chr((self.int >> shift) & 0xff) + bytes return bytes bytes = property(get_bytes) def get_fields(self): return (self.time_low, self.time_mid, self.time_hi_version, self.clock_seq_hi_variant, self.clock_seq_low, self.node) fields = property(get_fields) def get_time_low(self): return self.int >> 96L time_low = property(get_time_low) def get_time_mid(self): return (self.int >> 80L) & 0xffff time_mid = property(get_time_mid) def get_time_hi_version(self): return (self.int >> 64L) & 0xffff time_hi_version = property(get_time_hi_version) def get_clock_seq_hi_variant(self): return (self.int >> 56L) & 0xff clock_seq_hi_variant = property(get_clock_seq_hi_variant) def get_clock_seq_low(self): return (self.int >> 48L) & 0xff clock_seq_low = property(get_clock_seq_low) def get_time(self): return (((self.time_hi_version & 0x0fffL) << 48L) | (self.time_mid << 32L) | self.time_low) time = property(get_time) def get_clock_seq(self): return (((self.clock_seq_hi_variant & 0x3fL) << 8L) | self.clock_seq_low) clock_seq = property(get_clock_seq) def get_node(self): return self.int & 0xffffffffffff node = property(get_node) def get_hex(self): return '%032x' % self.int hex = property(get_hex) def get_urn(self): return 'urn:uuid:' + str(self) urn = property(get_urn) def get_variant(self): if not self.int & (0x8000 << 48L): return RESERVED_NCS elif not self.int & (0x4000 << 48L): return RFC_4122 elif not self.int & (0x2000 << 48L): return RESERVED_MICROSOFT else: return RESERVED_FUTURE variant = property(get_variant) def get_version(self): # The version bits are only meaningful for RFC 4122 UUIDs. if self.variant == RFC_4122: return int((self.int >> 76L) & 0xf) version = property(get_version) def _ifconfig_getnode(): """Get the hardware address on Unix by running ifconfig.""" import os for dir in ['', '/sbin/', '/usr/sbin']: try: pipe = os.popen(os.path.join(dir, 'ifconfig')) except IOError: continue for line in pipe: words = line.lower().split() for i in range(len(words)): if words[i] in ['hwaddr', 'ether']: return int(words[i + 1].replace(':', ''), 16) def _ipconfig_getnode(): """Get the hardware address on Windows by running ipconfig.exe.""" import os, re dirs = ['', r'c:\windows\system32', r'c:\winnt\system32'] try: import ctypes buffer = ctypes.create_string_buffer(300) ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300) dirs.insert(0, buffer.value.decode('mbcs')) except: pass for dir in dirs: try: pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all') except IOError: continue for line in pipe: value = line.split(':')[-1].strip().lower() if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value): return int(value.replace('-', ''), 16) def _netbios_getnode(): """Get the hardware address on Windows using NetBIOS calls. See http://support.microsoft.com/kb/118623 for details.""" import win32wnet, netbios ncb = netbios.NCB() ncb.Command = netbios.NCBENUM ncb.Buffer = adapters = netbios.LANA_ENUM() adapters._pack() if win32wnet.Netbios(ncb) != 0: return adapters._unpack() for i in range(adapters.length): ncb.Reset() ncb.Command = netbios.NCBRESET ncb.Lana_num = ord(adapters.lana[i]) if win32wnet.Netbios(ncb) != 0: continue ncb.Reset() ncb.Command = netbios.NCBASTAT ncb.Lana_num = ord(adapters.lana[i]) ncb.Callname = '*'.ljust(16) ncb.Buffer = status = netbios.ADAPTER_STATUS() if win32wnet.Netbios(ncb) != 0: continue status._unpack() bytes = map(ord, status.adapter_address) return ((bytes[0]<<40L) + (bytes[1]<<32L) + (bytes[2]<<24L) + (bytes[3]<<16L) + (bytes[4]<<8L) + bytes[5]) # Thanks to Thomas Heller for ctypes and for his help with its use here. # If ctypes is available, use it to find system routines for UUID generation. _uuid_generate_random = _uuid_generate_time = _UuidCreate = None try: import ctypes, ctypes.util _buffer = ctypes.create_string_buffer(16) # The uuid_generate_* routines are provided by libuuid on at least # Linux and FreeBSD, and provided by libc on Mac OS X. for libname in ['uuid', 'c']: try: lib = ctypes.CDLL(ctypes.util.find_library(libname)) except: continue if hasattr(lib, 'uuid_generate_random'): _uuid_generate_random = lib.uuid_generate_random if hasattr(lib, 'uuid_generate_time'): _uuid_generate_time = lib.uuid_generate_time # On Windows prior to 2000, UuidCreate gives a UUID containing the # hardware address. On Windows 2000 and later, UuidCreate makes a # random UUID and UuidCreateSequential gives a UUID containing the # hardware address. These routines are provided by the RPC runtime. try: lib = ctypes.windll.rpcrt4 except: lib = None _UuidCreate = getattr(lib, 'UuidCreateSequential', getattr(lib, 'UuidCreate', None)) except: pass def _unixdll_getnode(): """Get the hardware address on Unix using ctypes.""" _uuid_generate_time(_buffer) return UUID(bytes=_buffer.raw).node def _windll_getnode(): """Get the hardware address on Windows using ctypes.""" if _UuidCreate(_buffer) == 0: return UUID(bytes=_buffer.raw).node def _random_getnode(): """Get a random node ID, with eighth bit set as suggested by RFC 4122.""" import random return random.randrange(0, 1<<48L) | 0x010000000000L _node = None def getnode(): """Get the hardware address as a 48-bit integer. The first time this runs, it may launch a separate program, which could be quite slow. If all attempts to obtain the hardware address fail, we choose a random 48-bit number with its eighth bit set to 1 as recommended in RFC 4122.""" global _node if _node is not None: return _node import sys if sys.platform == 'win32': getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] else: getters = [_unixdll_getnode, _ifconfig_getnode] for getter in getters + [_random_getnode]: try: _node = getter() except: continue if _node is not None: return _node def uuid1(node=None, clock_seq=None): """Generate a UUID from a host ID, sequence number, and the current time. If 'node' is not given, getnode() is used to obtain the hardware address. If 'clock_seq' is given, it is used as the sequence number; otherwise a random 14-bit sequence number is chosen.""" # When the system provides a version-1 UUID generator, use it (but don't # use UuidCreate here because its UUIDs don't conform to RFC 4122). if _uuid_generate_time and node is clock_seq is None: _uuid_generate_time(_buffer) return UUID(bytes=_buffer.raw) import time nanoseconds = int(time.time() * 1e9) # 0x01b21dd213814000 is the number of 100-ns intervals between the # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. timestamp = int(nanoseconds/100) + 0x01b21dd213814000L if clock_seq is None: import random clock_seq = random.randrange(1<<14L) # instead of stable storage time_low = timestamp & 0xffffffffL time_mid = (timestamp >> 32L) & 0xffffL time_hi_version = (timestamp >> 48L) & 0x0fffL clock_seq_low = clock_seq & 0xffL clock_seq_hi_variant = (clock_seq >> 8L) & 0x3fL if node is None: node = getnode() return UUID(fields=(time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node), version=1) def uuid3(namespace, name): """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" import md5 hash = md5.md5(namespace.bytes + name).digest() return UUID(bytes=hash[:16], version=3) def uuid4(): """Generate a random UUID.""" # When the system provides a version-4 UUID generator, use it. if _uuid_generate_random: _uuid_generate_random(_buffer) return UUID(bytes=_buffer.raw) # Otherwise, get randomness from urandom or the 'random' module. try: import os return UUID(bytes=os.urandom(16), version=4) except: import random bytes = [chr(random.randrange(256)) for i in range(16)] return UUID(bytes=bytes, version=4) def uuid5(namespace, name): """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" import sha hash = sha.sha(namespace.bytes + name).digest() return UUID(bytes=hash[:16], version=5) # The following standard UUIDs are for use with uuid3() or uuid5(). NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8') NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8') NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8') Coherence-0.6.6.2/coherence/extern/__init__.py0000644000175000017500000000000011317660736017245 0ustar devdevCoherence-0.6.6.2/coherence/extern/logger.py0000644000175000017500000001461011317660736017001 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz import os, sys #import logging from twisted.python import log LOG_UNSET = 0 LOG_DEBUG = 10 LOG_INFO = 20 LOG_WARNING = 30 LOG_ERROR = 40 LOG_CRITICAL = 50 LOG_NONE = 100 log_levels = { 'info': LOG_INFO, 'debug': LOG_DEBUG, 'warning': LOG_WARNING, 'error': LOG_ERROR, 'critical': LOG_CRITICAL, 'none': LOG_NONE} class _Logger(object): """ a singleton LOG class """ def __new__(cls, *args, **kwargs): obj = getattr(cls,'_instance_',None) if obj is not None: return obj else: obj = super(_Logger, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj #logging.basicConfig(level=logging.INFO, # format='%(asctime)s %(message)s', # datefmt='%d %b %Y %H:%M:%S',) #obj.log = logging.getLogger('.') #obj.log.setLevel(logging.INFO) obj.feeds = {} obj.master_level = None return obj def __init__(self,name='',level=LOG_DEBUG): """ a LOG feed registers with us """ if not self.feeds.has_key(name): if self.master_level: level = self.master_level self.feeds[name] = {'active':True,'level':level} def start_logging(self, logfile=None): if logfile is not None: observer = log.FileLogObserver(open(logfile, 'w')) else: observer = log.FileLogObserver(sys.stdout) log.startLoggingWithObserver(observer.emit, setStdout=0) def send(self, name, level, *args): try: if self.feeds[name]['active'] == False: return if level >= self.feeds[name]['level']: a = [] for i in args: if isinstance(i,unicode): i = i.encode('ascii', 'ignore') else: i = str(i) a.append(i) msg = ' '.join(a) log.msg('%s: %s' % (name, msg)) except KeyError: log.msg("Logger error, feed %s not found" % name) def enable(self, name): try: self.feeds[name]['active'] = True except KeyError: self.feeds[name] = {'active':True,'level':LOG_DEBUG} def disable(self, name): try: self.feeds[name]['active'] = False except KeyError: self.feeds[name] = {'active':False,'level':LOG_DEBUG} def set_level(self, name, level): try: self.feeds[name]['level'] = level except KeyError: self.feeds[name] = {'active':False,'level':level} def get_level(self, name): try: return self.feeds[name]['level'] except KeyError: return None def set_master_level(self,level): self.master_level = level for feed in self.feeds.values(): feed['level'] = level class Logger: def __init__(self, name='', level=LOG_DEBUG): self.name = name self.log = _Logger(name,level) def start_logging(self, logfile=None): self.log.start_logging(logfile) def send(self, level, *args): self.log.send( self.name, LOG_UNSET, *args) def msg(self, *args): self.log.send( self.name, LOG_DEBUG, *args) def info(self, *args): self.log.send( self.name, LOG_INFO, *args) def debug(self, *args): self.log.send( self.name, LOG_DEBUG, *args) def warning(self, *args): self.log.send( self.name, LOG_WARNING, *args) def error(self, *args): self.log.send( self.name, LOG_ERROR, *args) def critical(self, *args): self.log.send( self.name, LOG_CRITICAL, *args) def enable(self, name=None): if name == None: name=self.name self.log.enable(name) def disable(self, name=None): if name == None: name=self.name self.log.disable(name) def set_level(self, name=None, level=LOG_INFO): if name == None: name=self.name if isinstance( level, str): try: level=log_levels[level] except: level=LOG_INFO self.log.set_level(name,level) def get_level(self, name=None): if name == None: name=self.name return self.log.get_level(name) def has_level(self, level, name=None): if name == None: name=self.name if self.log.get_level(name) <= level: return True else: return False def set_warning_level(self, name=None): if name == None: name=self.name self.log.set_level(name,LOG_WARNING) def set_critical_level(self, name=None): if name == None: name=self.name self.log.set_level(name,LOG_CRITICAL) def set_master_level(self, level=LOG_DEBUG): if isinstance( level, str): try: level=log_levels[level] except: level=LOG_DEBUG self.log.set_master_level(level) def overwrite(self,name,level=None,active=None): if level: self.log.set_level(name,level) if active != None: if active == True: self.log.enable(name) else: self.log.disable(name) def get_feeds(self): return self.log.feeds() if __name__ == '__main__': from twisted.internet import reactor def test1(): l1 = Logger('Test 1') l1.send( 'Dies', 'ist', 'ein', 'Send', 'Test') l1.info( 'Dies', 'ist', 'ein', 'Info', 'Test') l2 = Logger('Test 2') l2.info( 'Dies', 'ist', 'ein', 'Info', 'Test') l3 = Logger('Test 3') l3.error( 'Dies', 'ist', 'ein', 'Error', 'Test') l2.disable(name='Test 1') l1.info( 'Dies', 'ist', 'ein', 'Info', 'Test') l2.enable(name='Test 1') l1.info( 'Dies', 'ist', 'ein', 'Info', 'Test') l3.set_level(name='Test 1',level=LOG_ERROR) l1.info( 'Dies', 'ist', 'ein', 'Info', 'Test') l1.error( 'Dies', 'ist', 'ein', 'Error', 'Test') reactor.callWhenRunning( test1) reactor.run() Coherence-0.6.6.2/coherence/extern/youtubedl/0000755000000000000000000000000011317673117017537 5ustar rootrootCoherence-0.6.6.2/coherence/extern/youtubedl/youtubedl.py0000644000175000017500000013627311317660736021564 0ustar devdev#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: Ricardo Garcia Gonzalez # Author: Danny Colligan # Author: Jean-Michel Sizun (integration within coherence framework) # License: Public domain code import htmlentitydefs import httplib import locale import math import netrc import os import os.path import re import socket import string import sys import time from urllib import urlencode, unquote, unquote_plus from coherence.upnp.core.utils import getPage std_headers = { 'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', 'Accept-Language': 'en-us,en;q=0.5', } simple_title_chars = string.ascii_letters.decode('ascii') + string.digits.decode('ascii') def preferredencoding(): """Get preferred encoding. Returns the best encoding scheme for the system, based on locale.getpreferredencoding() and some further tweaks. """ try: pref = locale.getpreferredencoding() # Mac OSX systems have this problem sometimes if pref == '': return 'UTF-8' return pref except: sys.stderr.write('WARNING: problem obtaining preferred encoding. Falling back to UTF-8.\n') return 'UTF-8' class DownloadError(Exception): """Download Error exception. This exception may be thrown by FileDownloader objects if they are not configured to continue on errors. They will contain the appropriate error message. """ pass class SameFileError(Exception): """Same File exception. This exception will be thrown by FileDownloader objects if they detect multiple files would have to be downloaded to the same file on disk. """ pass class PostProcessingError(Exception): """Post Processing exception. This exception may be raised by PostProcessor's .run() method to indicate an error in the postprocessing task. """ pass class UnavailableFormatError(Exception): """Unavailable Format exception. This exception will be thrown when a video is requested in a format that is not available for that video. """ pass class ContentTooShortError(Exception): """Content Too Short exception. This exception may be raised by FileDownloader objects when a file they download is too small for what the server announced first, indicating the connection was probably interrupted. """ # Both in bytes downloaded = None expected = None def __init__(self, downloaded, expected): self.downloaded = downloaded self.expected = expected class FileDownloader(object): """File Downloader class. File downloader objects are the ones responsible of downloading the actual video file and writing it to disk if the user has requested it, among some other tasks. In most cases there should be one per program. As, given a video URL, the downloader doesn't know how to extract all the needed information, task that InfoExtractors do, it has to pass the URL to one of them. For this, file downloader objects have a method that allows InfoExtractors to be registered in a given order. When it is passed a URL, the file downloader handles it to the first InfoExtractor it finds that reports being able to handle it. The InfoExtractor extracts all the information about the video or videos the URL refers to, and asks the FileDownloader to process the video information, possibly downloading the video. File downloaders accept a lot of parameters. In order not to saturate the object constructor with arguments, it receives a dictionary of options instead. These options are available through the params attribute for the InfoExtractors to use. The FileDownloader also registers itself as the downloader in charge for the InfoExtractors that are added to it, so this is a "mutual registration". Available options: username: Username for authentication purposes. password: Password for authentication purposes. usenetrc: Use netrc for authentication instead. quiet: Do not print messages to stdout. forceurl: Force printing final URL. forcetitle: Force printing title. simulate: Do not download the video files. format: Video format code. outtmpl: Template for output names. ignoreerrors: Do not stop on download errors. ratelimit: Download speed limit, in bytes/sec. nooverwrites: Prevent overwriting files. continuedl: Try to continue downloads if possible. """ params = None _ies = [] _pps = [] _download_retcode = None def __init__(self, params): """Create a FileDownloader object with the given options.""" self._ies = [] self._pps = [] self._download_retcode = 0 self.params = params @staticmethod def pmkdir(filename): """Create directory components in filename. Similar to Unix "mkdir -p".""" components = filename.split(os.sep) aggregate = [os.sep.join(components[0:x]) for x in xrange(1, len(components))] aggregate = ['%s%s' % (x, os.sep) for x in aggregate] # Finish names with separator for dir in aggregate: if not os.path.exists(dir): os.mkdir(dir) @staticmethod def format_bytes(bytes): if bytes is None: return 'N/A' if type(bytes) is str: bytes = float(bytes) if bytes == 0.0: exponent = 0 else: exponent = long(math.log(bytes, 1024.0)) suffix = 'bkMGTPEZY'[exponent] converted = float(bytes) / float(1024**exponent) return '%.2f%s' % (converted, suffix) @staticmethod def calc_percent(byte_counter, data_len): if data_len is None: return '---.-%' return '%6s' % ('%3.1f%%' % (float(byte_counter) / float(data_len) * 100.0)) @staticmethod def calc_eta(start, now, total, current): if total is None: return '--:--' dif = now - start if current == 0 or dif < 0.001: # One millisecond return '--:--' rate = float(current) / dif eta = long((float(total) - float(current)) / rate) (eta_mins, eta_secs) = divmod(eta, 60) if eta_mins > 99: return '--:--' return '%02d:%02d' % (eta_mins, eta_secs) @staticmethod def calc_speed(start, now, bytes): dif = now - start if bytes == 0 or dif < 0.001: # One millisecond return '%10s' % '---b/s' return '%10s' % ('%s/s' % FileDownloader.format_bytes(float(bytes) / dif)) @staticmethod def best_block_size(elapsed_time, bytes): new_min = max(bytes / 2.0, 1.0) new_max = min(max(bytes * 2.0, 1.0), 4194304) # Do not surpass 4 MB if elapsed_time < 0.001: return long(new_max) rate = bytes / elapsed_time if rate > new_max: return long(new_max) if rate < new_min: return long(new_min) return long(rate) @staticmethod def parse_bytes(bytestr): """Parse a string indicating a byte quantity into a long integer.""" matchobj = re.match(r'(?i)^(\d+(?:\.\d+)?)([kMGTPEZY]?)$', bytestr) if matchobj is None: return None number = float(matchobj.group(1)) multiplier = 1024.0 ** 'bkmgtpezy'.index(matchobj.group(2).lower()) return long(round(number * multiplier)) @staticmethod def verify_url(url): """Verify a URL is valid and data could be downloaded. Return real data URL.""" request = urllib2.Request(url, None, std_headers) data = urllib2.urlopen(request) data.read(1) url = data.geturl() data.close() return url def add_info_extractor(self, ie): """Add an InfoExtractor object to the end of the list.""" self._ies.append(ie) ie.set_downloader(self) def add_post_processor(self, pp): """Add a PostProcessor object to the end of the chain.""" self._pps.append(pp) pp.set_downloader(self) def to_stdout(self, message, skip_eol=False): """Print message to stdout if not in quiet mode.""" if not self.params.get('quiet', False): print (u'%s%s' % (message, [u'\n', u''][skip_eol])).encode(preferredencoding()), sys.stdout.flush() def to_stderr(self, message): """Print message to stderr.""" print >>sys.stderr, message.encode(preferredencoding()) def fixed_template(self): """Checks if the output template is fixed.""" return (re.search(ur'(?u)%\(.+?\)s', self.params['outtmpl']) is None) def trouble(self, message=None): """Determine action to take when a download problem appears. Depending on if the downloader has been configured to ignore download errors or not, this method may throw an exception or not when errors are found, after printing the message. """ if message is not None: self.to_stderr(message) if not self.params.get('ignoreerrors', False): raise DownloadError(message) self._download_retcode = 1 def slow_down(self, start_time, byte_counter): """Sleep if the download speed is over the rate limit.""" rate_limit = self.params.get('ratelimit', None) if rate_limit is None or byte_counter == 0: return now = time.time() elapsed = now - start_time if elapsed <= 0.0: return speed = float(byte_counter) / elapsed if speed > rate_limit: time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit) def report_destination(self, filename): """Report destination filename.""" self.to_stdout(u'[download] Destination: %s' % filename) def report_progress(self, percent_str, data_len_str, speed_str, eta_str): """Report download progress.""" self.to_stdout(u'\r[download] %s of %s at %s ETA %s' % (percent_str, data_len_str, speed_str, eta_str), skip_eol=True) def report_resuming_byte(self, resume_len): """Report attemtp to resume at given byte.""" self.to_stdout(u'[download] Resuming download at byte %s' % resume_len) def report_file_already_downloaded(self, file_name): """Report file has already been fully downloaded.""" self.to_stdout(u'[download] %s has already been downloaded' % file_name) def report_unable_to_resume(self): """Report it was impossible to resume download.""" self.to_stdout(u'[download] Unable to resume') def report_finish(self): """Report download finished.""" self.to_stdout(u'') def process_info(self, info_dict): """Process a single dictionary returned by an InfoExtractor.""" # Do nothing else if in simulate mode if self.params.get('simulate', False): try: info_dict['url'] = self.verify_url(info_dict['url']) except (OSError, IOError, urllib2.URLError, httplib.HTTPException, socket.error), err: raise UnavailableFormatError # Forced printings if self.params.get('forcetitle', False): print info_dict['title'].encode(preferredencoding()) if self.params.get('forceurl', False): print info_dict['url'].encode(preferredencoding()) return try: template_dict = dict(info_dict) template_dict['epoch'] = unicode(long(time.time())) filename = self.params['outtmpl'] % template_dict except (ValueError, KeyError), err: self.trouble('ERROR: invalid output template or system charset: %s' % str(err)) if self.params['nooverwrites'] and os.path.exists(filename): self.to_stderr(u'WARNING: file exists: %s; skipping' % filename) return try: self.pmkdir(filename) except (OSError, IOError), err: self.trouble('ERROR: unable to create directories: %s' % str(err)) return try: success = self._do_download(filename, info_dict['url']) except (OSError, IOError), err: raise UnavailableFormatError except (urllib2.URLError, httplib.HTTPException, socket.error), err: self.trouble('ERROR: unable to download video data: %s' % str(err)) return except (ContentTooShortError, ), err: self.trouble('ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded)) return if success: try: self.post_process(filename, info_dict) except (PostProcessingError), err: self.trouble('ERROR: postprocessing: %s' % str(err)) return def download(self, url_list): """Download a given list of URLs.""" if len(url_list) > 1 and self.fixed_template(): raise SameFileError(self.params['outtmpl']) for url in url_list: suitable_found = False for ie in self._ies: # Go to next InfoExtractor if not suitable if not ie.suitable(url): continue # Suitable InfoExtractor found suitable_found = True # Extract information from URL and process it ie.extract(url) # Suitable InfoExtractor had been found; go to next URL break if not suitable_found: self.trouble('ERROR: no suitable InfoExtractor: %s' % url) return self._download_retcode def get_real_urls(self, url_list): """Download a given list of URLs.""" if len(url_list) > 1 and self.fixed_template(): raise SameFileError(self._params['outtmpl']) for url in url_list: suitable_found = False for ie in self._ies: if not ie.suitable(url): continue # Suitable InfoExtractor found suitable_found = True def got_all_results(all_results): results = [x for x in all_results if x is not None] if len(results) != len(all_results): retcode = self.trouble() if len(results) > 1 and self.fixed_template(): raise SameFileError(self._params['outtmpl']) real_urls = [] for result in results: real_urls.append(result['url']) return real_urls d = ie.extract(url) d.addCallback(got_all_results) return d return [] def post_process(self, filename, ie_info): """Run the postprocessing chain on the given file.""" info = dict(ie_info) info['filepath'] = filename for pp in self._pps: info = pp.run(info) if info is None: break # _do_download REMOVED class InfoExtractor(object): """Information Extractor class. Information extractors are the classes that, given a URL, extract information from the video (or videos) the URL refers to. This information includes the real video URL, the video title and simplified title, author and others. The information is stored in a dictionary which is then passed to the FileDownloader. The FileDownloader processes this information possibly downloading the video to the file system, among other possible outcomes. The dictionaries must include the following fields: id: Video identifier. url: Final video URL. uploader: Nickname of the video uploader. title: Literal title. stitle: Simplified title. ext: Video filename extension. Subclasses of this one should re-define the _real_initialize() and _real_extract() methods, as well as the suitable() static method. Probably, they should also be instantiated and added to the main downloader. """ _ready = False _downloader = None def __init__(self, downloader=None): """Constructor. Receives an optional downloader.""" self._ready = False self.set_downloader(downloader) @staticmethod def suitable(url): """Receives a URL and returns True if suitable for this IE.""" return False def initialize(self): """Initializes an instance (authentication, etc).""" if not self._ready: self._real_initialize() self._ready = True def extract(self, url): """Extracts URL information and returns it in list of dicts.""" self.initialize() return self._real_extract(url) def set_downloader(self, downloader): """Sets the downloader for this IE.""" self._downloader = downloader def to_stdout(self, message): """Print message to stdout if downloader is not in quiet mode.""" if self._downloader is None or not self._downloader.get_params().get('quiet', False): print message def to_stderr(self, message): """Print message to stderr.""" print >>sys.stderr, message def _real_initialize(self): """Real initialization process. Redefine in subclasses.""" pass def _real_extract(self, url): """Real extraction process. Redefine in subclasses.""" pass class YoutubeIE(InfoExtractor): """Information extractor for youtube.com.""" _VALID_URL = r'^((?:http://)?(?:\w+\.)?youtube\.com/(?:(?:v/)|(?:(?:watch(?:\.php)?)?\?(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$' _LANG_URL = r'http://uk.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1' _LOGIN_URL = 'http://www.youtube.com/signup?next=/&gl=US&hl=en' _AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en' _NETRC_MACHINE = 'youtube' _available_formats = ['22', '35', '18', '5', '17', '13', None] # listed in order of priority for -b flag _video_extensions = { '13': '3gp', '17': 'mp4', '18': 'mp4', '22': 'mp4', } @staticmethod def suitable(url): return (re.match(YoutubeIE._VALID_URL, url) is not None) @staticmethod def htmlentity_transform(matchobj): """Transforms an HTML entity to a Unicode character.""" entity = matchobj.group(1) # Known non-numeric HTML entity if entity in htmlentitydefs.name2codepoint: return unichr(htmlentitydefs.name2codepoint[entity]) # Unicode character mobj = re.match(ur'(?u)#(x?\d+)', entity) if mobj is not None: numstr = mobj.group(1) if numstr.startswith(u'x'): base = 16 numstr = u'0%s' % numstr else: base = 10 return unichr(long(numstr, base)) # Unknown entity in name, return its literal representation return (u'&%s;' % entity) def report_lang(self): """Report attempt to set language.""" self._downloader.to_stdout(u'[youtube] Setting language') def report_login(self): """Report attempt to log in.""" self._downloader.to_stdout(u'[youtube] Logging in') def report_age_confirmation(self): """Report attempt to confirm age.""" self._downloader.to_stdout(u'[youtube] Confirming age') def report_video_info_webpage_download(self, video_id): """Report attempt to download video info webpage.""" self._downloader.to_stdout(u'[youtube] %s: Downloading video info webpage' % video_id) def report_information_extraction(self, video_id): """Report attempt to extract video information.""" self._downloader.to_stdout(u'[youtube] %s: Extracting video information' % video_id) def report_unavailable_format(self, video_id, format): """Report extracted video URL.""" self._downloader.to_stdout(u'[youtube] %s: Format %s not available' % (video_id, format)) def report_video_url(self, video_id, video_real_url): """Report extracted video URL.""" self._downloader.to_stdout(u'[youtube] %s: URL: %s' % (video_id, video_real_url)) def _real_initialize(self): if self._downloader is None: return username = None password = None downloader_params = self._downloader.params # Attempt to use provided username and password or .netrc data if downloader_params.get('username', None) is not None: username = downloader_params['username'] password = downloader_params['password'] elif downloader_params.get('usenetrc', False): try: info = netrc.netrc().authenticators(self._NETRC_MACHINE) if info is not None: username = info[0] password = info[2] else: raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE) except (IOError, netrc.NetrcParseError), err: self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err)) return def gotAgeConfirmedPage(result): print "Age confirmed in Youtube" def gotLoggedInPage(result): data,headers = result if re.search(r'(?i)]* name="loginForm"', data) is not None: print 'WARNING: unable to log in: bad username or password' return print "logged in in Youtube" # Confirm age age_form = { 'next_url': '/', 'action_confirm': 'Confirm', } postdata = urlencode(age_form) d = getPage(self._AGE_URL, postdata=postdata, headers=std_headers) d.addCallback(gotAgeConfirmedPage) def gotLoginError(error): print "Unable to login to Youtube : %s:%s @ %s" % (username, password, self._LOGIN_URL) print "Error: %s" % error return def gotLanguageSet(result): data,headers = result # No authentication to be performed if username is None: return # Log in login_form = { 'current_form': 'loginForm', 'next': '/', 'action_login': 'Log In', 'username': username, 'password': password, } postdata = urlencode(login_form) d = getPage(self._LOGIN_URL, method='POST', postdata=postdata, headers=std_headers) d.addCallbacks(gotLoggedInPage, gotLoginError) def gotLanguageSetError(error): print "Unable to process Youtube request: %s" % self._LANG_URL print "Error: %s" % error return # Set language (will lead to log in, and then age confirmation) d = getPage(self._LANG_URL, headers=std_headers) d.addCallbacks(gotLanguageSet, gotLanguageSetError) def _real_extract(self, url): # Extract video id from URL mobj = re.match(self._VALID_URL, url) if mobj is None: self._downloader.trouble(u'ERROR: invalid URL: %s' % url) return video_id = mobj.group(2) # Downloader parameters best_quality = False format_param = None video_extension = None quality_index = 0 if self._downloader is not None: params = self._downloader.params format_param = params.get('format', None) if format_param == '0': format_param = self._available_formats[quality_index] best_quality = True video_extension = self._video_extensions.get(format_param, 'flv') # video info video_info_url = 'http://www.youtube.com/get_video_info?&video_id=%s&el=detailpage&ps=default&eurl=&gl=US&hl=en' % video_id if format_param is not None: video_info_url = '%s&fmt=%s' % (video_info_url, format_param) def gotPage(result, format_param, video_extension): video_info_webpage,headers = result # check format if (format_param == '22'): print "Check if HD video exists..." mobj = re.search(r'var isHDAvailable = true;', video_info_webpage) if mobj is None: print "No HD video -> switch back to SD" format_param = '18' else: print "...HD video OK!" # "t" param mobj = re.search(r'(?m)&token=([^&]+)(?:&|$)', video_info_webpage) if mobj is None: # Attempt to see if YouTube has issued an error message mobj = re.search(r'(?m)&reason=([^&]+)(?:&|$)', video_info_webpage) if mobj is None: self.to_stderr(u'ERROR: unable to extract "t" parameter') print video_info_webpage return [None] else: reason = unquote_plus(mobj.group(1)) self.to_stderr(u'ERROR: YouTube said: %s' % reason.decode('utf-8')) token = unquote(mobj.group(1)) video_real_url = 'http://www.youtube.com/get_video?video_id=%s&t=%s&eurl=&el=detailpage&ps=default&gl=US&hl=en' % (video_id, token) if format_param is not None: video_real_url = '%s&fmt=%s' % (video_real_url, format_param) # uploader mobj = re.search(r'(?m)&author=([^&]+)(?:&|$)', video_info_webpage) if mobj is None: self._downloader.trouble(u'ERROR: unable to extract uploader nickname') return video_uploader = unquote(mobj.group(1)) # title mobj = re.search(r'(?m)&title=([^&]+)(?:&|$)', video_info_webpage) if mobj is None: self._downloader.trouble(u'ERROR: unable to extract video title') return video_title = unquote(mobj.group(1)) video_title = video_title.decode('utf-8') video_title = re.sub(ur'(?u)&(.+?);', self.htmlentity_transform, video_title) video_title = video_title.replace(os.sep, u'%') # simplified title simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title) simple_title = simple_title.strip(ur'_') # Return information return [{ 'id': video_id.decode('utf-8'), 'url': video_real_url.decode('utf-8'), 'uploader': video_uploader.decode('utf-8'), 'title': video_title, 'stitle': simple_title, 'ext': video_extension.decode('utf-8'), }] def gotError(error): print "Unable to process Youtube request: %s" % url print "Error: %s" % error return [None] d = getPage(video_info_url, headers=std_headers) d.addCallback(gotPage, format_param, video_extension) d.addErrback(gotError) return d class MetacafeIE(InfoExtractor): """Information Extractor for metacafe.com.""" _VALID_URL = r'(?:http://)?(?:www\.)?metacafe\.com/watch/([^/]+)/([^/]+)/.*' _DISCLAIMER = 'http://www.metacafe.com/family_filter/' _FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user' _youtube_ie = None def __init__(self, youtube_ie, downloader=None): InfoExtractor.__init__(self, downloader) self._youtube_ie = youtube_ie @staticmethod def suitable(url): return (re.match(MetacafeIE._VALID_URL, url) is not None) def report_disclaimer(self): """Report disclaimer retrieval.""" self._downloader.to_stdout(u'[metacafe] Retrieving disclaimer') def report_age_confirmation(self): """Report attempt to confirm age.""" self._downloader.to_stdout(u'[metacafe] Confirming age') def report_download_webpage(self, video_id): """Report webpage download.""" self._downloader.to_stdout(u'[metacafe] %s: Downloading webpage' % video_id) def report_extraction(self, video_id): """Report information extraction.""" self._downloader.to_stdout(u'[metacafe] %s: Extracting information' % video_id) def _real_initialize(self): # Retrieve disclaimer request = urllib2.Request(self._DISCLAIMER, None, std_headers) try: self.report_disclaimer() disclaimer = urllib2.urlopen(request).read() except (urllib2.URLError, httplib.HTTPException, socket.error), err: self._downloader.trouble(u'ERROR: unable to retrieve disclaimer: %s' % str(err)) return # Confirm age disclaimer_form = { 'filters': '0', 'submit': "Continue - I'm over 18", } request = urllib2.Request(self._FILTER_POST, urllib.urlencode(disclaimer_form), std_headers) try: self.report_age_confirmation() disclaimer = urllib2.urlopen(request).read() except (urllib2.URLError, httplib.HTTPException, socket.error), err: self._downloader.trouble(u'ERROR: unable to confirm age: %s' % str(err)) return def _real_extract(self, url): # Extract id and simplified title from URL mobj = re.match(self._VALID_URL, url) if mobj is None: self._downloader.trouble(u'ERROR: invalid URL: %s' % url) return video_id = mobj.group(1) # Check if video comes from YouTube mobj2 = re.match(r'^yt-(.*)$', video_id) if mobj2 is not None: self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % mobj2.group(1)) return simple_title = mobj.group(2).decode('utf-8') video_extension = 'flv' # Retrieve video webpage to extract further information request = urllib2.Request('http://www.metacafe.com/watch/%s/' % video_id) try: self.report_download_webpage(video_id) webpage = urllib2.urlopen(request).read() except (urllib2.URLError, httplib.HTTPException, socket.error), err: self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % str(err)) return # Extract URL, uploader and title from webpage self.report_extraction(video_id) mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage) if mobj is None: self._downloader.trouble(u'ERROR: unable to extract media URL') return mediaURL = urllib.unquote(mobj.group(1)) #mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage) #if mobj is None: # self._downloader.trouble(u'ERROR: unable to extract gdaKey') # return #gdaKey = mobj.group(1) # #video_url = '%s?__gda__=%s' % (mediaURL, gdaKey) video_url = mediaURL mobj = re.search(r'(?im)(.*) - Video', webpage) if mobj is None: self._downloader.trouble(u'ERROR: unable to extract title') return video_title = mobj.group(1).decode('utf-8') mobj = re.search(r'(?ms)

  • .*?Submitter:.*?(.*?)<', webpage) if mobj is None: self._downloader.trouble(u'ERROR: unable to extract uploader nickname') return video_uploader = mobj.group(1) try: # Process video information self._downloader.process_info({ 'id': video_id.decode('utf-8'), 'url': video_url.decode('utf-8'), 'uploader': video_uploader.decode('utf-8'), 'title': video_title, 'stitle': simple_title, 'ext': video_extension.decode('utf-8'), }) except UnavailableFormatError: self._downloader.trouble(u'ERROR: format not available for video') class YoutubeSearchIE(InfoExtractor): """Information Extractor for YouTube search queries.""" _VALID_QUERY = r'ytsearch(\d+|all)?:[\s\S]+' _TEMPLATE_URL = 'http://www.youtube.com/results?search_query=%s&page=%s&gl=US&hl=en' _VIDEO_INDICATOR = r'href="/watch\?v=.+?"' _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*' _youtube_ie = None _max_youtube_results = 1000 def __init__(self, youtube_ie, downloader=None): InfoExtractor.__init__(self, downloader) self._youtube_ie = youtube_ie @staticmethod def suitable(url): return (re.match(YoutubeSearchIE._VALID_QUERY, url) is not None) def report_download_page(self, query, pagenum): """Report attempt to download playlist page with given number.""" self._downloader.to_stdout(u'[youtube] query "%s": Downloading page %s' % (query, pagenum)) def _real_initialize(self): self._youtube_ie.initialize() def _real_extract(self, query): mobj = re.match(self._VALID_QUERY, query) if mobj is None: self._downloader.trouble(u'ERROR: invalid search query "%s"' % query) return prefix, query = query.split(':') prefix = prefix[8:] if prefix == '': self._download_n_results(query, 1) return elif prefix == 'all': self._download_n_results(query, self._max_youtube_results) return else: try: n = long(prefix) if n <= 0: self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query)) return elif n > self._max_youtube_results: self._downloader.to_stderr(u'WARNING: ytsearch returns max %i results (you requested %i)' % (self._max_youtube_results, n)) n = self._max_youtube_results self._download_n_results(query, n) return except ValueError: # parsing prefix as integer fails self._download_n_results(query, 1) return def _download_n_results(self, query, n): """Downloads a specified number of results for a query""" video_ids = [] already_seen = set() pagenum = 1 while True: self.report_download_page(query, pagenum) result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum) request = urllib2.Request(result_url, None, std_headers) try: page = urllib2.urlopen(request).read() except (urllib2.URLError, httplib.HTTPException, socket.error), err: self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) return # Extract video identifiers for mobj in re.finditer(self._VIDEO_INDICATOR, page): video_id = page[mobj.span()[0]:mobj.span()[1]].split('=')[2][:-1] if video_id not in already_seen: video_ids.append(video_id) already_seen.add(video_id) if len(video_ids) == n: # Specified n videos reached for id in video_ids: self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id) return if re.search(self._MORE_PAGES_INDICATOR, page) is None: for id in video_ids: self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id) return pagenum = pagenum + 1 class YoutubePlaylistIE(InfoExtractor): """Information Extractor for YouTube playlists.""" _VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/(?:view_play_list|my_playlists)\?.*?p=([^&]+).*' _TEMPLATE_URL = 'http://www.youtube.com/view_play_list?p=%s&page=%s&gl=US&hl=en' _VIDEO_INDICATOR = r'/watch\?v=(.+?)&' _MORE_PAGES_INDICATOR = r'/view_play_list?p=%s&page=%s' _youtube_ie = None def __init__(self, youtube_ie, downloader=None): InfoExtractor.__init__(self, downloader) self._youtube_ie = youtube_ie @staticmethod def suitable(url): return (re.match(YoutubePlaylistIE._VALID_URL, url) is not None) def report_download_page(self, playlist_id, pagenum): """Report attempt to download playlist page with given number.""" self.to_stdout(u'[youtube] PL %s: Downloading page #%s' % (playlist_id, pagenum)) def _real_initialize(self): self._youtube_ie.initialize() def _real_extract(self, url): # Extract playlist id mobj = re.match(self._VALID_URL, url) if mobj is None: self._downloader.trouble(u'ERROR: invalid url: %s' % url) return # Download playlist pages playlist_id = mobj.group(1) video_ids = [] pagenum = 1 while True: self.report_download_page(playlist_id, pagenum) request = urllib2.Request(self._TEMPLATE_URL % (playlist_id, pagenum), None, std_headers) try: page = urllib2.urlopen(request).read() except (urllib2.URLError, httplib.HTTPException, socket.error), err: self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) return # Extract video identifiers ids_in_page = [] for mobj in re.finditer(self._VIDEO_INDICATOR, page): if mobj.group(1) not in ids_in_page: ids_in_page.append(mobj.group(1)) video_ids.extend(ids_in_page) if (self._MORE_PAGES_INDICATOR % (playlist_id.upper(), pagenum + 1)) not in page: break pagenum = pagenum + 1 for id in video_ids: self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id) return class PostProcessor(object): """Post Processor class. PostProcessor objects can be added to downloaders with their add_post_processor() method. When the downloader has finished a successful download, it will take its internal chain of PostProcessors and start calling the run() method on each one of them, first with an initial argument and then with the returned value of the previous PostProcessor. The chain will be stopped if one of them ever returns None or the end of the chain is reached. PostProcessor objects follow a "mutual registration" process similar to InfoExtractor objects. """ _downloader = None def __init__(self, downloader=None): self._downloader = downloader def to_stdout(self, message): """Print message to stdout if downloader is not in quiet mode.""" if self._downloader is None or not self._downloader.get_params().get('quiet', False): print message def to_stderr(self, message): """Print message to stderr.""" print >>sys.stderr, message def set_downloader(self, downloader): """Sets the downloader for this PP.""" self._downloader = downloader def run(self, information): """Run the PostProcessor. The "information" argument is a dictionary like the ones composed by InfoExtractors. The only difference is that this one has an extra field called "filepath" that points to the downloaded file. When this method returns None, the postprocessing chain is stopped. However, this method may return an information dictionary that will be passed to the next postprocessing object in the chain. It can be the one it received after changing some fields. In addition, this method may raise a PostProcessingError exception that will be taken into account by the downloader it was called from. """ return information # by default, do nothing ### MAIN PROGRAM ### if __name__ == '__main__': try: # Modules needed only when running the main program import getpass import optparse # General configuration urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())) urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor())) socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words) # Parse command line parser = optparse.OptionParser( usage='Usage: %prog [options] url...', version='2009.09.13', conflict_handler='resolve', ) parser.add_option('-h', '--help', action='help', help='print this help text and exit') parser.add_option('-v', '--version', action='version', help='print program version and exit') parser.add_option('-i', '--ignore-errors', action='store_true', dest='ignoreerrors', help='continue on download errors', default=False) parser.add_option('-r', '--rate-limit', dest='ratelimit', metavar='L', help='download rate limit (e.g. 50k or 44.6m)') authentication = optparse.OptionGroup(parser, 'Authentication Options') authentication.add_option('-u', '--username', dest='username', metavar='UN', help='account username') authentication.add_option('-p', '--password', dest='password', metavar='PW', help='account password') authentication.add_option('-n', '--netrc', action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False) parser.add_option_group(authentication) video_format = optparse.OptionGroup(parser, 'Video Format Options') video_format.add_option('-f', '--format', action='store', dest='format', metavar='FMT', help='video format code') video_format.add_option('-b', '--best-quality', action='store_const', dest='format', help='download the best quality video possible', const='0') video_format.add_option('-m', '--mobile-version', action='store_const', dest='format', help='alias for -f 17', const='17') video_format.add_option('-d', '--high-def', action='store_const', dest='format', help='alias for -f 22', const='22') parser.add_option_group(video_format) verbosity = optparse.OptionGroup(parser, 'Verbosity / Simulation Options') verbosity.add_option('-q', '--quiet', action='store_true', dest='quiet', help='activates quiet mode', default=False) verbosity.add_option('-s', '--simulate', action='store_true', dest='simulate', help='do not download video', default=False) verbosity.add_option('-g', '--get-url', action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False) verbosity.add_option('-e', '--get-title', action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False) parser.add_option_group(verbosity) filesystem = optparse.OptionGroup(parser, 'Filesystem Options') filesystem.add_option('-t', '--title', action='store_true', dest='usetitle', help='use title in file name', default=False) filesystem.add_option('-l', '--literal', action='store_true', dest='useliteral', help='use literal title in file name', default=False) filesystem.add_option('-o', '--output', dest='outtmpl', metavar='TPL', help='output filename template') filesystem.add_option('-a', '--batch-file', dest='batchfile', metavar='F', help='file containing URLs to download') filesystem.add_option('-w', '--no-overwrites', action='store_true', dest='nooverwrites', help='do not overwrite files', default=False) filesystem.add_option('-c', '--continue', action='store_true', dest='continue_dl', help='resume partially downloaded files', default=False) parser.add_option_group(filesystem) (opts, args) = parser.parse_args() # Batch file verification batchurls = [] if opts.batchfile is not None: try: batchurls = open(opts.batchfile, 'r').readlines() batchurls = [x.strip() for x in batchurls] batchurls = [x for x in batchurls if len(x) > 0] except IOError: sys.exit(u'ERROR: batch file could not be read') all_urls = batchurls + args # Conflicting, missing and erroneous options if len(all_urls) < 1: parser.error(u'you must provide at least one URL') if opts.usenetrc and (opts.username is not None or opts.password is not None): parser.error(u'using .netrc conflicts with giving username/password') if opts.password is not None and opts.username is None: parser.error(u'account username missing') if opts.outtmpl is not None and (opts.useliteral or opts.usetitle): parser.error(u'using output template conflicts with using title or literal title') if opts.usetitle and opts.useliteral: parser.error(u'using title conflicts with using literal title') if opts.username is not None and opts.password is None: opts.password = getpass.getpass(u'Type account password and press return:') if opts.ratelimit is not None: numeric_limit = FileDownloader.parse_bytes(opts.ratelimit) if numeric_limit is None: parser.error(u'invalid rate limit specified') opts.ratelimit = numeric_limit # Information extractors youtube_ie = YoutubeIE() metacafe_ie = MetacafeIE(youtube_ie) youtube_pl_ie = YoutubePlaylistIE(youtube_ie) youtube_search_ie = YoutubeSearchIE(youtube_ie) # File downloader fd = FileDownloader({ 'usenetrc': opts.usenetrc, 'username': opts.username, 'password': opts.password, 'quiet': (opts.quiet or opts.geturl or opts.gettitle), 'forceurl': opts.geturl, 'forcetitle': opts.gettitle, 'simulate': (opts.simulate or opts.geturl or opts.gettitle), 'format': opts.format, 'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding())) or (opts.usetitle and u'%(stitle)s-%(id)s.%(ext)s') or (opts.useliteral and u'%(title)s-%(id)s.%(ext)s') or u'%(id)s.%(ext)s'), 'ignoreerrors': opts.ignoreerrors, 'ratelimit': opts.ratelimit, 'nooverwrites': opts.nooverwrites, 'continuedl': opts.continue_dl, }) fd.add_info_extractor(youtube_search_ie) fd.add_info_extractor(youtube_pl_ie) fd.add_info_extractor(metacafe_ie) fd.add_info_extractor(youtube_ie) retcode = fd.download(all_urls) sys.exit(retcode) except DownloadError: sys.exit(1) except SameFileError: sys.exit(u'ERROR: fixed output name but more than one file to download') except KeyboardInterrupt: sys.exit(u'\nERROR: Interrupted by user') Coherence-0.6.6.2/coherence/extern/youtubedl/__init__.py0000644000175000017500000000021611317660736021272 0ustar devdevfrom youtubedl import FileDownloader from youtubedl import YoutubeIE from youtubedl import MetacafeIE from youtubedl import YoutubePlaylistIE Coherence-0.6.6.2/coherence/extern/config.py0000644000175000017500000001174611317660736016776 0ustar devdev# # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007,2008 Frank Scholz from coherence.extern.et import ET, indent class ConfigMixin(object): def nodes_to_dict(self,node): if node.tag.endswith('list'): return ConfigList(node) else: return ConfigDict(node) class ConfigList(list,ConfigMixin): def __init__(self,node): self.name = node.tag list.__init__(self) self.from_element(node) def to_element(self): root = ET.Element(self.name) for v in self: if isinstance(v, (dict,list)): root.append(v.to_element()) return root def from_element(self,node): for n in node: if n.get('active','yes') == 'yes': if len(n) == 0: a = {} for attr,value in n.items(): if attr == 'active': continue a[attr] = value if len(a): self.append(a) else: self.append(self.nodes_to_dict(n)) class ConfigDict(dict,ConfigMixin): def __init__(self,node): self.name = node.tag dict.__init__(self) self.from_element(node) def to_element(self): root = ET.Element(self.name) for key, value in self.items(): if isinstance(value, (dict,list)): root.append(value.to_element()) else: s = ET.SubElement(root,key) if isinstance(value, basestring): s.text = value else: s.text = str(value) return root def from_element(self, node): for attr,value in node.items(): if attr == 'active': continue self[attr] = value for n in node: if n.get('active','yes') == 'yes': if len(n) == 0: if n.text is not None and len(n.text)>0: self[n.get('name',n.tag)] = n.text for attr,value in n.items(): if attr == 'active': continue self[attr] = value else: tag = n.tag #if tag.endswith('list'): # tag = tag[:-4] self[n.get('name',tag)] = self.nodes_to_dict(n) #def __setitem__(self, key, value): # self.config[key] = value #def __getitem__(self, key): # """ fetch an item """ # value = self.config.get(key, None) # return value #def __delitem__(self, key): # del self.config[key] #def get(self, key, default=None): # try: # return self[key] # except KeyError: # return default #def items(self): # """ """ # return self.config.items() #def keys(self): # """ """ # return self.config.keys() #def values(self): # """ """ # return self.config.values() #def __repr__(self): # return "%r" % self.config class Config(ConfigDict): """ an incomplete XML file to dict and vice versa mapper - nodes with an attribute 'active' set to 'no' are ignored and not transferred into the dict - nodes with tags ending with 'list' are transferrend into an item with the key = 'tag' and a list with the subnodes as the value at the moment we parse the xml file and create dicts or lists out of the nodes, but maybe it is much easier to keep the xml structure as it is and simulate the dict/list access behavior on it? """ def __init__(self, file): self.file = file dict.__init__(self) try: xml = ET.parse(file) except SyntaxError, msg: raise SyntaxError, msg except IOError, msg: raise IOError, msg except Exception, msg: raise SyntaxError, msg xmlroot = xml.getroot() self.name = xmlroot.tag self.from_element(xmlroot) def save(self,file=None): if file == None: file = self.file e = ET.Element(self.name) for key, value in self.items(): if isinstance(value, (dict,list)): e.append(value.to_element()) else: s = ET.SubElement(e,key) if isinstance(value, basestring): s.text = value else: s.text = str(value) indent(e) db = ET.ElementTree(e) db.write(file, encoding='utf-8') if __name__ == '__main__': import sys config = Config(sys.argv[1]) print config config['serverport'] = 55555 config['test'] = 'test' config['logging']['level'] = 'info' del config['controlpoint'] #del config['logging']['level'] print config config.save('/tmp/t')Coherence-0.6.6.2/coherence/extern/galleryremote/0000755000000000000000000000000011317673117020376 5ustar rootrootCoherence-0.6.6.2/coherence/extern/galleryremote/__init__.py0000644000175000017500000000170411317660736022134 0ustar devdev# See galleryremote.Gallery for complete documentation # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Written by Brent Woodruff # and John Sutherland # Adapted by Jean-Michel Sizun from gallery import Gallery Coherence-0.6.6.2/coherence/extern/galleryremote/gallery.py0000644000175000017500000004356511317660736022047 0ustar devdev# Copyright (C) 2008 Jean-Michel Sizun # # Copyright (C) 2008 Brent Woodruff # http://www.fprimex.com # # Copyright (C) 2004 John Sutherland # http://garion.tzo.com/python/ # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # Change log: # # 16-Nov-08 - Migrated from urllib to coherence infrastructure # # 04-Aug-08 - Added Gallery2 compatibility # Changed fetch_albums and fetch_albums_prune to return dicts # Added docstrings # Created package and registered with Pypi # # 09-Jun-04 - Removed self.cookie='' from _doRequest to allow multiple # transactions for each login. # Fixed cut paste error in newAlbum. # (both patches from Yuti Takhteyev from coherence.upnp.core.utils import getPage import StringIO import string class Gallery: """ The Gallery class implements the Gallery Remote protocol as documented here: http://codex.gallery2.org/Gallery_Remote:Protocol The Gallery project is an open source web based photo album organizer written in php. Gallery's web site is: http://gallery.menalto.com/ This class is a 3rd party product which is not maintained by the creators of the Gallery project. Example usage: from galleryremote import Gallery my_gallery = Gallery('http://www.yoursite.com/gallery2', 2) my_gallery.login('username','password') albums = my_gallery.fetch_albums() """ def __init__(self, url, version=2): """ Create a Gallery for remote access. url - base address of the gallery version - version of the gallery being connected to (default 2), either 1 for Gallery1 or 2 for Gallery2 """ self.version = version # Gallery1 or Gallery2 if version == 1: self.url = url + '/gallery_remote2.php' else: # default to G2 self.url = url + '/main.php' self.auth_token = None self.logged_in = 0 self.cookie = '' self.protocol_version = '2.5' def _do_request(self, request): """ Send a request, encoded as described in the Gallery Remote protocol. request - a dictionary of protocol parameters and values """ if self.auth_token != None: request['g2_authToken'] = self.auth_token url = self.url if (len(request) > 0) : url += '?' for key,value in request.iteritems(): url += '%s=%s&' % (key,value) headers = None if self.cookie != '': headers = {'Cookie' : self.cookie} def gotPage(result): data,headers = result response = self._parse_response( data ) if response['status'] != '0': raise response['status_text'] try: self.auth_token = response['auth_token'] except: pass if headers.has_key('set-cookie'): cookie_info = headers['set-cookie'][-1] self.cookie = cookie_info.split(';')[0] return response def gotError(error): print "Unable to process Gallery2 request: %s" % url print "Error: %s" % error return None d = getPage(url, headers=headers) d.addCallback(gotPage) d.addErrback(gotError) return d def _parse_response(self, response): """ Decode the response from a request, returning a request dict response - The response from a gallery request, encoded according to the gallery remote protocol """ myStr = StringIO.StringIO(response) for line in myStr: if string.find( line, '#__GR2PROTO__' ) != -1: break # make sure the 1st line is #__GR2PROTO__ if string.find( line, '#__GR2PROTO__' ) == -1: raise "Bad response: \r\n" + response resDict = {} for myS in myStr: myS = myS.strip() strList = string.split(myS, '=', 2) try: resDict[strList[0]] = strList[1] except: resDict[strList[0]] = '' return resDict def _get(self, response, kwd): """ """ try: retval = response[kwd] except: retval = '' return retval def login(self, username, password): """ Establish an authenticated session to the remote gallery. username - A valid gallery user's username password - That valid user's password """ if self.version == 1: request = { 'protocol_version': self.protocol_version, 'cmd': 'login', 'uname': username, 'password': password } else: request = { 'g2_controller' : 'remote:GalleryRemote', 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'login', 'g2_form[uname]': username, 'g2_form[password]': password } def gotPage(result): if result is None: print "Unable to login as %s to gallery2 server (%s)" % (username, self.url) return self.logged_in = 1 d = self._do_request(request) d.addCallbacks(gotPage) return d def fetch_albums(self): """ Obtain a dict of albums contained in the gallery keyed by album name. In Gallery1, the name is alphanumeric. In Gallery2, the name is the unique identifying number for that album. """ if self.version == 1: request = { 'protocol_version' : self.protocol_version, 'cmd' : 'fetch-albums' } else: request = { 'g2_controller' : 'remote:GalleryRemote', 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'fetch-albums' } d = self._do_request(request) def gotResponse(response): if response is None: print "Unable to retrieve list of albums!" return None albums = {} for x in range(1, int(response['album_count']) + 1): album = {} album['name'] = self._get(response,'album.name.' + str(x)) album['title'] = self._get(response,'album.title.' + str(x)) album['summary'] = self._get(response,'album.summary.' + str(x)) album['parent'] = self._get(response,'album.parent.' + str(x)) album['resize_size'] = self._get(response,'album.resize_size.' + str(x)) album['perms.add'] = self._get(response,'album.perms.add.' + str(x)) album['perms.write'] = self._get(response,'album.perms.write.' + str(x)) album['perms.del_item'] = self._get(response,'album.perms.del_item.' + str(x)) album['perms.del_alb'] = self._get(response,'album.perms.del_alb.' + str(x)) album['perms.create_sub'] = self._get(response,'album.perms.create_sub.' + str(x)) album['perms.info.extrafields'] = self._get(response,'album.info.extrafields' + str(x)) albums[album['name']] = album return albums d.addCallback(gotResponse) return d def fetch_albums_prune(self): """ Obtain a dict of albums contained in the gallery keyed by album name. In Gallery1, the name is alphanumeric. In Gallery2, the name is the unique identifying number for that album. From the protocol docs: "The fetch_albums_prune command asks the server to return a list of all albums that the user can either write to, or that are visible to the user and contain a sub-album that is writable (including sub-albums several times removed)." """ if self.version == 1: request = { 'protocol_version' : self.protocol_version, 'cmd' : 'fetch-albums-prune' } else: request = { 'g2_controller' : 'remote:GalleryRemote', 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'fetch-albums-prune' } response = self._do_request(request) def gotResponse(response): # as long as it comes back here without an exception, we're ok. albums = {} for x in range(1, int(response['album_count']) + 1): album = {} album['name'] = self._get(response,'album.name.' + str(x)) album['title'] = self._get(response,'album.title.' + str(x)) album['summary'] = self._get(response,'album.summary.' + str(x)) album['parent'] = self._get(response,'album.parent.' + str(x)) album['resize_size'] = self._get(response,'album.resize_size.' + str(x)) album['perms.add'] = self._get(response,'album.perms.add.' + str(x)) album['perms.write'] = self._get(response,'album.perms.write.' + str(x)) album['perms.del_item'] = self._get(response,'album.perms.del_item.' + str(x)) album['perms.del_alb'] = self._get(response,'album.perms.del_alb.' + str(x)) album['perms.create_sub'] = self._get(response,'album.perms.create_sub.' + str(x)) album['perms.info.extrafields'] = self._get(response,'album.info.extrafields' + str(x)) albums[album['name']] = album return albums d.addCallback(gotResponse) return d def add_item(self, album, filename, caption, description): """ Add a photo to the specified album. album - album name / identifier filename - image to upload caption - string caption to add to the image description - string description to add to the image """ if self.version == 1: request = { 'protocol_version' : self.protocol_version, 'cmd' : 'add-item', 'set_albumName' : album, 'userfile' : file, 'userfile_name' : filename, 'caption' : caption, 'extrafield.Description' : description } else: request = { 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'add-item', 'g2_form[set_albumName]' : album, 'g2_form[userfile]' : file, 'g2_form[userfile_name]' : filename, 'g2_form[caption]' : caption, 'g2_form[extrafield.Description]' : description } file = open(filename) d = self._do_request(request) # if we get here, everything went ok. return d def album_properties(self, album): """ Obtain album property information for the specified album. album - the album name / identifier to obtain information for """ if self.version == 1: request = { 'protocol_version' : self.protocol_version, 'cmd' : 'album-properties', 'set_albumName' : album } else: request = { 'g2_controller' : 'remote:GalleryRemote', 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'album-properties', 'g2_form[set_albumName]' : album } d = self._do_request(request) def gotResponse(response): res_dict = {} if response.has_key('auto_resize'): res_dict['auto_resize'] = response['auto_resize'] if response.has_key('add_to_beginning'): res_dict['add_to_beginning'] = response['add_to_beginning'] return res_dict d.addCallback(gotResponse) return d def new_album(self, parent, name=None, title=None, description=None): """ Add an album to the specified parent album. parent - album name / identifier to contain the new album name - unique string name of the new album title - string title of the album description - string description to add to the image """ if self.version == 1: request = { 'g2_controller' : 'remote:GalleryRemote', 'protocol_version' : self.protocol_version, 'cmd' : 'new-album', 'set_albumName' : parent } if name != None: request['newAlbumName'] = name if title != None: request['newAlbumTitle'] = title if description != None: request['newAlbumDesc'] = description else: request = { 'g2_controller' : 'remote:GalleryRemote', 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'new-album', 'g2_form[set_albumName]' : parent } if name != None: request['g2_form[newAlbumName]'] = name if title != None: request['g2_form[newAlbumTitle]'] = title if description != None: request['g2_form[newAlbumDesc]'] = description d = self._do_request(request) def gotResponse(response): return response['album_name'] d.addCallback(d) return d def fetch_album_images(self, album): """ Get the image information for all images in the specified album. album - specifies the album from which to obtain image information """ if self.version == 1: request = { 'protocol_version' : self.protocol_version, 'cmd' : 'fetch-album-images', 'set_albumName' : album, 'albums_too' : 'no', 'extrafields' : 'yes' } else: request = { 'g2_controller' : 'remote:GalleryRemote', 'g2_form[protocol_version]' : self.protocol_version, 'g2_form[cmd]' : 'fetch-album-images', 'g2_form[set_albumName]' : album, 'g2_form[albums_too]' : 'no', 'g2_form[extrafields]' : 'yes' } d = self._do_request(request) def gotResponse (response): if response is None: print "Unable to retrieve list of item for album %s." % album return None images = [] for x in range(1, int(response['image_count']) + 1): image = {} image['name'] = self._get(response, 'image.name.' + str(x)) image['title'] = self._get(response, 'image.title.' + str(x)) image['raw_width'] = self._get(response, 'image.raw_width.' + str(x)) image['raw_height'] = self._get(response, 'image.raw_height.' + str(x)) image['resizedName'] = self._get(response, 'image.resizedName.' + str(x)) image['resized_width'] = self._get(response, 'image.resized_width.' + str(x)) image['resized_height'] = self._get(response, 'image.resized_height.' + str(x)) image['thumbName'] = self._get(response, 'image.thumbName.' + str(x)) image['thumb_width'] = self._get(response, 'image.thumb_width.' + str(x)) image['thumb_height'] = self._get(response, 'image.thumb_height.' + str(x)) image['raw_filesize'] = self._get(response, 'image.raw_filesize.' + str(x)) image['caption'] = self._get(response, 'image.caption.' + str(x)) image['clicks'] = self._get(response, 'image.clicks.' + str(x)) image['capturedate.year'] = self._get(response, 'image.capturedate.year' + str(x)) image['capturedate.mon'] = self._get(response, 'image.capturedate.mon' + str(x)) image['capturedate.mday'] = self._get(response, 'image.capturedate.mday' + str(x)) image['capturedate.hours'] = self._get(response, 'image.capturedate.hours' + str(x)) image['capturedate.minutes'] = self._get(response, 'image.capturedate.minutes' + str(x)) image['capturedate.seconds'] = self._get(response, 'image.capturedate.seconds' + str(x)) image['description'] = self._get(response, 'image.extrafield.Description.' + str(x)) images.append(image) return images d.addCallback(gotResponse) return d def get_URL_for_image(self, gallery2_id): url = '%s/main.php?g2_view=core.DownloadItem&g2_itemId=%s' % (self.url, gallery2_id) return urlCoherence-0.6.6.2/coherence/extern/db_row.py0000644000175000017500000001074011317660736016776 0ustar devdev# Wraps DB-API 2.0 query results to provide a nice list and dictionary interface. # Copyright (C) 2002 Dr. Conan C. Albrecht # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # I created this class and related functions because I like accessing # database results by field name rather than field number. Accessing # by field number has many problems: code is less readable, code gets # broken when field positions change or fields are added or deleted from # the query, etc. # # This class should have little overhead if you are already using fetchall(). # It wraps each result row in a ResultRow class which allows you to # retrieve results via a dictionary interface (by column name). The regular # list interface (by column number) is also provided. # # I can't believe the DB-API 2.0 api didn't include dictionary-style results. # I'd love to see the reasoning behind not requiring them of database connection # classes. # This module comes from: # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/163605 def get_rows(cursor, sql): """Return a list of ResultRow objects from an SQL query.""" # run the query cursor.execute(sql) # return the list return getdict(cursor.fetchall(), cursor.description) def getdict(results, description): """Return the list of DBRows in `results` with a given description.""" # get the field names fields = {} for i in range(len(description)): fields[description[i][0]] = i # generate the list of DBRow objects rows = [] for result in results: rows.append(DBRow(result, fields)) # return to the user return rows class DBRow(object): """A single row in a result set. Each DBRow has a dictionary-style and list-style interface. """ def __init__(self, row, fields): """Called by ResultSet function. Don't call directly""" self.fields = fields self.row = row self._extra_fields = {} def __repr__(self): return "" % len(self) def __str__(self): """Return a string representation""" return str(self.row) def __getattr__(self, attr): return self.row[self.fields[attr]] def set_extra_attr(self, attr, value): self._extra_fields[attr] = value def __getitem__(self, key): """Return the value of the named column""" if type(key) == type(1): # if a number return self.row[key] else: # a field name return self.row[self.fields[key]] def __setitem__(self, key, value): """Not used in this implementation""" raise TypeError, "can't set an item of a result set" def __getslice__(self, i, j): """Return the value of the numbered column""" return self.row[i: j] def __setslice__(self, i, j, list): """Not used in this implementation""" raise TypeError, "can't set an item of a result set" def keys(self): """Return the field names""" return self.fields.keys() def keymappings(self): """Return a dictionary of the keys and their indices in the row""" return self.fields def has_key(self, key): """Return whether the given key is valid""" return self.fields.has_key(key) def as_dict(self): d = {} for field_name, pos in self.fields.iteritems(): d[field_name] = self.row[pos] for field_name, field in self._extra_fields.iteritems(): d[field_name] = field return d def __len__(self): """Return how many columns are in this row""" return len(self.row) def __nonzero__(self): return len(self.row) != 0 def __eq__(self, other): ## Error if other is not set if other == None: return False return self.fields == other.fields Coherence-0.6.6.2/coherence/extern/xdg.py0000644000175000017500000000237211317660736016306 0ustar devdev# -*- coding: utf-8 -*- # # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz from os import getenv import os.path hot_dirs = {'XDG_MUSIC_DIR':('audio','audio'), 'XDG_PICTURES_DIR':('image','images'), 'XDG_VIDEOS_DIR':('video','videos')} def xdg_content(): content = [] xdg_config_home = os.path.expanduser(getenv('XDG_CONFIG_HOME', '~/.config')) user_dirs_file = os.path.join(xdg_config_home, 'user-dirs.dirs') if os.path.exists(user_dirs_file): for line in open(user_dirs_file).readlines(): if not line.startswith('#'): line = line.strip() key,value = line.split('=') try: info = hot_dirs[key] value = value.strip('"') value = os.path.expandvars(value) #content.append((value.decode('utf8'),info[0],info[1])) content.append((value,info[0],info[1])) except KeyError: pass if len(content) > 0: return content return None if __name__ == '__main__': print xdg_content()Coherence-0.6.6.2/coherence/mirabeau.py0000644000175000017500000000456511317660741016006 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 Philippe Normand from coherence.dbus_constants import BUS_NAME, DEVICE_IFACE, SERVICE_IFACE from coherence.extern.telepathy.mirabeau_tube_publisher import MirabeauTubePublisherConsumer from coherence.tube_service import TubeDeviceProxy from coherence import log class Mirabeau(log.Loggable): logCategory = "mirabeau" def __init__(self, config, coherence_instance): log.Loggable.__init__(self) self._tube_proxies = [] self._coherence = coherence_instance chatroom = config['chatroom'] manager = config['manager'] protocol = config['protocol'] # account dict keys are different for each protocol so we # assume the user gave the right account parameters depending # on the specified protocol. account = config['account'] try: allowed_devices = config["allowed_devices"].split(",") except KeyError: allowed_devices = None tubes_to_offer = {BUS_NAME: {}, DEVICE_IFACE: {}, SERVICE_IFACE: {}} callbacks = dict(found_peer_callback=self.found_peer, disapeared_peer_callback=self.disapeared_peer, got_devices_callback=self.got_devices) self._tube_publisher = MirabeauTubePublisherConsumer(manager, protocol, account, chatroom, tubes_to_offer, self._coherence, allowed_devices, **callbacks) def found_peer(self, peer): print "found", peer def disapeared_peer(self, peer): print "disapeared", peer def got_devices(self, devices): external_address = self._coherence.external_address for device in devices: uuid = device.get_id() print "MIRABEAU found:", uuid self._tube_proxies.append(TubeDeviceProxy(self._coherence, device, external_address)) def start(self): self._tube_publisher.start() def stop(self): self._tube_publisher.stop() Coherence-0.6.6.2/coherence/backends/0000755000000000000000000000000011317673117015770 5ustar rootrootCoherence-0.6.6.2/coherence/backends/youtube_storage.py0000644000175000017500000006142411317660740021207 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009, Jean-Michel Sizun # Copyright 2009 Frank Scholz import os.path from twisted.internet import reactor, threads from twisted.web import server, static from twisted.web.error import PageRedirect from coherence.upnp.core import utils from coherence.upnp.core.utils import ReverseProxyUriResource, ReverseProxyResource from coherence.upnp.core import DIDLLite from coherence.backend import BackendStore,BackendItem from coherence import log from gdata.youtube.service import YouTubeService from coherence.extern.youtubedl import FileDownloader,YoutubeIE,MetacafeIE,YoutubePlaylistIE from coherence.backends.picasa_storage import Container, LazyContainer, AbstractBackendStore MPEG4_MIMETYPE = 'video/mp4' MPEG4_EXTENSION = 'mp4' class TestVideoProxy(ReverseProxyUriResource, log.Loggable): logCategory = 'internetVideoProxy' def __init__(self, uri, id, proxy_mode, cache_directory, cache_maxsize=100000000, buffer_size=2000000, fct=None, **kwargs): ReverseProxyUriResource.__init__(self, uri) self.id = id if isinstance(self.id,int): self.id = '%d' % self.id self.proxy_mode = proxy_mode self.cache_directory = cache_directory self.cache_maxsize = int(cache_maxsize) self.buffer_size = int(buffer_size) self.downloader = None self.video_url = None # the url we get from the youtube page self.stream_url = None # the real video stream, cached somewhere self.mimetype = None self.filesize = 0 self.file_in_cache = False self.url_extractor_fct = fct self.url_extractor_params = kwargs def requestFinished(self, result): """ self.connection is set in utils.ReverseProxyResource.render """ self.info("ProxyStream requestFinished:",result) if hasattr(self,'connection'): self.connection.transport.loseConnection() def render(self, request): self.info("VideoProxy render", request, self.stream_url, self.video_url) self.info("VideoProxy headers:", request.getAllHeaders()) self.info("VideoProxy id:", self.id) d = request.notifyFinish() d.addBoth(self.requestFinished) if self.stream_url is None: web_url = "http://%s%s" % (self.host,self.path) self.info("Web_url: %s" % web_url) def got_real_urls(real_urls): if len(real_urls) == 0: self.warning('Unable to retrieve any URL for video stream') return self.requestFinished(None) else: got_real_url(real_urls[0]) def got_real_url(real_url): self.info("Real URL is %s" % real_url) self.stream_url = real_url if self.stream_url is None: self.warning('Unable to retrieve URL - inconsistent web page') return self.requestFinished(None) #FIXME self.stream_url = self.stream_url.encode('ascii', 'strict') self.resetUri(self.stream_url) self.info("Video URL: %s" % self.stream_url) self.video_url = self.stream_url[:] d = self.followRedirects(request) d.addCallback(self.proxyURL) d.addErrback(self.requestFinished) if self.url_extractor_fct is not None: d = self.url_extractor_fct(web_url, **self.url_extractor_params) d.addCallback(got_real_urls) else: got_real_url(web_url) return server.NOT_DONE_YET reactor.callLater(0.05,self.proxyURL,request) return server.NOT_DONE_YET def followRedirects(self, request): self.info("HTTP redirect ", request, self.stream_url) d = utils.getPage(self.stream_url, method="HEAD", followRedirect=0) def gotHeader(result,request): data,header = result self.info("finally got something %r", header) #FIXME what do we do here if the headers aren't there? self.filesize = int(header['content-length'][0]) self.mimetype = header['content-type'][0] return request def gotError(error,request): # error should be a "Failure" instance at this point self.info("gotError" % error) error_value = error.value if (isinstance(error_value,PageRedirect)): self.info("got PageRedirect %r" % error_value.location) self.stream_url = error_value.location self.resetUri(self.stream_url) return self.followRedirects(request) else: self.warning("Error while retrieving page header for URI ", self.stream_url) self.requestFinished(None) return error d.addCallback(gotHeader, request) d.addErrback(gotError,request) return d def proxyURL(self, request): self.info("proxy_mode: %s, request %s" % (self.proxy_mode,request.method)) if self.proxy_mode == 'redirect': # send stream url to client for redirection request.redirect(self.stream_url) request.finish() elif self.proxy_mode in ('proxy',): res = ReverseProxyResource.render(self,request) if isinstance(res,int): return res request.write(res) return elif self.proxy_mode in ('buffer','buffered'): # download stream to cache, # and send it to the client in // after X bytes filepath = os.path.join(self.cache_directory, self.id) file_is_already_available = False if (os.path.exists(filepath) and os.path.getsize(filepath) == self.filesize): res = self.renderFile(request, filepath) if isinstance(res,int): return res request.write(res) request.finish() else: if request.method != 'HEAD': self.downloadFile(request, filepath, None) range = request.getHeader('range') if range is not None: bytesrange = range.split('=') assert bytesrange[0] == 'bytes',\ "Syntactically invalid http range header!" start, end = bytesrange[1].split('-', 1) #print "%r %r" %(start,end) if start: start = int(start) if end: end = int(end) else: end = self.filesize -1 # Are we requesting something beyond the current size of the file? try: size = os.path.getsize(filepath) except OSError: size = 0 if (start >= size and end+10 > self.filesize and end-start < 200000): #print "let's hand that through, it is probably a mp4 index request" res = ReverseProxyResource.render(self,request) if isinstance(res,int): return res request.write(res) return res = self.renderBufferFile (request, filepath, self.buffer_size) if res == '' and request.method != 'HEAD': return server.NOT_DONE_YET if not isinstance(res,int): request.write(res) if request.method == 'HEAD': request.finish() else: self.warning("Unsupported Proxy Mode: %s" % self.proxy_mode) return self.requestFinished(None) def getMimetype(self): type = MPEG4_MIMETYPE if self.mimetype is not None: type = self.mimetype return type def renderFile(self,request,filepath): self.info('Cache file available %r %r ' %(request, filepath)) downloadedFile = utils.StaticFile(filepath, self.mimetype) downloadedFile.type = self.getMimetype() downloadedFile.encoding = None return downloadedFile.render(request) def renderBufferFile (self, request, filepath, buffer_size): # Try to render file(if we have enough data) self.info("renderBufferFile %s" % filepath) rendering = False if os.path.exists(filepath) is True: filesize = os.path.getsize(filepath) if ((filesize >= buffer_size) or (filesize == self.filesize)): rendering = True self.info("Render file", filepath, self.filesize, filesize, buffer_size) bufferFile = utils.BufferFile(filepath, self.filesize, MPEG4_MIMETYPE) bufferFile.type = self.getMimetype() bufferFile.encoding = None try: return bufferFile.render(request) except Exception,error: self.info(error) if request.method != 'HEAD': self.info('Will retry later to render buffer file') reactor.callLater(0.5, self.renderBufferFile, request,filepath,buffer_size) return '' def downloadFinished(self, result): self.info('Download finished!') self.downloader = None def gotDownloadError(self, error, request): self.info("Unable to download stream to file: %s" % self.stream_url) self.info(request) self.info(error) def downloadFile(self, request, filepath, callback, *args): if (self.downloader is None): self.info("Proxy: download data to cache file %s" % filepath) self.checkCacheSize() self.downloader = utils.downloadPage(self.stream_url, filepath, supportPartial=1) self.downloader.addCallback(self.downloadFinished) self.downloader.addErrback(self.gotDownloadError, request) if(callback is not None): self.downloader.addCallback(callback, request, filepath, *args) return self.downloader def checkCacheSize(self): cache_listdir = os.listdir(self.cache_directory) cache_size = 0 for filename in cache_listdir: path = "%s%s%s" % (self.cache_directory, os.sep, filename) statinfo = os.stat(path) cache_size += statinfo.st_size self.info("Cache size: %d (max is %s)" % (cache_size, self.cache_maxsize)) if (cache_size > self.cache_maxsize): cache_targetsize = self.cache_maxsize * 2/3 self.info("Cache above max size: Reducing to %d" % cache_targetsize) def compare_atime(filename1, filename2): path1 = "%s%s%s" % (self.cache_directory, os.sep, filename1) path2 = "%s%s%s" % (self.cache_directory, os.sep, filename2) cmp = int(os.stat(path1).st_atime - os.stat(path2).st_atime) return cmp cache_listdir = sorted(cache_listdir,compare_atime) while (cache_size > cache_targetsize): filename = cache_listdir.pop(0) path = "%s%s%s" % (self.cache_directory, os.sep, filename) cache_size -= os.stat(path).st_size os.remove(path) self.info("removed %s" % filename) self.info("new cache size is %d" % cache_size) class YoutubeVideoItem(BackendItem): def __init__(self, external_id, title, url, mimetype, entry, store): self.external_id = external_id self.name = title self.duration = None self.size = None self.mimetype = mimetype self.description = None self.date = None self.item = None self.youtube_entry = entry self.store = store def extractDataURL(url, quality): if (quality == 'hd'): format = '22' else: format = '18' kwargs = { 'usenetrc': False, 'quiet': True, 'forceurl': True, 'forcetitle': False, 'simulate': True, 'format': format, 'outtmpl': u'%(id)s.%(ext)s', 'ignoreerrors': True, 'ratelimit': None, } if len(self.store.login) > 0: kwargs['username'] = self.store.login kwargs['password'] = self.store.password fd = FileDownloader(kwargs) youtube_ie = YoutubeIE() fd.add_info_extractor(YoutubePlaylistIE(youtube_ie)) fd.add_info_extractor(MetacafeIE(youtube_ie)) fd.add_info_extractor(youtube_ie) deferred = fd.get_real_urls([url]) return deferred #self.location = VideoProxy(url, self.external_id, # store.proxy_mode, # store.cache_directory, store.cache_maxsize, store.buffer_size, # extractDataURL, quality=self.store.quality) self.location = TestVideoProxy(url, self.external_id, store.proxy_mode, store.cache_directory, store.cache_maxsize,store.buffer_size, extractDataURL, quality=self.store.quality) def get_item(self): if self.item == None: upnp_id = self.get_id() upnp_parent_id = self.parent.get_id() self.item = DIDLLite.VideoItem(upnp_id, upnp_parent_id, self.name) self.item.description = self.description self.item.date = self.date # extract thumbnail from youtube entry # we take the last one, hoping this is the bigger one thumbnail_url = None for image in self.youtube_entry.media.thumbnail: thumbnail_url = image.url if thumbnail_url is not None: self.item.albumArtURI = thumbnail_url res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) res.duration = self.duration res.size = self.size self.item.res.append(res) return self.item def get_path(self): self.url = self.store.urlbase + str(self.storage_id) + "." + MPEG4_EXTENSION return self.url def get_id(self): return self.storage_id class YouTubeStore(AbstractBackendStore): logCategory = 'youtube_store' implements = ['MediaServer'] description = ('Youtube', 'connects to the YouTube service and exposes the standard feeds (public) and the uploads/favorites/playlists/subscriptions of a given user.', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'refresh','text':'Refresh period','type':'string'}, {'option':'login','text':'User ID:','type':'string','group':'User Account'}, {'option':'password','text':'Password:','type':'string','group':'User Account'}, {'option':'location','text':'Locale:','type':'string'}, {'option':'quality','text':'Video quality:','type':'string', 'default':'sd','enum': ('sd','hd')}, {'option':'standard_feeds','text':'Include standard feeds:','type':'bool', 'default': True}, {'option':'proxy_mode','text':'Proxy mode:','type':'string', 'enum': ('redirect','proxy','cache','buffered')}, {'option':'buffer_size','text':'Buffering size:','type':'int'}, {'option':'cache_directory','text':'Cache directory:','type':'dir', 'group':'Cache'}, {'option':'cache_maxsize','text':'Cache max size:','type':'int', 'group':'Cache'}, ] def __init__(self, server, **kwargs): AbstractBackendStore.__init__(self, server, **kwargs) self.name = kwargs.get('name','YouTube') self.login = kwargs.get('userid',kwargs.get('login','')) self.password = kwargs.get('password','') self.locale = kwargs.get('location',None) self.quality = kwargs.get('quality','sd') self.showStandardFeeds = (kwargs.get('standard_feeds','True') in ['Yes','yes','true','True','1']) self.refresh = int(kwargs.get('refresh',60))*60 self.proxy_mode = kwargs.get('proxy_mode', 'redirect') self.cache_directory = kwargs.get('cache_directory', '/tmp/coherence-cache') try: if self.proxy_mode != 'redirect': os.mkdir(self.cache_directory) except: pass self.cache_maxsize = kwargs.get('cache_maxsize', 100000000) self.buffer_size = kwargs.get('buffer_size', 750000) rootItem = Container(None, self.name) self.set_root_item(rootItem) if (self.showStandardFeeds): standardfeeds_uri = 'http://gdata.youtube.com/feeds/api/standardfeeds' if self.locale is not None: standardfeeds_uri += "/%s" % self.locale standardfeeds_uri += "/%s" self.appendFeed('Most Viewed', standardfeeds_uri % 'most_viewed', rootItem) self.appendFeed('Top Rated', standardfeeds_uri % 'top_rated', rootItem) self.appendFeed('Recently Featured', standardfeeds_uri % 'recently_featured', rootItem) self.appendFeed('Watch On Mobile', standardfeeds_uri % 'watch_on_mobile', rootItem) self.appendFeed('Most Discussed', standardfeeds_uri % 'most_discussed', rootItem) self.appendFeed('Top Favorites', standardfeeds_uri % 'top_favorites', rootItem) self.appendFeed('Most Linked', standardfeeds_uri % 'most_linked', rootItem) self.appendFeed('Most Responded', standardfeeds_uri % 'most_responded', rootItem) self.appendFeed('Most Recent', standardfeeds_uri % 'most_recent', rootItem) if len(self.login) > 0: userfeeds_uri = 'http://gdata.youtube.com/feeds/api/users/%s/%s' self.appendFeed('My Uploads', userfeeds_uri % (self.login,'uploads'), rootItem) self.appendFeed('My Favorites', userfeeds_uri % (self.login,'favorites'), rootItem) playlistsItem = LazyContainer(rootItem, 'My Playlists', None, self.refresh, self.retrievePlaylistFeeds) rootItem.add_child(playlistsItem) subscriptionsItem = LazyContainer(rootItem, 'My Subscriptions', None, self.refresh, self.retrieveSubscriptionFeeds) rootItem.add_child(subscriptionsItem) self.init_completed() def __repr__(self): return self.__class__.__name__ def appendFeed( self, name, feed_uri, parent): item = LazyContainer(parent, name, None, self.refresh, self.retrieveFeedItems, feed_uri=feed_uri) parent.add_child(item, external_id=feed_uri) def appendVideoEntry(self, entry, parent): external_id = entry.id.text.split('/')[-1] title = entry.media.title.text url = entry.media.player.url mimetype = MPEG4_MIMETYPE #mimetype = 'video/mpeg' item = YoutubeVideoItem (external_id, title, url, mimetype, entry, self) item.parent = parent parent.add_child(item, external_id=external_id) def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:%s:*' % MPEG4_MIMETYPE], default=True) self.wmc_mapping = {'15': self.get_root_id()} self.yt_service = YouTubeService() self.yt_service.client_id = 'ytapi-JeanMichelSizun-youtubebackendpl-ruabstu7-0' self.yt_service.developer_key = 'AI39si7dv2WWffH-s3pfvmw8fTND-cPWeqF1DOcZ8rwTgTPi4fheX7jjQXpn7SG61Ido0Zm_9gYR52TcGog9Pt3iG9Sa88-1yg' self.yt_service.email = self.login self.yt_service.password = self.password self.yt_service.source = 'Coherence UPnP backend' if len(self.login) > 0: d = threads.deferToThread(self.yt_service.ProgrammaticLogin) def retrieveFeedItems (self, parent=None, feed_uri=''): feed = threads.deferToThread(self.yt_service.GetYouTubeVideoFeed, feed_uri) def gotFeed(feed): if feed is None: self.warning("Unable to retrieve feed %s" % feed_uri) return for entry in feed.entry: self.appendVideoEntry(entry, parent) def gotError(error): self.warning("ERROR: %s" % error) feed.addCallbacks(gotFeed, gotError) return feed def retrievePlaylistFeedItems (self, parent, playlist_id): feed = threads.deferToThread(self.yt_service.GetYouTubePlaylistVideoFeed,playlist_id=playlist_id) def gotFeed(feed): if feed is None: self.warning("Unable to retrieve playlist items %s" % feed_uri) return for entry in feed.entry: self.appendVideoEntry(entry, parent) def gotError(error): self.warning("ERROR: %s" % error) feed.addCallbacks(gotFeed, gotError) return feed def retrieveSubscriptionFeedItems (self, parent, uri): entry = threads.deferToThread(self.yt_service.GetYouTubeSubscriptionEntry,uri) def gotEntry(entry): if entry is None: self.warning("Unable to retrieve subscription items %s" % uri) return feed_uri = entry.feed_link[0].href return self.retrieveFeedItems(parent, feed_uri) def gotError(error): self.warning("ERROR: %s" % error) entry.addCallbacks(gotEntry, gotError) return entry def retrievePlaylistFeeds(self, parent): playlists_feed = threads.deferToThread(self.yt_service.GetYouTubePlaylistFeed, username=self.login) def gotPlaylists(playlist_video_feed): if playlist_video_feed is None: self.warning("Unable to retrieve playlists feed") return for playlist_video_entry in playlist_video_feed.entry: title = playlist_video_entry.title.text playlist_id = playlist_video_entry.id.text.split("/")[-1] # FIXME find better way to retrieve the playlist ID item = LazyContainer(parent, title, playlist_id, self.refresh, self.retrievePlaylistFeedItems, playlist_id=playlist_id) parent.add_child(item, external_id=playlist_id) def gotError(error): self.warning("ERROR: %s" % error) playlists_feed.addCallbacks(gotPlaylists, gotError) return playlists_feed def retrieveSubscriptionFeeds(self, parent): playlists_feed = threads.deferToThread(self.yt_service.GetYouTubeSubscriptionFeed, username=self.login) def gotPlaylists(playlist_video_feed): if playlist_video_feed is None: self.warning("Unable to retrieve subscriptions feed") return for entry in playlist_video_feed.entry: type = entry.GetSubscriptionType() title = entry.title.text uri = entry.id.text name = "[%s] %s" % (type,title) item = LazyContainer(parent, name, uri, self.refresh, self.retrieveSubscriptionFeedItems, uri=uri) item.parent = parent parent.add_child(item, external_id=uri) def gotError(error): self.warning("ERROR: %s" % error) playlists_feed.addCallbacks(gotPlaylists, gotError) return playlists_feedCoherence-0.6.6.2/coherence/backends/dvbd_storage.py0000644000175000017500000006733011317660740020434 0ustar devdev # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz from datetime import datetime import urllib from twisted.internet import reactor, defer from twisted.python import failure, util from twisted.python.filepath import FilePath from coherence.upnp.core import DIDLLite import dbus import dbus.service import coherence.extern.louie as louie from coherence.backend import BackendItem, BackendStore ROOT_CONTAINER_ID = 0 RECORDINGS_CONTAINER_ID = 100 CHANNELS_CONTAINER_ID = 200 CHANNEL_GROUPS_CONTAINER_ID = 300 BASE_CHANNEL_GROUP_ID = 1000 BUS_NAME = 'org.gnome.DVB' RECORDINGSSTORE_OBJECT_PATH = '/org/gnome/DVB/RecordingsStore' MANAGER_OBJECT_PATH = '/org/gnome/DVB/Manager' class Container(BackendItem): logCategory = 'dvbd_store' def __init__(self, id, parent_id, name, store=None, children_callback=None, container_class=DIDLLite.Container): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.item = container_class(id, parent_id,self.name) self.item.childCount = 0 self.update_id = 0 if children_callback != None: self.children = children_callback else: self.children = util.OrderedDict() if store!=None: self.get_url = lambda: store.urlbase + str(self.id) def add_child(self, child): id = child.id if isinstance(child.id, basestring): _,id = child.id.split('.') self.children[id] = child if self.item.childCount != None: self.item.childCount += 1 def get_children(self,start=0,end=0): self.info("container.get_children %r %r", start, end) if callable(self.children): return self.children(start,end-start) else: children = self.children.values() if end == 0: return children[start:] else: return children[start:end] def remove_children(self): if not callable(self.children): self.children = util.OrderedDict() self.item.childCount = 0 def get_child_count(self): if self.item.childCount != None: return self.item.childCount if callable(self.children): return len(self.children()) else: return len(self.children) def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class Channel(BackendItem): logCategory = 'dvbd_store' def __init__(self,store, id,parent_id, name, url, network, mimetype): self.store = store self.id = 'channel.%s' % id self.parent_id = parent_id self.real_id = id self.name = unicode(name) self.network = unicode(network) self.stream_url = url self.mimetype = str(mimetype) def get_children(self, start=0, end=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.debug("Channel get_item %r @ %r" %(self.id,self.parent_id)) item = DIDLLite.VideoBroadcast(self.id,self.parent_id) item.title = self.name res = DIDLLite.Resource(self.stream_url, 'rtsp-rtp-udp:*:%s:*' % self.mimetype) item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.name class Recording(BackendItem): logCategory = 'dvbd_store' def __init__(self,store, id,parent_id, file,title, date,duration, mimetype): self.store = store self.id = 'recording.%s' % id self.parent_id = parent_id self.real_id = id path = unicode(file) # make sure path is an absolute local path (and not an URL) if path.startswith("file://"): path = path[7:] self.location = FilePath(path) self.title = unicode(title) self.mimetype = str(mimetype) self.date = datetime.fromtimestamp(int(date)) self.duration = int(duration) try: self.size = self.location.getsize() except Exception, msg: self.size = 0 self.bitrate = 0 self.url = self.store.urlbase + str(self.id) def get_children(self, start=0, end=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.debug("Recording get_item %r @ %r" %(self.id,self.parent_id)) # create item item = DIDLLite.VideoBroadcast(self.id,self.parent_id) item.date = self.date item.title = self.title # add http resource res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) if self.size > 0: res.size = self.size if self.duration > 0: res.duration = str(self.duration) if self.bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) # add internal resource res = DIDLLite.Resource('file://'+ urllib.quote(self.get_path()), 'internal:%s:%s:*' % (self.store.server.coherence.hostname,self.mimetype)) if self.size > 0: res.size = self.size if self.duration > 0: res.duration = str(self.duration) if self.bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.title def get_url(self): return self.url def get_path(self): return self.location.path class DVBDStore(BackendStore): """ this is a backend to the DVB Daemon http://www.k-d-w.org/node/42 """ implements = ['MediaServer'] logCategory = 'dvbd_store' def __init__(self, server, **kwargs): if server.coherence.config.get('use_dbus','no') != 'yes': raise Exception, 'this backend needs use_dbus enabled in the configuration' BackendStore.__init__(self,server,**kwargs) self.config = kwargs self.name = kwargs.get('name','TV') self.update_id = 0 self.channel_groups = [] if kwargs.get('enable_destroy','no') == 'yes': self.upnp_DestroyObject = self.hidden_upnp_DestroyObject self.bus = dbus.SessionBus() dvb_daemon_recordingsStore = self.bus.get_object(BUS_NAME,RECORDINGSSTORE_OBJECT_PATH) dvb_daemon_manager = self.bus.get_object(BUS_NAME,MANAGER_OBJECT_PATH) self.store_interface = dbus.Interface(dvb_daemon_recordingsStore, 'org.gnome.DVB.RecordingsStore') self.manager_interface = dbus.Interface(dvb_daemon_manager, 'org.gnome.DVB.Manager') dvb_daemon_recordingsStore.connect_to_signal('Changed', self.recording_changed, dbus_interface='org.gnome.DVB.RecordingsStore') self.containers = {} self.containers[ROOT_CONTAINER_ID] = \ Container(ROOT_CONTAINER_ID,-1,self.name,store=self) self.containers[RECORDINGS_CONTAINER_ID] = \ Container(RECORDINGS_CONTAINER_ID,ROOT_CONTAINER_ID,'Recordings',store=self) self.containers[CHANNELS_CONTAINER_ID] = \ Container(CHANNELS_CONTAINER_ID,ROOT_CONTAINER_ID,'Channels',store=self) self.containers[CHANNEL_GROUPS_CONTAINER_ID] = \ Container(CHANNEL_GROUPS_CONTAINER_ID, ROOT_CONTAINER_ID, 'Channel Groups', store=self) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[RECORDINGS_CONTAINER_ID]) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[CHANNELS_CONTAINER_ID]) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[CHANNEL_GROUPS_CONTAINER_ID]) def query_finished(r): louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def query_failed(error): self.error("ERROR: %s", error) louie.send('Coherence.UPnP.Backend.init_failed', None, backend=self, msg=error) # get_device_groups is called after get_channel_groups, # because we need channel groups first channel_d = self.get_channel_groups() channel_d.addCallback(self.get_device_groups) channel_d.addErrback(query_failed) d = defer.DeferredList((channel_d, self.get_recordings())) d.addCallback(query_finished) d.addErrback(query_failed) def __repr__(self): return "DVBDStore" def get_by_id(self,id): self.info("looking for id %r", id) if isinstance(id, basestring): id = id.split('@',1) id = id[0] item = None try: id = int(id) item = self.containers[id] except (ValueError,KeyError): try: type,id = id.split('.') if type == 'recording': return self.containers[RECORDINGS_CONTAINER_ID].children[id] except (ValueError,KeyError): return None return item def recording_changed(self, id, mode): self.containers[RECORDINGS_CONTAINER_ID].remove_children() def handle_result(r): self.debug("recording changed, handle_result: %s", self.containers[RECORDINGS_CONTAINER_ID].update_id) self.containers[RECORDINGS_CONTAINER_ID].update_id += 1 if( self.server and hasattr(self.server,'content_directory_server')): if hasattr(self, 'update_id'): self.update_id += 1 self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) value = (RECORDINGS_CONTAINER_ID,self.containers[RECORDINGS_CONTAINER_ID].update_id) self.debug("ContainerUpdateIDs new value: %s", value) self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) def handle_error(error): self.error("ERROR: %s", error) return error d = self.get_recordings() d.addCallback(handle_result) d.addErrback(handle_error) def get_recording_details(self, id): self.debug("GET RECORDING DETAILS") def process_details(data): self.debug("GOT RECORDING DETAILS %s", data) rid, name, desc, length, start, channel, location = data if len(name) == 0: name = 'Recording ' + str(rid) return {'id':rid,'name':name,'path':location,'date': start,'duration':length} def handle_error(error): self.error("ERROR: %s", error) return error d = defer.Deferred() d.addCallback(process_details) d.addErrback(handle_error) self.store_interface.GetAllInformations(id, reply_handler=lambda x, success: d.callback(x), error_handler=lambda x, success: d.errback(x)) return d def get_recordings(self): self.debug("GET RECORDINGS") def handle_error(error): self.error("ERROR: %s", error) return error def process_query_result(ids): self.debug("GOT RECORDINGS: %s", ids) if len(ids) == 0: return [] l = [] for id in ids: l.append(self.get_recording_details(id)) dl = defer.DeferredList(l) return dl def process_details(results): #print 'process_details', results for result,recording in results: #print result, recording['name'] if result == True: #print "add", recording['id'], recording['name'], recording['path'], recording['date'], recording['duration'] video_item = Recording(self, recording['id'], RECORDINGS_CONTAINER_ID, recording['path'], recording['name'], recording['date'], recording['duration'], 'video/mpegts') self.containers[RECORDINGS_CONTAINER_ID].add_child(video_item) d = defer.Deferred() d.addCallback(process_query_result) d.addCallback(process_details) d.addErrback(handle_error) d.addErrback(handle_error) self.store_interface.GetRecordings(reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def get_channel_details(self, channelList_interface, id): self.debug("GET CHANNEL DETAILS %s" , id) def get_name(id): d = defer.Deferred() channelList_interface.GetChannelName(id, reply_handler=lambda x,success: d.callback(x), error_handler=lambda x,success: d.errback(x)) return d def get_network(id): d = defer.Deferred() channelList_interface.GetChannelNetwork(id, reply_handler=lambda x,success: d.callback(x), error_handler=lambda x,success: d.errback(x)) return d def get_url(id): d = defer.Deferred() channelList_interface.GetChannelURL(id, reply_handler=lambda x,success: d.callback(x), error_handler=lambda x,success: d.errback(x)) return d def process_details(r, id): self.debug("GOT DETAILS %d: %s", id, r) name = r[0][1] network = r[1][1] url = r[2][1] return {'id':id, 'name':name.encode('latin-1'), 'network':network, 'url':url} def handle_error(error): return error dl = defer.DeferredList((get_name(id),get_network(id), get_url(id))) dl.addCallback(process_details,id) dl.addErrback(handle_error) return dl def get_channelgroup_members(self, channel_items, channelList_interface): self.debug("GET ALL CHANNEL GROUP MEMBERS") def handle_error(error): self.error("ERROR: %s", error) return error def process_getChannelsOfGroup(results, group_id): for channel_id in results: channel_id = int(channel_id) if channel_id in channel_items: item = channel_items[channel_id] container_id = BASE_CHANNEL_GROUP_ID + group_id self.containers[container_id].add_child(item) def get_members(channelList_interface, group_id): self.debug("GET CHANNEL GROUP MEMBERS %d", group_id) d = defer.Deferred() d.addCallback(process_getChannelsOfGroup, group_id) d.addErrback(handle_error) channelList_interface.GetChannelsOfGroup(group_id, reply_handler=lambda x, success: d.callback(x), error_handler=lambda x, success: d.callback(x)) return d l = [] for group_id, group_name in self.channel_groups: l.append(get_members(channelList_interface, group_id)) dl = defer.DeferredList(l) return dl def get_tv_channels(self, channelList_interface): self.debug("GET TV CHANNELS") def handle_error(error): self.error("ERROR: %s", error) return error def process_getChannels_result(channels, channelList_interface): self.debug("GetChannels: %s", channels) if len(channels) == 0: return [] l = [] for channel_id in channels: l.append(self.get_channel_details(channelList_interface, channel_id)) dl = defer.DeferredList(l) return dl def process_details(results): self.debug('GOT DEVICE GROUP DETAILS %s', results) channels = {} for result,channel in results: #print channel if result == True: name = unicode(channel['name'], errors='ignore') #print "add", name, channel['url'] video_item = Channel(self, channel['id'], CHANNELS_CONTAINER_ID, name, channel['url'], channel['network'], 'video/mpegts') self.containers[CHANNELS_CONTAINER_ID].add_child(video_item) channels[int(channel['id'])] = video_item return channels d = defer.Deferred() d.addCallback(process_getChannels_result, channelList_interface) d.addCallback(process_details) d.addCallback(self.get_channelgroup_members, channelList_interface) d.addErrback(handle_error) d.addErrback(handle_error) d.addErrback(handle_error) channelList_interface.GetTVChannels(reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def get_deviceGroup_details(self, devicegroup_interface): self.debug("GET DEVICE GROUP DETAILS") def handle_error(error): self.error("ERROR: %s", error) return error def process_getChannelList_result(result): self.debug("GetChannelList: %s", result) dvbd_channelList = self.bus.get_object(BUS_NAME,result) channelList_interface = dbus.Interface (dvbd_channelList, 'org.gnome.DVB.ChannelList') return self.get_tv_channels(channelList_interface) d = defer.Deferred() d.addCallback(process_getChannelList_result) d.addErrback(handle_error) devicegroup_interface.GetChannelList(reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def get_device_groups(self, results): self.debug("GET DEVICE GROUPS") def handle_error(error): self.error("ERROR: %s", error) return error def process_query_result(ids): self.debug("GetRegisteredDeviceGroups: %s", ids) if len(ids) == 0: return l = [] for group_object_path in ids: dvbd_devicegroup = self.bus.get_object(BUS_NAME, group_object_path) devicegroup_interface = dbus.Interface(dvbd_devicegroup, 'org.gnome.DVB.DeviceGroup') l.append(self.get_deviceGroup_details(devicegroup_interface)) dl = defer.DeferredList(l) return dl d = defer.Deferred() d.addCallback(process_query_result) d.addErrback(handle_error) self.manager_interface.GetRegisteredDeviceGroups(reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def get_channel_groups(self): self.debug("GET CHANNEL GROUPS") def handle_error(error): self.error("ERROR: %s", error) return error def process_GetChannelGroups_result(data): self.debug("GOT CHANNEL GROUPS %s", data) for group in data: self.channel_groups.append(group) # id, name container_id = BASE_CHANNEL_GROUP_ID + group[0] group_item = Container(container_id, CHANNEL_GROUPS_CONTAINER_ID, group[1], store=self) self.containers[container_id] = group_item self.containers[CHANNEL_GROUPS_CONTAINER_ID].add_child(group_item) d = defer.Deferred() d.addCallback(process_GetChannelGroups_result) d.addErrback(handle_error) self.manager_interface.GetChannelGroups(reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:video/mpegts:*', 'internal:%s:video/mpegts:*' % self.server.coherence.hostname,], 'rtsp-rtp-udp:*:video/mpegts:*',) def hidden_upnp_DestroyObject(self, *args, **kwargs): ObjectID = kwargs['ObjectID'] item = self.get_by_id(ObjectID) if item == None: return failure.Failure(errorCode(701)) def handle_success(deleted): print 'deleted', deleted, kwargs['ObjectID'] if deleted == False: return failure.Failure(errorCode(715)) return {} def handle_error(error): return failure.Failure(errorCode(701)) d = defer.Deferred() self.store_interface.Delete(int(item.real_id), reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) d.addCallback(handle_success) d.addErrback(handle_error) return d class DVBDScheduledRecording(BackendStore): logCategory = 'dvbd_store' def __init__(self, server, **kwargs): if server.coherence.config.get('use_dbus','no') != 'yes': raise Exception, 'this backend needs use_dbus enabled in the configuration' BackendStore.__init__(self, server, **kwargs) self.state_update_id = 0 self.bus = dbus.SessionBus() # We have one ScheduleRecording service for each device group # TODO use one ScheduledRecording for each device group self.device_group_interface = None dvbd_recorder = self.device_group_interface.GetRecorder() self.recorder_interface = self.bus.get_object(BUS_NAME, dvdb_recorder) def __repr__(self): return "DVBDScheduledRecording" def get_timer_details(self, tid): self.debug("GET TIMER DETAILS %d", tid) def handle_error(error): self.error("ERROR: %s", error) return error def get_infos(tid): d = defer.Callback() self.recorder_interface.GetAllInformations(tid, reply_handler=lambda x, success: d.callback(x), error_handler=lambda x, success: d.errback(x)) return d def get_start_time(tid): d = defer.Callback() self.recorder_interface.GetStartTime(tid, reply_handler=lambda x, success: d.callback(x), error_handler=lambda x, success: d.errback(x)) return d def process_details(results): tid, duration, active, channel_name, title = results[0][1] start = results[1][1] start_datetime = datetime (*start) # TODO return what we actually need # FIXME we properly want the channel id here rather than the name return {'id': tid, 'duration': duration, 'channel': channel, 'start': start_datetime} d = defer.DeferredList((self.get_infos(tid), self.get_start_time(tid))) d.addCallback(process_details) d.addErrback(handle_error) return d def get_timers(self): self.debug("GET TIMERS") def handle_error(error): self.error("ERROR: %s", error) return error def process_GetTimers_results(timer_ids): l = [] for tid in timer_ids: l.append(self.get_timer_details(tid)) dl = defer.DeferredList(l) return dl d = defer.Deferred() d.addCallback(process_GetTimers_result) d.addErrback(handle_error) self.recorder_interface.GetTimers(reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def add_timer(self, channel_id, start_datetime, duration): self.debug("ADD TIMER") def handle_error(error): self.error("ERROR: %s", error) return error def process_AddTimer_result(timer_id): self.state_update_id += 1 return timer_id d = defer.Deferred() d.addCallback(process_AddTimer_result) d.addErrback(handle_error) self.recorder_interface.AddTimer(channel_id, start_datetime.year, start_datetime.month, start_datetime.day, start_datetime.hour, start_datetime.minute, duration, reply_handler=lambda x, success: d.callback(x), error_handler=lambda x, success: d.errback(x)) return d def delete_timer(self, tid): self.debug("DELETE TIMER %d", tid) def handle_error(error): self.error("ERROR: %s", error) return error def process_DeleteTimer_result(success): if not success: # TODO: return 704 return self.state_update_id += 1 d = defer.Deferred() d.addCallback(process_DeleteTimer_result) d.addErrback(handle_error) self.recorder_interface.DeleteTimer(tid, reply_handler=lambda x, success: d.callback(x), error_handler=lambda x, success: d.errback(x)) return d def upnp_GetPropertyList(self, *args, **kwargs): pass def upnp_GetAllowedValues(self, *args, **kwargs): pass def upnp_GetStateUpdateID(self, *args, **kwargs): return self.state_update_id def upnp_BrowseRecordSchedules(self, *args, **kwargs): schedules = [] sched = RecordSchedule() # ChannelID, StartDateTime, Duration return self.get_timers() def upnp_BrowseRecordTasks(self, *args, **kwargs): rec_sched_id = int(kwargs['RecordScheduleID']) tasks = [] task = RecordTask() # ScheduleID, ChannelID, StartDateTime, Duration return self.get_timer_details(rec_sched_id) def upnp_CreateRecordSchedule(self, *args, **kwargs): schedule = kwargs['RecordScheduleParts'] channel_id = schedule.getChannelID() # returns a python datetime object start = schedule.getStartDateTime() # duration in minutes duration = schedule.getDuration() return self.add_timer(channel_id, start, duration) def upnp_DeleteRecordSchedule(self, *args, **kwargs): rec_sched_id = int(kwargs['RecordScheduleID']) def handle_error(error): self.error("ERROR: %s", error) return error def process_IsTimerActive_result(is_active, rec_sched_id): if is_active: # TODO: Return 705 return else: return self.delete_timer(rec_sched_id) d = defer.Deferred() d.addCallback(process_IsTimerActive_result, rec_sched_id) d.addErrback(handle_error) self.recorder_interface.IsTimerActive(rec_sched_id, reply_handler=lambda x: d.callback(x), error_handler=lambda x: d.errback(x)) return d def upnp_GetRecordSchedule(self, *args, **kwargs): rec_sched_id = int(kwargs['RecordScheduleID']) return self.get_timer_details(rec_sched_id) def upnp_GetRecordTask(self, *args, **kwargs): rec_task_id = int(kwargs['RecordTaskID']) return self.get_timer_details(rec_task_id) Coherence-0.6.6.2/coherence/backends/miroguide_storage.py0000644000175000017500000002425311317660740021476 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Coherence backend presenting the content of the MIRO Guide catalog for on-line videos # # The APi is described on page: # https://develop.participatoryculture.org/trac/democracy/wiki/MiroGuideApi # Copyright 2009, Jean-Michel Sizun # Copyright 2009 Frank Scholz import urllib from coherence.upnp.core import utils from coherence.upnp.core import DIDLLite from coherence.backend import BackendStore, BackendItem, Container, LazyContainer, \ AbstractBackendStore from coherence.backends.youtube_storage import TestVideoProxy class VideoItem(BackendItem): def __init__(self, name, description, url, thumbnail_url, store): self.name = name self.duration = None self.size = None self.mimetype = "video" self.url = None self.video_url = url self.thumbnail_url = thumbnail_url self.description = description self.date = None self.item = None self.location = TestVideoProxy(self.video_url, hash(self.video_url), store.proxy_mode, store.cache_directory, store.cache_maxsize,store.buffer_size ) def get_item(self): if self.item == None: upnp_id = self.get_id() upnp_parent_id = self.parent.get_id() self.item = DIDLLite.VideoItem(upnp_id, upnp_parent_id, self.name) self.item.description = self.description self.item.date = self.date if self.thumbnail_url is not None: self.item.icon = self.thumbnail_url self.item.albumArtURI = self.thumbnail_url res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) res.duration = self.duration res.size = self.size self.item.res.append(res) return self.item def get_path(self): return self.url def get_id(self): return self.storage_id class MiroGuideStore(AbstractBackendStore): logCategory = 'miroguide_store' implements = ['MediaServer'] description = ('Miro Guide', 'connects to the MIRO Guide service and exposes the podcasts catalogued by the service. ', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'language','text':'Language:','type':'string', 'default':'English'}, {'option':'refresh','text':'Refresh period','type':'string'}, {'option':'proxy_mode','text':'Proxy mode:','type':'string', 'enum': ('redirect','proxy','cache','buffered')}, {'option':'buffer_size','text':'Buffering size:','type':'int'}, {'option':'cache_directory','text':'Cache directory:','type':'dir', 'group':'Cache'}, {'option':'cache_maxsize','text':'Cache max size:','type':'int', 'group':'Cache'}, ] def __init__(self, server, **kwargs): AbstractBackendStore.__init__(self, server, **kwargs) self.name = kwargs.get('name','MiroGuide') self.language = kwargs.get('language','English') self.refresh = int(kwargs.get('refresh',60))*60 self.proxy_mode = kwargs.get('proxy_mode', 'redirect') self.cache_directory = kwargs.get('cache_directory', '/tmp/coherence-cache') try: if self.proxy_mode != 'redirect': os.mkdir(self.cache_directory) except: pass self.cache_maxsize = kwargs.get('cache_maxsize', 100000000) self.buffer_size = kwargs.get('buffer_size', 750000) rootItem = Container(None, self.name) self.set_root_item(rootItem) categoriesItem = Container(rootItem, "All by Categories") rootItem.add_child(categoriesItem) languagesItem = Container(rootItem, "All by Languages") rootItem.add_child(languagesItem) self.appendLanguage("Recent Videos", self.language, rootItem, sort='-age', count=15) self.appendLanguage("Top Rated", self.language, rootItem, sort='rating', count=15) self.appendLanguage("Most Popular", self.language, rootItem, sort='-popular', count=15) def gotError(error): print "ERROR: %s" % error def gotCategories(result): if result is None: print "Unable to retrieve list of categories" return data,header = result categories = eval(data) # FIXME add some checks to avoid code injection for category in categories: name = category['name'].encode('ascii', 'strict') category_url = category['url'].encode('ascii', 'strict') self.appendCategory(name, name, categoriesItem) categories_url = "https://www.miroguide.com/api/list_categories" d1 = utils.getPage(categories_url) d1.addCallbacks(gotCategories, gotError) def gotLanguages(result): if result is None: print "Unable to retrieve list of languages" return data,header = result languages = eval(data) # FIXME add some checks to avoid code injection for language in languages: name = language['name'].encode('ascii', 'strict') language_url = language['url'].encode('ascii', 'strict') self.appendLanguage(name, name, languagesItem) languages_url = "https://www.miroguide.com/api/list_languages" d2 = utils.getPage(languages_url) d2.addCallbacks(gotLanguages, gotError) self.init_completed() def __repr__(self): return self.__class__.__name__ def appendCategory( self, name, category_id, parent): item = LazyContainer(parent, name, category_id, self.refresh, self.retrieveChannels, filter="category", filter_value=category_id, per_page=100) parent.add_child(item, external_id=category_id) def appendLanguage( self, name, language_id, parent, sort='name', count=0): item = LazyContainer(parent, name, language_id, self.refresh, self.retrieveChannels, filter="language", filter_value=language_id, per_page=100, sort=sort, count=count) parent.add_child(item, external_id=language_id) def appendChannel(self, name, channel_id, parent): item = LazyContainer(parent, name, channel_id, self.refresh, self.retrieveChannelItems, channel_id=channel_id) parent.add_child(item, external_id=channel_id) def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable( 0, 'SourceProtocolInfo', ['http-get:*:%s:*' % 'video/'], #FIXME put list of all possible video mimetypes default=True) self.wmc_mapping = {'15': self.get_root_id()} def retrieveChannels (self, parent, filter, filter_value, per_page=100, offset=0, count=0, sort='name'): filter_value = urllib.quote(filter_value.encode("utf-8")) limit = count if (count == 0): limit = per_page uri = "https://www.miroguide.com/api/get_channels?limit=%d&offset=%d&filter=%s&filter_value=%s&sort=%s" % (limit, offset, filter, filter_value, sort) #print uri d = utils.getPage(uri) def gotChannels(result): if result is None: print "Unable to retrieve channel for category %s" % category_id return data,header = result channels = eval(data) for channel in channels: publisher = channel['publisher'] description = channel['description'] url = channel['url'] hi_def = channel['hi_def'] thumbnail_url = channel['thumbnail_url'] postal_code = channel['postal_code'] id = channel['id'] website_url = channel['website_url'] name = channel['name'] self.appendChannel(name, id, parent) if ((count == 0) and (len(channels) >= per_page)): #print "reached page limit (%d)" % len(channels) parent.childrenRetrievingNeeded = True def gotError(error): print "ERROR: %s" % error d.addCallbacks(gotChannels, gotError) return d def retrieveChannelItems (self, parent, channel_id): uri = "https://www.miroguide.com/api/get_channel?id=%s" % channel_id d = utils.getPage(uri) def gotItems(result): if result is None: print "Unable to retrieve items for channel %s" % channel_id return data,header = result channel = eval(data) items = [] if (channel.has_key('item')): items = channel['item'] for item in items: #print "item:",item url = item['url'] description = item['description'] #print "description:", description name = item['name'] thumbnail_url = None if (channel.has_key('thumbnail_url')): #print "Thumbnail:", channel['thumbnail_url'] thumbnail_url = channel['thumbnail_url'] #size = size['size'] item = VideoItem(name, description, url, thumbnail_url, self) item.parent = parent parent.add_child(item, external_id=url) def gotError(error): print "ERROR: %s" % error d.addCallbacks(gotItems, gotError) return d Coherence-0.6.6.2/coherence/backends/bbc_storage.py0000644000175000017500000001574711317660740020250 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz from coherence.backend import BackendStore from coherence.backend import BackendItem from coherence.upnp.core import DIDLLite from coherence.upnp.core.utils import getPage from twisted.internet import reactor from twisted.python.util import OrderedDict from coherence.extern.et import parse_xml ROOT_CONTAINER_ID = 0 SERIES_CONTAINER_ID = 100 class BBCItem(BackendItem): def __init__(self, parent_id, id, title, url): self.parent_id = parent_id self.id = id self.location = url self.name = title self.duration = None self.size = None self.mimetype = 'audio/mpeg' self.description = None self.item = None def get_item(self): if self.item == None: self.item = DIDLLite.AudioItem(self.id, self.parent_id, self.name) self.item.description = self.description res = DIDLLite.Resource(self.location, 'http-get:*:%s:*' % self.mimetype) res.duration = self.duration res.size = self.size self.item.res.append(res) return self.item class Container(BackendItem): def __init__(self, id, store, parent_id, title): self.url = store.urlbase+str(id) self.parent_id = parent_id self.id = id self.name = title self.mimetype = 'directory' self.update_id = 0 self.children = [] self.item = DIDLLite.Container(self.id, self.parent_id, self.name) self.item.childCount = 0 self.sorted = False def add_child(self, child): id = child.id if isinstance(child.id, basestring): _,id = child.id.split('.') self.children.append(child) self.item.childCount += 1 self.sorted = False def get_children(self, start=0, end=0): if self.sorted == False: def childs_sort(x,y): r = cmp(x.name,y.name) return r self.children.sort(cmp=childs_sort) self.sorted = True if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): return len(self.children) def get_path(self): return self.url def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class BBCStore(BackendStore): implements = ['MediaServer'] rss_url = "http://open.bbc.co.uk/rad/uriplay/availablecontent" def __init__(self, server, *args, **kwargs): BackendStore.__init__(self,server,**kwargs) self.name = kwargs.get('name', 'BBC') self.refresh = int(kwargs.get('refresh', 1)) * (60 *60) self.next_id = 1000 self.update_id = 0 self.last_updated = None self.store = {} d = self.update_data() d.addCallback(self.init_completed) def get_next_id(self): self.next_id += 1 return self.next_id def get_by_id(self,id): #print "looking for id %r" % id if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: return self.store[int(id)] except (ValueError,KeyError): pass return None def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable( \ 0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01700000000000000000000000000000', 'http-get:*:audio/mpeg:*']) def update_data(self): def fail(f): print "fail", f return f dfr = getPage(self.rss_url) dfr.addCallback(parse_xml) dfr.addErrback(fail) dfr.addCallback(self.parse_data) dfr.addErrback(fail) dfr.addBoth(self.queue_update) return dfr def parse_data(self, xml_data): root = xml_data.getroot() self.store = {} self.store[ROOT_CONTAINER_ID] = \ Container(ROOT_CONTAINER_ID,self,-1, self.name) self.store[SERIES_CONTAINER_ID] = \ Container(SERIES_CONTAINER_ID,self,ROOT_CONTAINER_ID, 'Series') self.store[ROOT_CONTAINER_ID].add_child(self.store[SERIES_CONTAINER_ID]) for brand in root.findall('./{http://purl.org/ontology/po/}Brand'): first = None for episode in brand.findall('*/{http://purl.org/ontology/po/}Episode'): for version in episode.findall('*/{http://purl.org/ontology/po/}Version'): seconds = int(version.find('./{http://uriplay.org/elements/}publishedDuration').text) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 duration = ("%d:%02d:%02d") % (hours, minutes, seconds) for manifestation in version.findall('./{http://uriplay.org/elements/}manifestedAs'): encoding = manifestation.find('*/{http://uriplay.org/elements/}dataContainerFormat') size = manifestation.find('*/{http://uriplay.org/elements/}dataSize') for location in manifestation.findall('*/*/{http://uriplay.org/elements/}Location'): uri = location.find('./{http://uriplay.org/elements/}uri') uri = uri.attrib['{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource'] if first == None: id = self.get_next_id() self.store[id] = \ Container(id,self,SERIES_CONTAINER_ID,brand.find('./{http://purl.org/dc/elements/1.1/}title').text) self.store[SERIES_CONTAINER_ID].add_child(self.store[id]) first = self.store[id] item = BBCItem(first.id, self.get_next_id(), episode.find('./{http://purl.org/dc/elements/1.1/}title').text, uri) first.add_child(item) item.mimetype = encoding.text item.duration = duration item.size = int(size.text)*1024 item.description = episode.find('./{http://purl.org/dc/elements/1.1/}description').text self.update_id += 1 #if self.server: # self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) # value = (ROOT_CONTAINER_ID,self.container.update_id) # self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) def queue_update(self, error_or_failure): reactor.callLater(self.refresh, self.update_data) Coherence-0.6.6.2/coherence/backends/gstreamer_renderer.py0000644000175000017500000012647211317660740021653 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007,2008,2009 Frank Scholz from sets import Set from twisted.internet import reactor, defer from twisted.internet.task import LoopingCall from twisted.python import failure from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import DIDLLite import string import os, platform from StringIO import StringIO import tokenize import pygst pygst.require('0.10') import gst import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin from coherence import log class Player(log.Loggable): logCategory = 'gstreamer_player' max_playbin_volume = 1. def __init__(self, default_mimetype='audio/mpeg', audio_sink_name=None, video_sink_name=None, audio_sink_options=None, video_sink_options=None): self.audio_sink_name = audio_sink_name or "autoaudiosink" self.video_sink_name = video_sink_name or "autovideosink" self.audio_sink_options = audio_sink_options or {} self.video_sink_options = video_sink_options or {} self.player = None self.source = None self.sink = None self.bus = None self.views = [] self.playing = False self.duration = None self.mimetype = default_mimetype self.create_pipeline(self.mimetype) def add_view(self,view): self.views.append(view) def remove_view(self,view): self.views.remove(view) def update(self,message=None): for v in self.views: v(message=message) def create_pipeline(self, mimetype): self.debug("creating pipeline") if platform.uname()[1].startswith('Nokia'): self.bus = None self.player = None self.source = None self.sink = None if mimetype == 'application/ogg': self.player = gst.parse_launch('gnomevfssrc name=source ! oggdemux ! ivorbisdec ! audioconvert ! dsppcmsink name=sink') self.player.set_name('oggplayer') self.set_volume = self.set_volume_dsp_pcm_sink self.get_volume = self.get_volume_dsp_pcm_sink else: self.player = gst.parse_launch('gnomevfssrc name=source ! id3lib ! dspmp3sink name=sink') self.player.set_name('mp3player') self.set_volume = self.set_volume_dsp_mp3_sink self.get_volume = self.get_volume_dsp_mp3_sink self.source = self.player.get_by_name('source') self.sink = self.player.get_by_name('sink') self.player_uri = 'location' self.mute = self.mute_hack self.unmute = self.unmute_hack self.get_mute = self.get_mute_hack else: self.player = gst.element_factory_make('playbin2', 'player') self.player_uri = 'uri' self.source = self.sink = self.player self.set_volume = self.set_volume_playbin self.get_volume = self.get_volume_playbin self.mute = self.mute_playbin self.unmute = self.unmute_playbin self.get_mute = self.get_mute_playbin audio_sink = gst.element_factory_make(self.audio_sink_name) self._set_props(audio_sink, self.audio_sink_options) self.player.set_property("audio-sink", audio_sink) video_sink = gst.element_factory_make(self.video_sink_name) self._set_props(video_sink, self.video_sink_options) self.player.set_property("video-sink", video_sink) self.bus = self.player.get_bus() self.player_clean = True self.bus.connect('message', self.on_message) self.bus.add_signal_watch() self.update_LC = LoopingCall(self.update) def _set_props(self, element, props): for option, value in props.iteritems(): value = self._py_value(value) element.set_property(option, value) def _py_value(self, s): value = None g = tokenize.generate_tokens(StringIO(s).readline) for toknum, tokval, _, _, _ in g: if toknum == tokenize.NUMBER: if '.' in tokval: value = float(tokval) else: value = int(tokval) elif toknum == tokenize.NAME: value = tokval if value is not None: break return value def get_volume_playbin(self): """ playbin volume is a double from 0.0 - 10.0 """ volume = self.sink.get_property('volume') return int((volume*100) / self.max_playbin_volume) def set_volume_playbin(self, volume): volume = int(volume) if volume < 0: volume=0 if volume > 100: volume=100 volume = (volume * self.max_playbin_volume) / 100. self.sink.set_property('volume', volume) def get_volume_dsp_mp3_sink(self): """ dspmp3sink volume is a n in from 0 to 65535 """ volume = self.sink.get_property('volume') return int(volume*100/65535) def set_volume_dsp_mp3_sink(self, volume): volume = int(volume) if volume < 0: volume=0 if volume > 100: volume=100 self.sink.set_property('volume', volume*65535/100) def get_volume_dsp_pcm_sink(self): """ dspmp3sink volume is a n in from 0 to 65535 """ volume = self.sink.get_property('volume') return int(volume*100/65535) def set_volume_dsp_pcm_sink(self, volume): volume = int(volume) if volume < 0: volume=0 if volume > 100: volume=100 self.sink.set_property('volume', volume*65535/100) def mute_playbin(self): self.player.set_property('mute', True) def unmute_playbin(self): self.player.set_property('mute', False) def get_mute_playbin(self): return self.player.get_property('mute') def mute_hack(self): if hasattr(self,'stored_volume'): self.stored_volume = self.sink.get_property('volume') self.sink.set_property('volume', 0) else: self.sink.set_property('mute', True) def unmute_hack(self): if hasattr(self,'stored_volume'): self.sink.set_property('volume', self.stored_volume) else: self.sink.set_property('mute', False) def get_mute_hack(self): if hasattr(self,'stored_volume'): muted = self.sink.get_property('volume') == 0 else: try: muted = self.sink.get_property('mute') except TypeError: if not hasattr(self,'stored_volume'): self.stored_volume = self.sink.get_property('volume') muted = self.stored_volume == 0 except: muted = False self.warning("can't get mute state") return muted def get_state(self): return self.player.get_state() def get_uri(self): """ playbin2 has an empty uri property after a pipeline stops, as the uri is nowdays the next track to play, not the current one """ if self.player.get_name() != 'player': return self.source.get_property(self.player_uri) else: try: return self.current_uri except: return None def set_uri(self,uri): self.source.set_property(self.player_uri, uri.encode('utf-8')) if self.player.get_name() == 'player': self.current_uri = uri.encode('utf-8') def on_message(self, bus, message): #print "on_message", message #print "from", message.src.get_name() t = message.type #print t if t == gst.MESSAGE_ERROR: err, debug = message.parse_error() self.warning("Gstreamer error: %r,%r" % (err, debug)) if self.playing == True: self.seek('-0') #self.player.set_state(gst.STATE_READY) elif t == gst.MESSAGE_TAG: for key in message.parse_tag().keys(): self.tags[key] = message.structure[key] #print self.tags elif t == gst.MESSAGE_STATE_CHANGED: if message.src == self.player: old, new, pending = message.parse_state_changed() #print "player (%s) state_change:" %(message.src.get_path_string()), old, new, pending if new == gst.STATE_PLAYING: self.playing = True self.update_LC.start( 1, False) self.update() elif old == gst.STATE_PLAYING: self.playing = False try: self.update_LC.stop() except: pass self.update() #elif new == gst.STATE_READY: # self.update() elif t == gst.MESSAGE_EOS: self.debug("reached file end") self.seek('-0') self.update(message=gst.MESSAGE_EOS) def query_position( self): #print "query_position" try: position, format = self.player.query_position(gst.FORMAT_TIME) except: #print "CLOCK_TIME_NONE", gst.CLOCK_TIME_NONE position = gst.CLOCK_TIME_NONE position = 0 #print position if self.duration == None: try: self.duration, format = self.player.query_duration(gst.FORMAT_TIME) except: self.duration = gst.CLOCK_TIME_NONE self.duration = 0 #import traceback #print traceback.print_exc() #print self.duration r = {} if self.duration == 0: self.duration = None self.debug("duration unknown") return r r[u'raw'] = {u'position':unicode(str(position)), u'remaining':unicode(str(self.duration - position)), u'duration':unicode(str(self.duration))} position_human = u'%d:%02d' % (divmod( position/1000000000, 60)) duration_human = u'%d:%02d' % (divmod( self.duration/1000000000, 60)) remaining_human = u'%d:%02d' % (divmod( (self.duration-position)/1000000000, 60)) r[u'human'] = {u'position':position_human, u'remaining':remaining_human, u'duration':duration_human} r[u'percent'] = {u'position':position*100/self.duration, u'remaining':100-(position*100/self.duration)} self.debug(r) return r def load( self, uri, mimetype): self.debug("load --> %r %r" % (uri, mimetype)) _,state,_ = self.player.get_state() if( state == gst.STATE_PLAYING or state == gst.STATE_PAUSED): self.stop() #print "player -->", self.player.get_name() if self.player.get_name() != 'player': self.create_pipeline(mimetype) self.player.set_state(gst.STATE_READY) self.set_uri(uri) self.player_clean = True self.duration = None self.mimetype = mimetype self.tags = {} #self.player.set_state(gst.STATE_PAUSED) #self.update() self.debug("load <--") self.play() def play( self): uri = self.get_uri() mimetype = self.mimetype self.debug("play --> %r %r" % (uri, mimetype)) if self.player.get_name() != 'player': if self.player_clean == False: #print "rebuild pipeline" self.player.set_state(gst.STATE_NULL) self.create_pipeline(mimetype) self.set_uri(uri) self.player.set_state(gst.STATE_READY) else: self.player_clean = True self.player.set_state(gst.STATE_PLAYING) self.debug("play <--") def pause(self): self.debug("pause --> %r" % self.get_uri()) self.player.set_state(gst.STATE_PAUSED) self.debug("pause <--") def stop(self): self.debug("stop --> %r" % self.get_uri()) self.seek('-0') self.player.set_state(gst.STATE_READY) self.update(message=gst.MESSAGE_EOS) self.debug("stop <-- %r " % self.get_uri()) def seek(self, location): """ @param location: simple number = time to seek to, in seconds +nL = relative seek forward n seconds -nL = relative seek backwards n seconds """ _,state,_ = self.player.get_state() if state != gst.STATE_PAUSED: self.player.set_state(gst.STATE_PAUSED) l = long(location)*1000000000 p = self.query_position() #print p['raw']['position'], l if location[0] == '+': l = long(p[u'raw'][u'position']) + (long(location[1:])*1000000000) l = min( l, long(p[u'raw'][u'duration'])) elif location[0] == '-': if location == '-0': l = 0L else: l = long(p[u'raw'][u'position']) - (long(location[1:])*1000000000) l = max( l, 0L) self.debug("seeking to %r" % l) """ self.player.seek( 1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, l, gst.SEEK_TYPE_NONE, 0) """ event = gst.event_new_seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, gst.SEEK_TYPE_SET, l, gst.SEEK_TYPE_NONE, 0) res = self.player.send_event(event) if res: pass #print "setting new stream time to 0" #self.player.set_new_stream_time(0L) elif location != '-0': print "seek to %r failed" % location if location == '-0': content_type, _ = self.mimetype.split("/") try: self.update_LC.stop() except: pass if self.player.get_name() != 'player': self.player.set_state(gst.STATE_NULL) self.player_clean = False elif content_type != "image": self.player.set_state(gst.STATE_READY) self.update() else: self.player.set_state(state) if state == gst.STATE_PAUSED: self.update() class GStreamerPlayer(log.Loggable,Plugin): """ a backend with a GStreamer based audio player needs gnomevfssrc from gst-plugins-base unfortunately gnomevfs has way too much dependencies # not working -> http://bugzilla.gnome.org/show_bug.cgi?id=384140 # needs the neonhttpsrc plugin from gst-plugins-bad # tested with CVS version # and with this patch applied # --> http://bugzilla.gnome.org/show_bug.cgi?id=375264 # not working and id3demux from gst-plugins-good CVS too """ logCategory = 'gstreamer_player' implements = ['MediaRenderer'] vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'}, 'AVTransport': {'A_ARG_TYPE_SeekMode':('ABS_TIME','REL_TIME','TRACK_NR')}} vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}} def __init__(self, device, **kwargs): if(device.coherence.config.get('use_dbus','no') != 'yes' and device.coherence.config.get('glib','no') != 'yes'): raise Exception, 'this media renderer needs use_dbus enabled in the configuration' self.name = kwargs.get('name','GStreamer Audio Player') audio_sink_name = kwargs.get("audio_sink_name") audio_sink_options = kwargs.get("audio_sink_options") video_sink_name = kwargs.get("video_sink_name") video_sink_options = kwargs.get("video_sink_options") self.player = Player(audio_sink_name=audio_sink_name, video_sink_name=video_sink_name, audio_sink_options=audio_sink_options, video_sink_options=video_sink_options) self.player.add_view(self.update) self.metadata = None self.duration = None self.view = [] self.tags = {} self.server = device self.playcontainer = None self.dlna_caps = ['playcontainer-0-1'] louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def __repr__(self): return str(self.__class__).split('.')[-1] def update(self, message=None): _, current,_ = self.player.get_state() self.debug("update current %r", current) connection_manager = self.server.connection_manager_server av_transport = self.server.av_transport_server conn_id = connection_manager.lookup_avt_id(self.current_connection_id) if current == gst.STATE_PLAYING: state = 'playing' av_transport.set_variable(conn_id, 'TransportState', 'PLAYING') elif current == gst.STATE_PAUSED: state = 'paused' av_transport.set_variable(conn_id, 'TransportState', 'PAUSED_PLAYBACK') elif self.playcontainer != None and message == gst.MESSAGE_EOS and \ self.playcontainer[0]+1 < len(self.playcontainer[2]): state = 'transitioning' av_transport.set_variable(conn_id, 'TransportState', 'TRANSITIONING') next_track = () item = self.playcontainer[2][self.playcontainer[0]+1] infos = connection_manager.get_variable('SinkProtocolInfo') local_protocol_infos = infos.value.split(',') res = item.res.get_matching(local_protocol_infos, protocol_type='internal') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] infos = res.protocolInfo.split(':') remote_protocol, remote_network, remote_content_format, _ = infos didl = DIDLLite.DIDLElement() didl.addItem(item) next_track = (res.data, didl.toString(), remote_content_format) self.playcontainer[0] = self.playcontainer[0]+1 if len(next_track) == 3: av_transport.set_variable(conn_id, 'CurrentTrack', self.playcontainer[0]+1) self.load(next_track[0], next_track[1], next_track[2]) self.play() else: state = 'idle' av_transport.set_variable(conn_id, 'TransportState', 'STOPPED') elif message == gst.MESSAGE_EOS and \ len(av_transport.get_variable('NextAVTransportURI').value) > 0: state = 'transitioning' av_transport.set_variable(conn_id, 'TransportState', 'TRANSITIONING') CurrentURI = av_transport.get_variable('NextAVTransportURI').value metadata = av_transport.get_variable('NextAVTransportURIMetaData') CurrentURIMetaData = metadata.value av_transport.set_variable(conn_id, 'NextAVTransportURI', '') av_transport.set_variable(conn_id, 'NextAVTransportURIMetaData', '') r = self.upnp_SetAVTransportURI(self, InstanceID=0, CurrentURI=CurrentURI, CurrentURIMetaData=CurrentURIMetaData) if r == {}: self.play() else: state = 'idle' av_transport.set_variable(conn_id, 'TransportState', 'STOPPED') else: state = 'idle' av_transport.set_variable(conn_id, 'TransportState', 'STOPPED') self.info("update %r" % state) self._update_transport_position(state) def _update_transport_position(self, state): connection_manager = self.server.connection_manager_server av_transport = self.server.av_transport_server conn_id = connection_manager.lookup_avt_id(self.current_connection_id) position = self.player.query_position() #print position for view in self.view: view.status(self.status(position)) if position.has_key(u'raw'): if self.duration == None and 'duration' in position[u'raw']: self.duration = int(position[u'raw'][u'duration']) if self.metadata != None and len(self.metadata)>0: # FIXME: duration breaks client parsing MetaData? elt = DIDLLite.DIDLElement.fromString(self.metadata) for item in elt: for res in item.findall('res'): formatted_duration = self._format_time(self.duration) res.attrib['duration'] = formatted_duration self.metadata = elt.toString() #print self.metadata if self.server != None: av_transport.set_variable(conn_id, 'AVTransportURIMetaData', self.metadata) av_transport.set_variable(conn_id, 'CurrentTrackMetaData', self.metadata) self.info("%s %d/%d/%d - %d%%/%d%% - %s/%s/%s", state, string.atol(position[u'raw'][u'position'])/1000000000, string.atol(position[u'raw'][u'remaining'])/1000000000, string.atol(position[u'raw'][u'duration'])/1000000000, position[u'percent'][u'position'], position[u'percent'][u'remaining'], position[u'human'][u'position'], position[u'human'][u'remaining'], position[u'human'][u'duration']) duration = string.atol(position[u'raw'][u'duration']) formatted = self._format_time(duration) av_transport.set_variable(conn_id, 'CurrentTrackDuration', formatted) av_transport.set_variable(conn_id, 'CurrentMediaDuration', formatted) position = string.atol(position[u'raw'][u'position']) formatted = self._format_time(position) av_transport.set_variable(conn_id, 'RelativeTimePosition', formatted) av_transport.set_variable(conn_id, 'AbsoluteTimePosition', formatted) def _format_time(self, time): fmt = '%d:%02d:%02d' try: m, s = divmod(time / 1000000000, 60) h, m = divmod(m, 60) except: h = m = s = 0 fmt = '%02d:%02d:%02d' formatted = fmt % (h, m, s) return formatted def load( self, uri,metadata, mimetype=None): self.info("loading: %r %r " % (uri, mimetype)) _,state,_ = self.player.get_state() connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.stop(silent=True) # the check whether a stop is really needed is done inside stop if mimetype is None: _,ext = os.path.splitext(uri) if ext == '.ogg': mimetype = 'application/ogg' else: mimetype = 'audio/mpeg' self.player.load( uri, mimetype) self.metadata = metadata self.mimetype = mimetype self.tags = {} if self.playcontainer == None: self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',metadata) self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',1) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack',1) else: self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',self.playcontainer[1]) self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',len(self.playcontainer[2])) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack',self.playcontainer[0]+1) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',metadata) #self.server.av_transport_server.set_variable(connection_id, 'TransportState', 'TRANSITIONING') #self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','PLAY,STOP,PAUSE,SEEK,NEXT,PREVIOUS') if uri.startswith('http://'): transport_actions = Set(['PLAY,STOP,PAUSE']) else: transport_actions = Set(['PLAY,STOP,PAUSE,SEEK']) if len(self.server.av_transport_server.get_variable('NextAVTransportURI').value) > 0: transport_actions.add('NEXT') if self.playcontainer != None: if len(self.playcontainer[2]) - (self.playcontainer[0]+1) > 0: transport_actions.add('NEXT') if self.playcontainer[0] > 0: transport_actions.add('PREVIOUS') self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions',transport_actions) if state == gst.STATE_PLAYING: self.info("was playing...") self.play() self.update() def status( self, position): uri = self.player.get_uri() if uri == None: return {u'state':u'idle',u'uri':u''} else: r = {u'uri':unicode(uri), u'position':position} if self.tags != {}: try: r[u'artist'] = unicode(self.tags['artist']) except: pass try: r[u'title'] = unicode(self.tags['title']) except: pass try: r[u'album'] = unicode(self.tags['album']) except: pass if self.player.get_state()[1] == gst.STATE_PLAYING: r[u'state'] = u'playing' elif self.player.get_state()[1] == gst.STATE_PAUSED: r[u'state'] = u'paused' else: r[u'state'] = u'idle' return r def start( self, uri): self.load( uri) self.play() def stop(self,silent=False): self.info('Stopping: %r' % self.player.get_uri()) if self.player.get_uri() == None: return if self.player.get_state()[1] in [gst.STATE_PLAYING,gst.STATE_PAUSED]: self.player.stop() if silent is True: self.server.av_transport_server.set_variable(self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), 'TransportState', 'STOPPED') def play( self): self.info("Playing: %r" % self.player.get_uri()) if self.player.get_uri() == None: return self.player.play() self.server.av_transport_server.set_variable(self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), 'TransportState', 'PLAYING') def pause( self): self.info('Pausing: %r' % self.player.get_uri()) self.player.pause() self.server.av_transport_server.set_variable(self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), 'TransportState', 'PAUSED_PLAYBACK') def seek(self, location, old_state): self.player.seek(location) if old_state != None: self.server.av_transport_server.set_variable(0, 'TransportState', old_state) def mute(self): self.player.mute() rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'True') def unmute(self): self.player.unmute() rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'False') def get_mute(self): return self.player.get_mute() def get_volume(self): return self.player.get_volume() def set_volume(self, volume): self.player.set_volume(volume) rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.server.rendering_control_server.set_variable(rcs_id, 'Volume', volume) def playcontainer_browse(self,uri): """ dlna-playcontainer://uuid%3Afe814e3e-5214-4c24-847b-383fb599ff01?sid=urn%3Aupnp-org%3AserviceId%3AContentDirectory&cid=1441&fid=1444&fii=0&sc=&md=0 """ from urllib import unquote from cgi import parse_qs from coherence.extern.et import ET from coherence.upnp.core.utils import parse_xml def handle_reply(r,uri,action,kw): try: next_track = () elt = DIDLLite.DIDLElement.fromString(r['Result']) item = elt.getItems()[0] local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',') res = item.res.get_matching(local_protocol_infos, protocol_type='internal') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') didl = DIDLLite.DIDLElement() didl.addItem(item) next_track = (res.data,didl.toString(),remote_content_format) """ a list with these elements: the current track index - will change during playback of the container items the initial complete playcontainer-uri a list of all the items in the playcontainer the action methods to do the Browse call on the device the kwargs for the Browse call - kwargs['StartingIndex'] will be modified during further Browse requests """ self.playcontainer = [int(kw['StartingIndex']),uri,elt.getItems()[:],action,kw] def browse_more(starting_index,number_returned,total_matches): self.info("browse_more", starting_index,number_returned,total_matches) try: def handle_error(r): pass def handle_reply(r,starting_index): elt = DIDLLite.DIDLElement.fromString(r['Result']) self.playcontainer[2] += elt.getItems()[:] browse_more(starting_index,int(r['NumberReturned']),int(r['TotalMatches'])) if((number_returned != 5 or number_returned < (total_matches-starting_index)) and (total_matches-number_returned) != starting_index): self.info("seems we have been returned only a part of the result") self.info("requested %d, starting at %d" % (5,starting_index)) self.info("got %d out of %d" % (number_returned, total_matches)) self.info("requesting more starting now at %d" % (starting_index+number_returned)) self.playcontainer[4]['StartingIndex'] = str(starting_index+number_returned) d = self.playcontainer[3].call(**self.playcontainer[4]) d.addCallback(handle_reply,starting_index+number_returned) d.addErrback(handle_error) except: import traceback traceback.print_exc() browse_more(int(kw['StartingIndex']),int(r['NumberReturned']),int(r['TotalMatches'])) if len(next_track) == 3: return next_track except: import traceback traceback.print_exc() return failure.Failure(errorCode(714)) def handle_error(r): return failure.Failure(errorCode(714)) try: udn,args = uri[21:].split('?') udn = unquote(udn) args = parse_qs(args) type = args['sid'][0].split(':')[-1] try: sc = args['sc'][0] except: sc = '' device = self.server.coherence.get_device_with_id(udn) service = device.get_service_by_type(type) action = service.get_action('Browse') kw = {'ObjectID':args['cid'][0], 'BrowseFlag':'BrowseDirectChildren', 'StartingIndex':args['fii'][0], 'RequestedCount':str(5), 'Filter':'*', 'SortCriteria':sc} d = action.call(**kw) d.addCallback(handle_reply,uri,action,kw) d.addErrback(handle_error) return d except: return failure.Failure(errorCode(714)) def upnp_init(self): self.current_connection_id = None self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo', ['internal:%s:audio/mpeg:*' % self.server.coherence.hostname, 'http-get:*:audio/mpeg:*', 'internal:%s:audio/mp4:*' % self.server.coherence.hostname, 'http-get:*:audio/mp4:*', 'internal:%s:application/ogg:*' % self.server.coherence.hostname, 'http-get:*:application/ogg:*', 'internal:%s:audio/ogg:*' % self.server.coherence.hostname, 'http-get:*:audio/ogg:*', 'internal:%s:video/ogg:*' % self.server.coherence.hostname, 'http-get:*:video/ogg:*', 'internal:%s:video/x-msvideo:*' % self.server.coherence.hostname, 'http-get:*:video/x-msvideo:*', 'internal:%s:video/mp4:*' % self.server.coherence.hostname, 'http-get:*:video/mp4:*', 'internal:%s:video/quicktime:*' % self.server.coherence.hostname, 'http-get:*:video/quicktime:*', 'internal:%s:image/gif:*' % self.server.coherence.hostname, 'http-get:*:image/gif:*', 'internal:%s:image/jpeg:*' % self.server.coherence.hostname, 'http-get:*:image/jpeg:*', 'internal:%s:image/png:*' % self.server.coherence.hostname, 'http-get:*:image/png:*', 'http-get:*:*:*'], default=True) self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True) self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True) self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True) self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True) self.server.rendering_control_server.set_variable(0, 'Volume', self.get_volume()) self.server.rendering_control_server.set_variable(0, 'Mute', self.get_mute()) def upnp_Play(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Speed = int(kwargs['Speed']) self.play() return {} def upnp_Pause(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.pause() return {} def upnp_Stop(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.stop() return {} def upnp_Seek(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Unit = kwargs['Unit'] Target = kwargs['Target'] if InstanceID != 0: return failure.Failure(errorCode(718)) if Unit in ['ABS_TIME','REL_TIME']: old_state = self.server.av_transport_server.get_variable('TransportState').value self.server.av_transport_server.set_variable(InstanceID, 'TransportState', 'TRANSITIONING') sign = '' if Target[0] == '+': Target = Target[1:] sign = '+' if Target[0] == '-': Target = Target[1:] sign = '-' h,m,s = Target.split(':') seconds = int(h)*3600 + int(m)*60 + int(s) self.seek(sign+str(seconds), old_state) if Unit in ['TRACK_NR']: if self.playcontainer == None: NextURI = self.server.av_transport_server.get_variable('NextAVTransportURI',InstanceID).value if NextURI != '': self.server.av_transport_server.set_variable(InstanceID, 'TransportState', 'TRANSITIONING') NextURIMetaData = self.server.av_transport_server.get_variable('NextAVTransportURIMetaData').value self.server.av_transport_server.set_variable(InstanceID, 'NextAVTransportURI', '') self.server.av_transport_server.set_variable(InstanceID, 'NextAVTransportURIMetaData', '') r = self.upnp_SetAVTransportURI(self, InstanceID=InstanceID,CurrentURI=NextURI,CurrentURIMetaData=NextURIMetaData) return r else: Target = int(Target) if 0 < Target <= len(self.playcontainer[2]): self.server.av_transport_server.set_variable(InstanceID, 'TransportState', 'TRANSITIONING') next_track = () item = self.playcontainer[2][Target-1] local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',') res = item.res.get_matching(local_protocol_infos, protocol_type='internal') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') didl = DIDLLite.DIDLElement() didl.addItem(item) next_track = (res.data,didl.toString(),remote_content_format) self.playcontainer[0] = Target-1 if len(next_track) == 3: self.server.av_transport_server.set_variable(self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), 'CurrentTrack',Target) self.load(next_track[0],next_track[1],next_track[2]) self.play() return {} return failure.Failure(errorCode(711)) return {} def upnp_Next(self,*args,**kwargs): InstanceID = int(kwargs['InstanceID']) track_nr = self.server.av_transport_server.get_variable('CurrentTrack') return self.upnp_Seek(self,InstanceID=InstanceID,Unit='TRACK_NR',Target=str(int(track_nr.value)+1)) def upnp_Previous(self,*args,**kwargs): InstanceID = int(kwargs['InstanceID']) track_nr = self.server.av_transport_server.get_variable('CurrentTrack') return self.upnp_Seek(self,InstanceID=InstanceID,Unit='TRACK_NR',Target=str(int(track_nr.value)-1)) def upnp_SetNextAVTransportURI(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) NextURI = kwargs['NextURI'] current_connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) NextMetaData = kwargs['NextURIMetaData'] self.server.av_transport_server.set_variable(current_connection_id, 'NextAVTransportURI',NextURI) self.server.av_transport_server.set_variable(current_connection_id, 'NextAVTransportURIMetaData',NextMetaData) if len(NextURI) == 0 and self.playcontainer == None: transport_actions = self.server.av_transport_server.get_variable('CurrentTransportActions').value transport_actions = Set(transport_actions.split(',')) try: transport_actions.remove('NEXT') self.server.av_transport_server.set_variable(current_connection_id, 'CurrentTransportActions',transport_actions) except KeyError: pass return {} transport_actions = self.server.av_transport_server.get_variable('CurrentTransportActions').value transport_actions = Set(transport_actions.split(',')) transport_actions.add('NEXT') self.server.av_transport_server.set_variable(current_connection_id, 'CurrentTransportActions',transport_actions) return {} def upnp_SetAVTransportURI(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) CurrentURI = kwargs['CurrentURI'] CurrentURIMetaData = kwargs['CurrentURIMetaData'] #print "upnp_SetAVTransportURI",InstanceID, CurrentURI, CurrentURIMetaData if CurrentURI.startswith('dlna-playcontainer://'): def handle_result(r): self.load(r[0],r[1],mimetype=r[2]) return {} def pass_error(r): return r d = defer.maybeDeferred(self.playcontainer_browse,CurrentURI) d.addCallback(handle_result) d.addErrback(pass_error) return d elif len(CurrentURIMetaData)==0: self.playcontainer = None self.load(CurrentURI,CurrentURIMetaData) return {} else: local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',') #print local_protocol_infos elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData) if elt.numItems() == 1: item = elt.getItems()[0] res = item.res.get_matching(local_protocol_infos, protocol_type='internal') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') self.playcontainer = None self.load(res.data,CurrentURIMetaData,mimetype=remote_content_format) return {} return failure.Failure(errorCode(714)) def upnp_SetMute(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredMute = kwargs['DesiredMute'] if DesiredMute in ['TRUE', 'True', 'true', '1','Yes','yes']: self.mute() else: self.unmute() return {} def upnp_SetVolume(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredVolume = int(kwargs['DesiredVolume']) self.set_volume(DesiredVolume) return {} if __name__ == '__main__': import sys p = Player(None) if len(sys.argv) > 1: reactor.callWhenRunning( p.start, sys.argv[1]) reactor.run() Coherence-0.6.6.2/coherence/backends/elisa_storage.py0000644000175000017500000001607411317660740020611 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz import re from twisted.spread import pb from twisted.internet import reactor from twisted.python import failure from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement from coherence.upnp.core.soap_service import errorCode import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin class ElisaMediaStore(Plugin): """ this is a backend to the Elisa Media DB Elisa needs to expose two methods get_root_id(media_type) if media_type == '*' this returns the root id of the media collection if media_type == 'audio' this returns the root id of the audio collection get_item_by_id(id) this returns a dict with the following keys: id = id in the media db parent_id = parent_id in the media db name = title, album name or basename mimetype = 'directory' or real mimetype children = list of objects for which this item is the parent location = filesystem path if item is a file cover = url by which the cover image can be retrieved (OPTIONAL) size = in bytes (OPTIONAL) """ implements = ['MediaServer'] def __init__(self, server, **kwargs): self.name = kwargs.get('name','Elisa') self.host = kwargs.get('host','127.0.0.1') self.urlbase = kwargs.get('urlbase','') ignore_patterns = kwargs.get('ignore_patterns',[]) if self.urlbase[len(self.urlbase)-1] != '/': self.urlbase += '/' self.server = server self.update_id = 0 self.root_id = 0 self.get_root_id() def __repr__(self): return "Elisa storage" def get_store(self): factory = pb.PBClientFactory() factory.noisy = False reactor.connectTCP(self.host, 8789, factory) return factory.getRootObject() def get_by_id(self,id): try: return self.store[int(id)] except: return None def set_root_id( self, id): self.root_id = id louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def get_root_id( self, media_type='audio'): """ ask Elisa to tell us the id of the top item representing the media_type == 'something' collection """ store = self.get_store() dfr = store.addCallback(lambda object: object.callRemote('get_cache_manager')) dfr.addCallback(lambda cache_mgr: cache_mgr.callRemote("get_media_root_id", media_type)) dfr.addCallback(self.set_root_id) def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['internal:%s:*:*' % self.host, 'http-get:*:audio/mpeg:*']) def upnp_Browse(self, *args, **kwargs): ObjectID = kwargs['ObjectID'] BrowseFlag = kwargs['BrowseFlag'] Filter = kwargs['Filter'] StartingIndex = int(kwargs['StartingIndex']) RequestedCount = int(kwargs['RequestedCount']) SortCriteria = kwargs['SortCriteria'] def build_upnp_item(elisa_item): UPnPClass = classChooser(elisa_item['mimetype']) upnp_item = None if UPnPClass: upnp_item = UPnPClass(elisa_item['id'], elisa_item['parent_id'], elisa_item['name']) if isinstance(upnp_item, Container): upnp_item.childCount = len(elisa_item.get('children',[])) if len(Filter) > 0: upnp_item.searchable = True upnp_item.searchClass = ('object',) else: internal_url = elisa_item['location'].get('internal') external_url = elisa_item['location'].get('external') try: size = elisa_item['size'] except: size = None try: cover = elisa_item['cover'] if cover != '': upnp_item.albumArtURI = cover except: pass res = Resource(internal_url, 'internal:%s:*:*' %self.host) res.size = size upnp_item.res.append(res) res = Resource(external_url, 'http-get:*:%s:*' % elisa_item['mimetype']) res.size = size upnp_item.res.append(res) return upnp_item def got_result(elisa_item): didl = DIDLElement() children = elisa_item.get('children',[]) if BrowseFlag == 'BrowseDirectChildren': if RequestedCount == 0: childs = children[StartingIndex:] else: childs = children[StartingIndex:StartingIndex+RequestedCount] for child in childs: if child is not None: item = build_upnp_item(child) if item: didl.addItem(item) total = len(children) elif elisa_item: item = build_upnp_item(elisa_item) if item: didl.addItem(item) total = 1 r = { 'Result': didl.toString(), 'TotalMatches': total, 'NumberReturned': didl.numItems()} if hasattr(elisa_item, 'update_id'): r['UpdateID'] = item.update_id else: r['UpdateID'] = self.update_id return r def errback(r): return failure.Failure(errorCode(701)) id = ObjectID if id == 0: id = self.root_id store = self.get_store() dfr = store.addCallback(lambda object: object.callRemote('get_cache_manager')) dfr.addErrback(errback) dfr.addCallback(lambda cache_mgr: cache_mgr.callRemote("get_media_node_with_id", id)) dfr.addCallback(got_result) return dfr if __name__ == '__main__': def main(): p = 'localhost' def got_result(result): print result f = MediaStore(None,'my media',p, 'http://localhost/',()) dfr = f.upnp_Browse(BrowseFlag='BrowseDirectChildren', RequestedCount=0, StartingIndex=0, ObjectID=23, SortCriteria='*', Filter='') dfr.addCallback(got_result) dfr.addCallback(lambda _: reactor.stop()) reactor.callLater(0.1, main) reactor.run() Coherence-0.6.6.2/coherence/backends/itv_storage.py0000644000175000017500000002605211317660740020313 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Backend to retrieve the video streams from Shoutcast TV # Copyright 2007, Frank Scholz # Copyright 2008,2009 Jean-Michel Sizun from twisted.internet import defer,reactor from twisted.web import server from coherence.upnp.core import utils from coherence.upnp.core import DIDLLite from coherence.extern.simple_plugin import Plugin from coherence import log from coherence.backend import BackendItem, BackendStore import zlib from coherence.backend import BackendStore,BackendItem ROOT_CONTAINER_ID = 0 SHOUTCAST_WS_URL = 'http://www.shoutcast.com/sbin/newtvlister.phtml?service=winamp2&no_compress=1' SHOUTCAST_TUNEIN_URL = 'http://www.shoutcast.com/sbin/tunein-tvstation.pls?id=%s' VIDEO_MIMETYPE = 'video/x-nsv' class ProxyStream(utils.ReverseProxyUriResource, log.Loggable): logCategory = 'itv' stream_url = None def __init__(self, uri): self.stream_url = None utils.ReverseProxyUriResource.__init__(self, uri) def requestFinished(self, result): """ self.connection is set in utils.ReverseProxyResource.render """ self.info("ProxyStream requestFinished") if self.connection is not None: self.connection.transport.loseConnection() def render(self, request): if self.stream_url is None: def got_playlist(result): if result is None: self.warning('Error to retrieve playlist - nothing retrieved') return requestFinished(result) result = result[0].split('\n') for line in result: if line.startswith('File1='): self.stream_url = line[6:].split(";")[0] break #print "stream URL:", self.stream_url if self.stream_url is None: self.warning('Error to retrieve playlist - inconsistent playlist file') return requestFinished(result) #self.resetUri(self.stream_url) request.uri = self.stream_url return self.render(request) def got_error(error): self.warning(error) return None playlist_url = self.uri #print "playlist URL:", playlist_url d = utils.getPage(playlist_url, timeout=20) d.addCallbacks(got_playlist, got_error) return server.NOT_DONE_YET if request.clientproto == 'HTTP/1.1': self.connection = request.getHeader('connection') if self.connection: tokens = map(str.lower, self.connection.split(' ')) if 'close' in tokens: d = request.notifyFinish() d.addBoth(self.requestFinished) else: d = request.notifyFinish() d.addBoth(self.requestFinished) return utils.ReverseProxyUriResource.render(self, request) class Container(BackendItem): def __init__(self, id, store, parent_id, title): self.url = store.urlbase+str(id) self.parent_id = parent_id self.id = id self.name = title self.mimetype = 'directory' self.update_id = 0 self.children = [] self.store = store self.item = DIDLLite.Container(self.id, self.parent_id, self.name) self.item.childCount = 0 self.sorted = False def add_child(self, child): id = child.id if isinstance(child.id, basestring): _,id = child.id.split('.') if self.children is None: self.children = [] self.children.append(child) self.item.childCount += 1 self.sorted = False def get_children(self, start=0, end=0): if self.sorted == False: def childs_sort(x,y): r = cmp(x.name,y.name) return r self.children.sort(cmp=childs_sort) self.sorted = True if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): if self.children is None: return 0 return len(self.children) def get_path(self): return self.url def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class ITVItem(BackendItem): logCategory = 'itv' def __init__(self, store, id, obj, parent): self.parent = parent self.id = id self.name = obj.get('name') self.mimetype = obj.get('mimetype') self.description = None self.date = None self.item = None self.duration = None self.store = store self.url = self.store.urlbase + str(self.id) self.stream_url = obj.get('url') self.location = ProxyStream(self.stream_url) def get_item(self): if self.item == None: self.item = DIDLLite.VideoItem(self.id, self.parent.id, self.name) self.item.description = self.description self.item.date = self.date res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) res.duration = self.duration #res.size = 0 #None self.item.res.append(res) return self.item def get_path(self): return self.url class ITVStore(BackendStore): logCategory = 'itv' implements = ['MediaServer'] description = ('Shoutcast TV', 'cexposes the list of video streams from Shoutcast TV.', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'genrelist','text':'Server URL','type':'string', 'default':SHOUTCAST_WS_URL} ] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.config = kwargs self.name = kwargs.get('name','iTV') self.update_id = 0 self.store = {} self.wmc_mapping = {'4': 1000} self.shoutcast_ws_url = self.config.get('genrelist',SHOUTCAST_WS_URL) self.init_completed() def __repr__(self): return self.__class__.__name__ def storeItem(self, parent, item, id): self.store[id] = item parent.add_child(item) def appendGenre( self, genre, parent): id = self.getnextID() item = Container(id, self, -1, genre) self.storeItem(parent, item, id) return item def appendFeed( self, obj, parent): id = self.getnextID() item = ITVItem(self, id, obj, parent) self.storeItem(parent, item, id) return item def len(self): return len(self.store) def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: return self.store[int(id)] except (ValueError,KeyError): pass return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:%s:*' % VIDEO_MIMETYPE, ], default=True) rootItem = Container(ROOT_CONTAINER_ID,self,-1, self.name) self.store[ROOT_CONTAINER_ID] = rootItem self.retrieveList_attemptCount = 0 self.retrieveList(rootItem) def retrieveList(self, parent): self.info("Retrieving Shoutcast TV listing...") def got_page(result): if self.retrieveList_attemptCount == 0: self.info("Connection to ShoutCast service successful for TV listing") else: self.warning("Connection to ShoutCast service successful for TV listing after %d attempts." % self.retrieveList_attemptCount) result = result[0] result = utils.parse_xml(result, encoding='utf-8') genres = [] stations = {} for stationResult in result.findall('station'): mimetype = VIDEO_MIMETYPE station_id = stationResult.get('id') bitrate = stationResult.get('br') rating = stationResult.get('rt') name = stationResult.get('name').encode('utf-8') genre = stationResult.get('genre') url = SHOUTCAST_TUNEIN_URL % (station_id) if genres.count(genre) == 0: genres.append(genre) sameStation = stations.get(name) if sameStation == None or bitrate>sameStation['bitrate']: station = {'name':name, 'station_id':station_id, 'mimetype':mimetype, 'id':station_id, 'url':url, 'bitrate':bitrate, 'rating':rating, 'genre':genre } stations[name] = station genreItems = {} for genre in genres: genreItem = self.appendGenre(genre, parent) genreItems[genre] = genreItem for station in stations.values(): genre = station.get('genre') parentItem = genreItems[genre] self.appendFeed({'name':station.get('name'), 'mimetype':station['mimetype'], 'id':station.get('station_id'), 'url':station.get('url')}, parentItem) def got_error(error): self.warning("Connection to ShoutCast service failed. Will retry in 5s!") self.debug("%r", error.getTraceback()) # will retry later self.retrieveList_attemptCount += 1 reactor.callLater(5, self.retrieveList, parent=parent) d = utils.getPage(self.shoutcast_ws_url) d.addCallbacks(got_page, got_error) Coherence-0.6.6.2/coherence/backends/gallery2_storage.py0000644000175000017500000003150311317660740021227 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Frank Scholz # Copyright 2008, Jean-Michel Sizun from twisted.internet import defer from coherence.upnp.core import utils from coherence.upnp.core.utils import ReverseProxyUriResource from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement from coherence.backend import BackendStore from coherence.backend import BackendItem from urlparse import urlsplit from coherence.extern.galleryremote import Gallery class ProxyGallery2Image(ReverseProxyUriResource): def __init__(self, uri): ReverseProxyUriResource.__init__(self, uri) def render(self, request): del request.received_headers['referer'] return ReverseProxyUriResource.render(self, request) class Gallery2Item(BackendItem): logCategory = 'gallery2_item' def __init__(self, id, obj, parent, mimetype, urlbase, UPnPClass,update=False): self.id = id self.name = obj.get('title')#.encode('utf-8') if self.name == None: self.name = obj.get('name') if self.name == None: self.name = id self.mimetype = mimetype self.gallery2_id = obj.get('gallery2_id') self.parent = parent if parent: parent.add_child(self,update=update) if parent == None: parent_id = -1 else: parent_id = parent.get_id() self.item = UPnPClass(id, parent_id, self.name) if isinstance(self.item, Container): self.item.childCount = 0 self.child_count = 0 self.children = None if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' if parent == None: self.gallery2_url = None self.url = urlbase + str(self.id) elif self.mimetype == 'directory': #self.gallery2_url = parent.store.get_url_for_album(self.gallery2_id) self.url = urlbase + str(self.id) else: self.gallery2_url = parent.store.get_url_for_image(self.gallery2_id) self.url = urlbase + str(self.id) self.location = ProxyGallery2Image(self.gallery2_url) if self.mimetype == 'directory': self.update_id = 0 else: res = Resource(self.gallery2_url, 'http-get:*:%s:*' % self.mimetype) res.size = None self.item.res.append(res) def remove(self): if self.parent: self.parent.remove_child(self) del self.item def add_child(self, child, update=False): if self.children == None: self.children = [] self.children.append(child) self.child_count += 1 if isinstance(self.item, Container): self.item.childCount += 1 if update == True: self.update_id += 1 def remove_child(self, child): #self.info("remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name())) if child in self.children: self.child_count -= 1 if isinstance(self.item, Container): self.item.childCount -= 1 self.children.remove(child) self.update_id += 1 def get_children(self,start=0,request_count=0): def process_items(result = None): if self.children == None: return [] if request_count == 0: return self.children[start:] else: return self.children[start:request_count] if (self.children == None): d = self.store.retrieveItemsForAlbum(self.gallery2_id, self) d.addCallback(process_items) return d else: return process_items() def get_child_count(self): return self.child_count def get_id(self): return self.id def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): return self.url def get_name(self): return self.name def get_parent(self): return self.parent def get_item(self): return self.item def get_xml(self): return self.item.toString() def __repr__(self): return 'id: ' + str(self.id) class Gallery2Store(BackendStore): logCategory = 'gallery2_store' implements = ['MediaServer'] description = ('Gallery2', 'exposes the photos from a Gallery2 photo repository.', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'server_url','text':'Server URL:','type':'string'}, {'option':'username','text':'User ID:','type':'string','group':'User Account'}, {'option':'password','text':'Password:','type':'string','group':'User Account'}, ] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.config = kwargs self.name = kwargs.get('name','gallery2Store') self.wmc_mapping = {'16': 1000} self.update_id = 0 self.store = {} self.gallery2_server_url = self.config.get('server_url', 'http://localhost/gallery2') self.gallery2_username = self.config.get('username',None) self.gallery2_password = self.config.get('password',None) self.store[1000] = Gallery2Item( 1000, {'title':'Gallery2','gallery2_id':'0','mimetype':'directory'}, None, 'directory', self.urlbase,Container,update=True) self.store[1000].store = self self.gallery2_remote = Gallery(self.gallery2_server_url, 2) if not None in [self.gallery2_username, self.gallery2_password]: d = self.gallery2_remote.login(self.gallery2_username, self.gallery2_password) d.addCallback(lambda x : self.retrieveAlbums('0', self.store[1000])) d.addCallback(self.init_completed) else: d = self.retrieveAlbums('0', self.store[1000]) d.addCallback(self.init_completed) def __repr__(self): return self.__class__.__name__ def append( self, obj, parent): if isinstance(obj, basestring): mimetype = 'directory' else: mimetype = obj['mimetype'] UPnPClass = classChooser(mimetype) id = self.getnextID() update = False #if hasattr(self, 'update_id'): # update = True item = Gallery2Item( id, obj, parent, mimetype, self.urlbase, UPnPClass, update=update) self.store[id] = item self.store[id].store = self if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) #if parent: # value = (parent.get_id(),parent.get_update_id()) # if self.server: # self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) if mimetype == 'directory': return self.store[id] return None def len(self): return len(self.store) def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: id = int(id) except ValueError: id = 1000 if id == 0: id = 1000 try: return self.store[id] except: return None def getnextID(self): self.next_id += 1 return self.next_id def get_url_for_image(self, gallery2_id): url = self.gallery2_remote.get_URL_for_image(gallery2_id) return url def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:*,' 'http-get:*:image/gif:*,' 'http-get:*:image/png:*', default=True) def retrieveAlbums(self, album_gallery2_id, parent): d = self.gallery2_remote.fetch_albums() def gotAlbums (albums): if albums : albums = [album for album in albums.values() if album.get('parent') == album_gallery2_id] if album_gallery2_id == '0' and len(albums) == 1: album = albums[0] self.store[1000].gallery2_id = album.get('name') self.store[1000].name = album.get('title') self.store[1000].description = album.get('summary') else: for album in albums: gallery2_id = album.get('name') parent_gallery2_id = album.get('parent') title = album.get('title') description = album.get('summary') store_item = { 'name':id, 'gallery2_id':gallery2_id, 'parent_id':parent_gallery2_id, 'title':title, 'description':description, 'mimetype':'directory', } self.append(store_item, parent) d.addCallback(gotAlbums) return d def retrieveItemsForAlbum (self, album_id, parent): # retrieve subalbums d1 = self.retrieveAlbums(album_id, parent) # retrieve images d2 = self.gallery2_remote.fetch_album_images(album_id) def gotImages(images): if images : for image in images: image_gallery2_id = image.get('name') parent_gallery2_id = image.get('parent') thumbnail_gallery2_id = image.get('thumbName') resized_gallery2_id = image.get('resizedName') title = image.get('title') description = image.get('description') gallery2_id = resized_gallery2_id if gallery2_id == '': gallery2_id = image_gallery2_id store_item = { 'name':id, 'gallery2_id':gallery2_id, 'parent_id':parent_gallery2_id, 'thumbnail_gallery2_id':thumbnail_gallery2_id, 'title':title, 'description':description, 'mimetype':'image/jpeg', } self.append(store_item, parent) d2.addCallback(gotImages) dl = defer.DeferredList([d1,d2]) return dl def main(): f = Gallery2Store(None) def got_upnp_result(result): print "upnp", result f.upnp_init() if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/mediadb_storage.py0000644000175000017500000006043711317660740021103 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007,2008, Frank Scholz """ MediaStore A MediaServer with a database backend, exposes its content in All, Albums and Artists containers. Serves cover art with the Album object, and keeps references to the MusicBrainz DB - http://musicbrainz.org/ Should not scan for files, but gets feeded with proper tagged ones via some import tool or/and allow imports via the web-UI. depends on: for the sqlite db handling: Axiom - http://divmod.org/trac/wiki/DivmodAxiom Epsilon - http://divmod.org/trac/wiki/DivmodEpsilon for id3 tag extraction: libmtag - http://code.google.com/p/libmtag/ taglib - http://developer.kde.org/~wheeler/taglib.html or pyid3lib - http://pyid3lib.sourceforge.net/doc.html or tagpy - http://news.tiker.net/software/tagpy taglib - http://developer.kde.org/~wheeler/taglib.html CoversByAmazon - https://coherence.beebits.net/browser/trunk/coherence/extern/covers_by_amazon.py """ import os, shutil import string import urllib from urlparse import urlsplit from axiom import store, item, attributes from epsilon.extime import Time import coherence.extern.louie as louie from twisted.internet import reactor, defer from twisted.python.filepath import FilePath from coherence.upnp.core import DIDLLite from coherence.extern.covers_by_amazon import CoverGetter from coherence.backend import BackendItem, BackendStore KNOWN_AUDIO_TYPES = {'.mp3':'audio/mpeg', '.ogg':'application/ogg', '.mpc':'audio/x-musepack', '.flac':'audio/x-wavpack', '.wv':'audio/x-wavpack', '.m4a':'audio/mp4',} def _dict_from_tags(tag): tags = {} tags['artist'] = tag.artist.strip() tags['album'] = tag.album.strip() tags['title'] = tag.title.strip() if type(tag.track) == int: tags['track'] = tag.track elif type(tag.track) in (str, unicode): tags['track'] = int(tag.track.strip()) else: tags['track'] = tag.track[0] for key in ('artist', 'album', 'title'): value = tags.get(key, u'') if isinstance(value, unicode): tags[key] = value.encode('utf-8') return tags try: import libmtag def get_tags(filename): audio_file = libmtag.File(filename) tags = {} tags['artist'] = audio_file.tag().get('artist').strip() tags['album'] = audio_file.tag().get('album').strip() tags['title'] = audio_file.tag().get('title').strip() tags['track'] = audio_file.tag().get('track').strip() return tags except ImportError: try: import pyid3lib def get_tags(filename): audio_file = pyid3lib.tag(filename) return _dict_from_tags(audio_file) except ImportError: try: import tagpy def get_tags(filename): audio_file = tagpy.FileRef(filename) return _dict_from_tags(audio_file.tag()) except ImportError: get_tags = None if not get_tags: raise ImportError, "we need some installed id3 tag library for this backend: python-tagpy, pyid3lib or libmtag" MEDIA_DB = 'tests/media.db' ROOT_CONTAINER_ID = 0 AUDIO_CONTAINER = 100 AUDIO_ALL_CONTAINER_ID = 101 AUDIO_ARTIST_CONTAINER_ID = 102 AUDIO_ALBUM_CONTAINER_ID = 103 def sanitize(filename): badchars = ''.join(set(string.punctuation) - set('-_+.~')) f = unicode(filename.lower()) for old, new in ((u'ä','ae'),(u'ö','oe'),(u'ü','ue'),(u'ß','ss')): f = f.replace(unicode(old),unicode(new)) f = f.replace(badchars, '_') return f class Container(BackendItem): get_path = None def __init__(self, id, parent_id, name, children_callback=None,store=None,play_container=False): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.store = store self.play_container = play_container self.update_id = 0 if children_callback != None: self.children = children_callback else: self.children = [] def add_child(self, child): self.children.append(child) def get_children(self,start=0,request_count=0): if callable(self.children): children = self.children() else: children = self.children if request_count == 0: return children[start:] else: return children[start:request_count] def get_child_count(self): if callable(self.children): return len(self.children()) else: return len(self.children) def get_item(self): item = DIDLLite.Container(self.id, self.parent_id,self.name) item.childCount = self.get_child_count() if self.store and self.play_container == True: if item.childCount > 0: res = DIDLLite.PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=self.get_children()[0].get_id()) item.res.append(res) return item def get_name(self): return self.name def get_id(self): return self.id class Artist(item.Item,BackendItem): """ definition for an artist """ schemaVersion = 1 typeName = 'artist' mimetype = 'directory' name = attributes.text(allowNone=False, indexed=True) musicbrainz_id = attributes.text() get_path = None def get_artist_all_tracks(self,start=0,request_count=0): children = [x[1] for x in list(self.store.query((Album,Track), attributes.AND(Album.artist == self, Track.album == Album.storeID), sort=(Album.title.ascending,Track.track_nr.ascending) ))] if request_count == 0: return children[start:] else: return children[start:request_count] def get_children(self,start=0,request_count=0): all_id = 'artist_all_tracks_%d' % (self.storeID+1000) self.store.containers[all_id] = \ Container( all_id, self.storeID+1000, 'All tracks of %s' % self.name, children_callback=self.get_artist_all_tracks, store=self.store,play_container=True) children = [self.store.containers[all_id]] + list(self.store.query(Album, Album.artist == self,sort=Album.title.ascending)) if request_count == 0: return children[start:] else: return children[start:request_count] def get_child_count(self): return len(list(self.store.query(Album, Album.artist == self))) + 1 def get_item(self): item = DIDLLite.MusicArtist(self.storeID+1000, AUDIO_ARTIST_CONTAINER_ID, self.name) item.childCount = self.get_child_count() return item def get_id(self): return self.storeID + 1000 def get_name(self): return self.name def __repr__(self): return '' \ % (self.storeID, self.name.encode('ascii', 'ignore'), self.musicbrainz_id) class Album(item.Item,BackendItem): """ definition for an album """ schemaVersion = 1 typeName = 'album' mimetype = 'directory' title = attributes.text(allowNone=False, indexed=True) musicbrainz_id = attributes.text() artist = attributes.reference(allowNone=False, indexed=True) cd_count = attributes.integer(default=1) cover = attributes.text(default=u'') get_path = None def get_children(self,start=0,request_count=0): children = list(self.store.query(Track, Track.album == self,sort=Track.track_nr.ascending)) if request_count == 0: return children[start:] else: return children[start:request_count] def get_child_count(self): return len(list(self.store.query(Track, Track.album == self))) def get_item(self): item = DIDLLite.MusicAlbum(self.storeID+1000, AUDIO_ALBUM_CONTAINER_ID, self.title) item.artist = self.artist.name item.childCount = self.get_child_count() if len(self.cover)>0: _,ext = os.path.splitext(self.cover) item.albumArtURI = ''.join((self.store.urlbase,str(self.get_id()),'?cover',ext)) if self.get_child_count() > 0: res = DIDLLite.PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=self.get_children()[0].get_id()) item.res.append(res) return item def get_id(self): return self.storeID + 1000 def get_name(self): return self.title def get_cover(self): return self.cover def __repr__(self): return '' \ % (self.storeID, self.title.encode('ascii', 'ignore'), self.artist.name.encode('ascii', 'ignore'), self.cd_count, self.cover.encode('ascii', 'ignore'), self.musicbrainz_id) class Track(item.Item,BackendItem): """ definition for a track """ schemaVersion = 1 typeName = 'track' title = attributes.text(allowNone=False, indexed=True) track_nr = attributes.integer(default=1,allowNone=False) cd_nr = attributes.integer(default=1,allowNone=False) album = attributes.reference(allowNone=False, indexed=True) location = attributes.text(allowNone=False) rating=attributes.integer(default=0,allowNone=False) last_played=attributes.timestamp() added=attributes.timestamp(default=Time(),allowNone=False) def get_children(self,start=0,request_count=0): return [] def get_child_count(self): return 0 def get_item(self): item = DIDLLite.MusicTrack(self.storeID+1000, self.album.storeID+1000,self.title) item.artist = self.album.artist.name item.album = self.album.title if self.album.cover != '': _,ext = os.path.splitext(self.album.cover) """ add the cover image extension to help clients not reacting on the mimetype """ item.albumArtURI = ''.join((self.store.urlbase,str(self.storeID+1000),'?cover',ext)) item.originalTrackNumber = self.track_nr item.server_uuid = str(self.store.server.uuid)[5:] _,host_port,_,_,_ = urlsplit(self.store.urlbase) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) else: host = host_port _,ext = os.path.splitext(self.location) ext = ext.lower() try: mimetype = KNOWN_AUDIO_TYPES[ext] except KeyError: mimetype = 'audio/mpeg' statinfo = os.stat(self.location) res = DIDLLite.Resource('file://'+self.location, 'internal:%s:%s:*' % (host,mimetype)) try: res.size = statinfo.st_size except: res.size = 0 item.res.append(res) url = self.store.urlbase + str(self.storeID+1000) + ext res = DIDLLite.Resource(url, 'http-get:*:%s:*' % mimetype) try: res.size = statinfo.st_size except: res.size = 0 item.res.append(res) #if self.store.server.coherence.config.get('transcoding', 'no') == 'yes': # if mimetype in ('audio/mpeg', # 'application/ogg','audio/ogg', # 'audio/x-m4a', # 'application/x-flac'): # dlna_pn = 'DLNA.ORG_PN=LPCM' # dlna_tags = DIDLLite.simple_dlna_tags[:] # dlna_tags[1] = 'DLNA.ORG_CI=1' # #dlna_tags[2] = 'DLNA.ORG_OP=00' # new_res = DIDLLite.Resource(url+'?transcoded=lpcm', # 'http-get:*:%s:%s' % ('audio/L16;rate=44100;channels=2', ';'.join([dlna_pn]+dlna_tags))) # new_res.size = None # item.res.append(new_res) # # if mimetype != 'audio/mpeg': # new_res = DIDLLite.Resource(url+'?transcoded=mp3', # 'http-get:*:%s:*' % 'audio/mpeg') # new_res.size = None # item.res.append(new_res) try: # FIXME: getmtime is deprecated in Twisted 2.6 item.date = datetime.fromtimestamp(statinfo.st_mtime) except: item.date = None return item def get_path(self): return self.location.encode('utf-8') def get_id(self): return self.storeID + 1000 def get_name(self): return self.title def get_url(self): return self.store.urlbase + str(self.storeID+1000).encode('utf-8') def get_cover(self): return self.album.cover def __repr__(self): return '' \ % (self.storeID, self.title.encode('ascii', 'ignore'), self.track_nr, self.album.title.encode('ascii', 'ignore'), self.album.artist.name.encode('ascii', 'ignore'), self.location.encode('ascii', 'ignore')) class Playlist(item.Item,BackendItem): """ definition for a playlist - has a name - and references to tracks - that reference list must keep its ordering and items can be inserted at any place, moved up or down or deleted """ schemaVersion = 1 typeName = '' name = attributes.text(allowNone=False, indexed=True) # references to tracks get_path = None class MediaStore(BackendStore): logCategory = 'media_store' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.info("MediaStore __init__") self.update_id = 0 self.medialocation = kwargs.get('medialocation','tests/content/audio') self.coverlocation = kwargs.get('coverlocation',None) if self.coverlocation is not None and self.coverlocation[-1] != '/': self.coverlocation = self.coverlocation + '/' self.mediadb = kwargs.get('mediadb',MEDIA_DB) self.name = kwargs.get('name','MediaStore') self.containers = {} self.containers[ROOT_CONTAINER_ID] = \ Container( ROOT_CONTAINER_ID,-1, self.name) self.wmc_mapping.update({'4': lambda : self.get_by_id(AUDIO_ALL_CONTAINER_ID), # all tracks '7': lambda : self.get_by_id(AUDIO_ALBUM_CONTAINER_ID), # all albums '6': lambda : self.get_by_id(AUDIO_ARTIST_CONTAINER_ID), # all artists }) louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def walk(self, path): #print "walk", path if os.path.exists(path): for filename in os.listdir(path): if os.path.isdir(os.path.join(path,filename)): self.walk(os.path.join(path,filename)) else: _,ext = os.path.splitext(filename) if ext.lower() in KNOWN_AUDIO_TYPES: self.filelist.append(os.path.join(path,filename)) def get_music_files(self, musiclocation): if not isinstance(musiclocation, list): musiclocation = [musiclocation] self.filelist = [] for path in musiclocation: self.walk(path) def check_for_cover_art(path): #print "check_for_cover_art", path """ let's try to find in the current directory some jpg file, or png if the jpg search fails, and take the first one that comes around """ jpgs = [i for i in os.listdir(path) if os.path.splitext(i)[1] in ('.jpg', '.JPG')] try: return unicode(jpgs[0]) except IndexError: pngs = [i for i in os.listdir(path) if os.path.splitext(i)[1] in ('.png', '.PNG')] try: return unicode(pngs[0]) except IndexError: return u'' def got_tags(tags, file): #print "got_tags", tags album=tags.get('album', '') artist=tags.get('artist', '') title=tags.get('title', '') track=tags.get('track', 0) if len(artist) == 0: return; artist = u'UNKNOWN_ARTIST' if len(album) == 0: return; album = u'UNKNOWN_ALBUM' if len(title) == 0: return; title = u'UNKNOWN_TITLE' #print "Tags:", file, album, artist, title, track artist_ds = self.db.findOrCreate(Artist, name=unicode(artist,'utf8')) album_ds = self.db.findOrCreate(Album, title=unicode(album,'utf8'), artist=artist_ds) if len(album_ds.cover) == 0: dirname = unicode(os.path.dirname(file),'utf-8') album_ds.cover = check_for_cover_art(dirname) if len(album_ds.cover) > 0: filename = u"%s - %s" % ( album_ds.artist.name, album_ds.title) filename = sanitize(filename + os.path.splitext(album_ds.cover)[1]) filename = os.path.join(dirname,filename) shutil.move(os.path.join(dirname,album_ds.cover),filename) album_ds.cover = filename #print album_ds.cover track_ds = self.db.findOrCreate(Track, title=unicode(title,'utf8'), track_nr=int(track), album=album_ds, location=unicode(file,'utf8')) for file in self.filelist: d = defer.maybeDeferred(get_tags,file) d.addBoth(got_tags, file) def show_db(self): for album in list(self.db.query(Album,sort=Album.title.ascending)): print album for track in list(self.db.query(Track, Track.album == album,sort=Track.track_nr.ascending)): print track def show_albums(self): for album in list(self.db.query(Album,sort=Album.title.ascending)): print album def show_artists(self): for artist in list(self.db.query(Artist,sort=Artist.name.ascending)): print artist def show_tracks_by_artist(self, artist_name): """ artist = self.db.query(Artist,Artist.name == artist_name) artist = list(artist)[0] for album in list(self.db.query(Album, Album.artist == artist)): for track in list(self.db.query(Track, Track.album == album,sort=Track.title.ascending)): print track """ for track in [x[2] for x in list(self.db.query((Artist,Album,Track), attributes.AND(Artist.name == artist_name, Album.artist == Artist.storeID, Track.album == Album.storeID), sort=(Track.title.ascending) ))]: print track def show_tracks_by_title(self, title_or_part): for track in list(self.db.query(Track, Track.title.like(u'%',title_or_part,u'%'),sort=Track.title.ascending)): print track def show_tracks_to_filename(self, title_or_part): for track in list(self.db.query(Track, Track.title.like(u'%',title_or_part,u'%'),sort=Track.title.ascending)): print track.title, track.album.artist.name, track.track_nr _,ext = os.path.splitext(track.path) f = "%02d - %s - %s%s" % ( track.track_nr, track.album.artist.name, track.title, ext) f = sanitize(f) print f def get_album_covers(self): for album in list(self.db.query(Album, Album.cover == u'')): print "missing cover for:", album.artist.name, album.title filename = "%s - %s" % ( album.artist.name, album.title) filename = sanitize(filename) if self.coverlocation is not None: cover_path = os.path.join(self.coverlocation,filename +'.jpg') if os.path.exists(cover_path) is True: print "cover found:", cover_path album.cover = cover_path else: def got_it(f,a): print "cover saved:",f, a.title a.cover = f aws_key = '1XHSE4FQJ0RK0X3S9WR2' CoverGetter(cover_path,aws_key, callback=(got_it,(album)), artist=album.artist.name, title=album.title) def get_by_id(self,id): self.info("get_by_id %s" % id) if isinstance(id, basestring): id = id.split('@',1) id = id[0].split('.')[0] if isinstance(id, basestring) and id.startswith('artist_all_tracks_'): try: return self.containers[id] except: return None try: id = int(id) except ValueError: id = 1000 try: item = self.containers[id] except: try: item = self.db.getItemByID(id-1000) except: item = None self.info("get_by_id found", item) return item def upnp_init(self): #print "MediaStore upnp_init" db_is_new = False if os.path.exists(self.mediadb) is False: db_is_new = True self.db = store.Store(self.mediadb) self.containers[AUDIO_ALL_CONTAINER_ID] = \ Container( AUDIO_ALL_CONTAINER_ID,ROOT_CONTAINER_ID, 'All tracks', children_callback=lambda :list(self.db.query(Track,sort=Track.title.ascending)), store=self,play_container=True) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALL_CONTAINER_ID]) self.containers[AUDIO_ALBUM_CONTAINER_ID] = \ Container( AUDIO_ALBUM_CONTAINER_ID,ROOT_CONTAINER_ID, 'Albums', children_callback=lambda :list(self.db.query(Album,sort=Album.title.ascending))) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALBUM_CONTAINER_ID]) self.containers[AUDIO_ARTIST_CONTAINER_ID] = \ Container( AUDIO_ARTIST_CONTAINER_ID,ROOT_CONTAINER_ID, 'Artists', children_callback=lambda :list(self.db.query(Artist,sort=Artist.name.ascending))) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ARTIST_CONTAINER_ID]) self.db.server = self.server self.db.urlbase = self.urlbase self.db.containers = self.containers if db_is_new is True: self.get_music_files(self.medialocation) self.get_album_covers() #self.show_db() #self.show_artists() #self.show_albums() #self.show_tracks_by_artist(u'Meat Loaf') #self.show_tracks_by_artist(u'Beyonce') #self.show_tracks_by_title(u'Bad') #self.show_tracks_to_filename(u'säen') self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['internal:%s:audio/mpeg:*' % self.server.coherence.hostname, 'http-get:*:audio/mpeg:*', 'internal:%s:application/ogg:*' % self.server.coherence.hostname, 'http-get:*:application/ogg:*'], default=True) if __name__ == '__main__': from twisted.internet import reactor from twisted.internet import task def run(): m = MediaStore(None, medialocation='/data/audio/music', coverlocation='/data/audio/covers', mediadb='/tmp/media.db') m.upnp_init() reactor.callWhenRunning(run) reactor.run() Coherence-0.6.6.2/coherence/backends/yamj_storage.py0000644000175000017500000003261511317660740020453 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # a backend to expose a YMAJ library via UPnP # see http://code.google.com/p/moviejukebox/ for more info on YAMJ (Yet Another Movie Jukebox): # Copyright 2007, Frank Scholz # Copyright 2009, Jean-Michel Sizun # # TODO: add comments import urllib from coherence.upnp.core import DIDLLite from coherence.backend import BackendStore, BackendItem, Container, \ LazyContainer, AbstractBackendStore from coherence import log from coherence.upnp.core.utils import getPage from coherence.extern.et import parse_xml import mimetypes mimetypes.init() mimetypes.add_type('audio/x-m4a', '.m4a') mimetypes.add_type('video/mp4', '.mp4') mimetypes.add_type('video/mpegts', '.ts') mimetypes.add_type('video/divx', '.divx') mimetypes.add_type('video/divx', '.avi') mimetypes.add_type('video/x-matroska', '.mkv') class MovieItem(BackendItem): def __init__(self, movie, store, title = None, url = None): self.movie_id = 'UNK' if movie.find('./id') is not None: self.movie_id = movie.find('./id').text self.title = movie.find('./title').text self.baseFilename = movie.find('./baseFilename').text self.plot = movie.find('./plot').text self.outline = movie.find('./outline').text self.posterFilename = movie.find('./posterFile').text self.thumbnailFilename = movie.find('./thumbnail').text self.rating = movie.find('./rating').text self.director = movie.find('./director').text self.genres = movie.findall('./genres/genre') self.actors = movie.findall('./cast/actor') self.year = movie.find('year').text self.audioChannels = movie.find('audioChannels').text self.resolution = movie.find('resolution').text self.language = movie.find('language').text self.season = movie.find('season').text if title is not None: self.upnp_title = title else: self.upnp_title = self.title if url is not None: self.movie_url = url else: self.movie_url = movie.find('./files/file/fileURL').text self.posterURL = "%s/%s" % (store.jukebox_url, self.posterFilename) self.thumbnailURL = "%s/%s" % (store.jukebox_url, self.thumbnailFilename) #print self.movie_id, self.title, self.url, self.posterURL self.str_genres = [] for genre in self.genres: self.str_genres.append(genre.text) self.str_actors = [] for actor in self.actors: self.str_actors.append(actor.text) url_mimetype,_ = mimetypes.guess_type(self.movie_url,strict=False) if url_mimetype == None: url_mimetype = "video" self.name = self.title self.duration = None self.size = None self.mimetype = url_mimetype self.item = None def get_item(self): if self.item == None: upnp_id = self.get_id() upnp_parent_id = self.parent.get_id() self.item = DIDLLite.Movie(upnp_id, upnp_parent_id, self.upnp_title) self.item.album = None self.item.albumArtURI = self.posterURL self.item.artist = None self.item.creator = self.director self.item.date = self.year self.item.description = self.plot self.item.director = self.director self.item.longDescription = self.outline self.item.originalTrackNumber = None self.item.restricted = None self.item.title = self.upnp_title self.item.writeStatus = "PROTECTED" self.item.icon = self.thumbnailURL self.item.genre = None self.item.genres = self.str_genres self.item.language = self.language self.item.actors = self.str_actors res = DIDLLite.Resource(self.movie_url, 'http-get:*:%s:*' % self.mimetype) res.duration = self.duration res.size = self.size res.nrAudioChannels = self.audioChannels res.resolution = self.resolution self.item.res.append(res) return self.item def get_path(self): return self.movie_url def get_id(self): return self.storage_id class YamjStore(AbstractBackendStore): logCategory = 'yamj_store' implements = ['MediaServer'] description = ('YAMJ', 'exposes the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library.', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'refresh','text':'Refresh period','type':'string'}, {'option':'yamj_url','text':'Library URL:','type':'string', 'help':'URL to the library root directory.'} ] def __init__(self, server, **kwargs): AbstractBackendStore.__init__(self, server, **kwargs) self.name = kwargs.get('name','YAMJ') self.yamj_url = kwargs.get('yamj_url',"http://localhost/yamj"); self.jukebox_url = self.yamj_url + "/Jukebox/" self.refresh = int(kwargs.get('refresh',60))*60 self.nbMoviesPerFile = None rootItem = Container(None, self.name) self.set_root_item(rootItem) d = self.retrieveCategories(rootItem) def upnp_init(self): self.current_connection_id = None if self.server: self.server.presentationURL = self.yamj_url self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['internal:%s:video/mp4:*' % self.server.coherence.hostname, 'http-get:*:video/mp4:*', 'internal:%s:video/x-msvideo:*' % self.server.coherence.hostname, 'http-get:*:video/x-msvideo:*', 'internal:%s:video/mpeg:*' % self.server.coherence.hostname, 'http-get:*:video/mpeg:*', 'internal:%s:video/avi:*' % self.server.coherence.hostname, 'http-get:*:video/avi:*', 'internal:%s:video/divx:*' % self.server.coherence.hostname, 'http-get:*:video/divx:*', 'internal:%s:video/quicktime:*' % self.server.coherence.hostname, 'http-get:*:video/quicktime:*'], default=True) self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) #self.server.content_directory_server.set_variable(0, 'SortCapabilities', '*') def retrieveCategories (self, parent): filepath = self.jukebox_url + "Categories.xml" dfr = getPage(filepath) def read_categories(data, parent_item, jukebox_url): #nbMoviesPerFile = 1 #int(data.find('preferences/mjb.nbThumbnailsPerPage').text) #self.debug("YMAJ: Nb Movies per file = %s" % nbMoviesPerFile) for category in data.findall('category'): type = category.get('name') category_title = type if (type != 'Other'): category_title = "By %s" % category_title categoryItem = Container(parent_item, category_title) parent_item.add_child(categoryItem) for index in category.findall('./index'): name = index.get('name') first_filename = index.text root_name = first_filename[:-2] self.debug("adding index %s:%s" % (type,name)) parent = categoryItem if (type == 'Other'): parent = parent_item indexItem = LazyContainer(parent, name, None, self.refresh, self.retrieveIndexMovies, per_page=1, name=name, root_name=root_name) parent.add_child(indexItem) self.init_completed() def fail_categories_read(f): self.warning("failure reading yamj categories (%s): %r" % (filepath,f.getErrorMessage())) return f dfr.addCallback(parse_xml) dfr.addErrback(fail_categories_read) dfr.addCallback(read_categories, parent_item=parent, jukebox_url=self.jukebox_url) dfr.addErrback(fail_categories_read) return dfr def retrieveIndexMovies (self, parent, name, root_name, per_page=10, offset=0): #print offset, per_page if self.nbMoviesPerFile is None: counter = 1 else: counter = abs(offset / self.nbMoviesPerFile) + 1 fileUrl = "%s/%s_%d.xml" % (self.jukebox_url, urllib.quote(root_name), counter) def fail_readPage(f): self.warning("failure reading yamj index (%s): %r" % (fileUrl,f.getErrorMessage())) return f def fail_parseIndex(f): self.warning("failure parsing yamj index (%s): %r" % (fileUrl,f.getErrorMessage())) return f def readIndex(data): for index in data.findall('category/index'): current = index.get('current') if (current == "true"): currentIndex = index.get('currentIndex') lastIndex = index.get('lastIndex') if (currentIndex != lastIndex): parent.childrenRetrievingNeeded = True self.debug("%s: %s/%s" % (root_name, currentIndex, lastIndex)) break movies = data.findall('movies/movie') if self.nbMoviesPerFile is None: self.nbMoviesPerFile = len(movies) for movie in movies: isSet = (movie.attrib['isSet'] == 'true') if isSet is True: # the movie corresponds to a set name = movie.find('./title').text index_name = movie.find('./baseFilename').text set_root_name = index_name[:-2] self.debug("adding set %s" % name) indexItem = LazyContainer(parent, name, None, self.refresh, self.retrieveIndexMovies, per_page=1, name=name, root_name=set_root_name) parent.add_child(indexItem, set_root_name) else: # this is a real movie movie_id = "UNK" movie_id_xml = movie.find('./id') if movie_id_xml is not None: movie_id = movie_id_xml.text files = movie.findall('./files/file') if (len(files) == 1): url = files[0].find('./fileURL').text external_id = "%s/%s" % (movie_id,url) movieItem = MovieItem(movie, self) parent.add_child(movieItem, external_id) else: name = movie.find('./title').text if name is None or name == '': name = movie.find('./baseFilename').text season = movie.find('season').text if season is not None and season != '-1': name = "%s - season %s" % (name, season) container_item = Container(parent, name) parent.add_child(container_item, name) container_item.store = self for file in files: episodeIndex = file.attrib['firstPart'] episodeTitle = file.attrib['title'] if (episodeTitle == 'UNKNOWN'): title = "%s - %s" %(name, episodeIndex) else: title = "%s - %s " % (episodeIndex, episodeTitle) episodeUrl = file.find('./fileURL').text fileItem = MovieItem(movie, self, title=title, url=episodeUrl) file_external_id = "%s/%s" % (movie_id,episodeUrl) container_item.add_child(fileItem, file_external_id) self.debug("Reading index file %s" % fileUrl) d = getPage(fileUrl) d.addCallback(parse_xml) d.addErrback(fail_readPage) d.addCallback(readIndex) d.addErrback(fail_parseIndex) return d def __repr__(self): return self.__class__.__name__ Coherence-0.6.6.2/coherence/backends/iradio_storage.py0000644000175000017500000002664511317660740020770 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Frank Scholz from twisted.internet import defer from coherence.upnp.core import utils from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement from coherence import log from coherence.backend import BackendItem, BackendStore from urlparse import urlsplit class ProxyStream(utils.ReverseProxyResource, log.Loggable): logCategory = 'iradio' def __init__(self, uri): self.uri = uri _,host_port,path,_,_ = urlsplit(uri) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 if path == '': path = '/' #print "ProxyStream init", host, port, path utils.ReverseProxyResource.__init__(self, host, port, path) def requestFinished(self, result): """ self.connection is set in utils.ReverseProxyResource.render """ self.info("ProxyStream requestFinished") self.connection.transport.loseConnection() def render(self, request): self.info("this is our render method",request.method, request.uri, request.client, request.clientproto) self.info("render", request.getAllHeaders()) if request.clientproto == 'HTTP/1.1': self.connection = request.getHeader('connection') if self.connection: tokens = map(str.lower, self.connection.split(' ')) if 'close' in tokens: d = request.notifyFinish() d.addBoth(self.requestFinished) else: d = request.notifyFinish() d.addBoth(self.requestFinished) return utils.ReverseProxyResource.render(self, request) class IRadioItem(log.Loggable): logCategory = 'iradio' def __init__(self, id, obj, parent, mimetype, urlbase, UPnPClass,update=False): self.id = id self.name = obj.get('name') self.mimetype = mimetype self.parent = parent if parent: parent.add_child(self,update=update) if parent == None: parent_id = -1 else: parent_id = parent.get_id() self.item = UPnPClass(id, parent_id, self.name) if isinstance(self.item, Container): self.item.childCount = 0 self.child_count = 0 self.children = None if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' if self.mimetype == 'directory': self.url = urlbase + str(self.id) else: self.url = urlbase + str(self.id) self.location = ProxyStream(obj.get('url')) #self.url = obj.get('url') if self.mimetype == 'directory': self.update_id = 0 else: res = Resource(self.url, 'http-get:*:%s:%s' % (obj.get('mimetype'), ';'.join(('DLNA.ORG_PN=MP3', 'DLNA.ORG_CI=0', 'DLNA.ORG_OP=01', 'DLNA.ORG_FLAGS=01700000000000000000000000000000')))) res.size = 0 #None self.item.res.append(res) def remove(self): if self.parent: self.parent.remove_child(self) del self.item def add_child(self, child, update=False): if self.children == None: self.children = [] self.children.append(child) self.child_count += 1 if isinstance(self.item, Container): self.item.childCount += 1 if update == True: self.update_id += 1 def remove_child(self, child): self.info("remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name())) if child in self.children: self.child_count -= 1 if isinstance(self.item, Container): self.item.childCount -= 1 self.children.remove(child) self.update_id += 1 def get_children(self,start=0,request_count=0): if self.children == None: def got_page(result): result = utils.parse_xml(result, encoding='utf-8') tunein = result.find('tunein') if tunein != None: tunein = tunein.get('base','/sbin/tunein-station.pls') prot,host_port,path,_,_ = urlsplit(self.store.config.get('genrelist','http://www.shoutcast.com/sbin/newxml.phtml')) tunein = prot + '://' + host_port + tunein def append_new(result, s): result = result[0].split('\n') for line in result: if line.startswith('File1='): s['url'] = line[6:] self.store.append(s,self) break l = [] for station in result.findall('station'): if station.get('mt') == 'audio/mpeg': d2 = utils.getPage('%s?id=%s' % (tunein, station.get('id')), timeout=20) d2.addCallback(append_new, {'name':station.get('name').encode('utf-8'), 'mimetype':station.get('mt'), 'id':station.get('id'), 'url':None}) d2.addErrback(got_error) l.append(d2) dl = defer.DeferredList(l) def process_items(result): self.info("process_item", result, self.children) if self.children == None: return [] if request_count == 0: return self.children[start:] else: return self.children[start:request_count] dl.addCallback(process_items) return dl def got_error(error): self.warning("connection to ShoutCast service failed! %r", error) self.debug("%r", error.getTraceback()) d = utils.getPage('%s?genre=%s' % (self.store.config.get('genrelist','http://www.shoutcast.com/sbin/newxml.phtml'),self.name)) d.addCallbacks(got_page, got_error, None, None, None, None) return d else: if request_count == 0: return self.children[start:] else: return self.children[start:request_count] def get_child_count(self): return self.child_count def get_id(self): return self.id def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): return self.url def get_name(self): return self.name def get_parent(self): return self.parent def get_item(self): return self.item def get_xml(self): return self.item.toString() def __repr__(self): return 'id: ' + str(self.id) + ' @ ' + self.url + ' ' + self.name class IRadioStore(BackendStore): logCategory = 'iradio' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.config = kwargs self.name = kwargs.get('name','iRadioStore') self.update_id = 0 self.wmc_mapping = {'4': 1000} self.store = {} self.init_completed() def __repr__(self): return self.__class__.__name__ def append( self, obj, parent): if isinstance(obj, basestring): mimetype = 'directory' else: mimetype = obj['mimetype'] UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = IRadioItem( id, obj, parent, mimetype, self.urlbase, UPnPClass, update=update) self.store[id].store = self if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) if mimetype == 'directory': return self.store[id] return None def len(self): return len(self.store) def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: id = int(id) except ValueError: id = 1000 if id == 0: id = 1000 try: return self.store[id] except: return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret def upnp_init(self): self.current_connection_id = None #parent = self.append('iRadio', None) #self.append({'name':'GrooveFM','mimetype':'audio/mpeg','url':'http://80.252.111.34:10028/'}, parent) #self.append({'name':'Dancing Queen','mimetype':'audio/mpeg','url':'http://netzflocken.de/files/dq.mp3'}, parent) parent = self.append({'name':'iRadio','mimetype':'directory'}, None) def got_page(result): result = utils.parse_xml(result, encoding='utf-8') for genre in result.findall('genre'): self.append({'name':genre.get('name').encode('utf-8'), 'mimetype':'directory', 'url':'%s?genre=%s' % (self.config.get('genrelist','http://www.shoutcast.com/sbin/newxml.phtml'),genre.get('name'))},parent) def got_error(error): self.warning("connection to ShoutCast service failed! %r", error) self.debug("%r", error.getTraceback()) utils.getPage(self.config.get('genrelist','http://www.shoutcast.com/sbin/newxml.phtml')).addCallbacks(got_page, got_error, None, None, None, None) if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:*', 'http-get:*:audio/x-scpls:*'], default=True) def main(): f = IRadioStore(None) def got_upnp_result(result): print "upnp", result f.upnp_init() #print f.store #r = f.upnp_Browse(BrowseFlag='BrowseDirectChildren', # RequestedCount=0, # StartingIndex=0, # ObjectID=0, # SortCriteria='*', # Filter='') #got_upnp_result(r) if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/feed_storage.py0000644000175000017500000001715711317660740020422 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009, Dominik Ruf from coherence.backend import BackendItem from coherence.backend import BackendStore from coherence.upnp.core import DIDLLite from coherence.upnp.core.utils import ReverseProxyUriResource from xml.etree.ElementTree import ElementTree import urllib import httplib from urlparse import urlsplit try: import feedparser except: raise ImportError(""" This backend depends on the feedparser modul. You can get it at http://www.feedparser.org/.""") MIME_TYPES_EXTENTION_MAPPING = {'mp3': 'audio/mpeg',} ROOT_CONTAINER_ID = 0 AUDIO_ALL_CONTAINER_ID = 51 AUDIO_ARTIST_CONTAINER_ID = 52 AUDIO_ALBUM_CONTAINER_ID = 53 VIDEO_FOLDER_CONTAINER_ID = 54 class RedirectingReverseProxyUriResource(ReverseProxyUriResource): def render(self, request): self.uri = self.follow_redirect(self.uri) self.resetUri(self.uri) return ReverseProxyUriResource.render(self, request) def follow_redirect(self, uri): netloc, path, query, fragment = urlsplit(uri)[1:] conn = httplib.HTTPConnection(netloc) conn.request('HEAD', '%s?%s#%s' % (path, query, fragment)) res = conn.getresponse() if(res.status == 301 or res.status == 302): return self.follow_redirect(res.getheader('location')) else: return uri class FeedStorageConfigurationException(Exception): pass class FeedContainer(BackendItem): def __init__(self, parent_id, id, title): self.id = id self.parent_id = parent_id self.name = title self.mimetype = 'directory' self.item = DIDLLite.Container(self.id, self.parent_id, self.name) self.children = [] def get_children(self, start=0, end=0): """returns all the chidlren of this container""" if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): """returns the number of children in this container""" return len(self.children) class FeedEnclosure(BackendItem): def __init__(self, store, parent, id, title, enclosure): self.store = store self.parent = parent self.external_id = id self.name = title self.location = RedirectingReverseProxyUriResource(enclosure.url.encode('latin-1')) # doing this because some (Fraunhofer Podcast) feeds say there mime type is audio/x-mpeg # which at least my XBOX doesn't like ext = enclosure.url.rsplit('.', 1)[0] if ext in MIME_TYPES_EXTENTION_MAPPING: mime_type = MIME_TYPES_EXTENTION_MAPPING[ext] else: mime_type = enclosure.type if(enclosure.type.startswith('audio')): self.item = DIDLLite.AudioItem(id, parent, self.name) elif(enclosure.type.startswith('video')): self.item = DIDLLite.VideoItem(id, parent, self.name) elif(enclosure.type.startswith('image')): self.item = DIDLLite.ImageItem(id, parent, self.name) res = DIDLLite.Resource("%s%d" % (store.urlbase, id), 'http-get:*:%s:*' % mime_type) self.item.res.append(res) class FeedStore(BackendStore): """a general feed store""" logCategory = 'feed_store' implements = ['MediaServer'] def __init__(self,server,**kwargs): BackendStore.__init__(self,server,**kwargs) self.name = kwargs.get('name', 'Feed Store') self.urlbase = kwargs.get('urlbase','') if( len(self.urlbase)>0 and self.urlbase[len(self.urlbase)-1] != '/'): self.urlbase += '/' self.feed_urls = kwargs.get('feed_urls') self.opml_url = kwargs.get('opml_url') if(not(self.feed_urls or self.opml_url)): raise FeedStorageConfigurationException("either feed_urls or opml_url has to be set") if(self.feed_urls and self.opml_url): raise FeedStorageConfigurationException("only feed_urls OR opml_url can be set") self.server = server self.refresh = int(kwargs.get('refresh', 1)) * (60 * 60) # TODO: not used yet self.store = {} self.wmc_mapping = {'4': str(AUDIO_ALL_CONTAINER_ID), # all tracks '7': str(AUDIO_ALBUM_CONTAINER_ID), # all albums '6': str(AUDIO_ARTIST_CONTAINER_ID), # all artists '15': str(VIDEO_FOLDER_CONTAINER_ID), # all videos } self.store[ROOT_CONTAINER_ID] = FeedContainer(-1, ROOT_CONTAINER_ID, self.name) self.store[AUDIO_ALL_CONTAINER_ID] = FeedContainer(-1, AUDIO_ALL_CONTAINER_ID, 'AUDIO_ALL_CONTAINER') self.store[AUDIO_ALBUM_CONTAINER_ID] = FeedContainer(-1, AUDIO_ALBUM_CONTAINER_ID, 'AUDIO_ALBUM_CONTAINER') self.store[VIDEO_FOLDER_CONTAINER_ID] = FeedContainer(-1, VIDEO_FOLDER_CONTAINER_ID, 'VIDEO_FOLDER_CONTAINER') try: self._update_data() except Exception, e: self.error('error while updateing the feed contant for %s: %s' % (self.name, str(e))) self.init_completed() def get_by_id(self,id): """returns the item according to the DIDLite id""" if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: return self.store[int(id)] except (ValueError,KeyError): self.info("can't get item %d from %s feed storage" % (int(id), self.name)) return None def _update_data(self): """get the feed xml, parse it, etc.""" feed_urls = [] if(self.opml_url): tree = ElementTree(file=urllib.urlopen(self.opml_url)) body = tree.find('body') for outline in body.findall('outline'): feed_urls.append(outline.attrib['url']) if(self.feed_urls): feed_urls = self.feed_urls.split() container_id = 100 item_id = 1001 for feed_url in feed_urls: netloc, path, query, fragment = urlsplit(feed_url)[1:] conn = httplib.HTTPConnection(netloc) conn.request('HEAD', '%s?%s#%s' % (path, query, fragment)) res = conn.getresponse() if res.status >= 400: self.warning('error getting %s status code: %d' % (feed_url, res.status)) continue fp_dict = feedparser.parse(feed_url) name = fp_dict.feed.title self.store[container_id] = FeedContainer(ROOT_CONTAINER_ID, container_id, name) self.store[ROOT_CONTAINER_ID].children.append(self.store[container_id]) self.store[VIDEO_FOLDER_CONTAINER_ID].children.append(self.store[container_id]) self.store[AUDIO_ALBUM_CONTAINER_ID].children.append(self.store[container_id]) for item in fp_dict.entries: for enclosure in item.enclosures: self.store[item_id] = FeedEnclosure(self, container_id, item_id, '%04d - %s' % (item_id, item.title), enclosure) self.store[container_id].children.append(self.store[item_id]) if enclosure.type.startswith('audio'): self.store[AUDIO_ALL_CONTAINER_ID].children.append(self.store[item_id]) if not isinstance(self.store[container_id].item, DIDLLite.MusicAlbum): self.store[container_id].item = DIDLLite.MusicAlbum(container_id, AUDIO_ALBUM_CONTAINER_ID, name) item_id += 1 if container_id <= 1000: container_id += 1 else: raise Exception('to many containers') Coherence-0.6.6.2/coherence/backends/picasa_storage.py0000644000175000017500000001774011317660740020755 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009, Jean-Michel Sizun # Copyright 2009 Frank Scholz import os.path import time from twisted.internet import threads from twisted.web import server, static from twisted.web.error import PageRedirect from coherence.upnp.core.utils import ReverseProxyUriResource from twisted.internet import task from coherence.upnp.core import utils from coherence.upnp.core import DIDLLite from coherence.backend import BackendStore, BackendItem, Container, LazyContainer, \ AbstractBackendStore from coherence import log from urlparse import urlsplit import gdata.photos.service import gdata.media import gdata.geo class PicasaProxy(ReverseProxyUriResource): def __init__(self, uri): ReverseProxyUriResource.__init__(self, uri) def render(self, request): if request.received_headers.has_key('referer'): del request.received_headers['referer'] return ReverseProxyUriResource.render(self, request) class PicasaPhotoItem(BackendItem): def __init__(self, photo): #print photo self.photo = photo self.name = photo.summary.text if self.name is None: self.name = photo.title.text self.duration = None self.size = None self.mimetype = photo.content.type self.description = photo.summary.text self.date = None self.item = None self.photo_url = photo.content.src self.thumbnail_url = photo.media.thumbnail[0].url self.url = None self.location = PicasaProxy(self.photo_url) def replace_by(self, item): #print photo self.photo = item.photo self.name = photo.summary.text if self.name is None: self.name = photo.title.text self.mimetype = self.photo.content.type self.description = self.photo.summary.text self.photo_url = self.photo.content.src self.thumbnail_url = self.photo.media.thumbnail[0].url self.location = PicasaProxy(self.photo_url) return True def get_item(self): if self.item == None: upnp_id = self.get_id() upnp_parent_id = self.parent.get_id() self.item = DIDLLite.Photo(upnp_id,upnp_parent_id,self.name) res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) self.item.res.append(res) self.item.childCount = 0 return self.item def get_path(self): return self.url def get_id(self): return self.storage_id class PicasaStore(AbstractBackendStore): logCategory = 'picasa_store' implements = ['MediaServer'] description = ('Picasa Web Albums', 'connects to the Picasa Web Albums service and exposes the featured photos and albums for a given user.', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'refresh','text':'Refresh period','type':'string'}, {'option':'login','text':'User ID:','type':'string','group':'User Account'}, {'option':'password','text':'Password:','type':'string','group':'User Account'}, ] def __init__(self, server, **kwargs): AbstractBackendStore.__init__(self, server, **kwargs) self.name = kwargs.get('name','Picasa Web Albums') self.refresh = int(kwargs.get('refresh',60))*60 self.login = kwargs.get('userid',kwargs.get('login','')) self.password = kwargs.get('password','') rootContainer = Container(None, self.name) self.set_root_item(rootContainer) self.AlbumsContainer = LazyContainer(rootContainer, 'My Albums', None, self.refresh, self.retrieveAlbums) rootContainer.add_child(self.AlbumsContainer) self.FeaturedContainer = LazyContainer(rootContainer, 'Featured photos', None, self.refresh, self.retrieveFeaturedPhotos) rootContainer.add_child(self.FeaturedContainer) self.init_completed() def __repr__(self): return self.__class__.__name__ def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:*,' 'http-get:*:image/gif:*,' 'http-get:*:image/png:*', default=True) self.wmc_mapping = {'16': self.get_root_id()} self.gd_client = gdata.photos.service.PhotosService() self.gd_client.email = self.login self.gd_client.password = self.password self.gd_client.source = 'Coherence UPnP backend' if len(self.login) > 0: d = threads.deferToThread(self.gd_client.ProgrammaticLogin) def retrieveAlbums(self, parent=None): albums = threads.deferToThread(self.gd_client.GetUserFeed) def gotAlbums(albums): if albums is None: print "Unable to retrieve albums" return for album in albums.entry: title = album.title.text album_id = album.gphoto_id.text item = LazyContainer(parent, title, album_id, self.refresh, self.retrieveAlbumPhotos, album_id=album_id) parent.add_child(item, external_id=album_id) def gotError(error): print "ERROR: %s" % error albums.addCallbacks(gotAlbums, gotError) return albums def retrieveFeedPhotos (self, parent=None, feed_uri=''): #print feed_uri photos = threads.deferToThread(self.gd_client.GetFeed, feed_uri) def gotPhotos(photos): if photos is None: print "Unable to retrieve photos for feed %s" % feed_uri return for photo in photos.entry: photo_id = photo.gphoto_id.text item = PicasaPhotoItem(photo) item.parent = parent parent.add_child(item, external_id=photo_id) def gotError(error): print "ERROR: %s" % error photos.addCallbacks(gotPhotos, gotError) return photos def retrieveAlbumPhotos (self, parent=None, album_id=''): album_feed_uri = '/data/feed/api/user/%s/albumid/%s?kind=photo' % (self.login, album_id) return self.retrieveFeedPhotos(parent, album_feed_uri) def retrieveFeaturedPhotos (self, parent=None): feed_uri = 'http://picasaweb.google.com/data/feed/api/featured' return self.retrieveFeedPhotos(parent, feed_uri) Coherence-0.6.6.2/coherence/backends/playlist_storage.py0000644000175000017500000001531211317660740021347 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # a backend # Copyright 2007, Frank Scholz # Copyright 2008, Jean-Michel Sizun from twisted.internet import defer from coherence.upnp.core import utils from coherence.upnp.core import DIDLLite from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin from coherence import log from urlparse import urlsplit import re from coherence.upnp.core.utils import getPage from coherence.backend import BackendStore, BackendItem, Container, LazyContainer, \ AbstractBackendStore class PlaylistItem(BackendItem): logCategory = 'playlist_store' def __init__(self, title, stream_url, mimetype): self.name = title self.stream_url = stream_url self.mimetype = mimetype self.url = stream_url self.item = None def get_id(self): return self.storage_id def get_item(self): if self.item == None: upnp_id = self.get_id() upnp_parent_id = self.parent.get_id() if (self.mimetype.startswith('video/')): item = DIDLLite.VideoItem(upnp_id, upnp_parent_id, self.name) else: item = DIDLLite.AudioItem(upnp_id, upnp_parent_id, self.name) # what to do with MMS:// feeds? protocol = "http-get" if self.stream_url.startswith("rtsp://"): protocol = "rtsp-rtp-udp" res = Resource(self.stream_url, '%s:*:%s:*' % (protocol,self.mimetype)) res.size = None item.res.append(res) self.item = item return self.item def get_url(self): return self.url class PlaylistStore(AbstractBackendStore): logCategory = 'playlist_store' implements = ['MediaServer'] wmc_mapping = {'16': 1000} description = ('Playlist', 'exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...).', None) options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'playlist_url','text':'Playlist file URL:','type':'string','help':'URL to the playlist file (M3U).'}, ] playlist_url = None; def __init__(self, server, **kwargs): AbstractBackendStore.__init__(self, server, **kwargs) self.playlist_url = self.config.get('playlist_url', 'http://mafreebox.freebox.fr/freeboxtv/playlist.m3u') self.name = self.config.get('name', 'playlist') self.init_completed() def __repr__(self): return self.__class__.__name__ def append( self, obj, parent): if isinstance(obj, basestring): mimetype = 'directory' else: mimetype = obj['mimetype'] UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True item = PlaylistItem( id, obj, parent, mimetype, self.urlbase, UPnPClass, update=update) self.store[id] = item self.store[id].store = self if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) if mimetype == 'directory': return self.store[id] return None def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['rtsp-rtp-udp:*:video/mpeg:*', 'http-get:*:video/mpeg:*', 'rtsp-rtp-udp:*:audio/mpeg:*', 'http-get:*:audio/mpeg:*'], default=True) rootItem = Container(None, self.name) self.set_root_item(rootItem) self.retrievePlaylistItems(self.playlist_url, rootItem) def retrievePlaylistItems (self, url, parent_item): def gotPlaylist(playlist): self.info("got playlist") items = {} if playlist : content,header = playlist lines = content.splitlines().__iter__() line = lines.next() while line is not None: if re.search ( '#EXTINF', line): channel = re.match('#EXTINF:.*,(.*)',line).group(1) mimetype = 'video/mpeg' line = lines.next() while re.search ( '#EXTVLCOPT', line): option = re.match('#EXTVLCOPT:(.*)',line).group(1) if option == 'no-video': mimetype = 'audio/mpeg' line = lines.next() url = line item = PlaylistItem(channel, url, mimetype) parent_item.add_child(item) try: line = lines.next() except StopIteration: line = None return items def gotError(error): self.warning("Unable to retrieve playlist: %s" % url) print "Error: %s" % error return None d = getPage(url) d.addCallback(gotPlaylist) d.addErrback(gotError) return d Coherence-0.6.6.2/coherence/backends/buzztard_control.py0000755000175000017500000005044211317660740021375 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Frank Scholz from urlparse import urlsplit from twisted.internet import reactor, protocol from twisted.internet import reactor from twisted.internet.task import LoopingCall from twisted.internet.defer import Deferred from twisted.python import failure from twisted.protocols.basic import LineReceiver from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import DIDLLite from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin from coherence import log class BzClient(LineReceiver, log.Loggable): logCategory = 'buzztard_client' def connectionMade(self): self.info("connected to Buzztard") self.factory.clientReady(self) def lineReceived(self, line): self.debug( "received:", line) if line == 'flush': louie.send('Buzztard.Response.flush', None) elif line.find('event') == 0: louie.send('Buzztard.Response.event', None, line) elif line.find('volume') == 0: louie.send('Buzztard.Response.volume', None, line) elif line.find('mute') == 0: louie.send('Buzztard.Response.mute', None, line) elif line.find('repeat') == 0: louie.send('Buzztard.Response.repeat', None, line) elif line.find('playlist') == 0: louie.send('Buzztard.Response.browse', None, line) class BzFactory(protocol.ClientFactory, log.Loggable): logCategory = 'buzztard_factory' protocol = BzClient def __init__(self,backend): self.backend = backend def clientConnectionFailed(self, connector, reason): self.error('connection failed:', reason.getErrorMessage()) def clientConnectionLost(self, connector, reason): self.error('connection lost:', reason.getErrorMessage()) def startFactory(self): self.messageQueue = [] self.clientInstance = None def clientReady(self, instance): self.info("clientReady") louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self.backend) self.clientInstance = instance for msg in self.messageQueue: self.sendMessage(msg) def sendMessage(self, msg): if self.clientInstance is not None: self.clientInstance.sendLine(msg) else: self.messageQueue.append(msg) def rebrowse(self): self.backend.clear() self.browse() def browse(self): self.sendMessage('browse') class BzConnection(log.Loggable): """ a singleton class """ logCategory = 'buzztard_connection' def __new__(cls, *args, **kwargs): self.debug("BzConnection __new__") obj = getattr(cls,'_instance_',None) if obj is not None: louie.send('Coherence.UPnP.Backend.init_completed', None, backend=kwargs['backend']) return obj else: obj = super(BzConnection, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj obj.connection = BzFactory(kwargs['backend']) reactor.connectTCP( kwargs['host'], kwargs['port'], obj.connection) return obj def __init__(self,backend=None,host='localhost',port=7654): self.debug("BzConnection __init__") class BuzztardItem(log.Loggable): logCategory = 'buzztard_item' def __init__(self, id, name, parent, mimetype, urlbase, host, update=False): self.id = id self.name = name self.mimetype = mimetype self.parent = parent if parent: parent.add_child(self,update=update) if parent == None: parent_id = -1 else: parent_id = parent.get_id() UPnPClass = classChooser(mimetype, sub='music') # FIXME: this is stupid self.item = UPnPClass(id, parent_id, self.name) self.child_count = 0 self.children = [] if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' #self.url = urlbase + str(self.id) self.url = self.name if self.mimetype == 'directory': self.update_id = 0 else: res = Resource(self.url, 'internal:%s:%s:*' % (host,self.mimetype)) res.size = None self.item.res.append(res) self.item.artist = self.parent.name def __del__(self): self.debug("BuzztardItem __del__", self.id, self.name) pass def remove(self,store): self.debug("BuzztardItem remove", self.id, self.name, self.parent) while len(self.children) > 0: child = self.children.pop() self.remove_child(child) del store[int(child.id)] if self.parent: self.parent.remove_child(self) del store[int(self.id)] del self.item del self def add_child(self, child, update=False): self.children.append(child) self.child_count += 1 if isinstance(self.item, Container): self.item.childCount += 1 if update == True: self.update_id += 1 def remove_child(self, child): self.debug("remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name())) if child in self.children: self.child_count -= 1 if isinstance(self.item, Container): self.item.childCount -= 1 self.children.remove(child) self.update_id += 1 def get_children(self,start=0,request_count=0): if request_count == 0: return self.children[start:] else: return self.children[start:request_count] def get_child_count(self): return self.child_count def get_id(self): return self.id def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): return self.url def get_name(self): return self.name def get_parent(self): return self.parent def get_item(self): return self.item def get_xml(self): return self.item.toString() def __repr__(self): if self.parent == None: parent = 'root' else: parent = str(self.parent.get_id()) return 'id: ' + str(self.id) +'/' + self.name + '/' + parent + ' ' + str(self.child_count) + ' @ ' + self.url class BuzztardStore(log.Loggable,Plugin): logCategory = 'buzztard_store' implements = ['MediaServer'] def __init__(self, server, **kwargs): self.next_id = 1000 self.config = kwargs self.name = kwargs.get('name','Buzztard') self.urlbase = kwargs.get('urlbase','') if( len(self.urlbase)>0 and self.urlbase[len(self.urlbase)-1] != '/'): self.urlbase += '/' self.host = kwargs.get('host','127.0.0.1') self.port = int(kwargs.get('port',7654)) self.server = server self.update_id = 0 self.store = {} self.parent = None louie.connect( self.add_content, 'Buzztard.Response.browse', louie.Any) louie.connect( self.clear, 'Buzztard.Response.flush', louie.Any) self.buzztard = BzConnection(backend=self,host=self.host,port=self.port) def __repr__(self): return str(self.__class__).split('.')[-1] def add_content(self,line): data = line.split('|')[1:] parent = self.append(data[0], 'directory', self.parent) i = 0 for label in data[1:]: self.append(':'.join((label,str(i))), 'audio/mpeg', parent) i += 1 def append( self, name, mimetype, parent): id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = BuzztardItem( id, name, parent, mimetype, self.urlbase,self.host,update=update) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) if mimetype == 'directory': return self.store[id] return None def remove(self, id): item = self.store[int(id)] parent = item.get_parent() item.remove(self.store) try: del self.store[int(id)] except: pass if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) def clear(self): for item in self.get_by_id(1000).get_children(): self.remove(item.get_id()) self.buzztard.connection.browse() def len(self): return len(self.store) def get_by_id(self,id): id = int(id) if id == 0: id = 1000 try: return self.store[id] except: return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret def upnp_init(self): self.current_connection_id = None self.parent = self.append('Buzztard', 'directory', None) source_protocols = "" if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', source_protocols, default=True) self.buzztard.connection.browse() class BuzztardPlayer(log.Loggable): logCategory = 'buzztard_player' implements = ['MediaRenderer'] vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'}} vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}} def __init__(self, device, **kwargs): self.name = kwargs.get('name','Buzztard MediaRenderer') self.host = kwargs.get('host','127.0.0.1') self.port = int(kwargs.get('port',7654)) self.player = None self.playing = False self.state = None self.duration = None self.view = [] self.tags = {} self.server = device self.poll_LC = LoopingCall( self.poll_player) louie.connect( self.event, 'Buzztard.Response.event', louie.Any) louie.connect( self.get_volume, 'Buzztard.Response.volume', louie.Any) louie.connect( self.get_mute, 'Buzztard.Response.mute', louie.Any) louie.connect( self.get_repeat, 'Buzztard.Response.repeat', louie.Any) self.buzztard = BzConnection(backend=self,host=self.host,port=self.port) def event(self,line): infos = line.split('|')[1:] self.debug(infos) if infos[0] == 'playing': transport_state = 'PLAYING' if infos[0] == 'stopped': transport_state = 'STOPPED' if infos[0] == 'paused': transport_state = 'PAUSED_PLAYBACK' if self.server != None: connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) if self.state != transport_state: self.state = transport_state if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'TransportState', transport_state) label = infos[1] position = infos[2].split('.')[0] duration = infos[3].split('.')[0] if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack', 0) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackDuration', duration) self.server.av_transport_server.set_variable(connection_id, 'CurrentMediaDuration', duration) self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', position) self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', position) try: self.server.rendering_control_server.set_variable(connection_id, 'Volume', int(infos[4])) except: pass try: if infos[5] in ['on','1','true','True','yes','Yes']: mute = True else: mute = False self.server.rendering_control_server.set_variable(connection_id, 'Mute', mute) except: pass try: if infos[6] in ['on','1','true','True','yes','Yes']: self.server.av_transport_server.set_variable(connection_id, 'CurrentPlayMode', 'REPEAT_ALL') else: self.server.av_transport_server.set_variable(connection_id, 'CurrentPlayMode', 'NORMAL') except: pass def __repr__(self): return str(self.__class__).split('.')[-1] def poll_player( self): self.buzztard.connection.sendMessage('status') def load( self, uri, metadata): self.debug("load", uri, metadata) self.duration = None self.metadata = metadata connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','Play,Stop') self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',1) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',metadata) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',metadata) def start( self, uri): self.load( uri) self.play() def stop(self): self.buzztard.connection.sendMessage('stop') def play( self): connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) label_id = self.server.av_transport_server.get_variable('CurrentTrackURI',connection_id).value id = '0' if ':' in label_id: label,id = label_id.split(':') self.buzztard.connection.sendMessage('play|%s' % id) def pause( self): self.buzztard.connection.sendMessage('pause') def seek(self, location): """ @param location: simple number = time to seek to, in seconds +nL = relative seek forward n seconds -nL = relative seek backwards n seconds """ def mute(self): self.buzztard.connection.sendMessage('set|mute|on') def unmute(self): self.buzztard.connection.sendMessage('set|mute|off') def get_mute(self,line): infos = line.split('|')[1:] if infos[0] in ['on','1','true','True','yes','Yes']: mute = True else: mute = False self.server.rendering_control_server.set_variable(0, 'Mute', mute) def get_repeat(self,line): infos = line.split('|')[1:] if infos[0] in ['on','1','true','True','yes','Yes']: self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'REPEAT_ALL') else: self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL') def set_repeat(self, playmode): if playmode in ['REPEAT_ONE','REPEAT_ALL']: self.buzztard.connection.sendMessage('set|repeat|on') else: self.buzztard.connection.sendMessage('set|repeat|off') def get_volume(self,line): infos = line.split('|')[1:] self.server.rendering_control_server.set_variable(0, 'Volume', int(infos[0])) def set_volume(self, volume): volume = int(volume) if volume < 0: volume=0 if volume > 100: volume=100 self.buzztard.connection.sendMessage('set|volume|%d'% volume) def upnp_init(self): self.current_connection_id = None self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo', ['internal:%s:audio/mpeg:*' % self.host], default=True) self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True) self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True) self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True) self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True) self.buzztard.connection.sendMessage('get|volume') self.buzztard.connection.sendMessage('get|mute') self.buzztard.connection.sendMessage('get|repeat') self.poll_LC.start( 1.0, True) def upnp_Play(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Speed = int(kwargs['Speed']) self.play() return {} def upnp_Pause(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.pause() return {} def upnp_Stop(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.stop() return {} def upnp_SetAVTransportURI(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) CurrentURI = kwargs['CurrentURI'] CurrentURIMetaData = kwargs['CurrentURIMetaData'] local_protocol_info=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',') if len(CurrentURIMetaData)==0: self.load(CurrentURI,CurrentURIMetaData) return {} else: elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData) print elt.numItems() if elt.numItems() == 1: item = elt.getItems()[0] for res in item.res: print res.protocolInfo,local_protocol_info if res.protocolInfo in local_protocol_info: self.load(CurrentURI,CurrentURIMetaData) return {} return failure.Failure(errorCode(714)) def upnp_SetMute(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredMute = kwargs['DesiredMute'] if DesiredMute in ['TRUE', 'True', 'true', '1','Yes','yes']: self.mute() else: self.unmute() return {} def upnp_SetVolume(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredVolume = int(kwargs['DesiredVolume']) self.set_volume(DesiredVolume) return {} def test_init_complete(backend): print "Houston, we have a touchdown!" backend.buzztard.sendMessage('browse') def main(): louie.connect( test_init_complete, 'Coherence.UPnP.Backend.init_completed', louie.Any) f = BuzztardStore(None) f.parent = f.append('Buzztard', 'directory', None) print f.parent print f.store f.add_content('playlist|test label|start|stop') print f.store f.clear() print f.store f.add_content('playlist|after flush label|flush-start|flush-stop') print f.store #def got_upnp_result(result): # print "upnp", result #f.upnp_init() #print f.store #r = f.upnp_Browse(BrowseFlag='BrowseDirectChildren', # RequestedCount=0, # StartingIndex=0, # ObjectID=0, # SortCriteria='*', # Filter='') #got_upnp_result(r) if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/banshee_storage.py0000644000175000017500000007241511317660740021122 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009, Philippe Normand """ TODO: - podcasts """ from twisted.internet import reactor, defer, task from coherence.extern import db_row from coherence.upnp.core import DIDLLite from coherence.backend import BackendItem, BackendStore from coherence.log import Loggable import coherence.extern.louie as louie from sqlite3 import dbapi2 # fallback on pysqlite2.dbapi2 import re import os import time from urlparse import urlsplit import urllib2 import mimetypes mimetypes.init() mimetypes.add_type('audio/x-m4a', '.m4a') mimetypes.add_type('video/mp4', '.mp4') mimetypes.add_type('video/mpegts', '.ts') mimetypes.add_type('video/divx', '.divx') mimetypes.add_type('video/divx', '.avi') mimetypes.add_type('video/x-matroska', '.mkv') mimetypes.add_type('audio/x-musepack', '.mpc') mimetypes.add_type('audio/x-wavpack', '.flac') mimetypes.add_type('audio/x-wavpack', '.wv') mimetypes.add_type('audio/mp4', '.m4a') ROOT_CONTAINER_ID = 0 AUDIO_CONTAINER = 200 VIDEO_CONTAINER = 300 AUDIO_ALL_CONTAINER_ID = 201 AUDIO_ARTIST_CONTAINER_ID = 202 AUDIO_ALBUM_CONTAINER_ID = 203 AUDIO_PLAYLIST_CONTAINER_ID = 204 VIDEO_ALL_CONTAINER_ID = 301 VIDEO_PLAYLIST_CONTAINER_ID = 302 def get_cover_path(artist_name, album_title): def _escape_part(part): escaped = "" if part: if part.find("(") > -1: part = part[:part.find("(")] escaped = re.sub("[^A-Za-z0-9]*", "", part).lower() return escaped base_dir = os.path.expanduser("~/.cache/album-art") return os.path.join(base_dir, "%s-%s.jpg" % (_escape_part(artist_name), _escape_part(album_title))) class SQLiteDB(Loggable): """ Python DB API 2.0 backend support. """ logCategory = "sqlite" def __init__(self, database): """ Connect to a db backend hosting the given database. """ Loggable.__init__(self) self._params = {'database': database, 'check_same_thread': True} self.connect() def disconnect(self): self._db.close() def connect(self): """ Connect to the database, set L{_db} instance variable. """ self._db = dbapi2.connect(**self._params) def reconnect(self): """ Disconnect and reconnect to the database. """ self.disconnect() self.connect() def sql_execute(self, request, *params, **kw): """ Execute a SQL query in the db backend """ t0 = time.time() debug_msg = request if params: debug_msg = u"%s params=%r" % (request, params) debug_msg = u''.join(debug_msg.splitlines()) if debug_msg: self.debug('QUERY: %s', debug_msg) cursor = self._db.cursor() result = [] cursor.execute(request, params) if cursor.description: all_rows = cursor.fetchall() result = db_row.getdict(all_rows, cursor.description) cursor.close() delta = time.time() - t0 self.log("SQL request took %s seconds" % delta) return result class Container(BackendItem): get_path = None def __init__(self, id, parent_id, name, children_callback=None, store=None, play_container=False): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.store = store self.play_container = play_container self.update_id = 0 if children_callback != None: self.children = children_callback else: self.children = [] def add_child(self, child): self.children.append(child) def get_children(self,start=0,request_count=0): def got_children(children): if request_count == 0: return children[start:] else: return children[start:request_count] if callable(self.children): dfr = defer.maybeDeferred(self.children) else: dfr = defer.succeed(self.children) dfr.addCallback(got_children) return dfr def get_child_count(self): count = 0 if callable(self.children): count = defer.maybeDeferred(self.children) count.addCallback(lambda children: len(children)) else: count = len(self.children) return count def get_item(self): item = DIDLLite.Container(self.id, self.parent_id,self.name) def got_count(count): item.childCount = count if self.store and self.play_container == True: if item.childCount > 0: dfr = self.get_children(request_count=1) dfr.addCallback(got_child, item) return dfr return item def got_child(children, item): res = DIDLLite.PlayContainerResource(self.store.server.uuid, cid=self.get_id(), fid=children[0].get_id()) item.res.append(res) return item dfr = defer.maybeDeferred(self.get_child_count) dfr.addCallback(got_count) return dfr def get_name(self): return self.name def get_id(self): return self.id class Artist(BackendItem): def __init__(self, *args, **kwargs): BackendItem.__init__(self, *args, **kwargs) self._row = args[0] self._db = args[1] self._local_music_library_id = args[2] self.musicbrainz_id = self._row.MusicBrainzID self.itemID = self._row.ArtistID self.name = self._row.Name or '' if self.name: self.name = self.name.encode("utf-8") def get_children(self,start=0, end=0): albums = [] def query_db(): q = "select * from CoreAlbums where ArtistID=? and AlbumID in "\ "(select distinct(AlbumID) from CoreTracks where "\ "PrimarySourceID=?) order by Title" rows = self._db.sql_execute(q, self.itemID, self._local_music_library_id) for row in rows: album = Album(row, self._db, self) albums.append(album) yield album dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: albums) return dfr def get_child_count(self): q = "select count(AlbumID) as c from CoreAlbums where ArtistID=? and "\ "AlbumID in (select distinct(AlbumID) from CoreTracks where "\ "PrimarySourceID=?) " return self._db.sql_execute(q, self.itemID, self._local_music_library_id)[0].c def get_item(self): item = DIDLLite.MusicArtist(self.get_id(), AUDIO_ARTIST_CONTAINER_ID, self.name) item.childCount = self.get_child_count() return item def get_id(self): return "artist.%d" % self.itemID def __repr__(self): return '' % (self.itemID, self.name, self.musicbrainz_id) class Album(BackendItem): """ definition for an album """ mimetype = 'directory' get_path = None def __init__(self, *args, **kwargs): BackendItem.__init__(self, *args, **kwargs) self._row = args[0] self._db = args[1] self.artist = args[2] self.itemID = self._row.AlbumID self.title = self._row.Title self.cover = get_cover_path(self.artist.name, self.title) if self.title: self.title = self.title.encode("utf-8") self.musicbrainz_id = self._row.MusicBrainzID self.cd_count = 1 def get_children(self,start=0,request_count=0): tracks = [] def query_db(): q = "select * from CoreTracks where AlbumID=? order by TrackNumber" if request_count: q += " limit %d" % request_count rows = self._db.sql_execute(q, self.itemID) for row in rows: track = Track(row, self._db, self) tracks.append(track) yield track dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: tracks) return dfr def get_child_count(self): q = "select count(TrackID) as c from CoreTracks where AlbumID=?" count = self._db.sql_execute(q, self.itemID)[0].c return count def get_item(self): item = DIDLLite.MusicAlbum(self.get_id(), AUDIO_ALBUM_CONTAINER_ID, self.title) item.artist = self.artist.name item.childCount = self.get_child_count() if self.cover: _,ext = os.path.splitext(self.cover) item.albumArtURI = ''.join((self._db.urlbase, self.get_id(), '?cover', ext)) def got_tracks(tracks): res = DIDLLite.PlayContainerResource(self._db.server.uuid, cid=self.get_id(), fid=tracks[0].get_id()) item.res.append(res) return item if item.childCount > 0: dfr = self.get_children(request_count=1) dfr.addCallback(got_tracks) else: dfr = defer.succeed(item) return dfr def get_id(self): return "album.%d" % self.itemID def get_name(self): return self.title def get_cover(self): return self.cover def __repr__(self): return '' \ % (self.itemID, self.title, self.artist.name, self.cd_count, self.cover, self.musicbrainz_id) class BasePlaylist(BackendItem): """ definition for a playlist """ mimetype = 'directory' get_path = None def __init__(self, *args, **kwargs): BackendItem.__init__(self, *args, **kwargs) self._row = args[0] self._store = args[1] self._db = self._store.db self.title = self._row.Name if self.title: self.title = self.title.encode("utf-8") def get_tracks(self, request_count): return [] def db_to_didl(self, row): album = self._store.get_album_with_id(row.AlbumID) track = Track(row, self._db, album) return track def get_id(self): return "%s.%d" % (self.id_type, self.db_id) def __repr__(self): return '<%s %d title="%s">' % (self.__class___.__name__, self.db_id, self.title) def get_children(self, start=0, request_count=0): tracks = [] def query_db(): rows = self.get_tracks(request_count) for row in rows: track = self.db_to_didl(row) tracks.append(track) yield track dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: tracks) return dfr def get_child_count(self): return self._row.CachedCount def get_item(self): item = DIDLLite.PlaylistContainer(self.get_id(), AUDIO_PLAYLIST_CONTAINER_ID, self.title) item.childCount = self.get_child_count() def got_tracks(tracks): res = DIDLLite.PlayContainerResource(self._db.server.uuid, cid=self.get_id(), fid=tracks[0].get_id()) item.res.append(res) return item if item.childCount > 0: dfr = self.get_children(request_count=1) dfr.addCallback(got_tracks) else: dfr = defer.succeed(item) return dfr def get_name(self): return self.title class MusicPlaylist(BasePlaylist): id_type = "musicplaylist" @property def db_id(self): return self._row.PlaylistID def get_tracks(self, request_count): q = "select * from CoreTracks where TrackID in (select TrackID "\ "from CorePlaylistEntries where PlaylistID=?)" if request_count: q += " limit %d" % request_count return self._db.sql_execute(q, self.db_id) class MusicSmartPlaylist(BasePlaylist): id_type = "musicsmartplaylist" @property def db_id(self): return self._row.SmartPlaylistID def get_tracks(self, request_count): q = "select * from CoreTracks where TrackID in (select TrackID "\ "from CoreSmartPlaylistEntries where SmartPlaylistID=?)" if request_count: q += " limit %d" % request_count return self._db.sql_execute(q, self.db_id) class VideoPlaylist(MusicPlaylist): id_type = "videoplaylist" def db_to_didl(self, row): return Video(row, self._db) class VideoSmartPlaylist(MusicSmartPlaylist): id_type = "videosmartplaylist" def db_to_didl(self, row): return Video(row, self._db) class BaseTrack(BackendItem): """ definition for a track """ def __init__(self, *args, **kwargs): BackendItem.__init__(self, *args, **kwargs) self._row = args[0] self._db = args[1] self.itemID = self._row.TrackID self.title = self._row.Title self.track_nr = self._row.TrackNumber self.location = self._row.Uri self.playlist = kwargs.get("playlist") def get_children(self,start=0,request_count=0): return [] def get_child_count(self): return 0 def get_resources(self): resources = [] _,host_port,_,_,_ = urlsplit(self._db.urlbase) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) else: host = host_port _,ext = os.path.splitext(self.location) ext = ext.lower() # FIXME: drop this hack when we switch to tagbin mimetype, dummy = mimetypes.guess_type("dummy%s" % ext) if not mimetype: mimetype = 'audio/mpeg' ext = "mp3" statinfo = os.stat(self.get_path()) res = DIDLLite.Resource(self.location, 'internal:%s:%s:*' % (host, mimetype)) try: res.size = statinfo.st_size except: res.size = 0 resources.append(res) url = "%s%s%s" % (self._db.urlbase, self.get_id(), ext) res = DIDLLite.Resource(url, 'http-get:*:%s:*' % mimetype) try: res.size = statinfo.st_size except: res.size = 0 resources.append(res) return statinfo, resources def get_path(self): return urllib2.unquote(self.location[7:].encode('utf-8')) def get_id(self): return "track.%d" % self.itemID def get_name(self): return self.title def get_url(self): return self._db.urlbase + str(self.itemID).encode('utf-8') def get_cover(self): return self.album.cover def __repr__(self): return '' \ % (self.itemID, self.title, self.track_nr, self.album.title, self.album.artist.name, self.location) class Track(BaseTrack): def __init__(self, *args, **kwargs): BaseTrack.__init__(self, *args, **kwargs) self.album = args[2] def get_item(self): item = DIDLLite.MusicTrack(self.get_id(), self.album.itemID,self.title) item.artist = self.album.artist.name item.album = self.album.title item.playlist = self.playlist if self.album.cover != '': _,ext = os.path.splitext(self.album.cover) """ add the cover image extension to help clients not reacting on the mimetype """ item.albumArtURI = ''.join((self._db.urlbase, self.get_id(), '?cover',ext)) item.originalTrackNumber = self.track_nr item.server_uuid = str(self._db.server.uuid)[5:] statinfo, resources = self.get_resources() item.res.extend(resources) try: # FIXME: getmtime is deprecated in Twisted 2.6 item.date = datetime.fromtimestamp(statinfo.st_mtime) except: item.date = None return item class Video(BaseTrack): def get_item(self): item = DIDLLite.VideoItem(self.get_id(), VIDEO_ALL_CONTAINER_ID, self.title) item.server_uuid = str(self._db.server.uuid)[5:] statinfo, resources = self.get_resources() item.res.extend(resources) try: # FIXME: getmtime is deprecated in Twisted 2.6 item.date = datetime.fromtimestamp(statinfo.st_mtime) except: item.date = None return item class BansheeDB(Loggable): logCategory = "banshee_db" def __init__(self, path=None): Loggable.__init__(self) self._local_music_library_id = None self._local_video_library_id = None default_db_path = os.path.expanduser("~/.config/banshee-1/banshee.db") self._db_path = path or default_db_path def open_db(self): self.db = SQLiteDB(self._db_path) def close(self): self.db.disconnect() def get_local_music_library_id(self): if self._local_music_library_id is None: q = "select PrimarySourceID from CorePrimarySources where StringID=?" row = self.db.sql_execute(q, 'MusicLibrarySource-Library')[0] self._local_music_library_id = row.PrimarySourceID return self._local_music_library_id def get_local_video_library_id(self): if self._local_video_library_id is None: q = "select PrimarySourceID from CorePrimarySources where StringID=?" row = self.db.sql_execute(q, 'VideoLibrarySource-VideoLibrary')[0] self._local_video_library_id = row.PrimarySourceID return self._local_video_library_id def get_artists(self): artists = [] def query_db(): source_id = self.get_local_music_library_id() q = "select * from CoreArtists where ArtistID in "\ "(select distinct(ArtistID) from CoreTracks where "\ "PrimarySourceID=?) order by Name" for row in self.db.sql_execute(q, source_id): artist = Artist(row, self.db, source_id) artists.append(artist) yield artist dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: artists) return dfr def get_albums(self): albums = [] artists = {} def query_db(): q = "select * from CoreAlbums where AlbumID in "\ "(select distinct(AlbumID) from CoreTracks where "\ "PrimarySourceID=?) order by Title" for row in self.db.sql_execute(q, self.get_local_music_library_id()): try: artist = artists[row.ArtistID] except KeyError: artist = self.get_artist_with_id(row.ArtistID) artists[row.ArtistID] = artist album = Album(row, self.db, artist) albums.append(album) yield album dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: albums) return dfr def get_music_playlists(self): return self.get_playlists(self.get_local_music_library_id(), MusicPlaylist, MusicSmartPlaylist) def get_playlists(self, source_id, PlaylistClass, SmartPlaylistClass): playlists = [] def query_db(): q = "select * from CorePlaylists where PrimarySourceID=? order by Name" for row in self.db.sql_execute(q, source_id): playlist = PlaylistClass(row, self) playlists.append(playlist) yield playlist q = "select * from CoreSmartPlaylists where PrimarySourceID=? order by Name" for row in self.db.sql_execute(q, source_id): playlist = SmartPlaylistClass(row, self) playlists.append(playlist) yield playlist dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: playlists) return dfr def get_artist_with_id(self, artist_id): q = "select * from CoreArtists where ArtistID=? limit 1" row = self.db.sql_execute(q, artist_id)[0] return Artist(row, self.db, self.get_local_music_library_id()) def get_album_with_id(self, album_id): q = "select * from CoreAlbums where AlbumID=? limit 1" row = self.db.sql_execute(q, album_id)[0] artist = self.get_artist_with_id(row.ArtistID) return Album(row, self.db, artist) def get_playlist_with_id(self, playlist_id, PlaylistClass): q = "select * from CorePlaylists where PlaylistID=? limit 1" row = self.db.sql_execute(q, playlist_id)[0] return PlaylistClass(row, self) def get_smart_playlist_with_id(self, playlist_id, PlaylistClass): q = "select * from CoreSmartPlaylists where SmartPlaylistID=? limit 1" row = self.db.sql_execute(q, playlist_id)[0] return PlaylistClass(row, self) def get_music_playlist_with_id(self, playlist_id): return self.get_playlist_with_id(playlist_id, MusicPlaylist) def get_music_smart_playlist_with_id(self, playlist_id): return self.get_smart_playlist_with_id(playlist_id, MusicSmartPlaylist) def get_video_playlist_with_id(self, playlist_id): return self.get_playlist_with_id(playlist_id, VideoPlaylist) def get_video_smart_playlist_with_id(self, playlist_id): return self.get_smart_playlist_with_id(playlist_id, VideoSmartPlaylist) def get_track_with_id(self, track_id): q = "select * from CoreTracks where TrackID=? limit 1" row = self.db.sql_execute(q, track_id)[0] album = self.get_album_with_id(row.AlbumID) return Track(row, self.db, album) def get_track_for_uri(self, track_uri): q = "select * from CoreTracks where Uri=? limit 1" try: row = self.db.sql_execute(q, track_uri)[0] except IndexError: # not found track = None else: album = self.get_album_with_id(row.AlbumID) track = Track(row, self, album) return track def get_tracks(self): tracks = [] albums = {} def query_db(): q = "select * from CoreTracks where TrackID in "\ "(select distinct(TrackID) from CoreTracks where "\ "PrimarySourceID=?) order by AlbumID,TrackNumber" for row in self.db.sql_execute(q, self.get_local_music_library_id()): if row.AlbumID not in albums: album = self.get_album_with_id(row.AlbumID) albums[row.AlbumID] = album else: album = albums[row.AlbumID] track = Track(row, self.db,album) tracks.append(track) yield track dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: tracks) return dfr def get_video_with_id(self, video_id): q = "select * from CoreTracks where TrackID=? limit 1" row = self.db.sql_execute(q, video_id)[0] return Video(row, self.db) def get_videos(self): videos = [] def query_db(): source_id = self.get_local_video_library_id() q = "select * from CoreTracks where TrackID in "\ "(select distinct(TrackID) from CoreTracks where "\ "PrimarySourceID=?)" for row in self.db.sql_execute(q, source_id): video = Video(row, self.db, source_id) videos.append(video) yield video dfr = task.coiterate(query_db()) dfr.addCallback(lambda gen: videos) return dfr def get_video_playlists(self): return self.get_playlists(self.get_local_video_library_id(), VideoPlaylist, VideoSmartPlaylist) class BansheeStore(BackendStore, BansheeDB): logCategory = 'banshee_store' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) BansheeDB.__init__(self, kwargs.get("db_path")) self.update_id = 0 self.name = kwargs.get('name', 'Banshee') self.containers = {} self.containers[ROOT_CONTAINER_ID] = Container(ROOT_CONTAINER_ID, -1, self.name, store=self) louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def upnp_init(self): self.open_db() music = Container(AUDIO_CONTAINER, ROOT_CONTAINER_ID, 'Music', store=self) self.containers[ROOT_CONTAINER_ID].add_child(music) self.containers[AUDIO_CONTAINER] = music artists = Container(AUDIO_ARTIST_CONTAINER_ID, AUDIO_CONTAINER, 'Artists', children_callback=self.get_artists, store=self) self.containers[AUDIO_ARTIST_CONTAINER_ID] = artists self.containers[AUDIO_CONTAINER].add_child(artists) albums = Container(AUDIO_ALBUM_CONTAINER_ID, AUDIO_CONTAINER, 'Albums', children_callback=self.get_albums, store=self) self.containers[AUDIO_ALBUM_CONTAINER_ID] = albums self.containers[AUDIO_CONTAINER].add_child(albums) tracks = Container(AUDIO_ALL_CONTAINER_ID, AUDIO_CONTAINER, 'All tracks', children_callback=self.get_tracks, play_container=True, store=self) self.containers[AUDIO_ALL_CONTAINER_ID] = tracks self.containers[AUDIO_CONTAINER].add_child(tracks) playlists = Container(AUDIO_PLAYLIST_CONTAINER_ID, AUDIO_CONTAINER, 'Playlists', store=self, children_callback=self.get_music_playlists) self.containers[AUDIO_PLAYLIST_CONTAINER_ID] = playlists self.containers[AUDIO_CONTAINER].add_child(playlists) videos = Container(VIDEO_CONTAINER, ROOT_CONTAINER_ID, 'Videos', store=self) self.containers[ROOT_CONTAINER_ID].add_child(videos) self.containers[VIDEO_CONTAINER] = videos all_videos = Container(VIDEO_ALL_CONTAINER_ID, VIDEO_CONTAINER, 'All Videos', children_callback=self.get_videos, store=self) self.containers[VIDEO_ALL_CONTAINER_ID] = all_videos self.containers[VIDEO_CONTAINER].add_child(all_videos) playlists = Container(VIDEO_PLAYLIST_CONTAINER_ID, VIDEO_CONTAINER, 'Playlists', store=self, children_callback=self.get_video_playlists) self.containers[VIDEO_PLAYLIST_CONTAINER_ID] = playlists self.containers[VIDEO_CONTAINER].add_child(playlists) self.db.server = self.server self.db.urlbase = self.urlbase self.db.containers = self.containers self.current_connection_id = None if self.server: hostname = self.server.coherence.hostname source_protocol_info = ['internal:%s:audio/mpeg:*' % hostname, 'http-get:*:audio/mpeg:*', 'internal:%s:application/ogg:*' % hostname, 'http-get:*:application/ogg:*'] self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', source_protocol_info, default=True) def release(self): self.db.disconnect() def get_by_id(self,item_id): self.info("get_by_id %s" % item_id) if isinstance(item_id, basestring) and item_id.find('.') > 0: item_id = item_id.split('@',1) item_type, item_id = item_id[0].split('.')[:2] item_id = int(item_id) dfr = self._lookup(item_type, item_id) else: item_id = int(item_id) item = self.containers[item_id] dfr = defer.succeed(item) return dfr def _lookup(self, item_type, item_id): lookup_mapping = dict(artist=self.get_artist_with_id, album=self.get_album_with_id, musicplaylist=self.get_music_playlist_with_id, musicsmartplaylist=self.get_music_smart_playlist_with_id, videoplaylist=self.get_video_playlist_with_id, videosmartplaylist=self.get_video_smart_playlist_with_id, track=self.get_track_with_id, video=self.get_video_with_id) item = None func = lookup_mapping.get(item_type) if func: item = func(item_id) return defer.succeed(item) Coherence-0.6.6.2/coherence/backends/__init__.py0000644000175000017500000000000011317660740017505 0ustar devdevCoherence-0.6.6.2/coherence/backends/fs_storage.py0000644000175000017500000011317111317660740020120 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz import os, stat import tempfile import shutil import time import re from datetime import datetime import urllib from sets import Set import mimetypes mimetypes.init() mimetypes.add_type('audio/x-m4a', '.m4a') mimetypes.add_type('video/mp4', '.mp4') mimetypes.add_type('video/mpegts', '.ts') mimetypes.add_type('video/divx', '.divx') mimetypes.add_type('video/divx', '.avi') mimetypes.add_type('video/x-matroska', '.mkv') from urlparse import urlsplit from twisted.python.filepath import FilePath from twisted.python import failure from coherence.upnp.core.DIDLLite import classChooser, Container, Resource from coherence.upnp.core.DIDLLite import DIDLElement from coherence.upnp.core.DIDLLite import simple_dlna_tags from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import utils try: from coherence.extern.inotify import INotify from coherence.extern.inotify import IN_CREATE, IN_DELETE, IN_MOVED_FROM, IN_MOVED_TO, IN_ISDIR from coherence.extern.inotify import IN_CHANGED haz_inotify = True except Exception,msg: haz_inotify = False no_inotify_reason = msg from coherence.extern.xdg import xdg_content import coherence.extern.louie as louie from coherence.backend import BackendItem, BackendStore ## Sorting helpers NUMS = re.compile('([0-9]+)') def _natural_key(s): # strip the spaces s = s.get_name().strip() return [ part.isdigit() and int(part) or part.lower() for part in NUMS.split(s) ] class FSItem(BackendItem): logCategory = 'fs_item' def __init__(self, object_id, parent, path, mimetype, urlbase, UPnPClass,update=False,store=None): self.id = object_id self.parent = parent if parent: parent.add_child(self,update=update) if mimetype == 'root': self.location = unicode(path) else: if mimetype == 'item' and path is None: path = os.path.join(parent.get_realpath(),unicode(self.id)) #self.location = FilePath(unicode(path)) self.location = FilePath(path) self.mimetype = mimetype if urlbase[-1] != '/': urlbase += '/' self.url = urlbase + str(self.id) self.store = store if parent == None: parent_id = -1 else: parent_id = parent.get_id() self.item = UPnPClass(object_id, parent_id, self.get_name()) if isinstance(self.item, Container): self.item.childCount = 0 self.child_count = 0 self.children = [] self.sorted = False self.caption = None if mimetype in ['directory','root']: self.update_id = 0 self.get_url = lambda : self.url self.get_path = lambda : None #self.item.searchable = True #self.item.searchClass = 'object' if(isinstance(self.location,FilePath) and self.location.isdir() == True): self.check_for_cover_art() if hasattr(self, 'cover'): _,ext = os.path.splitext(self.cover) """ add the cover image extension to help clients not reacting on the mimetype """ self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext)) else: self.get_url = lambda : self.url if self.mimetype.startswith('audio/'): if hasattr(parent, 'cover'): _,ext = os.path.splitext(parent.cover) """ add the cover image extension to help clients not reacting on the mimetype """ self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext)) _,host_port,_,_,_ = urlsplit(urlbase) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) else: host = host_port try: size = self.location.getsize() except: size = 0 if self.store.server.coherence.config.get('transcoding', 'no') == 'yes': if self.mimetype in ('application/ogg','audio/ogg', 'audio/x-wav', 'audio/x-m4a', 'application/x-flac'): new_res = Resource(self.url+'/transcoded.mp3', 'http-get:*:%s:*' % 'audio/mpeg') new_res.size = None #self.item.res.append(new_res) if mimetype != 'item': res = Resource('file://'+ urllib.quote(self.get_path()), 'internal:%s:%s:*' % (host,self.mimetype)) res.size = size self.item.res.append(res) if mimetype != 'item': res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) else: res = Resource(self.url, 'http-get:*:*:*') res.size = size self.item.res.append(res) """ if this item is of type audio and we want to add a transcoding rule for it, this is the way to do it: create a new Resource object, at least a 'http-get' and maybe an 'internal' one too for transcoding to wav this looks like that res = Resource(url_for_transcoded audio, 'http-get:*:audio/x-wav:%s'% ';'.join(['DLNA.ORG_PN=JPEG_TN']+simple_dlna_tags)) res.size = None self.item.res.append(res) """ if self.store.server.coherence.config.get('transcoding', 'no') == 'yes': if self.mimetype in ('audio/mpeg', 'application/ogg','audio/ogg', 'audio/x-wav', 'audio/x-m4a', 'audio/flac', 'application/x-flac'): dlna_pn = 'DLNA.ORG_PN=LPCM' dlna_tags = simple_dlna_tags[:] #dlna_tags[1] = 'DLNA.ORG_OP=00' dlna_tags[2] = 'DLNA.ORG_CI=1' new_res = Resource(self.url+'?transcoded=lpcm', 'http-get:*:%s:%s' % ('audio/L16;rate=44100;channels=2', ';'.join([dlna_pn]+dlna_tags))) new_res.size = None #self.item.res.append(new_res) if self.mimetype != 'audio/mpeg': new_res = Resource(self.url+'?transcoded=mp3', 'http-get:*:%s:*' % 'audio/mpeg') new_res.size = None #self.item.res.append(new_res) """ if this item is an image and we want to add a thumbnail for it we have to follow these rules: create a new Resource object, at least a 'http-get' and maybe an 'internal' one too for an JPG this looks like that res = Resource(url_for_thumbnail, 'http-get:*:image/jpg:%s'% ';'.join(['DLNA.ORG_PN=JPEG_TN']+simple_dlna_tags)) res.size = size_of_thumbnail self.item.res.append(res) and for a PNG the Resource creation is like that res = Resource(url_for_thumbnail, 'http-get:*:image/png:%s'% ';'.join(simple_dlna_tags+['DLNA.ORG_PN=PNG_TN'])) if not hasattr(self.item, 'attachments'): self.item.attachments = {} self.item.attachments[key] = utils.StaticFile(filename_of_thumbnail) """ if self.mimetype in ('image/jpeg', 'image/png'): path = self.get_path() thumbnail = os.path.join(os.path.dirname(path),'.thumbs',os.path.basename(path)) if os.path.exists(thumbnail): mimetype,_ = mimetypes.guess_type(thumbnail, strict=False) if mimetype in ('image/jpeg','image/png'): if mimetype == 'image/jpeg': dlna_pn = 'DLNA.ORG_PN=JPEG_TN' else: dlna_pn = 'DLNA.ORG_PN=PNG_TN' dlna_tags = simple_dlna_tags[:] dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' hash_from_path = str(id(thumbnail)) new_res = Resource(self.url+'?attachment='+hash_from_path, 'http-get:*:%s:%s' % (mimetype, ';'.join([dlna_pn]+dlna_tags))) new_res.size = os.path.getsize(thumbnail) self.item.res.append(new_res) if not hasattr(self.item, 'attachments'): self.item.attachments = {} self.item.attachments[hash_from_path] = utils.StaticFile(urllib.quote(thumbnail)) if self.mimetype.startswith('video/'): path = self.get_path() caption,_ = os.path.splitext(path) caption = caption + '.srt' if os.path.exists(caption): hash_from_path = str(id(caption)) mimetype = 'smi/caption' new_res = Resource(self.url+'?attachment='+hash_from_path, 'http-get:*:%s:%s' % (mimetype, '*')) new_res.size = os.path.getsize(caption) self.caption = new_res.data self.item.res.append(new_res) if not hasattr(self.item, 'attachments'): self.item.attachments = {} self.item.attachments[hash_from_path] = utils.StaticFile(urllib.quote(caption)) try: # FIXME: getmtime is deprecated in Twisted 2.6 self.item.date = datetime.fromtimestamp(self.location.getmtime()) except: self.item.date = None def rebuild(self, urlbase): #print "rebuild", self.mimetype if self.mimetype != 'item': return #print "rebuild for", self.get_path() mimetype,_ = mimetypes.guess_type(self.get_path(),strict=False) if mimetype == None: return self.mimetype = mimetype #print "rebuild", self.mimetype UPnPClass = classChooser(self.mimetype) self.item = UPnPClass(self.id, self.parent.id, self.get_name()) if hasattr(self.parent, 'cover'): _,ext = os.path.splitext(self.parent.cover) """ add the cover image extension to help clients not reacting on the mimetype """ self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext)) _,host_port,_,_,_ = urlsplit(urlbase) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) else: host = host_port res = Resource('file://'+urllib.quote(self.get_path()), 'internal:%s:%s:*' % (host,self.mimetype)) try: res.size = self.location.getsize() except: res.size = 0 self.item.res.append(res) res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) try: res.size = self.location.getsize() except: res.size = 0 self.item.res.append(res) try: # FIXME: getmtime is deprecated in Twisted 2.6 self.item.date = datetime.fromtimestamp(self.location.getmtime()) except: self.item.date = None self.parent.update_id += 1 def check_for_cover_art(self): """ let's try to find in the current directory some jpg file, or png if the jpg search fails, and take the first one that comes around """ try: jpgs = [i.path for i in self.location.children() if i.splitext()[1] in ('.jpg', '.JPG')] try: self.cover = jpgs[0] except IndexError: pngs = [i.path for i in self.location.children() if i.splitext()[1] in ('.png', '.PNG')] try: self.cover = pngs[0] except IndexError: return except UnicodeDecodeError: self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", self.location.path) def remove(self): #print "FSItem remove", self.id, self.get_name(), self.parent if self.parent: self.parent.remove_child(self) del self.item def add_child(self, child, update=False): self.children.append(child) self.child_count += 1 if isinstance(self.item, Container): self.item.childCount += 1 if update == True: self.update_id += 1 self.sorted = False def remove_child(self, child): #print "remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name()) if child in self.children: self.child_count -= 1 if isinstance(self.item, Container): self.item.childCount -= 1 self.children.remove(child) self.update_id += 1 self.sorted = False def get_children(self,start=0,request_count=0): if self.sorted == False: self.children.sort(key=_natural_key) self.sorted = True if request_count == 0: return self.children[start:] else: return self.children[start:request_count] def get_child_count(self): return self.child_count def get_id(self): return self.id def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): if isinstance( self.location,FilePath): return self.location.path else: self.location def get_realpath(self): if isinstance( self.location,FilePath): return self.location.path else: self.location def set_path(self,path=None,extension=None): if path is None: path = self.get_path() if extension is not None: path,old_ext = os.path.splitext(path) path = ''.join((path,extension)) if isinstance( self.location,FilePath): self.location = FilePath(path) else: self.location = path def get_name(self): if isinstance(self.location,FilePath): name = self.location.basename().decode("utf-8", "replace") else: name = self.location.decode("utf-8", "replace") return name def get_cover(self): try: return self.cover except: try: return self.parent.cover except: return '' def get_parent(self): return self.parent def get_item(self): return self.item def get_xml(self): return self.item.toString() def __repr__(self): return 'id: ' + str(self.id) + ' @ ' + self.get_name().encode('ascii','xmlcharrefreplace') class FSStore(BackendStore): logCategory = 'fs_store' implements = ['MediaServer'] description = """MediaServer exporting files from the file-system""" options = [{'option':'name','type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, {'option':'version','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, {'option':'uuid','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, {'option':'content','type':'string','default':xdg_content(),'help':'the path(s) this MediaServer shall export'}, {'option':'ignore_patterns','type':'string','help':'list of regex patterns, matching filenames will be ignored'}, {'option':'enable_inotify','type':'string','default':'yes','help':'enable real-time monitoring of the content folders'}, {'option':'enable_destroy','type':'string','default':'no','help':'enable deleting a file via an UPnP method'}, {'option':'import_folder','type':'string','help':'The path to store files imported via an UPnP method, if empty the Import method is disabled'} ] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.name = kwargs.get('name','my media') self.content = kwargs.get('content',None) if self.content != None: if isinstance(self.content,basestring): self.content = [self.content] l = [] for a in self.content: l += a.split(',') self.content = l else: self.content = xdg_content() self.content = [x[0] for x in self.content] if self.content == None: self.content = 'tests/content' if not isinstance( self.content, list): self.content = [self.content] self.content = Set([os.path.abspath(x) for x in self.content]) ignore_patterns = kwargs.get('ignore_patterns',[]) self.store = {} self.inotify = None if kwargs.get('enable_inotify','yes') == 'yes': if haz_inotify == True: try: self.inotify = INotify() except Exception,msg: self.info("%s" %msg) else: self.info("%s" %no_inotify_reason) else: self.info("FSStore content auto-update disabled upon user request") if kwargs.get('enable_destroy','no') == 'yes': self.upnp_DestroyObject = self.hidden_upnp_DestroyObject self.import_folder = kwargs.get('import_folder',None) if self.import_folder != None: self.import_folder = os.path.abspath(self.import_folder) if not os.path.isdir(self.import_folder): self.import_folder = None self.ignore_file_pattern = re.compile('|'.join(['^\..*'] + list(ignore_patterns))) parent = None self.update_id = 0 if(len(self.content)>1 or utils.means_true(kwargs.get('create_root',False)) or self.import_folder != None): UPnPClass = classChooser('root') id = str(self.getnextID()) parent = self.store[id] = FSItem( id, parent, 'media', 'root', self.urlbase, UPnPClass, update=True,store=self) if self.import_folder != None: id = str(self.getnextID()) self.store[id] = FSItem( id, parent, self.import_folder, 'directory', self.urlbase, UPnPClass, update=True,store=self) self.import_folder_id = id for path in self.content: if isinstance(path,(list,tuple)): path = path[0] if self.ignore_file_pattern.match(path): continue try: self.walk(path, parent, self.ignore_file_pattern) except Exception,msg: self.warning('on walk of %r: %r' % (path,msg)) import traceback self.debug(traceback.format_exc()) self.wmc_mapping.update({'14': '0', '15': '0', '16': '0', '17': '0' }) louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def __repr__(self): return str(self.__class__).split('.')[-1] def release(self): if self.inotify != None: self.inotify.release() def len(self): return len(self.store) def get_by_id(self,id): #print "get_by_id", id, type(id) # we have referenced ids here when we are in WMC mapping mode if isinstance(id, basestring): id = id.split('@',1) id = id[0] #try: # id = int(id) #except ValueError: # id = 1000 if id == '0': id = '1000' #print "get_by_id 2", id try: r = self.store[id] except: r = None #print "get_by_id 3", r return r def get_id_by_name(self, parent='0', name=''): self.info('get_id_by_name %r (%r) %r' % (parent, type(parent), name)) try: parent = self.store[parent] self.debug("%r %d" % (parent,len(parent.children))) for child in parent.children: #if not isinstance(name, unicode): # name = name.decode("utf8") self.debug("%r %r %r" % (child.get_name(),child.get_realpath(), name == child.get_realpath())) if name == child.get_realpath(): return child.id except: import traceback self.info(traceback.format_exc()) self.debug('get_id_by_name not found') return None def get_url_by_name(self,parent='0',name=''): self.info('get_url_by_name %r %r' % (parent, name)) id = self.get_id_by_name(parent,name) #print 'get_url_by_name', id if id == None: return '' return self.store[id].url def update_config(self,**kwargs): print "update_config", kwargs if 'content' in kwargs: new_content = kwargs['content'] new_content = Set([os.path.abspath(x) for x in new_content.split(',')]) new_folders = new_content.difference(self.content) obsolete_folders = self.content.difference(new_content) print new_folders, obsolete_folders for folder in obsolete_folders: self.remove_content_folder(folder) for folder in new_folders: self.add_content_folder(folder) self.content = new_content def add_content_folder(self,path): path = os.path.abspath(path) if path not in self.content: self.content.add(path) self.walk(path, self.store['1000'], self.ignore_file_pattern) def remove_content_folder(self,path): path = os.path.abspath(path) if path in self.content: id = self.get_id_by_name('1000', path) self.remove(id) self.content.remove(path) def walk(self, path, parent=None, ignore_file_pattern=''): self.debug("walk %r" % path) containers = [] parent = self.append(path,parent) if parent != None: containers.append(parent) while len(containers)>0: container = containers.pop() try: self.debug('adding %r' % container.location) for child in container.location.children(): if ignore_file_pattern.match(child.basename()) != None: continue new_container = self.append(child.path,container) if new_container != None: containers.append(new_container) except UnicodeDecodeError: self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", container.get_path()) def create(self, mimetype, path, parent): self.debug("create ", mimetype, path, type(path), parent) UPnPClass = classChooser(mimetype) if UPnPClass == None: return None id = self.getnextID() if mimetype in ('root','directory'): id = str(id) else: _,ext = os.path.splitext(path) id = str(id) + ext.lower() update = False if hasattr(self, 'update_id'): update = True self.store[id] = FSItem( id, parent, path, mimetype, self.urlbase, UPnPClass, update=True,store=self) if hasattr(self, 'update_id'): self.update_id += 1 #print self.update_id if self.server: if hasattr(self.server,'content_directory_server'): self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent is not None: value = (parent.get_id(),parent.get_update_id()) if self.server: if hasattr(self.server,'content_directory_server'): self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return id def append(self,path,parent): self.debug("append ", path, type(path), parent) if os.path.exists(path) == False: self.warning("path %r not available - ignored", path) return None if stat.S_ISFIFO(os.stat(path).st_mode): self.warning("path %r is a FIFO - ignored", path) return None try: mimetype,_ = mimetypes.guess_type(path, strict=False) if mimetype == None: if os.path.isdir(path): mimetype = 'directory' if mimetype == None: return None id = self.create(mimetype,path,parent) if mimetype == 'directory': if self.inotify is not None: mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CHANGED self.inotify.watch(path, mask=mask, auto_add=False, callbacks=(self.notify,id)) return self.store[id] except OSError, msg: """ seems we have some permissions issues along the content path """ self.warning("path %r isn't accessible, error %r", path, msg) return None def remove(self, id): print 'FSSTore remove id', id try: item = self.store[id] parent = item.get_parent() item.remove() del self.store[id] if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) except: pass def notify(self, iwp, filename, mask, parameter=None): self.info("Event %s on %s %s - parameter %r" % ( ', '.join(self.inotify.flag_to_human(mask)), iwp.path, filename, parameter)) path = iwp.path if filename: path = os.path.join(path, filename) if mask & IN_CHANGED: # FIXME react maybe on access right changes, loss of read rights? #print '%s was changed, parent %d (%s)' % (path, parameter, iwp.path) pass if(mask & IN_DELETE or mask & IN_MOVED_FROM): self.info('%s was deleted, parent %r (%s)' % (path, parameter, iwp.path)) id = self.get_id_by_name(parameter,os.path.join(iwp.path,filename)) if id != None: self.remove(id) if(mask & IN_CREATE or mask & IN_MOVED_TO): if mask & IN_ISDIR: self.info('directory %s was created, parent %r (%s)' % (path, parameter, iwp.path)) else: self.info('file %s was created, parent %r (%s)' % (path, parameter, iwp.path)) if self.get_id_by_name(parameter,os.path.join(iwp.path,filename)) is None: if os.path.isdir(path): self.walk(path, self.get_by_id(parameter), self.ignore_file_pattern) else: if self.ignore_file_pattern.match(filename) == None: self.append(path, self.get_by_id(parameter)) def getnextID(self): ret = self.next_id self.next_id += 1 return ret def backend_import(self,item,data): try: f = open(item.get_path(), 'w+b') if hasattr(data,'read'): data = data.read() f.write(data) f.close() item.rebuild(self.urlbase) return 200 except IOError: self.warning("import of file %s failed" % item.get_path()) except Exception,msg: import traceback self.warning(traceback.format_exc()) return 500 def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', [#'http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01700000000000000000000000000000', #'http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01700000000000000000000000000000', #'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', #'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', #'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', #'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', #'http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000', #'http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000', 'internal:%s:audio/mpeg:*' % self.server.coherence.hostname, 'http-get:*:audio/mpeg:*', 'internal:%s:video/mp4:*' % self.server.coherence.hostname, 'http-get:*:video/mp4:*', 'internal:%s:application/ogg:*' % self.server.coherence.hostname, 'http-get:*:application/ogg:*', 'internal:%s:video/x-msvideo:*' % self.server.coherence.hostname, 'http-get:*:video/x-msvideo:*', 'internal:%s:video/mpeg:*' % self.server.coherence.hostname, 'http-get:*:video/mpeg:*', 'internal:%s:video/avi:*' % self.server.coherence.hostname, 'http-get:*:video/avi:*', 'internal:%s:video/divx:*' % self.server.coherence.hostname, 'http-get:*:video/divx:*', 'internal:%s:video/quicktime:*' % self.server.coherence.hostname, 'http-get:*:video/quicktime:*', 'internal:%s:image/gif:*' % self.server.coherence.hostname, 'http-get:*:image/gif:*', 'internal:%s:image/jpeg:*' % self.server.coherence.hostname, 'http-get:*:image/jpeg:*'], default=True) self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) #self.server.content_directory_server.set_variable(0, 'SortCapabilities', '*') def upnp_ImportResource(self, *args, **kwargs): SourceURI = kwargs['SourceURI'] DestinationURI = kwargs['DestinationURI'] if DestinationURI.endswith('?import'): id = DestinationURI.split('/')[-1] id = id[:-7] # remove the ?import else: return failure.Failure(errorCode(718)) item = self.get_by_id(id) if item == None: return failure.Failure(errorCode(718)) def gotPage(headers): #print "gotPage", headers content_type = headers.get('content-type',[]) if not isinstance(content_type, list): content_type = list(content_type) if len(content_type) > 0: extension = mimetypes.guess_extension(content_type[0], strict=False) item.set_path(None,extension) shutil.move(tmp_path, item.get_path()) item.rebuild(self.urlbase) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: if hasattr(self.server,'content_directory_server'): self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if item.parent is not None: value = (item.parent.get_id(),item.parent.get_update_id()) if self.server: if hasattr(self.server,'content_directory_server'): self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) def gotError(error, url): self.warning("error requesting", url) self.info(error) os.unlink(tmp_path) return failure.Failure(errorCode(718)) tmp_fp, tmp_path = tempfile.mkstemp() os.close(tmp_fp) utils.downloadPage(SourceURI, tmp_path).addCallbacks(gotPage, gotError, None, None, [SourceURI], None) transfer_id = 0 #FIXME return {'TransferID': transfer_id} def upnp_CreateObject(self, *args, **kwargs): #print "CreateObject", kwargs if kwargs['ContainerID'] == 'DLNA.ORG_AnyContainer': if self.import_folder != None: ContainerID = self.import_folder_id else: return failure.Failure(errorCode(712)) else: ContainerID = kwargs['ContainerID'] Elements = kwargs['Elements'] parent_item = self.get_by_id(ContainerID) if parent_item == None: return failure.Failure(errorCode(710)) if parent_item.item.restricted: return failure.Failure(errorCode(713)) if len(Elements) == 0: return failure.Failure(errorCode(712)) elt = DIDLElement.fromString(Elements) if elt.numItems() != 1: return failure.Failure(errorCode(712)) item = elt.getItems()[0] if item.parentID == 'DLNA.ORG_AnyContainer': item.parentID = ContainerID if(item.id != '' or item.parentID != ContainerID or item.restricted == True or item.title == ''): return failure.Failure(errorCode(712)) if('..' in item.title or '~' in item.title or os.sep in item.title): return failure.Failure(errorCode(712)) if item.upnp_class == 'object.container.storageFolder': if len(item.res) != 0: return failure.Failure(errorCode(712)) path = os.path.join(parent_item.get_path(),item.title) id = self.create('directory',path,parent_item) try: os.mkdir(path) except: self.remove(id) return failure.Failure(errorCode(712)) if self.inotify is not None: mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CHANGED self.inotify.watch(path, mask=mask, auto_add=False, callbacks=(self.notify,id)) new_item = self.get_by_id(id) didl = DIDLElement() didl.addItem(new_item.item) return {'ObjectID': id, 'Result': didl.toString()} if item.upnp_class.startswith('object.item'): _,_,content_format,_ = item.res[0].protocolInfo.split(':') extension = mimetypes.guess_extension(content_format, strict=False) path = os.path.join(parent_item.get_realpath(),item.title+extension) id = self.create('item',path,parent_item) new_item = self.get_by_id(id) for res in new_item.item.res: res.importUri = new_item.url+'?import' res.data = None didl = DIDLElement() didl.addItem(new_item.item) return {'ObjectID': id, 'Result': didl.toString()} return failure.Failure(errorCode(712)) def hidden_upnp_DestroyObject(self, *args, **kwargs): ObjectID = kwargs['ObjectID'] item = self.get_by_id(ObjectID) if item == None: return failure.Failure(errorCode(701)) print "upnp_DestroyObject", item.location try: item.location.remove() except Exception, msg: print Exception, msg return failure.Failure(errorCode(715)) return {} if __name__ == '__main__': from twisted.internet import reactor p = 'tests/content' f = FSStore(None,name='my media',content=p, urlbase='http://localhost/xyz') print f.len() print f.get_by_id(1000).child_count, f.get_by_id(1000).get_xml() print f.get_by_id(1001).child_count, f.get_by_id(1001).get_xml() print f.get_by_id(1002).child_count, f.get_by_id(1002).get_xml() print f.get_by_id(1003).child_count, f.get_by_id(1003).get_xml() print f.get_by_id(1004).child_count, f.get_by_id(1004).get_xml() print f.get_by_id(1005).child_count, f.get_by_id(1005).get_xml() print f.store[1000].get_children(0,0) #print f.upnp_Search(ContainerID ='4', # Filter ='dc:title,upnp:artist', # RequestedCount = '1000', # StartingIndex = '0', # SearchCriteria = '(upnp:class = "object.container.album.musicAlbum")', # SortCriteria = '+dc:title') f.upnp_ImportResource(SourceURI='http://spiegel.de',DestinationURI='ttt') reactor.run() Coherence-0.6.6.2/coherence/backends/ampache_storage.py0000644000175000017500000010575011317660740021112 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz import time import mimetypes mimetypes.init() try: import hashlib def md5(s): m =hashlib.md5() m.update(s) return m.hexdigest() def sha256(s): m =hashlib.sha256() m.update(s) return m.hexdigest() except ImportError: import md5 as oldmd5 def md5(s): m=oldmd5.new() m.update(s) return m.hexdigest() from twisted.internet import reactor,defer from twisted.python import failure from coherence.upnp.core import DIDLLite from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import utils import coherence.extern.louie as louie from coherence.backend import BackendItem, BackendStore ROOT_CONTAINER_ID = 0 AUDIO_CONTAINER = 100 AUDIO_ALL_CONTAINER_ID = 101 AUDIO_ARTIST_CONTAINER_ID = 102 AUDIO_ALBUM_CONTAINER_ID = 103 AUDIO_PLAYLIST_CONTAINER_ID = 104 AUDIO_GENRE_CONTAINER_ID = 105 AUDIO_TAG_CONTAINER_ID = 106 VIDEO_CONTAINER_ID = 200 from urlparse import urlsplit class ProxySong(utils.ReverseProxyResource): def __init__(self, uri): self.uri = uri _,host_port,path,query,_ = urlsplit(uri) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 utils.ReverseProxyResource.__init__(self, host, port, '?'.join((path,query))) class Container(BackendItem): logCategory = 'ampache_store' get_path = None def __init__(self, id, parent_id, name, store=None, children_callback=None, container_class=DIDLLite.Container,play_container=False): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.container_class = container_class self.update_id = 0 if children_callback != None: self.children = children_callback else: self.children = [] self.childCount = None self.store = store self.play_container = play_container if self.store!=None: self.get_url = lambda: self.store.urlbase + str(self.id) def add_child(self, child): self.children.append(child) if self.childCount == None: self.childCount = 0 self.childCount += 1 def get_children(self,start=0,end=0): self.info("container.get_children %r %r", start, end) if(end - start > 250 or end - start == 0): end = start+250 if callable(self.children): return self.children(start,end-start) else: children = self.children if end == 0: return children[start:] else: return children[start:end] def get_child_count(self): if self.childCount == None: if callable(self.children): self.childCount = len(self.children()) else: self.childCount = len(self.children) return self.childCount def get_item(self): item = self.container_class(self.id, self.parent_id,self.name) item.childCount = self.get_child_count() #if self.store and self.play_container == True: # if item.childCount > 0: # d = defer.maybeDeferred(self.get_children, 0, 1) # def process_result(r,item): # res = DIDLLite.PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=r[0].get_id()) # item.res.append(res) # return item # def got_error(f,item): # return item # d.addCallback(process_result,item) # d.addErrback(got_error,item) # return d return item def get_name(self): return self.name def get_id(self): return self.id class Playlist(BackendItem): logCategory = 'ampache_store' get_path = None def __init__(self, store, element): self.store = store self.ampache_id = element.get('id') self.id = 'playlist.%d' % int(element.get('id')) self.title = element.find('name').text self.creator = element.find('owner').text self.tracks = int(element.find('items').text) try: self.cover = element.find('art').text except: self.cover = None def get_children(self,start=0,end=0): return self.store.ampache_query('playlist_songs', start, end-start, filter=self.ampache_id) def get_child_count(self): return self.tracks def get_item(self, parent_id = AUDIO_PLAYLIST_CONTAINER_ID): item = DIDLLite.PlaylistItem(self.id, parent_id, self.title) item.childCount = self.get_child_count() #item.artist = self.artist item.albumArtURI = self.cover return item def get_id(self): return self.id def get_name(self): return self.title def get_cover(self): return self.cover class Album(BackendItem): logCategory = 'ampache_store' get_path = None def __init__(self, store, element): self.store = store self.ampache_id = element.get('id') self.id = 'album.%d' % int(element.get('id')) self.title = element.find('name').text self.artist = element.find('artist').text self.tracks = int(element.find('tracks').text) try: self.cover = element.find('art').text except: self.cover = None def get_children(self,start=0,end=0): return self.store.ampache_query('album_songs', start, end-start, filter=self.ampache_id) def get_child_count(self): return self.tracks def get_item(self, parent_id = AUDIO_ALBUM_CONTAINER_ID): item = DIDLLite.MusicAlbum(self.id, parent_id, self.title) item.childCount = self.get_child_count() item.artist = self.artist item.albumArtURI = self.cover #if item.childCount > 0: # d = defer.maybeDeferred(self.get_children, 0, 1) # def process_result(r,item): # res = DIDLLite.PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=r[0].get_id()) # item.res.append(res) # return item # def got_error(f,item): # return item # d.addCallback(process_result,item) # d.addErrback(got_error,item) # return d return item def get_id(self): return self.id def get_name(self): return self.title def get_cover(self): return self.cover class Artist(BackendItem): logCategory = 'ampache_store' get_path = None def __init__(self, store, element): self.store = store self.ampache_id = element.get('id') self.id = 'artist.%d' % int(element.get('id')) try: self.count_albums = int(element.find('albums').text) except: self.count_albums = None try: self.count_songs = int(element.find('songs').text) except: self.count_songs = None self.name = element.find('name').text def get_children(self,start=0,end=0): return self.store.ampache_query('artist_albums', start, end-start, filter=self.ampache_id) def get_child_count(self): if self.count_albums != None: return self.count_albums def got_childs(result): self.count_albums = len(result) return self.count_albums d = self.get_children() d.addCallback(got_childs) return d def get_item(self, parent_id = AUDIO_ARTIST_CONTAINER_ID): item = DIDLLite.MusicArtist(self.id, parent_id, self.name) return item def get_id(self): return self.id def get_name(self): return self.name class Genre(BackendItem): logCategory = 'ampache_store' get_path = None def __init__(self, store, element): self.store = store self.ampache_id = element.get('id') self.id = 'genre.%d' % int(element.get('id')) try: self.count_albums = int(element.find('albums').text) except: self.count_albums = None try: self.count_artists = int(element.find('artists').text) except: self.count_artists = None try: self.count_songs = int(element.find('songs').text) except: self.count_songs = None self.name = element.find('name').text def get_children(self,start=0,end=0): return self.store.ampache_query('genre_songs', start, end-start, filter=self.ampache_id) def get_child_count(self): if self.count_songs != None: return self.count_songs def got_childs(result): self.count_songs = len(result) return self.count_songs d = self.get_children() d.addCallback(got_childs) return d def get_item(self, parent_id = AUDIO_GENRE_CONTAINER_ID): item = DIDLLite.Genre(self.id, parent_id, self.name) return item def get_id(self): return self.id def get_name(self): return self.name class Tag(BackendItem): logCategory = 'ampache_store' get_path = None def __init__(self, store, element): self.store = store self.ampache_id = element.get('id') self.id = 'tag.%d' % int(element.get('id')) try: self.count_albums = int(element.find('albums').text) except: self.count_albums = None try: self.count_artists = int(element.find('artists').text) except: self.count_artists = None try: self.count_songs = int(element.find('songs').text) except: self.count_songs = None self.name = element.find('name').text def get_children(self,start=0,end=0): return self.store.ampache_query('tag_songs', start, end-start, filter=self.ampache_id) def get_child_count(self): if self.count_songs != None: return self.count_songs def got_childs(result): self.count_songs = len(result) return self.count_songs d = self.get_children() d.addCallback(got_childs) return d def get_item(self, parent_id = AUDIO_TAG_CONTAINER_ID): item = DIDLLite.Genre(self.id, parent_id, self.name) return item def get_id(self): return self.id def get_name(self): return self.name class Track(BackendItem): logCategory = 'ampache_store' def __init__(self,store,element): self.store = store self.id = 'song.%d' % int(element.get('id')) self.parent_id = 'album.%d' % int(element.find('album').get('id')) self.url = element.find('url').text seconds = int(element.find('time').text) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 self.duration = ("%d:%02d:%02d") % (hours, minutes, seconds) self.bitrate = 0 self.title = element.find('title').text self.artist = element.find('artist').text self.album = element.find('album').text self.genre = element.find('genre').text self.track_nr = element.find('track').text try: self.cover = element.find('art').text except: self.cover = None self.mimetype = None try: self.mimetype = element.find('mime').text except: self.mimetype,_ = mimetypes.guess_type(self.url, strict=False) if self.mimetype == None: self.mimetype = "audio/mpeg" try: self.size = int(element.find('size').text) except: self.size = 0 if self.store.proxy == True: self.location = ProxySong(self.url) def get_children(self, start=0, request_count=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.debug("Track get_item %r @ %r" %(self.id,self.parent_id)) # create item item = DIDLLite.MusicTrack(self.id,self.parent_id) item.album = self.album item.artist = self.artist #item.date = item.genre = self.genre item.originalTrackNumber = self.track_nr item.title = self.title item.albumArtURI = self.cover # add http resource res = DIDLLite.Resource(self.get_url(), 'http-get:*:%s:*' % self.mimetype) if self.size > 0: res.size = self.size if self.duration > 0: res.duration = str(self.duration) if self.bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.title def get_url(self): if self.store.proxy == True: return self.store.urlbase + str(self.id) else: return self.url def get_path(self): return None class Video(BackendItem): logCategory = 'ampache_store' def __init__(self,store,element): self.store = store self.id = 'video.%d' % int(element.get('id')) self.url = element.find('url').text try: seconds = int(element.find('time').text) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 self.duration = ("%d:%02d:%02d") % (hours, minutes, seconds) except: self.duration = 0 self.cover = None self.title = element.find('title').text self.mimetype = None try: self.mimetype = element.find('mime').text except: self.mimetype,_ = mimetypes.guess_type(self.url, strict=False) if self.mimetype == None: self.mimetype = "video/avi" try: self.size = int(element.find('size').text) except: self.size = 0 if self.store.proxy == True: self.location = ProxySong(self.url) def get_children(self, start=0, request_count=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=VIDEO_CONTAINER_ID): self.debug("video get_item %r @ %r" %(self.id,parent_id)) # create item item = DIDLLite.VideoItem(self.id,parent_id) item.title = self.title item.albumArtURI = self.cover # add http resource res = DIDLLite.Resource(self.get_url(), 'http-get:*:%s:*' % self.mimetype) if self.size > 0: res.size = self.size if self.duration > 0: res.duration = str(self.duration) item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.title def get_url(self): if self.store.proxy == True: return self.store.urlbase + str(self.id) else: return self.url def get_path(self): return None class AmpacheStore(BackendStore): """ this is a backend to the Ampache Media DB """ implements = ['MediaServer'] logCategory = 'ampache_store' def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.config = kwargs self.name = kwargs.get('name','Ampache') self.key = kwargs.get('password',kwargs.get('key','')) self.user = kwargs.get('user',None) self.url = kwargs.get('url','http://localhost/ampache/server/xml.server.php') if kwargs.get('proxy','no') in [1,'Yes','yes','True','true']: self.proxy = True else: self.proxy = False self.update_id = 0 self.token = None self.songs = 0 self.albums = 0 self.artists = 0 self.api_version=int(kwargs.get('api_version',350001)) #self.api_version=int(kwargs.get('api_version',340001)) self.get_token() def __repr__(self): return "Ampache storage" def get_by_id(self,id): self.info("looking for id %r", id) if isinstance(id, basestring): id = id.split('@',1) id = id[0] if isinstance(id, basestring) and id.startswith('artist_all_tracks_'): try: return self.containers[id] except: return None item = None try: id = int(id) item = self.containers[id] except ValueError: try: type,id = id.split('.') if type in ['song','artist','album','playlist','genre','tag','video']: item = self.ampache_query(type, filter=str(id)) except ValueError: return None return item def got_auth_response( self,response,renegotiate=False): self.info( "got_auth_response %r", response) try: response = utils.parse_xml(response, encoding='utf-8') except SyntaxError, msg: self.warning('error parsing ampache answer %r', msg) raise SyntaxError, 'error parsing ampache answer %r' % msg try: error = response.find('error').text self.warning('error on token request %r', error) raise ValueError, error except AttributeError: try: self.token = response.find('auth').text self.songs = int(response.find('songs').text) self.albums = int(response.find('albums').text) self.artists = int(response.find('artists').text) try: self.playlists = int(response.find('playlists').text) except: self.playlists = 0 try: self.genres = int(response.find('genres').text) except: self.genres = 0 try: self.tags = int(response.find('tags').text) except: self.tags = 0 try: self.videos = int(response.find('videos').text) except: self.videos = 0 self.info('ampache returned auth token %r', self.token) self.info('Songs: %d, Artists: %d, Albums: %d, Playlists %d, Genres %d, Tags %d, Videos %d' % (self.songs, self.artists,self.albums,self.playlists,self.genres,self.tags,self.videos)) if renegotiate == False: self.containers = {} self.containers[ROOT_CONTAINER_ID] = \ Container( ROOT_CONTAINER_ID,-1, self.name, store=self) self.wmc_mapping.update({'4': lambda : self.get_by_id(AUDIO_ALL_CONTAINER_ID), # all tracks '5': lambda : self.get_by_id(AUDIO_GENRE_CONTAINER_ID), # all genres '6': lambda : self.get_by_id(AUDIO_ARTIST_CONTAINER_ID), # all artists '7': lambda : self.get_by_id(AUDIO_ALBUM_CONTAINER_ID), # all albums '13': lambda : self.get_by_id(AUDIO_PLAYLIST_CONTAINER_ID), # all playlists '8': lambda : self.get_by_id(VIDEO_CONTAINER_ID), # all videos }) louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) except AttributeError: raise ValueError, 'no authorization token returned' def got_auth_error(self,e,renegotiate=False): self.warning('error calling ampache %r', e) if renegotiate == False: louie.send('Coherence.UPnP.Backend.init_failed', None, backend=self, msg=e) def get_token(self,renegotiate=False): """ ask Ampache for the authorization token """ timestamp = int(time.time()) if self.api_version < 350001: passphrase = md5('%d%s' % (timestamp, self.key)) else: passphrase = sha256('%d%s' % (timestamp, sha256(self.key))) request = ''.join((self.url, '?action=handshake&auth=%s×tamp=%d' % (passphrase, timestamp))) if self.user != None: request = ''.join((request, '&user=%s' % self.user)) if self.api_version != None: request = ''.join((request, '&version=%s' % str(self.api_version))) self.info("auth_request %r", request) d = utils.getPage(request) d.addCallback(self.got_auth_response,renegotiate) d.addErrback(self.got_auth_error,renegotiate) return d def got_error(self, e): self.warning('error calling ampache %r', e) return e def got_response(self, response, query_item, request): self.info("got a response for %r", query_item) self.debug(response) response = utils.parse_xml(response, encoding='utf-8') items = [] try: error = response.find('error') self.warning('error on token request %r %r' % (error.attrib['code'], error.text)) if error.attrib['code'] == '401': # session error, we need to renegotiate our session d = self.get_token(renegotiate=True) def resend_request(result, old_request): # exchange the auth token in the resending request new_request = old_request.split('&') for part in new_request: if part.startswith('auth='): new_request[new_request.index(part)] = 'auth=%s' % self.token break new_request = '&'.join(new_request) self.info("ampache_query %r", new_request) return utils.getPage(new_request) d.addCallback(resend_request, request) d.addErrBack(self.got_error) return d raise ValueError, error.text except AttributeError: if query_item in ('song','artist','album','playlist','genre','tag','video'): q = response.find(query_item) if q == None: return None else: if q.tag in ['song']: return Track(self,q) if q.tag == 'artist': return Artist(self,q) if q.tag in ['album']: return Album(self,q) if q.tag in ['playlist']: return Playlist(self,q) if q.tag in ['genre']: return Genre(self,q) if q.tag in ['tag']: return Tag(self,q) if q.tag in ['video']: return Video(self,q) else: if query_item in ('songs','artists','albums','playlists','genres','tags','videos'): query_item = query_item[:-1] if query_item in ('playlist_songs','album_songs','genre_songs','tag_songs'): query_item = 'song' if query_item in ('artist_albums',): query_item = 'album' for q in response.findall(query_item): if query_item in ('song',): items.append(Track(self,q)) if query_item in ('artist',): items.append(Artist(self,q)) if query_item in ('album',): items.append(Album(self,q)) if query_item in ('playlist',): items.append(Playlist(self,q)) if query_item in ('genre',): items.append(Genre(self,q)) if query_item in ('tag',): items.append(Tag(self,q)) if query_item in ('video',): items.append(Video(self,q)) return items def ampache_query(self, item, start=0, request_count=0, filter=None): request = ''.join((self.url, '?action=%s&auth=%s&offset=%d' % (item,self.token, start))) if request_count > 0: request = ''.join((request, '&limit=%d' % request_count)) if filter != None: request = ''.join((request, '&filter=%s' % filter)) self.info("ampache_query %r", request) d = utils.getPage(request) d.addCallback(self.got_response, item, request) d.addErrback(self.got_error) return d def ampache_query_songs(self, start=0, request_count=0): return self.ampache_query('songs', start, request_count) def ampache_query_albums(self, start=0, request_count=0): return self.ampache_query('albums', start, request_count) def ampache_query_artists(self, start=0, request_count=0): return self.ampache_query('artists', start, request_count) def ampache_query_playlists(self, start=0, request_count=0): return self.ampache_query('playlists', start, request_count) def ampache_query_genres(self, start=0, request_count=0): return self.ampache_query('genres', start, request_count) def ampache_query_tags(self, start=0, request_count=0): return self.ampache_query('tags', start, request_count) def ampache_query_videos(self, start=0, request_count=0): return self.ampache_query('videos', start, request_count) def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:*', 'http-get:*:application/ogg:*', 'http-get:*:video/mp4:*', 'http-get:*:video/x-msvideo:*', 'http-get:*:video/avi:*', 'http-get:*:video/quicktime:*',]) self.containers[AUDIO_ALL_CONTAINER_ID] = \ Container(AUDIO_ALL_CONTAINER_ID,ROOT_CONTAINER_ID, 'All tracks', store=self, children_callback=self.ampache_query_songs, play_container=True) self.containers[AUDIO_ALL_CONTAINER_ID].childCount = self.songs self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALL_CONTAINER_ID]) self.containers[AUDIO_ALBUM_CONTAINER_ID] = \ Container(AUDIO_ALBUM_CONTAINER_ID,ROOT_CONTAINER_ID, 'Albums', store=self, children_callback=self.ampache_query_albums) self.containers[AUDIO_ALBUM_CONTAINER_ID].childCount = self.albums self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ALBUM_CONTAINER_ID]) self.containers[AUDIO_ARTIST_CONTAINER_ID] = \ Container( AUDIO_ARTIST_CONTAINER_ID,ROOT_CONTAINER_ID, 'Artists', store=self, children_callback=self.ampache_query_artists) self.containers[AUDIO_ARTIST_CONTAINER_ID].childCount = self.artists self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_ARTIST_CONTAINER_ID]) self.containers[AUDIO_PLAYLIST_CONTAINER_ID] = \ Container(AUDIO_PLAYLIST_CONTAINER_ID,ROOT_CONTAINER_ID, 'Playlists', store=self, children_callback=self.ampache_query_playlists, container_class=DIDLLite.PlaylistContainer) self.containers[AUDIO_PLAYLIST_CONTAINER_ID].childCount = self.playlists self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_PLAYLIST_CONTAINER_ID]) self.containers[AUDIO_GENRE_CONTAINER_ID] = \ Container(AUDIO_GENRE_CONTAINER_ID,ROOT_CONTAINER_ID, 'Genres', store=self, children_callback=self.ampache_query_genres) self.containers[AUDIO_GENRE_CONTAINER_ID].childCount = self.genres self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_GENRE_CONTAINER_ID]) self.containers[AUDIO_TAG_CONTAINER_ID] = \ Container(AUDIO_TAG_CONTAINER_ID,ROOT_CONTAINER_ID, 'Tags', store=self, children_callback=self.ampache_query_tags) self.containers[AUDIO_TAG_CONTAINER_ID].childCount = self.tags self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_TAG_CONTAINER_ID]) self.containers[VIDEO_CONTAINER_ID] = \ Container(VIDEO_CONTAINER_ID,ROOT_CONTAINER_ID, 'Videos', store=self, children_callback=self.ampache_query_videos) self.containers[VIDEO_CONTAINER_ID].childCount = self.videos self.containers[ROOT_CONTAINER_ID].add_child(self.containers[VIDEO_CONTAINER_ID]) def upnp_XBrowse(self, *args, **kwargs): try: ObjectID = kwargs['ObjectID'] except: self.debug("hmm, a Browse action and no ObjectID argument? An XBox maybe?") try: ObjectID = kwargs['ContainerID'] except: ObjectID = 0 BrowseFlag = kwargs['BrowseFlag'] Filter = kwargs['Filter'] StartingIndex = int(kwargs['StartingIndex']) RequestedCount = int(kwargs['RequestedCount']) SortCriteria = kwargs['SortCriteria'] parent_container = None requested_id = None if BrowseFlag == 'BrowseDirectChildren': parent_container = str(ObjectID) else: requested_id = str(ObjectID) self.info("upnp_Browse request %r %r %r %r", ObjectID, BrowseFlag, StartingIndex, RequestedCount) didl = DIDLLite.DIDLElement(upnp_client=kwargs.get('X_UPnPClient', ''), requested_id=requested_id, parent_container=parent_container) def build_response(tm): num_ret = didl.numItems() #if int(kwargs['RequestedCount']) != 0 and num_ret != int(kwargs['RequestedCount']): # num_ret = 0 #if RequestedCount == 0 and tm-StartingIndex != num_ret: # num_ret = 0 r = {'Result': didl.toString(), 'TotalMatches': tm, 'NumberReturned': num_ret} self.info("upnp_Browse response %r %r", num_ret, tm) if hasattr(item, 'update_id'): r['UpdateID'] = item.update_id elif hasattr(self, 'update_id'): r['UpdateID'] = self.update_id # FIXME else: r['UpdateID'] = 0 return r total = 0 items = [] wmc_mapping = getattr(self, "wmc_mapping", None) if(kwargs.get('X_UPnPClient', '') == 'XBox' and wmc_mapping != None and wmc_mapping.has_key(ObjectID)): """ fake a Windows Media Connect Server """ root_id = wmc_mapping[ObjectID] if callable(root_id): item = root_id() if item is not None: if isinstance(item, list): total = len(item) if int(RequestedCount) == 0: items = item[StartingIndex:] else: items = item[StartingIndex:StartingIndex+RequestedCount] else: d = defer.maybeDeferred( item.get_children, StartingIndex, StartingIndex + RequestedCount) d.addCallback( process_result) d.addErrback(got_error) return d for i in items: didl.addItem(i.get_item()) return build_response(total) root_id = ObjectID item = self.get_by_id(root_id) if item == None: return failure.Failure(errorCode(701)) def got_error(r): return r def process_result(result, found_item): if result == None: result = [] if BrowseFlag == 'BrowseDirectChildren': l = [] def process_items(result, tm): if result == None: result = [] for i in result: if i[0] == True: didl.addItem(i[1]) return build_response(tm) for i in result: d = defer.maybeDeferred(i.get_item) l.append(d) def got_child_count(count): dl = defer.DeferredList(l) dl.addCallback(process_items, count) return dl d = defer.maybeDeferred(found_item.get_child_count) d.addCallback(got_child_count) return d else: didl.addItem(result) total = 1 return build_response(total) def proceed(result): if BrowseFlag == 'BrowseDirectChildren': d = defer.maybeDeferred( result.get_children, StartingIndex, StartingIndex + RequestedCount) else: d = defer.maybeDeferred( result.get_item) d.addCallback( process_result, result) d.addErrback(got_error) return d if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item) if __name__ == '__main__': from coherence.base import Coherence def main(): def got_result(result): print "got_result" def call_browse(ObjectID=0,StartingIndex=0,RequestedCount=0): r = f.backend.upnp_Browse(BrowseFlag='BrowseDirectChildren', RequestedCount=RequestedCount, StartingIndex=StartingIndex, ObjectID=ObjectID, SortCriteria='*', Filter='') r.addCallback(got_result) r.addErrback(got_result) def call_test(start,count): r = f.backend.ampache_query_artists(start,count) r.addCallback(got_result) r.addErrback(got_result) config = {} config['logmode'] = 'warning' c = Coherence(config) f = c.add_plugin('AmpacheStore', url='http://localhost/ampache/server/xml.server.php', key='password', user=None) reactor.callLater(3, call_browse, 0, 0, 0) #reactor.callLater(3, call_browse, AUDIO_ALL_CONTAINER_ID, 0, 0) #reactor.callLater(3, call_browse, AUDIO_ARTIST_CONTAINER_ID, 0, 10) #reactor.callLater(3, call_browse, AUDIO_ALBUM_CONTAINER_ID, 0, 10) #reactor.callLater(3, call_test, 0, 10) reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/appletrailers_storage.py0000644000175000017500000001625011317660740022357 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Benjamin Kampmann """ This is a Media Backend that allows you to access the Trailers from Apple.com """ from coherence.backend import BackendItem, BackendStore from coherence.upnp.core import DIDLLite from coherence.upnp.core.utils import ReverseProxyUriResource from twisted.web import client from twisted.internet import task, reactor from coherence.extern.et import parse_xml XML_URL = "http://www.apple.com/trailers/home/xml/current.xml" ROOT_ID = 0 class AppleTrailerProxy(ReverseProxyUriResource): def __init__(self, uri): ReverseProxyUriResource.__init__(self, uri) def render(self, request): request.received_headers['user-agent'] = 'QuickTime/7.6.2 (qtver=7.6.2;os=Windows NT 5.1Service Pack 3)' return ReverseProxyUriResource.render(self, request) class Trailer(BackendItem): def __init__(self, parent_id, urlbase, id=None, name=None, cover=None, url=None): self.parentid = parent_id self.id = id self.name = name self.cover = cover if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' self.url = urlbase + str(self.id) self.location = AppleTrailerProxy(url) self.item = DIDLLite.VideoItem(id, parent_id, self.name) self.item.albumArtURI = self.cover def get_path(self): return self.url class Container(BackendItem): logCategory = 'apple_trailers' def __init__(self, id, parent_id, name, store=None, \ children_callback=None): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.update_id = 0 self.children = [] self.item = DIDLLite.Container(id, parent_id, self.name) self.item.childCount = None #self.get_child_count() def get_children(self, start=0, end=0): if(end - start > 25 or start - end == start or end - start == 0): end = start+25 if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): return len(self.children) def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class AppleTrailersStore(BackendStore): logCategory = 'apple_trailers' implements = ['MediaServer'] def __init__(self, server, *args, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.name = kwargs.get('name','Apple Trailers') self.refresh = int(kwargs.get('refresh', 8)) * (60 *60) self.trailers = {} self.wmc_mapping = {'15': 0} dfr = self.update_data() # first get the first bunch of data before sending init_completed dfr.addCallback(lambda x: self.init_completed()) def queue_update(self, result): reactor.callLater(self.refresh, self.update_data) return result def update_data(self): dfr = client.getPage(XML_URL) dfr.addCallback(parse_xml) dfr.addCallback(self.parse_data) dfr.addCallback(self.queue_update) return dfr def parse_data(self, xml_data): def iterate(root): for item in root.findall('./movieinfo'): trailer = self._parse_into_trailer(item) yield trailer root = xml_data.getroot() return task.coiterate(iterate(root)) def _parse_into_trailer(self, item): """ info = item.find('info') for attr in ('title', 'runtime', 'rating', 'studio', 'postdate', 'releasedate', 'copyright', 'director', 'description'): setattr(trailer, attr, info.find(attr).text) """ data = {} data['id'] = item.get('id') data['name'] = item.find('./info/title').text data['cover'] = item.find('./poster/location').text data['url'] = item.find('./preview/large').text trailer = Trailer(ROOT_ID, self.urlbase, **data) duration = None try: hours = 0 minutes = 0 seconds = 0 duration = item.find('./info/runtime').text try: hours,minutes,seconds = duration.split(':') except ValueError: try: minutes,seconds = duration.split(':') except ValueError: seconds = duration duration = "%d:%02d:%02d" % (int(hours), int(minutes), int(seconds)) except: pass try: trailer.item.director = item.find('./info/director').text except: pass try: trailer.item.description = item.find('./info/description').text except: pass res = DIDLLite.Resource(trailer.get_path(), 'http-get:*:video/quicktime:*') res.duration = duration try: res.size = item.find('./preview/large').get('filesize',None) except: pass trailer.item.res.append(res) if self.server.coherence.config.get('transcoding', 'no') == 'yes': dlna_pn = 'DLNA.ORG_PN=AVC_TS_BL_CIF15_AAC' dlna_tags = DIDLLite.simple_dlna_tags[:] dlna_tags[2] = 'DLNA.ORG_CI=1' url = self.urlbase + str(trailer.id)+'?transcoded=mp4' new_res = DIDLLite.Resource(url, 'http-get:*:%s:%s' % ('video/mp4', ';'.join([dlna_pn]+dlna_tags))) new_res.size = None res.duration = duration trailer.item.res.append(new_res) dlna_pn = 'DLNA.ORG_PN=JPEG_TN' dlna_tags = DIDLLite.simple_dlna_tags[:] dlna_tags[2] = 'DLNA.ORG_CI=1' dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' url = self.urlbase + str(trailer.id)+'?attachment=poster&transcoded=thumb&type=jpeg' new_res = DIDLLite.Resource(url, 'http-get:*:%s:%s' % ('image/jpeg', ';'.join([dlna_pn] + dlna_tags))) new_res.size = None #new_res.resolution = "160x160" trailer.item.res.append(new_res) if not hasattr(trailer.item, 'attachments'): trailer.item.attachments = {} trailer.item.attachments['poster'] = data['cover'] self.trailers[trailer.id] = trailer def get_by_id(self, id): try: if int(id) == 0: return self.container else: return self.trailers.get(id,None) except: return None def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable( \ 0, 'SourceProtocolInfo', ['http-get:*:video/quicktime:*','http-get:*:video/mp4:*']) self.container = Container(ROOT_ID, -1, self.name) trailers = self.trailers.values() trailers.sort(cmp=lambda x,y : cmp(x.get_name().lower(),y.get_name().lower())) self.container.children = trailers def __repr__(self): return self.__class__.__name__Coherence-0.6.6.2/coherence/backends/flickr_storage.py0000644000175000017500000014144411317660740020766 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007,2008 Frank Scholz import time import re import os.path import mimetools,mimetypes from datetime import datetime from email.Utils import parsedate_tz try: import hashlib def md5(s): m =hashlib.md5() m.update(s) return m.hexdigest() except ImportError: import md5 as oldmd5 def md5(s): m=oldmd5.new() m.update(s) return m.hexdigest() from twisted.python import failure from twisted.web.xmlrpc import Proxy from twisted.web import client from twisted.internet import task from twisted.python.filepath import FilePath from coherence.upnp.core.utils import parse_xml, ReverseProxyResource from coherence.upnp.core.DIDLLite import classChooser,Container,PhotoAlbum,Photo,ImageItem,Resource,DIDLElement from coherence.upnp.core.DIDLLite import simple_dlna_tags, PlayContainerResource from coherence.upnp.core.soap_proxy import SOAPProxy from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core.utils import getPage from coherence.backend import BackendItem, BackendStore from coherence.extern.simple_plugin import Plugin from coherence import log from urlparse import urlsplit ROOT_CONTAINER_ID = 0 INTERESTINGNESS_CONTAINER_ID = 100 RECENT_CONTAINER_ID = 101 FAVORITES_CONTAINER_ID = 102 GALLERY_CONTAINER_ID = 200 UNSORTED_CONTAINER_ID = 201 CONTACTS_CONTAINER_ID = 300 class FlickrAuthenticate(object): def __init__(self,api_key,api_secret,frob,userid,password,perms): from mechanize import Browser browser = Browser() browser.set_handle_robots(False) browser.set_handle_refresh(True,max_time=1) browser.set_handle_redirect(True) api_sig = ''.join((api_secret,'api_key',api_key,'frob',frob,'perms',perms)) api_sig=md5(api_sig) login_url = "http://flickr.com/services/auth/?api_key=%s&perms=%s&frob=%s&api_sig=%s" % (api_key,perms,frob,api_sig) browser.open(login_url) browser.select_form(name='login_form') browser['login']=userid browser['passwd']=password browser.submit() for form in browser.forms(): try: if form['frob'] == frob: browser.form = form browser.submit() break; except: pass else: raise Exception('no form for authentication found') # lame :-/ class ProxyImage(ReverseProxyResource): def __init__(self, uri): self.uri = uri _,host_port,path,_,_ = urlsplit(uri) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 ReverseProxyResource.__init__(self, host, port, path) class FlickrItem(log.Loggable): logCategory = 'flickr_storage' def __init__(self, id, obj, parent, mimetype, urlbase, UPnPClass,store=None,update=False,proxy=False): self.id = id self.real_url = None self.obj = obj self.upnp_class = UPnPClass self.store = store self.item = None self.date = None if isinstance(obj, str): self.name = obj if isinstance(self.id,basestring) and self.id.startswith('upload.'): self.mimetype = mimetype else: self.mimetype = 'directory' elif mimetype == 'directory': title = obj.find('title') self.name = title.text; if len(self.name) == 0: self.name = obj.get('id') self.mimetype = 'directory' elif mimetype == 'contact': self.name = obj.get('realname') if self.name == '': self.name = obj.get('username') self.nsid = obj.get('nsid') self.mimetype = 'directory' else: self.name = obj.get('title') #.encode('utf-8') if self.name == None: self.name = obj.find('title') if self.name != None: self.name = self.name.text if self.name == None or len(self.name) == 0: self.name = 'untitled' self.mimetype = 'image/jpeg' self.parent = parent if not (isinstance(self.id,basestring) and self.id.startswith('upload.')): if parent: parent.add_child(self,update=update) if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' if self.mimetype == 'directory': try: self.flickr_id = obj.get('id') except: self.flickr_id = None self.url = urlbase + str(self.id) elif isinstance(self.id,basestring) and self.id.startswith('upload.'): self.url = urlbase + str(self.id) self.location = None else: self.flickr_id = obj.get('id') try: datetaken = obj.get('datetaken') date,time = datetaken.split(' ') year,month,day = date.split('-') hour,minute,second = time.split(':') self.date = datetime(int(year),int(month),int(day),int(hour),int(minute),int(second)) except: import traceback self.debug(traceback.format_exc()) self.real_url = "http://farm%s.static.flickr.com/%s/%s_%s.jpg" % ( obj.get('farm'), obj.get('server'), obj.get('id'), obj.get('secret')) if proxy == True: self.url = urlbase + str(self.id) self.location = ProxyImage(self.real_url) else: self.url = u"http://farm%s.static.flickr.com/%s/%s_%s.jpg" % ( obj.get('farm').encode('utf-8'), obj.get('server').encode('utf-8'), obj.get('id').encode('utf-8'), obj.get('secret').encode('utf-8')) if parent == None: self.parent_id = -1 else: self.parent_id = parent.get_id() if self.mimetype == 'directory': self.children = [] self.update_id = 0 def set_item_size_and_date(self): def gotPhoto(result): self.debug("gotPhoto", result) _, headers = result length = headers.get('content-length',None) modified = headers.get('last-modified',None) if length != None: self.item.res[0].size = int(length[0]) if modified != None: """ Tue, 06 Feb 2007 15:56:32 GMT """ self.item.date = datetime(*parsedate_tz(modified[0])[0:6]) def gotError(failure, url): self.warning("error requesting", failure, url) self.info(failure) getPage(self.real_url,method='HEAD',timeout=60).addCallbacks(gotPhoto, gotError, None, None, [self.real_url], None) def remove(self): #print "FSItem remove", self.id, self.get_name(), self.parent if self.parent: self.parent.remove_child(self) del self.item def add_child(self, child, update=False): self.children.append(child) if update == True: self.update_id += 1 def remove_child(self, child): self.info("remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name())) if child in self.children: self.children.remove(child) self.update_id += 1 def get_children(self,start=0,request_count=0): if request_count == 0: return self.children[start:] else: return self.children[start:request_count] def get_child_count(self): return len(self.children) def get_id(self): return self.id def get_location(self): return self.location def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): if isinstance(self.id,basestring) and self.id.startswith('upload.'): return '/tmp/' + self.id # FIXME return self.url def get_name(self): return self.name def get_flickr_id(self): return self.flickr_id def get_child_by_flickr_id(self, flickr_id): for c in self.children: if flickr_id == c.flickr_id: return c return None def get_parent(self): return self.parent def get_item(self): if self.item == None: if self.mimetype == 'directory': self.item = self.upnp_class(self.id, self.parent_id, self.get_name()) self.item.childCount = self.get_child_count() if self.get_child_count() > 0: res = PlayContainerResource(self.store.server.uuid,cid=self.get_id(),fid=self.get_children()[0].get_id()) self.item.res.append(res) else: return self.create_item() return self.item def create_item(self): def process(result): for size in result.getiterator('size'): #print size.get('label'), size.get('source') if size.get('label') == 'Original': self.original_url = (size.get('source'),size.get('width')+'x'+size.get('height')) if self.store.proxy == False: self.url = self.original_url[0] else: self.location = ProxyImage(self.original_url[0]) elif size.get('label') == 'Large': self.large_url = (size.get('source'),size.get('width')+'x'+size.get('height')) if self.store.proxy == False: self.url = self.large_url[0] else: self.location = ProxyImage(self.large_url[0]) elif size.get('label') == 'Medium': self.medium_url = (size.get('source'),size.get('width')+'x'+size.get('height')) elif size.get('label') == 'Small': self.small_url = (size.get('source'),size.get('width')+'x'+size.get('height')) elif size.get('label') == 'Thumbnail': self.thumb_url = (size.get('source'),size.get('width')+'x'+size.get('height')) self.item = Photo(self.id,self.parent.get_id(),self.get_name()) #print self.id, self.store.proxy, self.url self.item.date = self.date self.item.attachments = {} dlna_tags = simple_dlna_tags[:] dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' if hasattr(self,'original_url'): dlna_pn = 'DLNA.ORG_PN=JPEG_LRG' if self.store.proxy == False: res = Resource(self.original_url[0], 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) else: res = Resource(self.url+'?attachment=original', 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) self.item.attachments['original'] = ProxyImage(self.original_url[0]) res.resolution = self.original_url[1] self.item.res.append(res) elif hasattr(self,'large_url'): dlna_pn = 'DLNA.ORG_PN=JPEG_LRG' if self.store.proxy == False: res = Resource(self.large_url[0], 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) else: res = Resource(self.url+'?attachment=large', 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) self.item.attachments['large'] = ProxyImage(self.large_url[0]) res.resolution = self.large_url[1] self.item.res.append(res) if hasattr(self,'medium_url'): dlna_pn = 'DLNA.ORG_PN=JPEG_MED' if self.store.proxy == False: res = Resource(self.medium_url[0], 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) else: res = Resource(self.url+'?attachment=medium', 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) self.item.attachments['medium'] = ProxyImage(self.medium_url[0]) res.resolution = self.medium_url[1] self.item.res.append(res) if hasattr(self,'small_url'): dlna_pn = 'DLNA.ORG_PN=JPEG_SM' if self.store.proxy == False: res = Resource(self.small_url[0], 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) else: res = Resource(self.url+'?attachment=small', 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) self.item.attachments['small'] = ProxyImage(self.small_url[0]) res.resolution = self.small_url[1] self.item.res.append(res) if hasattr(self,'thumb_url'): dlna_pn = 'DLNA.ORG_PN=JPEG_TN' if self.store.proxy == False: res = Resource(self.thumb_url[0], 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) else: res = Resource(self.url+'?attachment=thumb', 'http-get:*:%s:%s' % (self.mimetype,';'.join([dlna_pn]+dlna_tags))) self.item.attachments['thumb'] = ProxyImage(self.thumb_url[0]) res.resolution = self.thumb_url[1] self.item.res.append(res) return self.item d = self.store.flickr_photos_getSizes(photo_id=self.flickr_id) d.addCallback(process) return d def get_xml(self): return self.item.toString() def __repr__(self): return 'id: ' + str(self.id) + ' @ ' + str(self.url) class FlickrStore(BackendStore): logCategory = 'flickr_storage' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 10000 self.name = kwargs.get('name','Flickr') self.proxy = kwargs.get('proxy','false') self.refresh = int(kwargs.get('refresh',60))*60 self.limit = int(kwargs.get('limit',100)) self.flickr_userid = kwargs.get('userid',None) self.flickr_password = kwargs.get('password',None) self.flickr_permissions = kwargs.get('permissions',None) if self.proxy in [1,'Yes','yes','True','true']: self.proxy = True else: self.proxy = False ignore_patterns = kwargs.get('ignore_patterns',[]) ignore_file_pattern = re.compile('|'.join(['^\..*'] + list(ignore_patterns))) self.wmc_mapping = {'16': 0} self.update_id = 0 self.flickr = Proxy('http://api.flickr.com/services/xmlrpc/') self.flickr_api_key = '837718c8a622c699edab0ea55fcec224' self.flickr_api_secret = '30a684822c341c3c' self.store = {} self.uploads = {} self.refresh_store_loop = task.LoopingCall(self.refresh_store) self.refresh_store_loop.start(self.refresh, now=False) #self.server.coherence.store_plugin_config(self.server.uuid, {'test':'äöüß'}) self.flickr_userid = kwargs.get('userid',None) self.flickr_password = kwargs.get('password',None) self.flickr_permissions = kwargs.get('permissions','read') self.flickr_authtoken = kwargs.get('authtoken',None) if(self.flickr_authtoken == None and self.server.coherence.writeable_config() == True): if not None in (self.flickr_userid,self.flickr_password): d = self.flickr_authenticate_app() d.addBoth(lambda x: self.init_completed()) return self.init_completed() def __repr__(self): return str(self.__class__).split('.')[-1] def append( self, obj, parent): if isinstance(obj, str): mimetype = 'directory' else: mimetype = 'image/' UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = FlickrItem( id, obj, parent, mimetype, self.urlbase, UPnPClass, store=self, update=update, proxy=self.proxy) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) if mimetype == 'directory': return self.store[id] def update_photo_details(result, photo): dates = result.find('dates') self.debug("update_photo_details", dates.get('posted'), dates.get('taken')) photo.item.date = datetime(*time.strptime(dates.get('taken'), "%Y-%m-%d %H:%M:%S")[0:6]) #d = self.flickr_photos_getInfo(obj.get('id'),obj.get('secret')) #d.addCallback(update_photo_details, self.store[id]) return None def appendDirectory( self, obj, parent): mimetype = 'directory' UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = FlickrItem( id, obj, parent, mimetype, self.urlbase, UPnPClass,store=self,update=update, proxy=self.proxy) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return self.store[id] def appendPhoto( self, obj, parent): mimetype = 'image/' UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = FlickrItem( id, obj, parent, mimetype, self.urlbase, UPnPClass,store=self,update=update, proxy=self.proxy) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) def update_photo_details(result, photo): dates = result.find('dates') self.debug("update_photo_details", dates.get('posted'), dates.get('taken')) photo.item.date = datetime(*time.strptime(dates.get('taken'), "%Y-%m-%d %H:%M:%S")[0:6]) #d = self.flickr_photos_getInfo(obj.get('id'),obj.get('secret')) #d.addCallback(update_photo_details, self.store[id]) return None def appendPhotoset( self, obj, parent): mimetype = 'directory' UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = FlickrItem( id, obj, parent, mimetype, self.urlbase, UPnPClass,store=self,update=update, proxy=self.proxy) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return self.store[id] def appendContact( self, obj, parent): mimetype = 'directory' UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = FlickrItem( id, obj, parent, 'contact', self.urlbase, UPnPClass,store=self,update=update, proxy=self.proxy) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return self.store[id] def remove(self, id): #print 'FlickrStore remove id', id try: item = self.store[int(id)] parent = item.get_parent() item.remove() del self.store[int(id)] if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) except: pass def append_flickr_result(self, result, parent): count = 0 for photo in result.getiterator('photo'): self.append(photo, parent) count += 1 self.info("initialized photo set %s with %d images" % (parent.get_name(), count)) def append_flickr_photo_result(self, result, parent): count = 0 for photo in result.getiterator('photo'): self.appendPhoto(photo, parent) count += 1 self.info("initialized photo set %s with %d images" % (parent.get_name(), count)) def append_flickr_photoset_result(self, result, parent): photoset_count = 0 for photoset in result.getiterator('photoset'): photoset = self.appendPhotoset(photoset, parent) d = self.flickr_photoset(photoset,per_page=self.limit) d.addCallback(self.append_flickr_photo_result, photoset) photoset_count += 1 def append_flickr_contact_result(self, result, parent): contact_count = 0 for contact in result.getiterator('contact'): contact = self.appendContact(contact, parent) d = self.flickr_photosets(user_id=contact.nsid) d.addCallback(self.append_flickr_photoset_result, contact) contact_count += 1 def len(self): return len(self.store) def get_by_id(self,id): if isinstance(id, basestring) and id.startswith('upload.'): self.info("get_by_id looking for %s", id) try: item = self.uploads[id] self.info('get_by_id found %r' % item) return item except: return None if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: id = int(id) except ValueError: id = 0 try: return self.store[id] except: return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret def got_error(self,error): self.warning("trouble refreshing Flickr data %r", error) self.debug("%r", error.getTraceback()) def update_flickr_result(self,result, parent, element='photo'): """ - is in in the store, but not in the update, remove it from the store - the photo is already in the store, skip it - if in the update, but not in the store, append it to the store """ old_ones = {} new_ones = {} for child in parent.get_children(): old_ones[child.get_flickr_id()] = child for photo in result.findall(element): new_ones[photo.get('id')] = photo for id,child in old_ones.items(): if new_ones.has_key(id): self.debug(id, "already there") del new_ones[id] elif child.id != UNSORTED_CONTAINER_ID: self.debug(child.get_flickr_id(), "needs removal") del old_ones[id] self.remove(child.get_id()) self.info("refresh pass 1:", "old", len(old_ones), "new", len(new_ones), "store", len(self.store)) for photo in new_ones.values(): if element == 'photo': self.appendPhoto(photo, parent) elif element == 'photoset': self.appendPhotoset(photo, parent) self.debug("refresh pass 2:", "old", len(old_ones), "new", len(new_ones), "store", len(self.store)) if len(new_ones) > 0: self.info("updated %s with %d new %ss" % (parent.get_name(), len(new_ones), element)) if element == 'photoset': """ now we need to check the childs of all photosets something that should be reworked imho """ for child in parent.get_children(): if child.id == UNSORTED_CONTAINER_ID: continue d = self.flickr_photoset(child,per_page=self.limit) d.addCallback(self.update_flickr_result, child) d.addErrback(self.got_error) def refresh_store(self): self.debug("refresh_store") d = self.flickr_interestingness() d.addCallback(self.update_flickr_result, self.most_wanted) d.addErrback(self.got_error) d = self.flickr_recent() d.addCallback(self.update_flickr_result, self.recent) d.addErrback(self.got_error) if self.flickr_authtoken != None: d= self.flickr_photosets() d.addCallback(self.update_flickr_result, self.photosets, 'photoset') d.addErrback(self.got_error) d = self.flickr_notInSet() d.addCallback(self.update_flickr_result, self.notinset) d.addErrback(self.got_error) d = self.flickr_favorites() d.addCallback(self.update_flickr_result, self.favorites) d.addErrback(self.got_error) def flickr_call(self, method, **kwargs): def got_result(result,method): self.debug('flickr_call %r result %r' % (method,result)) result = parse_xml(result, encoding='utf-8') return result def got_error(error,method): self.warning("connection to Flickr %r service failed! %r" % (method, error)) self.debug("%r", error.getTraceback()) return error args = {} args.update(kwargs) args['api_key'] = self.flickr_api_key if 'api_sig' in args: args['method'] = method self.debug('flickr_call %s %r',method,args) d = self.flickr.callRemote(method, args) d.addCallback(got_result,method) d.addErrback(got_error,method) return d def flickr_test_echo(self): d = self.flickr_call('flickr.test.echo') return d def flickr_test_login(self): d = self.flickr_call('flickr.test.login',signed=True) return d def flickr_auth_getFrob(self): api_sig = self.flickr_create_api_signature(method='flickr.auth.getFrob') d = self.flickr_call('flickr.auth.getFrob',api_sig=api_sig) return d def flickr_auth_getToken(self,frob): api_sig = self.flickr_create_api_signature(frob=frob,method='flickr.auth.getToken') d = self.flickr_call('flickr.auth.getToken',frob=frob,api_sig=api_sig) return d def flickr_photos_getInfo(self, photo_id=None, secret=None): if secret: d = self.flickr_call('flickr.photos.getInfo', photo_id=photo_id, secret=secret) else: d = self.flickr_call('flickr.photos.getInfo', photo_id=photo_id) return d def flickr_photos_getSizes(self, photo_id=None): if self.flickr_authtoken != None: api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,method='flickr.photos.getSizes', photo_id=photo_id) d = self.flickr_call('flickr.photos.getSizes', auth_token=self.flickr_authtoken, photo_id=photo_id,api_sig=api_sig) else: api_sig = self.flickr_create_api_signature(method='flickr.photos.getSizes', photo_id=photo_id) d = self.flickr_call('flickr.photos.getSizes',photo_id=photo_id,api_sig=api_sig) return d def flickr_interestingness(self, date=None, per_page=100): if date == None: date = time.strftime( "%Y-%m-%d", time.localtime(time.time()-86400)) if per_page > 500: per_page = 500 d = self.flickr_call('flickr.interestingness.getList',extras='date_taken',per_page=per_page) return d def flickr_recent(self, date=None, per_page=100): if date == None: date = time.strftime( "%Y-%m-%d", time.localtime(time.time()-86400)) if per_page > 500: per_page = 500 d = self.flickr_call('flickr.photos.getRecent',extras='date_taken',per_page=per_page) return d def flickr_notInSet(self, date=None, per_page=100): api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,extras='date_taken',method='flickr.photos.getNotInSet') d = self.flickr_call('flickr.photos.getNotInSet', auth_token=self.flickr_authtoken, extras='date_taken', api_sig=api_sig) return d def flickr_photosets(self,user_id=None,date=None,per_page=100): if user_id != None: api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,method='flickr.photosets.getList',user_id=user_id) d = self.flickr_call('flickr.photosets.getList', user_id=user_id, auth_token=self.flickr_authtoken, api_sig=api_sig) else: api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,method='flickr.photosets.getList') d = self.flickr_call('flickr.photosets.getList', auth_token=self.flickr_authtoken, api_sig=api_sig) return d def flickr_photoset(self, photoset, date=None, per_page=100): api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,extras='date_taken',method='flickr.photosets.getPhotos',photoset_id=photoset.obj.get('id')) d = self.flickr_call('flickr.photosets.getPhotos', photoset_id=photoset.obj.get('id'), extras='date_taken', auth_token=self.flickr_authtoken, api_sig=api_sig) return d def flickr_favorites(self, date=None, per_page=100): api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,extras='date_taken',method='flickr.favorites.getList') d = self.flickr_call('flickr.favorites.getList', auth_token=self.flickr_authtoken, extras='date_taken', api_sig=api_sig) return d def flickr_contacts(self, date=None, per_page=100): api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,method='flickr.contacts.getList') d = self.flickr_call('flickr.contacts.getList', auth_token=self.flickr_authtoken, api_sig=api_sig) return d def flickr_contact_recents(self, contact, date=None, per_page=100): api_sig = self.flickr_create_api_signature(auth_token=self.flickr_authtoken,method='flickr.photos.getContactsPhotos',photoset_id=photoset.obj.get('id')) d = self.flickr_call('flickr.photos.getContactsPhotos', photoset_id=photoset.obj.get('id'), auth_token=self.flickr_authtoken, api_sig=api_sig) return d def flickr_create_api_signature(self,**fields): api_sig = self.flickr_api_secret + 'api_key' + self.flickr_api_key for key in sorted(fields.keys()): api_sig += key + str(fields[key]) return md5(api_sig) def flickr_authenticate_app(self): def got_error(error): print error def got_auth_token(result): print "got_auth_token", result result = result.getroot() token = result.find('token').text print 'token', token self.flickr_authtoken = token self.server.coherence.store_plugin_config(self.server.uuid, {'authtoken':token}) def get_auth_token(result,frob): d =self.flickr_auth_getToken(frob) d.addCallback(got_auth_token) d.addErrback(got_error) return d def got_frob(result): print "flickr", result result = result.getroot() frob = result.text print frob from twisted.internet import threads d = threads.deferToThread(FlickrAuthenticate,self.flickr_api_key,self.flickr_api_secret,frob,self.flickr_userid,self.flickr_password,self.flickr_permissions) d.addCallback(get_auth_token, frob) d.addErrback(got_error) d = self.flickr_auth_getFrob() d.addCallback(got_frob) d.addErrback(got_error) return d def soap_flickr_test_echo(self, value): client = SOAPProxy("http://api.flickr.com/services/soap/", namespace=("x","urn:flickr"), envelope_attrib=[("xmlns:s", "http://www.w3.org/2003/05/soap-envelope"), ("xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance"), ("xmlns:xsd", "http://www.w3.org/1999/XMLSchema")], soapaction="FlickrRequest") d = client.callRemote( "FlickrRequest", method='flickr.test.echo', name=value, api_key='837718c8a622c699edab0ea55fcec224') def got_results(result): print result d.addCallback(got_results) return d def upnp_init(self): self.current_connection_id = None if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000,' 'http-get:*:image/jpeg:*,' 'http-get:*:image/gif:*,' 'http-get:*:image/png:*', default=True) self.store[ROOT_CONTAINER_ID] = FlickrItem( ROOT_CONTAINER_ID, 'Flickr', None, 'directory', self.urlbase, Container,store=self,update=True, proxy=self.proxy) self.store[INTERESTINGNESS_CONTAINER_ID] = FlickrItem(INTERESTINGNESS_CONTAINER_ID, 'Most Wanted', self.store[ROOT_CONTAINER_ID], 'directory', self.urlbase, PhotoAlbum,store=self,update=True, proxy=self.proxy) self.store[RECENT_CONTAINER_ID] = FlickrItem(RECENT_CONTAINER_ID, 'Recent', self.store[ROOT_CONTAINER_ID], 'directory', self.urlbase, PhotoAlbum,store=self,update=True, proxy=self.proxy) self.most_wanted = self.store[INTERESTINGNESS_CONTAINER_ID] d = self.flickr_interestingness(per_page=self.limit) d.addCallback(self.append_flickr_result, self.most_wanted) self.recent = self.store[RECENT_CONTAINER_ID] d = self.flickr_recent(per_page=self.limit) d.addCallback(self.append_flickr_photo_result, self.recent) if self.flickr_authtoken != None: self.store[GALLERY_CONTAINER_ID] = FlickrItem(GALLERY_CONTAINER_ID, 'Gallery', self.store[ROOT_CONTAINER_ID], 'directory', self.urlbase, PhotoAlbum,store=self,update=True, proxy=self.proxy) self.photosets = self.store[GALLERY_CONTAINER_ID] d = self.flickr_photosets() d.addCallback(self.append_flickr_photoset_result, self.photosets) self.store[UNSORTED_CONTAINER_ID] = FlickrItem(UNSORTED_CONTAINER_ID, 'Unsorted - Not in set', self.store[GALLERY_CONTAINER_ID], 'directory', self.urlbase, PhotoAlbum,store=self,update=True, proxy=self.proxy) self.notinset = self.store[UNSORTED_CONTAINER_ID] d = self.flickr_notInSet(per_page=self.limit) d.addCallback(self.append_flickr_photo_result, self.notinset) self.store[FAVORITES_CONTAINER_ID] = FlickrItem(FAVORITES_CONTAINER_ID, 'Favorites', self.store[ROOT_CONTAINER_ID], 'directory', self.urlbase, PhotoAlbum,store=self,update=True, proxy=self.proxy) self.favorites = self.store[FAVORITES_CONTAINER_ID] d = self.flickr_favorites(per_page=self.limit) d.addCallback(self.append_flickr_photo_result, self.favorites) self.store[CONTACTS_CONTAINER_ID] = FlickrItem(CONTACTS_CONTAINER_ID, 'Friends & Family', self.store[ROOT_CONTAINER_ID], 'directory', self.urlbase, PhotoAlbum,store=self,update=True, proxy=self.proxy) self.contacts = self.store[CONTACTS_CONTAINER_ID] d = self.flickr_contacts() d.addCallback(self.append_flickr_contact_result, self.contacts) def upnp_ImportResource(self, *args, **kwargs): print 'upnp_ImportResource', args, kwargs SourceURI = kwargs['SourceURI'] DestinationURI = kwargs['DestinationURI'] if DestinationURI.endswith('?import'): id = DestinationURI.split('/')[-1] id = id[:-7] # remove the ?import else: return failure.Failure(errorCode(718)) item = self.get_by_id(id) if item == None: return failure.Failure(errorCode(718)) def gotPage(result): try: import cStringIO as StringIO except ImportError: import StringIO self.backend_import(item,StringIO.StringIO(result[0])) def gotError(error, url): self.warning("error requesting", url) self.info(error) return failure.Failure(errorCode(718)) d = getPage(SourceURI) d.addCallbacks(gotPage, gotError, None, None, [SourceURI], None) transfer_id = 0 #FIXME return {'TransferID': transfer_id} def upnp_CreateObject(self, *args, **kwargs): print "upnp_CreateObject", args, kwargs ContainerID = kwargs['ContainerID'] Elements = kwargs['Elements'] parent_item = self.get_by_id(ContainerID) if parent_item == None: return failure.Failure(errorCode(710)) if parent_item.item.restricted: return failure.Failure(errorCode(713)) if len(Elements) == 0: return failure.Failure(errorCode(712)) elt = DIDLElement.fromString(Elements) if elt.numItems() != 1: return failure.Failure(errorCode(712)) item = elt.getItems()[0] if(item.id != '' or item.parentID != ContainerID or item.restricted == True or item.title == ''): return failure.Failure(errorCode(712)) if item.upnp_class.startswith('object.container'): if len(item.res) != 0: return failure.Failure(errorCode(712)) return failure.Failure(errorCode(712)) ### FIXME new_item = self.get_by_id(new_id) didl = DIDLElement() didl.addItem(new_item.item) return {'ObjectID': id, 'Result': didl.toString()} if item.upnp_class.startswith('object.item.imageItem'): new_id = self.getnextID() new_id = 'upload.'+str(new_id) title = item.title or 'unknown' mimetype = 'image/jpeg' self.uploads[new_id] = FlickrItem( new_id, title, self.store[UNSORTED_CONTAINER_ID], mimetype, self.urlbase, ImageItem,store=self,update=False, proxy=self.proxy) new_item = self.uploads[new_id] for res in new_item.item.res: res.importUri = new_item.url+'?import' res.data = None didl = DIDLElement() didl.addItem(new_item.item) r = {'ObjectID': new_id, 'Result': didl.toString()} print r return r return failure.Failure(errorCode(712)) # encode_multipart_form code is inspired by http://www.voidspace.org.uk/python/cgi.shtml#upload def encode_multipart_form(self,fields): boundary = mimetools.choose_boundary() body = [] for k, v in fields.items(): body.append("--" + boundary.encode("utf-8")) header = 'Content-Disposition: form-data; name="%s";' % k if isinstance(v, FilePath): header += 'filename="%s";' % v.basename() body.append(header) header = "Content-Type: application/octet-stream" body.append(header) body.append("") body.append(v.getContent()) elif hasattr(v,'read'): header += 'filename="%s";' % 'unknown' body.append(header) header = "Content-Type: application/octet-stream" body.append(header) body.append("") body.append(v.read()) else: body.append(header) body.append("") body.append(str(v).encode('utf-8')) body.append("--" + boundary.encode("utf-8")) content_type = 'multipart/form-data; boundary=%s' % boundary return (content_type, '\r\n'.join(body)) def flickr_upload(self,image,**kwargs): fields = {} for k,v in kwargs.items(): if v != None: fields[k] = v #fields['api_key'] = self.flickr_api_key fields['auth_token'] = self.flickr_authtoken fields['api_sig'] = self.flickr_create_api_signature(**fields) fields['api_key'] = self.flickr_api_key fields['photo'] = image (content_type, formdata) = self.encode_multipart_form(fields) headers= {"Content-Type": content_type, "Content-Length": str(len(formdata))} d= getPage("http://api.flickr.com/services/upload/", method="POST", headers=headers, postdata=formdata) def got_something(result): print "got_something", result result = parse_xml(result[0], encoding='utf-8') result = result.getroot() if(result.attrib['stat'] == 'ok' and result.find('photoid') != None): photoid = result.find('photoid').text return photoid else: error = result.find('err') return failure.Failure(Exception(error.attrib['msg'])) d.addBoth(got_something) return d def backend_import(self,item,data): """ we expect a FlickrItem and the actual image data as a FilePath or something with a read() method. like the content attribute of a Request """ d = self.flickr_upload(data,title=item.get_name()) def got_photoid(id,item): d = self.flickr_photos_getInfo(photo_id=id) def add_it(obj,parent): print "add_it", obj, obj.getroot(), parent root = obj.getroot() self.appendPhoto(obj.getroot(),parent) return 200 d.addCallback(add_it,item.parent) d.addErrback(got_fail) return d def got_fail(err): print err return err d.addCallback(got_photoid, item) d.addErrback(got_fail) #FIXME we should return the deferred here return d def main(): log.init(None, 'debug') f = FlickrStore(None,userid='x',password='xx', permissions='xxx', authtoken='xxx-x') def got_flickr_result(result): print "flickr", result for photo in result.getiterator('photo'): title = photo.get('title').encode('utf-8') if len(title) == 0: title = u'untitled' for k,item in photo.items(): print k, item url = "http://farm%s.static.flickr.com/%s/%s_%s.jpg" % ( photo.get('farm').encode('utf-8'), photo.get('server').encode('utf-8'), photo.get('id').encode('utf-8'), photo.get('secret').encode('utf-8')) #orginal_url = "http://farm%s.static.flickr.com/%s/%s_%s_o.jpg" % ( # photo.get('farm').encode('utf-8'), # photo.get('server').encode('utf-8'), # photo.get('id').encode('utf-8'), # photo.get('originalsecret').encode('utf-8')) print photo.get('id').encode('utf-8'), title, url def got_upnp_result(result): print "upnp", result def got_error(error): print error #f.flickr_upload(FilePath('/tmp/image.jpg'),title='test') #d = f.flickr_test_echo() #d = f.flickr_interestingness() #d.addCallback(got_flickr_result) #f.upnp_init() #print f.store #r = f.upnp_Browse(BrowseFlag='BrowseDirectChildren', # RequestedCount=0, # StartingIndex=0, # ObjectID=0, # SortCriteria='*', # Filter='') #got_upnp_result(r) if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/lolcats_storage.py0000644000175000017500000003253511317660740021155 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Benjamin Kampmann """ This is a Media Backend that allows you to access the cool and cute pictures from lolcats.com. This is mainly meant as a Sample Media Backend to learn how to write a Media Backend. So. You are still reading which allows me to assume that you want to learn how to write a Media Backend for Coherence. NICE :) . Once again: This is a SIMPLE Media Backend. It does not contain any big requests, searches or even transcoding. The only thing we want to do in this simple example, is to fetch a rss link on startup, parse it, save it and restart the process one hour later again. Well, on top of this, we also want to provide these informations as a Media Server in the UPnP/DLNA Network of course ;) . Wow. You are still reading. You must be really interessted. Then let's go. """ ########## NOTE: # Please don't complain about the coding style of this document - I know. It is # just this way to make it easier to document and to understand. ########## The imports # The entry point for each kind of Backend is a 'BackendStore'. The BackendStore # is the instance that does everything Usually. In this Example it can be # understood as the 'Server', the object retrieving and serving the data. from coherence.backend import BackendStore # The data itself is stored in BackendItems. They are also the first things we # are going to create. from coherence.backend import BackendItem # To make the data 'renderable' we need to define the DIDLite-Class of the Media # we are providing. For that we have a bunch of helpers that we also want to # import from coherence.upnp.core import DIDLLite # Coherence relies on the Twisted backend. I hope you are familar with the # concept of deferreds. If not please read: # http://twistedmatrix.com/projects/core/documentation/howto/async.html # # It is a basic concept that you need to understand to understand the following # code. But why am I talking about it? Oh, right, because we use a http-client # based on the twisted.web.client module to do our requests. from coherence.upnp.core.utils import getPage # And we also import the reactor, that allows us to specify an action to happen # later from twisted.internet import reactor # And to parse the RSS-Data (which is XML), we use the coherence helper from coherence.extern.et import parse_xml ########## The models # After the download and parsing of the data is done, we want to save it. In # this case, we want to fetch the images and store their URL and the title of # the image. That is the LolcatsImage class: class LolcatsImage(BackendItem): # We inherit from BackendItem as it already contains a lot of helper methods # and implementations. For this simple example, we only have to fill the # item with data. def __init__(self, parent_id, id, title, url): self.parentid = parent_id # used to be able to 'go back' self.update_id = 0 self.id = id # each item has its own and unique id self.location = url # the url of the picture self.name = title # the title of the picture. Inside # coherence this is called 'name' # Item.item is a special thing. This is used to explain the client what # kind of data this is. For e.g. A VideoItem or a MusicTrack. In our # case, we have an image. self.item = DIDLLite.ImageItem(id, parent_id, self.name) # each Item.item has to have one or more Resource objects # these hold detailed information about the media data # and can represent variants of it (different sizes, transcoded formats) res = DIDLLite.Resource(self.location, 'http-get:*:image/jpeg:*') res.size = None #FIXME: we should have a size here # and a resolution entry would be nice too self.item.res.append(res) class LolcatsContainer(BackendItem): # The LolcatsContainer will hold the reference to all our LolcatsImages. This # kind of BackenedItem is a bit different from the normal BackendItem, # because it has 'children' (the lolcatsimages). Because of that we have # some more stuff to do in here. def __init__(self, parent_id, id): # the ids as above self.parent_id = parent_id self.id = id # we never have a different name anyway self.name = 'LOLCats' # but we need to set it to a certain mimetype to explain it, that we # contain 'children'. self.mimetype = 'directory' # As we are updating our data periodically, we increase this value so # that our clients can check easier if something has changed since their # last request. self.update_id = 0 # that is where we hold the children self.children = [] # and we need to give a DIDLLite again. This time we want to be # understood as 'Container'. self.item = DIDLLite.Container(id, parent_id, self.name) self.item.childCount = None # will be set as soon as we have images def get_children(self, start=0, end=0): # This is the only important implementation thing: we have to return our # list of children if end != 0: return self.children[start:end] return self.children[start:] # there is nothing special in here # FIXME: move it to a base BackendContainer class def get_child_count(self): return len(self.children) def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id ########## The server # As already said before the implementation of the server is done in an # inheritance of a BackendStore. This is where the real code happens (usually). # In our case this would be: downloading the page, parsing the content, saving # it in the models and returning them on request. class LolcatsStore(BackendStore): # this *must* be set. Because the (most used) MediaServer Coherence also # allows other kind of Backends (like remote lights). implements = ['MediaServer'] # this is only for this implementation: the http link to the lolcats rss # feed that we want to read and parse: rss_url = "http://feeds.feedburner.com/ICanHasCheezburger?format=xml" # as we are going to build a (very small) tree with the items, we need to # define the first (the root) item: ROOT_ID = 0 def __init__(self, server, *args, **kwargs): # first we inizialize our heritage BackendStore.__init__(self,server,**kwargs) # When a Backend is initialized, the configuration is given as keyword # arguments to the initialization. We receive it here as a dicitonary # and allow some values to be set: # the name of the MediaServer as it appears in the network self.name = kwargs.get('name', 'Lolcats') # timeout between updates in hours: self.refresh = int(kwargs.get('refresh', 1)) * (60 *60) # the UPnP device that's hosting that backend, that's already done # in the BackendStore.__init__, just left here the sake of completeness self.server = server # internally used to have a new id for each item self.next_id = 1000 # we store the last update from the rss feed so that we know if we have # to parse again, or not: self.last_updated = None # initialize our lolcats container (no parent, this is the root) self.container = LolcatsContainer(None, self.ROOT_ID) # but as we also have to return them on 'get_by_id', we have our local # store of images per id: self.images = {} # we tell that if an XBox sends a request for images we'll # map the WMC id of that request to our local one self.wmc_mapping = {'16': 0} # and trigger an update of the data dfr = self.update_data() # So, even though the initialize is kind of done, Coherence does not yet # announce our Media Server. # Coherence does wait for signal send by us that we are ready now. # And we don't want that to happen as long as we don't have succeeded # in fetching some first data, so we delay this signaling after the update is done: dfr.addCallback(self.init_completed) dfr.addCallback(self.queue_update) def get_by_id(self, id): print "asked for", id, type(id) # what ever we are asked for, we want to return the container only if isinstance(id, basestring): id = id.split('@',1) id = id[0] if int(id) == self.ROOT_ID: return self.container return self.images.get(int(id), None) def upnp_init(self): # after the signal was triggered, this method is called by coherence and # from now on self.server is existing and we can do # the necessary setup here # that allows us to specify our server options in more detail. # here we define what kind of media content we do provide # mostly needed to make some naughty DLNA devices behave # will probably move into Coherence internals one day self.server.connection_manager_server.set_variable( \ 0, 'SourceProtocolInfo', ['http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', 'http-get:*:image/jpeg:*']) # and as it was done after we fetched the data the first time # we want to take care about the server wide updates as well self._update_container() def _update_container(self, result=None): # we need to inform Coherence about these changes # again this is something that will probably move # into Coherence internals one day if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) value = (self.ROOT_ID,self.container.update_id) self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return result def update_loop(self): # in the loop we want to call update_data dfr = self.update_data() # aftert it was done we want to take care about updating # the container dfr.addCallback(self._update_container) # in ANY case queue an update of the data dfr.addBoth(self.queue_update) def update_data(self): # trigger an update of the data # fetch the rss dfr = getPage(self.rss_url) # push it through our xml parser dfr.addCallback(parse_xml) # then parse the data into our models dfr.addCallback(self.parse_data) return dfr def parse_data(self, xml_data): # So. xml_data is a cElementTree Element now. We can read our data from # it now. # each xml has a root element root = xml_data.getroot() # from there, we look for the newest update and compare it with the one # we have saved. If they are the same, we don't need to go on: pub_date = root.find('./channel/lastBuildDate').text if pub_date == self.last_updated: return # not the case, set this as the last update and continue self.last_updated = pub_date # and reset the childrens list of the container and the local storage self.container.children = [] self.images = {} # Attention, as this is an example, this code is meant to be as simple # as possible and not as efficient as possible. IMHO the following code # pretty much sucks, because it is totally blocking (even though we have # 'only' 20 elements) # we go through our entries and do something specific to the # lolcats-rss-feed to fetch the data out of it url_item = './{http://search.yahoo.com/mrss/}content' for item in root.findall('./channel/item'): title = item.find('./title').text try: url = item.findall(url_item)[1].get('url', None) except IndexError: continue if url is None: continue image = LolcatsImage(self.ROOT_ID, self.next_id, title, url) self.container.children.append(image) self.images[self.next_id] = image # increase the next_id entry every time self.next_id += 1 # and increase the container update id and the system update id # so that the clients can refresh with the new data self.container.update_id += 1 self.update_id += 1 def queue_update(self, error_or_failure): # We use the reactor to queue another updating of our data print error_or_failure reactor.callLater(self.refresh, self.update_loop) Coherence-0.6.6.2/coherence/backends/axiscam_storage.py0000644000175000017500000001446711317660740021145 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007, Frank Scholz # check http://www.iana.org/assignments/rtp-parameters # for the RTP payload type identifier # from sets import Set from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement from coherence.backend import BackendStore,BackendItem class AxisCamItem(BackendItem): logCategory = 'axis_cam_item' def __init__(self, id, obj, parent, mimetype, urlbase, UPnPClass,update=False): self.id = id if mimetype == 'directory': self.name = obj self.mimetype = mimetype else: self.name = obj.get('name') self.mimetype = mimetype self.parent = parent if parent: parent.add_child(self,update=update) if parent == None: parent_id = -1 else: parent_id = parent.get_id() self.item = UPnPClass(id, parent_id, self.name) if isinstance(self.item, Container): self.item.childCount = 0 self.children = [] if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' if self.mimetype == 'directory': self.url = urlbase + str(self.id) else: self.url = obj.get('url') if self.mimetype == 'directory': self.update_id = 0 else: res = Resource(self.url, obj.get('protocol')) res.size = None self.item.res.append(res) def __del__(self): #print "AxisCamItem __del__", self.id, self.name pass def remove(self): #print "AxisCamItem remove", self.id, self.name, self.parent if self.parent: self.parent.remove_child(self) del self.item def add_child(self, child, update=False): self.children.append(child) if isinstance(self.item, Container): self.item.childCount += 1 if update == True: self.update_id += 1 def remove_child(self, child): self.info("remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name())) if child in self.children: if isinstance(self.item, Container): self.item.childCount -= 1 self.children.remove(child) self.update_id += 1 def get_children(self,start=0,request_count=0): if request_count == 0: return self.children[start:] else: return self.children[start:request_count] def get_child_count(self): return len(self.children) def get_id(self): return self.id def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): return self.url def get_name(self): return self.name def get_parent(self): return self.parent def get_item(self): return self.item def get_xml(self): return self.item.toString() def __repr__(self): return 'id: ' + str(self.id) + ' @ ' + self.url class AxisCamStore(BackendStore): logCategory = 'axis_cam_store' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.config = kwargs self.name = kwargs.get('name','AxisCamStore') self.update_id = 0 self.store = {} self.wmc_mapping = {'8': 1000} self.init_completed() def __repr__(self): return str(self.__class__).split('.')[-1] def append( self, obj, parent): if isinstance(obj, basestring): mimetype = 'directory' else: protocol,network,content_type,info = obj['protocol'].split(':') mimetype = content_type UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = AxisCamItem( id, obj, parent, mimetype, self.urlbase, UPnPClass, update=update) if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) if mimetype == 'directory': return self.store[id] return None def len(self): return len(self.store) def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0] id = int(id) if id == 0: id = 1000 try: return self.store[id] except: return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret def upnp_init(self): self.current_connection_id = None parent = self.append('AxisCam', None) source_protocols = Set() for k,v in self.config.items(): if isinstance(v,dict): v['name'] = k source_protocols.add(v['protocol']) self.append(v, parent) if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', source_protocols, default=True) def main(): f = AxisCamStore(None) def got_upnp_result(result): print "upnp", result #f.upnp_init() #print f.store #r = f.upnp_Browse(BrowseFlag='BrowseDirectChildren', # RequestedCount=0, # StartingIndex=0, # ObjectID=0, # SortCriteria='*', # Filter='') #got_upnp_result(r) if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/swr3_storage.py0000644000175000017500000001454711317660740020415 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008,2009 Frank Scholz from datetime import datetime from email.Utils import parsedate_tz from coherence.backend import BackendStore,BackendRssMixin from coherence.backend import BackendItem from coherence.upnp.core import DIDLLite from twisted.python.util import OrderedDict from coherence.upnp.core.utils import getPage from coherence.extern.et import parse_xml ROOT_CONTAINER_ID = 0 class Item(BackendItem): def __init__(self, parent, id, title, url): self.parent = parent self.id = id self.location = url self.name = title self.duration = None self.size = None self.mimetype = 'audio/mpeg' self.description = None self.date = None self.item = None def get_item(self): if self.item == None: self.item = DIDLLite.AudioItem(self.id, self.parent.id, self.name) self.item.description = self.description self.item.date = self.date if hasattr(self.parent, 'cover'): self.item.albumArtURI = self.parent.cover res = DIDLLite.Resource(self.location, 'http-get:*:%s:*' % self.mimetype) res.duration = self.duration res.size = self.size self.item.res.append(res) return self.item class Container(BackendItem): def __init__(self, id, store, parent_id, title): self.url = store.urlbase+str(id) self.parent_id = parent_id self.id = id self.name = title self.mimetype = 'directory' self.update_id = 0 self.children = [] self.item = DIDLLite.Container(self.id, self.parent_id, self.name) self.item.childCount = 0 self.sorted = False def add_child(self, child): id = child.id if isinstance(child.id, basestring): _,id = child.id.split('.') self.children.append(child) self.item.childCount += 1 self.sorted = False def get_children(self, start=0, end=0): if self.sorted == False: def childs_sort(x,y): r = cmp(x.name,y.name) return r self.children.sort(cmp=childs_sort) self.sorted = True if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): return len(self.children) def get_path(self): return self.url def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class SWR3Store(BackendStore,BackendRssMixin): implements = ['MediaServer'] def __init__(self, server, *args, **kwargs): BackendStore.__init__(self,server,**kwargs) self.name = kwargs.get('name', 'SWR3') self.opml = kwargs.get('opml', 'http://www.swr3.de/rdf-feed/podcast/') self.encoding = kwargs.get('encoding', "ISO-8859-1") self.refresh = int(kwargs.get('refresh', 1)) * (60 *60) self.next_id = 1000 self.update_id = 0 self.last_updated = None self.store = {} self.store[ROOT_CONTAINER_ID] = \ Container(ROOT_CONTAINER_ID,self,-1, self.name) self.parse_opml() self.init_completed() def parse_opml(self): def fail(f): self.info("fail %r", f) return f def create_containers(data): feeds = [] for feed in data.findall('body/outline'): #print feed.attrib['type'],feed.attrib['url'] if(feed.attrib['type'] == 'link' and feed.attrib['url'] not in feeds): feeds.append(feed.attrib['url']) self.update_data(feed.attrib['url'],self.get_next_id(),encoding=self.encoding) dfr = getPage(self.opml) dfr.addCallback(parse_xml,encoding=self.encoding) dfr.addErrback(fail) dfr.addCallback(create_containers) dfr.addErrback(fail) def get_next_id(self): self.next_id += 1 return self.next_id def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0] try: return self.store[int(id)] except (ValueError,KeyError): pass return None def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable( \ 0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:*']) def parse_data(self,xml_data,container): root = xml_data.getroot() title = root.find("./channel/title").text title = title.encode(self.encoding).decode('utf-8') self.store[container] = \ Container(container,self,ROOT_CONTAINER_ID, title) description = root.find("./channel/description").text description = description.encode(self.encoding).decode('utf-8') self.store[container].description = description self.store[container].cover = root.find("./channel/image/url").text self.store[ROOT_CONTAINER_ID].add_child(self.store[container]) for podcast in root.findall("./channel/item"): enclosure = podcast.find("./enclosure") title = podcast.find("./title").text title = title.encode(self.encoding).decode('utf-8') item = Item(self.store[container], self.get_next_id(), title, enclosure.attrib['url']) item.size = int(enclosure.attrib['length']) item.mimetype = enclosure.attrib['type'] self.store[container].add_child(item) description = podcast.find("./description") if description != None: description = description.text item.description = description.encode(self.encoding).decode('utf-8') #item.date = datetime(*parsedate_tz(podcast.find("./pubDate").text)[0:6]) #item.date = podcast.find("./pubDate") self.update_id += 1 #if self.server: # self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) # value = (ROOT_CONTAINER_ID,self.container.update_id) # self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) Coherence-0.6.6.2/coherence/backends/elisa_renderer.py0000644000175000017500000003150111317660740020743 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz from twisted.internet import reactor from twisted.internet.task import LoopingCall from twisted.internet.defer import Deferred from twisted.python import failure from twisted.spread import pb from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import DIDLLite import string import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin from coherence import log class ElisaPlayer(log.Loggable, Plugin): """ a backend to the Elisa player """ logCategory = 'elisa_player' implements = ['MediaRenderer'] vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'}} vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}} def __init__(self, device, **kwargs): self.name = kwargs.get('name','Elisa MediaRenderer') self.host = kwargs.get('host','127.0.0.1') self.player = None self.metadata = None if self.host == 'internal': try: from elisa.core import common self.player = common.get_application().get_player() louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) except: self.warning("this works only from within Elisa") raise ImportError else: factory = pb.PBClientFactory() factory.noisy = False reactor.connectTCP(self.host, 8789, factory) d = factory.getRootObject() def result(player): self.player = player louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def got_error(error): self.warning("connection to Elisa failed!") d.addCallback(lambda object: object.callRemote("get_player")) d.addCallback(result) d.addErrback(got_error) self.playing = False self.state = None self.duration = None self.view = [] self.tags = {} self.server = device self.poll_LC = LoopingCall( self.poll_player) def call_player(self, method, callback, *args): self.debug('call player.%s%s', method, args) if self.host == 'internal': dfr = Deferred() dfr.addCallback(callback) result = getattr(self.player, method)(*args) dfr.callback(result) else: dfr = self.player.callRemote(method, *args) dfr.addCallback(callback) return dfr def __repr__(self): return str(self.__class__).split('.')[-1] def poll_player(self): def got_result(result): self.info("poll_player %r", result) if self.server != None: connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) if result in ('STOPPED','READY'): transport_state = 'STOPPED' if result == 'PLAYING': transport_state = 'PLAYING' if result == 'PAUSED': transport_state = 'PAUSED_PLAYBACK' if transport_state == 'PLAYING': self.query_position() if self.state != transport_state: self.state = transport_state if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'TransportState', transport_state) self.call_player("get_readable_state", got_result) def query_position( self): def got_result(result): self.info("query_position", result) position, duration = result if self.server != None: connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack', 0) if position is not None: m,s = divmod( position/1000000000, 60) h,m = divmod(m,60) if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '%02d:%02d:%02d' % (h,m,s)) self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '%02d:%02d:%02d' % (h,m,s)) if duration is not None: m,s = divmod( duration/1000000000, 60) h,m = divmod(m,60) if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackDuration', '%02d:%02d:%02d' % (h,m,s)) self.server.av_transport_server.set_variable(connection_id, 'CurrentMediaDuration', '%02d:%02d:%02d' % (h,m,s)) if self.duration is None: if self.metadata is not None: elt = DIDLLite.DIDLElement.fromString(self.metadata) for item in elt: for res in item.findall('res'): res.attrib['duration'] = "%d:%02d:%02d" % (h,m,s) self.metadata = elt.toString() if self.server != None: self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata) self.duration = duration self.call_player("get_status", got_result) def load( self, uri, metadata): def got_result(result): self.duration = None self.metadata = metadata self.tags = {} connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id) self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','Play,Stop,Pause') self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',1) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri) self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',metadata) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri) self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',metadata) self.call_player("set_uri", got_result, uri) def start(self, uri): self.load(uri) self.play() def stop(self): def got_result(result): self.server.av_transport_server.set_variable( \ self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\ 'TransportState', 'STOPPED') self.call_player("stop", got_result) def play(self): def got_result(result): self.server.av_transport_server.set_variable( \ self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\ 'TransportState', 'PLAYING') self.call_player("play", got_result) def pause(self): def got_result(result): self.server.av_transport_server.set_variable( \ self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\ 'TransportState', 'PAUSED_PLAYBACK') self.call_player("pause", got_result) def seek(self, location): """ @param location: simple number = time to seek to, in seconds +nL = relative seek forward n seconds -nL = relative seek backwards n seconds """ def mute(self): def got_result(result): rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) #FIXME: use result, not True self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'True') self.call_player("mute", got_result) def unmute(self): def got_result(result): rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) #FIXME: use result, not False self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'False') self.call_player("un_mute", got_result) def get_mute(self): def got_infos(result): self.info("got_mute: %r", result) return result return self.call_player("get_mute", got_infos) def get_volume(self): """ playbin volume is a double from 0.0 - 10.0 """ def got_infos(result): self.info("got_volume: %r", result) return result return self.call_player('get_volume', got_infos) def set_volume(self, volume): volume = int(volume) if volume < 0: volume=0 if volume > 100: volume=100 def got_result(result): rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id) #FIXME: use result, not volume self.server.rendering_control_server.set_variable(rcs_id, 'Volume', volume) self.call_player("set_volume", got_result, volume) def upnp_init(self): self.current_connection_id = None self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo', ['internal:%s:*:*' % self.host, 'http-get:*:audio/mpeg:*'], default=True) self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True) self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True) self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True) self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True) self.server.rendering_control_server.set_variable(0, 'Volume', self.get_volume()) self.server.rendering_control_server.set_variable(0, 'Mute', self.get_mute()) self.poll_LC.start( 1.0, True) def upnp_Play(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Speed = int(kwargs['Speed']) self.play() return {} def upnp_Pause(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.pause() return {} def upnp_Stop(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) self.stop() return {} def upnp_SetAVTransportURI(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) CurrentURI = kwargs['CurrentURI'] CurrentURIMetaData = kwargs['CurrentURIMetaData'] local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',') #print '>>>', local_protocol_infos if len(CurrentURIMetaData)==0: self.load(CurrentURI,CurrentURIMetaData) else: elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData) if elt.numItems() == 1: item = elt.getItems()[0] res = item.res.get_matching(local_protocol_infos, protocol_type='internal') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') self.load(res.data,CurrentURIMetaData) return {} return failure.Failure(errorCode(714)) def upnp_SetMute(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredMute = kwargs['DesiredMute'] if DesiredMute in ['TRUE', 'True', 'true', '1','Yes','yes']: self.mute() else: self.unmute() return {} def upnp_SetVolume(self, *args, **kwargs): InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredVolume = int(kwargs['DesiredVolume']) self.set_volume(DesiredVolume) return {} def main(): f = ElisaPlayer(None) def call_player(): f.get_volume() f.get_mute() f.poll_player() reactor.callLater(2,call_player) if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/lastfm_storage.py0000644000175000017500000003335111317660740020777 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php """ INFO lastFM_user Dez 14 17:35:27 Got new sessionid: '1488f34a1cbed7c9f4232f8fd563c3bd' (coherence/backends/lastfm_storage.py:60) DEBUG lastFM_stream Dez 14 17:35:53 render (coherence/backends/lastfm_storage.py:148) command GET rest /user/e0362c757ef49169e9a0f0970cc2d367.mp3 headers {'icy-metadata': '1', 'host': 'kingpin5.last.fm', 'te': 'trailers', 'connection': 'TE', 'user-agent': 'gnome-vfs/2.12.0.19 neon/0.24.7'} ProxyClient handleStatus HTTP/1.1 200 OK ProxyClient handleHeader Content-Type audio/mpeg ProxyClient handleHeader Content-Length 4050441 ProxyClient handleHeader Cache-Control no-cache, must-revalidate DEBUG lastFM_stream Dez 14 17:35:53 render (coherence/backends/lastfm_storage.py:148) command GET rest /user/e0362c757ef49169e9a0f0970cc2d367.mp3 headers {'icy-metadata': '1', 'host': 'kingpin5.last.fm', 'te': 'trailers', 'connection': 'TE', 'user-agent': 'gnome-vfs/2.12.0.19 neon/0.24.7'} ProxyClient handleStatus HTTP/1.1 403 Invalid ticket """ # Copyright 2007, Frank Scholz # Copyright 2007, Moritz Struebe from twisted.internet import defer from coherence.upnp.core import utils from coherence.upnp.core.DIDLLite import classChooser, Container, Resource, DIDLElement import coherence.extern.louie as louie from coherence.extern.simple_plugin import Plugin from coherence import log from coherence.backend import BackendItem, BackendStore from urlparse import urlsplit try: from hashlib import md5 except ImportError: # hashlib is new in Python 2.5 from md5 import md5 import string class LastFMUser(log.Loggable): logCategory = 'lastFM_user' user = None passwd = None host = "ws.audioscrobbler.com" basepath = "/radio" sessionid = None parent = None getting_tracks = False tracks = [] def __init__(self, user, passwd): if user is None: self.warn("No User",) if passwd is None: self.warn("No Passwd",) self.user = user self.passwd = passwd def login(self): if self.sessionid != None: self.warning("Session seems to be valid",) return def got_page(result): lines = result[0].split("\n") for line in lines: tuple = line.rstrip().split("=", 1) if len(tuple) == 2: if tuple[0] == "session": self.sessionid = tuple[1] self.info("Got new sessionid: %r",self.sessionid ) if tuple[0] == "base_url": if(self.host != tuple[1]): self.host = tuple[1] self.info("Got new host: %s",self.host ) if tuple[0] == "base_path": if(self.basepath != tuple[1]): self.basepath = tuple[1] self.info("Got new path: %s",self.basepath) self.get_tracks() def got_error(error): self.warning("Login to LastFM Failed! %r", error) self.debug("%r", error.getTraceback()) def hexify(s): # This function might be GPL! Found this code in some other Projects, too. result = "" for c in s: result = result + ("%02x" % ord(c)) return result password = hexify(md5(self.passwd).digest()) req = self.basepath + "/handshake.php/?version=1&platform=win&username=" + self.user + "&passwordmd5=" + password + "&language=en&player=coherence" utils.getPage("http://" + self.host + req).addCallbacks(got_page, got_error, None, None, None, None) def get_tracks(self): if self.getting_tracks == True: return def got_page(result): result = utils.parse_xml(result, encoding='utf-8') self.getting_tracks = False print self.getting_tracks print "got Tracks" for track in result.findall('trackList/track'): data = {} def get_data(name): #print track.find(name).text.encode('utf-8') return track.find(name).text.encode('utf-8') #Fixme: This section needs some work print "adding Track" data['mimetype'] = 'audio/mpeg' data['name'] =get_data('creator') + " - " + get_data('title') data['title'] = get_data('title') data['artist'] = get_data('creator') data['creator'] = get_data('creator') data['album'] = get_data('album') data['duration'] = get_data('duration') #FIXME: Image is the wrong tag. data['image'] =get_data('image') data['url'] = track.find('location').text.encode('utf-8') item = self.parent.store.append(data, self.parent) self.tracks.append(item) def got_error(error): self.warning("Problem getting Tracks! %r", error) self.debug("%r", error.getTraceback()) self.getting_tracks = False self.getting_tracks = True req = self.basepath + "/xspf.php?sk=" + self.sessionid + "&discovery=0&desktop=1.3.1.1" utils.getPage("http://" + self.host + req).addCallbacks(got_page, got_error, None, None, None, None) def update(self, item): if 0 < self.tracks.count(item): while True: track = self.tracks[0] if track == item: break self.tracks.remove(track) # Do not remoce so the tracks to answer the browse # request correctly. #track.store.remove(track) #del track #if len(self.tracks) < 5: self.get_tracks() class LFMProxyStream(utils.ReverseProxyResource,log.Loggable): logCategory = 'lastFM_stream' def __init__(self, uri, parent): self.uri = uri self.parent = parent _,host_port,path,_,_ = urlsplit(uri) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 if path == '': path = '/' #print "ProxyStream init", host, port, path utils.ReverseProxyResource.__init__(self, host, port, path) def render(self, request): self.debug("render %r", request) self.parent.store.LFM.update(self.parent) self.parent.played = True return utils.ReverseProxyResource.render(self, request) class LastFMItem(log.Loggable): logCategory = 'LastFM_item' def __init__(self, id, obj, parent, mimetype, urlbase, UPnPClass,update=False): self.id = id self.name = obj.get('name') self.title = obj.get('title') self.artist = obj.get('artist') self.creator = obj.get('creator') self.album = obj.get('album') self.duration = obj.get('duration') self.mimetype = mimetype self.parent = parent if parent: parent.add_child(self,update=update) if parent == None: parent_id = -1 else: parent_id = parent.get_id() self.item = UPnPClass(id, parent_id, self.title,False ,self.creator) if isinstance(self.item, Container): self.item.childCount = 0 self.child_count = 0 self.children = [] if( len(urlbase) and urlbase[-1] != '/'): urlbase += '/' if self.mimetype == 'directory': self.url = urlbase + str(self.id) else: self.url = urlbase + str(self.id) self.location = LFMProxyStream(obj.get('url'), self) #self.url = obj.get('url') if self.mimetype == 'directory': self.update_id = 0 else: res = Resource(self.url, 'http-get:*:%s:%s' % (obj.get('mimetype'), ';'.join(('DLNA.ORG_PN=MP3', 'DLNA.ORG_CI=0', 'DLNA.ORG_OP=01', 'DLNA.ORG_FLAGS=01700000000000000000000000000000')))) res.size = -1 #None self.item.res.append(res) def remove(self): if self.parent: self.parent.remove_child(self) del self.item def add_child(self, child, update=False): if self.children == None: self.children = [] self.children.append(child) self.child_count += 1 if isinstance(self.item, Container): self.item.childCount += 1 if update == True: self.update_id += 1 def remove_child(self, child): self.info("remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name())) if child in self.children: self.child_count -= 1 if isinstance(self.item, Container): self.item.childCount -= 1 self.children.remove(child) self.update_id += 1 def get_children(self,start=0,request_count=0): if request_count == 0: return self.children[start:] else: return self.children[start:request_count] def get_child_count(self): if self.mimetype == 'directory': return 100 #Some Testing, with strange Numbers: 0/lots return self.child_count def get_id(self): return self.id def get_update_id(self): if hasattr(self, 'update_id'): return self.update_id else: return None def get_path(self): return self.url def get_name(self): return self.name def get_parent(self): return self.parent def get_item(self): return self.item def get_xml(self): return self.item.toString() def __repr__(self): return 'id: ' + str(self.id) + ' @ ' + self.url + ' ' + self.name class LastFMStore(log.Loggable,Plugin): logCategory = 'lastFM_store' implements = ['MediaServer'] def __init__(self, server, **kwargs): BackendStore.__init__(self,server,**kwargs) self.next_id = 1000 self.config = kwargs self.name = kwargs.get('name','LastFMStore') self.update_id = 0 self.store = {} self.wmc_mapping = {'4': 1000} louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def __repr__(self): return str(self.__class__).split('.')[-1] def append( self, obj, parent): if isinstance(obj, basestring): mimetype = 'directory' else: mimetype = obj['mimetype'] UPnPClass = classChooser(mimetype) id = self.getnextID() update = False if hasattr(self, 'update_id'): update = True self.store[id] = LastFMItem( id, obj, parent, mimetype, self.urlbase, UPnPClass, update=update) self.store[id].store = self if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) if parent: #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return self.store[id] def remove(self, item): try: parent = item.get_parent() item.remove() del self.store[int(id)] if hasattr(self, 'update_id'): self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) #value = '%d,%d' % (parent.get_id(),parent_get_update_id()) value = (parent.get_id(),parent.get_update_id()) if self.server: self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) except: pass def len(self): return len(self.store) def get_by_id(self,id): if isinstance(id, basestring): id = id.split('@',1) id = id[0] id = int(id) if id == 0: id = 1000 try: return self.store[id] except: return None def getnextID(self): ret = self.next_id self.next_id += 1 return ret def upnp_init(self): self.current_connection_id = None parent = self.append({'name':'LastFM','mimetype':'directory'}, None) self.LFM = LastFMUser(self.config.get("login"), self.config.get("password")) self.LFM.parent = parent self.LFM.login() if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:*'], default=True) def main(): f = LastFMStore(None) def got_upnp_result(result): print "upnp", result f.upnp_init() if __name__ == '__main__': from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/light.py0000644000175000017500000000677211317660740017103 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz import coherence.extern.louie as louie from coherence.upnp.core.utils import generalise_boolean from coherence.backend import Backend class SimpleLight(Backend): """ this is a backend for a simple light that only can be switched on or off therefore we need to inform Coherence about the state, and a method to change it everything else is done by Coherence """ implements = ['BinaryLight'] logCategory = 'simple_light' def __init__(self, server, **kwargs): self.name = kwargs.get('name','SimpleLight') self.server = server self.state = 0 # we start switched off louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def upnp_init(self): if self.server: self.server.switch_power_server.set_variable(0, 'Target', self.state) self.server.switch_power_server.set_variable(0, 'Status', self.state) def upnp_SetTarget(self,**kwargs): self.info('upnp_SetTarget %r', kwargs) self.state = int(generalise_boolean(kwargs['NewTargetValue'])) if self.server: self.server.switch_power_server.set_variable(0, 'Target', self.state) self.server.switch_power_server.set_variable(0, 'Status', self.state) print "we have been switched to state", self.state return {} class BetterLight(Backend): implements = ['DimmableLight'] logCategory = 'better_light' def __init__(self, server, **kwargs): self.name = kwargs.get('name','BetterLight') self.server = server self.state = 0 # we start switched off self.loadlevel = 50 # we start with 50% brightness louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def upnp_init(self): if self.server: self.server.switch_power_server.set_variable(0, 'Target', self.state) self.server.switch_power_server.set_variable(0, 'Status', self.state) self.server.dimming_server.set_variable(0, 'LoadLevelTarget', self.loadlevel) self.server.dimming_server.set_variable(0, 'LoadLevelStatus', self.loadlevel) def upnp_SetTarget(self,**kwargs): self.info('upnp_SetTarget %r', kwargs) self.state = int(generalise_boolean(kwargs['NewTargetValue'])) if self.server: self.server.switch_power_server.set_variable(0, 'Target', self.state) self.server.switch_power_server.set_variable(0, 'Status', self.state) print "we have been switched to state", self.state return {} def upnp_SetLoadLevelTarget(self,**kwargs): self.info('SetLoadLevelTarget %r', kwargs) self.loadlevel = int(kwargs['NewLoadlevelTarget']) self.loadlevel = min(max(0,self.loadlevel),100) if self.server: self.server.dimming_server.set_variable(0, 'LoadLevelTarget', self.loadlevel) self.server.dimming_server.set_variable(0, 'LoadLevelStatus', self.loadlevel) print "we have been dimmed to level", self.loadlevel return {} if __name__ == '__main__': from coherence.base import Coherence def main(): config = {} config['logmode'] = 'warning' c = Coherence(config) f = c.add_plugin('SimpleLight') f = c.add_plugin('BetterLight') from twisted.internet import reactor reactor.callWhenRunning(main) reactor.run() Coherence-0.6.6.2/coherence/backends/test_storage.py0000644000175000017500000003303511317660740020467 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz """ A MediaServer backend to test Items Item information can be passed on the commandline or in the config as an XMl fragment coherence --plugin=backend:TestStore,name:Test,\ item:audio.mp3\ audio/mpeg,\ item:audio.ogg\ audio/ogg coherence --plugin="backend:TestStore,name:Test,\ item:gstreamer\ v4l2src num-buffers=1 ! video/x-raw-yuv,width=640,height=480 ! ffmpegcolorspace ! jpegenc name=enc\ image/jpeg>" "video/x-raw-yuv,width=640,height=480" won't work here as it is a delimiter for the plugin string, so if you need things like that in the pipeline, you need to use a config file coherence --plugin="backend:TestStore,name:Test,\ item:process\ man date\ text/html" The XML fragment has these elements: 'type': file - the item is some file-system object (default) url - an item pointing to an object off-site gstreamer - the item is actually a GStreamer pipeline process - the items content is created by an external process 'location': the filesystem path or an url (mandatory) 'mimetype': the mimetype of the item (mandatory) 'extension': an optional extension to append to the url created for the DIDLLite resource data 'title': the 'title' this item should have (optional) 'upnp_class': the DIDLLite class the item shall have, object.item will be taken as default 'fourth_field': value for the 4th field of the protocolInfo phalanx, default is '*' 'pipeline': a GStreamer pipeline that has to end with a bin named 'enc', some pipelines do only work properly when we have a glib mainloop running, so coherence needs to be started with -o glib:yes 'command': the commandline for an external script to run, its output will be returned as the items content In the config file the definition of this backend could look like this: TestStore Test /tmp/audio.mp3 audio/mpeg /tmp/audio.ogg audio/ogg """ import os from twisted.python.filepath import FilePath from twisted.internet import protocol,reactor from twisted.web import resource,server from coherence.backend import BackendStore,BackendRssMixin from coherence.backend import BackendItem try: from coherence.transcoder import GStreamerPipeline except ImportError: pass from coherence.upnp.core import DIDLLite from coherence.upnp.core.utils import parse_xml from coherence import log ROOT_CONTAINER_ID = 0 class ExternalProcessProtocol(protocol.ProcessProtocol): def __init__(self,caller): self.caller = caller def connectionMade(self): print "pp connection made" def outReceived(self, data): print "outReceived with %d bytes!" % len(data) self.caller.write_data(data) def errReceived(self, data): #print "errReceived! with %d bytes!" % len(data) print "pp (err):", data.strip() def inConnectionLost(self): #print "inConnectionLost! stdin is closed! (we probably did it)" pass def outConnectionLost(self): #print "outConnectionLost! The child closed their stdout!" pass def errConnectionLost(self): #print "errConnectionLost! The child closed their stderr." pass def processEnded(self, status_object): print "processEnded, status %d" % status_object.value.exitCode print "processEnded quitting" self.caller.ended = True self.caller.write_data('') class ExternalProcessPipeline(resource.Resource, log.Loggable): logCategory = 'externalprocess' addSlash = True def __init__(self,pipeline,mimetype): self.uri = pipeline self.mimetype = mimetype def render(self, request): print "ExternalProcessPipeline render" if self.mimetype: request.setHeader('content-type', self.mimetype) ExternalProcessProducer(self.uri,request) return server.NOT_DONE_YET class ExternalProcessProducer(object): logCategory = 'externalprocess' addSlash = True def __init__(self, pipeline,request): self.pipeline = pipeline self.request = request self.process = None self.written = 0 self.data = '' self.ended = False request.registerProducer(self, 0) def write_data(self,data): if data: print "write %d bytes of data" % len(data) self.written += len(data) # this .write will spin the reactor, calling .doWrite and then # .resumeProducing again, so be prepared for a re-entrant call self.request.write(data) if self.request and self.ended == True: print "closing" self.request.unregisterProducer() self.request.finish() self.request = None def resumeProducing(self): print "resumeProducing", self.request if not self.request: return if self.process == None: argv = self.pipeline.split() executable = argv[0] argv[0] = os.path.basename(argv[0]) self.process = reactor.spawnProcess(ExternalProcessProtocol(self), executable, argv, {}) def pauseProducing(self): pass def stopProducing(self): print "stopProducing",self.request self.request.unregisterProducer() self.process.loseConnection() self.request.finish() self.request = None class Item(BackendItem): def __init__(self,parent,id,title,location,url): self.parent = parent self.id = id self.location = location self.url = url self.name = title self.duration = None self.size = None self.mimetype = None self.fourth_field = '*' self.description = None self.date = None self.upnp_class = DIDLLite.Item self.item = None def get_item(self): print "get_item %r" % self.item if self.item == None: self.item = self.upnp_class(self.id, self.parent.id, self.get_name()) self.item.description = self.description self.item.date = self.date res = DIDLLite.Resource(self.url, 'http-get:*:%s:%s' % (self.mimetype,self.fourth_field)) res.duration = self.duration res.size = self.get_size() self.item.res.append(res) return self.item def get_name(self): if self.name == None: if isinstance(self.location,FilePath): self.name = self.location.basename().decode("utf-8", "replace") else: self.name = 'item' return self.name def get_path(self): if isinstance( self.location,FilePath): return self.location.path else: return self.location def get_size(self): if isinstance( self.location,FilePath): try: return self.location.getsize() except OSError: return self.size else: return self.size class ResourceItem(Item,BackendItem): def get_name(self): if self.name == None: self.name = 'item' return self.name def get_path(self): return self.location def get_size(self): return self.size class Container(BackendItem): def __init__(self, id, store, parent_id, title): self.url = store.urlbase+str(id) self.parent_id = parent_id self.id = id self.name = title self.mimetype = 'directory' self.update_id = 0 self.children = [] self.item = DIDLLite.Container(self.id, self.parent_id, self.name) self.item.childCount = 0 self.sorted = False def add_child(self, child): print "ADD CHILD %r" % child #id = child.id #if isinstance(child.id, basestring): # _,id = child.id.split('.') self.children.append(child) self.item.childCount += 1 self.sorted = False def get_children(self, start=0, end=0): print "GET CHILDREN" if self.sorted == False: def childs_sort(x,y): r = cmp(x.name,y.name) return r self.children.sort(cmp=childs_sort) self.sorted = True if end != 0: return self.children[start:end] return self.children[start:] def get_child_count(self): return len(self.children) def get_path(self): return self.url def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class TestStore(BackendStore): implements = ['MediaServer'] def __init__(self, server, *args, **kwargs): print "TestStore kwargs", kwargs BackendStore.__init__(self,server,**kwargs) self.name = kwargs.get('name', 'TestServer') self.next_id = 1000 self.update_id = 0 self.store = {} self.store[ROOT_CONTAINER_ID] = \ Container(ROOT_CONTAINER_ID,self,-1, self.name) items = kwargs.get('item', []) if not isinstance( items, list): items = [items] for item in items: if isinstance(item,basestring): xml = parse_xml(item) print xml.getroot() item = {} for child in xml.getroot(): item[child.tag] = child.text type = item.get('type','file') try: name = item.get('title',None) if type == 'file': location = FilePath(item.get('location')) if type == 'url': location = item.get('location') mimetype = item.get('mimetype') item_id = self.get_next_id() extension = item.get('extension') if extension == None: extension = '' if len(extension) and extension[0] != '.': extension = '.' + extension if extension != None: item_id = str(item_id)+extension if type in ('file','url'): new_item = Item(self.store[ROOT_CONTAINER_ID], item_id, name, location,self.urlbase + str(item_id)) elif type == 'gstreamer': pipeline = item.get('pipeline') try: pipeline = GStreamerPipeline(pipeline,mimetype) new_item = ResourceItem(self.store[ROOT_CONTAINER_ID], item_id, name, pipeline,self.urlbase + str(item_id)) except NameError: self.warning("Can't enable GStreamerPipeline, probably pygst not installed") continue elif type == 'process': pipeline = item.get('command') pipeline = ExternalProcessPipeline(pipeline,mimetype) new_item = ResourceItem(self.store[ROOT_CONTAINER_ID], item_id, name, pipeline,self.urlbase + str(item_id)) try: new_item.upnp_class = self.get_upnp_class(item.get('upnp_class','object.item')) except: pass #item.description = u'some text what's the file about' #item.date = something #item.size = something new_item.mimetype = mimetype new_item.fourth_field = item.get('fourth_field','*') self.store[ROOT_CONTAINER_ID].add_child(new_item) self.store[item_id] = new_item except: import traceback self.warning(traceback.format_exc()) #print self.store self.init_completed() def get_upnp_class(self,name): try: return DIDLLite.upnp_classes[name] except KeyError: self.warning("upnp_class %r not found, trying fallback", name) parts = name.split('.') parts.pop() while len(parts) > 1: try: return DIDLLite.upnp_classes['.'.join(parts)] except KeyError: parts.pop() self.warning("WTF - no fallback for upnp_class %r found ?!?", name) return None def get_next_id(self): self.next_id += 1 return self.next_id def get_by_id(self, id): print "GET_BY_ID %r" % id item = self.store.get(id,None) if item == None: if int(id) == 0: item = self.store[ROOT_CONTAINER_ID] else: item = self.store.get(int(id),None) return itemCoherence-0.6.6.2/coherence/backends/tracker_storage.py0000644000175000017500000006734011317660740021151 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz import os.path from twisted.internet import reactor, defer from twisted.python import failure, util from coherence.upnp.core import DIDLLite from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import utils import dbus import dbus.service import coherence.extern.louie as louie from coherence.backend import BackendItem, BackendStore ROOT_CONTAINER_ID = 0 AUDIO_CONTAINER_ID = 100 AUDIO_ALL_CONTAINER_ID = 101 AUDIO_ARTIST_CONTAINER_ID = 102 AUDIO_ALBUM_CONTAINER_ID = 103 AUDIO_PLAYLIST_CONTAINER_ID = 104 AUDIO_GENRE_CONTAINER_ID = 105 VIDEO_CONTAINER_ID = 200 VIDEO_ALL_CONTAINER_ID = 201 IMAGE_CONTAINER_ID = 300 IMAGE_ALL_CONTAINER_ID = 301 BUS_NAME = 'org.freedesktop.Tracker' OBJECT_PATH = '/org/freedesktop/tracker' tracks_query = """ \ \ \ *\ \ \ """ video_query = """ \ \ \ *\ \ \ """ image_query = """ \ \ \ *\ \ \ """ class Container(BackendItem): logCategory = 'tracker_store' def __init__(self, id, parent_id, name, store=None, children_callback=None, container_class=DIDLLite.Container): self.id = id self.parent_id = parent_id self.name = name self.mimetype = 'directory' self.item = container_class(id, parent_id,self.name) self.item.childCount = 0 self.update_id = 0 if children_callback != None: self.children = children_callback else: self.children = util.OrderedDict() self.item.childCount = None #self.get_child_count() if store!=None: self.get_url = lambda: store.urlbase + str(self.id) def add_child(self, child): id = child.id if isinstance(child.id, basestring): _,id = child.id.split('.') self.children[id] = child if self.item.childCount != None: self.item.childCount += 1 def get_children(self,start=0,end=0): self.info("container.get_children %r %r", start, end) if callable(self.children): return self.children(start,end-start) else: children = self.children.values() if end == 0: return children[start:] else: return children[start:end] def get_child_count(self): if self.item.childCount != None: return self.item.childCount if callable(self.children): return len(self.children()) else: return len(self.children) def get_item(self): return self.item def get_name(self): return self.name def get_id(self): return self.id class Artist(BackendItem): logCategory = 'tracker_store' def __init__(self, store, id, name): self.store = store self.id = 'artist.%d' % int(id) self.name = name self.children = {} self.sorted_children = None def add_child(self, child): _,id = child.id.split('.') self.children[id] = child def sort_children(self): if self.sorted_children == None: def childs_sort(x,y): r = cmp(self.children[x].name,self.children[y].name) return r self.sorted_children = self.children.keys() self.sorted_children.sort(cmp=childs_sort) return self.sorted_children def get_artist_all_tracks(self,start=0,request_count=0): children = [] for album in self.sort_children(): children += album.get_children() if request_count == 0: return children[start:] else: return children[start:request_count] def get_children(self,start=0,end=0): children = [] for key in self.sort_children(): children.append(self.children[key]) if end == 0: return children[start:] else: return children[start:end] def get_child_count(self): return len(self.children) def get_item(self, parent_id = AUDIO_ARTIST_CONTAINER_ID): item = DIDLLite.MusicArtist(self.id, parent_id, self.name) return item def get_id(self): return self.id def get_name(self): return self.name class Album(BackendItem): logCategory = 'tracker_store' def __init__(self, store, id, title, artist): self.store = store self.id = 'album.%d' % int(id) self.name = unicode(title) self.artist = unicode(artist) self.cover = None self.children = {} self.sorted_children = None def add_child(self, child): _,id = child.id.split('.') self.children[id] = child def get_children(self,start=0,end=0): children = [] if self.sorted_children != None: for key in self.sorted_children: children.append(self.children[key]) else: def childs_sort(x,y): r = cmp(self.children[x].track_nr,self.children[y].track_nr) return r self.sorted_children = self.children.keys() self.sorted_children.sort(cmp=childs_sort) for key in self.sorted_children: children.append(self.children[key]) if end == 0: return children[start:] else: return children[start:end] def get_child_count(self): return len(self.children) def get_item(self, parent_id = AUDIO_ALBUM_CONTAINER_ID): item = DIDLLite.MusicAlbum(self.id, parent_id, self.name) item.childCount = self.get_child_count() item.artist = self.artist item.albumArtURI = self.cover return item def get_id(self): return self.id def get_name(self): return self.name def get_cover(self): return self.cover class Track(BackendItem): logCategory = 'tracker_store' def __init__(self,store, id,parent_id, file,title, artist,album,genre,\ duration,\ track_number,\ size,mimetype): self.store = store self.id = 'song.%d' % int(id) self.parent_id = parent_id self.path = unicode(file) duration = str(duration).strip() duration = duration.split('.')[0] if len(duration) == 0: duration = 0 seconds = int(duration) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 self.duration = ("%d:%02d:%02d") % (hours, minutes, seconds) self.bitrate = 0 self.title = unicode(title) self.artist = unicode(artist) self.album = unicode(album) self.genre = unicode(genre) track_number = str(track_number).strip() if len(track_number) == 0: track_number = 1 self.track_nr = int(track_number) self.cover = None self.mimetype = str(mimetype) self.size = int(size) self.url = self.store.urlbase + str(self.id) def get_children(self, start=0, end=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.debug("Track get_item %r @ %r" %(self.id,self.parent_id)) # create item item = DIDLLite.MusicTrack(self.id,self.parent_id) item.album = self.album item.artist = self.artist #item.date = item.genre = self.genre item.originalTrackNumber = self.track_nr item.title = self.title item.albumArtURI = self.cover # add http resource res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) if self.size > 0: res.size = self.size if self.duration > 0: res.duration = str(self.duration) if self.bitrate > 0: res.bitrate = str(bitrate) item.res.append(res) #if self.store.server.coherence.config.get('transcoding', 'no') == 'yes': # if self.mimetype in ('audio/mpeg', # 'application/ogg','audio/ogg', # 'audio/x-m4a', # 'application/x-flac'): # dlna_pn = 'DLNA.ORG_PN=LPCM' # dlna_tags = DIDLLite.simple_dlna_tags[:] # dlna_tags[1] = 'DLNA.ORG_CI=1' # #dlna_tags[2] = 'DLNA.ORG_OP=00' # new_res = DIDLLite.Resource(self.url+'?transcoded=lpcm', # 'http-get:*:%s:%s' % ('audio/L16;rate=44100;channels=2', ';'.join([dlna_pn]+dlna_tags))) # new_res.size = None # if self.duration > 0: # new_res.duration = str(self.duration) # item.res.append(new_res) # if self.mimetype != 'audio/mpeg': # new_res = DIDLLite.Resource(self.url+'?transcoded=mp3', # 'http-get:*:%s:*' % 'audio/mpeg') # new_res.size = None # if self.duration > 0: # new_res.duration = str(self.duration) # item.res.append(new_res) return item def get_id(self): return self.id def get_name(self): return self.title def get_url(self): return self.url def get_path(self): return self.path class Video(BackendItem): logCategory = 'tracker_store' def __init__(self,store, id,parent_id, file,title, duration,\ size,mimetype): self.store = store self.id = 'video.%d' % int(id) self.parent_id = parent_id self.path = unicode(file) duration = str(duration).strip() duration = duration.split('.')[0] if len(duration) == 0: duration = 0 seconds = int(duration) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 self.duration = ("%d:%02d:%02d") % (hours, minutes, seconds) self.title = unicode(title) self.cover = None self.mimetype = str(mimetype) self.size = int(size) self.url = self.store.urlbase + str(self.id) def get_children(self, start=0, end=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.debug("Video get_item %r @ %r" %(self.id,self.parent_id)) # create item item = DIDLLite.VideoItem(self.id,self.parent_id) #item.date = item.title = self.title item.albumArtURI = self.cover # add http resource res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) if self.size > 0: res.size = self.size if self.duration > 0: res.duration = str(self.duration) item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.title def get_url(self): return self.url def get_path(self): return self.path class Image(BackendItem): logCategory = 'tracker_store' def __init__(self,store, id,parent_id, file,title,album, date,width,height,\ size,mimetype): self.store = store self.id = 'image.%d' % int(id) self.parent_id = parent_id self.path = unicode(file) self.title = unicode(title) self.album = unicode(album.strip()) self.mimetype = str(mimetype) self.size = int(size) self.url = self.store.urlbase + str(self.id) def get_children(self, start=0, end=0): return [] def get_child_count(self): return 0 def get_item(self, parent_id=None): self.debug("Image get_item %r @ %r" %(self.id,self.parent_id)) # create item item = DIDLLite.ImageItem(self.id,self.parent_id) #item.date = item.title = self.title # add http resource res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype) if self.size > 0: res.size = self.size item.res.append(res) return item def get_id(self): return self.id def get_name(self): return self.title def get_url(self): return self.url def get_path(self): return self.path class TrackerStore(BackendStore): """ this is a backend to Meta Tracker http://www.gnome.org/projects/tracker/index.html """ implements = ['MediaServer'] logCategory = 'tracker_store' def __init__(self, server, **kwargs): if server.coherence.config.get('use_dbus','no') != 'yes': raise Exception, 'this backend needs use_dbus enabled in the configuration' BackendStore.__init__(self,server,**kwargs) self.config = kwargs self.name = kwargs.get('name','Tracker') self.update_id = 0 self.token = None self.songs = 0 self.albums = 0 self.artists = 0 self.playlists = 0 self.genres = 0 self.videos = 0 self.images = 0 self.bus = dbus.SessionBus() tracker_object = self.bus.get_object(BUS_NAME,OBJECT_PATH) self.tracker_interface = dbus.Interface(tracker_object, 'org.freedesktop.Tracker') self.search_interface = dbus.Interface(tracker_object, 'org.freedesktop.Tracker.Search') self.keywords_interface = dbus.Interface(tracker_object, 'org.freedesktop.Tracker.Keywords') self.metadata_interface = dbus.Interface(tracker_object, 'org.freedesktop.Tracker.Metadata') self.query_id = -1 self.containers = {} self.tracks = {} self.containers[ROOT_CONTAINER_ID] = \ Container(ROOT_CONTAINER_ID,-1,self.name,store=self) def queries_finished(r): louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) def queries_failed(r): error = '' louie.send('Coherence.UPnP.Backend.init_failed', None, backend=self, msg=error) services = kwargs.get('service','Music,Videos,Images') services = map(lambda x: x.strip().lower(),services.split(',')) l = [] mapping = {'music':self.get_tracks, 'videos':self.get_videos, 'images':self.get_images} for service in services: try: l.append(mapping[service]()) except KeyError: self.warning('Wrong Tracker service definition - %r' % service) if len(l)>0: dl = defer.DeferredList(l) dl.addCallback(queries_finished) dl.addErrback(lambda x: louie.send('Coherence.UPnP.Backend.init_failed', None, backend=self, msg='Connection to Tracker service(s) failed!')) else: louie.send('Coherence.UPnP.Backend.init_failed', None, backend=self, msg='No Tracker service defined!') def __repr__(self): return "TrackerStore" def get_by_id(self,id): self.info("looking for id %r", id) if isinstance(id, basestring): id = id.split('@',1) id = id[0] if isinstance(id, basestring) and id.startswith('artist_all_tracks_'): try: return self.containers[id] except: return None item = None try: id = int(id) item = self.containers[id] except (ValueError,KeyError): try: type,id = id.split('.') if type == 'song': return self.containers[AUDIO_ALL_CONTAINER_ID].children[id] if type == 'album': return self.containers[AUDIO_ALBUM_CONTAINER_ID].children[id] if type == 'artist': return self.containers[AUDIO_ARTIST_CONTAINER_ID].children[id] if type == 'video': return self.containers[VIDEO_ALL_CONTAINER_ID].children[id] if type == 'image': return self.containers[IMAGE_ALL_CONTAINER_ID].children[id] except (ValueError,KeyError): return None return item def get_videos(self): def handle_error(error): print error return error def parse_videos_query_result(resultlist): videos = [] for video in resultlist: file,_,title,\ duration,\ size,mimetype = video title = title.strip() if len(title) == 0: title = os.path.basename(file) if mimetype == 'video/x-theora+ogg': mimetype = u'video/ogg' video_item = Video(self, self.videos,VIDEO_ALL_CONTAINER_ID, file,title,\ duration,\ size,mimetype) self.videos += 1 videos.append(video_item) videos.sort(cmp=lambda x,y : cmp(x.get_name().lower(),y.get_name().lower())) for video_item in videos: self.containers[VIDEO_ALL_CONTAINER_ID].add_child(video_item) self.containers[VIDEO_CONTAINER_ID] = \ Container(VIDEO_CONTAINER_ID,ROOT_CONTAINER_ID,'Video',store=self) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[VIDEO_CONTAINER_ID]) self.containers[VIDEO_ALL_CONTAINER_ID] = \ Container( VIDEO_ALL_CONTAINER_ID,VIDEO_CONTAINER_ID,'All Videos', store=self, children_callback=None) self.containers[VIDEO_CONTAINER_ID].add_child(self.containers[VIDEO_ALL_CONTAINER_ID]) fields=[u'Video:Title',u'Video:Duration', u'File:Size',u'File:Mime'] d = defer.Deferred() d.addCallback(parse_videos_query_result) d.addErrback(handle_error) self.search_interface.Query(self.query_id,'Videos',fields,'','',video_query,False,0,-1, reply_handler=lambda x: d.callback(x),error_handler=lambda x: d.errback(x)) return d def get_images(self): def handle_error(error): return error def parse_images_query_result(resultlist): print "images", resultlist images = [] for image in resultlist: file,_,title,album,\ date,width, height, \ size,mimetype = image title = title.strip() if len(title) == 0: title = os.path.basename(file) image_item = Image(self, self.images,IMAGE_ALL_CONTAINER_ID, file,title,album,\ date,width,height,\ size,mimetype) self.images += 1 images.append(image_item) images.sort(cmp=lambda x,y : cmp(x.get_name().lower(),y.get_name().lower())) for image_item in images: self.containers[IMAGE_ALL_CONTAINER_ID].add_child(image_item) self.containers[IMAGE_CONTAINER_ID] = \ Container(IMAGE_CONTAINER_ID,ROOT_CONTAINER_ID,'Images',store=self) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[IMAGE_CONTAINER_ID]) self.containers[IMAGE_ALL_CONTAINER_ID] = \ Container(IMAGE_ALL_CONTAINER_ID,IMAGE_CONTAINER_ID,'All Images', store=self, children_callback=None) self.containers[IMAGE_CONTAINER_ID].add_child(self.containers[IMAGE_ALL_CONTAINER_ID]) fields=[u'Image:Title',u'Image:Album', u'Image:Date',u'Image:Width',u'Image:Height', u'File:Size',u'File:Mime'] d = defer.Deferred() d.addCallback(parse_images_query_result) d.addErrback(handle_error) self.search_interface.Query(self.query_id,'Images',fields,'','',image_query,False,0,-1, reply_handler=lambda x: d.callback(x),error_handler=lambda x: d.errback(x)) return d def get_tracks(self): def handle_error(error): return error def parse_tracks_query_result(resultlist): albums = {} artists = {} tracks = [] for track in resultlist: file,service,title,artist,album,genre,\ duration,album_track_count,\ track_number,codec,\ size,mimetype = track if mimetype == 'video/x-vorbis+ogg': mimetype = 'audio/ogg' track_item = Track(self, self.songs,AUDIO_ALL_CONTAINER_ID, file,title,artist,album,genre,\ duration,\ track_number,\ size,mimetype) self.songs += 1 tracks.append(track_item) tracks.sort(cmp=lambda x,y : cmp(x.get_name(),y.get_name())) for track_item in tracks: self.containers[AUDIO_ALL_CONTAINER_ID].add_child(track_item) try: album_item = albums[track_item.album] album_item.add_child(track_item) except: album_item = Album(self, self.albums, track_item.album, track_item.artist) albums[unicode(track_item.album)] = album_item self.albums += 1 album_item.add_child(track_item) try: artist_item = artists[track_item.artist] artist_item.add_child(album_item) except: artist_item = Artist(self, self.artists, track_item.artist) artists[unicode(track_item.artist)] = artist_item self.artists += 1 artist_item.add_child(album_item) sorted_keys = albums.keys() sorted_keys.sort() for key in sorted_keys: self.containers[AUDIO_ALBUM_CONTAINER_ID].add_child(albums[key]) sorted_keys = artists.keys() sorted_keys.sort() for key in sorted_keys: self.containers[AUDIO_ARTIST_CONTAINER_ID].add_child(artists[key]) self.containers[AUDIO_CONTAINER_ID] = \ Container(AUDIO_CONTAINER_ID,ROOT_CONTAINER_ID,'Audio',store=self) self.containers[ROOT_CONTAINER_ID].add_child(self.containers[AUDIO_CONTAINER_ID]) self.containers[AUDIO_ALL_CONTAINER_ID] = \ Container( AUDIO_ALL_CONTAINER_ID,AUDIO_CONTAINER_ID,'All Tracks', store=self, children_callback=None) self.containers[AUDIO_CONTAINER_ID].add_child(self.containers[AUDIO_ALL_CONTAINER_ID]) self.containers[AUDIO_ALBUM_CONTAINER_ID] = \ Container( AUDIO_ALBUM_CONTAINER_ID,AUDIO_CONTAINER_ID,'Albums', store=self, children_callback=None) self.containers[AUDIO_CONTAINER_ID].add_child(self.containers[AUDIO_ALBUM_CONTAINER_ID]) self.containers[AUDIO_ARTIST_CONTAINER_ID] = \ Container( AUDIO_ARTIST_CONTAINER_ID,AUDIO_CONTAINER_ID,'Artists', store=self, children_callback=None) self.containers[AUDIO_CONTAINER_ID].add_child(self.containers[AUDIO_ARTIST_CONTAINER_ID]) self.containers[AUDIO_PLAYLIST_CONTAINER_ID] = \ Container( AUDIO_PLAYLIST_CONTAINER_ID,AUDIO_CONTAINER_ID,'Playlists', store=self, children_callback=None, container_class=DIDLLite.PlaylistContainer) self.containers[AUDIO_CONTAINER_ID].add_child(self.containers[AUDIO_PLAYLIST_CONTAINER_ID]) self.containers[AUDIO_GENRE_CONTAINER_ID] = \ Container( AUDIO_GENRE_CONTAINER_ID,AUDIO_CONTAINER_ID,'Genres', store=self, children_callback=None) self.containers[AUDIO_CONTAINER_ID].add_child(self.containers[AUDIO_GENRE_CONTAINER_ID]) self.wmc_mapping.update({'4': lambda : self.get_by_id(AUDIO_ALL_CONTAINER_ID), # all tracks '5': lambda : self.get_by_id(AUDIO_GENRE_CONTAINER_ID), # all genres '6': lambda : self.get_by_id(AUDIO_ARTIST_CONTAINER_ID), # all artists '7': lambda : self.get_by_id(AUDIO_ALBUM_CONTAINER_ID), # all albums '13': lambda : self.get_by_id(AUDIO_PLAYLIST_CONTAINER_ID), # all playlists }) fields=[u'Audio:Title',u'Audio:Artist', u'Audio:Album',u'Audio:Genre', u'Audio:Duration',u'Audio:AlbumTrackCount', u'Audio:TrackNo',u'Audio:Codec', u'File:Size', u'File:Mime'] d = defer.Deferred() d.addCallback(parse_tracks_query_result) d.addErrback(handle_error) self.search_interface.Query(self.query_id,'Music',fields,'','',tracks_query,False,0,-1, reply_handler=lambda x: d.callback(x),error_handler=lambda x: d.errback(x)) return d def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:*', 'internal:%s:audio/mpeg:*' % self.server.coherence.hostname, 'http-get:*:application/ogg:*', 'internal:%s:application/ogg:*' % self.server.coherence.hostname, 'http-get:*:audio/ogg:*', 'internal:%s:audio/ogg:*' % self.server.coherence.hostname, 'http-get:*:video/ogg:*', 'internal:%s:video/ogg:*' % self.server.coherence.hostname, 'http-get:*:video/mpeg:*', 'internal:%s:video/mpeg:*' % self.server.coherence.hostname, 'http-get:*:video/x-msvideo:*', 'internal:%s:video/x-msvideo:*' % self.server.coherence.hostname, 'http-get:*:video/avi:*', 'internal:%s:video/avi:*' % self.server.coherence.hostname, 'http-get:*:video/mp4:*', 'internal:%s:video/mp4:*' % self.server.coherence.hostname, 'http-get:*:video/quicktime:*', 'internal:%s:video/quicktime:*' % self.server.coherence.hostname, 'http-get:*:image/jpg:*', 'internal:%s:image/jpg:*' % self.server.coherence.hostname, 'http-get:*:image/png:*', 'internal:%s:image/png:*' % self.server.coherence.hostname, 'http-get:*:image/gif:*', 'internal:%s:image/gif:*' % self.server.coherence.hostname,]) Coherence-0.6.6.2/coherence/backends/ted_storage.py0000644000175000017500000000721211317660740020262 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Benjamin Kampmann """ Another simple rss based Media Server, this time for TED.com content """ # I can reuse stuff. cool. But that also means we might want to refactor it into # a base class to reuse from coherence.backends.lolcats_storage import LolcatsStore from coherence.backends.appletrailers_storage import Container from coherence.backend import BackendItem from coherence.upnp.core import DIDLLite class TedTalk(BackendItem): def __init__(self, parent_id, id, title=None, url=None, duration=None, size=None): self.parentid = parent_id self.update_id = 0 self.id = id self.location = url self.name = title self.item = DIDLLite.VideoItem(id, parent_id, self.name) res = DIDLLite.Resource(self.location, 'http-get:*:video/mp4:*') # FIXME should be video/x-m4a res.size = size res.duration = duration self.item.res.append(res) class TEDStore(LolcatsStore): implements = ['MediaServer'] rss_url = "http://feeds.feedburner.com/tedtalks_video?format=xml" ROOT_ID = 0 def __init__(self, server, *args, **kwargs): BackendStore.__init__(self,server,**kwargs) self.name = kwargs.get('name', 'TEDtalks') self.refresh = int(kwargs.get('refresh', 1)) * (60 *60) self.next_id = 1001 self.last_updated = None self.container = Container(None, self.ROOT_ID, self.name) self.videos = {} dfr = self.update_data() dfr.addCallback(self.init_completed) def get_by_id(self, id): if int(id) == self.ROOT_ID: return self.container return self.videos.get(int(id), None) def upnp_init(self): if self.server: self.server.connection_manager_server.set_variable( \ 0, 'SourceProtocolInfo', ['http-get:*:video/mp4:*']) def parse_data(self, xml_data): root = xml_data.getroot() pub_date = root.find('./channel/lastBuildDate').text if pub_date == self.last_updated: return self.last_updated = pub_date self.container.children = [] self.videos = {} # FIXME: move these to generic constants somewhere mrss = './{http://search.yahoo.com/mrss/}' itunes = './{http://www.itunes.com/dtds/podcast-1.0.dtd}' url_item = mrss + 'content' duration = itunes + 'duration' summary = itunes + 'summary' for item in root.findall('./channel/item'): data = {} data['parent_id'] = self.ROOT_ID data['id'] = self.next_id data['title'] = item.find('./title').text.replace('TEDTalks : ', '') # data ['summary'] = item.find(summary).text # data ['duration'] = item.find(duration).text try: media_entry = item.find(url_item) data['url'] = media_entry.get('url', None) data['size'] = media_entry.get('size', None) except IndexError: continue video = TedTalk(**data) self.container.children.append(video) self.videos[self.next_id] = video self.next_id += 1 self.container.update_id += 1 self.update_id += 1 if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) value = (self.ROOT_ID,self.container.update_id) self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) Coherence-0.6.6.2/coherence/tube_service.py0000644000175000017500000003320111317660741016665 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz """ TUBE service classes """ import urllib, urlparse import dbus from twisted.web import resource from twisted.internet import defer from twisted.python.util import OrderedDict from coherence.upnp.core.utils import parse_xml from coherence.upnp.devices.basics import RootDeviceXML, DeviceHttpRoot from coherence.upnp.core import service from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core import action from coherence.upnp.core import variable from coherence.upnp.core import DIDLLite from coherence.upnp.core.utils import ReverseProxyUriResource from coherence import log class MirabeauProxy(resource.Resource, log.Loggable): logCategory = 'mirabeau' def __init__(self): resource.Resource.__init__(self) log.Loggable.__init__(self) self.isLeaf = 0 def getChildWithDefault(self, path, request): self.info('MiraBeau getChildWithDefault %s, %s, %s %s' % (request.method, path, request.uri, request.client)) uri = urllib.unquote_plus(path) self.info('MiraBeau uri %r' % uri) return ReverseProxyUriResource(uri) class TubeServiceControl(UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() def get_action_results(self, result, action, instance): """ check for out arguments if yes: check if there are related ones to StateVariables with non A_ARG_TYPE_ prefix if yes: check if there is a call plugin method for this action if yes: update StateVariable values with call result if no: get StateVariable values and add them to result dict """ self.debug('get_action_results', result) #print 'get_action_results', action, instance notify = [] for argument in action.get_out_arguments(): #print 'get_state_variable_contents', argument.name if argument.name[0:11] != 'A_ARG_TYPE_': variable = self.variables[instance][argument.get_state_variable()] variable.update(result[argument.name].decode('utf-8').encode('utf-8')) #print 'update state variable contents', variable.name, variable.value, variable.send_events if(variable.send_events == 'yes' and variable.moderated == False): notify.append(variable) self.service.propagate_notification(notify) self.info( 'action_results unsorted', action.name, result) if len(result) == 0: return result ordered_result = OrderedDict() for argument in action.get_out_arguments(): if action.name == 'XXXBrowse' and argument.name == 'Result': didl = DIDLLite.DIDLElement.fromString(result['Result'].decode('utf-8')) changed = False for item in didl.getItems(): new_res = DIDLLite.Resources() for res in item.res: remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') if remote_protocol == 'http-get' and remote_network == '*': quoted_url = urllib.quote_plus(res.data) #print "modifying", res.data res.data = urlparse.urlunsplit(('http', self.service.device.external_address,'mirabeau',quoted_url,"")) #print "--->", res.data new_res.append(res) changed = True item.res = new_res if changed == True: didl.rebuild() ordered_result[argument.name] = didl.toString().replace(' 0: in_arguments.remove(l[0]) else: self.critical('argument %s not valid for action %s' % (arg_name,action.name)) return failure.Failure(errorCode(402)) if len(in_arguments) > 0: self.critical('argument %s missing for action %s' % ([ a.get_name() for a in in_arguments],action.name)) return failure.Failure(errorCode(402)) def got_error(x): self.info('dbus error during call processing') return x # call plugin method for this action #print 'callit args', args #print 'callit kwargs', kwargs #print 'callit action', action #print 'callit dbus action', self.service.service.action d = defer.Deferred() self.service.service.call_action( action.name, dbus.Dictionary(kwargs,signature='ss'), reply_handler = d.callback, error_handler = d.errback,utf8_strings=True) d.addCallback( self.get_action_results, action, instance) d.addErrback(got_error) return d class TubeServiceProxy(service.ServiceServer, resource.Resource, log.Loggable): logCategory = 'dbus' def __init__(self, tube_service, device, backend=None): self.device = device self.service = tube_service resource.Resource.__init__(self) id = self.service.get_id().split(':')[3] service.ServiceServer.__init__(self, id, self.device.version, None) self.control = TubeServiceControl(self) self.putChild(self.scpd_url, service.scpdXML(self, self.control)) self.putChild(self.control_url, self.control) self.device.web_resource.putChild(id, self) def init_var_and_actions(self): """ retrieve all actions and create the Action classes for our (proxy) server retrieve all variables and create the StateVariable classes for our (proxy) server """ xml = self.service.get_scpd_xml() tree = parse_xml(xml, 'utf-8').getroot() ns = "urn:schemas-upnp-org:service-1-0" for action_node in tree.findall('.//{%s}action' % ns): name = action_node.findtext('{%s}name' % ns) arguments = [] for argument in action_node.findall('.//{%s}argument' % ns): arg_name = argument.findtext('{%s}name' % ns) arg_direction = argument.findtext('{%s}direction' % ns) arg_state_var = argument.findtext('{%s}relatedStateVariable' % ns) arguments.append(action.Argument(arg_name, arg_direction, arg_state_var)) self._actions[name] = action.Action(self, name, 'n/a', arguments) for var_node in tree.findall('.//{%s}stateVariable' % ns): send_events = var_node.attrib.get('sendEvents','yes') name = var_node.findtext('{%s}name' % ns) data_type = var_node.findtext('{%s}dataType' % ns) values = [] """ we need to ignore this, as there we don't get there our {urn:schemas-beebits-net:service-1-0}X_withVendorDefines attibute there """ for allowed in var_node.findall('.//{%s}allowedValue' % ns): values.append(allowed.text) instance = 0 self._variables.get(instance)[name] = variable.StateVariable(self, name, 'n/a', instance, send_events, data_type, values) """ we need to do this here, as there we don't get there our {urn:schemas-beebits-net:service-1-0}X_withVendorDefines attibute there """ self._variables.get(instance)[name].has_vendor_values = True class TubeDeviceProxy(log.Loggable): logCategory = 'dbus' def __init__(self, coherence, tube_device,external_address): log.Loggable.__init__(self) self.device = tube_device self.coherence = coherence self.external_address = external_address self.uuid = self.device.get_id().split('-') self.uuid[1] = 'tube' self.uuid = '-'.join(self.uuid) self.friendly_name = self.device.get_friendly_name() self.device_type = self.device.get_friendly_device_type() self.version = int(self.device.get_device_type_version()) self._services = [] self._devices = [] self.icons = [] self.info("uuid: %s, name: %r, device type: %r, version: %r", self.uuid, self.friendly_name, self.device_type, self.version) """ create the http entrypoint """ self.web_resource = DeviceHttpRoot(self) self.coherence.add_web_resource( str(self.uuid)[5:], self.web_resource) """ create the Service proxy(s) """ for service in self.device.services: self.debug("Proxying service %r", service) new_service = TubeServiceProxy(service, self) self._services.append(new_service) """ create a device description xml file(s) """ version = self.version while version > 0: self.web_resource.putChild( 'description-%d.xml' % version, RootDeviceXML( self.coherence.hostname, str(self.uuid), self.coherence.urlbase, device_type=self.device_type, version=version, friendly_name=self.friendly_name, model_description='Coherence UPnP %s' % self.device_type, model_name='Coherence UPnP %s' % self.device_type, services=self._services, devices=self._devices, icons=self.icons)) version -= 1 """ and register with SSDP server """ self.register() def register(self): s = self.coherence.ssdp_server uuid = str(self.uuid) host = self.coherence.hostname self.msg('%s register' % self.device_type) # we need to do this after the children are there, since we send notifies s.register('local', '%s::upnp:rootdevice' % uuid, 'upnp:rootdevice', self.coherence.urlbase + uuid[5:] + '/' + 'description-%d.xml' % self.version, host=host) s.register('local', uuid, uuid, self.coherence.urlbase + uuid[5:] + '/' + 'description-%d.xml' % self.version, host=host) version = self.version while version > 0: if version == self.version: silent = False else: silent = True s.register('local', '%s::urn:schemas-upnp-org:device:%s:%d' % (uuid, self.device_type, version), 'urn:schemas-upnp-org:device:%s:%d' % (self.device_type, version), self.coherence.urlbase + uuid[5:] + '/' + 'description-%d.xml' % version, silent=silent, host=host) version -= 1 for service in self._services: device_version = self.version service_version = self.version if hasattr(service,'version'): service_version = service.version silent = False while service_version > 0: try: namespace = service.namespace except: namespace = 'schemas-upnp-org' device_description_tmpl = 'description-%d.xml' % device_version if hasattr(service,'device_description_tmpl'): device_description_tmpl = service.device_description_tmpl s.register('local', '%s::urn:%s:service:%s:%d' % (uuid,namespace,service.id, service_version), 'urn:%s:service:%s:%d' % (namespace,service.id, service_version), self.coherence.urlbase + uuid[5:] + '/' + device_description_tmpl, silent=silent, host=host) silent = True service_version -= 1 device_version -= 1 Coherence-0.6.6.2/coherence/upnp/0000755000000000000000000000000011317673117015200 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/services/0000755000000000000000000000000011317673117017023 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/services/clients/0000755000000000000000000000000011317673117020464 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/services/clients/content_directory_client.py0000644000175000017500000002121511317660734025554 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz import sys, threading from twisted.internet import reactor, defer from twisted.python import log from coherence.upnp.core import DIDLLite from coherence.upnp.core import utils global work, pending work = [] pending = {} class ContentDirectoryClient: def __init__(self, service): self.service = service self.namespace = service.get_type() self.url = service.get_control_url() self.service.subscribe() self.service.client = self #print "ContentDirectoryClient __init__", self.url #def __del__(self): # print "ContentDirectoryClient deleted" # pass def remove(self): self.service.remove() self.service = None self.namespace = None self.url = None del self def subscribe_for_variable(self, var_name, callback,signal=False): self.service.subscribe_for_variable(var_name, instance=0, callback=callback,signal=signal) def get_search_capabilities(self): action = self.service.get_action('GetSearchCapabilities') return action.call() def get_sort_extension_capabilities(self): action = self.service.get_action('GetSortExtensionCapabilities') return action.call() def get_feature_list(self): action = self.service.get_action('GetFeatureList') return action.call() def get_system_update_id(self): action = self.service.get_action('GetSystemUpdateID') return action.call() def browse(self, object_id=0, browse_flag='BrowseDirectChildren', filter='*', sort_criteria='', starting_index=0, requested_count=0, process_result=True, backward_compatibility=False): def got_result(results): items = [] if results is not None: elt = DIDLLite.DIDLElement.fromString(results['Result']) items = elt.getItems() return items def got_process_result(result): #print result r = {} r['number_returned'] = result['NumberReturned'] r['total_matches'] = result['TotalMatches'] r['update_id'] = result['UpdateID'] r['items'] = {} elt = DIDLLite.DIDLElement.fromString(result['Result']) for item in elt.getItems(): #print "process_result", item i = {} i['upnp_class'] = item.upnp_class i['id'] = item.id i['title'] = item.title i['parent_id'] = item.parentID if hasattr(item,'childCount'): i['child_count'] = str(item.childCount) if hasattr(item,'date') and item.date: i['date'] = item.date if hasattr(item,'album') and item.album: i['album'] = item.album if hasattr(item,'artist') and item.artist: i['artist'] = item.artist if hasattr(item,'albumArtURI') and item.albumArtURI: i['album_art_uri'] = item.albumArtURI if hasattr(item,'res'): resources = {} for res in item.res: url = res.data resources[url] = res.protocolInfo if len(resources): i['resources']= resources r['items'][item.id] = i return r action = self.service.get_action('Browse') d = action.call( ObjectID=object_id, BrowseFlag=browse_flag, Filter=filter,SortCriteria=sort_criteria, StartingIndex=str(starting_index), RequestedCount=str(requested_count)) if process_result in [True,1,'1','true','True','yes','Yes']: d.addCallback(got_process_result) #else: # d.addCallback(got_result) return d def search(self, container_id, criteria, starting_index=0, requested_count=0): #print "search:", criteria starting_index = str(starting_index) requested_count = str(requested_count) action = self.service.get_action('Search') if action == None: return None d = action.call( ContainerID=container_id, SearchCriteria=criteria, Filter="*", StartingIndex=starting_index, RequestedCount=requested_count, SortCriteria="") d.addErrback(self._failure) def gotResults(results): items = [] if results is not None: elt = DIDLLite.DIDLElement.fromString(results['Result']) items = elt.getItems() return items d.addCallback(gotResults) return d def dict2item(self, elements): upnp_class = DIDLLite.upnp_classes.get(elements.get('upnp_class',None),None) if upnp_class is None: return None del elements['upnp_class'] item = upnp_class(id='', parentID=elements.get('parentID',None), title=elements.get('title',None), restricted=elements.get('restricted',None)) for k, v in elements.items(): attribute = getattr(item, k, None) if attribute is None: continue attribute = v return item def create_object(self, container_id, elements): if isinstance(elements, dict): elements = self.dict2item(elements) if isinstance(elements,DIDLLite.Object): didl = DIDLLite.DIDLElement() didl.addItem(elements) elements=didl.toString() if elements is None: elements = '' action = self.service.get_action('CreateObject') if action: # optional return action.call( ContainerID=container_id, Elements=elements) return None def destroy_object(self, object_id): action = self.service.get_action('DestroyObject') if action: # optional return action.call( ObjectID=object_id) return None def update_object(self, object_id, current_tag_value, new_tag_value): action = self.service.get_action('UpdateObject') if action: # optional return action.call( ObjectID=object_id, CurrentTagValue=current_tag_value, NewTagValue=new_tag_value) return None def move_object(self, object_id, new_parent_id): action = self.service.get_action('MoveObject') if action: # optional return action.call( ObjectID=object_id, NewParentID=new_parent_id) return None def import_resource(self, source_uri, destination_uri): action = self.service.get_action('ImportResource') if action: # optional return action.call( SourceURI=source_uri, DestinationURI=destination_uri) return None def export_resource(self, source_uri, destination_uri): action = self.service.get_action('ExportResource') if action: # optional return action.call( SourceURI=source_uri, DestinationURI=destination_uri) return None def delete_resource(self, resource_uri): action = self.service.get_action('DeleteResource') if action: # optional return action.call( ResourceURI=resource_uri) return None def stop_transfer_resource(self, transfer_id): action = self.service.get_action('StopTransferResource') if action: # optional return action.call( TransferID=transfer_id) return None def get_transfer_progress(self, transfer_id): action = self.service.get_action('GetTransferProgress') if action: # optional return action.call( TransferID=transfer_id) return None def create_reference(self, container_id, object_id): action = self.service.get_action('CreateReference') if action: # optional return action.call( ContainerID=container_id, ObjectID=object_id) return None def _failure(self, error): log.msg(error.getTraceback(), debug=True) error.trap(Exception) Coherence-0.6.6.2/coherence/upnp/services/clients/test/0000755000000000000000000000000011317673117021443 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/services/clients/test/__init__.py0000644000175000017500000000000011317660734023163 0ustar devdevCoherence-0.6.6.2/coherence/upnp/services/clients/test/test_switch_power_client.py0000644000175000017500000000341411317660734026552 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ Test cases for L{upnp.services.clients.switch_power_client} """ import os from twisted.trial import unittest from twisted.internet import reactor from twisted.internet.defer import Deferred from coherence import __version__ from coherence.base import Coherence from coherence.upnp.core.uuid import UUID from coherence.upnp.devices.control_point import DeviceQuery import coherence.extern.louie as louie class TestSwitchPowerClient(unittest.TestCase): def setUp(self): louie.reset() self.coherence = Coherence({'unittest':'yes','logmode':'error','subsystem_log':{'controlpoint':'error'},'controlpoint':'yes'}) self.uuid = UUID() p = self.coherence.add_plugin('SimpleLight', name='test-light-%d'%os.getpid(),uuid=str(self.uuid)) def tearDown(self): def cleaner(r): self.coherence.clear() return r dl = self.coherence.shutdown() dl.addBoth(cleaner) return dl def test_get_state(self): """ tries to find the activated SimpleLight backend and queries its state. The state is expected to be "off" """ d = Deferred() def the_result(r): #print "the_result", r self.assertEqual(str(self.uuid), r.udn) call = r.client.switch_power.get_status() def got_answer(r): self.assertEqual(int(r['ResultStatus']), 0) d.callback(None) call.addCallback(got_answer) self.coherence.ctrl.add_query(DeviceQuery('uuid', str(self.uuid), the_result, timeout=10, oneshot=True)) return d Coherence-0.6.6.2/coherence/upnp/services/clients/connection_manager_client.py0000644000175000017500000000405311317660734025650 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006-2008, Frank Scholz class ConnectionManagerClient: def __init__(self, service): self.service = service self.namespace = service.get_type() self.url = service.get_control_url() self.service.client = self self.service.subscribe() #def __del__(self): # #print "ConnectionManagerClient deleted" # pass def connection_manager_id(self): return "%s/%s" % (self.service.device.get_id(), self.service.get_id()) def remove(self): self.service.remove() self.service = None self.namespace = None self.url = None del self def subscribe_for_variable(self, var_name, callback,signal=False): self.service.subscribe_for_variable(var_name, instance=0, callback=callback,signal=signal) def get_protocol_info(self): action = self.service.get_action('GetProtocolInfo') return action.call() def prepare_for_connection(self, remote_protocol_info, peer_connection_manager, peer_connection_id, direction): action = self.service.get_action('PrepareForConnection') if action: # optional return action.call( RemoteProtocolInfo=remote_protocol_info, PeerConnectionManager=peer_connection_manager, PeerConnectionID=peer_connection_id, Direction=direction) return None def connection_complete(self, connection_id): action = self.service.get_action('ConnectionComplete') if action: # optional return action.call(ConnectionID=connection_id) return None def get_current_connection_ids(self): action = self.service.get_action('GetCurrentConnectionIDs') return action.call() def get_current_connection_info(self, connection_id): action = self.service.get_action('GetCurrentConnectionInfo') return action.call(ConnectionID=connection_id)Coherence-0.6.6.2/coherence/upnp/services/clients/__init__.py0000644000175000017500000000000011317660734022204 0ustar devdevCoherence-0.6.6.2/coherence/upnp/services/clients/dimming_client.py0000644000175000017500000000220711317660734023442 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz class DimmingClient: def __init__(self, service): self.service = service self.namespace = service.get_type() self.url = service.get_control_url() self.service.subscribe() self.service.client = self def remove(self): self.service.remove() self.service = None self.namespace = None self.url = None del self def subscribe_for_variable(self, var_name, callback,signal=False): self.service.subscribe_for_variable(var_name, instance=0, callback=callback,signal=signal) def set_load_level_target(self,new_load_level_target=0): action = self.service.get_action('SetLoadLevelTarget') return action.call(NewLoadLevelTarget=new_load_level_target) def get_load_level_target(self): action = self.service.get_action('GetLoadLevelTarget') return action.call() def get_load_level_status(self): action = self.service.get_action('GetLoadLevelStatus') return action.call() Coherence-0.6.6.2/coherence/upnp/services/clients/av_transport_client.py0000644000175000017500000000756111317660734024550 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006-2008, Frank Scholz from coherence import log class AVTransportClient(log.Loggable): logCategory = 'avtransportclient' def __init__(self, service): self.service = service self.namespace = service.get_type() self.url = service.get_control_url() self.service.subscribe() self.service.client = self #def __del__(self): # #print "AVTransportClient deleted" # pass def remove(self): self.service.remove() self.service = None self.namespace = None self.url = None del self def subscribe_for_variable(self, var_name, callback,signal=False): self.service.subscribe_for_variable(var_name, instance=0, callback=callback,signal=signal) def set_av_transport_uri(self, instance_id=0, current_uri='', current_uri_metadata=''): action = self.service.get_action('SetAVTransportURI') return action.call( InstanceID=instance_id, CurrentURI=current_uri, CurrentURIMetaData=current_uri_metadata) def set_next_av_transport_uri(self, instance_id=0, next_uri='', next_uri_metadata=''): action = self.service.get_action('SetNextAVTransportURI') if action: # optional return action.call( InstanceID=instance_id, NextURI=next_uri, NextURIMetaData=next_uri_metadata) return None def get_media_info(self, instance_id=0): action = self.service.get_action('GetMediaInfo') return action.call( InstanceID=instance_id) def get_media_info_ext(self, instance_id=0): action = self.service.get_action('GetMediaInfo_Ext') return action.call( InstanceID=instance_id) def get_transport_info(self, instance_id=0): action = self.service.get_action('GetTransportInfo') return action.call( InstanceID=instance_id) def get_position_info(self, instance_id=0): action = self.service.get_action('GetPositionInfo') return action.call( InstanceID=instance_id) def get_device_capabilities(self, instance_id=0): action = self.service.get_action('GetDeviceCapabilities') return action.call( InstanceID=instance_id) def get_transport_settings(self, instance_id=0): action = self.service.get_action('GetTransportSettings') return action.call( InstanceID=instance_id) def pause(self, instance_id=0): action = self.service.get_action('Pause') if action: # optional return action.call( InstanceID=instance_id) return None def play(self, instance_id=0, speed=1): action = self.service.get_action('Play') return action.call( InstanceID=instance_id,Speed=speed) def stop(self, instance_id=0): action = self.service.get_action('Stop') return action.call( InstanceID=instance_id) def record(self, instance_id=0): action = self.service.get_action('Record') if action: # optional return action.call( InstanceID=instance_id) return None def seek(self, instance_id=0, unit='', target=0): action = self.service.get_action('Seek') return action.call( InstanceID=instance_id, Unit=unit, Target=target) def next(self, instance_id=0): action = self.service.get_action('Next') return action.call( InstanceID=instance_id) def previous(self, instance_id=0): action = self.service.get_action('Previous') return action.call( InstanceID=instance_id) def get_current_transport_actions(self, instance_id=0): action = self.service.get_action('GetCurrentTransportActions') return action.call( InstanceID=instance_id) Coherence-0.6.6.2/coherence/upnp/services/clients/switch_power_client.py0000644000175000017500000000214511317660734024534 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz class SwitchPowerClient: def __init__(self, service): self.service = service self.namespace = service.get_type() self.url = service.get_control_url() self.service.subscribe() self.service.client = self def remove(self): if self.service != None: self.service.remove() self.service = None self.namespace = None self.url = None del self def subscribe_for_variable(self, var_name, callback,signal=False): self.service.subscribe_for_variable(var_name, instance=0, callback=callback,signal=signal) def set_target(self,new_target_value=0): action = self.service.get_action('SetTarget') return action.call(NewTargetValue=new_target_value) def get_target(self): action = self.service.get_action('GetTarget') return action.call() def get_status(self): action = self.service.get_action('GetStatus') return action.call()Coherence-0.6.6.2/coherence/upnp/services/clients/rendering_control_client.py0000644000175000017500000000661011317660734025535 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz class RenderingControlClient: def __init__(self, service): self.service = service self.namespace = service.get_type() self.url = service.get_control_url() self.service.subscribe() self.service.client = self #print "RenderingControlClient __init__", self.url #def __del__(self): # #print "RenderingControlClient deleted" # pass def remove(self): self.service.remove() self.service = None self.namespace = None self.url = None del self def subscribe_for_variable(self, var_name, callback,signal=False): self.service.subscribe_for_variable(var_name, instance=0, callback=callback,signal=signal) def list_presets(self, instance_id=0): action = self.service.get_action('ListPresets') return action.call(InstanceID=instance_id) def select_presets(self, instance_id=0, preset_name=''): action = self.service.get_action('SelectPresets') return action.call( InstanceID=instance_id, PresetName=preset_name) def get_mute(self, instance_id=0, channel='Master'): action = self.service.get_action('GetMute') return action.call( InstanceID=instance_id, Channel=channel) def set_mute(self, instance_id=0, channel='Master', desired_mute=0): action = self.service.get_action('SetMute') return action.call( InstanceID=instance_id, Channel=channel, DesiredMute=desired_mute) def get_volume(self, instance_id=0, channel='Master'): action = self.service.get_action('GetVolume') return action.call( InstanceID=instance_id, Channel=channel) def set_volume(self, instance_id=0, channel='Master', desired_volume=0): action = self.service.get_action('SetVolume') return action.call( InstanceID=instance_id, Channel=channel, DesiredVolume=desired_volume) def get_volume_db(self, instance_id=0, channel='Master'): action = self.service.get_action('GetVolumeDB') return action.call( InstanceID=instance_id, Channel=channel) def set_volume_db(self, instance_id=0, channel='Master', desired_volume=0): action = self.service.get_action('SetVolumeDB') return action.call( InstanceID=instance_id, Channel=channel, DesiredVolume=desired_volume) def get_volume_db_range(self, instance_id=0, channel='Master'): action = self.service.get_action('GetVolumeDBRange') return action.call( InstanceID=instance_id, Channel=channel) def get_loudness(self, instance_id=0, channel='Master'): action = self.service.get_action('GetLoudness') return action.call( InstanceID=instance_id, Channel=channel) def set_loudness(self, instance_id=0, channel='Master', desired_loudness=0): action = self.service.get_action('SetLoudness') return action.call( InstanceID=instance_id, Channel=channel, DesiredLoudness=desired_loudness)Coherence-0.6.6.2/coherence/upnp/services/__init__.py0000644000175000017500000000000011317660734020543 0ustar devdevCoherence-0.6.6.2/coherence/upnp/services/servers/0000755000000000000000000000000011317673117020514 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/services/servers/content_directory_server.py0000644000175000017500000002636011317660734025642 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2005, Tim Potter # Copyright 2006 John-Mark Gurney # Copyright 2006, Frank Scholz # Content Directory service from twisted.python import failure from twisted.web import resource from twisted.internet import defer from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core.DIDLLite import DIDLElement from coherence.upnp.core import service from coherence import log class ContentDirectoryControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class ContentDirectoryServer(service.ServiceServer, resource.Resource, log.Loggable): logCategory = 'content_directory_server' def __init__(self, device, backend=None,transcoding=False): self.device = device self.transcoding=transcoding if backend == None: backend = self.device.backend resource.Resource.__init__(self) service.ServiceServer.__init__(self, 'ContentDirectory', self.device.version, backend) self.control = ContentDirectoryControl(self) self.putChild('scpd.xml', service.scpdXML(self, self.control)) self.putChild('control', self.control) self.set_variable(0, 'SystemUpdateID', 0) self.set_variable(0, 'ContainerUpdateIDs', '') def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the ContentDirectory

      %s

    '% self.listchilds(request.uri) def upnp_Search(self, *args, **kwargs): ContainerID = kwargs['ContainerID'] Filter = kwargs['Filter'] StartingIndex = int(kwargs['StartingIndex']) RequestedCount = int(kwargs['RequestedCount']) SortCriteria = kwargs['SortCriteria'] SearchCriteria = kwargs['SearchCriteria'] total = 0 root_id = 0 item = None items = [] parent_container = str(ContainerID) didl = DIDLElement(upnp_client=kwargs.get('X_UPnPClient', ''), parent_container=parent_container, transcoding=self.transcoding) def build_response(tm): r = {'Result': didl.toString(), 'TotalMatches': tm, 'NumberReturned': didl.numItems()} if hasattr(item, 'update_id'): r['UpdateID'] = item.update_id elif hasattr(self.backend, 'update_id'): r['UpdateID'] = self.backend.update_id # FIXME else: r['UpdateID'] = 0 return r def got_error(r): return r def process_result(result,total=None,found_item=None): if result == None: result = [] l = [] def process_items(result, tm): if result == None: result = [] for i in result: if i[0] == True: didl.addItem(i[1]) return build_response(tm) for i in result: d = defer.maybeDeferred( i.get_item) l.append(d) if found_item != None: def got_child_count(count): dl = defer.DeferredList(l) dl.addCallback(process_items, count) return dl d = defer.maybeDeferred(found_item.get_child_count) d.addCallback(got_child_count) return d elif total == None: total = item.get_child_count() dl = defer.DeferredList(l) dl.addCallback(process_items, total) return dl def proceed(result): if(kwargs.get('X_UPnPClient', '') == 'XBox' and hasattr(result, 'get_artist_all_tracks')): d = defer.maybeDeferred( result.get_artist_all_tracks, StartingIndex, StartingIndex + RequestedCount) else: d = defer.maybeDeferred( result.get_children, StartingIndex, StartingIndex + RequestedCount) d.addCallback(process_result,found_item=result) d.addErrback(got_error) return d try: root_id = ContainerID except: pass wmc_mapping = getattr(self.backend, "wmc_mapping", None) if kwargs.get('X_UPnPClient', '') == 'XBox': if(wmc_mapping != None and wmc_mapping.has_key(ContainerID)): """ fake a Windows Media Connect Server """ root_id = wmc_mapping[ContainerID] if callable(root_id): item = root_id() if item is not None: if isinstance(item, list): total = len(item) if int(RequestedCount) == 0: items = item[StartingIndex:] else: items = item[StartingIndex:StartingIndex+RequestedCount] return process_result(items,total=total) else: if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item) item = self.backend.get_by_id(root_id) if item == None: return process_result([],total=0) if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item) item = self.backend.get_by_id(root_id) if item == None: return failure.Failure(errorCode(701)) if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item) def upnp_Browse(self, *args, **kwargs): try: ObjectID = kwargs['ObjectID'] except: self.debug("hmm, a Browse action and no ObjectID argument? An XBox maybe?") try: ObjectID = kwargs['ContainerID'] except: ObjectID = 0 BrowseFlag = kwargs['BrowseFlag'] Filter = kwargs['Filter'] StartingIndex = int(kwargs['StartingIndex']) RequestedCount = int(kwargs['RequestedCount']) SortCriteria = kwargs['SortCriteria'] parent_container = None requested_id = None item = None total = 0 items = [] if BrowseFlag == 'BrowseDirectChildren': parent_container = str(ObjectID) else: requested_id = str(ObjectID) self.info("upnp_Browse request %r %r %r %r", ObjectID, BrowseFlag, StartingIndex, RequestedCount) didl = DIDLElement(upnp_client=kwargs.get('X_UPnPClient', ''), requested_id=requested_id, parent_container=parent_container, transcoding=self.transcoding) def got_error(r): return r def process_result(result,total=None,found_item=None): if result == None: result = [] if BrowseFlag == 'BrowseDirectChildren': l = [] def process_items(result, tm): if result == None: result = [] for i in result: if i[0] == True: didl.addItem(i[1]) return build_response(tm) for i in result: d = defer.maybeDeferred( i.get_item) l.append(d) if found_item != None: def got_child_count(count): dl = defer.DeferredList(l) dl.addCallback(process_items, count) return dl d = defer.maybeDeferred(found_item.get_child_count) d.addCallback(got_child_count) return d elif total == None: total = item.get_child_count() dl = defer.DeferredList(l) dl.addCallback(process_items, total) return dl else: didl.addItem(result) total = 1 return build_response(total) def build_response(tm): r = {'Result': didl.toString(), 'TotalMatches': tm, 'NumberReturned': didl.numItems()} if hasattr(item, 'update_id'): r['UpdateID'] = item.update_id elif hasattr(self.backend, 'update_id'): r['UpdateID'] = self.backend.update_id # FIXME else: r['UpdateID'] = 0 return r def proceed(result): if BrowseFlag == 'BrowseDirectChildren': d = defer.maybeDeferred( result.get_children, StartingIndex, StartingIndex + RequestedCount) else: d = defer.maybeDeferred( result.get_item) d.addCallback(process_result,found_item=result) d.addErrback(got_error) return d root_id = ObjectID wmc_mapping = getattr(self.backend, "wmc_mapping", None) if(kwargs.get('X_UPnPClient', '') == 'XBox' and wmc_mapping != None and wmc_mapping.has_key(ObjectID)): """ fake a Windows Media Connect Server """ root_id = wmc_mapping[ObjectID] if callable(root_id): item = root_id() if item is not None: if isinstance(item, list): total = len(item) if int(RequestedCount) == 0: items = item[StartingIndex:] else: items = item[StartingIndex:StartingIndex+RequestedCount] return process_result(items,total=total) else: if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item) item = self.backend.get_by_id(root_id) if item == None: return process_result([],total=0) if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item) item = self.backend.get_by_id(root_id) if item == None: return failure.Failure(errorCode(701)) if isinstance(item,defer.Deferred): item.addCallback(proceed) return item else: return proceed(item)Coherence-0.6.6.2/coherence/upnp/services/servers/connection_manager_server.py0000644000175000017500000003046111317660734025732 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz # Connection Manager service import time from twisted.web import resource from twisted.python import failure from twisted.internet import task from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import service from coherence.upnp.core.DIDLLite import build_dlna_additional_info from coherence import log class ConnectionManagerControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class ConnectionManagerServer(service.ServiceServer, resource.Resource, log.Loggable): logCategory = 'connection_manager_server' def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) service.ServiceServer.__init__(self, 'ConnectionManager', self.device.version, backend) self.control = ConnectionManagerControl(self) self.putChild(self.scpd_url, service.scpdXML(self, self.control)) self.putChild(self.control_url, self.control) self.next_connection_id = 1 self.next_avt_id = 1 self.next_rcs_id = 1 self.connections = {} self.set_variable(0, 'SourceProtocolInfo', '') self.set_variable(0, 'SinkProtocolInfo', '') self.set_variable(0, 'CurrentConnectionIDs', '') self.does_playcontainer = False try: if 'playcontainer-0-1' in backend.dlna_caps: self.does_playcontainer = True except AttributeError: pass self.remove_lingering_connections_loop = task.LoopingCall(self.remove_lingering_connections) self.remove_lingering_connections_loop.start(180.0, now=False) def release(self): self.remove_lingering_connections_loop.stop() def add_connection(self, RemoteProtocolInfo, Direction, PeerConnectionID, PeerConnectionManager): id = self.next_connection_id self.next_connection_id += 1 avt_id = 0 rcs_id = 0 if self.device.device_type == 'MediaServer': self.connections[id] = {'ProtocolInfo':RemoteProtocolInfo, 'Direction':Direction, 'PeerConnectionID':PeerConnectionID, 'PeerConnectionManager':PeerConnectionManager, 'AVTransportID':avt_id, 'RcsID':rcs_id, 'Status':'OK'} if self.device.device_type == 'MediaRenderer': """ this is the place to instantiate AVTransport and RenderingControl for this connection """ avt_id = self.next_avt_id self.next_avt_id += 1 self.device.av_transport_server.create_new_instance(avt_id) rcs_id = self.next_rcs_id self.next_rcs_id += 1 self.device.rendering_control_server.create_new_instance(rcs_id) self.connections[id] = {'ProtocolInfo':RemoteProtocolInfo, 'Direction':Direction, 'PeerConnectionID':PeerConnectionID, 'PeerConnectionManager':PeerConnectionManager, 'AVTransportID':avt_id, 'RcsID':rcs_id, 'Status':'OK'} self.backend.current_connection_id = id csv_ids = ','.join([str(x) for x in self.connections]) self.set_variable(0, 'CurrentConnectionIDs', csv_ids) return id, avt_id, rcs_id def remove_connection(self,id): if self.device.device_type == 'MediaRenderer': try: self.device.av_transport_server.remove_instance(self.lookup_avt_id(id)) self.device.rendering_control_server.remove_instance(self.lookup_rcs_id(id)) del self.connections[id] except: pass self.backend.current_connection_id = None if self.device.device_type == 'MediaServer': del self.connections[id] csv_ids = ','.join([str(x) for x in self.connections]) self.set_variable(0, 'CurrentConnectionIDs', csv_ids) def remove_lingering_connections(self): """ check if we have a connection that hasn't a StateVariable change within the last 300 seconds, if so remove it """ if self.device.device_type != 'MediaRenderer': return now = time.time() for id, connection in self.connections.items(): avt_id = connection['AVTransportID'] rcs_id = connection['RcsID'] avt_active = True rcs_active = True #print "remove_lingering_connections", id, avt_id, rcs_id if avt_id > 0: avt_variables = self.device.av_transport_server.get_variables().get(avt_id) if avt_variables: avt_active = False for variable in avt_variables.values(): if variable.last_time_touched+300 >= now: avt_active = True break if rcs_id > 0: rcs_variables = self.device.rendering_control_server.get_variables().get(rcs_id) if rcs_variables: rcs_active = False for variable in rcs_variables.values(): if variable.last_time_touched+300 >= now: rcs_active = True break if( avt_active == False and rcs_active == False): self.remove_connection(id) def lookup_connection(self,id): return self.connections.get(id) def lookup_avt_id(self,id): try: return self.connections[id]['AVTransportID'] except: return 0 def lookup_rcs_id(self,id): try: return self.connections[id]['RcsID'] except: return 0 def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the ConnectionManager

      %s

    '% self.listchilds(request.uri) def set_variable(self, instance, variable_name, value, default=False): if(variable_name == 'SourceProtocolInfo' or variable_name == 'SinkProtocolInfo'): if isinstance(value,basestring) and len(value) > 0: value = [v.strip() for v in value.split(',')] without_dlna_tags = [] for v in value: protocol,network,content_format,additional_info = v.split(':') if additional_info == '*': without_dlna_tags.append(v) def with_some_tag_already_there(protocolinfo): protocol,network,content_format,additional_info = protocolinfo.split(':') for v in value: v_protocol,v_network,v_content_format,v_additional_info = v.split(':') if((protocol,network,content_format) == (v_protocol,v_network,v_content_format) and v_additional_info != '*'): return True return False for w in without_dlna_tags: if with_some_tag_already_there(w) == False: protocol,network,content_format,additional_info = w.split(':') if variable_name == 'SinkProtocolInfo': value.append(':'.join((protocol,network,content_format,build_dlna_additional_info(content_format,does_playcontainer=self.does_playcontainer)))) else: value.append(':'.join((protocol,network,content_format,build_dlna_additional_info(content_format)))) service.ServiceServer.set_variable(self,instance,variable_name,value,default=default) def upnp_PrepareForConnection(self, *args, **kwargs): self.info('upnp_PrepareForConnection') """ check if we really support that mimetype """ RemoteProtocolInfo = kwargs['RemoteProtocolInfo'] """ if we are a MR and this in not 'Input' then there is something strange going on """ Direction = kwargs['Direction'] if( self.device.device_type == 'MediaRenderer' and Direction == 'Output'): return failure.Failure(errorCode(702)) if( self.device.device_type == 'MediaServer' and Direction != 'Input'): return failure.Failure(errorCode(702)) """ the InstanceID of the MS ? """ PeerConnectionID = kwargs['PeerConnectionID'] """ ??? """ PeerConnectionManager = kwargs['PeerConnectionManager'] local_protocol_infos = None if self.device.device_type == 'MediaRenderer': local_protocol_infos = self.get_variable('SinkProtocolInfo').value if self.device.device_type == 'MediaServer': local_protocol_infos = self.get_variable('SourceProtocolInfo').value self.debug('ProtocalInfos:',RemoteProtocolInfo, '--', local_protocol_infos) try: remote_protocol,remote_network,remote_content_format,_ = RemoteProtocolInfo.split(':') except: self.warning("unable to process RemoteProtocolInfo", RemoteProtocolInfo) return failure.Failure(errorCode(701)) for protocol_info in local_protocol_infos.split(','): #print remote_protocol,remote_network,remote_content_format #print protocol_info local_protocol,local_network,local_content_format,_ = protocol_info.split(':') #print local_protocol,local_network,local_content_format if((remote_protocol == local_protocol or remote_protocol == '*' or local_protocol == '*') and (remote_network == local_network or remote_network == '*' or local_network == '*') and (remote_content_format == local_content_format or remote_content_format == '*' or local_content_format == '*')): connection_id, avt_id, rcs_id = \ self.add_connection(RemoteProtocolInfo, Direction, PeerConnectionID, PeerConnectionManager) return {'ConnectionID': connection_id, 'AVTransportID': avt_id, 'RcsID': rcs_id} return failure.Failure(errorCode(701)) def upnp_ConnectionComplete(self, *args, **kwargs): ConnectionID = int(kwargs['ConnectionID']) """ remove this ConnectionID and the associated instances @ AVTransportID and RcsID """ self.remove_connection(ConnectionID) return {} def upnp_GetCurrentConnectionInfo(self, *args, **kwargs): ConnectionID = int(kwargs['ConnectionID']) """ return for this ConnectionID the associated InstanceIDs @ AVTransportID and RcsID ProtocolInfo PeerConnectionManager PeerConnectionID Direction Status or send a 706 if there isn't such a ConnectionID """ connection = self.lookup_connection(ConnectionID) if connection == None: return failure.Failure(errorCode(706)) else: return {'AVTransportID':connection['AVTransportID'], 'RcsID':connection['RcsID'], 'ProtocolInfo':connection['ProtocolInfo'], 'PeerConnectionManager':connection['PeerConnectionManager'], 'PeerConnectionID':connection['PeerConnectionID'], 'Direction':connection['Direction'], 'Status':connection['Status'], } Coherence-0.6.6.2/coherence/upnp/services/servers/test/0000755000000000000000000000000011317673117021473 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/services/servers/test/test_content_directory_server.py0000644000175000017500000002100111317660734027643 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ Test cases for L{upnp.services.servers.content_directory_server} """ import os from twisted.trial import unittest from twisted.internet import reactor from twisted.internet.defer import Deferred from twisted.python.filepath import FilePath from coherence import __version__ from coherence.base import Coherence from coherence.upnp.core.uuid import UUID from coherence.upnp.devices.control_point import DeviceQuery from coherence.upnp.core import DIDLLite import coherence.extern.louie as louie class TestContentDirectoryServer(unittest.TestCase): def setUp(self): self.tmp_content = FilePath('tmp_content_coherence-%d'%os.getpid()) f = self.tmp_content.child('content') audio = f.child('audio') f.child('images').makedirs() f.child('video').makedirs() album = audio.child('album-1') album.makedirs() album.child('track-1.mp3').touch() album.child('track-2.mp3').touch() album = audio.child('album-2') album.makedirs() album.child('track-1.ogg').touch() album.child('track-2.ogg').touch() louie.reset() self.coherence = Coherence({'unittest':'yes','logmode':'debug','subsystem_log':{'controlpoint':'error', 'action':'error', 'soap':'error'},'controlpoint':'yes'}) self.uuid = UUID() p = self.coherence.add_plugin('FSStore', name='MediaServer-%d'%os.getpid(), content=self.tmp_content.path, uuid=str(self.uuid)) def tearDown(self): self.tmp_content.remove() def cleaner(r): self.coherence.clear() return r dl = self.coherence.shutdown() dl.addBoth(cleaner) return dl def test_Browse(self): """ tries to find the activated FSStore backend and browses its root. """ d = Deferred() def the_result(mediaserver): try: self.assertEqual(str(self.uuid), mediaserver.udn) except: d.errback() def got_second_answer(r,childcount): try: self.assertEqual(int(r['TotalMatches']), childcount) d.callback(None) except: d.errback() def got_first_answer(r): try: self.assertEqual(int(r['TotalMatches']), 1) except: d.errback() didl = DIDLLite.DIDLElement.fromString(r['Result']) item = didl.getItems()[0] try: self.assertEqual(item.childCount, 3) except: d.errback() call = mediaserver.client.content_directory.browse(object_id=item.id, process_result=False) call.addCallback(got_second_answer,item.childCount) return call call = mediaserver.client.content_directory.browse(process_result=False) call.addCallback(got_first_answer) self.coherence.ctrl.add_query(DeviceQuery('uuid', str(self.uuid), the_result, timeout=10, oneshot=True)) return d def test_Browse_Metadata(self): """ tries to find the activated FSStore backend and requests metadata for ObjectID 0. """ d = Deferred() def the_result(mediaserver): try: self.assertEqual(str(self.uuid), mediaserver.udn) except: d.errback() def got_first_answer(r): try: self.assertEqual(int(r['TotalMatches']), 1) except: d.errback() return didl = DIDLLite.DIDLElement.fromString(r['Result']) item = didl.getItems()[0] try: self.assertEqual(item.title, 'root') except: d.errback() return d.callback(None) call = mediaserver.client.content_directory.browse(object_id='0',browse_flag='BrowseMetadata',process_result=False) call.addCallback(got_first_answer) call.addErrback(lambda x: d.errback(None)) self.coherence.ctrl.add_query(DeviceQuery('uuid', str(self.uuid), the_result, timeout=10, oneshot=True)) return d def test_XBOX_Browse(self): """ tries to find the activated FSStore backend and browses all audio files. """ d = Deferred() def the_result(mediaserver): try: self.assertEqual(str(self.uuid), mediaserver.udn) except: d.errback() def got_first_answer(r): """ we expect four audio files here """ try: self.assertEqual(int(r['TotalMatches']), 4) except: d.errback() return d.callback(None) def my_browse(*args,**kwargs): kwargs['ContainerID'] = kwargs['ObjectID'] del kwargs['ObjectID'] del kwargs['BrowseFlag'] kwargs['SearchCriteria'] = '' return 'Search',kwargs #mediaserver.client.overlay_actions = {'Browse':my_browse} mediaserver.client.overlay_headers = {'user-agent':'Xbox/Coherence emulation'} call = mediaserver.client.content_directory.browse(object_id='4',process_result=False) call.addCallback(got_first_answer) call.addErrback(lambda x: d.errback(None)) self.coherence.ctrl.add_query(DeviceQuery('uuid', str(self.uuid), the_result, timeout=10, oneshot=True)) return d def test_XBOX_Browse_Metadata(self): """ tries to find the activated FSStore backend and requests metadata for ObjectID 0. """ d = Deferred() def the_result(mediaserver): try: self.assertEqual(str(self.uuid), mediaserver.udn) except: d.errback() def got_first_answer(r): """ we expect one item here """ try: self.assertEqual(int(r['TotalMatches']), 1) except: d.errback() return didl = DIDLLite.DIDLElement.fromString(r['Result']) item = didl.getItems()[0] try: self.assertEqual(item.title, 'root') except: d.errback() return d.callback(None) mediaserver.client.overlay_headers = {'user-agent':'Xbox/Coherence emulation'} call = mediaserver.client.content_directory.browse(object_id='0',browse_flag='BrowseMetadata',process_result=False) call.addCallback(got_first_answer) call.addErrback(lambda x: d.errback(None)) self.coherence.ctrl.add_query(DeviceQuery('uuid', str(self.uuid), the_result, timeout=10, oneshot=True)) return d def test_XBOX_Search(self): """ tries to find the activated FSStore backend and searches for all its audio files. """ d = Deferred() def the_result(mediaserver): try: self.assertEqual(str(self.uuid), mediaserver.udn) except: d.errback() def got_first_answer(r): """ we expect four audio files here """ try: self.assertEqual(len(r), 4) except: d.errback() d.callback(None) mediaserver.client.overlay_headers = {'user-agent':'Xbox/Coherence emulation'} call = mediaserver.client.content_directory.search(container_id='4', criteria='') call.addCallback(got_first_answer) call.addErrback(lambda x: d.errback(None)) self.coherence.ctrl.add_query(DeviceQuery('uuid', str(self.uuid), the_result, timeout=10, oneshot=True)) return d Coherence-0.6.6.2/coherence/upnp/services/servers/test/__init__.py0000644000175000017500000000000011317660734023213 0ustar devdevCoherence-0.6.6.2/coherence/upnp/services/servers/av_transport_server.py0000644000175000017500000000246111317660734024622 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz # AVTransport service from twisted.web import resource from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core import service class AVTransportControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class AVTransportServer(service.ServiceServer, resource.Resource): def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) service.ServiceServer.__init__(self, 'AVTransport', self.device.version, backend) self.control = AVTransportControl(self) self.putChild(self.scpd_url, service.scpdXML(self)) self.putChild(self.control_url, self.control) def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the AVTransport

      %s

    '% self.listchilds(request.uri) Coherence-0.6.6.2/coherence/upnp/services/servers/rendering_control_server.py0000644000175000017500000000255611317660734025622 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz # RenderingControl service from twisted.web import resource from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core import service class RenderingControlControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class RenderingControlServer(service.ServiceServer, resource.Resource): def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) service.ServiceServer.__init__(self, 'RenderingControl', self.device.version, backend) self.control = RenderingControlControl(self) self.putChild(self.scpd_url, service.scpdXML(self, self.control)) self.putChild(self.control_url, self.control) def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the RenderingControl

      %s

    '% self.listchilds(request.uri) Coherence-0.6.6.2/coherence/upnp/services/servers/switch_power_server.py0000644000175000017500000000237211317660734024616 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz # Switch Power service from twisted.web import resource from twisted.python import failure from twisted.internet import task from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core import service from coherence import log class SwitchPowerControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class SwitchPowerServer(service.ServiceServer, resource.Resource, log.Loggable): logCategory = 'switch_power_server' def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) service.ServiceServer.__init__(self, 'SwitchPower', self.device.version, backend) self.control = SwitchPowerControl(self) self.putChild(self.scpd_url, service.scpdXML(self, self.control)) self.putChild(self.control_url, self.control)Coherence-0.6.6.2/coherence/upnp/services/servers/__init__.py0000644000175000017500000000000011317660734022234 0ustar devdevCoherence-0.6.6.2/coherence/upnp/services/servers/scheduled_recording_server.py0000644000175000017500000000263411317660734026076 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009, Frank Scholz # ScheduledRecording service from twisted.web import resource from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core import service class ScheduledRecordingControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class ScheduledRecordingServer(service.ServiceServer, resource.Resource): implementation = 'optional' def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) self.version = 1 service.ServiceServer.__init__(self, 'ScheduledRecording', self.version, backend) self.control = ScheduledRecordingControl(self) self.putChild(self.scpd_url, service.scpdXML(self, self.control)) self.putChild(self.control_url, self.control) def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the ScheduledRecording

      %s

    '% self.listchilds(request.uri) Coherence-0.6.6.2/coherence/upnp/services/servers/media_receiver_registrar_server.py0000644000175000017500000000410411317660734027121 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz # Content Directory service from twisted.web import resource from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core import service class FakeMediaReceiverRegistrarBackend: def upnp_IsAuthorized(self, *args, **kwargs): r = { 'Result': 1} return r def upnp_IsValidated(self, *args, **kwargs): r = { 'Result': 1} return r def upnp_RegisterDevice(self, *args, **kwargs): """ in parameter RegistrationReqMsg """ RegistrationReqMsg = kwargs['RegistrationReqMsg'] """ FIXME: check with WMC and WMP """ r = { 'RegistrationRespMsg': 'WTF should be in here?'} return r class MediaReceiverRegistrarControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class MediaReceiverRegistrarServer(service.ServiceServer, resource.Resource): implementation = 'optional' def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) self.version = 1 self.namespace = 'microsoft.com' self.id_namespace = 'microsoft.com' service.ServiceServer.__init__(self, 'X_MS_MediaReceiverRegistrar', self.version, backend) self.device_description_tmpl = 'xbox-description-1.xml' self.control = MediaReceiverRegistrarControl(self) self.putChild('scpd.xml', service.scpdXML(self, self.control)) self.putChild('control', self.control) def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the MediaReceiverRegistrar

      %s

    '% self.listchilds(request.uri) Coherence-0.6.6.2/coherence/upnp/services/servers/dimming_server.py0000644000175000017500000000214411317660734023522 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz # Dimming service from twisted.web import resource from coherence.upnp.core.soap_service import UPnPPublisher from coherence.upnp.core import service from coherence import log class DimmingControl(service.ServiceControl,UPnPPublisher): def __init__(self, server): self.service = server self.variables = server.get_variables() self.actions = server.get_actions() class DimmingServer(service.ServiceServer, resource.Resource, log.Loggable): logCategory = 'dimming_server' def __init__(self, device, backend=None): self.device = device if backend == None: backend = self.device.backend resource.Resource.__init__(self) service.ServiceServer.__init__(self, 'Dimming', self.device.version, backend) self.control = DimmingControl(self) self.putChild(self.scpd_url, service.scpdXML(self, self.control)) self.putChild(self.control_url, self.control)Coherence-0.6.6.2/coherence/upnp/devices/0000755000000000000000000000000011317673117016622 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/devices/binary_light_client.py0000644000175000017500000000416711317660734022636 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz from coherence.upnp.services.clients.switch_power_client import SwitchPowerClient from coherence import log import coherence.extern.louie as louie class BinaryLightClient(log.Loggable): logCategory = 'binarylight_client' def __init__(self, device): self.device = device self.device_type = self.device.get_friendly_device_type() self.version = int(self.device.get_device_type_version()) self.icons = device.icons self.switch_power = None self.detection_completed = False louie.connect(self.service_notified, signal='Coherence.UPnP.DeviceClient.Service.notified', sender=self.device) for service in self.device.get_services(): if service.get_type() in ["urn:schemas-upnp-org:service:SwitchPower:1"]: self.switch_power = SwitchPowerClient(service) self.info("BinaryLight %s" % (self.device.get_friendly_name())) if self.switch_power: self.info("SwitchPower service available") else: self.warning("SwitchPower service not available, device not implemented properly according to the UPnP specification") def remove(self): self.info("removal of BinaryLightClient started") if self.switch_power != None: self.switch_power.remove() def service_notified(self, service): self.info("Service %r sent notification" % service); if self.detection_completed == True: return if self.switch_power != None: if not hasattr(self.switch_power.service, 'last_time_updated'): return if self.switch_power.service.last_time_updated == None: return self.detection_completed = True louie.send('Coherence.UPnP.DeviceClient.detection_completed', None, client=self,udn=self.device.udn) def state_variable_change( self, variable): self.info(variable.name, 'changed from', variable.old_value, 'to', variable.value) Coherence-0.6.6.2/coherence/upnp/devices/media_server.py0000644000175000017500000007301111317660734021264 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007 Frank Scholz import os import re import traceback from StringIO import StringIO import urllib from twisted.internet import task from twisted.internet import defer from twisted.web import static from twisted.web import resource, server #from twisted.web import proxy from twisted.python import util from twisted.python.filepath import FilePath from coherence.extern.et import ET, indent from coherence import __version__ from coherence.upnp.core.service import ServiceServer from coherence.upnp.core.utils import StaticFile from coherence.upnp.core.utils import ReverseProxyResource from coherence.upnp.services.servers.connection_manager_server import ConnectionManagerServer from coherence.upnp.services.servers.content_directory_server import ContentDirectoryServer from coherence.upnp.services.servers.scheduled_recording_server import ScheduledRecordingServer from coherence.upnp.services.servers.media_receiver_registrar_server import MediaReceiverRegistrarServer from coherence.upnp.services.servers.media_receiver_registrar_server import FakeMediaReceiverRegistrarBackend from coherence.upnp.devices.basics import BasicDeviceMixin from coherence import log COVER_REQUEST_INDICATOR = re.compile(".*?cover\.[A-Z|a-z]{3,4}$") ATTACHMENT_REQUEST_INDICATOR = re.compile(".*?attachment=.*$") TRANSCODED_REQUEST_INDICATOR = re.compile(".*/transcoded/.*$") class MSRoot(resource.Resource, log.Loggable): logCategory = 'mediaserver' def __init__(self, server, store): resource.Resource.__init__(self) log.Loggable.__init__(self) self.server = server self.store = store #def delayed_response(self, resrc, request): # print "delayed_response", resrc, request # body = resrc.render(request) # print "delayed_response", body # if body == 1: # print "delayed_response not yet done" # return # request.setHeader("Content-length", str(len(body))) # request.write(response) # request.finish() def getChildWithDefault(self, path, request): self.info('%s getChildWithDefault, %s, %s, %s %s' % (self.server.device_type, request.method, path, request.uri, request.client)) headers = request.getAllHeaders() self.msg( request.getAllHeaders()) try: if headers['getcontentfeatures.dlna.org'] != '1': request.setResponseCode(400) return static.Data('

    wrong value for getcontentFeatures.dlna.org

    ','text/html') except: pass if request.method == 'HEAD': if 'getcaptioninfo.sec' in headers: self.warning("requesting srt file for id %s" % path) ch = self.store.get_by_id(path) try: location = ch.get_path() caption = ch.caption if caption == None: raise KeyError request.setResponseCode(200) request.setHeader('CaptionInfo.sec', caption) return static.Data('','text/html') except: print traceback.format_exc() request.setResponseCode(404) return static.Data('

    the requested srt file was not found

    ','text/html') try: request._dlna_transfermode = headers['transfermode.dlna.org'] except KeyError: request._dlna_transfermode = 'Streaming' if request.method in ('GET','HEAD'): if COVER_REQUEST_INDICATOR.match(request.uri): self.info("request cover for id %s" % path) def got_item(ch): if ch is not None: request.setResponseCode(200) file = ch.get_cover() if os.path.exists(file): self.info("got cover %s" % file) return StaticFile(file) request.setResponseCode(404) return static.Data('

    cover requested not found

    ','text/html') dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_item) return dfr if ATTACHMENT_REQUEST_INDICATOR.match(request.uri): self.info("request attachment %r for id %s" % (request.args,path)) def got_attachment(ch): try: #FIXME same as below if 'transcoded' in request.args: if self.server.coherence.config.get('transcoding', 'no') == 'yes': format = request.args['transcoded'][0] type = request.args['type'][0] self.info("request transcoding %r %r" % (format, type)) try: from coherence.transcoder import TranscoderManager manager = TranscoderManager(self.server.coherence) return manager.select(format,ch.item.attachments[request.args['attachment'][0]]) except: self.debug(traceback.format_exc()) request.setResponseCode(404) return static.Data('

    the requested transcoded file was not found

    ','text/html') else: request.setResponseCode(404) return static.Data("

    This MediaServer doesn't support transcoding

    ",'text/html') else: return ch.item.attachments[request.args['attachment'][0]] except: request.setResponseCode(404) return static.Data('

    the requested attachment was not found

    ','text/html') dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_attachment) return dfr #if(request.method in ('GET','HEAD') and # XBOX_TRANSCODED_REQUEST_INDICATOR.match(request.uri)): # if self.server.coherence.config.get('transcoding', 'no') == 'yes': # id = path[:-15].split('/')[-1] # self.info("request transcoding to %r for id %s" % (request.args,id)) # ch = self.store.get_by_id(id) # uri = ch.get_path() # return MP3Transcoder(uri) if(request.method in ('GET','HEAD') and TRANSCODED_REQUEST_INDICATOR.match(request.uri)): self.info("request transcoding to %s for id %s" % (request.uri.split('/')[-1],path)) if self.server.coherence.config.get('transcoding', 'no') == 'yes': def got_stuff_to_transcode(ch): #FIXME create a generic transcoder class and sort the details there format = request.uri.split('/')[-1] #request.args['transcoded'][0] uri = ch.get_path() try: from coherence.transcoder import TranscoderManager manager = TranscoderManager(self.server.coherence) return manager.select(format,uri) except: self.debug(traceback.format_exc()) request.setResponseCode(404) return static.Data('

    the requested transcoded file was not found

    ','text/html') dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_stuff_to_transcode) return dfr request.setResponseCode(404) return static.Data("

    This MediaServer doesn't support transcoding

    ",'text/html') if(request.method == 'POST' and request.uri.endswith('?import')): d = self.import_file(path,request) if isinstance(d, defer.Deferred): d.addBoth(self.import_response,path) return d return self.import_response(None,path) if(headers.has_key('user-agent') and (headers['user-agent'].find('Xbox/') == 0 or # XBox headers['user-agent'].startswith("""Mozilla/4.0 (compatible; UPnP/1.0; Windows""")) and # wmp11 path in ['description-1.xml','description-2.xml']): self.info('XBox/WMP alert, we need to simulate a Windows Media Connect server') if self.children.has_key('xbox-description-1.xml'): self.msg( 'returning xbox-description-1.xml') return self.children['xbox-description-1.xml'] if self.children.has_key(path): return self.children[path] if request.uri == '/': return self return self.getChild(path, request) def requestFinished(self, result, id, request): self.info("finished, remove %d from connection table" % id) self.info("finished, sentLength: %d chunked: %d code: %d" % (request.sentLength, request.chunked, request.code)) self.info("finished %r" % request.headers) self.server.connection_manager_server.remove_connection(id) def import_file(self,name,request): self.info("import file, id %s" % name) print "import file, id %s" % name def got_file(ch): print "ch", ch if ch is not None: if hasattr(self.store,'backend_import'): response_code = self.store.backend_import(ch,request.content) if isinstance(response_code, defer.Deferred): return response_code request.setResponseCode(response_code) return else: request.setResponseCode(404) dfr = defer.maybeDeferred(self.store.get_by_id, name) dfr.addCallback(got_file) def prepare_connection(self,request): new_id,_,_ = self.server.connection_manager_server.add_connection('', 'Output', -1, '') self.info("startup, add %d to connection table" % new_id) d = request.notifyFinish() d.addBoth(self.requestFinished, new_id, request) def prepare_headers(self,ch,request): request.setHeader('transferMode.dlna.org', request._dlna_transfermode) if hasattr(ch,'item') and hasattr(ch.item, 'res'): if ch.item.res[0].protocolInfo is not None: additional_info = ch.item.res[0].get_additional_info() if additional_info != '*': request.setHeader('contentFeatures.dlna.org', additional_info) elif 'getcontentfeatures.dlna.org' in request.getAllHeaders(): request.setHeader('contentFeatures.dlna.org', "DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000") def process_child(self,ch,name,request): if ch != None: self.info('Child found', ch) if(request.method == 'GET' or request.method == 'HEAD'): headers = request.getAllHeaders() if headers.has_key('content-length'): self.warning('%s request with content-length %s header - sanitizing' % ( request.method, headers['content-length'])) del request.received_headers['content-length'] self.debug('data', ) if len(request.content.getvalue()) > 0: """ shall we remove that? can we remove that? """ self.warning('%s request with %d bytes of message-body - sanitizing' % ( request.method, len(request.content.getvalue()))) request.content = StringIO() if hasattr(ch, "location"): self.debug("we have a location %s" % isinstance(ch.location, resource.Resource)) if(isinstance(ch.location, ReverseProxyResource) or isinstance(ch.location, resource.Resource)): self.info('getChild proxy %s to %s' % (name, ch.location.uri)) self.prepare_connection(request) self.prepare_headers(ch,request) return ch.location try: p = ch.get_path() except TypeError: return self.list_content(name, ch, request) except Exception, msg: self.debug("error accessing items path %r" % msg) self.debug(traceback.format_exc()) return self.list_content(name, ch, request) if p != None and os.path.exists(p): self.info("accessing path %r" % p) self.prepare_connection(request) self.prepare_headers(ch,request) ch = StaticFile(p) else: self.debug("accessing path %r failed" % p) return self.list_content(name, ch, request) if ch is None: p = util.sibpath(__file__, name) if os.path.exists(p): ch = StaticFile(p) self.info('MSRoot ch', ch) return ch def getChild(self, name, request): self.info('getChild %s, %s' % (name, request)) ch = self.store.get_by_id(name) if isinstance(ch, defer.Deferred): ch.addCallback(self.process_child,name,request) #ch.addCallback(self.delayed_response, request) return ch return self.process_child(ch,name,request) def list_content(self, name, item, request): self.info('list_content', name, item, request) page = """%s

    %s

    """% \ (item.get_name().encode('ascii','xmlcharrefreplace'), item.get_name().encode('ascii','xmlcharrefreplace')) if( hasattr(item,'mimetype') and item.mimetype in ['directory','root']): uri = request.uri if uri[-1] != '/': uri += '/' def build_page(r,page): #print "build_page", r page += """
      """ if r is not None: for c in r: if hasattr(c,'get_url'): path = c.get_url() self.debug('has get_url', path) elif hasattr(c,'get_path') and c.get_path != None: #path = c.get_path().encode('utf-8').encode('string_escape') path = c.get_path() if isinstance(path,unicode): path = path.encode('ascii','xmlcharrefreplace') else: path = path.decode('utf-8').encode('ascii','xmlcharrefreplace') self.debug('has get_path', path) else: path = request.uri.split('/') path[-1] = str(c.get_id()) path = '/'.join(path) self.debug('got path', path) title = c.get_name() self.debug( 'title is:', type(title)) try: if isinstance(title,unicode): title = title.encode('ascii','xmlcharrefreplace') else: title = title.decode('utf-8').encode('ascii','xmlcharrefreplace') except (UnicodeEncodeError,UnicodeDecodeError): title = c.get_name().encode('utf-8').encode('string_escape') page += '
    • %s
    • ' % \ (path, title) page += """
    """ page += """""" return static.Data(page,'text/html') children = item.get_children() if isinstance(children, defer.Deferred): print "list_content, we have a Deferred", children children.addCallback(build_page,page) #children.addErrback(....) #FIXME return children return build_page(children,page) elif( hasattr(item,'mimetype') and item.mimetype.find('image/') == 0): #path = item.get_path().encode('utf-8').encode('string_escape') path = urllib.quote(item.get_path().encode('utf-8')) title = item.get_name().decode('utf-8').encode('ascii','xmlcharrefreplace') page += """

    %s

    """ % \ (path, title) else: pass page += """""" return static.Data(page,'text/html') def listchilds(self, uri): self.info('listchilds %s' % uri) if uri[-1] != '/': uri += '/' cl = '

    content

    ' % uri for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def import_response(self,result,id): return static.Data('

    import of %s finished

    '% id,'text/html') def render(self,request): #print "render", request return '

    root of the %s MediaServer

      %s

    '% \ (self.server.backend, self.listchilds(request.uri)) class RootDeviceXML(static.Data): def __init__(self, hostname, uuid, urlbase, device_type='MediaServer', version=2, friendly_name='Coherence UPnP A/V MediaServer', xbox_hack=False, services=[], devices=[], icons=[], presentationURL=None): uuid = str(uuid) root = ET.Element('root') root.attrib['xmlns']='urn:schemas-upnp-org:device-1-0' device_type = 'urn:schemas-upnp-org:device:%s:%d' % (device_type, int(version)) e = ET.SubElement(root, 'specVersion') ET.SubElement(e, 'major').text = '1' ET.SubElement(e, 'minor').text = '0' #if version == 1: # ET.SubElement(root, 'URLBase').text = urlbase + uuid[5:] + '/' d = ET.SubElement(root, 'device') ET.SubElement(d, 'deviceType').text = device_type if xbox_hack == False: ET.SubElement(d, 'friendlyName').text = friendly_name else: ET.SubElement(d, 'friendlyName').text = friendly_name + ' : 1 : Windows Media Connect' ET.SubElement(d, 'manufacturer').text = 'beebits.net' ET.SubElement(d, 'manufacturerURL').text = 'http://coherence.beebits.net' ET.SubElement(d, 'modelDescription').text = 'Coherence UPnP A/V MediaServer' if xbox_hack == False: ET.SubElement(d, 'modelName').text = 'Coherence UPnP A/V MediaServer' else: ET.SubElement(d, 'modelName').text = 'Windows Media Connect' ET.SubElement(d, 'modelNumber').text = __version__ ET.SubElement(d, 'modelURL').text = 'http://coherence.beebits.net' ET.SubElement(d, 'serialNumber').text = '0000001' ET.SubElement(d, 'UDN').text = uuid ET.SubElement(d, 'UPC').text = '' if len(icons): e = ET.SubElement(d, 'iconList') for icon in icons: icon_path = '' if icon.has_key('url'): if icon['url'].startswith('file://'): icon_path = icon['url'][7:] elif icon['url'] == '.face': icon_path = os.path.join(os.path.expanduser('~'), ".face") else: from pkg_resources import resource_filename icon_path = os.path.abspath(resource_filename(__name__, os.path.join('..','..','..','misc','device-icons',icon['url']))) if os.path.exists(icon_path) == True: i = ET.SubElement(e, 'icon') for k,v in icon.items(): if k == 'url': if v.startswith('file://'): ET.SubElement(i, k).text = '/'+uuid[5:]+'/'+os.path.basename(v) continue elif v == '.face': ET.SubElement(i, k).text = '/'+uuid[5:]+'/'+'face-icon.png' continue else: ET.SubElement(i, k).text = '/'+uuid[5:]+'/'+os.path.basename(v) continue ET.SubElement(i, k).text = str(v) if len(services): e = ET.SubElement(d, 'serviceList') for service in services: id = service.get_id() if xbox_hack == False and id == 'X_MS_MediaReceiverRegistrar': continue s = ET.SubElement(e, 'service') try: namespace = service.namespace except: namespace = 'schemas-upnp-org' if( hasattr(service,'version') and service.version < version): v = service.version else: v = version ET.SubElement(s, 'serviceType').text = 'urn:%s:service:%s:%d' % (namespace, id, int(v)) try: namespace = service.id_namespace except: namespace = 'upnp-org' ET.SubElement(s, 'serviceId').text = 'urn:%s:serviceId:%s' % (namespace,id) ET.SubElement(s, 'SCPDURL').text = '/' + uuid[5:] + '/' + id + '/' + service.scpd_url ET.SubElement(s, 'controlURL').text = '/' + uuid[5:] + '/' + id + '/' + service.control_url ET.SubElement(s, 'eventSubURL').text = '/' + uuid[5:] + '/' + id + '/' + service.subscription_url #ET.SubElement(s, 'SCPDURL').text = id + '/' + service.scpd_url #ET.SubElement(s, 'controlURL').text = id + '/' + service.control_url #ET.SubElement(s, 'eventSubURL').text = id + '/' + service.subscription_url if len(devices): e = ET.SubElement(d, 'deviceList') if presentationURL is None: presentationURL = '/' + uuid[5:] ET.SubElement(d, 'presentationURL').text = presentationURL x = ET.SubElement(d, 'dlna:X_DLNADOC') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'DMS-1.50' x = ET.SubElement(d, 'dlna:X_DLNADOC') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'M-DMS-1.50' x=ET.SubElement(d, 'dlna:X_DLNACAP') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'av-upload,image-upload,audio-upload' #if self.has_level(LOG_DEBUG): # indent( root) self.xml = """""" + ET.tostring( root, encoding='utf-8') static.Data.__init__(self, self.xml, 'text/xml') class MediaServer(log.Loggable,BasicDeviceMixin): logCategory = 'mediaserver' device_type = 'MediaServer' presentationURL = None def fire(self,backend,**kwargs): if kwargs.get('no_thread_needed',False) == False: """ this could take some time, put it in a thread to be sure it doesn't block as we can't tell for sure that every backend is implemented properly """ from twisted.internet import threads d = threads.deferToThread(backend, self, **kwargs) def backend_ready(backend): self.backend = backend def backend_failure(x): self.warning('backend %s not installed, MediaServer activation aborted - %s', backend, x.getErrorMessage()) self.debug(x) d.addCallback(backend_ready) d.addErrback(backend_failure) # FIXME: we need a timeout here so if the signal we wait for not arrives we'll # can close down this device else: self.backend = backend(self, **kwargs) def init_complete(self, backend): if self.backend != backend: return self._services = [] self._devices = [] try: self.connection_manager_server = ConnectionManagerServer(self) self._services.append(self.connection_manager_server) except LookupError,msg: self.warning( 'ConnectionManagerServer', msg) raise LookupError,msg try: transcoding = False if self.coherence.config.get('transcoding', 'no') == 'yes': transcoding = True self.content_directory_server = ContentDirectoryServer(self,transcoding=transcoding) self._services.append(self.content_directory_server) except LookupError,msg: self.warning( 'ContentDirectoryServer', msg) raise LookupError,msg try: self.media_receiver_registrar_server = MediaReceiverRegistrarServer(self, backend=FakeMediaReceiverRegistrarBackend()) self._services.append(self.media_receiver_registrar_server) except LookupError,msg: self.warning( 'MediaReceiverRegistrarServer (optional)', msg) try: self.scheduled_recording_server = ScheduledRecordingServer(self) self._services.append(self.scheduled_recording_server) except LookupError,msg: self.info( 'ScheduledRecordingServer', msg) upnp_init = getattr(self.backend, "upnp_init", None) if upnp_init: upnp_init() self.web_resource = MSRoot( self, backend) self.coherence.add_web_resource( str(self.uuid)[5:], self.web_resource) version = int(self.version) while version > 0: self.web_resource.putChild( 'description-%d.xml' % version, RootDeviceXML( self.coherence.hostname, str(self.uuid), self.coherence.urlbase, self.device_type, version, friendly_name=self.backend.name, services=self._services, devices=self._devices, icons=self.icons, presentationURL = self.presentationURL)) self.web_resource.putChild( 'xbox-description-%d.xml' % version, RootDeviceXML( self.coherence.hostname, str(self.uuid), self.coherence.urlbase, self.device_type, version, friendly_name=self.backend.name, xbox_hack=True, services=self._services, devices=self._devices, icons=self.icons, presentationURL = self.presentationURL)) version -= 1 self.web_resource.putChild('ConnectionManager', self.connection_manager_server) self.web_resource.putChild('ContentDirectory', self.content_directory_server) if hasattr(self,"scheduled_recording_server"): self.web_resource.putChild('ScheduledRecording', self.scheduled_recording_server) if hasattr(self,"media_receiver_registrar_server"): self.web_resource.putChild('X_MS_MediaReceiverRegistrar', self.media_receiver_registrar_server) for icon in self.icons: if icon.has_key('url'): if icon['url'].startswith('file://'): if os.path.exists(icon['url'][7:]): self.web_resource.putChild(os.path.basename(icon['url']), StaticFile(icon['url'][7:],defaultType=icon['mimetype'])) elif icon['url'] == '.face': face_path = os.path.abspath(os.path.join(os.path.expanduser('~'), ".face")) if os.path.exists(face_path): self.web_resource.putChild('face-icon.png',StaticFile(face_path,defaultType=icon['mimetype'])) else: from pkg_resources import resource_filename icon_path = os.path.abspath(resource_filename(__name__, os.path.join('..','..','..','misc','device-icons',icon['url']))) if os.path.exists(icon_path): self.web_resource.putChild(icon['url'],StaticFile(icon_path,defaultType=icon['mimetype'])) self.register() self.warning("%s %s (%s) activated with %s" % (self.backend.name, self.device_type, self.backend, str(self.uuid)[5:])) Coherence-0.6.6.2/coherence/upnp/devices/media_renderer.py0000644000175000017500000001322611317660734021566 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007 Frank Scholz import os.path from twisted.internet import task from twisted.internet import reactor from twisted.web import resource, static from coherence import __version__ from coherence.extern.et import ET, indent from coherence.upnp.core.utils import StaticFile from coherence.upnp.services.servers.connection_manager_server import ConnectionManagerServer from coherence.upnp.services.servers.rendering_control_server import RenderingControlServer from coherence.upnp.services.servers.av_transport_server import AVTransportServer from coherence.upnp.devices.basics import RootDeviceXML, DeviceHttpRoot, BasicDeviceMixin from coherence import log class HttpRoot(DeviceHttpRoot): logCategory = 'mediarenderer' class MediaRenderer(log.Loggable,BasicDeviceMixin): logCategory = 'mediarenderer' device_type = 'MediaRenderer' def fire(self,backend,**kwargs): if kwargs.get('no_thread_needed',False) == False: """ this could take some time, put it in a thread to be sure it doesn't block as we can't tell for sure that every backend is implemented properly """ from twisted.internet import threads d = threads.deferToThread(backend, self, **kwargs) def backend_ready(backend): self.backend = backend def backend_failure(x): self.warning('backend %s not installed, %s activation aborted - %s' % (backend, self.device_type, x.getErrorMessage())) self.debug(x) d.addCallback(backend_ready) d.addErrback(backend_failure) # FIXME: we need a timeout here so if the signal we wait for not arrives we'll # can close down this device else: self.backend = backend(self, **kwargs) def init_complete(self, backend): if self.backend != backend: return self._services = [] self._devices = [] try: self.connection_manager_server = ConnectionManagerServer(self) self._services.append(self.connection_manager_server) except LookupError,msg: self.warning( 'ConnectionManagerServer', msg) raise LookupError,msg try: self.rendering_control_server = RenderingControlServer(self) self._services.append(self.rendering_control_server) except LookupError,msg: self.warning( 'RenderingControlServer', msg) raise LookupError,msg try: self.av_transport_server = AVTransportServer(self) self._services.append(self.av_transport_server) except LookupError,msg: self.warning( 'AVTransportServer', msg) raise LookupError,msg upnp_init = getattr(self.backend, "upnp_init", None) if upnp_init: upnp_init() self.web_resource = HttpRoot(self) self.coherence.add_web_resource( str(self.uuid)[5:], self.web_resource) try: dlna_caps = self.backend.dlna_caps except AttributeError: dlna_caps = [] version = self.version while version > 0: self.web_resource.putChild( 'description-%d.xml' % version, RootDeviceXML( self.coherence.hostname, str(self.uuid), self.coherence.urlbase, device_type=self.device_type, version=version, #presentation_url='/'+str(self.uuid)[5:], friendly_name=self.backend.name, model_description='Coherence UPnP A/V %s' % self.device_type, model_name='Coherence UPnP A/V %s' % self.device_type, services=self._services, devices=self._devices, icons=self.icons, dlna_caps=dlna_caps)) version -= 1 self.web_resource.putChild('ConnectionManager', self.connection_manager_server) self.web_resource.putChild('RenderingControl', self.rendering_control_server) self.web_resource.putChild('AVTransport', self.av_transport_server) for icon in self.icons: if icon.has_key('url'): if icon['url'].startswith('file://'): if os.path.exists(icon['url'][7:]): self.web_resource.putChild(os.path.basename(icon['url']), StaticFile(icon['url'][7:],defaultType=icon['mimetype'])) elif icon['url'] == '.face': face_path = os.path.abspath(os.path.join(os.path.expanduser('~'), ".face")) if os.path.exists(face_path): self.web_resource.putChild('face-icon.png',StaticFile(face_path,defaultType=icon['mimetype'])) else: from pkg_resources import resource_filename icon_path = os.path.abspath(resource_filename(__name__, os.path.join('..','..','..','misc','device-icons',icon['url']))) if os.path.exists(icon_path): self.web_resource.putChild(icon['url'],StaticFile(icon_path,defaultType=icon['mimetype'])) self.register() self.warning("%s %s (%s) activated with %s" % (self.backend.name, self.device_type, self.backend, str(self.uuid)[5:])) Coherence-0.6.6.2/coherence/upnp/devices/media_server_client.py0000644000175000017500000001230611317660734022622 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz from coherence.upnp.services.clients.connection_manager_client import ConnectionManagerClient from coherence.upnp.services.clients.content_directory_client import ContentDirectoryClient from coherence.upnp.services.clients.av_transport_client import AVTransportClient from coherence import log import coherence.extern.louie as louie class MediaServerClient(log.Loggable): logCategory = 'ms_client' def __init__(self, device): self.device = device self.device_type = self.device.get_friendly_device_type() self.version = int(self.device.get_device_type_version()) self.icons = device.icons self.scheduled_recording = None self.content_directory = None self.connection_manager = None self.av_transport = None self.detection_completed = False louie.connect(self.service_notified, signal='Coherence.UPnP.DeviceClient.Service.notified', sender=self.device) for service in self.device.get_services(): if service.get_type() in ["urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ContentDirectory:2"]: self.content_directory = ContentDirectoryClient( service) if service.get_type() in ["urn:schemas-upnp-org:service:ConnectionManager:1", "urn:schemas-upnp-org:service:ConnectionManager:2"]: self.connection_manager = ConnectionManagerClient( service) if service.get_type() in ["urn:schemas-upnp-org:service:AVTransport:1", "urn:schemas-upnp-org:service:AVTransport:2"]: self.av_transport = AVTransportClient( service) #if service.get_type() in ["urn:schemas-upnp-org:service:ScheduledRecording:1", # "urn:schemas-upnp-org:service:ScheduledRecording:2"]: # self.scheduled_recording = ScheduledRecordingClient( service) self.info("MediaServer %s" % (self.device.get_friendly_name())) if self.content_directory: self.info("ContentDirectory available") else: self.warning("ContentDirectory not available, device not implemented properly according to the UPnP specification") return if self.connection_manager: self.info("ConnectionManager available") else: self.warning("ConnectionManager not available, device not implemented properly according to the UPnP specification") return if self.av_transport: self.info("AVTransport (optional) available") if self.scheduled_recording: self.info("ScheduledRecording (optional) available") #d = self.content_directory.browse(0) # browse top level #d.addCallback( self.process_meta) #def __del__(self): # #print "MediaServerClient deleted" # pass def remove(self): self.info("removal of MediaServerClient started") if self.content_directory != None: self.content_directory.remove() if self.connection_manager != None: self.connection_manager.remove() if self.av_transport != None: self.av_transport.remove() if self.scheduled_recording != None: self.scheduled_recording.remove() #del self def service_notified(self, service): self.info('notified about %r' % service) if self.detection_completed == True: return if self.content_directory != None: if not hasattr(self.content_directory.service, 'last_time_updated'): return if self.content_directory.service.last_time_updated == None: return if self.connection_manager != None: if not hasattr(self.connection_manager.service, 'last_time_updated'): return if self.connection_manager.service.last_time_updated == None: return if self.av_transport != None: if not hasattr(self.av_transport.service, 'last_time_updated'): return if self.av_transport.service.last_time_updated == None: return if self.scheduled_recording != None: if not hasattr(self.scheduled_recording.service, 'last_time_updated'): return if self.scheduled_recording.service.last_time_updated == None: return self.detection_completed = True louie.send('Coherence.UPnP.DeviceClient.detection_completed', None, client=self,udn=self.device.udn) self.info('detection_completed for %r' % self) def state_variable_change( self, variable, usn): self.info(variable.name, 'changed from', variable.old_value, 'to', variable.value) def print_results(self, results): self.info("results=", results) def process_meta( self, results): for k,v in results.iteritems(): dfr = self.content_directory.browse(k, "BrowseMetadata") dfr.addCallback( self.print_results) Coherence-0.6.6.2/coherence/upnp/devices/dimmable_light_client.py0000644000175000017500000000555411317660734023125 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz from coherence.upnp.services.clients.switch_power_client import SwitchPowerClient from coherence.upnp.services.clients.dimming_client import DimmingClient from coherence import log import coherence.extern.louie as louie class DimmableLightClient(log.Loggable): logCategory = 'dimminglight_client' def __init__(self, device): self.device = device self.device_type = self.device.get_friendly_device_type() self.version = int(self.device.get_device_type_version()) self.icons = device.icons self.switch_power = None self.dimming = None self.detection_completed = False louie.connect(self.service_notified, signal='Coherence.UPnP.DeviceClient.Service.notified', sender=self.device) for service in self.device.get_services(): if service.get_type() in ["urn:schemas-upnp-org:service:SwitchPower:1"]: self.switch_power = SwitchPowerClient(service) if service.get_type() in ["urn:schemas-upnp-org:service:Dimming:1"]: self.dimming = DimmingClient(service) self.info("DimmingLight %s" % (self.device.get_friendly_name())) if self.switch_power: self.info("SwitchPower service available") else: self.warning("SwitchPower service not available, device not implemented properly according to the UPnP specification") return if self.dimming: self.info("Dimming service available") else: self.warning("Dimming service not available, device not implemented properly according to the UPnP specification") def remove(self): self.info("removal of DimmingLightClient started") if self.dimming != None: self.dimming.remove() if self.switch_power != None: self.switch_power.remove() def service_notified(self, service): self.info("Service %r sent notification" % service); if self.detection_completed == True: return if self.switch_power != None: if not hasattr(self.switch_power.service, 'last_time_updated'): return if self.switch_power.service.last_time_updated == None: return if self.dimming != None: if not hasattr(self.dimming.service, 'last_time_updated'): return if self.dimming.service.last_time_updated == None: return self.detection_completed = True louie.send('Coherence.UPnP.DeviceClient.detection_completed', None, client=self,udn=self.device.udn) def state_variable_change( self, variable): self.info(variable.name, 'changed from', variable.old_value, 'to', variable.value) Coherence-0.6.6.2/coherence/upnp/devices/dimmable_light.py0000644000175000017500000001000611317660734021553 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz from twisted.internet import task from twisted.internet import reactor from twisted.web import resource, static from coherence import __version__ from coherence.extern.et import ET, indent from coherence.upnp.services.servers.switch_power_server import SwitchPowerServer from coherence.upnp.services.servers.dimming_server import DimmingServer from coherence.upnp.devices.basics import RootDeviceXML, DeviceHttpRoot, BasicDeviceMixin import coherence.extern.louie as louie from coherence import log class HttpRoot(DeviceHttpRoot): logCategory = 'dimmablelight' class DimmableLight(log.Loggable,BasicDeviceMixin): logCategory = 'dimmablelight' device_type = 'DimmableLight' version = 1 def fire(self,backend,**kwargs): if kwargs.get('no_thread_needed',False) == False: """ this could take some time, put it in a thread to be sure it doesn't block as we can't tell for sure that every backend is implemented properly """ from twisted.internet import threads d = threads.deferToThread(backend, self, **kwargs) def backend_ready(backend): self.backend = backend def backend_failure(x): self.warning('backend not installed, %s activation aborted' % self.device_type) self.debug(x) d.addCallback(backend_ready) d.addErrback(backend_failure) # FIXME: we need a timeout here so if the signal we wait for not arrives we'll # can close down this device else: self.backend = backend(self, **kwargs) def init_complete(self, backend): if self.backend != backend: return self._services = [] self._devices = [] try: self.switch_power_server = SwitchPowerServer(self) self._services.append(self.switch_power_server) except LookupError,msg: self.warning( 'SwitchPowerServer', msg) raise LookupError,msg try: self.dimming_server = DimmingServer(self) self._services.append(self.dimming_server) except LookupError,msg: self.warning( 'SwitchPowerServer', msg) raise LookupError,msg upnp_init = getattr(self.backend, "upnp_init", None) if upnp_init: upnp_init() self.web_resource = HttpRoot(self) self.coherence.add_web_resource( str(self.uuid)[5:], self.web_resource) version = self.version while version > 0: self.web_resource.putChild( 'description-%d.xml' % version, RootDeviceXML( self.coherence.hostname, str(self.uuid), self.coherence.urlbase, device_type=self.device_type, version=version, friendly_name=self.backend.name, model_description='Coherence UPnP %s' % self.device_type, model_name='Coherence UPnP %s' % self.device_type, services=self._services, devices=self._devices, icons=self.icons)) version -= 1 self.web_resource.putChild('SwitchPower', self.switch_power_server) self.web_resource.putChild('Dimming', self.dimming_server) for icon in self.icons: if icon.has_key('url'): if icon['url'].startswith('file://'): self.web_resource.putChild(os.path.basename(icon['url']), static.File(icon['url'][7:])) self.register() self.warning("%s %s (%s) activated with %s" % (self.backend.name, self.device_type, self.backend, str(self.uuid)[5:])) Coherence-0.6.6.2/coherence/upnp/devices/basics.py0000644000175000017500000003262611317660734020072 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz import os.path from twisted.python import util from twisted.web import resource, static from twisted.internet import reactor from coherence import __version__ from coherence.extern.et import ET, indent import coherence.extern.louie as louie from coherence import log class DeviceHttpRoot(resource.Resource, log.Loggable): logCategory = 'basicdevice' def __init__(self, server): resource.Resource.__init__(self) self.server = server def getChildWithDefault(self, path, request): self.info('DeviceHttpRoot %s getChildWithDefault ' % self.server.device_type, path, request.uri, request.client) self.info( request.getAllHeaders()) if self.children.has_key(path): return self.children[path] if request.uri == '/': return self return self.getChild(path, request) def getChild(self, name, request): self.info('DeviceHttpRoot %s getChild %s ' % (name, request)) ch = None if ch is None: p = util.sibpath(__file__, name) if os.path.exists(p): ch = static.File(p) self.info('DeviceHttpRoot ch ', ch) return ch def listchilds(self, uri): cl = '' for c in self.children: cl += '
  • %s
  • ' % (uri,c,c) return cl def render(self,request): return '

    root of the %s %s

      %s

    '% (self.server.backend.name, self.server.device_type, self.listchilds(request.uri)) class RootDeviceXML(static.Data): def __init__(self, hostname, uuid, urlbase, xmlns='urn:schemas-upnp-org:device-1-0', device_uri_base='urn:schemas-upnp-org:device', device_type='BasicDevice', version=2, friendly_name='Coherence UPnP BasicDevice', manufacturer='beebits.net', manufacturer_url='http://coherence.beebits.net', model_description='Coherence UPnP BasicDevice', model_name='Coherence UPnP BasicDevice', model_number=__version__, model_url='http://coherence.beebits.net', serial_number='0000001', presentation_url='', services=[], devices=[], icons=[], dlna_caps=[]): uuid = str(uuid) root = ET.Element('root') root.attrib['xmlns']=xmlns device_type_uri = ':'.join((device_uri_base,device_type, str(version))) e = ET.SubElement(root, 'specVersion') ET.SubElement( e, 'major').text = '1' ET.SubElement( e, 'minor').text = '0' #ET.SubElement(root, 'URLBase').text = urlbase + uuid[5:] + '/' d = ET.SubElement(root, 'device') if device_type == 'MediaServer': x = ET.SubElement(d, 'dlna:X_DLNADOC') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'DMS-1.50' x = ET.SubElement(d, 'dlna:X_DLNADOC') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'M-DMS-1.50' elif device_type == 'MediaRenderer': x = ET.SubElement(d, 'dlna:X_DLNADOC') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'DMR-1.50' x = ET.SubElement(d, 'dlna:X_DLNADOC') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = 'M-DMR-1.50' if len(dlna_caps) > 0: if isinstance(dlna_caps, basestring): dlna_caps = [dlna_caps] for cap in dlna_caps: x = ET.SubElement(d, 'dlna:X_DLNACAP') x.attrib['xmlns:dlna']='urn:schemas-dlna-org:device-1-0' x.text = cap ET.SubElement( d, 'deviceType').text = device_type_uri ET.SubElement( d, 'friendlyName').text = friendly_name ET.SubElement( d, 'manufacturer').text = manufacturer ET.SubElement( d, 'manufacturerURL').text = manufacturer_url ET.SubElement( d, 'modelDescription').text = model_description ET.SubElement( d, 'modelName').text = model_name ET.SubElement(d, 'modelNumber').text = model_number ET.SubElement( d, 'modelURL').text = model_url ET.SubElement( d, 'serialNumber').text = serial_number ET.SubElement( d, 'UDN').text = uuid ET.SubElement( d, 'UPC').text = '' ET.SubElement( d, 'presentationURL').text = presentation_url if len(services): e = ET.SubElement(d, 'serviceList') for service in services: id = service.get_id() s = ET.SubElement(e, 'service') try: namespace = service.namespace except: namespace = 'schemas-upnp-org' if( hasattr(service,'version') and service.version < version): v = service.version else: v = version ET.SubElement(s, 'serviceType').text = 'urn:%s:service:%s:%d' % (namespace, id, int(v)) try: namespace = service.id_namespace except: namespace = 'upnp-org' ET.SubElement(s, 'serviceId').text = 'urn:%s:serviceId:%s' % (namespace,id) ET.SubElement(s, 'SCPDURL').text = '/' + uuid[5:] + '/' + id + '/' + service.scpd_url ET.SubElement(s, 'controlURL').text = '/' + uuid[5:] + '/' + id + '/' + service.control_url ET.SubElement(s, 'eventSubURL').text = '/' + uuid[5:] + '/' + id + '/' + service.subscription_url if len(devices): e = ET.SubElement( d, 'deviceList') if len(icons): e = ET.SubElement(d, 'iconList') for icon in icons: icon_path = '' if icon.has_key('url'): if icon['url'].startswith('file://'): icon_path = icon['url'][7:] elif icon['url'] == '.face': icon_path = os.path.join(os.path.expanduser('~'), ".face") else: from pkg_resources import resource_filename icon_path = os.path.abspath(resource_filename(__name__, os.path.join('..','..','..','misc','device-icons',icon['url']))) if os.path.exists(icon_path) == True: i = ET.SubElement(e, 'icon') for k,v in icon.items(): if k == 'url': if v.startswith('file://'): ET.SubElement(i, k).text = '/'+uuid[5:]+'/'+os.path.basename(v) continue elif v == '.face': ET.SubElement(i, k).text = '/'+uuid[5:]+'/'+'face-icon.png' continue else: ET.SubElement(i, k).text = '/'+uuid[5:]+'/'+os.path.basename(v) continue ET.SubElement(i, k).text = str(v) #if self.has_level(LOG_DEBUG): # indent( root) self.xml = """""" + ET.tostring( root, encoding='utf-8') static.Data.__init__(self, self.xml, 'text/xml') class BasicDeviceMixin(object): def __init__(self, coherence, backend, **kwargs): self.coherence = coherence if not hasattr(self,'version'): self.version = int(kwargs.get('version',self.coherence.config.get('version',2))) try: self.uuid = kwargs['uuid'] if not self.uuid.startswith('uuid:'): self.uuid = 'uuid:' + self.uuid except KeyError: from coherence.upnp.core.uuid import UUID self.uuid = UUID() self.backend = None urlbase = self.coherence.urlbase if urlbase[-1] != '/': urlbase += '/' self.urlbase = urlbase + str(self.uuid)[5:] kwargs['urlbase'] = self.urlbase self.icons = kwargs.get('iconlist', kwargs.get('icons', [])) if len(self.icons) == 0: if kwargs.has_key('icon'): if isinstance(kwargs['icon'],dict): self.icons.append(kwargs['icon']) else: self.icons = kwargs['icon'] louie.connect( self.init_complete, 'Coherence.UPnP.Backend.init_completed', louie.Any) louie.connect( self.init_failed, 'Coherence.UPnP.Backend.init_failed', louie.Any) reactor.callLater(0.2, self.fire, backend, **kwargs) def init_failed(self, backend, msg): if self.backend != backend: return self.warning('backend not installed, %s activation aborted - %s' % (self.device_type,msg.getErrorMessage())) self.debug(msg) try: del self.coherence.active_backends[str(self.uuid)] except KeyError: pass def register(self): s = self.coherence.ssdp_server uuid = str(self.uuid) host = self.coherence.hostname self.msg('%s register' % self.device_type) # we need to do this after the children are there, since we send notifies s.register('local', '%s::upnp:rootdevice' % uuid, 'upnp:rootdevice', self.coherence.urlbase + uuid[5:] + '/' + 'description-%d.xml' % self.version, host=host) s.register('local', uuid, uuid, self.coherence.urlbase + uuid[5:] + '/' + 'description-%d.xml' % self.version, host=host) version = self.version while version > 0: if version == self.version: silent = False else: silent = True s.register('local', '%s::urn:schemas-upnp-org:device:%s:%d' % (uuid, self.device_type, version), 'urn:schemas-upnp-org:device:%s:%d' % (self.device_type, version), self.coherence.urlbase + uuid[5:] + '/' + 'description-%d.xml' % version, silent=silent, host=host) version -= 1 for service in self._services: device_version = self.version service_version = self.version if hasattr(service,'version'): service_version = service.version silent = False while service_version > 0: try: namespace = service.namespace except: namespace = 'schemas-upnp-org' device_description_tmpl = 'description-%d.xml' % device_version if hasattr(service,'device_description_tmpl'): device_description_tmpl = service.device_description_tmpl s.register('local', '%s::urn:%s:service:%s:%d' % (uuid,namespace,service.id, service_version), 'urn:%s:service:%s:%d' % (namespace,service.id, service_version), self.coherence.urlbase + uuid[5:] + '/' + device_description_tmpl, silent=silent, host=host) silent = True service_version -= 1 device_version -= 1 def unregister(self): if self.backend != None and hasattr(self.backend,'release'): self.backend.release() if not hasattr(self,'_services'): """ seems we never made it to actually completing that device """ return for service in self._services: try: service.check_subscribers_loop.stop() except: pass if hasattr(service,'check_moderated_loop') and service.check_moderated_loop != None: try: service.check_moderated_loop.stop() except: pass if hasattr(service,'release'): service.release() if hasattr(service,'_release'): service._release() s = self.coherence.ssdp_server uuid = str(self.uuid) self.coherence.remove_web_resource(uuid[5:]) version = self.version while version > 0: s.doByebye('%s::urn:schemas-upnp-org:device:%s:%d' % (uuid, self.device_type, version)) for service in self._services: if hasattr(service,'version') and service.version < version: continue try: namespace = service.namespace except AttributeError: namespace = 'schemas-upnp-org' s.doByebye('%s::urn:%s:service:%s:%d' % (uuid,namespace,service.id, version)) version -= 1 s.doByebye(uuid) s.doByebye('%s::upnp:rootdevice' % uuid)Coherence-0.6.6.2/coherence/upnp/devices/__init__.py0000644000175000017500000000000011317660734020342 0ustar devdevCoherence-0.6.6.2/coherence/upnp/devices/control_point.py0000644000175000017500000003001511317660734021505 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz import string import traceback from twisted.internet import task from twisted.internet import reactor from twisted.web import xmlrpc, client from coherence.upnp.core import service from coherence.upnp.core.event import EventServer from coherence.upnp.devices.media_server_client import MediaServerClient from coherence.upnp.devices.media_renderer_client import MediaRendererClient from coherence.upnp.devices.binary_light_client import BinaryLightClient from coherence.upnp.devices.dimmable_light_client import DimmableLightClient import coherence.extern.louie as louie from coherence import log class DeviceQuery(object): def __init__(self, type, pattern, callback, timeout=0, oneshot=True): self.type = type self.pattern = pattern self.callback = callback self.fired = False self.timeout = timeout self.oneshot = oneshot if self.type == 'uuid' and self.pattern.startswith('uuid:'): self.pattern = self.pattern[5:] def fire(self, device): if callable(self.callback): self.callback(device) elif isinstance(self.callback,basestring): louie.send(self.callback, None, device=device) self.fired = True def check(self, device): if self.fired and self.oneshot: return if(self.type == 'host' and device.host == self.pattern): self.fire(device) elif(self.type == 'friendly_name' and device.friendly_name == self.pattern): self.fire(device) elif(self.type == 'uuid' and device.get_uuid() == self.pattern): self.fire(device) class ControlPoint(log.Loggable): logCategory = 'controlpoint' def __init__(self,coherence,auto_client=['MediaServer','MediaRenderer','BinaryLight','DimmableLight']): self.coherence = coherence self.info("Coherence UPnP ControlPoint starting...") self.event_server = EventServer(self) self.coherence.add_web_resource('RPC2', XMLRPC(self)) self.auto_client = auto_client self.queries=[] for device in self.get_devices(): self.check_device( device) louie.connect(self.check_device, 'Coherence.UPnP.Device.detection_completed', louie.Any) louie.connect(self.remove_client, 'Coherence.UPnP.Device.remove_client', louie.Any) louie.connect(self.completed, 'Coherence.UPnP.DeviceClient.detection_completed', louie.Any) def browse(self, device): device = self.coherence.get_device_with_usn(infos['USN']) if not device: return self.check_device( device) def process_queries(self, device): for query in self.queries: query.check(device) def add_query(self, query): for device in self.get_devices(): query.check(device) if query.fired == False and query.timeout == 0: query.callback(None) else: self.queries.append(query) def connect(self,receiver,signal=louie.signal.All,sender=louie.sender.Any, weak=True): """ wrapper method around louie.connect """ louie.connect(receiver,signal=signal,sender=sender,weak=weak) def disconnect(self,receiver,signal=louie.signal.All,sender=louie.sender.Any, weak=True): """ wrapper method around louie.disconnect """ louie.disconnect(receiver,signal=signal,sender=sender,weak=weak) def get_devices(self): return self.coherence.get_devices() def get_device_with_id(self, id): return self.coherence.get_device_with_id(id) def get_device_by_host(self, host): return self.coherence.get_device_by_host(host) def check_device( self, device): if device.client == None: self.info("found device %s of type %s - %r" %(device.get_friendly_name(), device.get_device_type(), device.client)) short_type = device.get_friendly_device_type() if short_type in self.auto_client and short_type is not None: self.info("identified %s %r" % (short_type, device.get_friendly_name())) if short_type == 'MediaServer': client = MediaServerClient(device) if short_type == 'MediaRenderer': client = MediaRendererClient(device) if short_type == 'BinaryLight': client = BinaryLightClient(device) if short_type == 'DimmableLight': client = DimmableLightClient(device) client.coherence = self.coherence device.set_client( client) self.process_queries(device) def completed(self, client, udn): self.info('sending signal Coherence.UPnP.ControlPoint.%s.detected %r' % (client.device_type, udn)) louie.send('Coherence.UPnP.ControlPoint.%s.detected' % client.device_type, None, client=client,udn=udn) def remove_client(self, udn, client): louie.send('Coherence.UPnP.ControlPoint.%s.removed' % client.device_type, None, udn=udn) self.info("removed %s %s" % (client.device_type,client.device.get_friendly_name())) client.remove() def propagate(self, event): self.info('propagate: %r', event) if event.get_sid() in service.subscribers.keys(): try: service.subscribers[event.get_sid()].process_event(event) except Exception, msg: self.debug(msg) self.debug(traceback.format_exc()) pass def put_resource(self, url, path): def got_result(result): print result def got_error(result): print "error", result try: f = open(path) data = f.read() f.close() headers= { "Content-Type": "application/octet-stream", "Content-Length": str(len(data)) } df = client.getPage(url, method="POST", headers=headers, postdata=data) df.addCallback(got_result) df.addErrback(got_error) return df except IOError: pass class XMLRPC( xmlrpc.XMLRPC): def __init__(self, control_point): self.control_point = control_point self.allowNone = True def xmlrpc_list_devices(self): print "list_devices" r = [] for device in self.control_point.get_devices(): #print device.get_friendly_name(), device.get_service_type(), device.get_location(), device.get_id() d = {} d[u'friendly_name']=device.get_friendly_name() d[u'device_type']=device.get_device_type() d[u'location']=unicode(device.get_location()) d[u'id']=unicode(device.get_id()) r.append(d) return r def xmlrpc_mute_device(self, device_id): print "mute" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.rendering_control.set_mute(desired_mute=1) return "Ok" return "Error" def xmlrpc_unmute_device(self, device_id): print "unmute", device_id device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.rendering_control.set_mute(desired_mute=0) return "Ok" return "Error" def xmlrpc_set_volume(self, device_id, volume): print "set volume" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.rendering_control.set_volume(desired_volume=volume) return "Ok" return "Error" def xmlrpc_play(self, device_id): print "play" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.av_transport.play() return "Ok" return "Error" def xmlrpc_pause(self, device_id): print "pause" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.av_transport.pause() return "Ok" return "Error" def xmlrpc_stop(self, device_id): print "stop" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.av_transport.stop() return "Ok" return "Error" def xmlrpc_next(self, device_id): print "next" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.av_transport.next() return "Ok" return "Error" def xmlrpc_previous(self, device_id): print "previous" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.av_transport.previous() return "Ok" return "Error" def xmlrpc_set_av_transport_uri(self, device_id, uri): print "set_av_transport_uri" device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.av_transport.set_av_transport_uri(current_uri=uri) return "Ok" return "Error" def xmlrpc_create_object(self, device_id, container_id, arguments): print "create_object", arguments device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.content_directory.create_object(container_id, arguments) return "Ok" return "Error" def xmlrpc_import_resource(self, device_id, source_uri, destination_uri): print "import_resource", source_uri, destination_uri device = self.control_point.get_device_with_id(device_id) if device != None: client = device.get_client() client.content_directory.import_resource(source_uri, destination_uri) return "Ok" return "Error" def xmlrpc_put_resource(self, url, path): print "put_resource", url, path self.control_point.put_resource(url, path) return "Ok" def xmlrpc_ping(self): print "ping" return "Ok" def startXMLRPC( control_point, port): from twisted.web import server r = XMLRPC( control_point) print "XMLRPC-API on port %d ready" % port reactor.listenTCP(port, server.Site(r)) if __name__ == '__main__': from coherence.base import Coherence from coherence.upnp.devices.media_server_client import MediaServerClient from coherence.upnp.devices.media_renderer_client import MediaRendererClient config = {} config['logmode'] = 'warning' config['serverport'] = 30020 #ctrl = ControlPoint(Coherence(config),auto_client=[]) #ctrl = ControlPoint(Coherence(config)) def show_devices(): print "show_devices" for d in ctrl.get_devices(): print d, d.get_id() def the_result(r): print "result", r, r.get_id() def query_devices(): print "query_devices" ctrl.add_query(DeviceQuery('host', '192.168.1.163', the_result)) def query_devices2(): print "query_devices with timeout" ctrl.add_query(DeviceQuery('host', '192.168.1.163', the_result, timeout=10, oneshot=False)) #reactor.callLater(2, show_devices) #reactor.callLater(3, query_devices) #reactor.callLater(4, query_devices2) #reactor.callLater(5, ctrl.add_query, DeviceQuery('friendly_name', 'Coherence Test Content', the_result, timeout=10, oneshot=False)) reactor.run() Coherence-0.6.6.2/coherence/upnp/devices/binary_light.py0000644000175000017500000000720011317660734021267 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz from twisted.internet import task from twisted.internet import reactor from twisted.web import resource, static from coherence import __version__ from coherence.extern.et import ET, indent from coherence.upnp.services.servers.switch_power_server import SwitchPowerServer from coherence.upnp.devices.basics import RootDeviceXML, DeviceHttpRoot, BasicDeviceMixin import coherence.extern.louie as louie from coherence import log class HttpRoot(DeviceHttpRoot): logCategory = 'binarylight' class BinaryLight(log.Loggable,BasicDeviceMixin): logCategory = 'binarylight' device_type = 'BinaryLight' version = 1 def fire(self,backend,**kwargs): if kwargs.get('no_thread_needed',False) == False: """ this could take some time, put it in a thread to be sure it doesn't block as we can't tell for sure that every backend is implemented properly """ from twisted.internet import threads d = threads.deferToThread(backend, self, **kwargs) def backend_ready(backend): self.backend = backend def backend_failure(x): self.warning('backend not installed, %s activation aborted' % self.device_type) self.debug(x) d.addCallback(backend_ready) d.addErrback(backend_failure) # FIXME: we need a timeout here so if the signal we wait for not arrives we'll # can close down this device else: self.backend = backend(self, **kwargs) def init_complete(self, backend): if self.backend != backend: return self._services = [] self._devices = [] try: self.switch_power_server = SwitchPowerServer(self) self._services.append(self.switch_power_server) except LookupError,msg: self.warning( 'SwitchPowerServer', msg) raise LookupError,msg upnp_init = getattr(self.backend, "upnp_init", None) if upnp_init: upnp_init() self.web_resource = HttpRoot(self) self.coherence.add_web_resource( str(self.uuid)[5:], self.web_resource) version = self.version while version > 0: self.web_resource.putChild( 'description-%d.xml' % version, RootDeviceXML( self.coherence.hostname, str(self.uuid), self.coherence.urlbase, device_type=self.device_type, version=version, friendly_name=self.backend.name, model_description='Coherence UPnP %s' % self.device_type, model_name='Coherence UPnP %s' % self.device_type, services=self._services, devices=self._devices, icons=self.icons)) version -= 1 self.web_resource.putChild('SwitchPower', self.switch_power_server) for icon in self.icons: if icon.has_key('url'): if icon['url'].startswith('file://'): self.web_resource.putChild(os.path.basename(icon['url']), static.File(icon['url'][7:])) self.register() self.warning("%s %s (%s) activated with %s" % (self.backend.name, self.device_type, self.backend, str(self.uuid)[5:])) Coherence-0.6.6.2/coherence/upnp/devices/media_renderer_client.py0000644000175000017500000001206311317660734023122 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz from coherence.upnp.services.clients.connection_manager_client import ConnectionManagerClient from coherence.upnp.services.clients.rendering_control_client import RenderingControlClient from coherence.upnp.services.clients.av_transport_client import AVTransportClient from coherence import log import coherence.extern.louie as louie class MediaRendererClient(log.Loggable): logCategory = 'mr_client' def __init__(self, device): self.device = device self.device_type = self.device.get_friendly_device_type() self.version = int(self.device.get_device_type_version()) self.icons = device.icons self.rendering_control = None self.connection_manager = None self.av_transport = None self.detection_completed = False louie.connect(self.service_notified, signal='Coherence.UPnP.DeviceClient.Service.notified', sender=self.device) for service in self.device.get_services(): if service.get_type() in ["urn:schemas-upnp-org:service:RenderingControl:1", "urn:schemas-upnp-org:service:RenderingControl:2"]: self.rendering_control = RenderingControlClient( service) if service.get_type() in ["urn:schemas-upnp-org:service:ConnectionManager:1", "urn:schemas-upnp-org:service:ConnectionManager:2"]: self.connection_manager = ConnectionManagerClient( service) if service.get_type() in ["urn:schemas-upnp-org:service:AVTransport:1", "urn:schemas-upnp-org:service:AVTransport:2"]: self.av_transport = AVTransportClient( service) self.info("MediaRenderer %s" % (self.device.get_friendly_name())) if self.rendering_control: self.info("RenderingControl available") """ actions = self.rendering_control.service.get_actions() print actions for action in actions: print "Action:", action for arg in actions[action].get_arguments_list(): print " ", arg """ #self.rendering_control.list_presets() #self.rendering_control.get_mute() #self.rendering_control.get_volume() #self.rendering_control.set_mute(desired_mute=1) else: self.warning("RenderingControl not available, device not implemented properly according to the UPnP specification") return if self.connection_manager: self.info("ConnectionManager available") #self.connection_manager.get_protocol_info() else: self.warning("ConnectionManager not available, device not implemented properly according to the UPnP specification") return if self.av_transport: self.info("AVTransport (optional) available") #self.av_transport.service.subscribe_for_variable('LastChange', 0, self.state_variable_change) #self.av_transport.service.subscribe_for_variable('TransportState', 0, self.state_variable_change) #self.av_transport.service.subscribe_for_variable('CurrentTransportActions', 0, self.state_variable_change) #self.av_transport.get_transport_info() #self.av_transport.get_current_transport_actions() #def __del__(self): # #print "MediaRendererClient deleted" # pass def remove(self): self.info("removal of MediaRendererClient started") if self.rendering_control != None: self.rendering_control.remove() if self.connection_manager != None: self.connection_manager.remove() if self.av_transport != None: self.av_transport.remove() #del self def service_notified(self, service): self.info("Service %r sent notification" % service); if self.detection_completed == True: return if self.rendering_control != None: if not hasattr(self.rendering_control.service, 'last_time_updated'): return if self.rendering_control.service.last_time_updated == None: return if self.connection_manager != None: if not hasattr(self.connection_manager.service, 'last_time_updated'): return if self.connection_manager.service.last_time_updated == None: return if self.av_transport != None: if not hasattr(self.av_transport.service, 'last_time_updated'): return if self.av_transport.service.last_time_updated == None: return self.detection_completed = True louie.send('Coherence.UPnP.DeviceClient.detection_completed', None, client=self,udn=self.device.udn) def state_variable_change( self, variable): self.info(variable.name, 'changed from', variable.old_value, 'to', variable.value) Coherence-0.6.6.2/coherence/upnp/__init__.py0000644000175000017500000000000011317660735016721 0ustar devdevCoherence-0.6.6.2/coherence/upnp/core/0000755000000000000000000000000011317673117016130 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/core/utils.py0000644000175000017500000007446511317660735017304 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz import urlparse from urlparse import urlsplit from coherence.extern.et import parse_xml as et_parse_xml from coherence import SERVER_ID from twisted.web import server, http, static from twisted.web import client, error from twisted.web import proxy, resource, server from twisted.internet import reactor,protocol,defer,abstract from twisted.python import failure from twisted.python.util import InsensitiveDict try: from twisted.protocols._c_urlarg import unquote except ImportError: from urllib import unquote try: import netifaces have_netifaces = True except ImportError: have_netifaces = False def means_true(value): if isinstance(value,basestring): value = value.lower() return value in [True,1,'1','true','yes','ok'] def generalise_boolean(value): """ standardize the different boolean incarnations transform anything that looks like a "True" into a '1', and everything else into a '0' """ if means_true(value): return '1' return '0' generalize_boolean = generalise_boolean def parse_xml(data, encoding="utf-8"): return et_parse_xml(data,encoding) def parse_http_response(data): """ don't try to get the body, there are reponses without """ header = data.split('\r\n\r\n')[0] lines = header.split('\r\n') cmd = lines[0].split(' ') lines = map(lambda x: x.replace(': ', ':', 1), lines[1:]) lines = filter(lambda x: len(x) > 0, lines) headers = [x.split(':', 1) for x in lines] headers = dict(map(lambda x: (x[0].lower(), x[1]), headers)) return cmd, headers def get_ip_address(ifname): """ determine the IP address by interface name http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/439094 (c) Paul Cannon Uses the Linux SIOCGIFADDR ioctl to find the IP address associated with a network interface, given the name of that interface, e.g. "eth0". The address is returned as a string containing a dotted quad. Updated to work on BSD. OpenBSD and OSX share the same value for SIOCGIFADDR, and its likely that other BSDs do too. Updated to work on Windows, using the optional Python module netifaces http://alastairs-place.net/netifaces/ Thx Lawrence for that patch! """ if have_netifaces: if ifname in netifaces.interfaces(): iface = netifaces.ifaddresses(ifname) ifaceadr = iface[netifaces.AF_INET] # we now have a list of address dictionaries, there may be multiple addresses bound return ifaceadr[0]['addr'] import sys if sys.platform in ('win32','sunos5'): return '127.0.0.1' from os import uname import socket import fcntl import struct system_type = uname()[0] if system_type == "Linux": SIOCGIFADDR = 0x8915 else: SIOCGIFADDR = 0xc0206921 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: return socket.inet_ntoa(fcntl.ioctl( s.fileno(), SIOCGIFADDR, struct.pack('256s', ifname[:15]) )[20:24]) except: return '127.0.0.1' def get_host_address(): """ try to get determine the interface used for the default route, as this is most likely the interface we should bind to (on a single homed host!) """ import sys if sys.platform == 'win32': if have_netifaces: interfaces = netifaces.interfaces() if len(interfaces): return get_ip_address(interfaces[0]) # on windows assume first interface is primary else: try: route_file = '/proc/net/route' route = open(route_file) if(route): tmp = route.readline() #skip first line while (tmp != ''): tmp = route.readline() l = tmp.split('\t') if (len(l) > 2): if l[1] == '00000000': #default route... route.close() return get_ip_address(l[0]) except IOError, msg: """ fallback to parsing the output of netstat """ from twisted.internet import utils def result(r): from os import uname (osname,_, _, _,_) = uname() osname = osname.lower() lines = r.split('\n') for l in lines: l = l.strip(' \r\n') parts = [x.strip() for x in l.split(' ') if len(x) > 0] if parts[0] in ('0.0.0.0','default'): if osname[:6] == 'darwin': return get_ip_address(parts[5]) else: return get_ip_address(parts[-1]) return '127.0.0.1' def fail(f): return '127.0.0.1' d = utils.getProcessOutput('netstat', ['-rn']) d.addCallback(result) d.addErrback(fail) return d except Exception, msg: import traceback traceback.print_exc() """ return localhost if we haven't found anything """ return '127.0.0.1' def de_chunk_payload(response): try: import cStringIO as StringIO except ImportError: import StringIO """ This method takes a chunked HTTP data object and unchunks it.""" newresponse = StringIO.StringIO() # chunked encoding consists of a bunch of lines with # a length in hex followed by a data chunk and a CRLF pair. response = StringIO.StringIO(response) def read_chunk_length(): line = response.readline() try: len = int(line.strip(),16) except ValueError: len = 0 return len len = read_chunk_length() while (len > 0): newresponse.write(response.read(len)) line = response.readline() # after chunk and before next chunk length len = read_chunk_length() return newresponse.getvalue() class Request(server.Request): def process(self): "Process a request." # get site from channel self.site = self.channel.site # set various default headers self.setHeader('server', SERVER_ID) self.setHeader('date', http.datetimeToString()) self.setHeader('content-type', "text/html") # Resource Identification url = self.path #remove trailing "/", if ever url = url.rstrip('/') scheme, netloc, path, query, fragment = urlsplit(url) self.prepath = [] if path == "": self.postpath = [] else: self.postpath = map(unquote, path[1:].split('/')) try: def deferred_rendering(r): self.render(r) resrc = self.site.getResourceFor(self) if resrc is None: self.setResponseCode(http.NOT_FOUND, "Error: No resource for path %s" % path) self.finish() elif isinstance(resrc, defer.Deferred): resrc.addCallback(deferred_rendering) resrc.addErrback(self.processingFailed) else: self.render(resrc) except: self.processingFailed(failure.Failure()) class Site(server.Site): noisy = False requestFactory = Request def startFactory(self): pass #http._logDateTimeStart() class ProxyClient(http.HTTPClient): """Used by ProxyClientFactory to implement a simple web proxy.""" def __init__(self, command, rest, version, headers, data, father): self.father = father self.command = command self.rest = rest if headers.has_key("proxy-connection"): del headers["proxy-connection"] #headers["connection"] = "close" self.headers = headers #print "command", command #print "rest", rest #print "headers", headers self.data = data self.send_data = 0 def connectionMade(self): self.sendCommand(self.command, self.rest) for header, value in self.headers.items(): self.sendHeader(header, value) self.endHeaders() self.transport.write(self.data) def handleStatus(self, version, code, message): if message: # Add a whitespace to message, this allows empty messages # transparently message = " %s" % (message,) if version == 'ICY': version = 'HTTP/1.1' #print "ProxyClient handleStatus", version, code, message self.father.transport.write("%s %s %s\r\n" % (version, code, message)) def handleHeader(self, key, value): #print "ProxyClient handleHeader", key, value if not key.startswith('icy-'): #print "ProxyClient handleHeader", key, value self.father.transport.write("%s: %s\r\n" % (key, value)) def handleEndHeaders(self): #self.father.transport.write("%s: %s\r\n" % ( 'Keep-Alive', '')) #self.father.transport.write("%s: %s\r\n" % ( 'Accept-Ranges', 'bytes')) #self.father.transport.write("%s: %s\r\n" % ( 'Content-Length', '2000000')) #self.father.transport.write("%s: %s\r\n" % ( 'Date', 'Mon, 26 Nov 2007 11:04:12 GMT')) #self.father.transport.write("%s: %s\r\n" % ( 'Last-Modified', 'Sun, 25 Nov 2007 23:19:51 GMT')) ##self.father.transport.write("%s: %s\r\n" % ( 'Server', 'Apache/2.0.52 (Red Hat)')) self.father.transport.write("\r\n") def handleResponsePart(self, buffer): #print "ProxyClient handleResponsePart", len(buffer), self.father.chunked self.send_data += len(buffer) self.father.write(buffer) def handleResponseEnd(self): #print "handleResponseEnd", self.send_data self.transport.loseConnection() self.father.channel.transport.loseConnection() class ProxyClientFactory(protocol.ClientFactory): """ Used by ProxyRequest to implement a simple web proxy. """ protocol = proxy.ProxyClient def __init__(self, command, rest, version, headers, data, father): self.father = father self.command = command self.rest = rest self.headers = headers self.data = data self.version = version def buildProtocol(self, addr): return self.protocol(self.command, self.rest, self.version, self.headers, self.data, self.father) def clientConnectionFailed(self, connector, reason): self.father.transport.write("HTTP/1.0 501 Gateway error\r\n") self.father.transport.write("Content-Type: text/html\r\n") self.father.transport.write("\r\n") self.father.transport.write('''

    Could not connect

    ''') self.father.transport.loseConnection() class ReverseProxyResource(proxy.ReverseProxyResource): """ Resource that renders the results gotten from another server Put this resource in the tree to cause everything below it to be relayed to a different server. @ivar proxyClientFactoryClass: a proxy client factory class, used to create new connections. @type proxyClientFactoryClass: L{ClientFactory} @ivar reactor: the reactor used to create connections. @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP} """ proxyClientFactoryClass = ProxyClientFactory def __init__(self, host, port, path, reactor=reactor): """ @param host: the host of the web server to proxy. @type host: C{str} @param port: the port of the web server to proxy. @type port: C{port} @param path: the base path to fetch data from. Note that you shouldn't put any trailing slashes in it, it will be added automatically in request. For example, if you put B{/foo}, a request on B{/bar} will be proxied to B{/foo/bar}. @type path: C{str} """ resource.Resource.__init__(self) self.host = host self.port = port self.path = path self.qs = '' self.reactor = reactor def getChild(self, path, request): return ReverseProxyResource( self.host, self.port, self.path + '/' + path) def render(self, request): """ Render a request by forwarding it to the proxied server. """ # RFC 2616 tells us that we can omit the port if it's the default port, # but we have to provide it otherwise if self.port == 80: request.received_headers['host'] = self.host else: request.received_headers['host'] = "%s:%d" % (self.host, self.port) request.content.seek(0, 0) qs = urlparse.urlparse(request.uri)[4] if qs == '': qs = self.qs if qs: rest = self.path + '?' + qs else: rest = self.path clientFactory = self.proxyClientFactoryClass( request.method, rest, request.clientproto, request.getAllHeaders(), request.content.read(), request) self.reactor.connectTCP(self.host, self.port, clientFactory) return server.NOT_DONE_YET def resetTarget(self,host,port,path,qs=''): self.host = host self.port = port self.path = path self.qs = qs class ReverseProxyUriResource(ReverseProxyResource): uri = None def __init__(self, uri, reactor=reactor): self.uri = uri _,host_port,path,params,_ = urlsplit(uri) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 if path =='': path = '/' if params == '': rest = path else: rest = '?'.join((path, params)) ReverseProxyResource.__init__(self, host, port, rest, reactor) def resetUri (self, uri): self.uri = uri _,host_port,path,params,_ = urlsplit(uri) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 self.resetTarget(host, port, path, params) class myHTTPPageGetter(client.HTTPPageGetter): followRedirect = True def connectionMade(self): method = getattr(self, 'method', 'GET') #print "myHTTPPageGetter", method, self.factory.path self.sendCommand(method, self.factory.path) self.sendHeader('Host', self.factory.headers.get("host", self.factory.host)) self.sendHeader('User-Agent', self.factory.agent) if self.factory.cookies: l=[] for cookie, cookval in self.factory.cookies.items(): l.append('%s=%s' % (cookie, cookval)) self.sendHeader('Cookie', '; '.join(l)) data = getattr(self.factory, 'postdata', None) if data is not None: self.sendHeader("Content-Length", str(len(data))) for (key, value) in self.factory.headers.items(): if key.lower() != "content-length": # we calculated it on our own self.sendHeader(key, value) self.endHeaders() self.headers = {} if data is not None: self.transport.write(data) def handleResponse(self, response): if self.quietLoss: return if self.failed: self.factory.noPage( failure.Failure( error.Error( self.status, self.message, response))) elif self.factory.method != 'HEAD' and self.length != None and self.length != 0: self.factory.noPage(failure.Failure( client.PartialDownloadError(self.status, self.message, response))) else: if(self.headers.has_key('transfer-encoding') and self.headers['transfer-encoding'][0].lower() == 'chunked'): self.factory.page(de_chunk_payload(response)) else: self.factory.page(response) # server might be stupid and not close connection. admittedly # the fact we do only one request per connection is also # stupid... self.quietLoss = 1 self.transport.loseConnection() class HeaderAwareHTTPClientFactory(client.HTTPClientFactory): protocol = myHTTPPageGetter noisy = False def __init__(self, url, method='GET', postdata=None, headers=None, agent="Twisted PageGetter", timeout=0, cookies=None, followRedirect=True, redirectLimit=20): self.followRedirect = followRedirect self.redirectLimit = redirectLimit self._redirectCount = 0 self.timeout = timeout self.agent = agent if cookies is None: cookies = {} self.cookies = cookies if headers is not None: self.headers = InsensitiveDict(headers) else: self.headers = InsensitiveDict() if postdata is not None: self.headers.setdefault('Content-Length', len(postdata)) # just in case a broken http/1.1 decides to keep connection alive self.headers.setdefault("connection", "close") self.postdata = postdata self.method = method self.setURL(url) self.waiting = 1 self.deferred = defer.Deferred() self.response_headers = None def buildProtocol(self, addr): p = protocol.ClientFactory.buildProtocol(self, addr) p.method = self.method p.followRedirect = self.followRedirect if self.timeout: timeoutCall = reactor.callLater(self.timeout, p.timeout) self.deferred.addBoth(self._cancelTimeout, timeoutCall) return p def page(self, page): if self.waiting: self.waiting = 0 self.deferred.callback((page, self.response_headers)) class HeaderAwareHTTPDownloader(client.HTTPDownloader): def gotHeaders(self, headers): self.value = headers if self.requestedPartial: contentRange = headers.get("content-range", None) if not contentRange: # server doesn't support partial requests, oh well self.requestedPartial = 0 return start, end, realLength = http.parseContentRange(contentRange[0]) if start != self.requestedPartial: # server is acting wierdly self.requestedPartial = 0 def getPage(url, contextFactory=None, *args, **kwargs): """Download a web page as a string. Download a page. Return a deferred, which will callback with a page (as a string) or errback with a description of the error. See HTTPClientFactory to see what extra args can be passed. """ scheme, host, port, path = client._parse(url) factory = HeaderAwareHTTPClientFactory(url, *args, **kwargs) if scheme == 'https': from twisted.internet import ssl if contextFactory is None: contextFactory = ssl.ClientContextFactory() reactor.connectSSL(host, port, factory, contextFactory) else: reactor.connectTCP(host, port, factory) return factory.deferred def downloadPage(url, file, contextFactory=None, *args, **kwargs): """Download a web page to a file. @param file: path to file on filesystem, or file-like object. See HTTPDownloader to see what extra args can be passed. """ scheme, host, port, path = client._parse(url) factory = HeaderAwareHTTPDownloader(url, file, *args, **kwargs) factory.noisy = False if scheme == 'https': from twisted.internet import ssl if contextFactory is None: contextFactory = ssl.ClientContextFactory() reactor.connectSSL(host, port, factory, contextFactory) else: reactor.connectTCP(host, port, factory) return factory.deferred class StaticFile(static.File): """ taken from twisted.web.static and modified accordingly to the patch by John-Mark Gurney http://resnet.uoregon.edu/~gurney_j/jmpc/dist/twisted.web.static.patch """ def render(self, request): #print "" #print "StaticFile", request #print "StaticFile in", request.received_headers """You know what you doing.""" self.restat() if self.type is None: self.type, self.encoding = static.getTypeAndEncoding(self.basename(), self.contentTypes, self.contentEncodings, self.defaultType) if not self.exists(): return self.childNotFound.render(request) if self.isdir(): return self.redirect(request) #for content-length fsize = size = self.getFileSize() request.setHeader('accept-ranges','bytes') if self.type: request.setHeader('content-type', self.type) if self.encoding: request.setHeader('content-encoding', self.encoding) try: f = self.openForReading() except IOError, e: import errno if e[0] == errno.EACCES: return error.ForbiddenResource().render(request) else: raise if request.setLastModified(self.getmtime()) is http.CACHED: return '' trans = True range = request.getHeader('range') #print "StaticFile", range tsize = size if range is not None: # This is a request for partial data... bytesrange = range.split('=') assert bytesrange[0] == 'bytes',\ "Syntactically invalid http range header!" start, end = bytesrange[1].split('-', 1) if start: f.seek(int(start)) if end: end = int(end) else: end = size - 1 else: lastbytes = int(end) if size < lastbytes: lastbytes = size start = size - lastbytes f.seek(start) fsize = lastbytes end = size - 1 size = end + 1 fsize = end - int(start) + 1 # start is the byte offset to begin, and end is the byte offset # to end.. fsize is size to send, tsize is the real size of # the file, and size is the byte position to stop sending. if fsize <= 0: request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE) fsize = tsize trans = False else: request.setResponseCode(http.PARTIAL_CONTENT) request.setHeader('content-range',"bytes %s-%s/%s " % ( str(start), str(end), str(tsize))) #print "StaticFile", start, end, tsize request.setHeader('content-length', str(fsize)) if request.method == 'HEAD' or trans == False: # pretend we're a HEAD request, so content-length # won't be overwritten. #print "HEAD request" request.method = 'HEAD' return '' #print "StaticFile out", request.headers, request.code # return data # size is the byte position to stop sending, not how many bytes to send static.FileTransfer(f, size, request) # and make sure the connection doesn't get closed return server.NOT_DONE_YET class BufferFile(static.File): """ taken from twisted.web.static and modified accordingly to the patch by John-Mark Gurney http://resnet.uoregon.edu/~gurney_j/jmpc/dist/twisted.web.static.patch """ def __init__(self, path, target_size=0, *args): static.File.__init__(self, path, *args) self.target_size = target_size self.upnp_retry = None def render(self, request): #print "" #print "BufferFile", request # FIXME detect when request is REALLY finished if request is None or request.finished : print "No request to render!" return '' """You know what you doing.""" self.restat() if self.type is None: self.type, self.encoding = static.getTypeAndEncoding(self.basename(), self.contentTypes, self.contentEncodings, self.defaultType) if not self.exists(): return self.childNotFound.render(request) if self.isdir(): return self.redirect(request) #for content-length if (self.target_size > 0): fsize = size = int(self.target_size) else: fsize = size = int(self.getFileSize()) #print fsize if size == int(self.getFileSize()): request.setHeader('accept-ranges','bytes') if self.type: request.setHeader('content-type', self.type) if self.encoding: request.setHeader('content-encoding', self.encoding) try: f = self.openForReading() except IOError, e: import errno if e[0] == errno.EACCES: return error.ForbiddenResource().render(request) else: raise if request.setLastModified(self.getmtime()) is http.CACHED: return '' trans = True range = request.getHeader('range') #print "StaticFile", range tsize = size if range is not None: # This is a request for partial data... bytesrange = range.split('=') assert bytesrange[0] == 'bytes',\ "Syntactically invalid http range header!" start, end = bytesrange[1].split('-', 1) if start: start = int(start) # Are we requesting something beyond the current size of the file? if (start >= self.getFileSize()): # Retry later! print bytesrange print "Requesting data beyond current scope -> postpone rendering!" self.upnp_retry = reactor.callLater(1.0, self.render, request) return server.NOT_DONE_YET f.seek(start) if end: #print ":%s" % end end = int(end) else: end = size - 1 else: lastbytes = int(end) if size < lastbytes: lastbytes = size start = size - lastbytes f.seek(start) fsize = lastbytes end = size - 1 size = end + 1 fsize = end - int(start) + 1 # start is the byte offset to begin, and end is the byte offset # to end.. fsize is size to send, tsize is the real size of # the file, and size is the byte position to stop sending. if fsize <= 0: request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE) fsize = tsize trans = False else: request.setResponseCode(http.PARTIAL_CONTENT) request.setHeader('content-range',"bytes %s-%s/%s " % ( str(start), str(end), str(tsize))) #print "StaticFile", start, end, tsize request.setHeader('content-length', str(fsize)) if request.method == 'HEAD' or trans == False: # pretend we're a HEAD request, so content-length # won't be overwritten. request.method = 'HEAD' return '' #print "StaticFile out", request.headers, request.code # return data # size is the byte position to stop sending, not how many bytes to send BufferFileTransfer(f, size - f.tell(), request) # and make sure the connection doesn't get closed return server.NOT_DONE_YET class BufferFileTransfer(object): """ A class to represent the transfer of a file over the network. """ request = None def __init__(self, file, size, request): self.file = file self.size = size self.request = request self.written = self.file.tell() request.registerProducer(self, 0) def resumeProducing(self): #print "resumeProducing", self.request,self.size,self.written if not self.request: return data = self.file.read(min(abstract.FileDescriptor.bufferSize, self.size - self.written)) if data: self.written += len(data) # this .write will spin the reactor, calling .doWrite and then # .resumeProducing again, so be prepared for a re-entrant call self.request.write(data) if self.request and self.file.tell() == self.size: self.request.unregisterProducer() self.request.finish() self.request = None def pauseProducing(self): pass def stopProducing(self): #print "stopProducing",self.request self.request.unregisterProducer() self.file.close() self.request.finish() self.request = None from datetime import datetime, tzinfo, timedelta import random class CET(tzinfo): def __init__(self): self.__offset = timedelta(minutes=60) self.__name = 'CET' def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self,dt): return timedelta(0) class CEST(tzinfo): def __init__(self): self.__offset = timedelta(minutes=120) self.__name = 'CEST' def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self,dt): return timedelta(0) bdates = [ datetime(1997,2,28,17,20,tzinfo=CET()), # Sebastian Oliver datetime(1999,9,19,4,12,tzinfo=CEST()), # Patrick Niklas datetime(2000,9,23,4,8,tzinfo=CEST()), # Saskia Alexa datetime(2003,7,23,1,18,tzinfo=CEST()), # Mara Sophie # you are the best! ] def datefaker(): return random.choice(bdates) Coherence-0.6.6.2/coherence/upnp/core/variable.py0000644000175000017500000002106111317660735017711 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz import time from sets import Set from coherence.upnp.core import utils try: #FIXME: # there is some circular import, service imports variable, variable imports service # how is this done properly? # from coherence.upnp.core import service except ImportError: import service from coherence import log import coherence.extern.louie as louie class StateVariable(log.Loggable): logCategory = 'variable' def __init__(self, upnp_service, name, implementation, instance, send_events, data_type, allowed_values): self.service = upnp_service self.name = name self.implementation = implementation self.instance = instance self.send_events = utils.means_true(send_events) self.never_evented = False self.data_type = data_type self.allowed_values = allowed_values if self.allowed_values == None: self.allowed_values = [] self.has_vendor_values = False self.allowed_value_range = None self.dependant_variable = None self.default_value = '' self.old_value = '' self.value = '' self.last_time_touched = None self._callbacks = [] if isinstance( self.service, service.ServiceServer): self.moderated = self.service.is_variable_moderated(name) self.updated = False def as_tuples(self): r = [] r.append(('Name',self.name)) if self.send_events: r.append(('Evented','yes')) else: r.append(('Evented','no')) r.append(('Data Type',self.data_type)) r.append(('Default Value',self.default_value)) r.append(('Current Value',unicode(self.value))) if(self.allowed_values != None and len(self.allowed_values) > 0): r.append(('Allowed Values',','.join(self.allowed_values))) return r def set_default_value(self, value): self.update(value) self.default_value = self.value def set_allowed_values(self, values): if not isinstance(values,(list,tuple)): values = [values] self.allowed_values = values def set_allowed_value_range(self, **kwargs): self.allowed_value_range = kwargs def get_allowed_values(self): return self.allowed_values def set_never_evented(self, value): self.never_evented = utils.means_true(value) def update(self, value): self.info("variable check for update", self.name, value, self.service) if not isinstance( self.service, service.Service): if self.name == 'ContainerUpdateIDs': old_value = self.value if self.updated == True: if isinstance( value, tuple): v = old_value.split(',') i = 0 while i < len(v): if v[i] == str(value[0]): del v[i:i+2] old_value = ','.join(v) break; i += 2 if len(old_value): new_value = old_value + ',' + str(value[0]) + ',' + str(value[1]) else: new_value = str(value[0]) + ',' + str(value[1]) else: if len(old_value): new_value = str(old_value) + ',' + str(value) else: new_value = str(value) else: if isinstance( value, tuple): new_value = str(value[0]) + ',' + str(value[1]) else: new_value = value else: if self.data_type == 'string': if isinstance(value,basestring): value = value.split(',') if(isinstance(value,tuple) or isinstance(value,Set)): value = list(value) if not isinstance(value,list): value = [value] new_value = [] for v in value: if type(v) == unicode: v = v.encode('utf-8') else: v = str(v) if len(self.allowed_values): if self.has_vendor_values == True: new_value.append(v) elif v.upper() in [x.upper() for x in self.allowed_values]: new_value.append(v) else: self.warning("Variable %s update, %r value %s doesn't fit in %r" % (self.name, self.has_vendor_values, v, self.allowed_values)) new_value = 'Coherence_Value_Error' else: new_value.append(v) new_value = ','.join(new_value) elif self.data_type == 'boolean': new_value = utils.generalise_boolean(value) elif self.data_type == 'bin.base64': new_value = value else: new_value = int(value) else: if self.data_type == 'string': if type(value) == unicode: value = value.encode('utf-8') else: value = str(value) if len(self.allowed_values): if self.has_vendor_values == True: new_value = value elif value.upper() in [v.upper() for v in self.allowed_values]: new_value = value else: self.warning("Variable %s NOT updated, value %s doesn't fit" % (self.name, value)) new_value = 'Coherence_Value_Error' else: new_value = value elif self.data_type == 'boolean': new_value = utils.generalise_boolean(value) elif self.data_type == 'bin.base64': new_value = value else: try: new_value = int(value) except ValueError: new_value = 'Coherence_Value_Error' if new_value == 'Coherence_Value_Error': return if new_value == self.value: self.info("variable NOT updated, no value change", self.name, self.value) return self.old_value = self.value self.value = new_value self.last_time_touched = time.time() #print "UPDATED %s %r %r %r %r %r" % (self.name,self.service,isinstance( self.service, service.Service),self.instance,self.value,self._callbacks) self.notify() if isinstance( self.service, service.Service): #self.notify() pass else: self.updated = True if self.service.last_change != None: self.service.last_change.updated = True self.info("variable updated", self.name, self.value) def subscribe(self, callback): self._callbacks.append(callback) callback( self) def notify(self): if self.name.startswith('A_ARG_TYPE_'): return self.info("Variable %s sends notify about new value >%r<" %(self.name, self.value)) #if self.old_value == '': # return louie.send(signal='Coherence.UPnP.StateVariable.%s.changed' % self.name, sender=self.service, variable=self) louie.send(signal='Coherence.UPnP.StateVariable.changed',sender=self.service, variable=self) #print "CALLBACKS %s %r %r" % (self.name,self.instance,self._callbacks) for callback in self._callbacks: callback(self) def __repr__(self): return "Variable: %s, %s, %d, %s, %s, %s, %s, %s, %s, %s" % \ (self.name, str(self.service), self.instance, self.implementation, self.data_type, str(self.allowed_values), str(self.default_value), str(self.old_value), str(self.value), str(self.send_events)) Coherence-0.6.6.2/coherence/upnp/core/test/0000755000000000000000000000000011317673117017107 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/core/test/__init__.py0000644000175000017500000000005211317660734020636 0ustar devdev""" Tests for L{coherence.upnp.core}. """ Coherence-0.6.6.2/coherence/upnp/core/test/test_didl.py0000644000175000017500000000715011317660734021060 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ Test cases for L{upnp.core.DIDLLite} """ from copy import copy from twisted.trial import unittest from coherence.upnp.core import DIDLLite didl_fragment = """ 12 object.container.album.musicAlbum 1997-02-28T17:20:00+01:00 http://192.168.1.1:30020/776dec17-1ce1-4c87-841e-cac61a14a2e0/1161?cover.jpg Herby Sängermeister """ test_didl_fragment = """ New Track object.item.audioItem.musicTrack """ class TestDIDLLite(unittest.TestCase): def test_DIDLElement_class_detect(self): """ tests class creation from an XML DIDLLite fragment, expects a MusicAlbum container in return """ didl_element = DIDLLite.DIDLElement.fromString(didl_fragment) items = didl_element.getItems() self.assertEqual(len(items),1) self.assertTrue(isinstance(items[0],DIDLLite.MusicAlbum)) def test_DIDLElement_class_2_detect(self): """ tests class creation from an XML DIDLLite fragment, expects a MusicTrack item in return """ didl_element = DIDLLite.DIDLElement.fromString(test_didl_fragment) items = didl_element.getItems() self.assertEqual(len(items),1) self.assertTrue(isinstance(items[0],DIDLLite.MusicTrack)) def test_DIDLElement_class_fallback_1(self): """ tests class fallback creation from an XML DIDLLite fragment with an unknown UPnP class identifier, expects an Album container in return """ wrong_didl_fragment = copy(didl_fragment) wrong_didl_fragment = wrong_didl_fragment.replace('object.container.album.musicAlbum', 'object.container.album.videoAlbum') didl_element = DIDLLite.DIDLElement.fromString(wrong_didl_fragment) items = didl_element.getItems() self.assertEqual(len(items),1) self.assertTrue(isinstance(items[0],DIDLLite.Album)) def test_DIDLElement_class_fallback_2(self): """ tests class fallback creation from an XML DIDLLite fragment with an unknown UPnP class identifier, expects an Exception.AttributeError """ wrong_didl_fragment = copy(didl_fragment) wrong_didl_fragment = wrong_didl_fragment.replace('object.container.album.musicAlbum', 'object.wrongcontainer.wrongalbum.videoAlbum') e = None try: didl_element = DIDLLite.DIDLElement.fromString(wrong_didl_fragment) except AttributeError: return self.assert_(False,"DIDLElement didn't return None from a totally wrong UPnP class identifier") Coherence-0.6.6.2/coherence/upnp/core/test/test_utils.py0000644000175000017500000001112211317660734021276 0ustar devdev# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008, Frank Scholz """ Test cases for L{upnp.core.utils} """ from twisted.trial import unittest from coherence.upnp.core.utils import * # This data is joined using CRLF pairs. testChunkedData = ['200', ' ', '', ' ', ' 1 ', ' 0 ', ' ', ' ', ' urn:schemas-upnp-org:device:MediaRenderer:1 ', ' DMA201 ', ' ', ' ', ' DMA201 ', ' DMA ', ' 201 ', ' ', ' 0', '200', '00000000001 ', ' uuid:BE1C49F2-572D-3617-8F4C-BB1DEC3954FD ', ' ', ' ', ' ', ' urn:schemas-upnp-org:service:ConnectionManager:1', ' urn:upnp-org:serviceId:ConnectionManager', ' http://10.63.1.113:4444/CMSControl', ' http://10.63.1.113:4445/CMSEvent', ' /upnpdev.cgi?file=/ConnectionManager.xml', ' ', ' ', ' urn:schemas-upnp-org:service:AVTransport:1', ' urn:upnp-org:serviceId:AVTransport', ' http://10.63.1.113:4444/AVTControl', ' http://10.63.1.113:4445/AVTEvent', ' /upnpdev.cgi?file=/AVTransport.xml', ' ', ' ', ' urn:schemas-upnp-org:service:RenderingControl:1', ' urn:upnp-org:serviceId:RenderingControl', ' http://10.63.1.113:4444/RCSControl', ' http://10.63.1.113:4445/RCSEvent', ' /upnpdev.cgi?file=/RenderingControl.xml', ' ', ' ', ' ', '' '', '0', ''] testChunkedDataResult = [' ', '', ' ', ' 1 ', ' 0 ', ' ', ' ', ' urn:schemas-upnp-org:device:MediaRenderer:1 ', ' DMA201 ', ' ', ' ', ' DMA201 ', ' DMA ', ' 201 ', ' ', ' 000000000001 ', ' uuid:BE1C49F2-572D-3617-8F4C-BB1DEC3954FD ', ' ', ' ', ' ', ' urn:schemas-upnp-org:service:ConnectionManager:1', ' urn:upnp-org:serviceId:ConnectionManager', ' http://10.63.1.113:4444/CMSControl', ' http://10.63.1.113:4445/CMSEvent', ' /upnpdev.cgi?file=/ConnectionManager.xml', ' ', ' ', ' urn:schemas-upnp-org:service:AVTransport:1', ' urn:upnp-org:serviceId:AVTransport', ' http://10.63.1.113:4444/AVTControl', ' http://10.63.1.113:4445/AVTEvent', ' /upnpdev.cgi?file=/AVTransport.xml', ' ', ' ', ' urn:schemas-upnp-org:service:RenderingControl:1', ' urn:upnp-org:serviceId:RenderingControl', ' http://10.63.1.113:4444/RCSControl', ' http://10.63.1.113:4445/RCSEvent', ' /upnpdev.cgi?file=/RenderingControl.xml', ' ', ' ', ' ', '', '' ] class TestUpnpUtils(unittest.TestCase): def test_chunked_data(self): """ tests proper reassembling of a chunked http-response based on a test and data provided by Lawrence """ testData = '\r\n'.join(testChunkedData) newData = de_chunk_payload(testData) # see whether we can parse the result self.assertEqual(newData, '\r\n'.join( testChunkedDataResult)) # $Id:$ Coherence-0.6.6.2/coherence/upnp/core/dlna.py0000644000175000017500000000075311317660735017047 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007 - Frank Scholz """ DLNA decorator functions """ def AudioItem(func): def add(*args,**kwargs): result = func(*args, **kwargs) e = result.find('upnp:albumArtURI') if e != None: e.attrib['xmlns:dlna'] = 'urn:schemas-dlna-org:metadata-1-0' e.attrib['dlna:profileID'] = 'JPEG_TN' return result return add Coherence-0.6.6.2/coherence/upnp/core/msearch.py0000644000175000017500000000530111317660735017545 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz import socket import time from twisted.internet.protocol import DatagramProtocol from twisted.internet import reactor from twisted.internet import task from coherence.upnp.core import utils import coherence.extern.louie as louie SSDP_PORT = 1900 SSDP_ADDR = '239.255.255.250' from coherence import log class MSearch(DatagramProtocol, log.Loggable): logCategory = 'msearch' def __init__(self, ssdp_server, test=False): self.ssdp_server = ssdp_server if test == False: self.port = reactor.listenUDP(0, self) self.double_discover_loop = task.LoopingCall(self.double_discover) self.double_discover_loop.start(120.0) def datagramReceived(self, data, (host, port)): cmd, headers = utils.parse_http_response(data) self.info('datagramReceived from %s:%d, protocol %s code %s' % (host, port, cmd[0], cmd[1])) if cmd[0].startswith('HTTP/1.') and cmd[1] == '200': self.msg('for %r', headers['usn']) if not self.ssdp_server.isKnown(headers['usn']): self.info('register as remote %s, %s, %s' % (headers['usn'], headers['st'], headers['location'])) self.ssdp_server.register('remote', headers['usn'], headers['st'], headers['location'], headers['server'], headers['cache-control'], host=host) else: self.ssdp_server.known[headers['usn']]['last-seen'] = time.time() self.debug('updating last-seen for %r' % headers['usn']) # make raw data available # send out the signal after we had a chance to register the device louie.send('UPnP.SSDP.datagram_received', None, data, host, port) def double_discover(self): " Because it's worth it (with UDP's reliability) " self.info('send out discovery for ssdp:all') self.discover() self.discover() def discover(self): req = [ 'M-SEARCH * HTTP/1.1', 'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT), 'MAN: "ssdp:discover"', 'MX: 5', 'ST: ssdp:all', '',''] req = '\r\n'.join(req) try: self.transport.write(req, (SSDP_ADDR, SSDP_PORT)) except socket.error, msg: self.info("failure sending out the discovery message: %r" % msg) Coherence-0.6.6.2/coherence/upnp/core/DIDLLite.py0000644000175000017500000011203411317664504017455 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2005, Tim Potter # Copyright 2006, Frank Scholz """ TODO: - use more XPath expressions in fromElement() methods """ import os import string import urllib from datetime import datetime DC_NS = 'http://purl.org/dc/elements/1.1/' UPNP_NS = 'urn:schemas-upnp-org:metadata-1-0/upnp/' my_namespaces = { DC_NS: 'dc', UPNP_NS: 'upnp' } from coherence.extern.et import ET, namespace_map_update, ElementInterface namespace_map_update(my_namespaces) from coherence.upnp.core import utils from coherence.upnp.core import dlna from coherence import log def qname(tag,ns=''): if len(ns) == 0: return tag return "{%s}%s" % (ns,tag) def is_audio(mimetype): """ checks for type audio, expects a mimetype or an UPnP protocolInfo """ test = mimetype.split(':') if len(test) == 4: mimetype = test[2] if mimetype == 'application/ogg': return True if mimetype.startswith('audio/'): return True return False def is_video(mimetype): """ checks for type video, expects a mimetype or an UPnP protocolInfo """ test = mimetype.split(':') if len(test) == 4: mimetype = test[2] if mimetype.startswith('video/'): return True return False class Resources(list): """ a list of resources, always sorted after an append """ def __init__(self, *args, **kwargs): list.__init__(self, *args, **kwargs) self.sort(cmp=self.p_sort) def append(self, value): list.append(self,value) self.sort(cmp=self.p_sort) def p_sort(self,x,y): """ we want the following order http-get is always at the beginning rtsp-rtp-udp the second anything else after that """ if x.protocolInfo == None: return 1 if y.protocolInfo == None: return -1 x_protocol = x.protocolInfo.split(':')[0] y_protocol = y.protocolInfo.split(':')[0] x_protocol = x_protocol.lower() y_protocol = y_protocol.lower() if( x_protocol == y_protocol): return 0 if(x_protocol == 'http-get'): return -1 if(x_protocol == 'rtsp-rtp-udp' and y_protocol == 'http-get'): return 1 if(x_protocol == 'rtsp-rtp-udp' and y_protocol != 'http-get'): return -1 return 1 def get_matching(self, local_protocol_infos, protocol_type = None): result = [] if not isinstance(local_protocol_infos, list): local_protocol_infos = [local_protocol_infos] for res in self: if res.importUri != None: continue #print "res", res.protocolInfo, res.data remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') #print "remote", remote_protocol,remote_network,remote_content_format if(protocol_type is not None and remote_protocol.lower() != protocol_type.lower()): continue for protocol_info in local_protocol_infos: local_protocol,local_network,local_content_format,_ = protocol_info.split(':') #print "local", local_protocol,local_network,local_content_format if((remote_protocol == local_protocol or remote_protocol == '*' or local_protocol == '*') and (remote_network == local_network or remote_network == '*' or local_network == '*') and (remote_content_format.startswith(local_content_format) or remote_content_format == '*' or local_content_format == '*')): #print result, res result.append(res) return result def classChooser(mimetype, sub=None): if mimetype == 'root': return Container if mimetype == 'item': return Item if mimetype == 'directory': if sub == 'music': return MusicAlbum return Container else: if string.find (mimetype,'image/') == 0: return Photo if string.find (mimetype,'audio/') == 0: if sub == 'music': # FIXME: this is stupid return MusicTrack return AudioItem if string.find (mimetype,'video/') == 0: return VideoItem if mimetype == 'application/ogg': if sub == 'music': # FIXME: this is stupid return MusicTrack return AudioItem if mimetype == 'application/x-flac': if sub == 'music': # FIXME: this is stupid return MusicTrack return AudioItem return None simple_dlna_tags = ['DLNA.ORG_OP=01', # operations parameter 'DLNA.ORG_PS=1', # play speed parameter 'DLNA.ORG_CI=0', # transcoded parameter 'DLNA.ORG_FLAGS=01100000000000000000000000000000'] def build_dlna_additional_info(content_format,does_playcontainer=False): additional_info = ['*'] if content_format == 'audio/mpeg': additional_info = ['DLNA.ORG_PN=MP3']+simple_dlna_tags if content_format == 'audio/ms-wma': additional_info = ['DLNA.ORG_PN=WMABASE']+simple_dlna_tags if content_format == 'image/jpeg': dlna_tags = simple_dlna_tags[:] dlna_tags[3] = 'DLNA.ORG_FLAGS=00900000000000000000000000000000' additional_info = ['DLNA.ORG_PN=JPEG_LRG']+dlna_tags if content_format == 'image/png': dlna_tags = simple_dlna_tags[:] dlna_tags[3] = 'DLNA.ORG_FLAGS=00900000000000000000000000000000' additional_info = ['DLNA.ORG_PN=PNG_LRG']+dlna_tags if content_format == 'video/mpeg': additional_info = ['DLNA.ORG_PN=MPEG_PS_PAL']+simple_dlna_tags if content_format == 'video/mpegts': additional_info = ['DLNA.ORG_PN=MPEG_TS_PAL']+simple_dlna_tags content_format = 'video/mpeg' if content_format in ['video/mp4','video/x-m4a']: additional_info = ['DLNA.ORG_PN=AVC_TS_BL_CIF15_AAC']+simple_dlna_tags if content_format in ['video/x-msvideo','video/avi','video/divx']: #additional_info = ';'.join(['DLNA.ORG_PN=MPEG4_P2_MP4_SP_AAC']+simple_dlna_tags) additional_info = ['*'] if content_format == 'video/x-ms-wmv': additional_info = ['DLNA.ORG_PN=WMV_BASE']+simple_dlna_tags if content_format == '*': additional_info = simple_dlna_tags if does_playcontainer == True: i = 0 for part in additional_info: if part.startswith('DLNA.ORG_FLAGS'): _,bits = part.split('=') bits = int(bits,16) bits |= 0x10000000000000000000000000000000 additional_info[i] = 'DLNA.ORG_FLAGS=%.32x' % bits break i += 1 return ';'.join(additional_info) class Resource(object): """An object representing a resource.""" def __init__(self, data=None, protocolInfo=None): self.data = data self.protocolInfo = protocolInfo self.bitrate = None self.size = None self.duration = None self.nrAudioChannels = None self.resolution = None self.importUri = None if self.protocolInfo is not None: protocol,network,content_format,additional_info = self.protocolInfo.split(':') if additional_info == '*': self.protocolInfo = ':'.join((protocol,network,content_format,build_dlna_additional_info(content_format))) elif additional_info == '#': self.protocolInfo = ':'.join((protocol,network,content_format,'*')) def get_additional_info(self,upnp_client=''): protocol,network,content_format,additional_info = self.protocolInfo.split(':') if upnp_client in ('XBox','Philips-TV',): """ we don't need the DLNA tags there, and maybe they irritate these poor things anyway """ additional_info = '*' elif upnp_client in ('PLAYSTATION3',): if content_format.startswith('video/'): additional_info = '*' a_list = additional_info.split(';') for part in a_list: if part == 'DLNA.ORG_PS=1': a_list.remove(part) break additional_info = ';'.join(a_list) return additional_info def toElement(self,**kwargs): root = ET.Element('res') if kwargs.get('upnp_client','') in ('XBox',): protocol,network,content_format,additional_info = self.protocolInfo.split(':') if content_format in ['video/divx','video/x-msvideo']: content_format = 'video/avi' if content_format == 'audio/x-wav': content_format = 'audio/wav' additional_info = self.get_additional_info(upnp_client=kwargs.get('upnp_client','')) root.attrib['protocolInfo'] = ':'.join((protocol,network,content_format,additional_info)) else: protocol,network,content_format,additional_info = self.protocolInfo.split(':') if content_format == 'video/x-msvideo': content_format = 'video/divx' additional_info = self.get_additional_info(upnp_client=kwargs.get('upnp_client','')) root.attrib['protocolInfo'] = ':'.join((protocol,network,content_format,additional_info)) root.text = self.data if self.bitrate is not None: root.attrib['bitrate'] = str(self.bitrate) if self.size is not None: root.attrib['size'] = str(self.size) if self.duration is not None: root.attrib['duration'] = self.duration if self.nrAudioChannels is not None: root.attrib['nrAudioChannels'] = self.nrAudioChannels if self.resolution is not None: root.attrib['resolution'] = self.resolution if self.importUri is not None: root.attrib['importUri'] = self.importUri return root def fromElement(self, elt): self.protocolInfo = elt.attrib['protocolInfo'] self.data = elt.text self.bitrate = elt.attrib.get('bitrate') self.size = elt.attrib.get('size') self.duration = elt.attrib.get('duration',None) self.resolution = elt.attrib.get('resolution',None) self.importUri = elt.attrib.get('importUri',None) def toString(self,**kwargs): return ET.tostring(self.toElement(**kwargs),encoding='utf-8') @classmethod def fromString(cls, aString): instance = cls() elt = utils.parse_xml(aString) #elt = ElementTree(elt) instance.fromElement(elt.getroot()) return instance def transcoded(self,format): protocol,network,content_format,additional_info = self.protocolInfo.split(':') dlna_tags = simple_dlna_tags[:] #dlna_tags[1] = 'DLNA.ORG_OP=00' dlna_tags[2] = 'DLNA.ORG_CI=1' if format == 'mp3': if content_format == 'audio/mpeg': return None content_format='audio/mpeg' dlna_pn = 'DLNA.ORG_PN=MP3' elif format == 'lpcm': dlna_pn = 'DLNA.ORG_PN=LPCM' content_format='audio/L16;rate=44100;channels=2' elif format == 'mpegts': if content_format == 'video/mpeg': return None dlna_pn = 'DLNA.ORG_PN=MPEG_PS_PAL' # 'DLNA.ORG_PN=MPEG_TS_SD_EU' # FIXME - don't forget HD content_format='video/mpeg' else: return None additional_info = ';'.join([dlna_pn]+dlna_tags) new_protocol_info = ':'.join((protocol,network,content_format,additional_info)) new_res = Resource(self.data+'/transcoded/%s' % format, new_protocol_info) new_res.size = None new_res.duration = self.duration new_res.resolution = self.resolution return new_res class PlayContainerResource(Resource): """An object representing a DLNA playcontainer resource.""" def __init__(self, udn, sid='urn:upnp-org:serviceId:ContentDirectory', cid=None, fid=None, fii=0, sc='',md=0, protocolInfo=None): Resource.__init__(self) if cid == None: raise AttributeError('missing Container Id') if fid == None: raise AttributeError('missing first Child Id') self.protocolInfo = protocolInfo args = ['sid=' + urllib.quote(sid), 'cid=' + urllib.quote(str(cid)), 'fid=' + urllib.quote(str(fid)), 'fii=' + urllib.quote(str(fii)), 'sc=' + urllib.quote(''), 'md=' + urllib.quote(str(0))] self.data = 'dlna-playcontainer://' + urllib.quote(str(udn)) \ + '?' + '&'.join(args) if self.protocolInfo == None: self.protocolInfo = 'http-get:*:*:*' class Object(log.Loggable): """The root class of the entire content directory class heirachy.""" logCategory = 'didllite' upnp_class = 'object' creator = None res = None writeStatus = None date = None albumArtURI = None artist = None genre = None genres = None album = None originalTrackNumber=None description = None longDescription = None refID = None server_uuid = None def __init__(self, id=None, parentID=None, title=None, restricted=False, creator=None): self.id = id self.parentID = parentID self.title = title self.creator = creator self.restricted = restricted self.res = Resources() def checkUpdate(self): return self def toElement(self,**kwargs): root = ET.Element(self.elementName) #if self.id == 1000: # root.attrib['id'] = '0' # ET.SubElement(root, 'dc:title').text = 'root' #else: # root.attrib['id'] = str(self.id) # ET.SubElement(root, 'dc:title').text = self.title root.attrib['id'] = str(self.id) ET.SubElement(root, qname('title',DC_NS)).text = self.title #if self.title != None: # ET.SubElement(root, 'dc:title').text = self.title #else: # ET.SubElement(root, 'dc:title').text = 'root' root.attrib['parentID'] = str(self.parentID) if(kwargs.get('upnp_client','') != 'XBox'): if self.refID: root.attrib['refID'] = str(self.refID) if kwargs.get('requested_id',None): if kwargs.get('requested_id') == '0': t = root.find(qname('title',DC_NS)) t.text = 'root' #if kwargs.get('requested_id') != '0' and kwargs.get('requested_id') != root.attrib['id']: if kwargs.get('requested_id') != root.attrib['id']: if(kwargs.get('upnp_client','') != 'XBox'): root.attrib['refID'] = root.attrib['id'] r_id = kwargs.get('requested_id') root.attrib['id'] = r_id r_id = r_id.split('@',1) try: root.attrib['parentID'] = r_id[1] except IndexError: pass if(kwargs.get('upnp_client','') != 'XBox'): self.info("Changing ID from %r to %r, with parentID %r", root.attrib['refID'], root.attrib['id'], root.attrib['parentID']) else: self.info("Changing ID from %r to %r, with parentID %r", self.id, root.attrib['id'], root.attrib['parentID']) elif kwargs.get('parent_container',None): if(kwargs.get('parent_container') != '0' and kwargs.get('parent_container') != root.attrib['parentID']): if(kwargs.get('upnp_client','') != 'XBox'): root.attrib['refID'] = root.attrib['id'] root.attrib['id'] = '@'.join((root.attrib['id'],kwargs.get('parent_container'))) root.attrib['parentID'] = kwargs.get('parent_container') if(kwargs.get('upnp_client','') != 'XBox'): self.info("Changing ID from %r to %r, with parentID from %r to %r", root.attrib['refID'], root.attrib['id'], self.parentID, root.attrib['parentID']) else: self.info("Changing ID from %r to %r, with parentID from %r to %r", self.id, root.attrib['id'], self.parentID, root.attrib['parentID']) ET.SubElement(root, qname('class',UPNP_NS)).text = self.upnp_class if kwargs.get('upnp_client','') == 'XBox': u = root.find(qname('class',UPNP_NS)) if(kwargs.get('parent_container',None) != None and u.text.startswith('object.container')): if kwargs.get('parent_container') in ('14','15','16'): u.text = 'object.container.storageFolder' if self.upnp_class == 'object.container': u.text = 'object.container.storageFolder' if self.restricted: root.attrib['restricted'] = '1' else: root.attrib['restricted'] = '0' if self.creator is not None: ET.SubElement(root, qname('creator',DC_NS)).text = self.creator if self.writeStatus is not None: ET.SubElement(root, qname('writeStatus',UPNP_NS)).text = self.writeStatus if self.date is not None: if isinstance(self.date, datetime): ET.SubElement(root, qname('date',DC_NS)).text = self.date.isoformat() else: ET.SubElement(root, qname('date',DC_NS)).text = self.date else: ET.SubElement(root, qname('date',DC_NS)).text = utils.datefaker().isoformat() if self.albumArtURI is not None: e = ET.SubElement(root, qname('albumArtURI',UPNP_NS)) e.text = self.albumArtURI e.attrib['xmlns:dlna'] = 'urn:schemas-dlna-org:metadata-1-0' e.attrib['dlna:profileID'] = 'JPEG_TN' if self.artist is not None: ET.SubElement(root, qname('artist',UPNP_NS)).text = self.artist if self.genre is not None: ET.SubElement(root, qname('genre',UPNP_NS)).text = self.genre if self.genres is not None: for genre in self.genres: ET.SubElement(root, qname('genre',UPNP_NS)).text = genre if self.originalTrackNumber is not None: ET.SubElement(root, qname('originalTrackNumber',UPNP_NS)).text = str(self.originalTrackNumber) if self.description is not None: ET.SubElement(root, qname('description',DC_NS)).text = self.description if self.longDescription is not None: ET.SubElement(root, qname('longDescription',UPNP_NS)).text = self.longDescription if self.server_uuid is not None: ET.SubElement(root, qname('server_uuid',UPNP_NS)).text = self.server_uuid return root def toString(self,**kwargs): return ET.tostring(self.toElement(**kwargs),encoding='utf-8') def fromElement(self, elt): """ TODO: * creator * writeStatus """ self.elementName = elt.tag self.id = elt.attrib.get('id',None) self.parentID = elt.attrib.get('parentID',None) self.refID = elt.attrib.get('refID',None) if elt.attrib.get('restricted',None) in [1,'true','True','1','yes','Yes']: self.restricted = True else: self.restricted = False for child in elt.getchildren(): if child.tag.endswith('title'): self.title = child.text elif child.tag.endswith('albumArtURI'): self.albumArtURI = child.text elif child.tag.endswith('originalTrackNumber'): self.originalTrackNumber = int(child.text) elif child.tag.endswith('description'): self.description = child.text elif child.tag.endswith('longDescription'): self.longDescription = child.text elif child.tag.endswith('artist'): self.artist = child.text elif child.tag.endswith('genre'): if self.genre != None: if self.genres == None: self.genres = [self.genre,] self.genres.append(child.text) self.genre = child.text elif child.tag.endswith('album'): self.album = child.text elif child.tag.endswith('class'): self.upnp_class = child.text elif child.tag.endswith('server_uuid'): self.server_uuid = child.text elif child.tag.endswith('res'): res = Resource.fromString(ET.tostring(child)) self.res.append(res) @classmethod def fromString(cls, data): instance = cls() elt = utils.parse_xml(data) #elt = ElementTree(elt) instance.fromElement(elt.getroot()) return instance class Item(Object): """A class used to represent atomic (non-container) content objects.""" upnp_class = Object.upnp_class + '.item' elementName = 'item' refID = None director = None actors = None language = None def __init__(self, *args, **kwargs): Object.__init__(self, *args, **kwargs) def toElement(self,**kwargs): root = Object.toElement(self,**kwargs) if self.director is not None: ET.SubElement(root, qname('director',UPNP_NS)).text = self.director if self.refID is not None: ET.SubElement(root, 'refID').text = self.refID if self.actors is not None: for actor in self.actors: ET.SubElement(root, qname('actor',DC_NS)).text = actor #if self.language is not None: # ET.SubElement(root, qname('language',DC_NS)).text = self.language if kwargs.get('transcoding',False) == True: res = self.res.get_matching(['*:*:*:*'], protocol_type='http-get') if len(res) > 0 and is_audio(res[0].protocolInfo): old_res = res[0] if(kwargs.get('upnp_client','') == 'XBox'): transcoded_res = old_res.transcoded('mp3') if transcoded_res != None: root.append(transcoded_res.toElement(**kwargs)) else: root.append(old_res.toElement(**kwargs)) else: for res in self.res: root.append(res.toElement(**kwargs)) transcoded_res = old_res.transcoded('lpcm') if transcoded_res != None: root.append(transcoded_res.toElement(**kwargs)) elif len(res) > 0 and is_video(res[0].protocolInfo): old_res = res[0] for res in self.res: root.append(res.toElement(**kwargs)) transcoded_res = old_res.transcoded('mpegts') if transcoded_res != None: root.append(transcoded_res.toElement(**kwargs)) else: for res in self.res: root.append(res.toElement(**kwargs)) else: for res in self.res: root.append(res.toElement(**kwargs)) return root def fromElement(self, elt): Object.fromElement(self, elt) for child in elt.getchildren(): if child.tag.endswith('refID'): self.refID = child.text elif child.tag.endswith('director'): self.director = child.text class ImageItem(Item): upnp_class = Item.upnp_class + '.imageItem' rating = None storageMedium = None publisher = None rights = None def toElement(self,**kwargs): root = Item.toElement(self,**kwargs) if self.rating is not None: ET.SubElement(root, qname('rating',UPNP_NS)).text = str(self.rating) if self.storageMedium is not None: ET.SubElement(root, qname('storageMedium',UPNP_NS)).text = self.storageMedium if self.publisher is not None: ET.SubElement(root, qname('publisher',DC_NS)).text = self.contributor if self.rights is not None: ET.SubElement(root, qname('rights',DC_NS)).text = self.rights return root class Photo(ImageItem): upnp_class = ImageItem.upnp_class + '.photo' album = None def toElement(self,**kwargs): root = ImageItem.toElement(self,**kwargs) if self.album is not None: ET.SubElement(root, qname('album',UPNP_NS)).text = self.album return root class AudioItem(Item): """A piece of content that when rendered generates some audio.""" upnp_class = Item.upnp_class + '.audioItem' publisher = None language = None relation = None rights = None valid_keys = ['genre', 'description', 'longDescription', 'publisher', 'language', 'relation', 'rights', 'albumArtURI'] #@dlna.AudioItem def toElement(self,**kwargs): root = Item.toElement(self,**kwargs) if self.publisher is not None: ET.SubElement(root, qname('publisher',DC_NS)).text = self.publisher if self.language is not None: ET.SubElement(root, qname('language',DC_NS)).text = self.language if self.relation is not None: ET.SubElement(root, qname('relation',DC_NS)).text = self.relation if self.rights is not None: ET.SubElement(root, qname('rights',DC_NS)).text = self.rights return root def fromElement(self, elt): Item.fromElement(self, elt) for child in elt.getchildren(): tag = child.tag val = child.text if tag in self.valid_keys: setattr(self, tag, val) class MusicTrack(AudioItem): """A discrete piece of audio that should be interpreted as music.""" upnp_class = AudioItem.upnp_class + '.musicTrack' album = None playlist = None storageMedium = None contributor = None def toElement(self,**kwargs): root = AudioItem.toElement(self,**kwargs) if self.album is not None: ET.SubElement(root, qname('album',UPNP_NS)).text = self.album if self.playlist is not None: ET.SubElement(root, qname('playlist',UPNP_NS)).text = self.playlist if self.storageMedium is not None: ET.SubElement(root, qname('storageMedium',UPNP_NS)).text = self.storageMedium if self.contributor is not None: ET.SubElement(root, qname('contributor',DC_NS)).text = self.contributor return root class AudioBroadcast(AudioItem): upnp_class = AudioItem.upnp_class + '.audioBroadcast' class AudioBook(AudioItem): upnp_class = AudioItem.upnp_class + '.audioBook' class VideoItem(Item): upnp_class = Item.upnp_class + '.videoItem' valid_attrs = dict(genre=UPNP_NS, longDescription=UPNP_NS, producer=UPNP_NS, rating=UPNP_NS, actor=UPNP_NS, director=UPNP_NS, description=DC_NS, publisher=DC_NS, language=DC_NS, relation=DC_NS) def toElement(self,**kwargs): root = Item.toElement(self,**kwargs) for attr_name, ns in self.valid_attrs.iteritems(): value = getattr(self, attr_name, None) if value: ET.SubElement(root, qname(attr_name, ns)).text = value return root def fromElement(self, elt): Item.fromElement(self, elt) for child in elt.getchildren(): tag = child.tag val = child.text if tag in self.valid_attrs.keys(): setattr(self, tag, val) class Movie(VideoItem): upnp_class = VideoItem.upnp_class + '.movie' def __init__(self, *args, **kwargs): VideoItem.__init__(self, *args, **kwargs) self.valid_attrs.update(dict(storageMedium=UPNP_NS, DVDRegionCode=UPNP_NS, channelName=UPNP_NS, scheduledStartTime=UPNP_NS, sccheduledEndTime=UPNP_NS)) class VideoBroadcast(VideoItem): upnp_class = VideoItem.upnp_class + '.videoBroadcast' class MusicVideoClip(VideoItem): upnp_class = VideoItem.upnp_class + '.musicVideoClip' class PlaylistItem(Item): upnp_class = Item.upnp_class + '.playlistItem' class TextItem(Item): upnp_class = Item.upnp_class + '.textItem' class Container(Object): """An object that can contain other objects.""" upnp_class = Object.upnp_class + '.container' elementName = 'container' childCount = None createClass = None searchable = None def __init__(self, id=None, parentID=None, title=None, restricted = False, creator = None): Object.__init__(self, id, parentID, title, restricted, creator) self.searchClass = [] def toElement(self,**kwargs): root = Object.toElement(self,**kwargs) if self.childCount is not None: root.attrib['childCount'] = str(self.childCount) if self.createClass is not None: ET.SubElement(root, qname('createclass',UPNP_NS)).text = self.createClass if not isinstance(self.searchClass, (list, tuple)): self.searchClass = [self.searchClass] for i in self.searchClass: sc = ET.SubElement(root, qname('searchClass',UPNP_NS)) sc.attrib['includeDerived'] = '1' sc.text = i if self.searchable is not None: if self.searchable in (1, '1', True, 'true', 'True'): root.attrib['searchable'] = '1' else: root.attrib['searchable'] = '0' for res in self.res: root.append(res.toElement(**kwargs)) return root def fromElement(self, elt): Object.fromElement(self, elt) v = elt.attrib.get('childCount',None) if v is not None: self.childCount = int(v) #self.searchable = int(elt.attrib.get('searchable','0')) self.searchable = elt.attrib.get('searchable','0') in [1,'True','true','1'] self.searchClass = [] for child in elt.getchildren(): if child.tag.endswith('createclass'): self.createClass = child.text elif child.tag.endswith('searchClass'): self.searchClass.append(child.text) class Person(Container): upnp_class = Container.upnp_class + '.person' class MusicArtist(Person): upnp_class = Person.upnp_class + '.musicArtist' class PlaylistContainer(Container): upnp_class = Container.upnp_class + '.playlistContainer' class Album(Container): upnp_class = Container.upnp_class + '.album' class MusicAlbum(Album): upnp_class = Album.upnp_class + '.musicAlbum' class PhotoAlbum(Album): upnp_class = Album.upnp_class + '.photoAlbum' class Genre(Container): upnp_class = Container.upnp_class + '.genre' class MusicGenre(Genre): upnp_class = Genre.upnp_class + '.musicGenre' class MovieGenre(Genre): upnp_class = Genre.upnp_class + '.movieGenre' class StorageSystem(Container): upnp_class = Container.upnp_class + '.storageSystem' class StorageVolume(Container): upnp_class = Container.upnp_class + '.storageVolume' class StorageFolder(Container): upnp_class = Container.upnp_class + '.storageFolder' class DIDLElement(ElementInterface,log.Loggable): logCategory = 'didllite' def __init__(self, upnp_client='', parent_container=None,requested_id=None, transcoding=False): ElementInterface.__init__(self, 'DIDL-Lite', {}) self.attrib['xmlns'] = 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/' self.attrib['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/' self.attrib['xmlns:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp/' self.attrib['xmlns:dlna'] = 'urn:schemas-dlna-org:metadata-1-0' self.attrib['xmlns:pv'] = 'http://www.pv.com/pvns/' self._items = [] self.upnp_client = upnp_client self.parent_container = parent_container self.requested_id = requested_id self.transcoding = transcoding def addContainer(self, id, parentID, title, restricted = False): e = Container(id, parentID, title, restricted, creator = '') self.append(e.toElement()) def addItem(self, item): self.append(item.toElement(upnp_client=self.upnp_client, parent_container=self.parent_container, requested_id=self.requested_id, transcoding=self.transcoding)) self._items.append(item) def rebuild(self): self._children = [] for item in self._items: self.append(item.toElement(upnp_client=self.upnp_client, parent_container=self.parent_container, requested_id=self.requested_id, transcoding=self.transcoding)) def numItems(self): return len(self) def getItems(self): return self._items def toString(self): """ sigh - having that optional preamble here breaks some of the older ContentDirectoryClients """ #preamble = """""" #return preamble + ET.tostring(self,encoding='utf-8') return ET.tostring(self,encoding='utf-8') def get_upnp_class(self,name): try: return upnp_classes[name]() except KeyError: self.warning("upnp_class %r not found, trying fallback", name) parts = name.split('.') parts.pop() while len(parts) > 1: try: return upnp_classes['.'.join(parts)]() except KeyError: parts.pop() self.warning("WTF - no fallback for upnp_class %r found ?!?", name) return None @classmethod def fromString(cls, aString): instance = cls() elt = utils.parse_xml(aString, 'utf-8') elt = elt.getroot() for node in elt.getchildren(): upnp_class_name = node.findtext('{%s}class' % 'urn:schemas-upnp-org:metadata-1-0/upnp/') upnp_class = instance.get_upnp_class(upnp_class_name.strip()) new_node = upnp_class.fromString(ET.tostring(node)) instance.addItem(new_node) return instance def element_to_didl(item): """ a helper method to create a DIDLElement out of one ET element or XML fragment string """ if not isinstance(item,basestring): item = ET.tostring(item) didl = """""" \ + item + \ """""" return didl upnp_classes = {'object': Object, 'object.item': Item, 'object.item.imageItem': ImageItem, 'object.item.imageItem.photo': Photo, 'object.item.audioItem': AudioItem, 'object.item.audioItem.musicTrack': MusicTrack, 'object.item.audioItem.audioBroadcast': AudioBroadcast, 'object.item.audioItem.audioBook': AudioBook, 'object.item.videoItem': VideoItem, 'object.item.videoItem.movie': Movie, 'object.item.videoItem.videoBroadcast': VideoBroadcast, 'object.item.videoItem.musicVideoClip': MusicVideoClip, 'object.item.playlistItem': PlaylistItem, 'object.item.textItem': TextItem, 'object.container': Container, 'object.container.person': Person, 'object.container.person.musicArtist': MusicArtist, 'object.container.playlistContainer': PlaylistContainer, 'object.container.album': Album, 'object.container.album.musicAlbum': MusicAlbum, 'object.container.album.photoAlbum': PhotoAlbum, 'object.container.genre': Genre, 'object.container.genre.musicGenre': MusicGenre, 'object.container.genre.movieGenre': MovieGenre, 'object.container.storageSystem': StorageSystem, 'object.container.storageVolume': StorageVolume, 'object.container.storageFolder': StorageFolder, } if __name__ == '__main__': res = Resources() res.append(Resource('1','file:*:*:*')) res.append(Resource('2','rtsp-rtp-udp:*:*:*')) res.append(Resource('3',None)) res.append(Resource('4','internal:*:*:*')) res.append(Resource('5','http-get:*:*:*')) res.append(Resource('6','something:*:*:*')) res.append(Resource('7','http-get:*:*:*')) for r in res: print r.data, r.protocolInfo Coherence-0.6.6.2/coherence/upnp/core/device.py0000644000175000017500000004510611317660735017371 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz import urllib2 import time from twisted.internet import defer from coherence.upnp.core.service import Service from coherence.upnp.core import utils from coherence import log import coherence.extern.louie as louie ns = "urn:schemas-upnp-org:device-1-0" class Device(log.Loggable): logCategory = 'device' def __init__(self, parent=None): self.parent = parent self.services = [] #self.uid = self.usn[:-len(self.st)-2] self.friendly_name = "" self.device_type = "" self.friendly_device_type = "[unknown]" self.device_type_version = 0 self.udn = None self.detection_completed = False self.client = None self.icons = [] self.devices = [] louie.connect( self.receiver, 'Coherence.UPnP.Service.detection_completed', self) louie.connect( self.service_detection_failed, 'Coherence.UPnP.Service.detection_failed', self) def __repr__(self): return "embedded device %r %r, parent %r" % (self.friendly_name,self.device_type,self.parent) #def __del__(self): # #print "Device removal completed" # pass def as_dict(self): import copy d = {'device_type': self.get_device_type(), 'friendly_name': self.get_friendly_name(), 'udn': self.get_id(), 'services': [x.as_dict() for x in self.services]} icons = [] for icon in self.icons: icons.append({"mimetype":icon['mimetype'],"url":icon['url'], "height":icon['height'], "width":icon['width'], "depth":icon['depth']}) d['icons'] = icons return d def remove(self,*args): self.info("removal of ", self.friendly_name, self.udn) while len(self.devices)>0: device = self.devices.pop() self.debug("try to remove %r", device) device.remove() while len(self.services)>0: service = self.services.pop() self.debug("try to remove %r", service) service.remove() if self.client != None: louie.send('Coherence.UPnP.Device.remove_client', None, self.udn, self.client) self.client = None #del self def receiver(self, *args, **kwargs): if self.detection_completed == True: return for s in self.services: if s.detection_completed == False: return if self.udn == None: return self.detection_completed = True if self.parent != None: self.info("embedded device %r %r initialized, parent %r" % (self.friendly_name,self.device_type,self.parent)) louie.send('Coherence.UPnP.Device.detection_completed', None, device=self) if self.parent != None: louie.send('Coherence.UPnP.Device.detection_completed', self.parent, device=self) else: louie.send('Coherence.UPnP.Device.detection_completed', self, device=self) def service_detection_failed( self, device): self.remove() def get_id(self): return self.udn def get_uuid(self): return self.udn[5:] def get_services(self): return self.services def get_service_by_type(self,type): for service in self.services: _,_,_,service_class,version = service.service_type.split(':') if service_class == type: return service def add_service(self, service): self.debug("add_service %r", service) self.services.append(service) def remove_service_with_usn(self, service_usn): for service in self.services: if service.get_usn() == service_usn: self.services.remove(service) service.remove() break def add_device(self, device): self.debug("Device add_device %r", device) self.devices.append(device) def get_friendly_name(self): return self.friendly_name def get_device_type(self): return self.device_type def get_friendly_device_type(self): return self.friendly_device_type def get_markup_name(self): try: return self._markup_name except AttributeError: self._markup_name = u"%s:%s %s" % (self.friendly_device_type, self.device_type_version, self.friendly_name) return self._markup_name def get_device_type_version(self): return self.device_type_version def set_client(self, client): self.client = client def get_client(self): return self.client def renew_service_subscriptions(self): """ iterate over device's services and renew subscriptions """ self.info("renew service subscriptions for %s" % self.friendly_name) now = time.time() for service in self.services: self.info("check service %r %r " % (service.id, service.get_sid()), service.get_timeout(), now) if service.get_sid() is not None: if service.get_timeout() < now: self.debug("wow, we lost an event subscription for %s %s, " % (self.friendly_name, service.get_id()), "maybe we need to rethink the loop time and timeout calculation?") if service.get_timeout() < now + 30 : service.renew_subscription() for device in self.devices: device.renew_service_subscriptions() def unsubscribe_service_subscriptions(self): """ iterate over device's services and unsubscribe subscriptions """ l = [] for service in self.get_services(): if service.get_sid() is not None: l.append(service.unsubscribe()) dl = defer.DeferredList(l) return dl def parse_device(self, d): self.info("parse_device %r" %d) self.device_type = unicode(d.findtext('./{%s}deviceType' % ns)) self.friendly_device_type, self.device_type_version = \ self.device_type.split(':')[-2:] self.friendly_name = unicode(d.findtext('./{%s}friendlyName' % ns)) self.udn = d.findtext('./{%s}UDN' % ns) self.info("found udn %r %r" % (self.udn,self.friendly_name)) try: self.manufacturer = d.findtext('./{%s}manufacturer' % ns) except: pass try: self.manufacturer_url = d.findtext('./{%s}manufacturerURL' % ns) except: pass try: self.model_name = d.findtext('./{%s}modelName' % ns) except: pass try: self.model_description = d.findtext('./{%s}modelDescription' % ns) except: pass try: self.model_number = d.findtext('./{%s}modelNumber' % ns) except: pass try: self.model_url = d.findtext('./{%s}modelURL' % ns) except: pass try: self.serial_number = d.findtext('./{%s}serialNumber' % ns) except: pass try: self.upc = d.findtext('./{%s}UPC' % ns) except: pass try: self.presentation_url = d.findtext('./{%s}presentationURL' % ns) except: pass try: for dlna_doc in d.findall('./{urn:schemas-dlna-org:device-1-0}X_DLNADOC'): try: self.dlna_dc.append(dlna_doc.text) except AttributeError: self.dlna_dc = [] self.dlna_dc.append(dlna_doc.text) except: pass try: for dlna_cap in d.findall('./{urn:schemas-dlna-org:device-1-0}X_DLNACAP'): for cap in dlna_cap.text.split(','): try: self.dlna_cap.append(cap) except AttributeError: self.dlna_cap = [] self.dlna_cap.append(cap) except: pass icon_list = d.find('./{%s}iconList' % ns) if icon_list is not None: import urllib2 url_base = "%s://%s" % urllib2.urlparse.urlparse(self.get_location())[:2] for icon in icon_list.findall('./{%s}icon' % ns): try: i = {} i['mimetype'] = icon.find('./{%s}mimetype' % ns).text i['width'] = icon.find('./{%s}width' % ns).text i['height'] = icon.find('./{%s}height' % ns).text i['depth'] = icon.find('./{%s}depth' % ns).text i['realurl'] = icon.find('./{%s}url' % ns).text i['url'] = icon.find('./{%s}url' % ns).text if i['url'].startswith('/'): i['url'] = ''.join((url_base,i['url'])) self.icons.append(i) self.debug("adding icon %r for %r" % (i,self.friendly_name)) except: import traceback self.debug(traceback.format_exc()) self.warning("device %r seems to have an invalid icon description, ignoring that icon" % self.friendly_name) serviceList = d.find('./{%s}serviceList' % ns) if serviceList: for service in serviceList.findall('./{%s}service' % ns): serviceType = service.findtext('{%s}serviceType' % ns) serviceId = service.findtext('{%s}serviceId' % ns) controlUrl = service.findtext('{%s}controlURL' % ns) eventSubUrl = service.findtext('{%s}eventSubURL' % ns) presentationUrl = service.findtext('{%s}presentationURL' % ns) scpdUrl = service.findtext('{%s}SCPDURL' % ns) """ check if values are somehow reasonable """ if len(scpdUrl) == 0: self.warning("service has no uri for its description") continue if len(eventSubUrl) == 0: self.warning("service has no uri for eventing") continue if len(controlUrl) == 0: self.warning("service has no uri for controling") continue self.add_service(Service(serviceType, serviceId, self.get_location(), controlUrl, eventSubUrl, presentationUrl, scpdUrl, self)) # now look for all sub devices embedded_devices = d.find('./{%s}deviceList' % ns) if embedded_devices: for d in embedded_devices.findall('./{%s}device' % ns): embedded_device = Device(self) self.add_device(embedded_device) embedded_device.parse_device(d) self.receiver() def get_location(self): return self.parent.get_location() def get_usn(self): return self.parent.get_usn() def get_upnp_version(self): return self.parent.get_upnp_version() def get_urlbase(self): return self.parent.get_urlbase() def get_presentation_url(self): try: return self.make_fullyqualified(self.presentation_url) except: return '' def get_parent_id(self): try: return self.parent.get_id() except: return '' def make_fullyqualified(self,url): return self.parent.make_fullyqualified(url) def as_tuples(self): r = [] def append(name,attribute): try: if isinstance(attribute,tuple): if callable(attribute[0]): v1 = attribute[0]() else: v1 = getattr(self,attribute[0]) if v1 in [None,'None']: return if callable(attribute[1]): v2 = attribute[1]() else: v2 = getattr(self,attribute[1]) if v2 in [None,'None']: return r.append((name,(v1,v2))) return elif callable(attribute): v = attribute() else: v = getattr(self,attribute) if v not in [None,'None']: r.append((name,v)) except: import traceback self.debug(traceback.format_exc()) try: r.append(('Location',(self.get_location(),self.get_location()))) except: pass try: append('URL base',self.get_urlbase) except: pass try: r.append(('UDN',self.get_id())) except: pass try: r.append(('Type',self.device_type)) except: pass try: r.append(('UPnP Version',self.upnp_version)) except: pass try: r.append(('DLNA Device Class',','.join(self.dlna_dc))) except: pass try: r.append(('DLNA Device Capability',','.join(self.dlna_cap))) except: pass try: r.append(('Friendly Name',self.friendly_name)) except: pass try: append('Manufacturer','manufacturer') except: pass try: append('Manufacturer URL',('manufacturer_url','manufacturer_url')) except: pass try: append('Model Description','model_description') except: pass try: append('Model Name','model_name') except: pass try: append('Model Number','model_number') except: pass try: append('Model URL',('model_url','model_url')) except: pass try: append('Serial Number','serial_number') except: pass try: append('UPC','upc') except: pass try: append('Presentation URL',('presentation_url',lambda: self.make_fullyqualified(getattr(self,'presentation_url')))) except: pass for icon in self.icons: r.append(('Icon', (icon['realurl'], self.make_fullyqualified(icon['realurl']), {'Mimetype': icon['mimetype'], 'Width':icon['width'], 'Height':icon['height'], 'Depth':icon['depth']}))) return r class RootDevice(Device): def __init__(self, infos): self.usn = infos['USN'] self.server = infos['SERVER'] self.st = infos['ST'] self.location = infos['LOCATION'] self.manifestation = infos['MANIFESTATION'] self.host = infos['HOST'] self.root_detection_completed = False Device.__init__(self, None) louie.connect( self.device_detect, 'Coherence.UPnP.Device.detection_completed', self) # we need to handle root device completion # these events could be ourself or our children. self.parse_description() def __repr__(self): return "rootdevice %r %r %r %r, manifestation %r" % (self.friendly_name,self.udn,self.st,self.host,self.manifestation) def get_usn(self): return self.usn def get_st(self): return self.st def get_location(self): return self.location def get_upnp_version(self): return self.upnp_version def get_urlbase(self): return self.urlbase def get_host(self): return self.host def is_local(self): if self.manifestation == 'local': return True return False def is_remote(self): if self.manifestation != 'local': return True return False def device_detect( self, *args, **kwargs): self.debug("device_detect %r", kwargs) if self.root_detection_completed == True: return self.debug("root_detection_completed %r", self.root_detection_completed) # our self is not complete yet if self.detection_completed == False: return self.debug("detection_completed %r", self.detection_completed) # now check child devices. self.debug("self.devices %r", self.devices) for d in self.devices: self.debug("check device %r %r", d.detection_completed, d) if d.detection_completed == False: return # now must be done, so notify root done self.root_detection_completed = True self.info("rootdevice %r %r %r initialized, manifestation %r" % (self.friendly_name,self.st,self.host,self.manifestation)) louie.send('Coherence.UPnP.RootDevice.detection_completed', None, device=self) def add_device(self, device): self.debug("RootDevice add_device %r", device) self.devices.append(device) def get_devices(self): self.debug("RootDevice get_devices:", self.devices) return self.devices def parse_description(self): def gotPage(x): self.debug("got device description from %r" % self.location) data, headers = x tree = utils.parse_xml(data, 'utf-8').getroot() major = tree.findtext('./{%s}specVersion/{%s}major' % (ns,ns)) minor = tree.findtext('./{%s}specVersion/{%s}minor' % (ns,ns)) try: self.upnp_version = '.'.join((major,minor)) except: self.upnp_version = 'n/a' try: self.urlbase = tree.findtext('./{%s}URLBase' % ns) except: import traceback self.debug(traceback.format_exc()) d = tree.find('./{%s}device' % ns) if d is not None: self.parse_device(d) # root device def gotError(failure, url): self.warning("error getting device description from %r", url) self.info(failure) utils.getPage(self.location).addCallbacks(gotPage, gotError, None, None, [self.location], None) def make_fullyqualified(self,url): if url.startswith('http://'): return url import urlparse base = self.get_urlbase() if base != None: if base[-1] != '/': base += '/' r = urlparse.urljoin(base,url) else: r = urlparse.urljoin(self.get_location(),url) return r Coherence-0.6.6.2/coherence/upnp/core/action.py0000644000175000017500000001423611317660735017407 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006,2007,2008,2009 Frank Scholz from twisted.python import failure from twisted.python.util import OrderedDict from coherence import log class Argument: def __init__(self, name, direction, state_variable): self.name = name self.direction = direction self.state_variable = state_variable def get_name(self): return self.name def get_direction(self): return self.direction def get_state_variable(self): return self.state_variable def __repr__(self): return "Argument: %s, %s, %s" % (self.get_name(), self.get_direction(), self.get_state_variable()) def as_tuples(self): r = [] r.append(('Name',self.name)) r.append(('Direction',self.direction)) r.append(('Related State Variable',self.state_variable)) return r def as_dict(self): return {'name':self.name,'direction':self.direction,'related_state_variable':self.state_variable} class Action(log.Loggable): logCategory = 'action' def __init__(self, service, name, implementation, arguments_list): self.service = service self.name = name self.implementation = implementation self.arguments_list = arguments_list def _get_client(self): client = self.service._get_client( self.name) return client def get_name(self): return self.name def get_implementation(self): return self.implementation def get_arguments_list(self): return self.arguments_list def get_in_arguments(self): return [arg for arg in self.arguments_list if arg.get_direction() == 'in'] def get_out_arguments(self): return [arg for arg in self.arguments_list if arg.get_direction() == 'out'] def get_service(self): return self.service def set_callback(self, callback): self.callback = callback def get_callback(self): try: return self.callback except: return None def call(self, *args, **kwargs): self.info("calling", self.name) in_arguments = self.get_in_arguments() self.info("in arguments", [a.get_name() for a in in_arguments]) instance_id = 0 for arg_name, arg in kwargs.iteritems(): l = [ a for a in in_arguments if arg_name == a.get_name()] if len(l) > 0: in_arguments.remove(l[0]) else: self.error("argument %s not valid for action %s" % (arg_name,self.name)) return if arg_name == 'InstanceID': instance_id = int(arg) if len(in_arguments) > 0: self.error("argument %s missing for action %s" % ([ a.get_name() for a in in_arguments],self.name)) return action_name = self.name if(hasattr(self.service.device.client, 'overlay_actions') and self.service.device.client.overlay_actions.has_key(self.name)): self.info("we have an overlay method %r for action %r", self.service.device.client.overlay_actions[self.name], self.name) action_name, kwargs = self.service.device.client.overlay_actions[self.name](**kwargs) self.info("changing action to %r %r", action_name, kwargs) def got_error(failure): self.warning("error on %s request with %s %s" % (self.name,self. service.service_type, self.service.control_url)) self.info(failure) return failure if hasattr(self.service.device.client, 'overlay_headers'): self.info("action call has headers %r", kwargs.has_key('headers')) if kwargs.has_key('headers'): kwargs['headers'].update(self.service.device.client.overlay_headers) else: kwargs['headers'] = self.service.device.client.overlay_headers self.info("action call with new/updated headers %r", kwargs['headers']) client = self._get_client() ordered_arguments = OrderedDict() for argument in self.get_in_arguments(): ordered_arguments[argument.name] = kwargs[argument.name] d = client.callRemote(action_name, ordered_arguments) d.addCallback(self.got_results, instance_id=instance_id, name=action_name) d.addErrback(got_error) return d def got_results( self, results, instance_id, name): instance_id = int(instance_id) out_arguments = self.get_out_arguments() self.info( "call %s (instance %d) returns %d arguments: %r" % (name, instance_id, len(out_arguments), results)) # XXX A_ARG_TYPE_ arguments probably don't need a variable update #if len(out_arguments) == 1: # self.service.get_state_variable(out_arguments[0].get_state_variable(), instance_id).update(results) #elif len(out_arguments) > 1: if len(out_arguments) > 0: for arg_name, value in results.items(): state_variable_name = [a.get_state_variable() for a in out_arguments if a.get_name() == arg_name] self.service.get_state_variable(state_variable_name[0], instance_id).update(value) return results def __repr__(self): return "Action: %s [%s], (%s args)" % (self.get_name(), self.get_implementation(), len(self.get_arguments_list())) def as_tuples(self): r = [] r.append(('Name',self.get_name())) r.append(("Number of 'in' arguments",len(self.get_in_arguments()))) r.append(("Number of 'out' arguments",len(self.get_out_arguments()))) return r def as_dict(self): return {'name': self.get_name(),'arguments':[a.as_dict() for a in self.arguments_list]} Coherence-0.6.6.2/coherence/upnp/core/soap_lite.py0000644000175000017500000001014111317660735020100 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007 - Frank Scholz """ SOAP-lite some simple functions to implement the SOAP msgs needed by UPnP with ElementTree inspired by ElementSOAP.py """ from twisted.python.util import OrderedDict from coherence.extern.et import ET NS_SOAP_ENV = "{http://schemas.xmlsoap.org/soap/envelope/}" NS_SOAP_ENC = "{http://schemas.xmlsoap.org/soap/encoding/}" NS_XSI = "{http://www.w3.org/1999/XMLSchema-instance}" NS_XSD = "{http://www.w3.org/1999/XMLSchema}" SOAP_ENCODING = "http://schemas.xmlsoap.org/soap/encoding/" UPNPERRORS = {401:'Invalid Action', 402:'Invalid Args', 501:'Action Failed', 600:'Argument Value Invalid', 601:'Argument Value Out of Range', 602:'Optional Action Not Implemented', 603:'Out Of Memory', 604:'Human Intervention Required', 605:'String Argument Too Long', 606:'Action Not Authorized', 607:'Signature Failure', 608:'Signature Missing', 609:'Not Encrypted', 610:'Invalid Sequence', 611:'Invalid Control URL', 612:'No Such Session',} def build_soap_error(status,description='without words'): """ builds an UPnP SOAP error msg """ root = ET.Element('s:Fault') ET.SubElement(root,'faultcode').text='s:Client' ET.SubElement(root,'faultstring').text='UPnPError' e = ET.SubElement(root,'detail') e = ET.SubElement(e, 'UPnPError') e.attrib['xmlns']='urn:schemas-upnp-org:control-1-0' ET.SubElement(e,'errorCode').text=str(status) ET.SubElement(e,'errorDescription').text=UPNPERRORS.get(status,description) return build_soap_call(None, root, encoding=None) def build_soap_call(method, arguments, is_response=False, encoding=SOAP_ENCODING, envelope_attrib=None, typed=None): """ create a shell for a SOAP request or response element - set method to none to omitt the method element and add the arguments directly to the body (for an error msg) - arguments can be a dict or an ET.Element """ envelope = ET.Element("s:Envelope") if envelope_attrib: for n in envelope_attrib: envelope.attrib.update({n[0] : n[1]}) else: envelope.attrib.update({'s:encodingStyle' : "http://schemas.xmlsoap.org/soap/encoding/"}) envelope.attrib.update({'xmlns:s' :"http://schemas.xmlsoap.org/soap/envelope/"}) body = ET.SubElement(envelope, "s:Body") if method: # append the method call if is_response is True: method += "Response" re = ET.SubElement(body,method) if encoding: re.set(NS_SOAP_ENV + "encodingStyle", encoding) else: re = body # append the arguments if isinstance(arguments,(dict,OrderedDict)) : type_map = {str: 'xsd:string', unicode: 'xsd:string', int: 'xsd:int', float: 'xsd:float', bool: 'xsd:boolean'} for arg_name, arg_val in arguments.iteritems(): arg_type = type_map[type(arg_val)] if arg_type == 'xsd:string' and type(arg_val) == unicode: arg_val = arg_val.encode('utf-8') if arg_type == 'xsd:int' or arg_type == 'xsd:float': arg_val = str(arg_val) if arg_type == 'xsd:boolean': arg_val = arg_val.lower() e = ET.SubElement(re, arg_name) if typed and arg_type: if not isinstance(type, ET.QName): arg_type = ET.QName("http://www.w3.org/1999/XMLSchema", arg_type) e.set(NS_XSI + "type", arg_type) e.text = arg_val else: if arguments == None: arguments = {} re.append(arguments) preamble = """""" return preamble + ET.tostring(envelope,'utf-8') Coherence-0.6.6.2/coherence/upnp/core/soap_proxy.py0000644000175000017500000001072311317660735020332 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007 - Frank Scholz from twisted.python import failure from coherence import log from coherence.extern.et import ET, namespace_map_update from coherence.upnp.core.utils import getPage, parse_xml from coherence.upnp.core import soap_lite class SOAPProxy(log.Loggable): """ A Proxy for making remote SOAP calls. Based upon twisted.web.soap.Proxy and extracted to remove the SOAPpy dependency Pass the URL of the remote SOAP server to the constructor. Use proxy.callRemote('foobar', 1, 2) to call remote method 'foobar' with args 1 and 2, proxy.callRemote('foobar', x=1) will call foobar with named argument 'x'. """ logCategory = 'soap' def __init__(self, url, namespace=None, envelope_attrib=None, header=None, soapaction=None): self.url = url self.namespace = namespace self.header = header self.action = None self.soapaction = soapaction self.envelope_attrib = envelope_attrib def callRemote(self, soapmethod, arguments): soapaction = soapmethod or self.soapaction if '#' not in soapaction: soapaction = '#'.join((self.namespace[1],soapaction)) self.action = soapaction.split('#')[1] self.info("callRemote %r %r %r %r", self.soapaction, soapmethod, self.namespace, self.action) headers = { 'content-type': 'text/xml ;charset="utf-8"', 'SOAPACTION': '"%s"' % soapaction,} if arguments.has_key('headers'): headers.update(arguments['headers']) del arguments['headers'] payload = soap_lite.build_soap_call("{%s}%s" % (self.namespace[1], self.action), arguments, encoding=None) self.info("callRemote soapaction: ", self.action,self.url) self.debug("callRemote payload: ", payload) def gotError(error, url): self.warning("error requesting url %r" % url) self.debug(error) try: tree = parse_xml(error.value.response) body = tree.find('{http://schemas.xmlsoap.org/soap/envelope/}Body') return failure.Failure(Exception("%s - %s" % (body.find('.//{urn:schemas-upnp-org:control-1-0}errorCode').text, body.find('.//{urn:schemas-upnp-org:control-1-0}errorDescription').text))) except: import traceback self.debug(traceback.format_exc()) return error return getPage(self.url, postdata=payload, method="POST", headers=headers ).addCallbacks(self._cbGotResult, gotError, None, None, [self.url], None) def _cbGotResult(self, result): #print "_cbGotResult 1", result page, headers = result #result = SOAPpy.parseSOAPRPC(page) #print "_cbGotResult 2", result def print_c(e): for c in e.getchildren(): print c, c.tag print_c(c) self.debug("result: %r" % page) tree = parse_xml(page) #print tree, "find %s" % self.action #root = tree.getroot() #print_c(root) body = tree.find('{http://schemas.xmlsoap.org/soap/envelope/}Body') #print "body", body response = body.find('{%s}%sResponse' % (self.namespace[1], self.action)) if response == None: """ fallback for improper SOAP action responses """ response = body.find('%sResponse' % self.action) self.debug("callRemote response ", response) result = {} if response != None: for elem in response: result[elem.tag] = self.decode_result(elem) #print "_cbGotResult 3", result return result def decode_result(self, element): type = element.get('{http://www.w3.org/1999/XMLSchema-instance}type') if type is not None: try: prefix, local = type.split(":") if prefix == 'xsd': type = local except ValueError: pass if type == "integer" or type == "int": return int(element.text) if type == "float" or type == "double": return float(element.text) if type == "boolean": return element.text == "true" return element.text or "" Coherence-0.6.6.2/coherence/upnp/core/__init__.py0000644000175000017500000000000011317660735017651 0ustar devdevCoherence-0.6.6.2/coherence/upnp/core/uuid.py0000644000175000017500000000137611317660735017101 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # # Copyright 2005, Tim Potter # Copyright 2006, Frank Scholz # # # uses uuid4 method from http://zesty.ca/python/uuid.html # Copyright 2006, Ka-Ping Yee # try: from uuid import uuid4 except ImportError: try: from coherence.extern.uuid.uuid import uuid4 except ImportError: print 'fallback: define own uuid4' def uuid4(): import random import string return ''.join(map(lambda x: random.choice(string.letters), xrange(20))) class UUID: def __init__(self): self.uuid = 'uuid:' + str(uuid4()) def __repr__(self): return self.uuid Coherence-0.6.6.2/coherence/upnp/core/soap_service.py0000644000175000017500000001463411317660735020616 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2007 - Frank Scholz from twisted.web import server, resource from twisted.python import failure from twisted.internet import defer from coherence import log, SERVER_ID from coherence.extern.et import ET, namespace_map_update from coherence.upnp.core.utils import parse_xml from coherence.upnp.core import soap_lite import coherence.extern.louie as louie class errorCode(Exception): def __init__(self, status): Exception.__init__(self) self.status = status class UPnPPublisher(resource.Resource, log.Loggable): """ Based upon twisted.web.soap.SOAPPublisher and extracted to remove the SOAPpy dependency UPnP requires headers and OUT parameters to be returned in a slightly different way than the SOAPPublisher class does. """ logCategory = 'soap' isLeaf = 1 encoding = "UTF-8" envelope_attrib = None def _sendResponse(self, request, response, status=200): self.debug('_sendResponse', status, response) if status == 200: request.setResponseCode(200) else: request.setResponseCode(500) if self.encoding is not None: mimeType = 'text/xml; charset="%s"' % self.encoding else: mimeType = "text/xml" request.setHeader("Content-type", mimeType) request.setHeader("Content-length", str(len(response))) request.setHeader("EXT", '') request.setHeader("SERVER", SERVER_ID) request.write(response) request.finish() def _methodNotFound(self, request, methodName): response = soap_lite.build_soap_error(401) self._sendResponse(request, response, status=401) def _gotResult(self, result, request, methodName, ns): self.debug('_gotResult', result, request, methodName, ns) response = soap_lite.build_soap_call("{%s}%s" % (ns, methodName), result, is_response=True, encoding=None) #print "SOAP-lite response", response self._sendResponse(request, response) def _gotError(self, failure, request, methodName, ns): self.info('_gotError', failure, failure.value) e = failure.value status = 500 if isinstance(e, errorCode): status = e.status else: failure.printTraceback() response = soap_lite.build_soap_error(status) self._sendResponse(request, response, status=status) def lookupFunction(self, functionName): function = getattr(self, "soap_%s" % functionName, None) if not function: function = getattr(self, "soap__generic", None) if function: return function, getattr(function, "useKeywords", False) else: return None, None def render(self, request): """Handle a SOAP command.""" data = request.content.read() headers = request.getAllHeaders() self.info('soap_request:', headers) # allow external check of data louie.send('UPnPTest.Control.Client.CommandReceived', None, headers, data) def print_c(e): for c in e.getchildren(): print c, c.tag print_c(c) tree = parse_xml(data) #root = tree.getroot() #print_c(root) body = tree.find('{http://schemas.xmlsoap.org/soap/envelope/}Body') method = body.getchildren()[0] methodName = method.tag ns = None if methodName.startswith('{') and methodName.rfind('}') > 1: ns, methodName = methodName[1:].split('}') args = [] kwargs = {} for child in method.getchildren(): kwargs[child.tag] = self.decode_result(child) args.append(kwargs[child.tag]) #p, header, body, attrs = SOAPpy.parseSOAPRPC(data, 1, 1, 1) #methodName, args, kwargs, ns = p._name, p._aslist, p._asdict, p._ns try: headers['content-type'].index('text/xml') except: self._gotError(failure.Failure(errorCode(415)), request, methodName) return server.NOT_DONE_YET self.debug('headers: %r' % headers) function, useKeywords = self.lookupFunction(methodName) #print 'function', function, 'keywords', useKeywords, 'args', args, 'kwargs', kwargs if not function: self._methodNotFound(request, methodName) return server.NOT_DONE_YET else: keywords = {'soap_methodName':methodName} if(headers.has_key('user-agent') and headers['user-agent'].find('Xbox/') == 0): keywords['X_UPnPClient'] = 'XBox' #if(headers.has_key('user-agent') and # headers['user-agent'].startswith("""Mozilla/4.0 (compatible; UPnP/1.0; Windows""")): # keywords['X_UPnPClient'] = 'XBox' if(headers.has_key('x-av-client-info') and headers['x-av-client-info'].find('"PLAYSTATION3') > 0): keywords['X_UPnPClient'] = 'PLAYSTATION3' if(headers.has_key('user-agent') and headers['user-agent'].find('Philips-Software-WebClient/4.32') == 0): keywords['X_UPnPClient'] = 'Philips-TV' for k, v in kwargs.items(): keywords[str(k)] = v self.info('call', methodName, keywords) if hasattr(function, "useKeywords"): d = defer.maybeDeferred(function, **keywords) else: d = defer.maybeDeferred(function, *args, **keywords) d.addCallback(self._gotResult, request, methodName, ns) d.addErrback(self._gotError, request, methodName, ns) return server.NOT_DONE_YET def decode_result(self, element): type = element.get('{http://www.w3.org/1999/XMLSchema-instance}type') if type is not None: try: prefix, local = type.split(":") if prefix == 'xsd': type = local except ValueError: pass if type == "integer" or type == "int": return int(element.text) if type == "float" or type == "double": return float(element.text) if type == "boolean": return element.text == "true" return element.text or "" Coherence-0.6.6.2/coherence/upnp/core/ssdp.py0000644000175000017500000002726411317660735017110 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2005, Tim Potter # Copyright 2006 John-Mark Gurney # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006,2007,2008,2009 Frank Scholz # # Implementation of a SSDP server under Twisted Python. # import random import string import sys import time import socket from twisted.internet.protocol import DatagramProtocol from twisted.internet import reactor, error from twisted.internet import task from twisted.web.http import datetimeToString from coherence import log, SERVER_ID import coherence.extern.louie as louie SSDP_PORT = 1900 SSDP_ADDR = '239.255.255.250' class SSDPServer(DatagramProtocol, log.Loggable): """A class implementing a SSDP server. The notifyReceived and searchReceived methods are called when the appropriate type of datagram is received by the server.""" logCategory = 'ssdp' known = {} _callbacks = {} def __init__(self,test=False,interface=''): # Create SSDP server self.test = test if self.test == False: try: self.port = reactor.listenMulticast(SSDP_PORT, self, listenMultiple=True) #self.port.setLoopbackMode(1) self.port.joinGroup(SSDP_ADDR,interface=interface) self.resend_notify_loop = task.LoopingCall(self.resendNotify) self.resend_notify_loop.start(777.0, now=False) self.check_valid_loop = task.LoopingCall(self.check_valid) self.check_valid_loop.start(333.0, now=False) except error.CannotListenError, err: self.warning("There seems to be already a SSDP server running on this host, no need starting a second one.") self.active_calls = [] def shutdown(self): for call in reactor.getDelayedCalls(): if call.func == self.send_it: call.cancel() if self.test == False: if self.resend_notify_loop.running: self.resend_notify_loop.stop() if self.check_valid_loop.running: self.check_valid_loop.stop() '''Make sure we send out the byebye notifications.''' for st in self.known: if self.known[st]['MANIFESTATION'] == 'local': self.doByebye(st) def datagramReceived(self, data, (host, port)): """Handle a received multicast datagram.""" try: header, payload = data.split('\r\n\r\n')[:2] except ValueError, err: print err print 'Arggg,', data import pdb; pdb.set_trace() lines = header.split('\r\n') cmd = string.split(lines[0], ' ') lines = map(lambda x: x.replace(': ', ':', 1), lines[1:]) lines = filter(lambda x: len(x) > 0, lines) headers = [string.split(x, ':', 1) for x in lines] headers = dict(map(lambda x: (x[0].lower(), x[1]), headers)) self.msg('SSDP command %s %s - from %s:%d' % (cmd[0], cmd[1], host, port)) self.debug('with headers:', headers) if cmd[0] == 'M-SEARCH' and cmd[1] == '*': # SSDP discovery self.discoveryRequest(headers, (host, port)) elif cmd[0] == 'NOTIFY' and cmd[1] == '*': # SSDP presence self.notifyReceived(headers, (host, port)) else: self.warning('Unknown SSDP command %s %s' % (cmd[0], cmd[1])) # make raw data available # send out the signal after we had a chance to register the device louie.send('UPnP.SSDP.datagram_received', None, data, host, port) def register(self, manifestation, usn, st, location, server=SERVER_ID, cache_control='max-age=1800', silent=False, host=None): """Register a service or device that this SSDP server will respond to.""" self.info('Registering %s (%s)' % (st, location)) self.known[usn] = {} self.known[usn]['USN'] = usn self.known[usn]['LOCATION'] = location self.known[usn]['ST'] = st self.known[usn]['EXT'] = '' self.known[usn]['SERVER'] = server self.known[usn]['CACHE-CONTROL'] = cache_control self.known[usn]['MANIFESTATION'] = manifestation self.known[usn]['SILENT'] = silent self.known[usn]['HOST'] = host self.known[usn]['last-seen'] = time.time() self.msg(self.known[usn]) if manifestation == 'local': self.doNotify(usn) if st == 'upnp:rootdevice': louie.send('Coherence.UPnP.SSDP.new_device', None, device_type=st, infos=self.known[usn]) #self.callback("new_device", st, self.known[usn]) def unRegister(self, usn): self.msg("Un-registering %s" % usn) st = self.known[usn]['ST'] if st == 'upnp:rootdevice': louie.send('Coherence.UPnP.SSDP.removed_device', None, device_type=st, infos=self.known[usn]) #self.callback("removed_device", st, self.known[usn]) del self.known[usn] def isKnown(self, usn): return self.known.has_key(usn) def notifyReceived(self, headers, (host, port)): """Process a presence announcement. We just remember the details of the SSDP service announced.""" self.info('Notification from (%s,%d) for %s' % (host, port, headers['nt'])) self.debug('Notification headers:', headers) if headers['nts'] == 'ssdp:alive': try: self.known[headers['usn']]['last-seen'] = time.time() self.debug('updating last-seen for %r' % headers['usn']) except KeyError: self.register('remote', headers['usn'], headers['nt'], headers['location'], headers['server'], headers['cache-control'], host=host) elif headers['nts'] == 'ssdp:byebye': if self.isKnown(headers['usn']): self.unRegister(headers['usn']) else: self.warning('Unknown subtype %s for notification type %s' % (headers['nts'], headers['nt'])) louie.send('Coherence.UPnP.Log', None, 'SSDP', host, 'Notify %s for %s' % (headers['nts'], headers['usn'])) def send_it(self,response,destination,delay,usn): self.info('send discovery response delayed by %ds for %s to %r' % (delay,usn,destination)) try: self.transport.write(response,destination) except (AttributeError,socket.error), msg: self.info("failure sending out byebye notification: %r" % msg) def discoveryRequest(self, headers, (host, port)): """Process a discovery request. The response must be sent to the address specified by (host, port).""" self.info('Discovery request from (%s,%d) for %s' % (host, port, headers['st'])) self.info('Discovery request for %s' % headers['st']) louie.send('Coherence.UPnP.Log', None, 'SSDP', host, 'M-Search for %s' % headers['st']) # Do we know about this service? for i in self.known.values(): if i['MANIFESTATION'] == 'remote': continue if(headers['st'] == 'ssdp:all' and i['SILENT'] == True): continue if( i['ST'] == headers['st'] or headers['st'] == 'ssdp:all'): response = [] response.append('HTTP/1.1 200 OK') for k, v in i.items(): if k == 'USN': usn = v if k not in ('MANIFESTATION','SILENT','HOST'): response.append('%s: %s' % (k, v)) response.append('DATE: %s' % datetimeToString()) response.extend(('', '')) delay = random.randint(0, int(headers['mx'])) reactor.callLater(delay, self.send_it, '\r\n'.join(response), (host, port), delay, usn) def doNotify(self, usn): """Do notification""" if self.known[usn]['SILENT'] == True: return self.info('Sending alive notification for %s' % usn) resp = [ 'NOTIFY * HTTP/1.1', 'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT), 'NTS: ssdp:alive', ] stcpy = dict(self.known[usn].iteritems()) stcpy['NT'] = stcpy['ST'] del stcpy['ST'] del stcpy['MANIFESTATION'] del stcpy['SILENT'] del stcpy['HOST'] del stcpy['last-seen'] resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) resp.extend(('', '')) self.debug('doNotify content', resp) try: self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) except (AttributeError,socket.error), msg: self.info("failure sending out alive notification: %r" % msg) def doByebye(self, usn): """Do byebye""" self.info('Sending byebye notification for %s' % usn) resp = [ 'NOTIFY * HTTP/1.1', 'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT), 'NTS: ssdp:byebye', ] try: stcpy = dict(self.known[usn].iteritems()) stcpy['NT'] = stcpy['ST'] del stcpy['ST'] del stcpy['MANIFESTATION'] del stcpy['SILENT'] del stcpy['HOST'] del stcpy['last-seen'] resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) resp.extend(('', '')) self.debug('doByebye content', resp) if self.transport: try: self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) except (AttributeError,socket.error), msg: self.info("failure sending out byebye notification: %r" % msg) except KeyError, msg: self.debug("error building byebye notification: %r" % msg) def resendNotify( self): for usn in self.known: if self.known[usn]['MANIFESTATION'] == 'local': self.doNotify(usn) def check_valid(self): """ check if the discovered devices are still ok, or if we haven't received a new discovery response """ self.debug("Checking devices/services are still valid") removable = [] for usn in self.known: if self.known[usn]['MANIFESTATION'] != 'local': _,expiry = self.known[usn]['CACHE-CONTROL'].split('=') expiry = int(expiry) now = time.time() last_seen = self.known[usn]['last-seen'] self.debug("Checking if %r is still valid - last seen %d (+%d), now %d" % (self.known[usn]['USN'],last_seen,expiry,now)) if last_seen + expiry + 30 < now: self.debug("Expiring: %r" % self.known[usn]) if self.known[usn]['ST'] == 'upnp:rootdevice': louie.send('Coherence.UPnP.SSDP.removed_device', None, device_type=self.known[usn]['ST'], infos=self.known[usn]) removable.append(usn) while len(removable) > 0: usn = removable.pop(0) del self.known[usn] def subscribe(self, name, callback): self._callbacks.setdefault(name,[]).append(callback) def unsubscribe(self, name, callback): callbacks = self._callbacks.get(name,[]) if callback in callbacks: callbacks.remove(callback) self._callbacks[name] = callbacks def callback(self, name, *args): for callback in self._callbacks.get(name,[]): callback(*args) Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/0000755000000000000000000000000011317673117023072 5ustar rootrootCoherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/MediaRenderer2.xml0000644000175000017500000000174711317660735026037 0ustar devdev urn:schemas-upnp-org:service:RenderingControl:2 RenderingControl urn:schemas-upnp-org:service:ConnectionManager:2 ConnectionManager urn:schemas-upnp-org:service:AVTransport:2 AVTransport Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/Dimming1.xml0000644000175000017500000002466311317660735024716 0ustar devdev LoadLevelTarget no ui1 0 0 100 LoadLevelStatus yes ui1 0 0 100 MinLevel no ui1 0 0 100 OnEffectLevel no ui1 100 0 100 no OnEffect no string Default OnEffectLevel LastSetting Default ValidOutputValues no string StepDelta yes ui1 1 100 RampRate yes ui1 0 0 100 RampTime no ui4 0 0 4294967295 IsRamping yes boolean 0 RampPaused yes boolean 0 SetLoadLevelTarget NewLoadlevelTarget in LoadLevelTarget GetLoadLevelTarget GetLoadlevelTarget out LoadLevelTarget GetLoadLevelStatus RetLoadlevelStatus out LoadLevelStatus SetOnEffectLevel NewOnEffectLevel in OnEffectLevel SetOnEffect NewOnEffect in OnEffect GetOnEffectParameters RetOnEffect out OnEffect RetOnEffectLevel out OnEffectLevel StepUp StepDown StartRampUp StartRampDown StopRamp StartRampToLevel NewLoadLevelTarget in LoadLevelTarget NewRampTime in RampTime SetStepDelta NewStepDelta in StepDelta GetStepDelta RetStepDelta out StepDelta SetRampRate NewRampRate in RampRate GetRampRate RetRampRate out RampRate PauseRamp ResumeRamp GetIsRamping RetIsRamping out IsRamping GetRampPaused RetRampPaused out RampPaused GetRampTime RetRampTime out RampTime Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/X_MS_MediaReceiverRegistrar1.xml0000644000175000017500000000674211317660735030605 0ustar devdev 1 0 IsAuthorized DeviceID in A_ARG_TYPE_DeviceID Result out A_ARG_TYPE_Result RegisterDevice RegistrationReqMsg in A_ARG_TYPE_RegistrationReqMsg RegistrationRespMsg out A_ARG_TYPE_RegistrationRespMsg IsValidated DeviceID in A_ARG_TYPE_DeviceID Result out A_ARG_TYPE_Result A_ARG_TYPE_DeviceID no string A_ARG_TYPE_Result no int A_ARG_TYPE_RegistrationReqMsg no bin.base64 A_ARG_TYPE_RegistrationRespMsg no bin.base64 AuthorizationGrantedUpdateID no ui4 AuthorizationDeniedUpdateID no ui4 ValidationSucceededUpdateID no ui4 ValidationRevokedUpdateID no ui4 Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/ScheduledRecording1.xml0000644000175000017500000003251211317660735027057 0ustar devdev SortCapabilities no string SortLevelCapability no ui4 StateUpdateID no ui4 LastChange yes string A_ARG_TYPE_PropertyList no string A_ARG_TYPE_DataTypeID no string A_ARG_TYPE_RecordSchedule A_ARG_TYPE_RecordTask A_ARG_TYPE_RecordScheduleParts A_ARG_TYPE_ObjectID no string A_ARG_TYPE_ObjectIDList no string A_ARG_TYPE_PropertyInfo no string A_ARG_TYPE_Index no ui4 A_ARG_TYPE_Count no ui4 A_ARG_TYPE_SortCriteria no string A_ARG_TYPE_RecordSchedule no string A_ARG_TYPE_RecordTask no string A_ARG_TYPE_RecordScheduleParts no string GetSortCapabilities SortCaps out SortCapabilities SortLevelCap out SortLevelCapability GetPropertyList DataTypeID in A_ARG_TYPE_DataTypeID PropertyList out A_ARG_TYPE_PropertyList GetAllowedValues DataTypeID in A_ARG_TYPE_DataTypeID Filter in A_ARG_TYPE_PropertyList PropertyInfo out A_ARG_TYPE_PropertyInfo GetStateUpdateID Id out StateUpdateID BrowseRecordSchedules Filter in A_ARG_TYPE_PropertyList StartingIndex in A_ARG_TYPE_Index RequestedCount in A_ARG_TYPE_Count SortCriteria in A_ARG_TYPE_SortCriteria Result out A_ARG_TYPE_RecordSchedule NumberReturned out A_ARG_TYPE_Count TotalMatches out A_ARG_TYPE_Count UpdateID out StateUpdateID BrowseRecordTasks RecordScheduleID in A_ARG_TYPE_ObjectID Filter in A_ARG_TYPE_PropertyList StartingIndex in A_ARG_TYPE_Index RequestedCount in A_ARG_TYPE_Count SortCriteria in A_ARG_TYPE_SortCriteria Result out A_ARG_TYPE_RecordTask NumberReturned out A_ARG_TYPE_Count TotalMatches out A_ARG_TYPE_Count UpdateID out StateUpdateID CreateRecordSchedule Elements in A_ARG_TYPE_RecordScheduleParts RecordScheduleID out A_ARG_TYPE_ObjectID Result out A_ARG_TYPE_RecordSchedule UpdateID out StateUpdateID DeleteRecordSchedule RecordScheduleID in A_ARG_TYPE_ObjectID GetRecordSchedule RecordScheduleID in A_ARG_TYPE_ObjectID Filter in A_ARG_TYPE_PropertyList Result out A_ARG_TYPE_RecordSchedule UpdateID out StateUpdateID EnableRecordSchedule RecordScheduleID in A_ARG_TYPE_ObjectID DisableRecordSchedule RecordScheduleID in A_ARG_TYPE_ObjectID DeleteRecordTask RecordTaskID in A_ARG_TYPE_ObjectID GetRecordTask RecordTaskID in A_ARG_TYPE_ObjectID Filter in A_ARG_TYPE_PropertyList Result out A_ARG_TYPE_RecordTask UpdateID out StateUpdateID EnableRecordTask RecordTaskID in A_ARG_TYPE_ObjectID DisableRecordTask RecordTaskID in A_ARG_TYPE_ObjectID ResetRecordTask RecordTaskID in A_ARG_TYPE_ObjectID GetRecordScheduleConflicts RecordScheduleID in A_ARG_TYPE_ObjectID RecordScheduleConflictIDList out A_ARG_TYPE_ObjectIDList UpdateID out StateUpdateID GetRecordTaskConflicts RecordTaskID in A_ARG_TYPE_ObjectID RecordTaskConflictIDList out A_ARG_TYPE_ObjectIDList UpdateID out StateUpdateID Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/MediaServer2.xml0000644000175000017500000000222211317660735025524 0ustar devdev urn:schemas-upnp-org:service:ContentDirectory:2 ContentDirectory urn:schemas-upnp-org:service:ConnectionManager:2 ConnectionManager urn:schemas-upnp-org:service:AVTransport:2 AVTransport urn:schemas-upnp-org:service:ScheduledRecording:1 ScheduledRecording Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/RenderingControl2.xml0000644000175000017500000007127211317660735026607 0ustar devdev LastChange yes string PresetNameList no string Brightness no ui2 0 1 0 Contrast no ui2 0 1 0 Sharpness no ui2 0 1 0 RedVideoGain no ui2 0 1 0 GreenVideoGain no ui2 0 1 0 BlueVideoGain no ui2 0 1 0 RedVideoBlackLevel no ui2 0 1 0 GreenVideoBlackLevel no ui2 0 1 0 BlueVideoBlackLevel no ui2 0 1 0 ColorTemperature no ui2 0 1 0 HorizontalKeystone no i2 1 0 VerticalKeystone no i2 1 0 Mute no boolean False A_ARG_TYPE_Channel Volume no ui2 0 1 100 0 A_ARG_TYPE_Channel VolumeDB no i2 0 A_ARG_TYPE_Channel Loudness no boolean False A_ARG_TYPE_Channel A_ARG_TYPE_Channel no string Master LF RF CF LFE LS RS LFC RFC SD SL SR T B A_ARG_TYPE_InstanceID no ui4 A_ARG_TYPE_PresetName no string FactoryDefaults InstallationDefaults A_ARG_TYPE_DeviceUDN no string A_ARG_TYPE_ServiceType no string A_ARG_TYPE_ServiceID no string A_ARG_TYPE_StateVariableValuePairs no string A_ARG_TYPE_StateVariableList no string ListPresets InstanceID in A_ARG_TYPE_InstanceID CurrentPresetNameList out PresetNameList SelectPreset InstanceID in A_ARG_TYPE_InstanceID PresetName in A_ARG_TYPE_PresetName GetBrightness InstanceID in A_ARG_TYPE_InstanceID CurrentBrightness out Brightness SetBrightness InstanceID in A_ARG_TYPE_InstanceID DesiredBrightness in Brightness GetContrast InstanceID in A_ARG_TYPE_InstanceID CurrentContrast out Contrast SetContrast InstanceID in A_ARG_TYPE_InstanceID DesiredContrast in Contrast GetSharpness InstanceID in A_ARG_TYPE_InstanceID CurrentSharpness out Sharpness SetSharpness InstanceID in A_ARG_TYPE_InstanceID DesiredSharpness in Sharpness GetRedVideoGain InstanceID in A_ARG_TYPE_InstanceID CurrentRedVideoGain out RedVideoGain SetRedVideoGain InstanceID in A_ARG_TYPE_InstanceID DesiredRedVideoGain in RedVideoGain GetGreenVideoGain InstanceID in A_ARG_TYPE_InstanceID CurrentGreenVideoGain out GreenVideoGain SetGreenVideoGain InstanceID in A_ARG_TYPE_InstanceID DesiredGreenVideoGain in GreenVideoGain GetBlueVideoGain InstanceID in A_ARG_TYPE_InstanceID CurrentBlueVideoGain out BlueVideoGain SetBlueVideoGain InstanceID in A_ARG_TYPE_InstanceID DesiredBlueVideoGain in BlueVideoGain GetRedVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID CurrentRedVideoBlackLevel out RedVideoBlackLevel SetRedVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID DesiredRedVideoBlackLevel in RedVideoBlackLevel GetGreenVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID CurrentGreenVideoBlackLevel out GreenVideoBlackLevel SetGreenVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID DesiredGreenVideoBlackLevel in GreenVideoBlackLevel GetBlueVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID CurrentBlueVideoBlackLevel out BlueVideoBlackLevel SetBlueVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID DesiredBlueVideoBlackLevel in BlueVideoBlackLevel GetColorTemperature InstanceID in A_ARG_TYPE_InstanceID CurrentColorTemperature out ColorTemperature SetColorTemperature InstanceID in A_ARG_TYPE_InstanceID DesiredColorTemperature in ColorTemperature GetHorizontalKeystone InstanceID in A_ARG_TYPE_InstanceID CurrentHorizontalKeystone out HorizontalKeystone SetHorizontalKeystone InstanceID in A_ARG_TYPE_InstanceID DesiredHorizontalKeystone in HorizontalKeystone GetVerticalKeystone InstanceID in A_ARG_TYPE_InstanceID CurrentVerticalKeystone out VerticalKeystone SetVerticalKeystone InstanceID in A_ARG_TYPE_InstanceID DesiredVerticalKeystone in VerticalKeystone GetMute InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentMute out Mute SetMute InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredMute in Mute GetVolume InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentVolume out Volume SetVolume InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredVolume in Volume GetVolumeDB InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentVolume out VolumeDB SetVolumeDB InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredVolume in VolumeDB GetVolumeDBRange InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel MinValue out VolumeDB MaxValue out VolumeDB GetLoudness InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentLoudness out Loudness SetLoudness InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredLoudness in Loudness GetStateVariables InstanceID in A_ARG_TYPE_InstanceID StateVariableList in A_ARG_TYPE_StateVariableList StateVariableValuePairs out A_ARG_TYPE_StateVariableValuePairs SetStateVariables InstanceID in A_ARG_TYPE_InstanceID RenderingControlUDN in A_ARG_TYPE_DeviceUDN ServiceType in A_ARG_TYPE_ServiceType ServiceId in A_ARG_TYPE_ServiceID StateVariableValuePairs in A_ARG_TYPE_StateVariableValuePairs StateVariableList out A_ARG_TYPE_StateVariableList Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/ContentDirectory1.xml0000644000175000017500000003356011317660735026625 0ustar devdev TransferIDs yes string A_ARG_TYPE_ObjectID no string A_ARG_TYPE_Result no string A_ARG_TYPE_SearchCriteria no string A_ARG_TYPE_BrowseFlag no string BrowseMetadata BrowseDirectChildren A_ARG_TYPE_Filter no string A_ARG_TYPE_SortCriteria no string A_ARG_TYPE_Index no ui4 A_ARG_TYPE_Count no ui4 A_ARG_TYPE_UpdateID no ui4 A_ARG_TYPE_TransferID no ui4 A_ARG_TYPE_TransferStatus no string COMPLETED ERROR IN_PROGRESS STOPPED A_ARG_TYPE_TransferLength no string A_ARG_TYPE_TransferTotal no string A_ARG_TYPE_TagValueList no string A_ARG_TYPE_URI no uri SearchCapabilities no string SortCapabilities no string SystemUpdateID yes ui4 ContainerUpdateIDs yes string GetSearchCapabilities SearchCaps out SearchCapabilities GetSortCapabilities SortCaps out SortCapabilities GetSystemUpdateID Id out SystemUpdateID Browse ObjectID in A_ARG_TYPE_ObjectID BrowseFlag in A_ARG_TYPE_BrowseFlag Filter in A_ARG_TYPE_Filter StartingIndex in A_ARG_TYPE_Index RequestedCount in A_ARG_TYPE_Count SortCriteria in A_ARG_TYPE_SortCriteria Result out A_ARG_TYPE_Result NumberReturned out A_ARG_TYPE_Count TotalMatches out A_ARG_TYPE_Count UpdateID out A_ARG_TYPE_UpdateID Search ContainerID in A_ARG_TYPE_ObjectID SearchCriteria in A_ARG_TYPE_SearchCriteria Filter in A_ARG_TYPE_Filter StartingIndex in A_ARG_TYPE_Index RequestedCount in A_ARG_TYPE_Count SortCriteria in A_ARG_TYPE_SortCriteria Result out A_ARG_TYPE_Result NumberReturned out A_ARG_TYPE_Count TotalMatches out A_ARG_TYPE_Count UpdateID out A_ARG_TYPE_UpdateID CreateObject ContainerID in A_ARG_TYPE_ObjectID Elements in A_ARG_TYPE_Result ObjectID out A_ARG_TYPE_ObjectID Result out A_ARG_TYPE_Result DestroyObject ObjectID in A_ARG_TYPE_ObjectID UpdateObject ObjectID in A_ARG_TYPE_ObjectID CurrentTagValue in A_ARG_TYPE_TagValueList NewTagValue in A_ARG_TYPE_TagValueList ImportResource SourceURI in A_ARG_TYPE_URI DestinationURI in A_ARG_TYPE_URI TransferID out A_ARG_TYPE_TransferID ExportResource SourceURI in A_ARG_TYPE_URI DestinationURI in A_ARG_TYPE_URI TransferID out A_ARG_TYPE_TransferID StopTransferResource TransferID in A_ARG_TYPE_TransferID GetTransferProgress TransferID in A_ARG_TYPE_TransferID TransferStatus out A_ARG_TYPE_TransferStatus TransferLength out A_ARG_TYPE_TransferLength TransferTotal out A_ARG_TYPE_TransferTotal DeleteResource ResourceURI in A_ARG_TYPE_URI CreateReference ContainerID in A_ARG_TYPE_ObjectID ObjectID in A_ARG_TYPE_ObjectID NewID out A_ARG_TYPE_ObjectID Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/AVTransport2.xml0000644000175000017500000007114411317660735025552 0ustar devdev TransportState no string STOPPED PAUSED_PLAYBACK PAUSED_RECORDING PLAYING RECORDING TRANSITIONING NO_MEDIA_PRESENT NO_MEDIA_PRESENT TransportStatus no string OK ERROR_OCCURRED OK CurrentMediaCategory no string NO_MEDIA TRACK_AWARE TRACK_UNAWARE NO_MEDIA PlaybackStorageMedium no string UNKNOWN DV MINI-DV VHS W-VHS S-VHS D-VHS VHSC VIDEO8 HI8 CD-ROM CD-DA CD-R CD-RW VIDEO-CD SACD MD-AUDIO MD-PICTURE DVD-ROM DVD-VIDEO DVD-R DVD+RW DVD-RW DVD-RAM DVD-AUDIO DAT LD HDD MV NETWORK NONE NOT_IMPLEMENTED SD PC-CARD MMC CF BD MS HD_DVD NONE RecordStorageMedium no string UNKNOWN DV MINI-DV VHS W-VHS S-VHS D-VHS VHSC VIDEO8 HI8 CD-ROM CD-DA CD-R CD-RW VIDEO-CD SACD MD-AUDIO MD-PICTURE DVD-ROM DVD-VIDEO DVD-R DVD+RW DVD-RW DVD-RAM DVD-AUDIO DAT LD HDD MV NETWORK NONE NOT_IMPLEMENTED SD PC-CARD MMC CF BD MS HD_DVD NOT_IMPLEMENTED PossiblePlaybackStorageMedia no string NETWORK PossibleRecordStorageMedia no string NOT_IMPLEMENTED CurrentPlayMode no string NORMAL SHUFFLE REPEAT_ONE REPEAT_ALL RANDOM DIRECT_1 INTRO NORMAL TransportPlaySpeed no string 1 1 RecordMediumWriteStatus no string NOT_IMPLEMENTED CurrentRecordQualityMode no string WRITABLE PROTECTED NOT_WRITABLE UNKNOWN NOT_IMPLEMENTED NOT_IMPLEMENTED PossibleRecordQualityModes no string 0:EP 1:LP 2:SP 0:BASIC 1:MEDIUM 2:HIGH NOT_IMPLEMENTED NOT_IMPLEMENTED NumberOfTracks no ui4 0 0 CurrentTrack no ui4 0 1 0 CurrentTrackDuration no string 00:00:00 NOT_IMPLEMENTED 00:00:00 CurrentMediaDuration no string 00:00:00 NOT_IMPLEMENTED 00:00:00 CurrentTrackMetaData no string NOT_IMPLEMENTED CurrentTrackURI no string AVTransportURI no string AVTransportURIMetaData no string NOT_IMPLEMENTED NextAVTransportURI no string NextAVTransportURIMetaData no string NOT_IMPLEMENTED RelativeTimePosition no string 00:00:00 NOT_IMPLEMENTED 00:00:00 AbsoluteTimePosition no string 00:00:00 NOT_IMPLEMENTED END_OF_MEDIA 00:00:00 RelativeCounterPosition no i4 -2147483646 2147483647 0 AbsoluteCounterPosition no i4 0 2147483647 0 CurrentTransportActions no string LastChange yes string DRMState no string OK UNKNOWN PROCESSING_CONTENT_KEY CONTENT_KEY_FAILURE ATTEMPTING_AUTHENTICATION FAILED_AUTHENTICATION NOT_AUTHENTICATED DEVICE_REVOCATION UNKNOWN A_ARG_TYPE_SeekMode no string ABS_TIME REL_TIME ABS_COUNT REL_COUNT TRACK_NR CHANNEL_FREQ TAPE-INDEX FRAME A_ARG_TYPE_SeekTarget no string A_ARG_TYPE_InstanceID no ui4 A_ARG_TYPE_DeviceUDN no string A_ARG_TYPE_ServiceType no string A_ARG_TYPE_ServiceID no string A_ARG_TYPE_StateVariableValuePairs no string A_ARG_TYPE_StateVariableList no string SetAVTransportURI InstanceID in A_ARG_TYPE_InstanceID CurrentURI in AVTransportURI CurrentURIMetaData in AVTransportURIMetaData SetNextAVTransportURI InstanceID in A_ARG_TYPE_InstanceID NextURI in NextAVTransportURI NextURIMetaData in NextAVTransportURIMetaData GetMediaInfo InstanceID in A_ARG_TYPE_InstanceID NrTracks out NumberOfTracks MediaDuration out CurrentMediaDuration CurrentURI out AVTransportURI CurrentURIMetaData out AVTransportURIMetaData NextURI out NextAVTransportURI NextURIMetaData out NextAVTransportURIMetaData PlayMedium out PlaybackStorageMedium RecordMedium out RecordStorageMedium WriteStatus out RecordMediumWriteStatus GetMediaInfo_Ext InstanceID in A_ARG_TYPE_InstanceID CurrentType out CurrentMediaCategory NrTracks out NumberOfTracks MediaDuration out CurrentMediaDuration CurrentURI out AVTransportURI CurrentURIMetaData out AVTransportURIMetaData NextURI out NextAVTransportURI NextURIMetaData out NextAVTransportURIMetaData PlayMedium out PlaybackStorageMedium RecordMedium out RecordStorageMedium WriteStatus out RecordMediumWriteStatus GetTransportInfo InstanceID in A_ARG_TYPE_InstanceID CurrentTransportState out TransportState CurrentTransportStatus out TransportStatus CurrentSpeed out TransportPlaySpeed GetPositionInfo InstanceID in A_ARG_TYPE_InstanceID Track out CurrentTrack TrackDuration out CurrentTrackDuration TrackMetaData out CurrentTrackMetaData TrackURI out CurrentTrackURI RelTime out RelativeTimePosition AbsTime out AbsoluteTimePosition RelCount out RelativeCounterPosition AbsCount out AbsoluteCounterPosition GetDeviceCapabilities InstanceID in A_ARG_TYPE_InstanceID PlayMedia out PossiblePlaybackStorageMedia RecMedia out PossibleRecordStorageMedia RecQualityModes out PossibleRecordQualityModes GetTransportSettings InstanceID in A_ARG_TYPE_InstanceID PlayMode out CurrentPlayMode RecQualityMode out CurrentRecordQualityMode Stop InstanceID in A_ARG_TYPE_InstanceID Play InstanceID in A_ARG_TYPE_InstanceID Speed in TransportPlaySpeed Pause InstanceID in A_ARG_TYPE_InstanceID Record InstanceID in A_ARG_TYPE_InstanceID Seek InstanceID in A_ARG_TYPE_InstanceID Unit in A_ARG_TYPE_SeekMode Target in A_ARG_TYPE_SeekTarget Next InstanceID in A_ARG_TYPE_InstanceID Previous InstanceID in A_ARG_TYPE_InstanceID SetPlayMode InstanceID in A_ARG_TYPE_InstanceID NewPlayMode in CurrentPlayMode SetRecordQualityMode InstanceID in A_ARG_TYPE_InstanceID NewRecordQualityMode in CurrentRecordQualityMode GetCurrentTransportActions InstanceID in A_ARG_TYPE_InstanceID Actions out CurrentTransportActions GetDRMState InstanceID in A_ARG_TYPE_InstanceID CurrentDRMState out DRMState GetStateVariables InstanceID in A_ARG_TYPE_InstanceID StateVariableList in A_ARG_TYPE_StateVariableList StateVariableValuePairs out A_ARG_TYPE_StateVariableValuePairs SetStateVariables InstanceID in A_ARG_TYPE_InstanceID AVTransportUDN in A_ARG_TYPE_DeviceUDN ServiceType in A_ARG_TYPE_ServiceType ServiceId in A_ARG_TYPE_ServiceID StateVariableValuePairs in A_ARG_TYPE_StateVariableValuePairs StateVariableList out A_ARG_TYPE_StateVariableList Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/ConnectionManager1.xml0000644000175000017500000001727611317660735026726 0ustar devdev SourceProtocolInfo yes string SinkProtocolInfo yes string CurrentConnectionIDs yes string A_ARG_TYPE_ConnectionStatus no string OK ContentFormatMismatch InsufficientBandwidth UnreliableChannel Unknown A_ARG_TYPE_ConnectionManager no string A_ARG_TYPE_Direction no string Input Output A_ARG_TYPE_ProtocolInfo no string A_ARG_TYPE_ConnectionID no i4 A_ARG_TYPE_AVTransportID no i4 A_ARG_TYPE_RcsID no i4 GetProtocolInfo Source out SourceProtocolInfo Sink out SinkProtocolInfo PrepareForConnection RemoteProtocolInfo in A_ARG_TYPE_ProtocolInfo PeerConnectionManager in A_ARG_TYPE_ConnectionManager PeerConnectionID in A_ARG_TYPE_ConnectionID Direction in A_ARG_TYPE_Direction ConnectionID out A_ARG_TYPE_ConnectionID AVTransportID out A_ARG_TYPE_AVTransportID RcsID out A_ARG_TYPE_RcsID ConnectionComplete ConnectionID in A_ARG_TYPE_ConnectionID GetCurrentConnectionIDs ConnectionIDs out CurrentConnectionIDs GetCurrentConnectionInfo ConnectionID in A_ARG_TYPE_ConnectionID RcsID out A_ARG_TYPE_RcsID AVTransportID out A_ARG_TYPE_AVTransportID ProtocolInfo out A_ARG_TYPE_ProtocolInfo PeerConnectionManager out A_ARG_TYPE_ConnectionManager PeerConnectionID out A_ARG_TYPE_ConnectionID Direction out A_ARG_TYPE_Direction Status out A_ARG_TYPE_ConnectionStatus Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/ConnectionManager2.xml0000644000175000017500000001452711317660735026723 0ustar devdev SourceProtocolInfo yes string SinkProtocolInfo yes string CurrentConnectionIDs yes string A_ARG_TYPE_ConnectionStatus no string OK ContentFormatMismatch InsufficientBandwidth UnreliableChannel Unknown A_ARG_TYPE_ConnectionManager no string A_ARG_TYPE_Direction no string Input Output A_ARG_TYPE_ProtocolInfo no string A_ARG_TYPE_ConnectionID no i4 A_ARG_TYPE_AVTransportID no i4 A_ARG_TYPE_RcsID no i4 GetProtocolInfo Source out SourceProtocolInfo Sink out SinkProtocolInfo PrepareForConnection RemoteProtocolInfo in A_ARG_TYPE_ProtocolInfo PeerConnectionManager in A_ARG_TYPE_ConnectionManager PeerConnectionID in A_ARG_TYPE_ConnectionID Direction in A_ARG_TYPE_Direction ConnectionID out A_ARG_TYPE_ConnectionID AVTransportID out A_ARG_TYPE_AVTransportID RcsID out A_ARG_TYPE_RcsID ConnectionComplete ConnectionID in A_ARG_TYPE_ConnectionID GetCurrentConnectionIDs ConnectionIDs out CurrentConnectionIDs GetCurrentConnectionInfo ConnectionID in A_ARG_TYPE_ConnectionID RcsID out A_ARG_TYPE_RcsID AVTransportID out A_ARG_TYPE_AVTransportID ProtocolInfo out A_ARG_TYPE_ProtocolInfo PeerConnectionManager out A_ARG_TYPE_ConnectionManager PeerConnectionID out A_ARG_TYPE_ConnectionID Direction out A_ARG_TYPE_Direction Status out A_ARG_TYPE_ConnectionStatus Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/AVTransport1.xml0000644000175000017500000005274111317660735025553 0ustar devdev TransportState no string STOPPED PLAYING TransportStatus no string OK ERROR_OCCURRED PlaybackStorageMedium no string RecordStorageMedium no string PossiblePlaybackStorageMedia no string PossibleRecordStorageMedia no string CurrentPlayMode no string NORMAL NORMAL TransportPlaySpeed no string 1 no RecordMediumWriteStatus string CurrentRecordQualityMode no string PossibleRecordQualityModes no string NumberOfTracks no ui4 0 CurrentTrack no ui4 0 1 CurrentTrackDuration no string CurrentMediaDuration no string CurrentTrackMetaData no string CurrentTrackURI no string AVTransportURI no string AVTransportURIMetaData no string NextAVTransportURI no string NextAVTransportURIMetaData no string RelativeTimePosition no string AbsoluteTimePosition no string RelativeCounterPosition no i4 AbsoluteCounterPosition no i4 CurrentTransportActions no string LastChange yes string A_ARG_TYPE_SeekMode no string TRACK_NR A_ARG_TYPE_SeekTarget no string A_ARG_TYPE_InstanceID no ui4 SetAVTransportURI InstanceID in A_ARG_TYPE_InstanceID CurrentURI in AVTransportURI CurrentURIMetaData in AVTransportURIMetaData SetNextAVTransportURI InstanceID in A_ARG_TYPE_InstanceID NextURI in NextAVTransportURI NextURIMetaData in NextAVTransportURIMetaData GetMediaInfo InstanceID in A_ARG_TYPE_InstanceID NrTracks out NumberOfTracks MediaDuration out CurrentMediaDuration CurrentURI out AVTransportURI CurrentURIMetaData out AVTransportURIMetaData NextURI out NextAVTransportURI NextURIMetaData out NextAVTransportURIMetaData PlayMedium out PlaybackStorageMedium RecordMedium out RecordStorageMedium WriteStatus out RecordMediumWriteStatus GetTransportInfo InstanceID in A_ARG_TYPE_InstanceID CurrentTransportState out TransportState CurrentTransportStatus out TransportStatus CurrentSpeed out TransportPlaySpeed GetPositionInfo InstanceID in A_ARG_TYPE_InstanceID Track out CurrentTrack TrackDuration out CurrentTrackDuration TrackMetaData out CurrentTrackMetaData TrackURI out CurrentTrackURI RelTime out RelativeTimePosition AbsTime out AbsoluteTimePosition RelCount out RelativeCounterPosition AbsCount out AbsoluteCounterPosition GetDeviceCapabilities InstanceID in A_ARG_TYPE_InstanceID PlayMedia out PossiblePlaybackStorageMedia RecMedia out PossibleRecordStorageMedia RecQualityModes out PossibleRecordQualityModes GetTransportSettings InstanceID in A_ARG_TYPE_InstanceID PlayMode out CurrentPlayMode RecQualityMode out CurrentRecordQualityMode Stop InstanceID in A_ARG_TYPE_InstanceID Play InstanceID in A_ARG_TYPE_InstanceID Speed in TransportPlaySpeed Pause InstanceID in A_ARG_TYPE_InstanceID Record InstanceID in A_ARG_TYPE_InstanceID Seek InstanceID in A_ARG_TYPE_InstanceID Unit in A_ARG_TYPE_SeekMode Target in A_ARG_TYPE_SeekTarget Next InstanceID in A_ARG_TYPE_InstanceID Previous InstanceID in A_ARG_TYPE_InstanceID SetPlayMode InstanceID in A_ARG_TYPE_InstanceID NewPlayMode in CurrentPlayMode SetRecordQualityMode InstanceID in A_ARG_TYPE_InstanceID NewRecordQualityMode in CurrentRecordQualityMode GetCurrentTransportActions InstanceID in A_ARG_TYPE_InstanceID Actions out CurrentTransportActions Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/RenderingControl1.xml0000644000175000017500000006132611317660735026605 0ustar devdev PresetNameList no string LastChange yes string Brightness no ui2 0 1 Contrast no ui2 0 1 Sharpness no ui2 0 1 RedVideoGain no ui2 GreenVideoGain no ui2 0 1 BlueVideoGain no ui2 0 1 RedVideoBlackLevel no ui2 0 1 GreenVideoBlackLevel no ui2 0 1 BlueVideoBlackLevel no ui2 0 1 ColorTemperature no ui2 0 1 HorizontalKeystone no i2 1 VerticalKeystone no i2 1 Mute no boolean Volume no ui2 0 1 VolumeDB no i2 Loudness no boolean A_ARG_TYPE_Channel no string Master A_ARG_TYPE_InstanceID no ui4 A_ARG_TYPE_PresetName no string FactoryDefaults ListPresets InstanceID in A_ARG_TYPE_InstanceID CurrentPresetNameList out PresetNameList SelectPreset InstanceID in A_ARG_TYPE_InstanceID PresetName in A_ARG_TYPE_PresetName GetBrightness InstanceID in A_ARG_TYPE_InstanceID CurrentBrightness out Brightness SetBrightness InstanceID in A_ARG_TYPE_InstanceID DesiredBrightness in Brightness GetContrast InstanceID in A_ARG_TYPE_InstanceID CurrentContrast out Contrast SetContrast InstanceID in A_ARG_TYPE_InstanceID DesiredContrast in Contrast GetSharpness InstanceID in A_ARG_TYPE_InstanceID CurrentSharpness out Sharpness SetSharpness InstanceID in A_ARG_TYPE_InstanceID DesiredSharpness in Sharpness GetRedVideoGain InstanceID in A_ARG_TYPE_InstanceID CurrentRedVideoGain out RedVideoGain SetRedVideoGain InstanceID in A_ARG_TYPE_InstanceID DesiredRedVideoGain in RedVideoGain GetGreenVideoGain InstanceID in A_ARG_TYPE_InstanceID CurrentGreenVideoGain out GreenVideoGain SetGreenVideoGain InstanceID in A_ARG_TYPE_InstanceID DesiredGreenVideoGain in GreenVideoGain GetBlueVideoGain InstanceID in A_ARG_TYPE_InstanceID CurrentBlueVideoGain out BlueVideoGain SetBlueVideoGain InstanceID in A_ARG_TYPE_InstanceID DesiredBlueVideoGain in BlueVideoGain GetRedVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID CurrentRedVideoBlackLevel out RedVideoBlackLevel SetRedVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID DesiredRedVideoBlackLevel in RedVideoBlackLevel GetGreenVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID CurrentGreenVideoBlackLevel out GreenVideoBlackLevel SetGreenVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID DesiredGreenVideoBlackLevel in GreenVideoBlackLevel GetBlueVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID CurrentBlueVideoBlackLevel out BlueVideoBlackLevel SetBlueVideoBlackLevel InstanceID in A_ARG_TYPE_InstanceID DesiredBlueVideoBlackLevel in BlueVideoBlackLevel GetColorTemperature InstanceID in A_ARG_TYPE_InstanceID CurrentColorTemperature out ColorTemperature SetColorTemperature InstanceID in A_ARG_TYPE_InstanceID DesiredColorTemperature in ColorTemperature GetHorizontalKeystone InstanceID in A_ARG_TYPE_InstanceID CurrentHorizontalKeystone out HorizontalKeystone SetHorizontalKeystone InstanceID in A_ARG_TYPE_InstanceID DesiredHorizontalKeystone in HorizontalKeystone GetVerticalKeystone InstanceID in A_ARG_TYPE_InstanceID CurrentVerticalKeystone out VerticalKeystone SetVerticalKeystone InstanceID in A_ARG_TYPE_InstanceID DesiredVerticalKeystone in VerticalKeystone GetMute InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentMute out Mute SetMute InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredMute in Mute GetVolume InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentVolume out Volume SetVolume InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredVolume in Volume GetVolumeDB InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentVolume out VolumeDB SetVolumeDB InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredVolume in VolumeDB GetVolumeDBRange InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel MinValue out VolumeDB MaxValue out VolumeDB GetLoudness InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel CurrentLoudness out Loudness SetLoudness InstanceID in A_ARG_TYPE_InstanceID Channel in A_ARG_TYPE_Channel DesiredLoudness in Loudness Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/ContentDirectory2.xml0000644000175000017500000003622211317660735026624 0ustar devdev SearchCapabilities no string SortCapabilities no string SortExtensionCapabilities no string SystemUpdateID yes ui4 0 ContainerUpdateIDs yes string TransferIDs yes string FeatureList no string A_ARG_TYPE_ObjectID no string A_ARG_TYPE_Result no string A_ARG_TYPE_SearchCriteria no string A_ARG_TYPE_BrowseFlag no string BrowseMetadata BrowseDirectChildren A_ARG_TYPE_Filter no string A_ARG_TYPE_SortCriteria no string A_ARG_TYPE_Index no ui4 A_ARG_TYPE_Count no ui4 A_ARG_TYPE_UpdateID no ui4 A_ARG_TYPE_TransferID no ui4 A_ARG_TYPE_TransferStatus no string COMPLETED ERROR IN_PROGRESS STOPPED A_ARG_TYPE_TransferLength no string A_ARG_TYPE_TransferTotal no string A_ARG_TYPE_TagValueList no string A_ARG_TYPE_URI no uri GetSearchCapabilities SearchCaps out SearchCapabilities GetSortCapabilities SortCaps out SortCapabilities GetSortExtensionCapabilities SortExtensionCaps out SortExtensionCapabilities GetFeatureList FeatureList out FeatureList GetSystemUpdateID Id out SystemUpdateID Browse ObjectID in A_ARG_TYPE_ObjectID BrowseFlag in A_ARG_TYPE_BrowseFlag Filter in A_ARG_TYPE_Filter StartingIndex in A_ARG_TYPE_Index RequestedCount in A_ARG_TYPE_Count SortCriteria in A_ARG_TYPE_SortCriteria Result out A_ARG_TYPE_Result NumberReturned out A_ARG_TYPE_Count TotalMatches out A_ARG_TYPE_Count UpdateID out A_ARG_TYPE_UpdateID Search ContainerID in A_ARG_TYPE_ObjectID SearchCriteria in A_ARG_TYPE_SearchCriteria Filter in A_ARG_TYPE_Filter StartingIndex in A_ARG_TYPE_Index RequestedCount in A_ARG_TYPE_Count SortCriteria in A_ARG_TYPE_SortCriteria Result out A_ARG_TYPE_Result NumberReturned out A_ARG_TYPE_Count TotalMatches out A_ARG_TYPE_Count UpdateID out A_ARG_TYPE_UpdateID CreateObject ContainerID in A_ARG_TYPE_ObjectID Elements in A_ARG_TYPE_Result ObjectID out A_ARG_TYPE_ObjectID Result out A_ARG_TYPE_Result DestroyObject ObjectID in A_ARG_TYPE_ObjectID UpdateObject ObjectID in A_ARG_TYPE_ObjectID CurrentTagValue in A_ARG_TYPE_TagValueList NewTagValue in A_ARG_TYPE_TagValueList MoveObject objectID in A_ARG_TYPE_ObjectID NewParentID in A_ARG_TYPE_ObjectID NewObjectID out A_ARG_TYPE_ObjectID ImportResource SourceURI in A_ARG_TYPE_URI DestinationURI in A_ARG_TYPE_URI TransferID out A_ARG_TYPE_TransferID ExportResource SourceURI in A_ARG_TYPE_URI DestinationURI in A_ARG_TYPE_URI TransferID out A_ARG_TYPE_TransferID DeleteResource ResourceURI in A_ARG_TYPE_URI StopTransferResource TransferID in A_ARG_TYPE_TransferID GetTransferProgress TransferID in A_ARG_TYPE_TransferID TransferStatus out A_ARG_TYPE_TransferStatus TransferLength out A_ARG_TYPE_TransferLength TransferTotal out A_ARG_TYPE_TransferTotal CreateReference ContainerID in A_ARG_TYPE_ObjectID ObjectID in A_ARG_TYPE_ObjectID NewID out A_ARG_TYPE_ObjectID Coherence-0.6.6.2/coherence/upnp/core/xml-service-descriptions/SwitchPower1.xml0000644000175000017500000000314011317660735025573 0ustar devdev Target no boolean 0 Status yes boolean 0 SetTarget NewTargetValue in Target GetTarget RetTargetValue out Target GetStatus ResultStatus out Status Coherence-0.6.6.2/coherence/upnp/core/event.py0000644000175000017500000003710211317660735017250 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006,2007,2008,2009 Frank Scholz import time from urlparse import urlsplit from twisted.internet import reactor, defer from twisted.web import resource, server from twisted.web.http import datetimeToString from twisted.internet.protocol import Protocol, ClientCreator, _InstanceFactory from twisted.python import failure from coherence import log, SERVER_ID from coherence.upnp.core import utils import coherence.extern.louie as louie global hostname, web_server_port hostname = None web_server_port = None class EventServer(resource.Resource, log.Loggable): logCategory = 'event_server' def __init__(self, control_point): self.coherence = control_point.coherence self.control_point = control_point self.coherence.add_web_resource('events', self) global hostname, web_server_port hostname = self.coherence.hostname web_server_port = self.coherence.web_server_port self.info("EventServer ready...") def render_NOTIFY(self, request): self.info("EventServer received notify from %s, code: %d" % (request.client, request.code)) data = request.content.getvalue() request.setResponseCode(200) command = {'method': request.method, 'path': request.path} headers = request.received_headers louie.send('UPnP.Event.Server.message_received', None, command, headers, data) if request.code != 200: self.info("data:", data) else: self.debug("data:", data) headers = request.getAllHeaders() sid = headers['sid'] try: tree = utils.parse_xml(data).getroot() except (SyntaxError,AttributeError): self.warning("malformed event notification from %r", request.client) self.debug("data: %r", data) request.setResponseCode(400) return "" event = Event(sid,tree,raw=data) if len(event) != 0: self.control_point.propagate(event) return "" class EventSubscriptionServer(resource.Resource, log.Loggable): """ This class ist the server part on the device side. It listens to subscribe requests and registers the subscriber to send event messages to this device. If an unsubscribe request is received, the subscription is cancelled and no more event messages will be sent. we receive a subscription request {'callback': '', 'host': '192.168.213.107:30020', 'nt': 'upnp:event', 'content-length': '0', 'timeout': 'Second-300'} modify the callback value callback = callback[1:len(callback)-1] and pack it into a subscriber dict {'uuid:oAQbxiNlyYojCAdznJnC': {'callback': '', 'created': 1162374189.257338, 'timeout': 'Second-300', 'sid': 'uuid:oAQbxiNlyYojCAdznJnC'}} """ logCategory = 'event_subscription_server' def __init__(self, service): resource.Resource.__init__(self) log.Loggable.__init__(self) self.service = service self.subscribers = service.get_subscribers() try: self.backend_name = self.service.backend.name except AttributeError: self.backend_name = self.service.backend def render_SUBSCRIBE(self, request): self.info( "EventSubscriptionServer %s (%s) received subscribe request from %s, code: %d" % ( self.service.id, self.backend_name, request.client, request.code)) data = request.content.getvalue() request.setResponseCode(200) command = {'method': request.method, 'path': request.path} headers = request.received_headers louie.send('UPnP.Event.Client.message_received', None, command, headers, data) if request.code != 200: self.debug("data:", data) else: headers = request.getAllHeaders() try: #print self.subscribers #print headers['sid'] if self.subscribers.has_key(headers['sid']): s = self.subscribers[headers['sid']] s['timeout'] = headers['timeout'] s['created'] = time.time() elif not headers.has_key('callback'): request.setResponseCode(404) request.setHeader('SERVER', SERVER_ID) request.setHeader('CONTENT-LENGTH', 0) return "" except: from uuid import UUID sid = UUID() s = { 'sid' : str(sid), 'callback' : headers['callback'][1:len(headers['callback'])-1], 'seq' : 0} s['timeout'] = headers['timeout'] s['created'] = time.time() self.service.new_subscriber(s) request.setHeader('SID', s['sid']) #request.setHeader('Subscription-ID', sid) wrong example in the UPnP UUID spec? request.setHeader('TIMEOUT', s['timeout']) request.setHeader('SERVER', SERVER_ID) request.setHeader('CONTENT-LENGTH', 0) return "" def render_UNSUBSCRIBE(self, request): self.info( "EventSubscriptionServer %s (%s) received unsubscribe request from %s, code: %d" % ( self.service.id, self.backend_name, request.client, request.code)) data = request.content.getvalue() request.setResponseCode(200) command = {'method': request.method, 'path': request.path} headers = request.received_headers louie.send('UPnP.Event.Client.message_received', None, command, headers, data) if request.code != 200: self.debug("data:", data) else: headers = request.getAllHeaders() try: del self.subscribers[headers['sid']] except: """ XXX if not found set right error code """ pass #print self.subscribers return "" class Event(dict, log.Loggable): logCategory = 'event' ns = "urn:schemas-upnp-org:event-1-0" def __init__(self, sid,elements=None,raw=None): dict.__init__(self) self._sid = sid self.raw = raw if elements != None: self.from_elements(elements) def get_sid(self): return self._sid def from_elements(self,elements): for prop in elements.findall('{%s}property' % self.ns): self._update_event(prop) if len(self) == 0: self.warning("event notification without property elements") self.debug("data: %r", self.raw) for prop in elements.findall('property'): self._update_event(prop) def _update_event(self,prop): for var in prop.getchildren(): tag = var.tag idx = tag.find('}') + 1 value = var.text if value == None: value = '' self.update({tag[idx:]: value}) class EventProtocol(Protocol, log.Loggable): logCategory = 'event_protocol' def __init__(self, service, action): self.service = service self.action = action def teardown(self): self.transport.loseConnection() self.service.event_connection = None def connectionMade(self): self.timeout_checker = reactor.callLater(30, self.teardown) def dataReceived(self, data): try: self.timeout_checker.cancel() except: pass self.info("response received from the Service Events HTTP server ") #self.debug(data) cmd, headers = utils.parse_http_response(data) self.debug("%r %r", cmd, headers) if int(cmd[1]) != 200: self.warning("response with error code %r received upon our %r request", cmd[1], self.action) # XXX get around devices that return an error on our event subscribe request self.service.process_event({}) else: try: self.service.set_sid(headers['sid']) timeout = headers['timeout'] self.debug("%r %r", headers['sid'], headers['timeout']) if timeout == 'infinite': self.service.set_timeout(time.time() + 4294967296) # FIXME: that's lame elif timeout.startswith('Second-'): timeout = int(timeout[len('Second-'):]) self.service.set_timeout(timeout) except: #print headers pass self.teardown() def connectionLost( self, reason): try: self.timeout_checker.cancel() except: pass self.debug( "connection closed %r from the Service Events HTTP server", reason) def unsubscribe(service, action='unsubscribe'): return subscribe(service, action) def subscribe(service, action='subscribe'): """ send a subscribe/renewal/unsubscribe request to a service return the device response """ log_category = "event_protocol" log.info(log_category, "event.subscribe, action: %r", action) _,host_port,path,_,_ = urlsplit(service.get_base_url()) if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 def send_request(p, action): log.info(log_category, "event.subscribe.send_request %r, action: %r %r", p, action, service.get_event_sub_url()) _,_,event_path,_,_ = urlsplit(service.get_event_sub_url()) if action == 'subscribe': timeout = service.timeout if timeout == 0: timeout = 1800 request = ["SUBSCRIBE %s HTTP/1.1" % event_path, "HOST: %s:%d" % (host, port), "TIMEOUT: Second-%d" % timeout, ] service.event_connection = p else: request = ["UNSUBSCRIBE %s HTTP/1.1" % event_path, "HOST: %s:%d" % (host, port), ] if service.get_sid(): request.append("SID: %s" % service.get_sid()) else: # XXX use address and port set in the coherence instance #ip_address = p.transport.getHost().host global hostname, web_server_port #print hostname, web_server_port url = 'http://%s:%d/events' % (hostname, web_server_port) request.append("CALLBACK: <%s>" % url) request.append("NT: upnp:event") request.append('Date: %s' % datetimeToString()) request.append( "Content-Length: 0") request.append( "") request.append( "") request = '\r\n'.join(request) log.debug(log_category, "event.subscribe.send_request %r %r", request, p) try: p.transport.writeSomeData(request) except AttributeError: log.info(log_category, "transport for event %r already gone", action) # print "event.subscribe.send_request", d #return d def got_error(failure, action): log.info(log_category, "error on %s request with %s" % (action,service.get_base_url())) log.debug(log_category, failure) def teardown_connection(c, d): log.info(log_category, "event.subscribe.teardown_connection") del d del c def prepare_connection( service, action): log.info(log_category, "event.subscribe.prepare_connection action: %r %r", action, service.event_connection) if service.event_connection == None: c = ClientCreator(reactor, EventProtocol, service=service, action=action) log.info(log_category, "event.subscribe.prepare_connection: %r %r", host, port) d = c.connectTCP(host, port) d.addCallback(send_request, action=action) d.addErrback(got_error, action) #reactor.callLater(3, teardown_connection, c, d) else: d = defer.Deferred() d.addCallback(send_request, action=action) d.callback(service.event_connection) #send_request(service.event_connection, action) return d """ FIXME: we need to find a way to be sure that our unsubscribe calls get through on shutdown reactor.addSystemEventTrigger( 'before', 'shutdown', prepare_connection, service, action) """ return prepare_connection(service, action) #print "event.subscribe finished" class NotificationProtocol(Protocol, log.Loggable): logCategory = "notification_protocol" def connectionMade(self): self.timeout_checker = reactor.callLater(30, lambda : self.transport.loseConnection()) def dataReceived(self, data): try: self.timeout_checker.cancel() except: pass cmd, headers = utils.parse_http_response(data) self.debug( "notification response received %r %r", cmd, headers) try: if int(cmd[1]) != 200: self.warning("response with error code %r received upon our notification", cmd[1]) except: self.debug("response without error code received upon our notification") self.transport.loseConnection() def connectionLost( self, reason): try: self.timeout_checker.cancel() except: pass self.debug("connection closed %r", reason) def send_notification(s, xml): """ send a notification a subscriber return its response """ log_category = "notification_protocol" _,host_port,path,_,_ = urlsplit(s['callback']) if path == '': path = '/' if host_port.find(':') != -1: host,port = tuple(host_port.split(':')) port = int(port) else: host = host_port port = 80 def send_request(p,port_item): request = ['NOTIFY %s HTTP/1.1' % path, 'HOST: %s:%d' % (host, port), 'SEQ: %d' % s['seq'], 'CONTENT-TYPE: text/xml;charset="utf-8"', 'SID: %s' % s['sid'], 'NTS: upnp:propchange', 'NT: upnp:event', 'Content-Length: %d' % len(xml), '', xml] request = '\r\n'.join(request) log.info(log_category, "send_notification.send_request to %r %r", s['sid'], s['callback']) log.debug(log_category, "request: %r", request) s['seq'] += 1 if s['seq'] > 0xffffffff: s['seq'] = 1 p.transport.write(request) port_item.disconnect() #return p.transport.write(request) def got_error(failure,port_item): port_item.disconnect() log.info(log_category, "error sending notification to %r %r", s['sid'], s['callback']) log.debug(log_category, failure) #c = ClientCreator(reactor, NotificationProtocol) #d = c.connectTCP(host, port) d = defer.Deferred() f = _InstanceFactory(reactor, NotificationProtocol(), d) port_item = reactor.connectTCP(host, port, f, timeout=30, bindAddress=None) d.addCallback(send_request, port_item) d.addErrback(got_error, port_item) return d,port_item Coherence-0.6.6.2/coherence/upnp/core/service.py0000644000175000017500000013550411317660735017574 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz import os import time import urllib2 from coherence.upnp.core import action from coherence.upnp.core import event from coherence.upnp.core import variable from coherence.upnp.core import utils from coherence.upnp.core.soap_proxy import SOAPProxy from coherence.upnp.core.soap_service import errorCode from coherence.upnp.core.event import EventSubscriptionServer from coherence.extern.et import ET from twisted.web import static from twisted.internet import defer, reactor from twisted.python import failure, util from twisted.internet import task import coherence.extern.louie as louie from coherence import log global subscribers subscribers = {} def subscribe(service): subscribers[service.get_sid()] = service def unsubscribe(service): if subscribers.has_key(service.get_sid()): del subscribers[service.get_sid()] class Service(log.Loggable): logCategory = 'service_client' def __init__(self, service_type, service_id, location, control_url, event_sub_url, presentation_url, scpd_url, device): #if not control_url.startswith('/'): # control_url = "/%s" % control_url #if not event_sub_url.startswith('/'): # event_sub_url = "/%s" % event_sub_url #if presentation_url and not presentation_url.startswith('/'): # presentation_url = "/%s" % presentation_url #if not scpd_url.startswith('/'): # scpd_url = "/%s" % scpd_url self.service_type = service_type self.detection_completed = False self.id = service_id self.control_url = control_url self.event_sub_url = event_sub_url self.presentation_url = presentation_url self.scpd_url = scpd_url self.device = device self._actions = {} self._variables = { 0: {}} self._var_subscribers = {} self.subscription_id = None self.timeout = 0 self.event_connection = None self.last_time_updated = None self.client = None parsed = urllib2.urlparse.urlparse(location) self.url_base = "%s://%s" % (parsed[0], parsed[1]) self.parse_actions() self.info("%s %s %s initialized" % (self.device.friendly_name,self.service_type,self.id)) def as_tuples(self): r = [] def append(name,attribute): try: if isinstance(attribute,tuple): if callable(attribute[0]): v1 = attribute[0]() elif hasattr(self,attribute[0]): v1 = getattr(self,attribute[0]) else: v1 = attribute[0] if v1 in [None,'None']: return if callable(attribute[1]): v2 = attribute[1]() elif hasattr(self,attribute[1]): v2 = getattr(self,attribute[1]) else: v2 = attribute[1] if v2 in [None,'None']: return if len(attribute)> 2: r.append((name,(v1,v2,attribute[2]))) else: r.append((name,(v1,v2))) return elif callable(attribute): v = attribute() elif hasattr(self,attribute): v = getattr(self,attribute) else: v = attribute if v not in [None,'None']: r.append((name,v)) except: import traceback self.debug(traceback.format_exc()) r.append(('Location',(self.device.get_location(),self.device.get_location()))) append('URL base',self.device.get_urlbase) r.append(('UDN',self.device.get_id())) r.append(('Type',self.service_type)) r.append(('ID',self.id)) append('Service Description URL',(self.scpd_url,lambda: self.device.make_fullyqualified(self.scpd_url))) append('Control URL',(self.control_url,lambda: self.device.make_fullyqualified(self.control_url),False)) append('Event Subscription URL',(self.event_sub_url,lambda: self.device.make_fullyqualified(self.event_sub_url),False)) return r def as_dict(self): d = {'type':self.service_type} d['actions'] = [a.as_dict() for a in self._actions.values()] return d def __repr__(self): return "Service %s %s" % (self.service_type,self.id) #def __del__(self): # print "Service deleted" # pass def _get_client(self, name): url = self.get_control_url() namespace = self.get_type() action = "%s#%s" % (namespace, name) client = SOAPProxy( url, namespace=("u",namespace), soapaction=action) return client def remove(self): self.info("removal of ", self.device.friendly_name, self.service_type, self.id) try: self.renew_subscription_call.cancel() except: pass if self.event_connection != None: self.event_connection.teardown() if self.subscription_id != None: self.unsubscribe() for name,action in self._actions.items(): self.debug("remove", name,action) del self._actions[name] del action for instance,variables in self._variables.items(): for name, variable in variables.items(): del variables[name] del variable if variables.has_key(instance): del variables[instance] del variables del self def get_device(self): return self.device def get_type(self): return self.service_type def set_timeout(self, timeout): self.info("set timout for %s/%s to %d" %(self.device.friendly_name,self.service_type,int(timeout))) self.timeout = timeout try: self.renew_subscription_call.reset(int(self.timeout)-30) self.info("reset renew subscription call for %s/%s to %d" % (self.device.friendly_name,self.service_type,int(self.timeout)-30)) except: self.renew_subscription_call = reactor.callLater(int(self.timeout)-30, self.renew_subscription) self.info("starting renew subscription call for %s/%s to %d" % (self.device.friendly_name,self.service_type,int(self.timeout)-30)) def get_timeout(self): return self.timeout def get_id(self): return self.id def get_sid(self): return self.subscription_id def set_sid(self, sid): self.info("set subscription id for %s/%s to %s" %(self.device.friendly_name,self.service_type,sid)) self.subscription_id = sid if sid is not None: subscribe(self) self.debug("add subscription for %s", self.id) def get_actions(self): return self._actions def get_scpdXML(self): return self.scpdXML def get_action( self, name): try: return self._actions[name] except KeyError: return None # not implemented def get_state_variables(self, instance): return self._variables.get(int(instance)) def get_state_variable(self, name, instance=0): return self._variables.get(int(instance)).get(name) def get_control_url(self): return self.device.make_fullyqualified(self.control_url) def get_event_sub_url(self): return self.device.make_fullyqualified(self.event_sub_url) def get_presentation_url(self): return self.device.make_fullyqualified(self.presentation_url) def get_scpd_url(self): return self.device.make_fullyqualified(self.scpd_url) def get_base_url(self): return self.device.make_fullyqualified('.') def subscribe(self): self.debug("subscribe %s", self.id) event.subscribe(self) #global subscribers #subscribers[self.get_sid()] = self def unsubscribe(self): def remove_it(r, sid): self.debug("remove subscription for %s", self.id) unsubscribe(self) self.subscription_id = None #global subscribers #if subscribers.has_key(sid): # del subscribers[sid] self.debug("unsubscribe %s", self.id) d = event.unsubscribe(self) d.addCallback(remove_it, self.get_sid()) return d def subscribe_for_variable(self, var_name, instance=0, callback=None, signal=False): variable = self.get_state_variable(var_name) if variable: if callback != None: if signal == True: callback(variable) louie.connect(callback, signal='Coherence.UPnP.StateVariable.%s.changed' % var_name, sender=self) else: variable.subscribe(callback) def renew_subscription(self): self.info("renew_subscription") event.subscribe(self) def process_event(self,event): self.info("process event %r %r" % (self,event)) for var_name, var_value in event.items(): if var_name == 'LastChange': self.info("we have a LastChange event") self.get_state_variable(var_name, 0).update(var_value) tree = utils.parse_xml(var_value, 'utf-8').getroot() namespace_uri, tag = tree.tag[1:].split( "}", 1) for instance in tree.findall('{%s}InstanceID' % namespace_uri): instance_id = instance.attrib['val'] self.info("instance_id %r %r" % (instance,instance_id)) for var in instance.getchildren(): self.info("var %r" % var) namespace_uri, tag = var.tag[1:].split("}", 1) self.info("%r %r %r" % (namespace_uri, tag,var.attrib['val'])) self.get_state_variable(tag, instance_id).update(var.attrib['val']) self.info("updated var %r" % var) if len(var.attrib) > 1: self.info("Extended StateVariable %s - %r", var.tag, var.attrib) if var.attrib.has_key('channel') and var.attrib['channel'] != 'Master': # TODO handle attributes that them selves have multiple instances self.info("Skipping update to %s its not for master channel %s", var.tag, var.attrib) pass else: if not self.get_state_variables(instance_id): # TODO Create instance ? self.error("%r update failed (not self.get_state_variables(instance_id)) %r", self, instance_id) elif not self.get_state_variables(instance_id).has_key(tag): # TODO Create instance StateVariable? # SONOS stuff self.error("%r update failed (not self.get_state_variables(instance_id).has_key(tag)) %r", self, tag) else: val = None if var.attrib.has_key('val'): val = var.attrib['val'] #self.debug("%r update %r %r %r", self,namespace_uri, tag, var.attrib['val']) self.get_state_variable(tag, instance_id).update(var.attrib['val']) self.debug("updated 'attributed' var %r", var) louie.send('Coherence.UPnP.DeviceClient.Service.Event.processed',None,self,(var_name,var_value,event.raw)) else: self.get_state_variable(var_name, 0).update(var_value) louie.send('Coherence.UPnP.DeviceClient.Service.Event.processed',None,self,(var_name,var_value,event.raw)) if self.last_time_updated == None: # The clients (e.g. media_server_client) check for last time to detect whether service detection is complete # so we need to set it here and now to avoid a potential race condition self.last_time_updated = time.time() louie.send('Coherence.UPnP.DeviceClient.Service.notified', sender=self.device, service=self) self.info("send signal Coherence.UPnP.DeviceClient.Service.notified for %r" % self) self.last_time_updated = time.time() def parse_actions(self): def gotPage(x): #print "gotPage" #print x self.scpdXML, headers = x tree = utils.parse_xml(self.scpdXML, 'utf-8').getroot() ns = "urn:schemas-upnp-org:service-1-0" for action_node in tree.findall('.//{%s}action' % ns): name = action_node.findtext('{%s}name' % ns) arguments = [] for argument in action_node.findall('.//{%s}argument' % ns): arg_name = argument.findtext('{%s}name' % ns) arg_direction = argument.findtext('{%s}direction' % ns) arg_state_var = argument.findtext('{%s}relatedStateVariable' % ns) arguments.append(action.Argument(arg_name, arg_direction, arg_state_var)) self._actions[name] = action.Action(self, name, 'n/a', arguments) for var_node in tree.findall('.//{%s}stateVariable' % ns): send_events = var_node.attrib.get('sendEvents','yes') name = var_node.findtext('{%s}name' % ns) data_type = var_node.findtext('{%s}dataType' % ns) values = [] """ we need to ignore this, as there we don't get there our {urn:schemas-beebits-net:service-1-0}X_withVendorDefines attibute there """ for allowed in var_node.findall('.//{%s}allowedValue' % ns): values.append(allowed.text) instance = 0 self._variables.get(instance)[name] = variable.StateVariable(self, name, 'n/a', instance, send_events, data_type, values) """ we need to do this here, as there we don't get there our {urn:schemas-beebits-net:service-1-0}X_withVendorDefines attibute there """ self._variables.get(instance)[name].has_vendor_values = True #print 'service parse:', self, self.device self.detection_completed = True louie.send('Coherence.UPnP.Service.detection_completed', sender=self.device, device=self.device) self.info("send signal Coherence.UPnP.Service.detection_completed for %r" % self) """ if (self.last_time_updated == None): if( self.id.endswith('AVTransport') or self.id.endswith('RenderingControl')): louie.send('Coherence.UPnP.DeviceClient.Service.notified', sender=self.device, service=self) self.last_time_updated = time.time() """ def gotError(failure, url): self.warning('error requesting', url) self.info('failure', failure) louie.send('Coherence.UPnP.Service.detection_failed', self.device, device=self.device) #print 'getPage', self.get_scpd_url() utils.getPage(self.get_scpd_url()).addCallbacks(gotPage, gotError, None, None, [self.get_scpd_url()], None) moderated_variables = \ {'urn:schemas-upnp-org:service:AVTransport:2': ['LastChange'], 'urn:schemas-upnp-org:service:AVTransport:1': ['LastChange'], 'urn:schemas-upnp-org:service:ContentDirectory:2': ['SystemUpdateID', 'ContainerUpdateIDs'], 'urn:schemas-upnp-org:service:ContentDirectory:1': ['SystemUpdateID', 'ContainerUpdateIDs'], 'urn:schemas-upnp-org:service:RenderingControl:2': ['LastChange'], 'urn:schemas-upnp-org:service:RenderingControl:1': ['LastChange'], 'urn:schemas-upnp-org:service:ScheduledRecording:1': ['LastChange'], } class ServiceServer(log.Loggable): logCategory = 'service_server' def __init__(self, id, version, backend): self.id = id self.version = version self.backend = backend if getattr(self, "namespace", None) == None: self.namespace = 'schemas-upnp-org' if getattr(self, "id_namespace", None) == None: self.id_namespace = 'upnp-org' self.service_type = 'urn:%s:service:%s:%d' % (self.namespace, id, int(self.version)) self.scpd_url = 'scpd.xml' self.control_url = 'control' self.subscription_url = 'subscribe' self.event_metadata = '' if id == 'AVTransport': self.event_metadata = 'urn:schemas-upnp-org:metadata-1-0/AVT/' if id == 'RenderingControl': self.event_metadata = 'urn:schemas-upnp-org:metadata-1-0/RCS/' if id == 'ScheduledRecording': self.event_metadata = 'urn:schemas-upnp-org:av:srs-event' self._actions = {} self._variables = {0: {}} self._subscribers = {} self._pending_notifications = {} self.last_change = None self.init_var_and_actions() try: if 'LastChange' in moderated_variables[self.service_type]: self.last_change = self._variables[0]['LastChange'] except: pass self.putChild(self.subscription_url, EventSubscriptionServer(self)) self.check_subscribers_loop = task.LoopingCall(self.check_subscribers) self.check_subscribers_loop.start(120.0, now=False) self.check_moderated_loop = None if moderated_variables.has_key(self.service_type): self.check_moderated_loop = task.LoopingCall(self.check_moderated_variables) #self.check_moderated_loop.start(5.0, now=False) self.check_moderated_loop.start(0.5, now=False) def _release(self): for p in self._pending_notifications.values(): p.disconnect() self._pending_notifications = {} def get_action(self, action_name): try: return self._actions[action_name] except KeyError: return None # not implemented def get_actions(self): return self._actions def get_variables(self): return self._variables def get_subscribers(self): return self._subscribers def rm_notification(self,result,d): del self._pending_notifications[d] def new_subscriber(self, subscriber): notify = [] for vdict in self._variables.values(): notify += [v for v in vdict.values() if v.send_events == True] self.info("new_subscriber", subscriber, notify) if len(notify) <= 0: return root = ET.Element('e:propertyset') root.attrib['xmlns:e']='urn:schemas-upnp-org:event-1-0' evented_variables = 0 for n in notify: e = ET.SubElement( root, 'e:property') if n.name == 'LastChange': if subscriber['seq'] == 0: text = self.build_last_change_event(n.instance, force=True) else: text = self.build_last_change_event(n.instance) if text is not None: ET.SubElement( e, n.name).text = text evented_variables += 1 else: ET.SubElement( e, n.name).text = str(n.value) evented_variables += 1 if evented_variables > 0: xml = ET.tostring( root, encoding='utf-8') d,p = event.send_notification(subscriber, xml) self._pending_notifications[d] = p d.addBoth(self.rm_notification,d) self._subscribers[subscriber['sid']] = subscriber def get_id(self): return self.id def get_type(self): return self.service_type def create_new_instance(self, instance): self._variables[instance] = {} for v in self._variables[0].values(): self._variables[instance][v.name] = variable.StateVariable( v.service, v.name, v.implementation, instance, v.send_events, v.data_type, v.allowed_values) self._variables[instance][v.name].has_vendor_values = v.has_vendor_values self._variables[instance][v.name].default_value = v.default_value #self._variables[instance][v.name].value = v.default_value # FIXME self._variables[instance][v.name].old_value = v.old_value self._variables[instance][v.name].value = v.value self._variables[instance][v.name].dependant_variable = v.dependant_variable def remove_instance(self, instance): if instance == 0: return del(self._variables[instance]) def set_variable(self, instance, variable_name, value, default=False): def process_value(result): variable.update(result) if default == True: variable.default_value = variable.value if(variable.send_events == True and variable.moderated == False and len(self._subscribers) > 0): xml = self.build_single_notification(instance, variable_name, variable.value) for s in self._subscribers.values(): d,p = event.send_notification(s, xml) self._pending_notifications[d] = p d.addBoth(self.rm_notification,d) try: variable = self._variables[int(instance)][variable_name] if isinstance( value, defer.Deferred): value.addCallback(process_value) else: process_value(value) except: pass def get_variable(self, variable_name, instance=0): try: return self._variables[int(instance)][variable_name] except: return None def build_single_notification(self, instance, variable_name, value): root = ET.Element('e:propertyset') root.attrib['xmlns:e']='urn:schemas-upnp-org:event-1-0' e = ET.SubElement( root, 'e:property') s = ET.SubElement( e, variable_name).text = str(value) return ET.tostring( root, encoding='utf-8') def build_last_change_event(self, instance=0, force=False): got_one = False root = ET.Element('Event') root.attrib['xmlns']=self.event_metadata for instance, vdict in self._variables.items(): e = ET.SubElement( root, 'InstanceID') e.attrib['val']=str(instance) for variable in vdict.values(): if( variable.name != 'LastChange' and variable.name[0:11] != 'A_ARG_TYPE_' and variable.never_evented == False and (variable.updated == True or force == True)): s = ET.SubElement( e, variable.name) s.attrib['val'] = str(variable.value) variable.updated = False got_one = True if variable.dependant_variable != None: dependants = variable.dependant_variable.get_allowed_values() if dependants != None and len(dependants) > 0: s.attrib['channel']=dependants[0] if got_one == True: return ET.tostring( root, encoding='utf-8') else: return None def propagate_notification(self, notify): #print "propagate_notification", notify if len(self._subscribers) <= 0: return if len(notify) <= 0: return root = ET.Element('e:propertyset') root.attrib['xmlns:e']='urn:schemas-upnp-org:event-1-0' if isinstance( notify, variable.StateVariable): notify = [notify,] evented_variables = 0 for n in notify: e = ET.SubElement( root, 'e:property') if n.name == 'LastChange': text = self.build_last_change_event(instance=n.instance) if text is not None: ET.SubElement( e, n.name).text = text evented_variables += 1 else: s = ET.SubElement( e, n.name).text = str(n.value) evented_variables += 1 if n.dependant_variable != None: dependants = n.dependant_variable.get_allowed_values() if dependants != None and len(dependants) > 0: s.attrib['channel']=dependants[0] if evented_variables == 0: return xml = ET.tostring( root, encoding='utf-8') #print "propagate_notification", xml for s in self._subscribers.values(): d,p = event.send_notification(s,xml) self._pending_notifications[d] = p d.addBoth(self.rm_notification,d) def check_subscribers(self): for s in self._subscribers.values(): timeout = 86400 #print s if s['timeout'].startswith('Second-'): timeout = int(s['timeout'][len('Second-'):]) if time.time() > s['created'] + timeout: del s def check_moderated_variables(self): #print "check_moderated for %s" % self.id #print self._subscribers if len(self._subscribers) <= 0: return variables = moderated_variables[self.get_type()] notify = [] for v in variables: #print self._variables[0][v].name, self._variables[0][v].updated for vdict in self._variables.values(): if vdict[v].updated == True: vdict[v].updated = False notify.append(vdict[v]) self.propagate_notification(notify) def is_variable_moderated(self, name): try: variables = moderated_variables[self.get_type()] if name in variables: return True except: pass return False def simulate_notification(self): print "simulate_notification for", self.id self.set_variable(0, 'CurrentConnectionIDs', '0') def get_scpdXML(self): if not hasattr(self,'scpdXML') or self.scpdXML == None: self.scpdXML = scpdXML(self) self.scpdXML = self.scpdXML.build_xml() return self.scpdXML def register_vendor_variable(self,name,implementation='optional', instance=0, evented='no', data_type='string', dependant_variable=None, default_value=None, allowed_values=None,has_vendor_values=False,allowed_value_range=None, moderated=False): """ enables a backend to add an own, vendor defined, StateVariable to the service @ivar name: the name of the new StateVariable @ivar implementation: either 'optional' or 'required' @ivar instance: the instance number of the service that variable should be assigned to, usually '0' @ivar evented: boolean, or the special keyword 'never' if the variable doesn't show up in a LastChange event too @ivar data_type: 'string','boolean','bin.base64' or various number formats @ivar dependant_variable: the name of another StateVariable that depends on this one @ivar default_value: the value this StateVariable should have by default when created for another instance of in the service @ivar allowed_values: a C{list} of values this StateVariable can have @ivar has_vendor_values: boolean if there are values outside the allowed_values list too @ivar allowed_value_range: a C{dict} of 'minimum','maximum' and 'step' values @ivar moderated: boolean, True if this StateVariable should only be evented via a LastChange event """ # FIXME # we should raise an Exception when there as a StateVariable with that name already if evented == 'never': send_events = 'no' else: send_events = evented new_variable = variable.StateVariable(self,name,implementation,instance,send_events, data_type,allowed_values) if default_value == None: new_variable.default_value = '' else: new_variable.default_value = new_variable.old_value = new_variable.value = default_value new_variable.dependant_variable = dependant_variable new_variable.has_vendor_values = has_vendor_values new_variable.allowed_value_range = allowed_value_range new_variable.moderated = moderated if evented == 'never': new_variable.never_evented = True self._variables.get(instance)[name] = new_variable return new_variable def register_vendor_action(self,name,implementation,arguments=None,needs_callback=True): """ enables a backend to add an own, vendor defined, Action to the service @ivar name: the name of the new Action @ivar implementation: either 'optional' or 'required' @ivar arguments: a C{list} if argument C{tuples}, like (name,direction,relatedStateVariable) @ivar needs_callback: this Action needs a method in the backend or service class """ # FIXME # we should raise an Exception when there as an Action with that name already # we should raise an Exception when there is no related StateVariable for an Argument """ check for action in backend """ callback = getattr(self.backend, "upnp_%s" % name, None) if callback == None: """ check for action in ServiceServer """ callback = getattr(self, "upnp_%s" % name, None) if( needs_callback == True and callback == None): """ we have one or more 'A_ARG_TYPE_' variables issue a warning for now """ if implementation == 'optional': self.info('%s has a missing callback for %s action %s, action disabled' % (self.id,implementation,name)) return else: if((hasattr(self,'implementation') and self.implementation == 'required') or not hasattr(self,'implementation')): self.warning('%s has a missing callback for %s action %s, service disabled' % (self.id,implementation,name)) raise LookupError,"missing callback" arguments_list = [] for argument in arguments: arguments_list.append(action.Argument(argument[0],argument[1].lower(),argument[2])) new_action = action.Action(self, name, implementation, arguments_list) self._actions[name] = new_action if callback != None: new_action.set_callback(callback) self.info('Add callback %s for %s/%s' % (callback, self.id, name)) return new_action def init_var_and_actions(self): desc_file = util.sibpath(__file__, os.path.join('xml-service-descriptions', '%s%d.xml' % (self.id, int(self.version)))) tree = ET.parse(desc_file) for action_node in tree.findall('.//action'): name = action_node.findtext('name') implementation = 'required' needs_callback = False if action_node.attrib.get('{urn:schemas-beebits-net:service-1-0}X_needs_backend', None) != None: needs_callback = True if action_node.find('Optional') != None: implementation = 'optional' if(action_node.find('Optional').attrib.get( '{urn:schemas-beebits-net:service-1-0}X_needs_backend', None) != None or action_node.attrib.get('{urn:schemas-beebits-net:service-1-0}X_needs_backend', None) != None): needs_callback = True arguments = [] for argument in action_node.findall('.//argument'): arg_name = argument.findtext('name') arg_direction = argument.findtext('direction') arg_state_var = argument.findtext('relatedStateVariable') arguments.append(action.Argument(arg_name, arg_direction, arg_state_var)) if( arg_state_var[0:11] == 'A_ARG_TYPE_' and arg_direction == 'out'): needs_callback = True #print arg_name, arg_direction, needs_callback """ check for action in backend """ callback = getattr(self.backend, "upnp_%s" % name, None) if callback == None: """ check for action in ServiceServer """ callback = getattr(self, "upnp_%s" % name, None) if( needs_callback == True and callback == None): """ we have one or more 'A_ARG_TYPE_' variables issue a warning for now """ if implementation == 'optional': self.info('%s has a missing callback for %s action %s, action disabled' % (self.id,implementation,name)) continue else: if((hasattr(self,'implementation') and self.implementation == 'required') or not hasattr(self,'implementation')): self.warning('%s has a missing callback for %s action %s, service disabled' % (self.id,implementation,name)) raise LookupError,"missing callback" new_action = action.Action(self, name, implementation, arguments) self._actions[name] = new_action if callback != None: new_action.set_callback(callback) self.info('Add callback %s for %s/%s' % (callback, self.id, name)) backend_vendor_value_defaults = getattr(self.backend, "vendor_value_defaults", None) service_value_defaults = None if backend_vendor_value_defaults: service_value_defaults = backend_vendor_value_defaults.get(self.id,None) backend_vendor_range_defaults = getattr(self.backend, "vendor_range_defaults", None) service_range_defaults = None if backend_vendor_range_defaults: service_range_defaults = backend_vendor_range_defaults.get(self.id) for var_node in tree.findall('.//stateVariable'): instance = 0 name = var_node.findtext('name') implementation = 'required' if action_node.find('Optional') != None: implementation = 'optional' #if implementation == 'optional': # for action_object in self._actions.values(): # if name in [a.get_state_variable() for a in action_object.arguments_list]: # break # else: # continue send_events = var_node.findtext('sendEventsAttribute') data_type = var_node.findtext('dataType') values = [] for allowed in var_node.findall('.//allowedValue'): values.append(allowed.text) self._variables.get(instance)[name] = variable.StateVariable(self, name, implementation, instance, send_events, data_type, values) dependant_variable = var_node.findtext('{urn:schemas-beebits-net:service-1-0}X_dependantVariable') if dependant_variable: self._variables.get(instance)[name].dependant_variable = dependant_variable default_value = var_node.findtext('defaultValue') if default_value: self._variables.get(instance)[name].set_default_value(default_value) if var_node.find('sendEventsAttribute') != None: never_evented = var_node.find('sendEventsAttribute').attrib.get( '{urn:schemas-beebits-net:service-1-0}X_no_means_never', None) if never_evented is not None: self._variables.get(instance)[name].set_never_evented(never_evented) allowed_value_list = var_node.find('allowedValueList') if allowed_value_list != None: vendor_values = allowed_value_list.attrib.get( '{urn:schemas-beebits-net:service-1-0}X_withVendorDefines', None) if service_value_defaults: variable_value_defaults = service_value_defaults.get(name, None) if variable_value_defaults: self.info("overwriting %s default value with %s" % (name, variable_value_defaults)) self._variables.get(instance)[name].set_allowed_values(variable_value_defaults) if vendor_values != None: self._variables.get(instance)[name].has_vendor_values = True allowed_value_range = var_node.find('allowedValueRange') if allowed_value_range: vendor_values = allowed_value_range.attrib.get( '{urn:schemas-beebits-net:service-1-0}X_withVendorDefines', None) range = {} for e in list(allowed_value_range): range[e.tag] = e.text if( vendor_values != None): if service_range_defaults: variable_range_defaults = service_range_defaults.get(name) if( variable_range_defaults != None and variable_range_defaults.get(e.tag) != None): self.info("overwriting %s attribute %s with %s" % (name, e.tag, str(variable_range_defaults[e.tag]))) range[e.tag] = variable_range_defaults[e.tag] elif e.text == None: self.info("missing vendor definition for %s, attribute %s" % (name, e.tag)) self._variables.get(instance)[name].set_allowed_value_range(**range) if vendor_values != None: self._variables.get(instance)[name].has_vendor_values = True elif service_range_defaults: variable_range_defaults = service_range_defaults.get(name) if variable_range_defaults != None: self._variables.get(instance)[name].set_allowed_value_range(**variable_range_defaults) self._variables.get(instance)[name].has_vendor_values = True for v in self._variables.get(0).values(): if isinstance( v.dependant_variable, str): v.dependant_variable = self._variables.get(instance).get(v.dependant_variable) class scpdXML(static.Data): def __init__(self, server, control=None): self.service_server = server self.control = control static.Data.__init__(self, None, 'text/xml') def render(self, request): if self.data == None: self.data = self.build_xml() return static.Data.render(self,request) def build_xml(self): root = ET.Element('scpd') root.attrib['xmlns']='urn:schemas-upnp-org:service-1-0' e = ET.SubElement(root, 'specVersion') ET.SubElement( e, 'major').text = '1' ET.SubElement( e, 'minor').text = '0' e = ET.SubElement( root, 'actionList') for action in self.service_server._actions.values(): s = ET.SubElement( e, 'action') ET.SubElement( s, 'name').text = action.get_name() al = ET.SubElement( s, 'argumentList') for argument in action.get_arguments_list(): a = ET.SubElement( al, 'argument') ET.SubElement( a, 'name').text = argument.get_name() ET.SubElement( a, 'direction').text = argument.get_direction() ET.SubElement( a, 'relatedStateVariable').text = argument.get_state_variable() e = ET.SubElement( root, 'serviceStateTable') for var in self.service_server._variables[0].values(): s = ET.SubElement( e, 'stateVariable') if var.send_events == True: s.attrib['sendEvents'] = 'yes' else: s.attrib['sendEvents'] = 'no' ET.SubElement( s, 'name').text = var.name ET.SubElement( s, 'dataType').text = var.data_type if(not var.has_vendor_values and len(var.allowed_values)): #if len(var.allowed_values): v = ET.SubElement( s, 'allowedValueList') for value in var.allowed_values: ET.SubElement( v, 'allowedValue').text = value if( var.allowed_value_range != None and len(var.allowed_value_range) > 0): complete = True for name,value in var.allowed_value_range.items(): if value == None: complete = False if complete == True: avl = ET.SubElement( s, 'allowedValueRange') for name,value in var.allowed_value_range.items(): if value != None: ET.SubElement( avl, name).text = str(value) return """""" + ET.tostring( root, encoding='utf-8') from twisted.python.util import OrderedDict class ServiceControl: def get_action_results(self, result, action, instance): """ check for out arguments if yes: check if there are related ones to StateVariables with non A_ARG_TYPE_ prefix if yes: check if there is a call plugin method for this action if yes: update StateVariable values with call result if no: get StateVariable values and add them to result dict """ self.debug('get_action_results', result) #print 'get_action_results', action, instance r = result notify = [] for argument in action.get_out_arguments(): #print 'get_state_variable_contents', argument.name if argument.name[0:11] != 'A_ARG_TYPE_': if action.get_callback() != None: variable = self.variables[instance][argument.get_state_variable()] variable.update(r[argument.name]) #print 'update state variable contents', variable.name, variable.value, variable.send_events if(variable.send_events == 'yes' and variable.moderated == False): notify.append(variable) else: variable = self.variables[instance][argument.get_state_variable()] #print 'get state variable contents', variable.name, variable.value r[argument.name] = variable.value #print "r", r self.service.propagate_notification(notify) #r= { '%sResponse'%action.name: r} self.info( 'action_results unsorted', action.name, r) if len(r) == 0: return r ordered_result = OrderedDict() for argument in action.get_out_arguments(): ordered_result[argument.name] = r[argument.name] self.info( 'action_results sorted', action.name, ordered_result) return ordered_result def soap__generic(self, *args, **kwargs): """ generic UPnP service control method, which will be used if no soap_ACTIONNAME method in the server service control class can be found """ try: action = self.actions[kwargs['soap_methodName']] except: return failure.Failure(errorCode(401)) try: instance = int(kwargs['InstanceID']) except: instance = 0 self.info("soap__generic", action, __name__, kwargs) del kwargs['soap_methodName'] if( kwargs.has_key('X_UPnPClient') and kwargs['X_UPnPClient'] == 'XBox'): if(action.name == 'Browse' and kwargs.has_key('ContainerID')): """ XXX: THIS IS SICK """ kwargs['ObjectID'] = kwargs['ContainerID'] del kwargs['ContainerID'] in_arguments = action.get_in_arguments() for arg_name, arg in kwargs.iteritems(): if arg_name.find('X_') == 0: continue l = [ a for a in in_arguments if arg_name == a.get_name()] if len(l) > 0: in_arguments.remove(l[0]) else: self.critical('argument %s not valid for action %s' % (arg_name,action.name)) return failure.Failure(errorCode(402)) if len(in_arguments) > 0: self.critical('argument %s missing for action %s' % ([ a.get_name() for a in in_arguments],action.name)) return failure.Failure(errorCode(402)) def callit( *args, **kwargs): #print 'callit args', args #print 'callit kwargs', kwargs result = {} callback = action.get_callback() if callback != None: return callback( **kwargs) return result def got_error(x): #print 'failure', x self.info('soap__generic error during call processing') return x # call plugin method for this action d = defer.maybeDeferred( callit, *args, **kwargs) d.addCallback( self.get_action_results, action, instance) d.addErrback(got_error) return d Coherence-0.6.6.2/coherence/base.py0000644000175000017500000006052011317660741015124 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006,2007,2008 Frank Scholz import string import socket import os, sys import traceback import copy from twisted.python import filepath, util from twisted.internet.tcp import CannotListenError from twisted.internet import task, address, defer from twisted.internet import reactor from twisted.web import resource,static import coherence.extern.louie as louie from coherence import __version__ from coherence.upnp.core.ssdp import SSDPServer from coherence.upnp.core.msearch import MSearch from coherence.upnp.core.device import Device, RootDevice from coherence.upnp.core.utils import parse_xml, get_ip_address, get_host_address from coherence.upnp.core.utils import Site from coherence.upnp.devices.control_point import ControlPoint from coherence.upnp.devices.media_server import MediaServer from coherence.upnp.devices.media_renderer import MediaRenderer from coherence.upnp.devices.binary_light import BinaryLight from coherence.upnp.devices.dimmable_light import DimmableLight from coherence import log class SimpleRoot(resource.Resource, log.Loggable): addSlash = True logCategory = 'coherence' def __init__(self, coherence): resource.Resource.__init__(self) self.coherence = coherence self.http_hostname = '%s:%d' % (self.coherence.hostname, self.coherence.web_server_port) def getChild(self, name, request): self.debug('SimpleRoot getChild %s, %s' % (name, request)) if name == 'oob': """ we have an out-of-band request """ return static.File(self.coherence.dbus.pinboard[request.args['key'][0]]) if name == '': return self # at this stage, name should be a device UUID try: return self.coherence.children[name] except: self.warning("Cannot find device for requested name:", name) request.setResponseCode(404) return static.Data('

    No device for requested UUID: %s

    ' % name,'text/html') def listchilds(self, uri): self.info('listchilds %s' % uri) if uri[-1] != '/': uri += '/' cl = [] for child in self.coherence.children: device = self.coherence.get_device_with_id(child) if device is not None: cl.append('
  • %s:%s %s
  • ' % ( uri, child, device.get_friendly_device_type(), device.get_device_type_version(), device.get_friendly_name())) for child in self.children: cl.append('
  • %s
  • ' % (uri, child, child)) return "".join(cl) def render(self,request): result = """Coherence Coherence - a Python DLNA/UPnP framework for the Digital Living

    Hosting:

      %s

    """ % self.listchilds(request.uri) return result.encode('utf-8') class WebServer(log.Loggable): logCategory = 'webserver' def __init__(self, ui, port, coherence): try: if ui != 'yes': """ use this to jump out here if we do not want the web ui """ raise ImportError self.warning("Web UI not supported atm, will return with version 0.7.0") raise ImportError from nevow import __version_info__, __version__ if __version_info__ <(0,9,17): self.warning( "Nevow version %s too old, disabling WebUI" % __version__) raise ImportError from nevow import appserver, inevow from coherence.web.ui import Web, IWeb, WebUI from twisted.python.components import registerAdapter def ResourceFactory( original): return WebUI( IWeb, original) registerAdapter(ResourceFactory, Web, inevow.IResource) self.web_root_resource = Web(coherence) self.site = appserver.NevowSite( self.web_root_resource) except ImportError: self.site = Site(SimpleRoot(coherence)) self.port = reactor.listenTCP( port, self.site) coherence.web_server_port = self.port._realPortNumber # XXX: is this the right way to do it? self.warning( "WebServer on port %d ready" % coherence.web_server_port) class Plugins(log.Loggable): logCategory = 'plugins' _instance_ = None # Singleton _valids = ("coherence.plugins.backend.media_server", "coherence.plugins.backend.media_renderer", "coherence.plugins.backend.binary_light", "coherence.plugins.backend.dimmable_light") _plugins = {} def __new__(cls, *args, **kwargs): obj = getattr(cls, '_instance_', None) if obj is not None: return obj else: obj = super(Plugins, cls).__new__(cls, *args, **kwargs) cls._instance_ = obj obj._collect(*args, **kwargs) return obj def __repr__(self): return str(self._plugins) def __init__(self, *args, **kwargs): pass def __getitem__(self, key): return self._plugins.__getitem__(key) def get(self, key,default=None): try: return self.__getitem__(key) except KeyError: return default def __setitem__(self, key, value): self._plugins.__setitem__(key,value) def set(self, key,value): return self.__setitem__(key,value) def keys(self): return self._plugins.keys() def _collect(self, ids=_valids): if not isinstance(ids, (list,tuple)): ids = (ids) try: import pkg_resources for id in ids: for entrypoint in pkg_resources.iter_entry_points(id): try: #print entrypoint, type(entrypoint) self._plugins[entrypoint.name] = entrypoint.load(require=False) except (ImportError, AttributeError, pkg_resources.ResolutionError), msg: self.warning("Can't load plugin %s (%s), maybe missing dependencies..." % (entrypoint.name,msg)) self.info(traceback.format_exc()) except ImportError: self.info("no pkg_resources, fallback to simple plugin handling") except Exception, msg: self.warning(msg) if len(self._plugins) == 0: self._collect_from_module() def _collect_from_module(self): from coherence.extern.simple_plugin import Reception reception = Reception(os.path.join(os.path.dirname(__file__),'backends'), log=self.warning) self.info(reception.guestlist()) for cls in reception.guestlist(): self._plugins[cls.__name__.split('.')[-1]] = cls class Coherence(log.Loggable): logCategory = 'coherence' _instance_ = None # Singleton def __new__(cls, *args, **kwargs): obj = getattr(cls, '_instance_', None) if obj is not None: cls._incarnations_ += 1 return obj else: obj = super(Coherence, cls).__new__(cls) cls._instance_ = obj cls._incarnations_ = 1 obj.setup(*args, **kwargs) obj.cls = cls return obj def __init__(self, *args, **kwargs): pass def clear(self): """ we do need this to survive multiple calls to Coherence during trial tests """ self.cls._instance_ = None def setup(self, config={}): self._mirabeau = None self.devices = [] self.children = {} self._callbacks = {} self.active_backends = {} self.dbus = None self.config = config network_if = config.get('interface') self.web_server_port = int(config.get('serverport', 0)) """ initializes logsystem a COHERENCE_DEBUG environment variable overwrites all level settings here """ try: logmode = config.get('logging').get('level','warning') except (KeyError,AttributeError): logmode = config.get('logmode', 'warning') _debug = [] try: subsystems = config.get('logging')['subsystem'] if isinstance(subsystems,dict): subsystems = [subsystems] for subsystem in subsystems: try: if subsystem['active'] == 'no': continue except (KeyError,TypeError): pass self.info( "setting log-level for subsystem %s to %s" % (subsystem['name'],subsystem['level'])) _debug.append('%s:%d' % (subsystem['name'].lower(), log.human2level(subsystem['level']))) except (KeyError,TypeError): subsystem_log = config.get('subsystem_log',{}) for subsystem,level in subsystem_log.items(): #self.info( "setting log-level for subsystem %s to %s" % (subsystem,level)) _debug.append('%s:%d' % (subsystem.lower(), log.human2level(level))) if len(_debug) > 0: _debug = ','.join(_debug) else: _debug = '*:%d' % log.human2level(logmode) try: logfile = config.get('logging').get('logfile',None) if logfile != None: logfile = unicode(logfile) except (KeyError,AttributeError,TypeError): logfile = config.get('logfile', None) log.init(logfile, _debug) self.warning("Coherence UPnP framework version %s starting..." % __version__) if network_if: self.hostname = get_ip_address('%s' % network_if) else: try: self.hostname = socket.gethostbyname(socket.gethostname()) except socket.gaierror: self.warning("hostname can't be resolved, maybe a system misconfiguration?") self.hostname = '127.0.0.1' if self.hostname.startswith('127.'): """ use interface detection via routing table as last resort """ def catch_result(hostname): self.hostname = hostname self.setup_part2() d = defer.maybeDeferred(get_host_address) d.addCallback(catch_result) else: self.setup_part2() def setup_part2(self): self.info('running on host: %s' % self.hostname) if self.hostname.startswith('127.'): self.warning('detection of own ip failed, using %s as own address, functionality will be limited', self.hostname) unittest = self.config.get('unittest', 'no') if unittest == 'no': unittest = False else: unittest = True self.ssdp_server = SSDPServer(test=unittest,interface=self.hostname) louie.connect( self.create_device, 'Coherence.UPnP.SSDP.new_device', louie.Any) louie.connect( self.remove_device, 'Coherence.UPnP.SSDP.removed_device', louie.Any) louie.connect( self.add_device, 'Coherence.UPnP.RootDevice.detection_completed', louie.Any) #louie.connect( self.receiver, 'Coherence.UPnP.Service.detection_completed', louie.Any) self.ssdp_server.subscribe("new_device", self.add_device) self.ssdp_server.subscribe("removed_device", self.remove_device) self.msearch = MSearch(self.ssdp_server,test=unittest) reactor.addSystemEventTrigger( 'before', 'shutdown', self.shutdown, force=True) try: self.web_server = WebServer( self.config.get('web-ui',None), self.web_server_port, self) except CannotListenError: self.warning('port %r already in use, aborting!' % self.web_server_port) reactor.stop() return self.urlbase = 'http://%s:%d/' % (self.hostname, self.web_server_port) #self.renew_service_subscription_loop = task.LoopingCall(self.check_devices) #self.renew_service_subscription_loop.start(20.0, now=False) self.available_plugins = None self.ctrl = None try: plugins = self.config['plugin'] if isinstance(plugins,dict): plugins=[plugins] except: plugins = None if plugins is None: plugins = self.config.get('plugins',None) if plugins is None: self.info("No plugin defined!") else: if isinstance(plugins,dict): for plugin,arguments in plugins.items(): try: if not isinstance(arguments, dict): arguments = {} self.add_plugin(plugin, **arguments) except Exception, msg: self.warning("Can't enable plugin, %s: %s!" % (plugin, msg)) self.info(traceback.format_exc()) else: for plugin in plugins: try: if plugin['active'] == 'no': continue except (KeyError,TypeError): pass try: backend = plugin['backend'] arguments = copy.copy(plugin) del arguments['backend'] backend = self.add_plugin(backend, **arguments) if self.writeable_config() == True: if 'uuid' not in plugin: plugin['uuid'] = str(backend.uuid)[5:] self.config.save() except Exception, msg: self.warning("Can't enable plugin, %s: %s!" % (plugin, msg)) self.info(traceback.format_exc()) self.external_address = ':'.join((self.hostname,str(self.web_server_port))) if(self.config.get('controlpoint', 'no') == 'yes' or self.config.get('json','no') == 'yes'): self.ctrl = ControlPoint(self) if self.config.get('json','no') == 'yes': from coherence.json import JsonInterface self.json = JsonInterface(self.ctrl) if self.config.get('transcoding', 'no') == 'yes': from coherence.transcoder import TranscoderManager self.transcoder_manager = TranscoderManager(self) if self.config.get('use_dbus', 'no') == 'yes': try: from coherence import dbus_service if self.ctrl == None: self.ctrl = ControlPoint(self) self.dbus = dbus_service.DBusPontoon(self.ctrl) except Exception, msg: self.warning("Unable to activate dbus sub-system: %r" % msg) self.debug(traceback.format_exc()) else: if self.config.get('enable_mirabeau', 'no') == 'yes': from coherence import mirabeau from coherence.tube_service import MirabeauProxy mirabeau_cfg = self.config.get('mirabeau', {}) try: self.external_address = mirabeau_cfg['external_address'] except KeyError: pass self._mirabeau = mirabeau.Mirabeau(mirabeau_cfg, self) self.add_web_resource('mirabeau', MirabeauProxy()) self._mirabeau.start() def add_plugin(self, plugin, **kwargs): self.info("adding plugin %r", plugin) self.available_plugins = Plugins() try: plugin_class = self.available_plugins.get(plugin,None) if plugin_class == None: raise KeyError for device in plugin_class.implements: try: device_class=globals().get(device,None) if device_class == None: raise KeyError self.info("Activating %s plugin as %s..." % (plugin, device)) new_backend = device_class(self, plugin_class, **kwargs) self.active_backends[str(new_backend.uuid)] = new_backend return new_backend except KeyError: self.warning("Can't enable %s plugin, sub-system %s not found!" % (plugin, device)) except Exception, msg: self.warning("Can't enable %s plugin for sub-system %s, %s!" % (plugin, device, msg)) self.debug(traceback.format_exc()) except KeyError, error: self.warning("Can't enable %s plugin, not found!" % plugin) except Exception, msg: self.warning("Can't enable %s plugin, %s!" % (plugin, msg)) self.debug(traceback.format_exc()) def remove_plugin(self, plugin): """ removes a backend from Coherence """ """ plugin is the object return by add_plugin """ """ or an UUID string """ if isinstance(plugin,basestring): try: plugin = self.active_backends[plugin] except KeyError: self.warning("no backend with the uuid %r found" % plugin) return "" try: del self.active_backends[str(plugin.uuid)] self.info("removing plugin %r", plugin) plugin.unregister() return plugin.uuid except KeyError: self.warning("no backend with the uuid %r found" % plugin.uuid) return "" def writeable_config(self): """ do we have a new-style config file """ from coherence.extern.simple_config import ConfigItem if isinstance(self.config,ConfigItem): return True return False def store_plugin_config(self,uuid,items): """ find the backend with uuid and store in its the config the key and value pair(s) """ plugins = self.config.get('plugin') if plugins is None: self.warning("storing a plugin config option is only possible with the new config file format") return if isinstance(plugins,dict): plugins = [plugins] uuid = str(uuid) if uuid.startswith('uuid:'): uuid = uuid[5:] if isinstance(items,tuple): new = {} new[items[0]] = items[1] for plugin in plugins: try: if plugin['uuid'] == uuid: for k,v in items.items(): plugin[k] = v self.config.save() except: pass else: self.info("storing plugin config option for %s failed, plugin not found" % uuid) def receiver( self, signal, *args, **kwargs): #print "Coherence receiver called with", signal #print kwargs pass def shutdown( self,force=False): if force == True: self._incarnations_ = 1 if self._incarnations_ > 1: self._incarnations_ -= 1 return if self._mirabeau is not None: self._mirabeau.stop() self._mirabeau = None for backend in self.active_backends.itervalues(): backend.unregister() self.active_backends = {} """ send service unsubscribe messages """ try: if self.web_server.port != None: self.web_server.port.stopListening() self.web_server.port = None if hasattr(self.msearch, 'double_discover_loop'): self.msearch.double_discover_loop.stop() if hasattr(self.msearch, 'port'): self.msearch.port.stopListening() if hasattr(self.ssdp_server, 'resend_notify_loop'): self.ssdp_server.resend_notify_loop.stop() if hasattr(self.ssdp_server, 'port'): self.ssdp_server.port.stopListening() #self.renew_service_subscription_loop.stop() except: pass l = [] for root_device in self.get_devices(): for device in root_device.get_devices(): d = device.unsubscribe_service_subscriptions() l.append(d) d.addCallback(device.remove) d = root_device.unsubscribe_service_subscriptions() l.append(d) d.addCallback(root_device.remove) """anything left over""" self.ssdp_server.shutdown() dl = defer.DeferredList(l) self.warning('Coherence UPnP framework shutdown') return dl def check_devices(self): """ iterate over devices and their embedded ones and renew subscriptions """ for root_device in self.get_devices(): root_device.renew_service_subscriptions() for device in root_device.get_devices(): device.renew_service_subscriptions() def subscribe(self, name, callback): self._callbacks.setdefault(name,[]).append(callback) def unsubscribe(self, name, callback): callbacks = self._callbacks.get(name,[]) if callback in callbacks: callbacks.remove(callback) self._callbacks[name] = callbacks def callback(self, name, *args): for callback in self._callbacks.get(name,[]): callback(*args) def get_device_by_host(self, host): found = [] for device in self.devices: if device.get_host() == host: found.append(device) return found def get_device_with_usn(self, usn): found = None for device in self.devices: if device.get_usn() == usn: found = device break return found def get_device_with_id(self, device_id): found = None for device in self.devices: id = device.get_id() if device_id[:5] != 'uuid:': id = id[5:] if id == device_id: found = device break return found def get_devices(self): return self.devices def get_local_devices(self): return [d for d in self.devices if d.manifestation == 'local'] def get_nonlocal_devices(self): return [d for d in self.devices if d.manifestation == 'remote'] def create_device(self, device_type, infos): self.info("creating ", infos['ST'],infos['USN']) if infos['ST'] == 'upnp:rootdevice': self.info("creating upnp:rootdevice ", infos['USN']) root = RootDevice(infos) else: self.info("creating device/service ",infos['USN']) root_id = infos['USN'][:-len(infos['ST'])-2] root = self.get_device_with_id(root_id) device = Device(infos, root) # fire this only after the device detection is fully completed # and we are on the device level already, so we can work with them instead with the SSDP announce #if infos['ST'] == 'upnp:rootdevice': # self.callback("new_device", infos['ST'], infos) def add_device(self, device): self.info("adding device",device.get_id(),device.get_usn(),device.friendly_device_type) self.devices.append(device) def remove_device(self, device_type, infos): self.info("removed device",infos['ST'],infos['USN']) device = self.get_device_with_usn(infos['USN']) if device: louie.send('Coherence.UPnP.Device.removed', None, usn=infos['USN']) self.devices.remove(device) device.remove() if infos['ST'] == 'upnp:rootdevice': louie.send('Coherence.UPnP.RootDevice.removed', None, usn=infos['USN']) self.callback("removed_device", infos['ST'], infos['USN']) def add_web_resource(self, name, sub): self.children[name] = sub def remove_web_resource(self, name): try: del self.children[name] except KeyError: """ probably the backend init failed """ pass def connect(self,receiver,signal=louie.signal.All,sender=louie.sender.Any, weak=True): """ wrapper method around louie.connect """ louie.connect(receiver,signal=signal,sender=sender,weak=weak) def disconnect(self,receiver,signal=louie.signal.All,sender=louie.sender.Any, weak=True): """ wrapper method around louie.disconnect """ louie.disconnect(receiver,signal=signal,sender=sender,weak=weak) Coherence-0.6.6.2/coherence/dispatcher.py0000644000175000017500000001327111317660741016341 0ustar devdev from twisted.internet import defer class Receiver(object): def __init__(self, signal, callback, args, kwargs): self.signal = signal self.callback = callback self.arguments = args self.keywords = kwargs def __call__(self, *args, **kwargs): args = args + self.arguments kw = self.keywords.copy() if kwargs: kw.update(kwargs) return self.callback(*args, **kw) def __repr__(self): return "" % (id(self), self.signal, self.callback, ', '.join( ['%r' % x for x in self.arguments] ), ', '.join( ['%s=%s' % (x, y) for x, y in self.keywords.iteritems()] ) ) class UnknownSignal(Exception): pass class Dispatcher(object): __signals__ = {} def __init__(self): self.receivers = {} for signal in self.__signals__.iterkeys(): self.receivers[signal] = [] def connect(self, signal, callback, *args, **kw): receiver = Receiver(signal, callback, args, kw) try: self.receivers[signal].append(receiver) except KeyError: raise UnknownSignal(signal) return receiver def disconnect(self, receiver): if not receiver: return try: self.receivers[receiver.signal].remove(receiver) except KeyError: raise UnknownSignal(receiver.signal) except AttributeError: raise TypeError("'%r' is not a Receiver-like object" % receiver) except ValueError: # receiver not in the list, goal achieved pass def emit(self, signal, *args, **kwargs): results = [] errors = [] for receiver in self._get_receivers(signal): try: results.append((receiver, receiver(*args, **kwargs))) except Exception, e: errors.append((receiver, e)) return results, errors def deferred_emit(self, signal, *args, **kwargs): receivers = [] dfrs = [] # TODO: the loop is blocking, use callLaters and/or coiterate here for receiver in self._get_receivers(signal): receivers.append(receiver) dfrs.append(defer.maybeDeferred(receiver, *args, **kwargs)) if not dfrs: return defer.succeed([]) result_dfr = defer.DeferredList(dfrs) result_dfr.addCallback(self._merge_results_and_receivers, receivers) return result_dfr def save_emit(self, signal, *args, **kwargs): deferred = defer.Deferred() # run the deferred_emit in as a callback deferred.addCallback(self.deferred_emit, *args, **kwargs) # and callback the deferred with the signal as the 'result' in the # next mainloop iteration from twisted.internet import reactor reactor.callLater(0, deferred.callback, signal) return deferred def _merge_results_and_receivers(self, result, receivers): # make a list of (rec1, res1), (rec2, res2), (rec3, res3) ... return [(receiver, result[counter]) for counter, receiver in enumerate(receivers)] def _get_receivers(self, signal): try: return self.receivers[signal] except KeyError: raise UnknownSignal(signal) class SignalingProperty(object): """ Does emit self.signal when the value has changed but only if HAS changed (means old_value != new_value). """ def __init__(self, signal, var_name=None, default=None): self.signal = signal if var_name is None: var_name = "__%s__val" % signal self.var_name = var_name self.default = default def __get__(self, obj, objtype=None): return getattr(obj, self.var_name, self.default) def __set__(self, obj, value): if self.__get__(obj) == value: return setattr(obj, self.var_name, value) obj.emit(self.signal, value) class ChangedSignalingProperty(SignalingProperty): """ Does send the signal with two values when changed: 1. the new value 2. the value it has been before """ def __set__(self, obj, value): before = self.__get__(obj) if before == value: return setattr(obj, self.var_name, value) obj.emit(self.signal, value, before) class CustomSignalingProperty(object): """ Signal changes to this property. allows to specify fget and fset as the build in property-decorator. """ def __init__(self, signal, fget, fset, fdel=None, doc=None): """ fdel is there for API compability only. As there is no good way to signal a deletion it is not implemented at all. """ self.signal = signal self.fget = fget self.fset = fset self.__doc__ = doc def __get__(self, obj, objtype): return self.fget(obj) def __set__(self, obj, value): """ Call fset with value. Call fget before and after to figure out if something actually changed. Only if something changed the signal is emitted. The signal will be emitted with the new value given by fget. *Note*: This means that fset might gets called with the same value twice while the signal is not emitted a second time. You might want to check for that in your fset. """ old_value = self.fget(obj) self.fset(obj, value) new_value = self.fget(obj) if old_value == new_value: return obj.emit(self.signal, new_value) Coherence-0.6.6.2/coherence/web/0000755000000000000000000000000011317673117014773 5ustar rootrootCoherence-0.6.6.2/coherence/web/__init__.py0000644000175000017500000000000011317660740016510 0ustar devdevCoherence-0.6.6.2/coherence/web/ui.py0000644000175000017500000002027111317660740015402 0ustar devdev# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2006, Frank Scholz import os from twisted.internet import reactor from twisted.python import filepath, util from nevow import athena, inevow, loaders, tags, static from twisted.web import server, resource from zope.interface import implements, Interface import coherence.extern.louie as louie from coherence import log class IWeb(Interface): def goingLive(self): pass class Web(object): def __init__(self, coherence): super(Web, self).__init__() self.coherence = coherence class MenuFragment(athena.LiveElement, log.Loggable): logCategory = 'webui_menu_fragment' jsClass = u'Coherence.Base' fragmentName = 'coherence-menu' docFactory = loaders.stan( tags.div(render=tags.directive('liveElement'))[ tags.div(id="coherence_menu_box",class_="coherence_menu_box")[""], ] ) def __init__(self, page): super(MenuFragment, self).__init__() self.setFragmentParent(page) self.page = page self.coherence = page.coherence self.tabs = [] def going_live(self): self.info("add a view to the MenuFragment") d = self.page.notifyOnDisconnect() d.addCallback( self.remove_me) d.addErrback( self.remove_me) if len(self.tabs): return self.tabs else: return {} athena.expose(going_live) def add_tab(self,title,active,id): self.info("add tab %s to the MenuFragment" % title) new_tab = {u'title':unicode(title), u'active':unicode(active), u'athenaid':u'athenaid:%d' % id} for t in self.tabs: if t[u'title'] == new_tab[u'title']: return self.tabs.append(new_tab) self.callRemote('addTab', new_tab) def remove_me(self, result): self.info("remove view from MenuFragment") class DevicesFragment(athena.LiveElement, log.Loggable): logCategory = 'webui_device_fragment' jsClass = u'Coherence.Devices' fragmentName = 'coherence-devices' docFactory = loaders.stan( tags.div(render=tags.directive('liveElement'))[ tags.div(id="Devices-container",class_="coherence_container")[""], ] ) def __init__(self, page, active): super(DevicesFragment, self).__init__() self.setFragmentParent(page) self.page = page self.coherence = page.coherence self.active = active def going_live(self): self.info("add a view to the DevicesFragment", self._athenaID) self.page.menu.add_tab('Devices', self.active, self._athenaID) d = self.page.notifyOnDisconnect() d.addCallback(self.remove_me) d.addErrback(self.remove_me) devices = [] for device in self.coherence.get_devices(): if device is not None: devices.append({u'name': device.get_markup_name(), u'usn':unicode(device.get_usn())}) louie.connect(self.add_device, 'Coherence.UPnP.Device.detection_completed', louie.Any) louie.connect(self.remove_device, 'Coherence.UPnP.Device.removed', louie.Any) return devices athena.expose(going_live) def remove_me(self, result): self.info("remove view from the DevicesFragment") def add_device(self, device): self.info("DevicesFragment found device %s %s of type %s" %( device.get_usn(), device.get_friendly_name(), device.get_device_type())) self.callRemote('addDevice', {u'name': device.get_markup_name(), u'usn':unicode(device.get_usn())}) def remove_device(self, usn): self.info("DevicesFragment remove device %s", usn) self.callRemote('removeDevice', unicode(usn)) def render_devices(self, ctx, data): cl = [] self.info('children: %s' % self.coherence.children) for child in self.coherence.children: device = self.coherence.get_device_with_id(child) if device is not None: cl.append( tags.li[tags.a(href='/' + child)[ device.get_friendly_device_type, ':', device.get_device_type_version, ' ', device.get_friendly_name()]]) else: cl.append( tags.li[child]) return ctx.tag[tags.ul[cl]] class LoggingFragment(athena.LiveElement, log.Loggable): logCategory = 'webui_logging_fragment' jsClass = u'Coherence.Logging' fragmentName = 'coherence-logging' docFactory = loaders.stan( tags.div(render=tags.directive('liveElement'))[ tags.div(id="Logging-container",class_="coherence_container")[""], ] ) def __init__(self, page, active): super(LoggingFragment, self).__init__() self.setFragmentParent(page) self.page = page self.coherence = page.coherence self.active = active def going_live(self): self.info("add a view to the LoggingFragment",self._athenaID) self.page.menu.add_tab('Logging',self.active,self._athenaID) d = self.page.notifyOnDisconnect() d.addCallback( self.remove_me) d.addErrback( self.remove_me) return {} athena.expose(going_live) def remove_me(self, result): self.info("remove view from the LoggingFragment") class WebUI(athena.LivePage, log.Loggable): """ """ logCategory = 'webui' jsClass = u'Coherence' addSlash = True docFactory = loaders.xmlstr("""\
    Coherence
    """) def __init__(self, *a, **kw): super(WebUI, self).__init__( *a, **kw) self.coherence = self.rootObject.coherence self.jsModules.mapping.update({ 'MochiKit': filepath.FilePath(__file__).parent().child('static').child('MochiKit.js').path}) self.jsModules.mapping.update({ 'Coherence': filepath.FilePath(__file__).parent().child('static').child('Coherence.js').path}) self.jsModules.mapping.update({ 'Coherence.Base': filepath.FilePath(__file__).parent().child('static').child('Coherence.Base.js').path}) self.jsModules.mapping.update({ 'Coherence.Devices': filepath.FilePath(__file__).parent().child('static').child('Coherence.Devices.js').path}) self.jsModules.mapping.update({ 'Coherence.Logging': filepath.FilePath(__file__).parent().child('static').child('Coherence.Logging.js').path}) self.menu = MenuFragment(self) def childFactory(self, ctx, name): self.info('WebUI childFactory: %s' % name) try: return self.rootObject.coherence.children[name] except: ch = super(WebUI, self).childFactory(ctx, name) if ch is None: p = util.sibpath(__file__, name) self.info('looking for file',p) if os.path.exists(p): ch = static.File(p) return ch def render_listmenu(self, ctx, data): l = [] l.append(tags.div(id="t",class_="coherence_menu_item")[tags.a(href='/'+'devices',class_="coherence_menu_link")['Devices']]) l.append(tags.div(id="t",class_="coherence_menu_item")[tags.a(href='/'+'logging',class_="coherence_menu_link")['Logging']]) return ctx.tag[l] def render_menu(self, ctx, data): self.info('render_menu') return self.menu def render_devices(self, ctx, data): self.info('render_devices') f = DevicesFragment(self,'yes') return f def render_logging(self, ctx, data): self.info('render_logging') f = LoggingFragment(self,'no') return f