コード例 #1
0
ファイル: client.py プロジェクト: MarvinMuuss/webradio-1
class Client(GObject):
    (STATE_INITIALIZED,
     STATE_STATIONS_LOADED,
     STATE_CHANNELS_LOADED) = range(3)

    __gtype_name__ = 'WebRadioClient'

    __gsignals__ = {
        'station-added':       (SIGNAL_RUN_LAST, TYPE_NONE, (object,)),
        'channel-added':       (SIGNAL_RUN_LAST, TYPE_NONE, (object,)),
        'state-changed':       (SIGNAL_RUN_LAST, TYPE_NONE, ()),
        'stream-tags-changed': (SIGNAL_RUN_LAST, TYPE_NONE, ()),
    }

    @staticmethod
    def decode_stream(uri, title, length):
        return Stream(uri, title, length)

    @classmethod
    def decode_channel(cls, station, uri, tags, streams):
        streams = [cls.decode_stream(*s) for s in streams]
        return Channel(station, uri, tags, streams)

    def __init__(self):
        super(Client, self).__init__()

        self.__stations = dict()
        self.__channels = dict()
        self.__stream_tags = dict()

        self.__current_channel = None
        self.__is_playing = False

        def register_channel(station, channel):
            if station:
                station.channels.append(channel)

            for stream in channel.streams:
                self.__channels[stream.uri] = channel

            self.__channels[channel.uri] = channel

        def station_added_cb(station):
            id, title, uri, channels = station
            station = Station(id, title, uri)

            for channel in channels:
                channel = self.decode_channel(station, *channel)
                register_channel(station, channel)

            self.__stations[station.id] = station
            self.emit('station-added', station)

        def channel_added_cb(station_id, channel):
            station = self.find_station(station_id)
            channel = self.decode_channel(station, *channel)
            register_channel(station, channel)
            self.emit('channel-added', channel)

        def state_changed_cb(playing, stream_uri):
            self.__stream_tags = self.__service.GetStreamTags()
            self.__current_channel = self.__channels.get(stream_uri)
            self.__is_playing = playing
            self.emit('state-changed')

        def stream_tags_changed_cb(tags):
            self.__stream_tags.update(tags)
            self.emit('stream-tags-changed')

        def name_owner_cb(new_owner):
            if not new_owner:
                # FIXME
                from gtk import main_quit
                main_quit()

        self.__bus = SessionBus()
        proxy = self.__bus.get_object(Service.name, '/')
        self.__bus.watch_name_owner(Service.name, name_owner_cb)
        self.__service = Interface(proxy, Service.interface)
        self.__service.connect_to_signal('StationAdded',      station_added_cb)
        self.__service.connect_to_signal('ChannelAdded',      channel_added_cb)
        self.__service.connect_to_signal('StateChanged',      state_changed_cb)
        self.__service.connect_to_signal('StreamTagsChanged', stream_tags_changed_cb)

        for station in self.__service.GetStations():
            station_added_cb(station)

        state_changed_cb(*self.__service.GetState())

    def wait(self, stage=STATE_CHANNELS_LOADED):
        loop = MainLoop(None, True)

        def data_ready_cb(new_stage):
            if new_stage >= stage:
                loop.quit()

        self.__service.connect_to_signal('DataReady', data_ready_cb)

        if self.__service.GetDataStage() >= stage:
            loop.quit()

        progress_id = 0

        if loop.is_running():
            if sys.stdout.isatty():
                progress = ['-\r', '\\\r', '|\r', '/\r']

                def progress_cb():
                    c = progress.pop(0)
                    sys.stdout.write(c)
                    sys.stdout.flush()
                    progress.append(c)
                    return True

                progress_id = timeout_add(250, progress_cb)
                sys.stdout.write('  loading...\r')

            loop.run()

        if progress_id:
            source_remove(progress_id)
            sys.stdout.write('\r\033[K')
            sys.stdout.flush()

    def find_channels(self, query=[]):
        result = list()

        for station_id, channel in self.__service.Find(query):
            station = self.__stations.get(station_id)
            channel = self.decode_channel(station, *channel)
            result.append(channel)

        return result

    def find_station(self, id):
        return self.__stations.get(id)
    def get_stations(self):
        return self.__stations.values()

    def get_tags(self):
        return self.__service.GetTags()

    def play(self, channel):
        self.__service.Play(channel.streams[0].uri)

    def pause(self):
        self.__service.Pause()

    def resume(self):
        self.__service.Resume()

    def quit(self):
        self.__service.Quit()

    def get_equalizer_profiles(self):
        return self.__service.ListEqualizerProfiles()
    def __get_equalizer_profile(self):
        return self.__service.GetEqualizerProfile()
    def __set_equalizer_profile(self, value):
        self.__service.SetEqualizerProfile(value)

    is_playing = property(fget=lambda self: self.__is_playing)
    current_channel = property(fget=lambda self: self.__current_channel)
    stream_tags = property(fget=lambda self: self.__stream_tags)
    equalizer_profile = property(fget=__get_equalizer_profile, fset=__set_equalizer_profile)
コード例 #2
0
class TelepathyPlugin(gobject.GObject):
    __gsignals__ = {
        'contacts-online':
            # Contacts in the subscribe list have come online.
            # args:
            #   contact identification (based on key ID or JID): list of str
            #   contact handle: list of int or long
            #   contact identifier (JID): list of str or unicode
            (gobject.SIGNAL_RUN_FIRST, None, [object, object, object]),
        'contacts-offline':
            # Contacts in the subscribe list have gone offline.
            # args: iterable over contact handles
            (gobject.SIGNAL_RUN_FIRST, None, [object]),
        'status':
            # Connection status changed.
            # args: status, reason as for Telepathy StatusChanged
            (gobject.SIGNAL_RUN_FIRST, None, [int, int]),
        'activity-invitation':
            # We were invited to join an activity
            # args:
            #   activity room: Channel
            #   activity room handle: int or long
            #   inviter contact handle: int or long
            #   message: unicode
            (gobject.SIGNAL_RUN_FIRST, None, [object] * 4),
        'private-invitation':
            # We were invited to join a chat or a media call
            # args:
            #   channel object path
            (gobject.SIGNAL_RUN_FIRST, None, [object, str]),
        'want-to-connect':
            # The TelepathyPlugin wants to connect. presenceservice.py will
            # call the start() method if that's OK with its policy.
            (gobject.SIGNAL_RUN_FIRST, None, []),
    }

    _RECONNECT_INITIAL_TIMEOUT = 5000    # 5 seconds
    _RECONNECT_MAX_TIMEOUT = 300000      # 5 minutes
    _TP_CONN_MANAGER = 'gabble'
    _PROTOCOL = 'jabber'

    def __init__(self, registry, owner):
        """Initialize the ServerPlugin instance

        :Parameters:
            `registry` : telepathy.client.ManagerRegistry
                From the PresenceService, used to find the connection
                manager details
            `owner` : buddy.GenericOwner
                The Buddy representing the owner of this XO (normally a
                buddy.ShellOwner instance)
        """
        gobject.GObject.__init__(self)

        #: The connection, a `telepathy.client.Connection`
        self._conn = None

        #: List of dbus-python SignalMatch objects representing signal match
        #: rules associated with the connection, so we don't leak the match
        #: rules when disconnected.
        self._matches = []

        #: The manager registry, a `telepathy.client.ManagerRegistry`
        self._registry = registry

        #: set of contact handles: those for whom we've emitted contacts-online
        self._online_contacts = set()

        #: The owner, a `buddy.GenericOwner`
        self._owner = owner
        #: The owner's handle on this connection
        self.self_handle = None
        #: The owner's identifier (e.g. JID) on this connection
        self.self_identifier = None

        #: The connection's status
        self._conn_status = CONNECTION_STATUS_DISCONNECTED

        #: GLib source ID indicating when we may try to reconnect
        self._backoff_id = 0

        #: length of the next reconnect timeout
        self._reconnect_timeout = self._RECONNECT_INITIAL_TIMEOUT

        #: The ``subscribe`` channel: a `telepathy.client.Channel` or None
        self._subscribe_channel = None
        #: The members of the ``subscribe`` channel
        self._subscribe_members = set()
        #: The local-pending members of the ``subscribe`` channel
        self._subscribe_local_pending = set()
        #: The remote-pending members of the ``subscribe`` channel
        self._subscribe_remote_pending = set()

        #: The ``publish`` channel: a `telepathy.client.Channel` or None
        self._publish_channel = None

        self._session_bus = SessionBus()
        self._init_connection()

    @property
    def status(self):
        """Return the Telepathy connection status."""
        return self._conn_status

    def get_connection(self):
        """Retrieve our telepathy.client.Connection object"""
        return self._conn

    def suggest_room_for_activity(self, activity_id):
        """Suggest a room to use to share the given activity.
        """
        return activity_id

    def identify_contacts(self, tp_chan, handles, identifiers=None):
        raise NotImplementedError

    def _init_connection(self):
        """Set up our connection

        if there is no existing connection
            (_find_existing_connection returns None)
        produce a new connection with our protocol for our
        account.

        if there is an existing connection, reuse it by
        registering for various of events on it.
        """
        _logger.debug('%r: init connection', self)
        conn = self._find_existing_connection()
        if not conn:
            _logger.debug('%r: no existing connection', self)
            return

        m = conn[CONN_INTERFACE].connect_to_signal('StatusChanged',
           self._handle_connection_status_change)
        self._matches.append(m)
        m = conn[CONN_INTERFACE].connect_to_signal('NewChannel',
                                                   self._new_channel_cb)
        self._matches.append(m)
        self._watch_conn_name = self._session_bus.watch_name_owner(
            conn.service_name, self._watch_conn_name_cb)

        self._conn = conn

        status = self._conn.GetStatus()
        self._handle_connection_status_change(status, CONNECTION_STATUS_REASON_NONE_SPECIFIED)

    def _find_existing_connection(self):
        raise NotImplementedError

    def _watch_conn_name_cb(self, dbus_name):
        """Check if we still have a connection on the DBus session bus.

        If the connection disappears, stop the plugin.
        """
        if not dbus_name and self._conn is not None:
            _logger.warning(
                'D-Bus name %s disappeared, this probably means %s crashed',
                self._conn.service_name, self._TP_CONN_MANAGER)
            self._handle_connection_status_change(
                CONNECTION_STATUS_DISCONNECTED, 
                CONNECTION_STATUS_REASON_NONE_SPECIFIED)

    def _handle_connection_status_change(self, status, reason):
        if status == self._conn_status:
            return

        if status == CONNECTION_STATUS_CONNECTING:
            self._conn_status = status
            _logger.debug("%r: connecting...", self)
        elif status == CONNECTION_STATUS_CONNECTED:
            self._conn_status = status
            _logger.debug("%r: connected", self)
            self._connected_cb()
        elif status == CONNECTION_STATUS_DISCONNECTED:
            self._conn = None
            _logger.debug("%r: disconnected (reason %r)", self, reason)
            if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
                # FIXME: handle connection failure; retry later?
                _logger.debug("%r: authentification failed. Give up ", self)

        self.emit('status', self._conn_status, int(reason))

    def _could_connect(self):
        # Don't allow connection unless the reconnect timeout has elapsed,
        # or this is the first attempt
        return (self._backoff_id == 0)

    def _contacts_offline(self, handles):
        """Handle contacts going offline (send message, update set)"""
        self._online_contacts -= handles
        _logger.debug('%r: Contacts now offline: %r', self, handles)
        self.emit("contacts-offline", handles)

    def _inspect_handles_one_by_one(self, handle_type, handles):
        jids = []

        for handle in handles:
            try:
                jid = self._conn[CONN_INTERFACE].InspectHandles(handle_type,
                     [handle])
            except (InvalidArgument, InvalidHandle):
                continue
            else:
                jids.append(jid[0])

        return jids

    def _handle_is_channel_specific(self, handle):
        raise NotImplementedError

    def _contacts_online(self, handles):
        """Handle contacts coming online"""
        relevant = []

        for handle in handles:
            if handle == self.self_handle:
                # ignore network events for Owner property changes since those
                # are handled locally
                pass
            elif (handle in self._subscribe_members or
                  handle in self._subscribe_local_pending or
                  handle in self._subscribe_remote_pending or
                  not self._handle_is_channel_specific(handle)):
                relevant.append(handle)
            # else it's probably a channel-specific handle - can't create a
            # Buddy object for those yet

        if not relevant:
            return

        try:
            jids = self._conn[CONN_INTERFACE].InspectHandles(
                    HANDLE_TYPE_CONTACT, relevant)
        except (InvalidArgument, InvalidHandle):
            # InspectHandles failed so discard invalid handles by trying to
            # inspect them one by one.
            # FIXME: the Contacts interface should offer a proper way to do this
            jids = self._inspect_handles_one_by_one(HANDLE_TYPE_CONTACT,
                                                    relevant)
            if not jids:
                return

        handle_to_objid = self.identify_contacts(None, relevant, jids)
        objids = []
        for handle in relevant:
            objids.append(handle_to_objid[handle])

        self._online_contacts |= frozenset(relevant)
        _logger.debug('%r: Contacts now online:', self)
        for handle, objid in izip(relevant, objids):
            _logger.debug('  %u .../%s', handle, objid)
        self.emit('contacts-online', objids, relevant, jids)

    def _subscribe_members_changed_cb(self, message, added, removed,
                                      local_pending, remote_pending,
                                      actor, reason):

        added = set(added)
        removed = set(removed)
        local_pending = set(local_pending)
        remote_pending = set(remote_pending)

        affected = added|removed
        affected |= local_pending
        affected |= remote_pending

        self._subscribe_members -= affected
        self._subscribe_members |= added
        self._subscribe_local_pending -= affected
        self._subscribe_local_pending |= local_pending
        self._subscribe_remote_pending -= affected
        self._subscribe_remote_pending |= remote_pending

    def _publish_members_changed_cb(self, message, added, removed,
            local_pending, remote_pending, actor, reason):
        pass

    def _presence_update_cb(self, presence):
        """Send update for online/offline status of presence"""

        now_online = set()
        now_offline = set(presence.iterkeys())

        for handle in presence:
            timestamp, statuses = presence[handle]
            for status, params in statuses.items():
                # FIXME: use correct logic involving the GetStatuses method
                if status in ["available", "away", "brb", "busy", "dnd", "xa"]:
                    now_online.add(handle)
                    now_offline.discard(handle)

        now_online -= self._online_contacts
        now_offline &= self._online_contacts

        if now_online:
            self._contacts_online(now_online)
        if now_offline:
            self._contacts_offline(now_offline)

    def _new_channel_cb(self, object_path, channel_type, handle_type, handle,
                        suppress_handler):
        """Handle creation of a new channel
        """
        if (handle_type == HANDLE_TYPE_ROOM and
            channel_type == CHANNEL_TYPE_TEXT):

            def ready(channel):
                # workaround for odd behaviour of nested scopes
                room_self = []

                def got_lpwi(info):
                    for invitee, actor, reason, message in info:
                        if ((invitee == room_self[0]
                             or invitee == self.self_handle)
                            and reason == CHANNEL_GROUP_CHANGE_REASON_INVITED):
                            self.emit('activity-invitation', channel, handle,
                                      actor, message)
                def got_lpwi_err(e):
                    _logger.warning('Unable to get channel members for %s:',
                                    object_path, exc_info=1)

                def got_self_handle(self_handle):
                    room_self.append(self_handle)
                    group.GetLocalPendingMembersWithInfo(
                            reply_handler=got_lpwi,
                            error_handler=got_lpwi_err)
                def got_self_handle_err(e):
                    _logger.warning('Unable to get self-handle for %s:',
                                    object_path, exc_info=1)

                group = channel[CHANNEL_INTERFACE_GROUP]
                group.GetSelfHandle(reply_handler=got_self_handle,
                                    error_handler=got_self_handle_err)

            # we throw away the channel as soon as ready() finishes
            Channel(self._conn.service_name, object_path,
                    ready_handler=ready)

        elif (handle_type == HANDLE_TYPE_CONTACT and
              channel_type in (CHANNEL_TYPE_TEXT,
                               CHANNEL_TYPE_STREAMED_MEDIA)):
            self.emit("private-invitation", object_path, channel_type)

        elif (handle_type == HANDLE_TYPE_LIST and
              channel_type == CHANNEL_TYPE_CONTACT_LIST):
            name = self._conn.InspectHandles(handle_type, [handle])[0]
            channel = Channel(self._conn.service_name, object_path)

            if name == 'publish':
                self._publish_channel_cb(channel)
            elif name == 'subscribe':
                self._subscribe_channel_cb(channel)

    def _publish_channel_cb(self, channel):
        # the group of contacts who may receive your presence
        self._publish_channel = channel
        m = channel[CHANNEL_INTERFACE_GROUP].connect_to_signal(
                'MembersChanged', self._publish_members_changed_cb)
        self._matches.append(m)

    def _subscribe_channel_cb(self, channel):
        if self._subscribe_channel is not None:
            return
        # the group of contacts for whom you wish to receive presence
        self._subscribe_channel = channel
        m = channel[CHANNEL_INTERFACE_GROUP].connect_to_signal(
                'MembersChanged', self._subscribe_members_changed_cb)
        self._matches.append(m)
        subscribe_handles, subscribe_lp, subscribe_rp = \
                channel[CHANNEL_INTERFACE_GROUP].GetAllMembers()
        self._subscribe_members = set(subscribe_handles)
        self._subscribe_local_pending = set(subscribe_lp)
        self._subscribe_remote_pending = set(subscribe_rp)

        if CONN_INTERFACE_PRESENCE in self._conn:
            # request presence for everyone we're subscribed to
            self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(
                    subscribe_handles)

    def _connected_cb(self):
        """Callback on successful connection to a server
        """
        # FIXME: cope with CMs that lack some of the interfaces
        # FIXME: cope with CMs with no 'publish' or 'subscribe'

        # FIXME: retry if getting the channel times out

        interfaces = self._conn.get_valid_interfaces()

        # FIXME: this is a hack, but less harmful than the previous one -
        # the next version of telepathy-python will contain a better fix
        for iface in self._conn[CONN_INTERFACE].GetInterfaces():
            interfaces.add(iface)

        # FIXME: do this async?
        self.self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
        self.self_identifier = self._conn[CONN_INTERFACE].InspectHandles(
                HANDLE_TYPE_CONTACT, [self.self_handle])[0]

        if CONN_INTERFACE_PRESENCE in self._conn:
            # Ask to be notified about presence changes
            m = self._conn[CONN_INTERFACE_PRESENCE].connect_to_signal(
                    'PresenceUpdate', self._presence_update_cb)
            self._matches.append(m)
        else:
            _logger.warning('%s does not support Connection.Interface.'
                            'Presence', self._conn.object_path)

        properties = {
                CHANNEL + '.ChannelType': CHANNEL_TYPE_CONTACT_LIST,
                CHANNEL + '.TargetHandleType': HANDLE_TYPE_LIST,
                CHANNEL + '.TargetID': 'subscribe',
                }
        properties = dbus.Dictionary(properties, signature='sv')
        connection = self._conn[CONNECTION_INTERFACE_REQUESTS]
        is_ours, channel_path, properties = connection.EnsureChannel(properties)

        channel = Channel(self._conn.service_name, channel_path)
        self._subscribe_channel_cb(channel)
コード例 #3
0
ファイル: client.py プロジェクト: dineshkummarc/webradio
class Client(GObject):
    (STATE_INITIALIZED, STATE_STATIONS_LOADED,
     STATE_CHANNELS_LOADED) = range(3)

    __gtype_name__ = 'WebRadioClient'

    __gsignals__ = {
        'station-added': (SIGNAL_RUN_LAST, TYPE_NONE, (object, )),
        'channel-added': (SIGNAL_RUN_LAST, TYPE_NONE, (object, )),
        'state-changed': (SIGNAL_RUN_LAST, TYPE_NONE, ()),
        'stream-tags-changed': (SIGNAL_RUN_LAST, TYPE_NONE, ()),
    }

    @staticmethod
    def decode_stream(uri, title, length):
        return Stream(uri, title, length)

    @classmethod
    def decode_channel(cls, station, uri, tags, streams):
        streams = [cls.decode_stream(*s) for s in streams]
        return Channel(station, uri, tags, streams)

    def __init__(self):
        super(Client, self).__init__()

        self.__stations = dict()
        self.__channels = dict()
        self.__stream_tags = dict()

        self.__current_channel = None
        self.__is_playing = False

        def register_channel(station, channel):
            if station:
                station.channels.append(channel)

            for stream in channel.streams:
                self.__channels[stream.uri] = channel

            self.__channels[channel.uri] = channel

        def station_added_cb(station):
            id, title, uri, channels = station
            station = Station(id, title, uri)

            for channel in channels:
                channel = self.decode_channel(station, *channel)
                register_channel(station, channel)

            self.__stations[station.id] = station
            self.emit('station-added', station)

        def channel_added_cb(station_id, channel):
            station = self.find_station(station_id)
            channel = self.decode_channel(station, *channel)
            register_channel(station, channel)
            self.emit('channel-added', channel)

        def state_changed_cb(playing, stream_uri):
            self.__stream_tags = self.__service.GetStreamTags()
            self.__current_channel = self.__channels.get(stream_uri)
            self.__is_playing = playing
            self.emit('state-changed')

        def stream_tags_changed_cb(tags):
            self.__stream_tags.update(tags)
            self.emit('stream-tags-changed')

        def name_owner_cb(new_owner):
            if not new_owner:
                # FIXME
                from gtk import main_quit
                main_quit()

        self.__bus = SessionBus()
        proxy = self.__bus.get_object(Service.name, '/')
        self.__bus.watch_name_owner(Service.name, name_owner_cb)
        self.__service = Interface(proxy, Service.interface)
        self.__service.connect_to_signal('StationAdded', station_added_cb)
        self.__service.connect_to_signal('ChannelAdded', channel_added_cb)
        self.__service.connect_to_signal('StateChanged', state_changed_cb)
        self.__service.connect_to_signal('StreamTagsChanged',
                                         stream_tags_changed_cb)

        for station in self.__service.GetStations():
            station_added_cb(station)

        state_changed_cb(*self.__service.GetState())

    def wait(self, stage=STATE_CHANNELS_LOADED):
        loop = MainLoop(None, True)

        def data_ready_cb(new_stage):
            if new_stage >= stage:
                loop.quit()

        self.__service.connect_to_signal('DataReady', data_ready_cb)

        if self.__service.GetDataStage() >= stage:
            loop.quit()

        progress_id = 0

        if loop.is_running():
            if sys.stdout.isatty():
                progress = ['-\r', '\\\r', '|\r', '/\r']

                def progress_cb():
                    c = progress.pop(0)
                    sys.stdout.write(c)
                    sys.stdout.flush()
                    progress.append(c)
                    return True

                progress_id = timeout_add(250, progress_cb)
                sys.stdout.write('  loading...\r')

            loop.run()

        if progress_id:
            source_remove(progress_id)
            sys.stdout.write('\r\033[K')
            sys.stdout.flush()

    def find_channels(self, query=[]):
        result = list()

        for station_id, channel in self.__service.Find(query):
            station = self.__stations.get(station_id)
            channel = self.decode_channel(station, *channel)
            result.append(channel)

        return result

    def find_station(self, id):
        return self.__stations.get(id)

    def get_stations(self):
        return self.__stations.values()

    def get_tags(self):
        return self.__service.GetTags()

    def play(self, channel):
        self.__service.Play(channel.streams[0].uri)

    def pause(self):
        self.__service.Pause()

    def resume(self):
        self.__service.Resume()

    def quit(self):
        self.__service.Quit()

    def get_equalizer_profiles(self):
        return self.__service.ListEqualizerProfiles()

    def __get_equalizer_profile(self):
        return self.__service.GetEqualizerProfile()

    def __set_equalizer_profile(self, value):
        self.__service.SetEqualizerProfile(value)

    is_playing = property(fget=lambda self: self.__is_playing)
    current_channel = property(fget=lambda self: self.__current_channel)
    stream_tags = property(fget=lambda self: self.__stream_tags)
    equalizer_profile = property(fget=__get_equalizer_profile,
                                 fset=__set_equalizer_profile)