def add_station(uri): """Fetches the URI content and extracts IRFiles""" irfs = [] if isinstance(uri, unicode): uri = uri.encode('utf-8') if uri.lower().endswith(".pls") or uri.lower().endswith(".m3u"): try: sock = urllib.urlopen(uri) except EnvironmentError as e: encoding = util.get_locale_encoding() try: err = e.strerror.decode(encoding, 'replace') except (TypeError, AttributeError): err = e.strerror[1].decode(encoding, 'replace') qltk.ErrorMessage(None, _("Unable to add station"), err).run() return [] if uri.lower().endswith(".pls"): irfs = ParsePLS(sock) elif uri.lower().endswith(".m3u"): irfs = ParseM3U(sock) sock.close() else: try: irfs = [IRFile(uri)] except ValueError, err: qltk.ErrorMessage(None, _("Unable to add station"), err).run()
def __edit_tag_name(self, renderer, path, new_tag, model): new_tag = ' '.join(new_tag.splitlines()).lower() row = model[path] if new_tag == row[TAG]: return elif not self.__songinfo.can_change(row[TAG]): # Can't remove the old tag. title = _("Invalid tag") msg = _( "Invalid tag <b>%s</b>\n\nThe files currently" " selected do not support editing this tag.") % util.escape( row[TAG]) qltk.ErrorMessage(self, title, msg).run() elif not self.__songinfo.can_change(new_tag): # Can't add the new tag. title = _("Invalid tag") msg = _("Invalid tag <b>%s</b>\n\nThe files currently" " selected do not support editing this tag." ) % util.escape(new_tag) qltk.ErrorMessage(self, title, msg).run() else: if new_tag in massagers.tags: fmt = massagers.tags[new_tag] v = util.unescape(row[VALUE]) if not fmt.is_valid(v): qltk.WarningMessage( self, _("Invalid value"), _("Invalid value: <b>%(value)s</b>\n\n%(error)s") % { "value": row[VALUE], "error": fmt.error }).run() return value = fmt.validate(v) else: value = row[VALUE] value = util.unescape(value) if row[ORIGVALUE] is None: # The tag hasn't been saved yet, so we can just update # the name in the model, and the value, since it # may have been re-validated. row[TAG] = new_tag row[VALUE] = value else: # The tag has been saved, so delete the old tag and # add a new one with the old (or sanitized) value. row[RENAMED] = row[EDITED] = True row[ORIGTAG] = row[TAG] row[TAG] = new_tag
def plugin_playlist(self, playlist): # TODO - only get coordinator nodes, somehow self.device: SoCo = soco.discovery.any_soco() device = self.device if not device: qltk.ErrorMessage( app.window, _("Error finding Sonos device(s)"), _("Error finding Sonos. Please check settings")).run() else: sonos_pls = device.get_sonos_playlists() pl_id_to_name = {pl.item_id: pl.title for pl in sonos_pls} print_d("Sonos playlists: %s" % pl_id_to_name) ret = GetSonosPlaylistDialog(pl_id_to_name).run(playlist.name) if ret: spl_id, name = ret if spl_id: spl: DidlPlaylistContainer = next(s for s in sonos_pls if s.item_id == spl_id) print_w(f"Replacing existing Sonos playlist {spl!r}") device.remove_sonos_playlist(spl) print_d(f"Creating new playlist {name!r}") spl = device.create_sonos_playlist(name) task = Task("Sonos", _("Export to Sonos playlist"), stop=self.__cancel_add) copool.add(self.__add_songs, task, playlist.songs, spl, funcid="sonos-playlist-save")
def __save(self, *data): """Save the cover and spawn the program to edit it if selected""" filename = self.name_combo.get_active_text() # Allow support for filename patterns pattern = Pattern(filename) filename = fsencode(pattern.format(self.song)) file_path = os.path.join(self.dirname, filename) msg = (_('The file <b>%s</b> already exists.\n\nOverwrite?') % util.escape(filename)) if (os.path.exists(file_path) and not qltk.ConfirmAction(None, _('File exists'), msg).run()): return try: f = open(file_path, 'wb') f.write(self.current_data) f.close() except IOError: qltk.ErrorMessage(None, _('Saving failed'), _('Unable to save "%s".') % file_path).run() else: if self.open_check.get_active(): try: util.spawn([self.cmd.get_text(), file_path]) except: pass app.window.emit("artwork-changed", [self.song]) self.main_win.destroy()
def __save(self, *data): """Save the cover and spawn the program to edit it if selected""" save_format = self.name_combo.get_active_text() # Allow use of patterns in creating cover filenames pattern = ArbitraryExtensionFileFromPattern( save_format.decode("utf-8")) filename = pattern.format(self.song) print_d("Using '%s' as filename based on %s" % (filename, save_format)) file_path = os.path.join(self.dirname, filename) msg = (_('The file <b>%s</b> already exists.\n\nOverwrite?') % util.escape(filename)) if (os.path.exists(file_path) and not qltk.ConfirmAction(None, _('File exists'), msg).run()): return try: f = open(file_path, 'wb') f.write(self.current_data) f.close() except IOError: qltk.ErrorMessage(None, _('Saving failed'), _('Unable to save "%s".') % file_path).run() else: if self.open_check.get_active(): try: util.spawn([self.cmd.get_text(), file_path]) except: pass app.window.emit("artwork-changed", [self.song]) self.main_win.destroy()
def __mkdir(self, button): model, paths = self.get_selection().get_selected_rows() if len(paths) != 1: return path = paths[0] directory = model[path][0] dir_ = GetStringDialog(None, _("New Folder"), _("Enter a name for the new folder:")).run() if not dir_: return dir_ = glib2fsn(dir_) fullpath = os.path.realpath(os.path.join(directory, dir_)) try: os.makedirs(fullpath) except EnvironmentError as err: error = "<b>%s</b>: %s" % (err.filename, err.strerror) qltk.ErrorMessage(None, _("Unable to create folder"), error).run() return self.emit('test-expand-row', model.get_iter(path), path) self.expand_row(path, False)
def printError(): exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) for line in lines: print(line) qltk.ErrorMessage(None, _('Error occured'), _('%s.') % lines).run()
def check_wrapper_changed(library, parent, songs): needs_write = filter(lambda s: s._needs_write, songs) if needs_write: win = WritingWindow(parent, len(needs_write)) win.show() for song in needs_write: try: song._song.write() except Exception: qltk.ErrorMessage( None, _("Unable to edit song"), _("Saving <b>%s</b> failed. The file " "may be read-only, corrupted, or you " "do not have permission to edit it.") % util.escape(song('~basename'))).run() if win.step(): break win.destroy() changed = [] for song in songs: if song._was_updated(): changed.append(song._song) elif not song.valid() and song.exists(): library.reload(song._song) library.changed(changed)
def __save(self, *data): """Save the cover and spawn the program to edit it if selected""" save_format = self.name_combo.get_active_text() # Allow use of patterns in creating cover filenames pattern = ArbitraryExtensionFileFromPattern(save_format) filename = pattern.format(self.song) print_d("Using '%s' as filename based on %s" % (filename, save_format)) file_path = os.path.join(self.dirname, filename) if os.path.exists(file_path): resp = ConfirmFileReplace(self, file_path).run() if resp != ConfirmFileReplace.RESPONSE_REPLACE: return try: f = open(file_path, 'wb') f.write(self.current_data) f.close() except IOError: qltk.ErrorMessage(None, _('Saving failed'), _('Unable to save "%s".') % file_path).run() else: if self.open_check.get_active(): try: util.spawn([self.cmd.get_text(), file_path]) except: pass app.cover_manager.cover_changed([self.song._song]) self.main_win.destroy()
def __save_files(self, parent, model, library): win = WritingWindow(parent, len(model)) was_changed = set() for song, track in [(r[0], r[2]) for r in model]: if song.get("tracknumber") == track: win.step() continue if not song.valid() and not qltk.ConfirmAction( win, _("Tag may not be accurate"), _("<b>%s</b> changed while the program was running. " "Saving without refreshing your library may " "overwrite other changes to the song.\n\n" "Save this song anyway?") % util.escape(fsdecode(song("~basename")))).run(): break song["tracknumber"] = track try: song.write() except: util.print_exc() qltk.ErrorMessage( win, _("Unable to save song"), _("Saving <b>%s</b> failed. The file may be " "read-only, corrupted, or you do not have " "permission to edit it.") % util.escape(fsdecode(song('~basename')))).run() library.reload(song, changed=was_changed) break was_changed.add(song) if win.step(): break library.changed(was_changed) win.destroy()
def __drag_data_get(self, view, ctx, sel, tid, etime): model, paths = self.get_selection().get_selected_rows() if tid == DND_QL: songs = [model[path][0] for path in paths if model[path][0].can_add] if len(songs) != len(paths): qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to copy songs"), _("The files selected cannot be copied to other " "song lists or the queue.")).run() Gdk.drag_abort(ctx, etime) return qltk.selection_set_songs(sel, songs) # DEM 2018/05/25: The below check is a deliberate repetition of # code in the drag-motion signal handler. In MacOS/Quartz, the # context action is not propogated between event handlers for # drag-motion and drag-data-get using "ctx.get_actions()". It is # unclear if this is a bug or expected behavior. Regardless, the # context widget information is the same so identical behavior can # be achieved by simply using the same widget check as in the move # action. if Gtk.drag_get_source_widget(ctx) == self and \ not self.__force_copy: self.__drag_iters = list(map(model.get_iter, paths)) else: self.__drag_iters = [] else: uris = [model[path][0]("~uri") for path in paths] sel.set_uris(uris) self.__drag_iters = []
def _show_sync_error(self, title, message): """ Show an error message whenever a synchronization error occurs. :param title: The title of the message popup. :param message: The error message. """ qltk.ErrorMessage(self.main_vbox, title, message).run() print_e(title)
def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = list(filter(None, [library.get(f) for f in filenames])) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = XSPFBackedPlaylist.from_songs(PLAYLISTS, songs, library) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) # Cause a refresh to the dragged-to playlist if it is selected # so that the dragged (duplicate) track(s) appears if playlist is self.__get_name_of_current_selected_playlist(): model, plist_iter = self.__selected_playlists() songlist = qltk.get_top_parent(self).songlist self.activate(resort=not songlist.is_sorted()) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = _name_for(name or os.path.basename(uri)) try: sock = urlopen(uri) if uri.lower().endswith('.pls'): playlist = parse_pls(sock, name, library=library) elif (uri.lower().endswith('.m3u') or uri.lower().endswith('.m3u8')): playlist = parse_m3u(sock, name, library=library) else: raise IOError library.add(playlist.songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U/M3U8 " "and PLS formats.")).run()
def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = listfilter(None, [library.get(f) for f in filenames]) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = FileBackedPlaylist.from_songs(PLAYLISTS, songs, library) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = name or os.path.basename(uri) or _("New Playlist") uri = uri.encode('utf-8') try: sock = urlopen(uri) f = NamedTemporaryFile() f.write(sock.read()) f.flush() if uri.lower().endswith('.pls'): playlist = parse_pls(f.name, library=library) elif uri.lower().endswith('.m3u'): playlist = parse_m3u(f.name, library=library) else: raise IOError library.add_filename(playlist) if name: playlist.rename(name) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U " "and PLS formats.")).run()
def __save(self, addreplace, library): pattern_text = self.combo.get_child().get_text().decode('utf-8') pattern = TagsFromPattern(pattern_text) model = self.view.get_model() add = bool(addreplace.get_active()) win = WritingWindow(self, len(model)) win.show() was_changed = set() for row in (model or []): song = row[0] changed = False if not song.valid() and not qltk.ConfirmAction( self, _("Tag may not be accurate"), _("<b>%s</b> changed while the program was running. " "Saving without refreshing your library may " "overwrite other changes to the song.\n\n" "Save this song anyway?") % ( util.escape(fsdecode(song("~basename")))) ).run(): break for i, h in enumerate(pattern.headers): if row[i + 2]: text = row[i + 2].decode("utf-8") if not add or h not in song or not song.multiple_values: song[h] = text changed = True else: for val in text.split("\n"): if val not in song.list(h): song.add(h, val) changed = True if changed: try: song.write() except: qltk.ErrorMessage( self, _("Unable to edit song"), _("Saving <b>%s</b> failed. The file " "may be read-only, corrupted, or you " "do not have permission to edit it.") % ( util.escape(fsdecode(song('~basename')))) ).run() library.reload(song, changed=was_changed) break was_changed.add(song) if win.step(): break win.destroy() library.changed(was_changed) self.save.set_sensitive(False)
def __eject(self, button): model, iter = self.__view.get_selection().get_selected() if iter: device = model[iter][0] status = device.eject() if status is not True: msg = _("Ejecting %s failed.") % util.bold(device['name']) if status: msg += "\n\n%s" % status qltk.ErrorMessage(self, _("Unable to eject device"), msg).run()
def enabled(self): print_d("Debug is set to %s" % self._debug) self.active = True self.init_server() self.server.pause() if not self.server.is_connected: qltk.ErrorMessage( None, _("Error finding Squeezebox server"), _("Error finding %s. Please check settings") % self.server.config).run()
def __check_markup(self, apply): try: f = AudioFile({"~filename": "dummy"}) Pango.parse_markup(XMLFromPattern(self.text) % f, -1, u"\u0000") except (ValueError, GLib.GError), e: qltk.ErrorMessage( self, _("Invalid pattern"), _("The pattern you entered was invalid. Make sure you enter " "< and > as \\< and \\> and that your tags are " "balanced.\n\n%s") % util.escape(str(e))).run() apply.stop_emission('clicked')
def __rmdir(self, button): model, paths = self.get_selection().get_selected_rows() if len(paths) != 1: return directory = model[paths[0]][0] try: os.rmdir(directory) except EnvironmentError, err: error = "<b>%s</b>: %s" % (err.filename, err.strerror) qltk.ErrorMessage(None, _("Unable to delete folder"), error).run() return
def __check_markup(self, apply): try: validate_markup_pattern(self.text, self._alternative_markup, self._links) except ValueError as e: qltk.ErrorMessage( self, _("Invalid pattern"), _("The pattern you entered was invalid. Make sure you enter " "< and > as \\< and \\> and that your tags are " "balanced.\n\n%s") % util.escape(str(e))).run() apply.stop_emission('clicked') return False
def _rename(self, path, newname): playlist = self._lists[path][0] try: playlist.rename(newname) except ValueError as s: qltk.ErrorMessage(None, _("Unable to rename playlist"), s).run() else: row = self._lists[path] child_model = self.model child_model.remove(self._lists.convert_iter_to_child_iter( row.iter)) child_model.append(row=[playlist]) self._select_playlist(playlist, scroll=True)
def __add_new_tag(self, model, tag, value): if (tag in self.__songinfo and not self.__songinfo.multiple_values): title = _("Unable to add tag") msg = _( "Unable to add <b>%s</b>\n\nThe files currently" " selected do not support multiple values.") % util.escape(tag) qltk.ErrorMessage(self, title, msg).run() return iters = [row.iter for row in model if row[TAG] == tag] row = [tag, util.escape(value), True, True, False, None, False, None] if len(iters): model.insert_after(iters[-1], row=row) else: model.append(row=row)
def plugin_playlist(self, playlist): self.init_server() if not self.server.is_connected: qltk.ErrorMessage( None, _("Error finding Squeezebox server"), _("Error finding %s. Please check settings") % self.server.config ).run() else: name = self.__get_playlist_name(name=playlist.name) if name: task = Task("Squeezebox", _("Export to Squeezebox playlist"), stop=self.__cancel_add) copool.add(self.__add_songs, task, playlist.songs, name, funcid="squeezebox-playlist-save")
def __edit_tag_name(self, renderer, path, new_tag, model): new_tag = ' '.join(new_tag.splitlines()).lower() path = Gtk.TreePath.new_from_string(path) entry = model[path][0] if new_tag == entry.tag: return elif not self.__songinfo.can_change(new_tag): # Can't add the new tag. title = _("Invalid tag") msg = _("Invalid tag <b>%s</b>\n\nThe files currently" " selected do not support editing this tag." ) % util.escape(new_tag) qltk.ErrorMessage(self, title, msg).run() else: # FIXME: In case this is a special one we only # validate one value and never write it back.. text = entry.value.text if not massagers.is_valid(new_tag, text): qltk.WarningMessage( self, _("Invalid value"), _("Invalid value: <b>%(value)s</b>\n\n%(error)s") % { "value": text, "error": massagers.error_message(new_tag, text) }).run() return text = massagers.validate(new_tag, text) if entry.origvalue is None: # The tag hasn't been saved yet, so we can just update # the name in the model, and the value, since it # may have been re-validated. entry.tag = new_tag entry.value = Comment(text) else: # The tag has been saved, so delete the old tag and # add a new one with the old (or sanitized) value. entry.renamed = entry.edited = True entry.origtag = entry.tag entry.tag = new_tag if not entry.value.is_special(): entry.value = Comment(text) entry.canedit = True model.row_changed(path, model.get_iter(path))
def __upload(self, song): filename = song["~filename"] basename = song("~basename") dirname = os.path.basename(os.path.dirname(filename)) target = os.path.join(dirname, basename) # Avoid spurious calls to ifp mkdir; this can take a long time # on a noisy USB line. if dirname not in self.__madedir: os.system("ifp mkdir %r> /dev/null 2>/dev/null" % dirname) self.__madedir.append(dirname) if os.system("ifp upload %r %r > /dev/null" % (filename, target)): qltk.ErrorMessage( None, _("Error uploading"), _("Unable to upload <b>%s</b>. The device may be " "out of space, or turned off.") % ( util.escape(filename))).run() return True
def __rmdir(self, button): model, paths = self.get_selection().get_selected_rows() if len(paths) != 1: return directory = model[paths[0]][0] try: os.rmdir(directory) except EnvironmentError as err: error = "<b>%s</b>: %s" % (err.filename, err.strerror) qltk.ErrorMessage(None, _("Unable to delete folder"), error).run() return ppath = Gtk.TreePath(paths[0][:-1]) expanded = self.row_expanded(ppath) self.emit('test-expand-row', model.get_iter(ppath), ppath) if expanded: self.expand_row(ppath, False)
def __add_station(self, uri): irfs = add_station(uri) if not irfs: qltk.ErrorMessage( None, _("No stations found"), _("No Internet radio stations were found at %s.") % util.escape(uri)).run() return irfs = filter(lambda station: station not in self.__fav_stations, irfs) if not irfs: qltk.WarningMessage( None, _("Unable to add station"), _("All stations listed are already in your library.")).run() if irfs: self.__fav_stations.add(irfs)
def __preview(self, songs): model = self.view.get_model() if songs is None: songs = [e.song for e in model.itervalues()] pattern_text = self.combo.get_child().get_text().decode("utf-8") try: pattern = FileFromPattern(pattern_text) except ValueError: qltk.ErrorMessage( self, _("Path is not absolute"), _("The pattern\n\t<b>%s</b>\ncontains / but " "does not start from root. To avoid misnamed " "folders, root your pattern by starting " "it with / or ~/.") % ( util.escape(pattern))).run() return else: if pattern: self.combo.prepend_text(pattern_text) self.combo.write(NBP) # native paths orignames = [song["~filename"] for song in songs] newnames = [pattern.format(song) for song in songs] for f in self.filter_box.filters: if f.active: newnames = f.filter_list(orignames, newnames) model.clear() for song, newname in zip(songs, newnames): entry = Entry(song) entry.new_name = fsdecode(newname) model.append(row=[entry]) self.preview.set_sensitive(False) self.save.set_sensitive(bool(pattern_text)) for song in songs: if not song.is_file: self.set_sensitive(False) break else: self.set_sensitive(True)
def __import(self, activator, library): filt = lambda fn: fn.endswith(".pls") or fn.endswith(".m3u") from quodlibet.qltk.chooser import FileChooser chooser = FileChooser(self, _("Import Playlist"), filt, get_home_dir()) files = chooser.run() chooser.destroy() for filename in files: if filename.endswith(".m3u"): playlist = parse_m3u(filename, library=library) elif filename.endswith(".pls"): playlist = parse_pls(filename, library=library) else: qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U " "and PLS formats.")).run() return self.changed(playlist) library.add(playlist)
def __rmdir(self, button): model, paths = self.get_selection().get_selected_rows() directories = [model[path][0] for path in paths] print_d("Deleting %d empty directories" % len(directories)) for directory in directories: try: os.rmdir(directory) except EnvironmentError as err: error = "<b>%s</b>: %s" % (err.filename, err.strerror) qltk.ErrorMessage(None, _("Unable to delete folder"), error).run() return ppath = Gtk.TreePath(paths[0][:-1]) expanded = self.row_expanded(ppath) self.emit('test-expand-row', model.get_iter(ppath), ppath) if expanded: self.expand_row(ppath, False)