def _downloaded(self, msg: Soup.Message, result: Any, data: Tuple) -> None: path, song = data try: headers = msg.get_property('response-headers') size = int(headers.get('content-length')) content_type = headers.get('content-type') print_d( f"Downloaded {format_size(size)} of {content_type}: {song('title')}" ) _, ext = splitext(urlparse(song("~uri")).path) fn = (escape_filename(song("~artist~title")[:100], safe=b" ,';") or song("~basename") or f"download-{hash(song('~filename'))}") path = path / Path(fn + ext) if path.is_file() and path.stat(): print_w(f"{path!s} already exists. Skipping download") self.success(song) return with open(path, "wb") as f: f.write(result) self.success(song) print_d(f"Downloaded to {path} successfully!") except Exception as e: print_e(f"Failed download ({e})") self.failure(song)
def _finished(self, manager, results): if not any(results.values()): print_w("Nothing found from %d provider(s)" % len(self._groups)) self.button.set_sensitive(False) if not self.headless: self._quit(results)
def plugin_songs(self, songs): # Check this is a launch, not a configure if self.chosen_site: url_pat = self.get_url_pattern(self.chosen_site) pat = Pattern(url_pat) urls = set() for song in songs: # Generate a sanitised AudioFile; allow through most tags subs = AudioFile() for k in (USER_TAGS + MACHINE_TAGS): vals = song.comma(k) if vals: try: subs[k] = quote_plus(unicode(vals).encode('utf-8')) # Dodgy unicode problems except KeyError: print_d("Problem with %s tag values: %r" % (k, vals)) url = str(pat.format(subs)) if not url: print_w("Couldn't build URL using \"%s\"." "Check your pattern?" % url_pat) return # Grr, set.add() should return boolean... if url not in urls: urls.add(url) website(url)
def parse_body(self, body, query_path_=None): if body is None: raise QueryPluginError body = body.strip().lower() # Use provided query file for testing if query_path_: query_path = query_path_ else: query_path = os.path.join(get_user_dir(), 'lists', 'queries.saved') try: with open(query_path, 'r', encoding="utf-8") as query_file: for query_string in query_file: name = next(query_file).strip().lower() if name == body: try: return Query(query_string.strip()) except QueryError: raise QueryPluginError # We've searched the whole file and haven't found a match print_w(f"None found for {body}") raise QueryPluginError except IOError: raise QueryPluginError except StopIteration: # The file has an odd number of lines. This shouldn't happen unless # it has been externally modified raise QueryPluginError
def _on_bus_message(self, bus, message): if message.type == Gst.MessageType.ERROR: error, debug = message.parse_error() print_d("Error received from element {name}: {error}".format( name=message.src.get_name(), error=error)) print_d("Debugging information: {}".format(debug)) elif message.type == Gst.MessageType.ELEMENT: structure = message.get_structure() if structure.get_name() == "level": rms_db = structure.get_value("rms") if rms_db: # Calculate average of all channels (usually 2) rms_db_avg = sum(rms_db) / len(rms_db) # Normalize dB value to value between 0 and 1 rms = pow(10, (rms_db_avg / 20)) self._new_rms_vals.append(rms) else: print_w("Got unexpected message of type {}" .format(message.type)) elif message.type == Gst.MessageType.EOS: self._clean_pipeline() # Update the waveform with the new data self._rms_vals = self._new_rms_vals self._waveform_scale.reset(self._rms_vals) self._waveform_scale.set_placeholder(False) self._update_redraw_interval() # Clear temporary reference to the waveform data del self._new_rms_vals
def __request(self, line, raw=False, want_reply=True): """ Send a request to the server, if connected, and return its response """ line = line.strip() if not (self.is_connected or line.split()[0] == 'login'): print_d("Can't do '%s' - not connected" % line.split()[0], self) return None if self._debug: print_(">>>> \"%s\"" % line) try: self.telnet.write(line + "\n") if not want_reply: return None raw_response = self.telnet.read_until("\n").strip() except socket.error as e: print_w("Couldn't communicate with squeezebox (%s)" % e) self.failures += 1 if self.failures >= self._MAX_FAILURES: print_w("Too many Squeezebox failures. Disconnecting") self.is_connected = False return None response = raw_response if raw else urllib.unquote(raw_response) if self._debug: print_("<<<< \"%s\"" % (response,)) return response[len(line) - 1:] if line.endswith("?")\ else response[len(line) + 1:]
def __request(self, line, raw=False, want_reply=True): """ Send a request to the server, if connected, and return its response """ line = line.strip() if not (self.is_connected or line.split()[0] == 'login'): print_d("Can't do '%s' - not connected" % line.split()[0], self) return None if self._debug: print_(">>>> \"%s\"" % line) try: self.telnet.write(line + "\n") if not want_reply: return None raw_response = self.telnet.read_until("\n").strip() except socket.error as e: print_w("Couldn't communicate with squeezebox (%s)" % e) self.failures += 1 if self.failures >= self._MAX_FAILURES: print_w("Too many Squeezebox failures. Disconnecting") self.is_connected = False return None response = raw_response if raw else urllib.unquote(raw_response) if self._debug: print_("<<<< \"%s\"" % (response, )) return response[len(line) - 1:] if line.endswith("?")\ else response[len(line) + 1:]
def _do_trash_songs(parent, songs, librarian): dialog = TrashDialog.for_songs(parent, songs) resp = dialog.run() if resp != TrashDialog.RESPONSE_TRASH: return window_title = _("Moving %(current)d/%(total)d.") w = WaitLoadWindow(parent, len(songs), window_title) w.show() ok = [] failed = [] for song in songs: filename = song("~filename") try: trash.trash(filename) except trash.TrashError as e: print_w("Couldn't trash file (%s)" % e) failed.append(song) else: ok.append(song) w.step() w.destroy() if failed: ErrorMessage(parent, _("Unable to move to trash"), _("Moving one or more files to the trash failed.")).run() if ok: librarian.remove(ok)
def _do_trash_songs(parent, songs, librarian): dialog = TrashDialog.for_songs(parent, songs) resp = dialog.run() if resp != TrashDialog.RESPONSE_TRASH: return window_title = _("Moving %(current)d/%(total)d.") w = WaitLoadWindow(parent, len(songs), window_title) w.show() ok = [] failed = [] for song in songs: filename = song("~filename") try: trash.trash(filename) except trash.TrashError as e: print_w("Couldn't trash file (%s)" % e) failed.append(song) else: ok.append(song) w.step() w.destroy() if failed: ErrorMessage(parent, _("Unable to move to trash"), _("Moving one or more files to the trash failed.") ).run() if ok: librarian.remove(ok)
def cfg_get(cls, name, default=None): try: key = __name__ + "_" + name return config.get("plugins", key) except (ValueError, ConfigParser.Error): print_w("Config entry '%s' not found. Using '%s'" % (key, default,)) return default
def _finished(self, manager, results): self.__cancellable.cancel() if not any(results.values()): print_w(f"Nothing found from {len(self._groups)} provider(s)") self.button.set_sensitive(False) if not self.headless: self._quit(results)
def slider_changed(_slider): new_size = slider.get_value() try: for child in self.flow_box.get_children(): img = self.__image_from_child(child) img.resize(new_size) except AttributeError as e: print_w(f"Couldn't set picture size(s) ({e})")
def disabled(self): if not self._pid: return print_d("Shutting down %s" % self.PLUGIN_NAME) try: os.kill(self._pid, signal.SIGTERM) os.kill(self._pid, signal.SIGKILL) except Exception as e: print_w("Couldn't shut down cleanly (%s)" % e)
def runner(self, cache): changed = True try: changed = cache.update_charts(self.progress) except Exception as e: print_w(f"Couldn't update cache ({e})") if changed: self.cache_shelf[cache.username] = cache self.cache_shelf.close()
def parse_body(self, body): if body is None: raise QueryPluginError self._raw_body = body.strip() self._reported.clear() try: self._globals.update(_ts=time.time()) return compile(body.strip(), 'query', 'eval') except SyntaxError as e: print_w("Couldn't compile query (%s)" % e) raise QueryPluginError
def move_root(self, old_root: str, new_root: fsnative) \ -> Generator[None, None, None]: """ Move the root for all songs in a given (scan) directory. We avoid dereferencing the destination, to allow users things like: 1. Symlink new_path -> old_root 2. Move QL root to new_path 3. Remove symlink 4. Move audio files: old_root -> new_path """ old_path = Path(normalize_path(old_root, canonicalise=True)).expanduser() new_path = Path(normalize_path(new_root)).expanduser() if not old_path.is_dir(): raise ValueError(f"Source {old_path!r} is not a directory") if not new_path.is_dir(): raise ValueError(f"Destination {new_path!r} is not a directory") print_d(f"{self._name}: checking entire library for {old_path!r}") missing: Set[AudioFile] = set() changed = set() total = len(self) if not total: return with Task(_("Library"), _("Moving library files")) as task: yield for i, song in enumerate(list(self.values())): task.update(i / total) key = normalize_path(song.key) path = Path(key) if old_path in path.parents: # TODO: more Pathlib-friendly dir replacement... new_key = key.replace(str(old_path), str(new_path), 1) new_key = normalize_path(new_key, canonicalise=False) if new_key == key: print_w(f"Substitution failed for {key!r}") # We need to update ~filename and ~mountpoint song.sanitize() song.write() if self.move_song(song, new_key): changed.add(song) else: missing.add(song) elif not (i % 1000): print_d(f"Not moved, for example: {key!r}") if not i % 100: yield self.changed(changed) if missing: print_w(f"Couldn't find {len(list(missing))} files: {missing}") yield self.save() print_d(f"Done moving to {new_path!r}.")
def _set_bookmarks(self, model, a, b, library, song): def stringify(s): return s.decode('utf-8') if isinstance(s, bytes) else s try: song.bookmarks = [(t, stringify(l)) for t, l in model] except (AttributeError, ValueError) as e: print_w("Couldn't save bookmark for %s (%s)" % (song("~filename"), e)) else: if library is not None: library.changed([song])
def _save_lyrics(self, song, text): # First, try writing to the tags. song["lyrics"] = text try: song.write() except AudioFileError as e: print_w("Couldn't write embedded lyrics (%s)" % e) self._save_to_file(song, text) else: print_d("Wrote embedded lyrics into %s" % song("~filename")) app.librarian.emit('changed', [song]) self._delete_file(song.lyric_filename)
def __select_song(self, player, path, col): if len(path) == 1: if self.row_expanded(path): self.collapse_row(path) else: self.expand_row(path, False) else: songs = self.get_selected_songs() if songs and player.go_to(songs[0], True): player.paused = False else: print_w("Sorry, can't play song outside current list.")
def destroy() -> None: """Destroy all registered libraries """ print_d("Destroying all libraries...") librarian = SongFileLibrary.librarian if librarian: for lib in list(librarian.libraries.values()): try: lib.destroy() except Exception as e: print_w(f"Couldn't destroy {lib} ({e!r})") librarian.destroy()
def _save_to_file(self, song, text): lyric_fn = song.lyric_filename if not lyric_fn: print_w("No lyrics file to save to, ignoring.") return try: os.makedirs(os.path.dirname(lyric_fn), exist_ok=True) except EnvironmentError: errorhook() try: with open(lyric_fn, "wb") as f: f.write(text.encode("utf-8")) print_d(f"Saved lyrics to file {lyric_fn!r}") except EnvironmentError: errorhook()
def __search_thread(self, engine, query, replace): """Creates searching threads which call the callback function after they are finished""" clean_query = self.__cleanup_query(query, replace) result = [] try: result = engine().start(clean_query, self.overall_limit) except Exception: print_w("[AlbumArt] %s: %r" % (engine.__name__, query)) print_exc() self.finished += 1 #progress is between 0..1 progress = float(self.finished) / len(self.engine_list) GLib.idle_add(self.callback, result, progress)
def conclude(self, fails, reason): from quodlibet import print_w if fails: def format_occurrences(e): occurences = [(self.lang + ".po", e.linenum)] occurences += e.occurrences return ', '.join('%s:%s' % o for o in occurences) messages = [ '"%s" - "%s" (%s)' % (e.msgid, e.msgstr, format_occurrences(e)) for e in fails ] for message in messages: print_w(message) self.fail( "One or more messages did not pass (%s).\n" "Please check the warning messages above." % reason)
def search(self, data, body): try: self._globals['s'] = data # Albums can be queried too... self._globals['a'] = data # eval modifies the globals in place, it seems ret = eval(body, dict(self._globals)) return ret except Exception as e: key = str(e) if key not in self._reported: self._reported.add(key) print_w("%s(%s) in expression '%s'. " "Example failing data: %s" % (type(e).__name__, key, self._raw_body, self._globals)) return False
def _on_set_pattern(self, button, edit, player): self._pattern = edit.text.rstrip() if self._pattern == SongInfo._pattern: try: os.unlink(self._pattern_filename) except OSError: pass else: try: with open(self._pattern_filename, "wb") as h: h.write(self._pattern.encode("utf-8") + b"\n") except EnvironmentError as e: print_w("Couldn't save display pattern '%s' (%s)" % (self._pattern, e)) self._compiled = XMLFromMarkupPattern(self._pattern) self._update_info(player)
def _load_item(self, item, force=False): """Add an item, or refresh it if it's already in the library. No signals will be fired. Return a tuple of booleans: (changed, removed) """ print_d(f"Loading {item.key!r}", self._name) valid = item.valid() # The item is fine; add it if it's not present. if not force and valid: print_d(f"{item.key!r} is valid.", self._name) self._contents[item.key] = item return False, False else: # Either we should force a load, or the item is not okay. # We're going to reload; this could change the key. So # remove the item if it's currently in. try: del self._contents[item.key] except KeyError: present = False else: present = True # If the item still exists, reload it. if item.exists(): try: item.reload() except AudioFileError: print_w(f"Error reloading {item.key!r}", self._name) return False, True else: print_d(f"Reloaded {item.key!r}.", self._name) self._contents[item.key] = item return True, False elif not item.mounted(): # We don't know if the item is okay or not, since # it's not not mounted. If the item was present # we need to mark it as removed. print_d(f"Masking {item.key!r}", self._name) self._masked.setdefault(item.mountpoint, {}) self._masked[item.mountpoint][item.key] = item return False, present else: # The item doesn't exist at all anymore. Mark it as # removed if it was present, otherwise nothing. print_d(f"Ignoring (so removing) {item.key!r}.", self._name) return False, present
def _sent(self, msg, result, data): headers = self.message.get_property('response-headers') self.size = int(headers.get('content-length')) self._content_type = headers.get('content-type') self._original = result try: loader = GdkPixbuf.PixbufLoader() except GLib.GError as e: print_w("Couldn't create GdkPixbuf (%s)" % e) else: loader.write(result) loader.close() self._pixbuf = loader.get_pixbuf() self.emit("info-known", self._content_type, self.size, self._pixbuf.props) self.resize() self.queue_draw()
def monitor_dir(self, path: Path) -> None: """Monitors a single directory""" # Only add one monitor per absolute path... if path not in self._monitors: f = Gio.File.new_for_path(str(path)) try: monitor = f.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, None) except GLib.GError as e: print_w(f"Couldn't watch {path} ({e})", self._name) monitor = None if not monitor: return handler_id = monitor.connect("changed", self.__file_changed) # Don't destroy references - http://stackoverflow.com/q/4535227 self._monitors[path] = (monitor, handler_id) print_d(f"Monitoring {path!s}", self._name)
def _save_lyrics(self, song, text): # First, try writing to the tags. if "lyrics" not in song and "unsyncedlyrics" in song: tag = "unsyncedlyrics" else: tag = "lyrics" song[tag] = text try: song.write() except AudioFileError as e: print_w(f"Couldn't write embedded lyrics ({e!r})") self._save_to_file(song, text) else: print_d(f"Wrote embedded lyrics into {song('~filename')}") app.librarian.emit('changed', [song]) fn = song.lyric_filename if fn: self._delete_file(fn)
def save(self, filename=None): """Save the library to the given filename.""" self._save_lock.acquire() if filename is None: filename = self.filename print_d("Saving contents to %r." % filename, self) if not os.path.isdir(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) # Issue 479. Catch problem early if os.path.isdir(filename): msg = _("Cannot save library contents to %s (it's a directory). " "Please remove it and try again.") % filename print_w(msg) # TODO: Better handling of this edge-case... ErrorMessage(None, _("Library Error"), msg).run() self._save_lock.release() return fileobj = file(filename + ".tmp", "wb") if fcntl is not None: fcntl.flock(fileobj.fileno(), fcntl.LOCK_EX) items = self.values() for masked in self._masked.values(): items.extend(masked.values()) # Item keys are often based on filenames, in which case # sorting takes advantage of the filesystem cache when we # reload/rescan the files. items.sort(key=lambda item: item.key) # While protocol 2 is usually faster it uses __setitem__ # for unpickle and we override it to clear the sort cache. # This roundtrip makes it much slower, so we use protocol 1 # unpickle numbers (py2.7): # 2: 0.66s / 2 + __set_item__: 1.18s / 1 + __set_item__: 0.72s # see: http://bugs.python.org/issue826897 pickle.dump(items, fileobj, 1) fileobj.flush() os.fsync(fileobj.fileno()) fileobj.close() if os.name == "nt": try: os.remove(filename) except EnvironmentError: pass os.rename(filename + ".tmp", filename) self.dirty = False print_d("Done saving contents to %r." % filename, self) self._save_lock.release()
def _write(self, _widget: Gtk.Widget, model: QueueModel, force=False): diff = time.time() - self._updated_time if not self._should_write(force, diff): self._pending += 1 return print_d(f"Saving play queue after {diff:.1f}s") filenames = [row[0]["~filename"] for row in model] try: with open(QUEUE, "wb") as f: for filename in filenames: try: line = fsn2bytes(filename, "utf-8") except ValueError as e: print_w(f"Ignoring queue save error ({e})") continue f.write(line + b"\n") except EnvironmentError as e: print_e(f"Error saving queue ({e})") self._updated_time = time.time() self._pending = 0
def close(self): if self._client_priv is None: return self._client_priv.disconnect(self._sig_id) self._sig_id = None try: bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) session_mgr = Gio.DBusProxy.new_sync( bus, Gio.DBusProxyFlags.NONE, None, self.DBUS_NAME, self.DBUS_OBJECT_PATH, self.DBUS_MAIN_INTERFACE, None) session_mgr.UnregisterClient('(o)', self._client_path) except GLib.Error as e: print_w(str(e)) print_d("Disconnected from gnome session manager: %s" % self._client_path) self._client_path = None
def __move(self, widget): selection = self.view.get_selection() model, paths = selection.get_selected_rows() rows = [model[p] for p in paths or []] if len(rows) > 1: print_w("Can't do multiple moves at once") return elif not rows: return base_dir = rows[0][0] chooser = _get_chooser(_("Select This Directory"), _("_Cancel")) chooser.set_title( _("Select Actual / New Directory for {dir!r}").format( dir=base_dir)) chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER) chooser.set_local_only(True) chooser.set_select_multiple(False) results = _run_chooser(self, chooser) if not results: return new_dir = results[0] desc = (_("This will move QL metadata:\n\n" "{old!r} -> {new!r}\n\n" "The audio files themselves are not moved by this.\n" "Nonetheless, a backup is recommended " "(including the Quodlibet 'songs' file)").format( old=base_dir, new=new_dir)) title = _("Move scan root {dir!r}?").format(dir=base_dir) value = ConfirmationPrompt(self, title=title, description=desc, ok_button_text=_("OK, move it!")).run() if value != ConfirmationPrompt.RESPONSE_INVOKE: print_d("User aborted") return print_d(f"Migrate from {base_dir} -> {new_dir}") copool.add(app.librarian.move_root, base_dir, new_dir) path = paths[0] self.model[path] = [new_dir] self.model.row_changed(path, rows[0].iter) self.__save()
def _on_bus_message(self, bus, message): if message.type == Gst.MessageType.ERROR: error, debug = message.parse_error() print_d("Error received from element {name}: {error}".format( name=message.src.get_name(), error=error)) print_d("Debugging information: {}".format(debug)) elif message.type == Gst.MessageType.ELEMENT: structure = message.get_structure() if structure.get_name() == "level": rms_db = structure.get_value("rms") # Calculate average of all channels (usually 2) rms_db_avg = sum(rms_db) / len(rms_db) # Normalize dB value to value between 0 and 1 rms = pow(10, (rms_db_avg / 20)) self._rms_vals.append(rms) else: print_w("Got unexpected message of type {}" .format(message.type)) elif message.type == Gst.MessageType.EOS: self._pipeline.set_state(Gst.State.NULL) if self._player.info: self._waveform_scale.reset(self._rms_vals, self._player) self._waveform_scale.set_placeholder(False)
def __init__(self, parent, song): super(CoverArea, self).__init__() self.song = song self.dirname = song("~dirname") self.main_win = parent self.data_cache = [] self.current_data = None self.current_pixbuf = None self.image = Gtk.Image() self.button = Button(_("_Save"), Icons.DOCUMENT_SAVE) self.button.set_sensitive(False) self.button.connect('clicked', self.__save) close_button = Button(_("_Close"), Icons.WINDOW_CLOSE) close_button.connect('clicked', lambda x: self.main_win.destroy()) self.window_fit = self.ConfigCheckButton(_('Fit image to _window'), 'fit', True) self.window_fit.connect('toggled', self.__scale_pixbuf) self.name_combo = Gtk.ComboBoxText() self.cmd = qltk.entry.ValidatingEntry(iscommand) # Both labels label_open = Gtk.Label(label=_('_Program:')) label_open.set_use_underline(True) label_open.set_mnemonic_widget(self.cmd) label_open.set_justify(Gtk.Justification.LEFT) self.open_check = self.ConfigCheckButton(_('_Edit image after saving'), 'edit', False) label_name = Gtk.Label(label=_('File_name:'), use_underline=True) label_name.set_use_underline(True) label_name.set_mnemonic_widget(self.name_combo) label_name.set_justify(Gtk.Justification.LEFT) self.cmd.set_text(self.config_get('edit_cmd', 'gimp')) # Create the filename combo box fn_list = ['cover.jpg', 'folder.jpg', '.folder.jpg'] # Issue 374 - add dynamic file names artist = song("artist") alartist = song("albumartist") album = song("album") labelid = song("labelid") if album: fn_list.append("<album>.jpg") if alartist: fn_list.append("<albumartist> - <album>.jpg") else: fn_list.append("<artist> - <album>.jpg") else: print_w(u"No album for \"%s\". Could be difficult " u"finding art…" % song("~filename")) title = song("title") if title and artist: fn_list.append("<artist> - <title>.jpg") if labelid: fn_list.append("<labelid>.jpg") set_fn = self.config_get('fn', fn_list[0]) for i, fn in enumerate(fn_list): self.name_combo.append_text(fn) if fn == set_fn: self.name_combo.set_active(i) if self.name_combo.get_active() < 0: self.name_combo.set_active(0) table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False) table.set_row_spacing(0, 5) table.set_row_spacing(1, 5) table.set_col_spacing(0, 5) table.set_col_spacing(1, 5) table.attach(label_open, 0, 1, 0, 1) table.attach(label_name, 0, 1, 1, 2) table.attach(self.cmd, 1, 2, 0, 1) table.attach(self.name_combo, 1, 2, 1, 2) self.scrolled = Gtk.ScrolledWindow() self.scrolled.add_with_viewport(self.image) self.scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) bbox = Gtk.HButtonBox() bbox.set_spacing(6) bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.button, True, True, 0) bbox.pack_start(close_button, True, True, 0) bb_align = Align(valign=Gtk.Align.END, right=6) bb_align.add(bbox) main_hbox = Gtk.HBox() main_hbox.pack_start(table, False, True, 6) main_hbox.pack_start(bb_align, True, True, 0) top_hbox = Gtk.HBox() top_hbox.pack_start(self.open_check, True, True, 0) top_hbox.pack_start(self.window_fit, False, True, 0) main_vbox = Gtk.VBox() main_vbox.pack_start(top_hbox, True, True, 2) main_vbox.pack_start(main_hbox, True, True, 0) self.pack_start(self.scrolled, True, True, 0) self.pack_start(main_vbox, False, True, 5) # 5 MB image cache size self.max_cache_size = 1024 * 1024 * 5 # For managing fast selection switches of covers.. self.stop_loading = False self.loading = False self.current_job = 0 self.connect('destroy', self.__save_config)
def __set_async(self, url): """Manages various things: Fast switching of covers (aborting old HTTP requests), The image cache, etc.""" self.current_job += 1 job = self.current_job self.stop_loading = True while self.loading: time.sleep(0.05) self.stop_loading = False if job != self.current_job: return self.loading = True GLib.idle_add(self.button.set_sensitive, False) self.current_pixbuf = None pbloader = GdkPixbuf.PixbufLoader() pbloader.connect('closed', self.__close) # Look for cached images raw_data = None for entry in self.data_cache: if entry[0] == url: raw_data = entry[1] break if not raw_data: pbloader.connect('area-updated', self.__update) data_store = StringIO() try: request = urllib2.Request(url) request.add_header('User-Agent', USER_AGENT) url_sock = urllib2.urlopen(request) except urllib2.HTTPError: print_w(_("[albumart] HTTP Error: %s") % url) else: while not self.stop_loading: tmp = url_sock.read(1024 * 10) if not tmp: break pbloader.write(tmp) data_store.write(tmp) url_sock.close() if not self.stop_loading: raw_data = data_store.getvalue() self.data_cache.insert(0, (url, raw_data)) while 1: cache_sizes = [len(data[1]) for data in self.data_cache] if sum(cache_sizes) > self.max_cache_size: del self.data_cache[-1] else: break data_store.close() else: # Sleep for fast switching of cached images time.sleep(0.05) if not self.stop_loading: pbloader.write(raw_data) try: pbloader.close() except GLib.GError: pass self.current_data = raw_data if not self.stop_loading: GLib.idle_add(self.button.set_sensitive, True) self.loading = False
def check_unused(self): if self.unused: from quodlibet import print_w print_w('ListWithUnused has unused items: %s' % self.unused)