def notify(title, text): """Notify the user that a new device has been loggend.""" try: bus = dbus.SessionBus() except DBusException as e: log.error("no dbus session bus (%s)" % e) return try: proxy = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") notid = dbus.Interface(proxy, "org.freedesktop.Notifications") except DBusException as e: log.error("failed to connect to notification daemon (%s)" % e) return try: caps = notid.GetCapabilities() except DBusException as e: return if not caps or "body-markup" not in caps: text = text.replace("<b>", "") text = text.replace("</b>", "") try: notid.Notify("Remuco", 0, "phone", title, text, [], {}, 15) except DBusException as e: log.warning("user notification failed (%s)" % e) return
def ctrl_previous(self): """Play the previous item. @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def __init__(self, clients, pinfo, msg_handler_fn, config): """ Create a new server. @param clients: a list to add connected clients to @param pinfo: player info (type data.PlayerInfo) @param msg_handler_fn: callback function for passing received messages to @param config: adapter configuration """ self.__clients = clients self.__msg_handler_fn = msg_handler_fn self.__pinfo_msg = build_message(message.CONN_PINFO, pinfo) self.__sid = None self._pinfo = pinfo self._config = config self._sock = None # set up socket try: self._sock = self._create_socket() self._sock.settimeout(_Server.SOCKET_TIMEOUT) except (IOError, socket.error), e: # TODO: socket.error may be removed when 2.5 support is dropped log.error("failed to set up %s server (%s)" % (self._get_type(), e)) return
def __handle_message_action(self, id, bindata): a = serial.unpack(Action, bindata) if a is None: return if id == message.ACT_PLAYLIST: self.action_playlist_item(a.id, a.positions, a.items) elif id == message.ACT_QUEUE: self.action_queue_item(a.id, a.positions, a.items) elif id == message.ACT_MLIB and a.id < 0: # list action id self.action_mlib_list(a.id, a.path) elif id == message.ACT_MLIB and a.id > 0: # item action id self.action_mlib_item(a.id, a.path, a.positions, a.items) elif id == message.ACT_FILES: uris = self.__util_files_to_uris(a.items) self.action_files(a.id, a.items, uris) elif id == message.ACT_SEARCH: self.action_search_item(a.id, a.positions, a.items) else: log.error("** BUG ** unexpected action message: %d" % id)
def __handle_message(self, client, id, bindata): if message.is_control(id): log.debug("control from client %s" % client) self.__handle_message_control(id, bindata) elif message.is_action(id): log.debug("action from client %s" % client) self.__handle_message_action(id, bindata) elif message.is_request(id): log.debug("request from client %s" % client) self.__handle_message_request(client, id, bindata) elif id == message.PRIV_INITIAL_SYNC: msg = net.build_message(message.SYNC_STATE, self.__state) client.send(msg) msg = net.build_message(message.SYNC_PROGRESS, self.__progress) client.send(msg) msg = net.build_message(message.SYNC_ITEM, self.__item(client)) client.send(msg) else: log.error("** BUG ** unexpected message: %d" % id)
def send(self, msg): """Send a message to the client. @param msg: complete message (incl. ID and length) in binary format (net.build_message() is your friend here) @see: net.build_message() """ if msg is None: log.error("** BUG ** msg is None") return if self.__sock is None: log.debug("cannot send message to %s, already disconnected" % self) return if self.__psave: log.debug("%s is in sleep mode, send nothing" % self) return self.__snd_buff = "%s%s" % (self.__snd_buff, msg) # if not already trying to send data .. if self.__sid_out == 0: # .. do it when it is possible: self.__sid_out = gobject.io_add_watch(self.__sock, gobject.IO_OUT, self.__io_send)
def get_buff(self): if isinstance(self.__data, basestring): return self.__data elif isinstance(self.__data, array.array): return self.__data.tostring() else: log.error("** BUG ** unexpected buffer type")
def get_buff(self): if isinstance(self.__data, str): return self.__data elif isinstance(self.__data, array.array): return self.__data.tostring() else: log.error("** BUG ** unexpected buffer type")
def ctrl_toggle_playing(self): """Toggle play and pause. @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def __handle_message_request(self, client, id, bindata): request = serial.unpack(Request, bindata) if request is None: return reply = ListReply(client, request.request_id, id, request.page, path=request.path) if id == message.REQ_PLAYLIST: self.request_playlist(reply) elif id == message.REQ_QUEUE: self.request_queue(reply) elif id == message.REQ_MLIB: self.request_mlib(reply, request.path) elif id == message.REQ_FILES: reply.nested, reply.ids, reply.names = self.__filelib.get_level(request.path) reply.send() elif id == message.REQ_SEARCH: self.request_search(reply, request.path) else: log.error("** BUG ** unexpected request message: %d" % id)
def getx(self, key, default, converter=None, save=True): """Get the value of a non-standard, player specific option. @param key: config option name @param default: default value (as string!) @keyword converter: value converter function, e.g. `int` @keyword save: save default value in config file if not yet set @return: option value, optionally converted """ key = "x-%s" % key if not self.__cp.has_option(self.player, key) and save: self.__cp.set(self.player, key, default) self.__save() try: value = self.__cp.get(self.player, key) except ConfigParser.NoOptionError: value = default converter = converter or (lambda v: v) try: return converter(value) except Exception, e: log.error("malformed option '%s: %s' (%s)" % (key, value, e)) return converter(default) # if this fails then, it's a bug
def ctrl_toggle_fullscreen(self): """Toggle full screen mode. @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def notify(title, text): """Notify the user that a new device has been loggend.""" try: bus = dbus.SessionBus() except DBusException, e: log.error("no dbus session bus (%s)" % e) return
def action_files(self, action_id, files, uris): if action_id == FA_ENQUEUE.id: subprocess.Popen(["totem", "--enqueue"] + uris) elif action_id == FA_SETPL.id: subprocess.Popen(["totem", "--replace"] + uris) else: log.error("** BUG ** unexpected action ID")
def action_search_item(self, action_id, positions, ids): if action_id == IA_ENQUEUE.id: self.__enqueue_items(ids) else: log.error("** BUG ** unexpected action: %d" % action_id)
def ctrl_toggle_shuffle(self): """Toggle shuffle mode. @note: Override if it is possible and makes sense. @see: update_shuffle() """ log.error("** BUG ** in feature handling")
def _start_pa(pa): """Start the given player adapter with error handling.""" log.info("start player adapter") try: pa.start() except StandardError, e: log.error("failed to start player adapter (%s)" % e) return False
def ctrl_rate(self, rating): """Rate the currently played item. @param rating: rating value (int) @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def request_queue(self, reply): """Request the content of the play queue. @param reply: a ListReply object @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def action_queue_item(self, action_id, positions, ids): if action_id == IA_JUMP.id: track = self.__ex.collection.get_track_by_loc(ids[0]) self.__ex.queue.next(track=track) self.__remove_tracks_from_playlist(positions, pl=self.__ex.queue) elif action_id == IA_REMOVE.id: self.__remove_tracks_from_playlist(positions, pl=self.__ex.queue) else: log.error("** BUG ** unexpected queue item action")
def __ctrl_shutdown_system(self): if self.config.system_shutdown_enabled: log.debug("run system shutdown command") cmd = "sh -c '%s'" % self.config.system_shutdown_cmd ret, out = commands.getstatusoutput(cmd) if ret != os.EX_OK: log.error("system-shutdown failed: %s" % out) return self.stop()
def ctrl_navigate(self, action): """Navigate through menus (typically DVD menus). @param action: A number selecting one of these actions: UP, DOWN, LEFT, RIGHT, SELECT, RETURN, TOPMENU (e.g. 0 is UP and 6 is TOPMENU). @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def action_playlist_item(self, action_id, positions, ids): if self.__handle_generic_item_action(action_id, ids): pass # we are done elif action_id == IA_JUMP.id: track = self.__ex.collection.get_track_by_loc(ids[0]) self.__ex.queue.next(track=track) self.__ex.queue.current_playlist.set_current_pos(positions[0]) elif action_id == IA_REMOVE.id: self.__remove_tracks_from_playlist(positions) else: log.error("** BUG ** unexpected playlist item action")
def ctrl_volume(self, direction): """Adjust volume. @param volume: * -1: decrease by some percent (5 is a good value) * 0: mute volume * +1: increase by some percent (5 is a good value) @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
def action_mlib_list(self, action_id, path): """Do an action on a list from the player's media library. @param action_id: ID of the action to do - this specifies one of the actions passed previously to reply_mlib_request() by the keyword 'list_actions' @param path: path specifying the list to apply the action to @note: Override if list actions gets passed to reply_mlib_request(). """ log.error("** BUG ** action_mlib_list() not implemented")
def action_mlib_item(self, action_id, path, positions, ids): if self.__handle_generic_item_action(action_id, ids): pass # we are done elif action_id == IA_JUMP.id: self.action_mlib_list(LA_ACTIVATE.id, path) track = self.__ex.collection.get_track_by_loc(ids[0]) self.__ex.queue.next(track=track) self.__ex.queue.current_playlist.set_current_pos(positions[0]) elif action_id == IA_REMOVE.id: pl, i = self.__get_open_playlist(path) self.__remove_tracks_from_playlist(positions, pl=pl) else: log.error("** BUG ** unexpected mlib item action")
def action_files(self, action_id, files, uris): if action_id == IA_APPEND.id or action_id == IA_APPEND_PLAY.id: try: self._mp_t.AddTrack(uris[0], action_id == IA_APPEND_PLAY.id) for uri in uris[1:]: self._mp_t.AddTrack(uri, False) except DBusException as e: log.warning("dbus error: %s" % e) return else: log.error("** BUG ** unexpected action: %d" % action_id)
def __ctrl_volume_master(self, direction): """Adjust volume using custom volume command (instead of player).""" if direction < 0: cmd = self.config.master_volume_down_cmd elif direction > 0: cmd = self.config.master_volume_up_cmd else: cmd = self.config.master_volume_mute_cmd ret, out = commands.getstatusoutput("sh -c '%s'" % cmd) if ret != os.EX_OK: log.error("master-volume-... failed: %s" % out) else: gobject.idle_add(self.__update_volume_master)
def action_search_item(self, action_id, positions, ids): """Do an action on one or more items from a search result. @param action_id: ID of the action to do - this specifies one of the actions passed previously to reply_search_request() by the keyword 'item_actions' @param positions: list of positions to apply the action to @param ids: list of IDs to apply the action to @note: Override if list actions gets passed to reply_search_request(). """ log.error("** BUG ** action_search_item() not implemented")
def _start_pa(pa): """Start the given player adapter with error handling.""" log.info("start player adapter") try: pa.start() except StandardError as e: log.error("failed to start player adapter (%s)" % e) return False except Exception as e: log.exception("** BUG ** %s", e) return False else: log.info("player adapter started") return True
def ctrl_tag(self, id, tags): """Attach some tags to an item. @param id: ID of the item to attach the tags to @param tags: a list of tags @note: Tags does not mean ID3 tags or similar. It means the general idea of tags (e.g. like used at last.fm). @note: Override if it is possible and makes sense. """ log.error("** BUG ** in feature handling")
class _DBusObserver(): """DBus based observer for a player's run state. A DBus observer uses DBus name owner change notifications to automatically start and stop a player adapter if the corresponding media player starts or stops. """ def __init__(self, pa, dbus_name): """Create a new DBus observer. @param pa: the PlayerAdapter to automatically start and stop @param dbus_name: the bus name used by the adapter's media player """ DBusGMainLoop(set_as_default=True) self.__pa = pa self.__dbus_name = dbus_name try: bus = dbus.SessionBus() except DBusException, e: log.error("no dbus session bus (%s)" % e) return try: proxy = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH) self.__dbus = dbus.Interface(proxy, dbus.BUS_DAEMON_IFACE) except DBusException, e: log.error("failed to connect to dbus daemon (%s)" % e) return
def action_mlib_item(self, action_id, path, positions, ids): if action_id == IA_ENQUEUE.id: self.__enqueue_items(ids) if action_id == IA_JUMP.id: self.action_mlib_list(LA_PLAY.id, path) # delay jump, otherwise sync with clients sometimes fails gobject.timeout_add(100, self.action_playlist_item, IA_JUMP.id, positions, ids) else: log.error("** BUG ** unexpected action: %d" % action_id)
def __getattribute__(self, attr): """Attribute-style access to standard options.""" try: return super(Config, self).__getattribute__(attr) except AttributeError, e: _attr = attr.replace("_", "-") if _attr in _OPTIONS: attr = _attr elif attr not in _OPTIONS: raise e value = self.__cp.get(self.player, attr) converter = _OPTIONS[attr][1] or (lambda v: v) try: return converter(value) except Exception, e: log.error("malformed option '%s: %s' (%s)" % (attr, e)) return converter(_DEFAULTS[attr])
def __init__(self, pa, dbus_name): """Create a new DBus observer. @param pa: the PlayerAdapter to automatically start and stop @param dbus_name: the bus name used by the adapter's media player """ DBusGMainLoop(set_as_default=True) self.__pa = pa self.__dbus_name = dbus_name try: bus = dbus.SessionBus() except DBusException, e: log.error("no dbus session bus (%s)" % e) return
def __handle_io(self, fd, condition): """ GObject callback function (when there is a socket event). """ if condition == gobject.IO_IN: try: log.debug("connection request from %s client" % self._get_type()) client_sock, addr = self._sock.accept() log.debug("connection request accepted") client_sock.setblocking(0) ClientConnection(client_sock, addr, self.__clients, self.__pinfo_msg, self.__msg_handler_fn, self._get_type()) except IOError, e: log.error("accepting %s client failed: %s" % (self._get_type(), e)) return True
def request_mlib(self, reply, path): if not path: reply.nested = (PLAYLISTS_OPEN, PLAYLISTS_SMART, PLAYLISTS_CUSTOM) reply.send() elif path[0] == PLAYLISTS_SMART: if len(path) == 1: reply.nested = self.__ex.smart_playlists.list_playlists() reply.list_actions = MLIB_LIST_ACTIONS else: pl = self.__ex.smart_playlists.get_playlist(path[1]) reply.ids, reply.names = ["XXX" ], ["This is a dynamic playlist!"] elif path[0] == PLAYLISTS_CUSTOM: if len(path) == 1: reply.nested = self.__ex.playlists.list_playlists() reply.list_actions = MLIB_LIST_ACTIONS else: pl = self.__ex.playlists.get_playlist(path[1]) tracks = pl.get_ordered_tracks() reply.ids, reply.names = self.__tracklist_to_itemlist(tracks) elif path[0] == PLAYLISTS_OPEN: if len(path) == 1: plo_list, pln_list = self.__get_open_playlists() reply.nested = pln_list reply.list_actions = MLIB_LIST_OPEN_ACTIONS else: pl, i = self.__get_open_playlist(path) tracks = pl.get_ordered_tracks() reply.ids, reply.names = self.__tracklist_to_itemlist(tracks) reply.item_actions = MLIB_ITEM_ACTIONS else: log.error("** BUG ** unexpected mlib path") reply.send()
def action_mlib_list(self, action_id, path): if action_id == LA_ACTIVATE.id: if path[0] == PLAYLISTS_OPEN: pl, i = self.__get_open_playlist(path) self.__ex.gui.main.playlist_notebook.set_current_page(i) else: log.error("** BUG ** unexpected mlib path %s" % path) elif action_id == LA_OPEN.id: if path[0] == PLAYLISTS_SMART: pl = self.__ex.smart_playlists.get_playlist(path[1]) pl = pl.get_playlist(self.__ex.collection) self.__ex.gui.main.add_playlist(pl) elif path[0] == PLAYLISTS_CUSTOM: pl = self.__ex.playlists.get_playlist(path[1]) self.__ex.gui.main.add_playlist(pl) else: log.error("** BUG ** unexpected mlib path %s" % path) elif action_id == LA_CLOSE.id: pl, i = self.__get_open_playlist(path) nb = self.__ex.gui.main.playlist_notebook nb.remove_page(i) else: log.error("** BUG ** unexpected mlib list action")
def __mlib_path_to_source(self, path): """Get the source object related to a library path. @param path: must contain the source' group and name (2 element list) """ if len(path) != 2: log.error("** BUG ** invalid path length: %s" % path) return None group_name, source_name = path if group_name is None or source_name is None: return None slm = self.__shell.props.sourcelist_model for group in slm: if group_name == group[2]: for source in group.iterchildren(): if source_name == source[2]: return source[3]
def __init__(self, player_name): """Create a new instance for the given player (adapter).""" super(Config, self).__init__() # convert descriptive name to a plain canonical one self.player = re.sub(r'[^\w-]', '', player_name).lower() # paths self.dir = join(user_config_dir, "remuco") self.cache = join(user_cache_dir, "remuco") self.file = join(self.dir, "remuco.cfg") # remove old stuff self.__cleanup() # create directories for dname in (self.dir, self.cache): try: if not isdir(dname): os.makedirs(dname) except OSError, e: log.error("failed to make dir: %s", e)
def pack(serializable): fmt = serializable.get_fmt() data = serializable.get_data() if len(fmt) != len(data): log.error("** BUG ** format string and data differ in length") return None #log.debug("data to pack: %s" % str(data)) bin = Bin() try: for i in range(0, len(fmt)): type = fmt[i] bin.write_byte(type) if type == TYPE_Y: bin.write_byte(data[i]) elif type == TYPE_B: bin.write_boolean(data[i]) elif type == TYPE_N: bin.write_short(data[i]) elif type == TYPE_I: bin.write_int(data[i]) elif type == TYPE_L: bin.write_long(data[i]) elif type == TYPE_S: bin.write_string(data[i]) elif type == TYPE_AB: bin.write_array_boolean(data[i]) elif type == TYPE_AY: bin.write_array_byte(data[i]) elif type == TYPE_AN: bin.write_array_short(data[i]) elif type == TYPE_AI: bin.write_array_int(data[i]) elif type == TYPE_AL: bin.write_array_long(data[i]) elif type == TYPE_AS: bin.write_array_string(data[i]) else: log.error("** BUG ** unknown type (%d) in format string" % type) return None except struct.error, e: log.exception("** BUG ** %s" % e) return None
def notify(title, text): """Notify the user that a new device has been loggend.""" try: bus = dbus.SessionBus() except DBusException, e: log.error("no dbus session bus (%s)" % e) return try: proxy = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") notid = dbus.Interface(proxy, "org.freedesktop.Notifications") except DBusException, e: log.error("failed to connect to notification daemon (%s)" % e) return try: caps = notid.GetCapabilities() except DBusException, e: return if not caps or "body-markup" not in caps: text = text.replace("<b>", "") text = text.replace("</b>", "") try: notid.Notify("Remuco", 0, "phone", title, text, [], {}, 15) except DBusException, e: log.warning("user notification failed (%s)" % e)
self._get_type()) client_sock, addr = self._sock.accept() log.debug("connection request accepted") client_sock.setblocking(0) ClientConnection(client_sock, addr, self.__clients, self.__pinfo_msg, self.__msg_handler_fn, self._get_type()) except IOError, e: log.error("accepting %s client failed: %s" % (self._get_type(), e)) return True else: log.error("%s server socket broken" % self._get_type()) self.__sid = None return False def down(self): """ Shut down the server. """ if self.__sid is not None: gobject.source_remove(self.__sid) if self._sock is not None: log.debug("closing %s server socket" % self._get_type()) try: self._sock.shutdown(socket.SHUT_RDWR) except socket.error: pass
def action_search_item(self, action_id, positions, ids): if self.__handle_generic_item_action(action_id, ids): pass # we are done else: log.error("** BUG ** unexpected search item action")
proxy = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH) self.__dbus = dbus.Interface(proxy, dbus.BUS_DAEMON_IFACE) except DBusException, e: log.error("failed to connect to dbus daemon (%s)" % e) return try: self.__handlers = (self.__dbus.connect_to_signal( "NameOwnerChanged", self.__on_owner_change, arg0=self.__dbus_name), ) self.__dbus.NameHasOwner(self.__dbus_name, reply_handler=self.__reply_has_owner, error_handler=self.__dbus_error) except DBusException, e: log.error("failed to talk with dbus daemon (%s)" % e) return def __on_owner_change(self, name, old, new): log.debug("dbus name owner changed: '%s' -> '%s'" % (old, new)) _stop_pa(self.__pa) if new: _start_pa(self.__pa) def __reply_has_owner(self, has_owner): log.debug("dbus name has owner: %s" % has_owner) if has_owner:
def action_playlist_item(self, action_id, positions, ids): if action_id == IA_JUMP.id: try: self.__jump_in_plq(self.__playlist_sc, positions[0]) except gobject.GError, e: log.debug("playlist jump failed: %s" % e) elif action_id == IA_ENQUEUE.id: self.__enqueue_items(ids) else: log.error("** BUG ** unexpected action: %d" % action_id) def action_queue_item(self, action_id, positions, ids): if action_id == IA_JUMP.id: try: self.__jump_in_plq(self.__queue_sc, positions[0]) except gobject.GError, e: log.debug("queue jump failed: %s" % e) elif action_id == IA_REMOVE.id: for id in ids: self.__shell.remove_from_queue(id)