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 plugin_on_song_started(self, song): """Called when a song is started. Loads the lyrics. If there are lyrics associated with `song`, load them into the lyrics viewer. Otherwise, hides the lyrics viewer. """ lyrics = None if song is not None: print_d("Looking for lyrics for %s" % song("~filename")) lyrics = song("~lyrics") if lyrics: self.textbuffer.set_text(lyrics) self.adjustment.set_value(0) # Scroll to the top. self.textview.show() else: title = _("No lyrics found for\n %s") % song("~basename") self._set_italicised(title) def edit(widget): print_d("Launching lyrics editor for %s" % song("~filename")) assert isinstance(song, SongWrapper) information = Information(app.librarian, [song._song]) information.get_child()._switch_to_lyrics() information.show() if self._sig: self._edit_button.disconnect(self._sig) self._sig = self._edit_button.connect('clicked', edit)
def get_players(self): """ Returns (and caches) a list of the Squeezebox players available""" if self.players: return self.players pairs = self.__request("players 0 99", True).split(" ") def demunge(string): s = urllib.unquote(string) cpos = s.index(":") return (s[0:cpos], s[cpos + 1:]) # Do a meaningful URL-unescaping and tuplification for all values pairs = map(demunge, pairs) # First element is always count count = int(pairs.pop(0)[1]) self.players = [] for pair in pairs: if pair[0] == "playerindex": playerindex = int(pair[1]) self.players.append(SqueezeboxPlayerSettings()) else: # Don't worry playerindex is always the first entry... self.players[playerindex][pair[0]] = pair[1] if self._debug: print_d("Found %d player(s): %s" % (len(self.players), self.players)) assert (count == len(self.players)) return self.players
def _load(self, item): """Load a item. Return (changed, removed).""" # Subclases should override this if they want to check # item validity; see FileLibrary. print_d("Loading %r." % item.key, self) self.dirty = True self._contents[item.key] = item
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 __build_model(klass, library, model): print_d("Updating tag model for whole library") all_tags = klass.__tags model.clear() tags = set() songs = list(library) for count, song in enumerate(songs): for tag in song.keys(): if not (tag.startswith("~#") or tag in MACHINE_TAGS): tags.add(tag) if count % 500 == 0 or count + 1 == len(songs): tags -= all_tags for tag in tags: model.append([tag]) all_tags.update(tags) tags.clear() yield True tags.update(["~dirname", "~basename", "~people", "~format"]) for tag in ["track", "disc", "playcount", "skipcount", "lastplayed", "mtime", "added", "rating", "length"]: tags.add("#(" + tag) for tag in ["date", "bpm"]: if tag in all_tags: tags.add("#(" + tag) tags -= all_tags for tag in tags: model.append([tag]) all_tags.update(tags) print_d("Done updating tag model for whole library")
def load(self, filename, skip=False): """Load a library from a file, containing a picked list. Loading does not cause added, changed, or removed signals. """ self.filename = filename print_d("Loading contents of %r." % filename, self) try: if os.path.exists(filename): # pickle makes 1000 read syscalls for 6000 songs # read the file into memory so that there are less # context switches. saves 40% here.. fileobj = file(filename, "rb") try: items = pickle.loads(fileobj.read()) except (pickle.PickleError, EnvironmentError, ImportError, EOFError): util.print_exc() try: shutil.copy(filename, filename + ".not-valid") except EnvironmentError: util.print_exc() items = [] fileobj.close() else: return except EnvironmentError: return if skip: for item in filter(skip, items): self._contents[item.key] = item else: map(self._load, items) print_d("Done loading contents of %r." % filename, self)
def __quit(self, widget=None, response=None): if response == Gtk.ResponseType.OK or \ response == Gtk.ResponseType.CLOSE: print_d("Exiting plugin on user request...") self.finished = True self.destroy() return
def __create_playlist(name, source_dir, files, library): playlist = FileBackedPlaylist.new(PLAYLISTS, name, library=library) print_d("Created playlist %s" % playlist) songs = [] win = WaitLoadWindow( None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): if not uri_is_valid(filename): # Plain filename. songs.append(_af_for(filename, library, source_dir)) else: try: filename = uri2fsn(filename) except ValueError: # Who knows! Hand it off to GStreamer. songs.append(formats.remote.RemoteFile(filename)) else: # URI-encoded local filename. songs.append(_af_for(filename, library, source_dir)) if win.step(): break win.destroy() playlist.extend(list(filter(None, songs))) return playlist
def _changed(self, items): # Called by the changed method and Librarians. if not items: return print_d("Changing %d items." % len(items), self) self.dirty = True self.emit('changed', items)
def close(self): if self._client is None: return print_d("Disconnecting from XSMP") self._client.close() self._client = None
def plugin_songs(self, songs): model = DuplicatesTreeModel() self.__cfg_cache = {} # Index all songs by our custom key # TODO: make this cache-friendly print_d("Calculating duplicates for %d song(s)..." % len(songs)) groups = {} for song in songs: key = self.get_key(song) if key and key in groups: print_d("Found duplicate based on '%s'" % key) groups[key].add(song._song) elif key: groups[key] = {song._song} for song in app.library: key = self.get_key(song) if key in groups: groups[key].add(song) # Now display the grouped duplicates for (key, children) in groups.items(): if len(children) < self.MIN_GROUP_SIZE: continue # The parent (group) label model.add_group(key, children) dialog = DuplicateDialog(model) dialog.show() # Mainly for testing... return dialog
def enabled(self): if not self.running: wm = WatchManager() self.event_handler = LibraryEvent(app.library) # Choose event types to watch for # FIXME: watch for IN_CREATE or for some reason folder copies # are missed, --nickb FLAGS = ['IN_DELETE', 'IN_CLOSE_WRITE',# 'IN_MODIFY', 'IN_MOVED_FROM', 'IN_MOVED_TO', 'IN_CREATE'] mask = reduce(lambda x, s: x | EventsCodes.ALL_FLAGS[s], FLAGS, 0) if self.USE_THREADS: print_d("Using threaded notifier") self.notifier = ThreadedNotifier(wm, self.event_handler) # Daemonize to ensure thread dies on exit self.notifier.daemon = True self.notifier.start() else: self.notifier = Notifier(wm, self.event_handler, timeout=100) GLib.timeout_add(1000, self.unthreaded_callback) for path in get_scan_dirs(): print_d('Watching directory %s for %s' % (path, FLAGS)) # See https://github.com/seb-m/pyinotify/wiki/ # Frequently-Asked-Questions wm.add_watch(path, mask, rec=True, auto_add=True) self.running = True
def next(self, playlist, iter): next = self.wrapped.next(playlist, iter) if next: return next self.wrapped.reset(playlist) print_d("Restarting songlist") return playlist.get_iter_first()
def _check_feed(self): """Validate stream a bit - failing fast where possible. Constructs an equivalent(ish) HEAD request, without re-writing feedparser completely. (it never times out if reading from a stream - see #2257)""" req = feedparser._build_urllib2_request( self.uri, feedparser.USER_AGENT, None, None, None, None, {}) req.method = "HEAD" opener = build_opener(feedparser._FeedURLHandler()) try: result = opener.open(req) ct_hdr = result.headers.get('Content-Type', "Unknown type") content_type = ct_hdr.split(';')[0] try: status = result.status except AttributeError: print_w("Missing status code for feed %s" % self.uri) else: print_d("Pre-check: %s returned %s with content type '%s'" % (self.uri, status, content_type)) if content_type not in feedparser.ACCEPT_HEADER: print_w("Unusable content: %s. Perhaps %s is not a feed?" % (content_type, self.uri)) return False # No real need to check HTTP Status - errors are very unlikely # to be a usable content type, and we should try to parse finally: opener.close() return True
def go_to(self, song, explicit=False): self.__iter = None if isinstance(song, Gtk.TreeIter): self.__iter = song self.sourced = True elif not self.find_row(song): print_d("Failed to find song") return self.__iter
def _get_saved_searches(self): filename = self.PATTERNS_FILE + ".saved" self._url_pats = StandaloneEditor.load_values(filename) # Failing all else... if not len(self._url_pats): print_d("No saved searches found in %s. Using defaults." % filename, context=self) self._url_pats = self.DEFAULT_URL_PATS
def show_files_cb(menu_item): print_d("Trying to show files...") if not show_songs(songs): msg = ErrorMessage(self.plugin_window, _("Unable to show files"), _("Error showing files, " "or no program available to show them.")) msg.run()
def __init__(self, string, star=None, clock=time.time): super(SoundcloudQuery, self).__init__(string, star) self._clock = clock try: self.terms = self._extract_terms(self._match) except self.error as e: print_d("Couldn't use query: %s" % e) self.type = QueryType.INVALID self.terms = {}
def plugin_enable(self, plugin): self.__plugins[plugin.cls] = pl_obj = plugin.get_instance() sidebar = pl_obj.create_sidebar() app.window.hide_side_book() if sidebar: print_d("Enabling sidebar for %s" % plugin.cls) self.__sidebars[plugin] = app.window.add_sidebar( sidebar, name=plugin.name) sidebar.show_all()
def _save_images(self, data, img): ext = 'jpg' if self.config.re_encode else img.extension paths = self._filenames(self.config.save_pattern, ext, full_path=True) print_d("Saving %s to %s" % (data, paths)) first_path = paths.pop() img.save_image(first_path) # Copying faster than potentially resizing for path in paths: shutil.copy(first_path, path)
def add_to_existing_group(self, key, song): """Tries to add a song to an existing group. Returns None if not able """ for parent in self: if key == parent[0]: print_d("Found group", self) return self.append(parent.iter, self.__make_row(song)) # TODO: update group return None
def remaining(self, playlist): """Gets a map of all song indices to their song from the `playlist` that haven't yet been played""" all_indices = set(range(len(playlist))) played = set(self._played) print_d("Played %d of %d song(s)" % (len(self._played), len(playlist))) remaining = list(all_indices.difference(played)) all_songs = playlist.get() return {i: all_songs[i] for i in remaining}
def __update_song(klass, library, songs, model): print_d("Updating tag model for %d songs" % len(songs)) tags = klass.__tags for song in songs: for tag in song.keys(): if not (tag.startswith("~#") or tag in MACHINE_TAGS or tag in tags): klass.__tags.add(tag) model.append([tag]) print_d("Done updating tag model for %d songs" % len(songs))
def go_to(self, song, explicit=False): #print_d("Duplicates: told to go to %r" % song, context=self) self.__iter = None if isinstance(song, Gtk.TreeIter): self.__iter = song self.sourced = True elif not self.find_row(song): print_d("Failed to find song", context=self) return self.__iter
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 write_to_song(tag, pattern, value): if value is None or value == "": return existing = song(tag, None) if existing and not self.overwrite_existing: print_d("Not overwriting existing tag %s (=%s) for %s" % (tag, existing, self.song("~filename"))) return song[tag] = pattern % value
def mask(self, point): print_d(f"Masking {point!r}", self._name) removed = {} for item in self.values(): if item.mountpoint == point: removed[item.key] = item if removed: self.remove(removed.values()) self._masked.setdefault(point, {}).update(removed)
def show_files_cb(menu_item): print_d("Trying to show files...") if not show_songs(songs): parent = get_menu_item_top_parent(menu_item) msg = ErrorMessage(parent, _("Unable to show files"), _("Error showing files, " "or no program available to show them.")) msg.run()
def __init__(self): print_d("Initialising") self._globals = { 'random': random, 'Random': random.Random, 'time': time } self._reported = set() self._raw_body = None
def unmonitor_dir(self, path: Path) -> None: """Disconnect and remove any monitor for a directory, if found""" monitor, handler_id = self._monitors.get(path, (None, None)) if not monitor: print_d(f"Couldn't find path {path} in active monitors", self._name) return monitor.disconnect(handler_id) del self._monitors[path]
def _get_saved_searches(self): filename = self.PATTERNS_FILE + ".saved" #print_d("Checking saved searches in %s..." % filename, context=self) self._url_pats = StandaloneEditor.load_values(filename) # Failing all else... if not len(self._url_pats): print_d("No saved searches found in %s. Using defaults." % filename, context=self) self._url_pats = self.DEFAULT_URL_PATS
def plugin_on_changed(self, songs): cur = app.player.info if cur: fn = cur("~filename") for s in songs: if s("~filename") == fn: print_d("Active song changed, reloading lyrics") self.plugin_on_song_started(SongWrapper(cur)) else: self._set_italicised(_("No active song"))
def _finished(p, successes, failures): msg = (f"<b>{successes}</b> " + _("successful") + f"\n<b>{failures}</b> " + _("failed")) print_d(msg.replace("\n", "; ")) warning = Message(Gtk.MessageType.INFO, app.window, _("Downloads complete"), msg, escape_desc=False) warning.run()
def __init__(self, conf): print_d("Creating Lyrics web view") super(LyricsWebView, self).__init__() self.conf = conf self._thread = LyricsWikiaSearchThread() self.connect("destroy", lambda *x: self._thread.stop()) self.current_song = None self._reload_web_view()
def __init__(self, conf): print_d("Creating Lyrics web view") super().__init__() self.conf = conf self._thread = LyricsWikiaSearchThread() self.connect("destroy", lambda *x: self._thread.stop()) self.current_song = None self._reload_web_view()
def _save_images(self, data: CoverData, img: Gtk.Image): paths = self._filenames(self.config.save_pattern, img.extension, full_path=True) first_path = paths.pop() print_d(f"Saving {data} to {first_path}") img.save_image(first_path) # Copying faster than potentially resizing for path in paths: shutil.copy(first_path, path)
def destroy(self): for id_ in self._pids: self._player.disconnect(id_) for id_ in self._lids: print_d(f"Disconnecting signal {id_} from {self._library}") self._library.disconnect(id_) try: os.unlink(self.path) except EnvironmentError: pass
def rebuild(self, paths, force=False, exclude=None, cofuncid=None): """Reload or remove songs if they have changed or been deleted. This generator rebuilds the library over the course of iteration. Any paths given will be scanned for new files, using the 'scan' method. Only items present in the library when the rebuild is started will be checked. If this function is copooled, set "cofuncid" to enable pause/stop buttons in the UI. """ print_d(f"Rebuilding, force is {force}", self._name) task = Task(_("Library"), _("Checking mount points")) if cofuncid: task.copool(cofuncid) for i, (point, items) in task.list(enumerate(self._masked.items())): if ismount(point): self._contents.update(items) del self._masked[point] self.emit('added', list(items.values())) yield True task = Task(_("Library"), _("Scanning library")) if cofuncid: task.copool(cofuncid) changed, removed = set(), set() for i, (key, item) in task.list(enumerate(sorted(self.items()))): if key in self._contents and force or not item.valid(): self.reload(item, changed, removed) # These numbers are pretty empirical. We should yield more # often than we emit signals; that way the main loop stays # interactive and doesn't get bogged down in updates. if len(changed) >= 200: self.emit('changed', changed) changed = set() if len(removed) >= 200: self.emit('removed', removed) removed = set() if len(changed) > 20 or i % 200 == 0: yield True print_d(f"Removing {len(removed)}, changing {len(changed)}).", self._name) if removed: self.emit('removed', removed) if changed: self.emit('changed', changed) for value in self.scan(paths, exclude, cofuncid): yield value
def remove(self, items): """Remove items. This causes a 'removed' signal.""" if not items: return print_d("Removing %d items." % len(items), self) for item in items: del(self._contents[item.key]) self.dirty = True self.emit('removed', items)
def __init__(self, library): self.librarian = None print_d("Initializing Album Library to watch %r" % library._name) super().__init__("AlbumLibrary for %s" % library._name) self._library = library self._asig = library.connect('added', self.__added) self._rsig = library.connect('removed', self.__removed) self._csig = library.connect('changed', self.__changed) self.__added(library, library.values(), signal=False)
def save_image(self, fsn): fsn = path2fsn(fsn) if self.config.re_encode: ret = self._pixbuf.savev(fsn, 'jpeg', ['quality'], [str(JPEG_QUALITY)]) if not ret: raise IOError("Couldn't save to %s" % fsn) else: print_d("Saving original image to %s" % fsn) with open(fsn, "wb") as f: f.write(self._original)
def link_many(elements: Iterable[Gst.Element]) -> None: """Links all elements together :raises OSError: if they can't all be linked""" last = None print_d(f"Attempting to link Gstreamer element(s): " f"{[type(e).__name__ for e in elements]}") for element in elements: if last: if not Gst.Element.link(last, element): raise OSError(f"Failed on element: {type(element).__name__}") last = element
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 __init__(self, string: str, star: Optional[Iterable[str]] = None): """Parses the query string and returns a match object. :param string: The text to parse :param star: Tags to look in, if none are specified in the query. Defaults to those specified in `STAR`. This parses the query language as well as some tagless shortcuts: "foo bar" -> &(star1,star2=foo,star1,star2=bar) "!foo" -> !star1,star2=foo "&(foo, bar)" -> &(star1,star2=foo, star1,star2=bar) "&(foo, !bar)" -> &(star1,star2=foo, !star1,star2=bar) "|(foo, bar)" -> |(star1,star2=foo, star1,star2=bar) "!&(foo, bar)" -> !&(star1,star2=foo, star1,star2=bar) "!(foo, bar)" -> !star1,star2=(foo, bar) etc... """ print_d(f"Creating query {string!r}") if star is None: star = self.STAR assert isinstance(string, str) self.star = list(star) self.string = string self.type = QueryType.VALID try: self._match = QueryParser(string, star=star).StartQuery() breakpoint() if not self._match.valid: self.type = QueryType.INVALID return except self.Error: pass if not set("#=").intersection(string): for c in config.get("browsers", "ignored_characters"): string = string.replace(c, "") parts = ["/%s/d" % re_escape(s) for s in string.split()] string = "&(" + ",".join(parts) + ")" self.string = string try: self.type = QueryType.TEXT self._match = QueryParser(string, star=star).StartQuery() return except self.Error: pass print_d("Query '%s' is invalid" % string) self.type = QueryType.INVALID self._match = False_()
def save_image(self, fsn): fsn = path2fsn(fsn) if self.config.re_encode: quality = str(self.config.jpg_quality) print_d(f"Converting image to JPEG @ {quality}%") ret = self._pixbuf.savev(fsn, "jpeg", ["quality"], [quality]) if not ret: raise IOError("Couldn't save to %s" % fsn) else: print_d("Saving original image to %s" % fsn) with open(fsn, "wb") as f: f.write(self._original)
def _delete_file(self, filename): try: os.unlink(filename) print_d("Removed lyrics file '%s'" % filename) except EnvironmentError: pass lyric_dir = os.path.dirname(filename) try: os.rmdir(lyric_dir) print_d("Removed lyrics directory '%s'" % lyric_dir) except EnvironmentError: pass
def _save_to_file(self, song, text): lyricname = song.lyric_filename try: os.makedirs(os.path.dirname(lyricname), exist_ok=True) except EnvironmentError: errorhook() try: with open(lyricname, "w") as f: f.write(text) print_d("Saved lyrics to file (%s)" % lyricname) except EnvironmentError: errorhook()
def _callback(self, message, json, data): if json is None: print_d(f"[HTTP {message.status_code}] Invalid / empty JSON. " f"Body: {message.response_body.data!r} (request: {data})") return if 'errors' in json: raise ValueError("Got HTTP %d (%s)" % (message.status_code, json['errors'])) if 'error' in json: raise ValueError("Got HTTP %d (%s)" % (message.status_code, json['error'])) return wrapped(self, json, data)
def get_next_album(self): next_album = None while not next_album: if not self._todo: print_d("No more albums to process") return None next_album = self._todo.pop(0) if not next_album.should_process: print_d("%s needs no processing" % next_album.title) self._done.append(next_album) self.__update_view_for(next_album) next_album = None return next_album