예제 #1
0
    def scan(self, paths, exclude=[], cofuncid=None):

        def need_yield(last_yield=[0]):
            current = time.time()
            if abs(current - last_yield[0]) > 0.015:
                last_yield[0] = current
                return True
            return False

        def need_added(last_added=[0]):
            current = time.time()
            if abs(current - last_added[0]) > 1.0:
                last_added[0] = current
                return True
            return False

        # first scan each path for new files
        paths_to_load = []
        for scan_path in paths:
            print_d("Scanning %r." % scan_path)
            desc = _("Scanning %s") % (fsn2text(unexpand(scan_path)))
            with Task(_("Library"), desc) as task:
                if cofuncid:
                    task.copool(cofuncid)

                for real_path in iter_paths(scan_path, exclude=exclude):
                    if need_yield():
                        task.pulse()
                        yield
                    # skip unknown file extensions
                    if not formats.filter(real_path):
                        continue
                    # already loaded
                    if self.contains_filename(real_path):
                        continue
                    paths_to_load.append(real_path)

        yield

        # then (try to) load all new files
        with Task(_("Library"), _("Loading files")) as task:
            if cofuncid:
                task.copool(cofuncid)

            added = []
            for real_path in task.gen(paths_to_load):
                item = self.add_filename(real_path, False)
                if item is not None:
                    added.append(item)
                    if len(added) > 100 or need_added():
                        self.add(added)
                        added = []
                        yield
                if added and need_yield():
                    yield
            if added:
                self.add(added)
                added = []
                yield True
예제 #2
0
    def rebuild(self,
                paths,
                force=False,
                exclude=[],
                scan_dots=False,
                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("Rebuilding, force is %s." % force, self)

        task = Task(_("Library"), _("Checking mount points"))
        if cofuncid:
            task.copool(cofuncid)
        for i, (point, items) in task.list(enumerate(self._masked.items())):
            if os.path.ismount(point):
                self._contents.update(items)
                del (self._masked[point])
                self.emit('added', 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) > 100:
                self.emit('changed', changed)
                changed = set()
            if len(removed) > 100:
                self.emit('removed', removed)
                removed = set()
            if len(changed) > 5 or i % 100 == 0:
                yield True
        print_d("Removing %d, changing %d." % (len(removed), len(changed)),
                self)
        if removed:
            self.emit('removed', removed)
        if changed:
            self.emit('changed', changed)

        for value in self.scan(paths, exclude, scan_dots, cofuncid):
            yield value
예제 #3
0
    def __set_inhibit_play(self, inhibit):
        """Change the inhibit state"""

        if inhibit == self._inhibit_play:
            return
        self._inhibit_play = inhibit

        # task management
        if inhibit:
            if not self._task:
                def stop_buf(*args):
                    self._player.paused = True

                self._task = Task(_("Stream"), _("Buffering"), stop=stop_buf)
        elif self._task:
            self._task.finish()
            self._task = None

        # state management
        if inhibit:
            # save the current state
            status, state, pending = self.bin.get_state(
                timeout=STATE_CHANGE_TIMEOUT)
            if status == Gst.StateChangeReturn.SUCCESS and \
                    state == Gst.State.PLAYING:
                self._wanted_state = state
            else:
                # no idea, at least don't play
                self._wanted_state = Gst.State.PAUSED

            self.bin.set_state(Gst.State.PAUSED)
        else:
            # restore the old state
            self.bin.set_state(self._wanted_state)
            self._wanted_state = None
예제 #4
0
    def move_root(self,
                  old_root: str,
                  new_root: fsnative,
                  write_files: bool = True) -> 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

        """
        # TODO: only move primary library
        old_path = Path(normalize_path(old_root, canonicalise=True)).expanduser()
        new_path = Path(normalize_path(new_root)).expanduser()
        if not old_path.is_dir():
            print_w(f"Source dir {str(old_path)!r} doesn't exist, assuming that's OK",
                    self._name)
        if not new_path.is_dir():
            raise ValueError(f"Destination {new_path!r} is not a directory")
        print_d(f"Checking entire library for {str(old_path)!r}", self._name)
        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}", self._name)
                    # We need to update ~filename and ~mountpoint
                    song.sanitize()
                    if write_files:
                        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}", self._name)
                if not i % 100:
                    yield
            self.changed(changed)
            if missing:
                print_w(f"Couldn't find {len(list(missing))} files: {missing}",
                        self._name)
        yield
        self.save()
        print_d(f"Done moving {len(changed)} track(s) (of {total}) "
                f"to {str(new_path)!r}.", self._name)
예제 #5
0
파일: file.py 프로젝트: Vistaus/quodlibet
 def remove_roots(self,
                  old_roots: Iterable[str]) -> Generator[None, None, None]:
     """Remove library roots (scandirs) entirely, and all their songs"""
     old_paths = [
         Path(normalize_path(root, canonicalise=True)).expanduser()
         for root in old_roots
     ]
     total = len(self)
     removed = set()
     print_d(
         f"Removing library roots {old_roots} from {self._name} library")
     yield
     with Task(_("Library"), _("Removing library files")) as task:
         for i, song in enumerate(list(self.values())):
             task.update(i / total)
             key = normalize_path(song.key)
             song_path = Path(key)
             if any(path in song_path.parents for path in old_paths):
                 removed.add(song)
             if not i % 100:
                 yield
     if removed:
         self.remove(removed)
     else:
         print_d(f"No tracks in {old_roots} to remove from {self._name}")
예제 #6
0
    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")
예제 #7
0
 def test_multiple_tasks(self):
     self.assertEquals(self.c.active_tasks, [])
     self.assertNotEqual(self.c.source, "")
     t1 = Task("src", "desc", controller=self.c)
     self.assertEquals(self.c.source, "src")
     self.assertEquals(self.c.active_tasks, [t1])
     t1.update(0.5)
     self.assertEquals(self.c.frac, 0.5)
     t2 = Task("src2", "desc2", controller=self.c)
     self.assertEquals(self.c.source, _("Active tasks"))
     self.assertEquals(self.c.frac, 0.25)
     Task("src3", "desc3", controller=self.c, known_length=False)
     self.assertAlmostEqual(self.c.frac, 0.5 / 3)
     t1.finish()
     t2.finish()
     self.assertEquals(self.c.desc, "desc3")
     self.assertEquals(self.c.frac, None)
예제 #8
0
파일: refresh.py 프로젝트: ch1huizong/scode
 def check_songs():
     with Task(_("Refresh songs"), _("%d songs") % len(songs)) as task:
         task.copool(check_songs)
         for i, song in enumerate(songs):
             song = song._song
             if song in app.library:
                 app.library.reload(song)
             task.update((float(i) + 1) / len(songs))
             yield
예제 #9
0
 def check_songs():
     desc = numeric_phrase("%d song", "%d songs", len(songs))
     with Task(_("Rescan songs"), desc) as task:
         task.copool(check_songs)
         for i, song in enumerate(songs):
             song = song._song
             if song in app.library:
                 app.library.reload(song)
             task.update((float(i) + 1) / len(songs))
             yield
예제 #10
0
    def scan(self, paths, exclude=[], cofuncid=None):
        added = []
        exclude = [expanduser(path) for path in exclude if path]

        def need_yield(last_yield=[0]):
            current = time.time()
            if abs(current - last_yield[0]) > 0.015:
                last_yield[0] = current
                return True
            return False

        def need_added(last_added=[0]):
            current = time.time()
            if abs(current - last_added[0]) > 1.0:
                last_added[0] = current
                return True
            return False

        for fullpath in paths:
            print_d("Scanning %r." % fullpath, self)
            desc = _("Scanning %s") % (unexpand(fsdecode(fullpath)))
            with Task(_("Library"), desc) as task:
                if cofuncid:
                    task.copool(cofuncid)
                fullpath = expanduser(fullpath)
                if filter(fullpath.startswith, exclude):
                    continue
                for path, dnames, fnames in os.walk(fullpath):
                    for filename in fnames:
                        fullfilename = os.path.join(path, filename)
                        if filter(fullfilename.startswith, exclude):
                            continue
                        if fullfilename not in self._contents:
                            fullfilename = os.path.realpath(fullfilename)
                            # skip unknown file extensions
                            if not formats.filter(fullfilename):
                                continue
                            if filter(fullfilename.startswith, exclude):
                                continue
                            if fullfilename not in self._contents:
                                item = self.add_filename(fullfilename, False)
                                if item is not None:
                                    added.append(item)
                                    if len(added) > 100 or need_added():
                                        self.add(added)
                                        added = []
                                        task.pulse()
                                        yield
                                if added and need_yield():
                                    yield
                if added:
                    self.add(added)
                    added = []
                    task.pulse()
                    yield True
예제 #11
0
파일: library.py 프로젝트: WammKD/quodlibet
 def _get_stream_urls(self, songs):
     # Pre-cache. It's horrible, but at least you can play things immediately
     with Task(_("Soundcloud"), "Pre-fetching stream URLs") as task:
         total = len(songs)
         for i, song in enumerate(songs):
             # Only update ones without streaming URLs
             # TODO: But yes these will time out...
             if "~uri" not in song or "api.soundcloud.com" in song["~uri"]:
                 self.client.get_stream_url(song)
             task.update(i / total)
             yield
예제 #12
0
    def plugin_playlist(self, playlist):
        pattern_text = CONFIG.default_pattern
        dialog = ExportToFolderDialog(self.plugin_window, pattern_text)
        if dialog.run() == Gtk.ResponseType.OK:
            directory = dialog.directory_chooser.get_filename()
            pattern = FileFromPattern(dialog.pattern_entry.get_text())

            task = Task("Export", _("Export Playlist to Folder"),
                        stop=self.__cancel_copy)
            copool.add(self.__copy_songs, task,
                       playlist.songs, directory, pattern,
                       funcid="export-playlist-folder")

        dialog.destroy()
예제 #13
0
def download_taglist(callback, cofuncid, step=1024 * 10):
    """Generator for loading the bz2 compressed tag list.

    Calls callback with the decompressed data or None in case of
    an error."""

    with Task(_("Internet Radio"), _("Downloading station list")) as task:
        if cofuncid:
            task.copool(cofuncid)

        try:
            response = urlopen(STATION_LIST_URL)
        except (EnvironmentError, HTTPException) as e:
            print_e("Failed fetching from %s" % STATION_LIST_URL, e)
            GLib.idle_add(callback, None)
            return
        try:
            size = int(response.info().get("content-length", 0))
        except ValueError:
            size = 0

        decomp = bz2.BZ2Decompressor()

        data = b""
        temp = b""
        read = 0
        while temp or not data:
            read += len(temp)

            if size:
                task.update(float(read) / size)
            else:
                task.pulse()
            yield True

            try:
                data += decomp.decompress(temp)
                temp = response.read(step)
            except (IOError, EOFError):
                data = None
                break
        response.close()

        yield True

        stations = None
        if data:
            stations = parse_taglist(data)

        GLib.idle_add(callback, stations)
예제 #14
0
 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")
예제 #15
0
    def _resume_after_delay(delay, refresh_rate=20):
        if delay <= 0:
            return
        app.player.paused = True
        delay_timer = GLib.timeout_add(1000 * delay, app.player.play)
        task = Task(_("Shuffle by Grouping"),
                    _("Waiting to start new group…"),
                    stop=lambda: GLib.source_remove(delay_timer))

        def countdown():
            for i in range(int(refresh_rate * delay)):
                task.update(i / (refresh_rate * delay))
                yield True
            task.finish()
            yield False
        GLib.timeout_add(1000 / refresh_rate, next, countdown())
예제 #16
0
def _get_stations_from(uri: str,
                       on_done: Callable[[Iterable[IRFile], str], None])\
        -> None:
    """Fetches the URI content and extracts IRFiles
       Called from thread - so no direct GTK+ interaction
       :param uri: URI of station
       :param on_done: a callback taking files when done (or none if errored)
       """

    with Task(_("Internet Radio"), _("Add stations")) as task:
        irfs: Collection[IRFile] = []
        GLib.idle_add(task.pulse)
        if (uri.lower().endswith(".pls")
                or uri.lower().endswith(".m3u")
                or uri.lower().endswith(".m3u8")):
            if not re.match('^([^/:]+)://', uri):
                # Assume HTTP if no protocol given. See #2731
                uri = 'http://' + uri
                print_d("Assuming http: %s" % uri)

            # Error handling outside
            sock = None
            GLib.idle_add(task.pulse)
            _fn, ext = splitext(uri.lower())
            try:
                sock = urlopen(uri, timeout=6)
                if ext == ".pls":
                    irfs = parse_pls(sock)
                elif ext in (".m3u", ".m3u8"):
                    irfs = parse_m3u(sock)
                GLib.idle_add(task.pulse)
            except IOError as e:
                print_e(f"Couldn't download from {uri} ({e})")
            finally:
                if sock:
                    sock.close()
        else:
            try:
                irfs = [IRFile(uri)]
            except ValueError as e:
                print_e("Can't add URI %s" % uri, e)
    on_done(irfs, uri)
예제 #17
0
 def watching_producer():
     # TODO: integrate this better with scanning.
     for fullpath in paths:
         desc = _("Adding watches for %s") % (fsn2text(unexpand(fullpath)))
         with Task(_("Library"), desc) as task:
             normalised = Path(normalize_path(fullpath, True)).expanduser()
             if any(Path(exclude) in normalised.parents
                    for exclude in exclude_dirs):
                 continue
             unpulsed = 0
             self.monitor_dir(normalised)
             for path, dirs, files in os.walk(normalised):
                 normalised = Path(normalize_path(path, True))
                 for d in dirs:
                     self.monitor_dir(normalised / d)
                 unpulsed += len(dirs)
                 if unpulsed > 50:
                     task.pulse()
                     unpulsed = 0
                 yield
예제 #18
0
파일: library.py 프로젝트: faubi/quodlibet
def emit_signal(songs, signal="changed", block_size=50, name=None,
                cofuncid=None):
    """
    A generator that signals `signal` on the library
    in blocks of `block_size`. Useful for copools.
    """
    i = 0
    with Task(_("Library"), name or signal) as task:
        if cofuncid:
            task.copool(cofuncid)
        total = len(songs)
        while i < total:
            more = songs[i:i + block_size]
            if not more:
                return
            if 0 == ((i / block_size) % 10):
                print_d("Signalling '%s' (%d/%d songs)"
                        % (signal, i, total))
            task.update(float(i) / total)
            app.library.emit(signal, more)
            i += block_size
            yield
예제 #19
0
 def scan(self, paths, exclude=[], cofuncid=None):
     added = []
     exclude = [expanduser(path) for path in exclude if path]
     for fullpath in paths:
         print_d("Scanning %r." % fullpath, self)
         desc = _("Scanning %s") % (unexpand(fsdecode(fullpath)))
         with Task(_("Library"), desc) as task:
             if cofuncid:
                 task.copool(cofuncid)
             fullpath = expanduser(fullpath)
             if filter(fullpath.startswith, exclude):
                 continue
             for path, dnames, fnames in os.walk(util.fsnative(fullpath)):
                 for filename in fnames:
                     fullfilename = os.path.join(path, filename)
                     if filter(fullfilename.startswith, exclude):
                         continue
                     if fullfilename not in self._contents:
                         fullfilename = os.path.realpath(fullfilename)
                         # skip unknown file extensions
                         if not formats.filter(fullfilename):
                             continue
                         if filter(fullfilename.startswith, exclude):
                             continue
                         if fullfilename not in self._contents:
                             item = self.add_filename(fullfilename, False)
                             if item is not None:
                                 added.append(item)
                                 if len(added) > 20:
                                     self.add(added)
                                     added = []
                                     task.pulse()
                                     yield True
                 if added:
                     self.add(added)
                     added = []
                     task.pulse()
                     yield True
예제 #20
0
 def __init__(self, songs: Collection[AudioFile], task=None) -> None:
     super().__init__()
     self.songs = songs
     self.successful: Set[AudioFile] = set()
     self.failed: Set[AudioFile] = set()
     self.task = task or Task(_("Browser"), _("Downloading files"))
예제 #21
0
    def search_cover(self, cancellable, songs):
        """Search for all the covers applicable to `songs` across all providers
        Every successful image result emits a 'covers-found' signal
        (unless cancelled)."""

        sources = [source for source in self.sources if not source.embedded]
        processed = {}
        all_groups = {}
        task = Task(_("Cover Art"),
                    _("Querying album art providers"),
                    stop=cancellable.cancel)

        def finished(provider, success):
            processed[provider] = success
            total = self._total_groupings(all_groups)

            frac = len(processed) / total
            print_d("%s is finished: %d / %d" %
                    (provider, len(processed), total))
            task.update(frac)
            if frac >= 1:
                task.finish()
                self.emit('searches-complete', processed)

        def search_complete(provider, results):
            name = provider.name
            if not results:
                print_d('No covers from {0}'.format(name))
                finished(provider, False)
                return
            finished(provider, True)
            if not (cancellable and cancellable.is_cancelled()):
                covers = {
                    CoverData(url=res['cover'],
                              source=name,
                              dimensions=res.get('dimensions', None))
                    for res in results
                }
                self.emit('covers-found', provider, covers)
            provider.disconnect_by_func(search_complete)

        def failure(provider, result):
            finished(provider, False)
            name = provider.__class__.__name__
            print_d('Failed to get cover from {name} ({msg})'.format(
                name=name, msg=result))
            provider.disconnect_by_func(failure)

        def song_groups(songs, sources):
            all_groups = {}
            for plugin in sources:
                groups = {}
                for song in songs:
                    group = plugin.group_by(song) or ''
                    groups.setdefault(group, []).append(song)
                all_groups[plugin] = groups
            return all_groups

        all_groups = song_groups(songs, sources)
        print_d("Got %d plugin groupings" % self._total_groupings(all_groups))

        for plugin, groups in all_groups.items():
            print_d("Getting covers from %s" % plugin)
            for key, group in sorted(groups.items()):
                song = sorted(group, key=lambda s: s.key)[0]
                artists = {s.comma('artist') for s in group}
                if len(artists) > 1:
                    print_d("%d artist groups in %s - probably a compilation. "
                            "Using provider to search for compilation" %
                            (len(artists), key))
                    song = AudioFile(song)
                    try:
                        del song['artist']
                    except KeyError:
                        # Artist(s) from other grouped songs, never mind.
                        pass
                provider = plugin(song)
                provider.connect('search-complete', search_complete)
                provider.connect('fetch-failure', failure)
                provider.search()
        return all_groups