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 _poll_volume(self): try: self._mp_p.VolumeGet(reply_handler=self._notify_volume, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def __notify_playing_uri_changed(self, sp, uri): """Shell player signal callback to handle an item change.""" log.debug("playing uri changed: %s" % uri) db = self.__shell.props.db entry = sp.get_playing_entry() if entry is None: id = None else: id = db.entry_get(entry, rhythmdb.PROP_LOCATION) self.__item_id = id self.__item_entry = entry if entry is not None and id is not None: info = self.__get_item_from_entry(entry) img_data = db.entry_request_extra_metadata(entry, "rb:coverArt") if img_data is None: img_file = self.find_image(id) else: try: img_file = "%s/art.png" % self.config.cache_dir img_data.save(img_file, "png") except IOError, e: log.warning("failed to save cover art (%s)" % e) img_file = None
def _poll_progress(self): try: self._mp_p.PositionGet(reply_handler=self._notify_progress, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def write_dicts_to_file(filename, dicts, keys=None, comment=None): """Write a list of dictionaries into a file. @param filename: Name of the file to write into. @param dicts: Either a list of dictionaries or a list of strings, i.e. already flattened dictionaries. @keyword keys: See dict_to_string(). Only used if dictionaries are not yet flattened. @keyword comment: A comment text to put at the beginning of the file. """ lines = [] if comment: lines.append("%s\n" % comment) for dic in dicts: if not isinstance(dic, basestring): dic = dict_to_string(dic, keys=keys) lines.append("%s\n" % dic) try: with open(filename, "w") as fp: fp.writelines(lines) except IOError, e: log.warning("failed to write to %s (%s)" % (filename, e))
def down(self): if self._sock is not None: try: bluetooth.stop_advertising(self._sock) except bluetooth.BluetoothError, e: log.warning("failed to unregister bluetooth service (%s)" % e)
def write_string(self, s): """ Write a string. If the string is a unicode string, it will be encoded as a normal string in Bin.NET_ENCODING. If it already is a normal string it will be converted from Bin.HOST_ENCODING to Bin.NET_ENCODING. """ if s is None: self.__write_string(s) return if Bin.HOST_ENCODING not in Bin.NET_ENCODING_ALT: log.debug("convert '%s' from %s to %s" % (s, Bin.HOST_ENCODING, Bin.NET_ENCODING)) try: s = unicode(s, Bin.HOST_ENCODING).encode(Bin.NET_ENCODING) except UnicodeDecodeError as e: log.warning("could not decode '%s' with codec %s (%s)" % (s, Bin.HOST_ENCODING, e)) except UnicodeEncodeError as e: log.warning("could not encode '%s' with codec %s (%s)" % (s, Bin.NET_ENCODING, e)) self.__write_string(s)
def __io_send(self, fd, cond): """ GObject callback function (when data can be written). """ if not self.__snd_buff: self.__sid_out = 0 return False log.debug("try to send %d bytes to %s" % (len(self.__snd_buff), self)) try: sent = self.__sock.send(self.__snd_buff) except socket.error as e: log.warning("failed to send data to %s (%s)" % (self, e)) self.disconnect() return False log.debug("sent %d bytes" % sent) if sent == 0: log.warning("failed to send data to %s" % self) self.disconnect() return False self.__snd_buff = self.__snd_buff[sent:] if not self.__snd_buff: self.__sid_out = 0 return False else: return True
def _notify_tracklist_change(self, new_len): log.debug("tracklist change") try: self._mp_t.GetCurrentTrack(reply_handler=self._notify_position, error_handler=self._dbus_error) except DBusException as e: log.warning("dbus error: %s" % e)
def start(self, shell): if self.__shell is not None: log.warning("already started") return remuco.PlayerAdapter.start(self) self.__shell = shell sp = self.__shell.get_player() # gconf is used to adjust repeat and shuffle self.__gconf = gconf.client_get_default() # shortcuts to RB data self.__item_id = None self.__item_entry = None self.__playlist_sc = sp.get_playing_source() self.__queue_sc = self.__shell.props.queue_source # connect to shell player signals self.__signal_ids = ( sp.connect("playing_changed", self.__notify_playing_changed), sp.connect("playing_uri_changed", self.__notify_playing_uri_changed), sp.connect("playing-source-changed", self.__notify_source_changed) ) # state sync will happen by timeout # trigger item sync: self.__notify_playing_uri_changed(sp, sp.get_playing_path()) # item sync log.debug("start done")
def __thumbnail_img(self, img, img_size, img_type): if img_size == 0: return [] if isinstance(img, basestring) and img.startswith("file://"): img = urlparse.urlparse(img)[2] img = urllib.url2pathname(img) if not img: return [] try: if not isinstance(img, Image.Image): img = Image.open(img) img.thumbnail((img_size, img_size)) file_tmp = tempfile.TemporaryFile() if img_type == "JPEG" and img.mode == "P": img = img.convert("RGB") img.save(file_tmp, img_type) file_tmp.seek(0) thumb = file_tmp.read() file_tmp.close() return thumb except IOError, e: log.warning("failed to thumbnail %s (%s)" % (img, e)) return []
def build_message(id, serializable): """Create a message ready to send on a socket. @param id: message id @param serializable: message content (object of type Serializable) @return: the message as a binary string or None if serialization failed """ # This is not included in ClientConnection.send() because if there are # multiple clients, each client would serialize the data to send again. # Using this method, a message can be serialized once and send to many # clients. if serializable is not None: ba = serial.pack(serializable) if ba is None: log.warning("failed to serialize (msg-id %d)" % id) return None else: ba = "" header = struct.pack("!hi", id, len(ba)) return "%s%s" % (header, ba)
def _dbus_error(self, error): """ DBus error handler.""" if self._mp_p is None: return # do not log errors when not stopped already log.warning("DBus error: %s" % error)
def __trim_root_dirs(self, dirs): """Trim a directory list. Expands variables and '~' and removes duplicate, relative, non existent and optionally hidden directories. @return: a trimmed directory list """ trimmed = [] for dir in dirs: dir = os.path.expandvars(dir) dir = os.path.expanduser(dir) if not self.__show_hidden and dir.startswith("."): continue if not os.path.isabs(dir): log.warning("path %s not absolute, ignore" % dir) continue if not os.path.isdir(dir): log.warning("path %s not a directory, ignore" % dir) continue if dir not in trimmed: trimmed.append(dir) return trimmed
def disconnect(self, remove_from_list=True, send_bye_msg=False): """ Disconnect the client. @keyword remove_from_list: whether to remove the client from the client list or not (default is true) @keyword send_bye_msg: whether to send a bye message before disconnecting """ # send bye message if send_bye_msg and self.__sock is not None: log.info("send 'bye' to %s" % self) msg = build_message(message.CONN_BYE, None) sent = 0 retry = 0 while sent < len(msg) and retry < 10: try: sent += self.__sock.send(msg) except socket.error, e: log.warning("failed to send 'bye' to %s (%s)" % (self, e)) break time.sleep(0.02) retry += 1 if sent < len(msg): log.warning("failed to send 'bye' to %s" % self) else: # give client some time to close connection: time.sleep(0.1)
def __thumbnail_img(self, img, img_size, img_type): if img_size == 0: return [] if isinstance(img, basestring) and img.startswith("file://"): img = urlparse.urlparse(img)[2] img = urllib.url2pathname(img) if not img: return [] try: if not isinstance(img, Image.Image): img = Image.open(img) img.thumbnail((img_size, img_size)) file_tmp = tempfile.TemporaryFile() img.save(file_tmp, img_type) file_tmp.seek(0) thumb = file_tmp.read() file_tmp.close() return thumb except IOError, e: log.warning("failed to thumbnail %s (%s)" % (img, e)) return []
def start(self): PlayerAdapter.start(self) try: bus = dbus.SessionBus() proxy = bus.get_object("org.mpris.%s" % self.__name, "/Player") self._mp_p = dbus.Interface(proxy, "org.freedesktop.MediaPlayer") proxy = bus.get_object("org.mpris.%s" % self.__name, "/TrackList") self._mp_t = dbus.Interface(proxy, "org.freedesktop.MediaPlayer") except DBusException as e: raise StandardError("dbus error: %s" % e) try: self.__dbus_signal_handler = ( self._mp_p.connect_to_signal("TrackChange", self._notify_track), self._mp_p.connect_to_signal("StatusChange", self._notify_status), self._mp_p.connect_to_signal("CapsChange", self._notify_caps), self._mp_t.connect_to_signal("TrackListChange", self._notify_tracklist_change), ) except DBusException as e: raise StandardError("dbus error: %s" % e) try: self._mp_p.GetStatus(reply_handler=self._notify_status, error_handler=self._dbus_error) self._mp_p.GetMetadata(reply_handler=self._notify_track, error_handler=self._dbus_error) self._mp_p.GetCaps(reply_handler=self._notify_caps, error_handler=self._dbus_error) except DBusException as e: # this is not necessarily a fatal error log.warning("dbus error: %s" % e)
def __notify_playing_uri_changed(self, sp, uri): """Shell player signal callback to handle an item change.""" log.debug("playing uri changed: %s" % uri) db = self.__shell.props.db entry = sp.get_playing_entry() if entry is None: id = None else: id = db.entry_get(entry, rhythmdb.PROP_LOCATION) self.__item_id = id self.__item_entry = entry if entry is not None and id is not None: info = self.__get_item_from_entry(entry) img_data = db.entry_request_extra_metadata(entry, "rb:coverArt") if img_data is None: img_file = self.find_image(id) else: try: img_file = "%s/rhythmbox.cover" % self.config.cache img_data.save(img_file, "png") except IOError, e: log.warning("failed to save cover art (%s)" % e) img_file = None
def __save(self): try: self.__cp.write(open(self.__file_config, 'w')) except IOError, e: log.warning("failed to save config to %s (%s)" % (self.__file_config, e))
def ctrl_toggle_repeat(self): try: self._mp_t.SetLoop(not self._repeat, reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def ctrl_toggle_shuffle(self): try: self._mp_t.SetRandom(not self._shuffle, reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def __get_tracklist(self): """Get a list of track dicts of all tracks in the tracklist.""" try: length = self._mp_t.GetLength() except DBusException, e: log.warning("dbus error: %s" % e) length = 0
def _notify_tracklist_change(self, new_len): log.debug("tracklist change") try: self._mp_t.GetCurrentTrack(reply_handler=self._notify_position, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def __load(self): log.debug("try to load config from %s" % self.__file_config) try: self.__cp.read(self.__file_config) except ConfigParser.Error, e: log.warning("failed to read config from %s (%s) -> %s" % (self.__file_config, e, "using defaults"))
def ctrl_toggle_playing(self): try: if self._playing == PLAYBACK_STOP: self._mp_p.Play(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) else: self._mp_p.Pause(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException as e: log.warning("dbus error: %s" % e)
def request_queue(self, reply): sc = self.__queue_sc qm = sc.props.query_model try: reply.ids, reply.names = self.__get_item_list_from_qmodel(qm) except gobject.GError, e: log.warning("failed to get queue items: %s" % e)
def read_type(self, expected): type = self.read_byte() if type != expected: log.warning("bin data malformed (expected type %d, have %d)" % (expected, type)) return False else: return True
def ctrl_next(self): if not self.__can_next: log.debug("go to next item is currently not possible") return try: self._mp_p.Next(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException as e: log.warning("dbus error: %s" % e)
def zc_unpublish(): """Unpublish the previously published service.""" global _zc_group if _zc_group: try: _zc_group.Reset() except DBusException, e: log.warning("failed to unpublish zeroconf service (%s)" % e) _zc_group = None
def ctrl_previous(self): if not self.__can_prev: log.debug("go to previous is currently not possible") return try: self._mp_p.Prev(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException as e: log.warning("dbus error: %s" % e)
def ctrl_toggle_playing(self): try: if self._playing == PLAYBACK_STOP: self._mp_p.Play(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) else: self._mp_p.Pause(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def ctrl_next(self): if not self.__can_next: log.debug("go to next item is currently not possible") return try: self._mp_p.Next(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
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, e: log.warning("dbus error: %s" % e) return
def ctrl_previous(self): if not self.__can_prev: log.debug("go to previous is currently not possible") return try: self._mp_p.Prev(reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def __ctrl_shutdown_system(self): shutdown_cmd = config.get_system_shutdown_command() if shutdown_cmd: log.debug("run shutdown command") try: subprocess.Popen(shutdown_cmd, shell=True) except OSError, e: log.warning("failed to run shutdown command (%s)", e) return self.stop()
def request_playlist(self, reply): if self.__playlist_sc is None: reply.send() return try: qm = self.__playlist_sc.get_entry_view().props.model reply.ids, reply.names = self.__get_item_list_from_qmodel(qm) except gobject.GError, e: log.warning("failed to get playlist items: %s" % e)
def _poll_status(self): """Poll player status information. Some MPRIS players do not notify about all status changes, so that status must be polled. Subclasses may call this method for that purpose. """ try: self._mp_p.GetStatus(reply_handler=self._notify_status, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def action_playlist_item(self, action_id, positions, ids): if action_id == IA_REMOVE.id: positions.sort() positions.reverse() try: for pos in positions: self._mp_t.DelTrack(pos) except DBusException, e: log.warning("dbus error: %s" % e) return
def __io_send(self, fd, cond): """ GObject callback function (when data can be written). """ if not self.__snd_buff: self.__sid_out = 0 return False log.debug("try to send %d bytes to %s" % (len(self.__snd_buff), self)) try: sent = self.__sock.send(self.__snd_buff) except socket.error, e: log.warning("failed to send data to %s (%s)" % (self, e)) self.disconnect() return False
def ctrl_volume(self, direction): if direction == 0: volume = 0 else: volume = self.__volume + 5 * direction volume = min(volume, 100) volume = max(volume, 0) try: self._mp_p.VolumeSet(volume, reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def __recv_buff(self, rcv_buff): """ Receive some data and put it into the given ReceiveBuffer. @param rcv_buff: the receive buffer to put received data into @return: true if some data has been received, false if an error occurred """ try: log.debug("try to receive %d bytes" % rcv_buff.rest) data = self.__sock.recv(rcv_buff.rest) except socket.timeout, e: # TODO: needed? log.warning("connection to %s broken (%s)" % (self, e)) self.disconnect() return False
def request_mlib(self, reply, path): slm = self.__shell.props.sourcelist_model ### root ? ### if not path: for group in slm: group_name = group[2] reply.nested.append(group_name) reply.send() return ### group ? ### Library, Playlists if len(path) == 1: for group in slm: group_name = group[2] if path[0] == group_name: for sc in group.iterchildren(): source_name = sc[2] # FIXME: how to be l10n independent here? if source_name.startswith("Play Queue"): continue if source_name.startswith("Import Error"): continue log.debug("append %s" % source_name) reply.nested.append(source_name) break reply.list_actions = MLIB_LIST_ACTIONS reply.send() return ### regular playlist (source) ! ### Library/???, Playlists/??? sc = self.__mlib_path_to_source(path) if sc is None: reply.send() return qm = sc.get_entry_view().props.model try: reply.ids, reply.names = self.__get_item_list_from_qmodel(qm) except gobject.GError, e: log.warning("failed to list items: %s" % e)
def ctrl_seek(self, direction): if not self.__can_seek: log.debug("seeking is currently not possible") return self.__progress_now += 5 * direction self.__progress_now = min(self.__progress_now, self.__progress_max) self.__progress_now = max(self.__progress_now, 0) log.debug("new progress: %d" % self.__progress_now) try: self._mp_p.PositionSet(self.__progress_now * 1000, reply_handler=self._dbus_ignore, error_handler=self._dbus_error) except DBusException, e: log.warning("dbus error: %s" % e)
def action_mlib_list(self, action_id, path): if action_id == LA_PLAY.id: sc = self.__mlib_path_to_source(path) if sc is None: log.warning("no source for path %s" % path) return sp = self.__shell.get_player() if sc != self.__playlist_sc: try: sp.set_selected_source(sc) sp.set_playing_source(sc) self.__jump_in_plq(sc, 0) except gobject.GError, e: log.debug("switching source failed: %s" % str(e))