Beispiel #1
0
class TDirectoryTree(TestCase):

    if os.name == "nt":
        ROOTS = [get_home_dir(), u"C:\\"]
    else:
        ROOTS = [get_home_dir(), "/"]

    def setUp(self):
        quodlibet.config.init()

    def tearDown(self):
        quodlibet.config.quit()

    def test_initial(self):
        paths = ["/", get_home_dir(), "/usr/bin"]
        if os.name == "nt":
            paths = [u"C:\\", get_home_dir()]

        for path in paths:
            dirlist = DirectoryTree(path, folders=self.ROOTS)
            model, rows = dirlist.get_selection().get_selected_rows()
            selected = [model[row][0] for row in rows]
            dirlist.destroy()
            self.failUnlessEqual([path], selected)

    def test_bad_initial(self):
        invalid = os.path.join("bin", "file", "does", "not", "exist")
        for path in self.ROOTS:
            newpath = os.path.join(path, invalid)
            dirlist = DirectoryTree(newpath, folders=self.ROOTS)
            selected = dirlist.get_selected_paths()
            dirlist.destroy()
            # select the last valid parent directory
            self.assertEqual(len(selected), 1)
            self.assertTrue(selected[0].startswith(path))

    def test_bad_go_to(self):
        newpath = fsnative(u"/woooooo/bar/fun/broken")
        dirlist = DirectoryTree(fsnative(u"/"), folders=self.ROOTS)
        dirlist.go_to(newpath)
        dirlist.destroy()

    def test_main(self):
        folders = ["/"]
        if os.name == "nt":
            folders = [u"C:\\"]
        main = MainDirectoryTree(folders=folders)
        self.assertTrue(len(main.get_model()))

        main = MainDirectoryTree()
        self.assertTrue(len(main.get_model()))

    def test_get_drives(self):
        for path in get_drives():
            self.assertTrue(is_fsnative(path))
Beispiel #2
0
    def test_initial(self):
        paths = ["/", get_home_dir(), "/usr/bin"]
        if os.name == "nt":
            paths = [u"C:\\", get_home_dir()]

        for path in paths:
            dirlist = DirectoryTree(path, folders=self.ROOTS)
            model, rows = dirlist.get_selection().get_selected_rows()
            selected = [model[row][0] for row in rows]
            dirlist.destroy()
            self.failUnlessEqual([path], selected)
    def test_initial(self):
        paths = ["/", get_home_dir(), "/usr/bin"]
        if os.name == "nt":
            paths = [u"C:\\", get_home_dir()]

        for path in paths:
            dirlist = DirectoryTree(path, folders=self.ROOTS)
            model, rows = dirlist.get_selection().get_selected_rows()
            selected = [model[row][0] for row in rows]
            dirlist.destroy()
            self.failUnlessEqual([path], selected)
    def test_initial(self):
        if os.name == "nt":
            paths = [u"C:\\", get_home_dir()]
        else:
            paths = ["/", get_home_dir(), sys.prefix]

        for path in paths:
            dirlist = DirectoryTree(path, folders=self.ROOTS)
            model, rows = dirlist.get_selection().get_selected_rows()
            selected = [model[row][0] for row in rows]
            dirlist.destroy()
            self.failUnlessEqual([os.path.normpath(path)], selected)
Beispiel #5
0
def get_init_select_dir():
    scandirs = get_scan_dirs()
    if scandirs and os.path.isdir(scandirs[-1]):
        # start with last added directory
        return scandirs[-1]
    else:
        return get_home_dir()
Beispiel #6
0
    def __get_themes(self):
        # deprecated, but there is no public replacement
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            theme_dir = Gtk.rc_get_theme_dir()

        theme_dirs = [theme_dir, os.path.join(get_home_dir(), ".themes")]

        themes = set()
        for theme_dir in theme_dirs:
            try:
                subdirs = os.listdir(theme_dir)
            except OSError:
                continue
            for dir_ in subdirs:
                gtk_dir = os.path.join(theme_dir, dir_, "gtk-3.0")
                if os.path.isdir(gtk_dir):
                    themes.add(dir_)

        try:
            resource_themes = Gio.resources_enumerate_children(
                "/org/gtk/libgtk/theme", 0)
        except GLib.GError:
            pass
        else:
            themes.update([t.rstrip("/") for t in resource_themes])

        return themes
Beispiel #7
0
    def __get_themes(self):
        # deprecated, but there is no public replacement
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            theme_dir = Gtk.rc_get_theme_dir()

        theme_dirs = [theme_dir, os.path.join(get_home_dir(), ".themes")]

        themes = set()
        for theme_dir in theme_dirs:
            try:
                subdirs = os.listdir(theme_dir)
            except OSError:
                continue
            for dir_ in subdirs:
                gtk_dir = os.path.join(theme_dir, dir_, "gtk-3.0")
                if os.path.isdir(gtk_dir):
                    themes.add(dir_)

        try:
            resource_themes = Gio.resources_enumerate_children(
                "/org/gtk/libgtk/theme", 0)
        except GLib.GError:
            pass
        else:
            themes.update([t.rstrip("/") for t in resource_themes])

        return themes
Beispiel #8
0
    def test_get_exclude_dirs(self):
        some_path = os.path.join(get_home_dir(), "foo")
        if os.name != "nt":
            some_path = unexpand(some_path)
        config.set('library', 'exclude', some_path)
        assert expanduser(some_path) in get_exclude_dirs()

        assert all([isinstance(p, fsnative) for p in get_exclude_dirs()])
Beispiel #9
0
    def test_get_scan_dirs(self):
        some_path = os.path.join(get_home_dir(), "foo")
        if os.name != "nt":
            some_path = unexpand(some_path)
        config.set('settings', 'scan', some_path)
        assert expanduser(some_path) in get_scan_dirs()

        assert all([isinstance(p, fsnative) for p in get_scan_dirs()])
Beispiel #10
0
    def open_chooser(self, action):
        last_dir = self.last_dir
        if not os.path.exists(last_dir):
            last_dir = get_home_dir()

        class MusicFolderChooser(FolderChooser):
            def __init__(self, parent, init_dir):
                super(MusicFolderChooser,
                      self).__init__(parent, _("Add Music"), init_dir)

                cb = Gtk.CheckButton(_("Watch this folder for new songs"))
                # enable if no folders are being watched
                cb.set_active(not get_scan_dirs())
                cb.show()
                self.set_extra_widget(cb)

            def run(self):
                fns = super(MusicFolderChooser, self).run()
                cb = self.get_extra_widget()
                return fns, cb.get_active()

        class MusicFileChooser(FileChooser):
            def __init__(self, parent, init_dir):
                super(MusicFileChooser,
                      self).__init__(parent, _("Add Music"), formats.filter,
                                     init_dir)

        if action.get_name() == "AddFolders":
            dialog = MusicFolderChooser(self, last_dir)
            fns, do_watch = dialog.run()
            dialog.destroy()
            if fns:
                fns = map(glib2fsnative, fns)
                # scan them
                self.last_dir = fns[0]
                copool.add(self.__library.scan,
                           fns,
                           cofuncid="library",
                           funcid="library")

                # add them as library scan directory
                if do_watch:
                    dirs = get_scan_dirs()
                    for fn in fns:
                        if fn not in dirs:
                            dirs.append(fn)
                    set_scan_dirs(dirs)
        else:
            dialog = MusicFileChooser(self, last_dir)
            fns = dialog.run()
            dialog.destroy()
            if fns:
                fns = map(glib2fsnative, fns)
                self.last_dir = os.path.dirname(fns[0])
                for filename in map(os.path.realpath, fns):
                    self.__library.add_filename(filename)
    def open_chooser(self, action):
        last_dir = self.last_dir
        if not os.path.exists(last_dir):
            last_dir = get_home_dir()

        class MusicFolderChooser(FolderChooser):
            def __init__(self, parent, init_dir):
                super(MusicFolderChooser, self).__init__(
                    parent, _("Add Music"), init_dir)

                cb = Gtk.CheckButton(_("Watch this folder for new songs"))
                # enable if no folders are being watched
                cb.set_active(not get_scan_dirs())
                cb.show()
                self.set_extra_widget(cb)

            def run(self):
                fns = super(MusicFolderChooser, self).run()
                cb = self.get_extra_widget()
                return fns, cb.get_active()

        class MusicFileChooser(FileChooser):
            def __init__(self, parent, init_dir):
                super(MusicFileChooser, self).__init__(
                    parent, _("Add Music"), formats.filter, init_dir)

        if action.get_name() == "AddFolders":
            dialog = MusicFolderChooser(self, last_dir)
            fns, do_watch = dialog.run()
            dialog.destroy()
            if fns:
                fns = map(glib2fsnative, fns)
                # scan them
                self.last_dir = fns[0]
                copool.add(self.__library.scan, fns, cofuncid="library",
                           funcid="library")

                # add them as library scan directory
                if do_watch:
                    dirs = get_scan_dirs()
                    for fn in fns:
                        if fn not in dirs:
                            dirs.append(fn)
                    set_scan_dirs(dirs)
        else:
            dialog = MusicFileChooser(self, last_dir)
            fns = dialog.run()
            dialog.destroy()
            if fns:
                fns = map(glib2fsnative, fns)
                self.last_dir = os.path.dirname(fns[0])
                for filename in map(os.path.realpath, fns):
                    self.__library.add_filename(filename)
Beispiel #12
0
def get_init_select_dir():
    """Returns a path which might be a good starting point when browsing
    for a path for library scanning.

    Returns:
        fsnative
    """

    scandirs = get_scan_dirs()
    if scandirs and os.path.isdir(scandirs[-1]):
        # start with last added directory
        return scandirs[-1]
    else:
        return get_home_dir()
Beispiel #13
0
def get_init_select_dir():
    """Returns a path which might be a good starting point when browsing
    for a path for library scanning.

    Returns:
        fsnative
    """

    scandirs = get_scan_dirs()
    if scandirs and os.path.isdir(scandirs[-1]):
        # start with last added directory
        return scandirs[-1]
    else:
        return get_home_dir()
Beispiel #14
0
    def __get_themes(self):
        # deprecated, but there is no public replacement
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            theme_dir = Gtk.rc_get_theme_dir()

        theme_dirs = [theme_dir, os.path.join(get_home_dir(), ".themes")]

        themes = set()
        for theme_dir in theme_dirs:
            try:
                subdirs = os.listdir(theme_dir)
            except OSError:
                continue
            for dir_ in subdirs:
                gtk_dir = os.path.join(theme_dir, dir_, "gtk-3.0")
                if os.path.isdir(gtk_dir):
                    themes.add(dir_)
        return themes
Beispiel #15
0
    def __get_themes(self):
        # deprecated, but there is no public replacement
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            theme_dir = Gtk.rc_get_theme_dir()

        theme_dirs = [theme_dir, os.path.join(get_home_dir(), ".themes")]

        themes = set()
        for theme_dir in theme_dirs:
            try:
                subdirs = os.listdir(theme_dir)
            except OSError:
                continue
            for dir_ in subdirs:
                gtk_dir = os.path.join(theme_dir, dir_, "gtk-3.0")
                if os.path.isdir(gtk_dir):
                    themes.add(dir_)
        return themes
Beispiel #16
0
def get_favorites():
    """A list of paths of commonly used folders (Desktop,..)

    Paths don't have to exist.
    """

    if os.name == "nt":
        return _get_win_favorites()
    else:
        paths = [get_home_dir()]

        xfg_user_dirs = xdg_get_user_dirs()
        for key in ["XDG_DESKTOP_DIR", "XDG_DOWNLOAD_DIR", "XDG_MUSIC_DIR"]:
            if key in xfg_user_dirs:
                path = xfg_user_dirs[key]
                if path not in paths:
                    paths.append(path)

        return paths
Beispiel #17
0
 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)
Beispiel #18
0
def get_favorites():
    """A list of paths of commonly used folders (Desktop,..)

    Paths don't have to exist.
    """

    if os.name == "nt":
        return _get_win_favorites()
    else:
        paths = [get_home_dir()]

        xfg_user_dirs = xdg_get_user_dirs()
        for key in ["XDG_DESKTOP_DIR", "XDG_DOWNLOAD_DIR", "XDG_MUSIC_DIR"]:
            if key in xfg_user_dirs:
                path = xfg_user_dirs[key]
                if path not in paths:
                    paths.append(path)

        return paths
Beispiel #19
0
def get_gtk_bookmarks():
    """A list of paths from the GTK+ bookmarks.
    The paths don't have to exist.

    Returns:
        List[fsnative]
    """

    if os.name == "nt":
        return []

    path = os.path.join(get_home_dir(), ".gtk-bookmarks")
    folders = []
    try:
        with open(path, "rb") as f:
            folders = parse_gtk_bookmarks(f.read())
    except (EnvironmentError, ValueError):
        pass

    return folders
 def test_lyric_filename_search_order_priority(self):
     """test custom lyrics order priority"""
     with self.lyric_filename_test_setup() as ts:
         root2 = os.path.join(get_home_dir(), ".lyrics")  # built-in default
         fp2 = os.path.join(root2,
                            ts["artist"] + " - " + ts["title"] + ".lyric")
         p2 = os.path.dirname(fp2)
         mkdir(p2)
         with io.open(fp2, "w", encoding='utf-8') as f:
             f.write(u"")
         fp = os.path.join(ts.root,
                           ts["artist"] + " - " + ts["title"] + ".lyric")
         with io.open(fp, "w", encoding='utf-8') as f:
             f.write(u"")
         mkdir(p2)
         search = ts.lyric_filename
         os.remove(fp2)
         os.rmdir(p2)
         os.remove(fp)
         self.assertEqual(search, fp)
 def test_lyric_filename_search_order_priority(self):
     """test custom lyrics order priority"""
     with self.lyric_filename_test_setup() as ts:
         root2 = os.path.join(get_home_dir(), ".lyrics") # built-in default
         fp2 = os.path.join(root2, ts["artist"] + " - " +
                                   ts["title"] + ".lyric")
         p2 = os.path.dirname(fp2)
         mkdir(p2)
         with io.open(fp2, "w", encoding='utf-8') as f:
             f.write(u"")
         fp = os.path.join(ts.root, ts["artist"] + " - " +
                                    ts["title"] + ".lyric")
         with io.open(fp, "w", encoding='utf-8') as f:
             f.write(u"")
         mkdir(p2)
         search = ts.lyric_filename
         os.remove(fp2)
         os.rmdir(p2)
         os.remove(fp)
         self.assertEqual(search, fp)
Beispiel #22
0
def get_gtk_bookmarks():
    """A list of paths from the GTK+ bookmarks.
    The paths don't have to exist.

    Returns:
        List[fsnative]
    """

    if os.name == "nt":
        return []

    path = os.path.join(get_home_dir(), ".gtk-bookmarks")
    folders = []
    try:
        with open(path, "rb") as f:
            folders = parse_gtk_bookmarks(f.read())
    except (EnvironmentError, ValueError):
        pass

    return folders
Beispiel #23
0
 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
         PlaylistsBrowser.changed(playlist)
         library.add(playlist)
Beispiel #24
0
    def __get_themes(self):
        # deprecated, but there is no public replacement
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            theme_dir = Gtk.rc_get_theme_dir()

        theme_dirs = [theme_dir, os.path.join(get_home_dir(), ".themes")]
        theme_dirs += [
            os.path.join(d, "themes") for d in xdg_get_system_data_dirs()
        ]

        def is_valid_teme_dir(path):
            """If the path contains a theme for the running gtk version"""

            major = qltk.gtk_version[0]
            minor = qltk.gtk_version[1]
            names = ["gtk-%d.%d" % (major, m) for m in range(minor, -1, -1)]
            for name in names:
                if os.path.isdir(os.path.join(path, name)):
                    return True
            return False

        themes = set()
        for theme_dir in set(theme_dirs):
            try:
                subdirs = os.listdir(theme_dir)
            except OSError:
                continue
            for dir_ in subdirs:
                if is_valid_teme_dir(os.path.join(theme_dir, dir_)):
                    themes.add(dir_)

        try:
            resource_themes = Gio.resources_enumerate_children(
                "/org/gtk/libgtk/theme", 0)
        except GLib.GError:
            pass
        else:
            themes.update([t.rstrip("/") for t in resource_themes])

        return themes
Beispiel #25
0
def get_current_dir():
    """Returns the currently active chooser directory path.
    The path might not actually exist.

    Returns:
        fsnative
    """

    data = config.getbytes("memory", "chooser_dir", b"")
    try:
        path = bytes2fsn(data, "utf-8") or None
    except ValueError:
        path = None

    # the last user dir might not be there any more, try showing a parent
    # instead
    if path is not None:
        path = find_nearest_dir(path)

    if path is None:
        path = get_home_dir()

    return path
Beispiel #26
0
def get_gtk_bookmarks():
    """A list of paths from the GTK+ bookmarks.

    The paths don't have to exist.
    """

    if os.name == "nt":
        return []

    path = os.path.join(get_home_dir(), ".gtk-bookmarks")
    folders = []
    try:
        with open(path, "rb") as f:
            for line in f.readlines():
                parts = line.split()
                if not parts:
                    continue
                folder_url = parts[0]
                folders.append(urlparse.urlsplit(folder_url)[2])
    except EnvironmentError:
        pass

    return folders
Beispiel #27
0
def get_gtk_bookmarks():
    """A list of paths from the GTK+ bookmarks.

    The paths don't have to exist.
    """

    if os.name == "nt":
        return []

    path = os.path.join(get_home_dir(), ".gtk-bookmarks")
    folders = []
    try:
        with open(path, "rb") as f:
            for line in f.readlines():
                parts = line.split()
                if not parts:
                    continue
                folder_url = parts[0]
                folders.append(urlsplit(folder_url)[2])
    except EnvironmentError:
        pass

    return folders
Beispiel #28
0
 def test_get_home_dir(self):
     self.assertTrue(isinstance(get_home_dir(), fsnative))
     self.assertTrue(os.path.isabs(get_home_dir()))
    def test_get_exclude_dirs(self):
        some_path = os.path.join(unexpand(get_home_dir()), "foo")
        config.set('library', 'exclude', some_path)
        assert expanduser(some_path) in get_exclude_dirs()

        assert all([isinstance(p, fsnative) for p in get_exclude_dirs()])
Beispiel #30
0
    def lyric_filename(self):
        """Returns the validated, or default, lyrics filename for this
        file. User defined '[memory] lyric_rootpaths' and
        '[memory] lyric_filenames' matches take precedence"""

        from quodlibet.pattern \
            import ArbitraryExtensionFileFromPattern as expand_patterns

        rx_params = re.compile(r'[^\\]<[^' + re.escape(os.sep) + r']*[^\\]>')

        def expand_pathfile(rpf):
            """Return the expanded RootPathFile"""
            expanded = []
            root = expanduser(rpf.root)
            pathfile = expanduser(rpf.pathfile)
            if rx_params.search(pathfile):
                root = expand_patterns(root).format(self)
                pathfile = expand_patterns(pathfile).format(self)
            rpf = RootPathFile(root, pathfile)
            expanded.append(rpf)
            if not os.path.exists(pathfile) and is_windows():
                # prioritise a special character encoded version
                #
                # most 'alien' chars are supported for 'nix fs paths, and we
                # only pass the proposed path through 'escape_filename' (which
                # apparently doesn't respect case) if we don't care about case!
                #
                # FIX: assumes 'nix build used on a case-sensitive fs, nt case
                # insensitive. clearly this is not biting anyone though (yet!)
                pathfile = os.path.sep.join([rpf.root, rpf.end_escaped])
                rpf = RootPathFile(rpf.root, pathfile)
                expanded.insert(len(expanded) - 1, rpf)
            return expanded

        def sanitise(sep, parts):
            """Return a santisied version of a path's parts"""
            return sep.join(part.replace(os.path.sep, u'')[:128]
                                for part in parts)

        # setup defaults (user-defined take precedence)
        # root search paths
        lyric_paths = \
            config.getstringlist("memory", "lyric_rootpaths", [])
        # ensure default paths
        lyric_paths.append(os.path.join(get_home_dir(), ".lyrics"))
        lyric_paths.append(
            os.path.join(os.path.dirname(self.comma('~filename'))))
        # search pathfile names
        lyric_filenames = \
            config.getstringlist("memory", "lyric_filenames", [])
        # ensure some default pathfile names
        lyric_filenames.append(
            sanitise(os.sep, [(self.comma("lyricist") or
                              self.comma("artist")),
                              self.comma("title")]) + u'.lyric')
        lyric_filenames.append(
            sanitise(' - ', [(self.comma("lyricist") or
                             self.comma("artist")),
                             self.comma("title")]) + u'.lyric')

        # generate all potential paths (unresolved/unexpanded)
        pathfiles = OrderedDict()
        for r in lyric_paths:
            for f in lyric_filenames:
                pathfile = os.path.join(r, os.path.dirname(f),
                                        fsnative(os.path.basename(f)))
                rpf = RootPathFile(r, pathfile)
                if not pathfile in pathfiles:
                    pathfiles[pathfile] = rpf

        #print_d("searching for lyrics in:\n%s" % '\n'.join(pathfiles.keys()))

        # expand each raw pathfile in turn and test for existence
        match_ = ""
        pathfiles_expanded = OrderedDict()
        for pf, rpf in pathfiles.items():
            for rpf in expand_pathfile(rpf):  # resolved as late as possible
                pathfile = rpf.pathfile
                pathfiles_expanded[pathfile] = rpf
                if os.path.exists(pathfile):
                    match_ = pathfile
                    break
            if match_ != "":
                break

        if not match_:
            # search even harder!
            lyric_extensions = ['lyric', 'lyrics', '', 'txt']
            #print_d("extending search to extensions: %s" % lyric_extensions)

            def generate_mod_ext_paths(pathfile):
                # separate pathfile's extension (if any)
                ext = os.path.splitext(pathfile)[1][1:]
                path = pathfile[:-1 * len(ext)].strip('.') if ext else pathfile
                # skip the proposed lyric extension if it is the same as
                # the original for a given search pathfile stub - it has
                # already been tested without success!
                extra_extensions = [x for x in lyric_extensions if x != ext]

                # join valid new extensions to pathfile stub and return
                return ['.'.join([path, ext]) if ext else path
                           for ext in extra_extensions]

            # look for a match by modifying the extension for each of the
            # (now fully resolved) 'pathfiles_expanded' search items
            for pathfile in pathfiles_expanded.keys():
                # get alternatives for existence testing
                paths_mod_ext = generate_mod_ext_paths(pathfile)
                for path_ext in paths_mod_ext:
                    if os.path.exists(path_ext):
                        # persistence has paid off!
                        #print_d("extended search match!")
                        match_ = path_ext
                        break
                if match_:
                    break

        if not match_:
            # default
            match_ = list(pathfiles_expanded.keys())[0]

        return match_
class TDirectoryTree(TestCase):

    if os.name == "nt":
        ROOTS = [get_home_dir(), u"C:\\"]
    else:
        ROOTS = [get_home_dir(), "/"]

    def setUp(self):
        quodlibet.config.init()

    def tearDown(self):
        quodlibet.config.quit()

    def test_initial(self):
        if os.name == "nt":
            paths = [u"C:\\", get_home_dir()]
        else:
            paths = ["/", get_home_dir(), sys.prefix]

        for path in paths:
            dirlist = DirectoryTree(path, folders=self.ROOTS)
            model, rows = dirlist.get_selection().get_selected_rows()
            selected = [model[row][0] for row in rows]
            dirlist.destroy()
            self.failUnlessEqual([os.path.normpath(path)], selected)

    def test_bad_initial(self):
        invalid = os.path.join("bin", "file", "does", "not", "exist")
        for path in self.ROOTS:
            newpath = os.path.join(path, invalid)
            dirlist = DirectoryTree(newpath, folders=self.ROOTS)
            selected = dirlist.get_selected_paths()
            dirlist.destroy()
            # select the last valid parent directory
            self.assertEqual(len(selected), 1)
            self.assertTrue(selected[0].startswith(path))

    def test_bad_go_to(self):
        newpath = fsnative(u"/woooooo/bar/fun/broken")
        dirlist = DirectoryTree(fsnative(u"/"), folders=self.ROOTS)
        dirlist.go_to(newpath)
        dirlist.destroy()

    def test_main(self):
        folders = ["/"]
        if os.name == "nt":
            folders = [u"C:\\"]
        main = MainDirectoryTree(folders=folders)
        self.assertTrue(len(main.get_model()))

        main = MainDirectoryTree()
        self.assertTrue(len(main.get_model()))

    def test_get_drives(self):
        for path in get_drives():
            self.assertTrue(isinstance(path, fsnative))

    def test_popup(self):
        dt = DirectoryTree(None, folders=self.ROOTS)
        menu = dt._create_menu()
        dt._popup_menu(menu)
        children = menu.get_children()
        self.failUnlessEqual(len(children), 4)
        delete = children[1]
        self.failUnlessEqual(delete.get_label(), __("_Delete"))
        self.failUnless(delete.get_sensitive())

    def test_multiple_selections(self):
        dt = DirectoryTree(None, folders=self.ROOTS)
        menu = dt._create_menu()
        dt._popup_menu(menu)
        children = menu.get_children()
        select_sub = children[3]
        self.failUnless("sub-folders" in select_sub.get_label().lower())
        self.failUnless(select_sub.get_sensitive())
        sel = dt.get_selection()
        model = dt.get_model()
        for it, pth in model.iterrows(None):
            sel.select_iter(it)
        self.failUnless(select_sub.get_sensitive(),
                        msg="Select All should work for multiple")
        self.failIf(children[0].get_sensitive(),
                    msg="New Folder should be disabled for multiple")
        self.failUnless(children[3].get_sensitive(),
                        msg="Refresh should be enabled for multiple")
Beispiel #32
0
    def __init__(self, library, player, headless=False, restore_cb=None):
        super(QuodLibetWindow, self).__init__(dialog=False)
        self.last_dir = get_home_dir()

        self.__destroyed = False
        self.__update_title(player)
        self.set_default_size(550, 450)

        main_box = Gtk.VBox()
        self.add(main_box)

        # create main menubar, load/restore accelerator groups
        self.__library = library
        ui = self.__create_menu(player, library)
        accel_group = ui.get_accel_group()
        self.add_accel_group(accel_group)

        def scroll_and_jump(*args):
            self.__jump_to_current(True, True)

        keyval, mod = Gtk.accelerator_parse("<control><shift>J")
        accel_group.connect(keyval, mod, 0, scroll_and_jump)

        # dbus app menu
        # Unity puts the app menu next to our menu bar. Since it only contains
        # menu items also available in the menu bar itself, don't add it.
        if not util.is_unity():
            AppMenu(self, ui.get_action_groups()[0])

        # custom accel map
        accel_fn = os.path.join(quodlibet.get_user_dir(), "accels")
        Gtk.AccelMap.load(accel_fn)
        # save right away so we fill the file with example comments of all
        # accels
        Gtk.AccelMap.save(accel_fn)

        menubar = ui.get_widget("/Menu")

        # Since https://git.gnome.org/browse/gtk+/commit/?id=b44df22895c79
        # toplevel menu items show an empty 16x16 image. While we don't
        # need image items there UIManager creates them by default.
        # Work around by removing the empty GtkImages
        for child in menubar.get_children():
            if isinstance(child, Gtk.ImageMenuItem):
                child.set_image(None)

        main_box.pack_start(menubar, False, True, 0)

        # get the playlist up before other stuff
        self.songlist = MainSongList(library, player)
        self.songlist.show_all()
        self.songlist.connect("key-press-event", self.__songlist_key_press)
        self.songlist.connect_after('drag-data-received',
                                    self.__songlist_drag_data_recv)
        self.song_scroller = SongListScroller(
            ui.get_widget("/Menu/View/SongList"))
        self.song_scroller.add(self.songlist)
        self.qexpander = QueueExpander(ui.get_widget("/Menu/View/Queue"),
                                       library, player)
        self.playlist = PlaylistMux(player, self.qexpander.model,
                                    self.songlist.model)

        top_bar = TopBar(self, player, library)
        main_box.pack_start(top_bar, False, True, 0)
        self.top_bar = top_bar

        self.__browserbox = Align(bottom=3)
        main_box.pack_start(self.__browserbox, True, True, 0)

        statusbox = StatusBarBox(self.songlist.model, player)
        self.order = statusbox.order
        self.repeat = statusbox.repeat
        self.statusbar = statusbox.statusbar

        main_box.pack_start(Align(statusbox, border=3, top=-3, right=3), False,
                            True, 0)

        self.songpane = ConfigRVPaned("memory", "queue_position", 0.75)
        self.songpane.pack1(self.song_scroller, resize=True, shrink=False)
        self.songpane.pack2(self.qexpander, resize=True, shrink=False)
        self.__handle_position = self.songpane.get_property("position")

        def songpane_button_press_cb(pane, event):
            """If we start to drag the pane handle while the
            queue expander is unexpanded, expand it and move the handle
            to the bottom, so we can 'drag' the queue out
            """

            if event.window != pane.get_handle_window():
                return False

            if not self.qexpander.get_expanded():
                self.qexpander.set_expanded(True)
                pane.set_relative(1.0)
            return False

        self.songpane.connect("button-press-event", songpane_button_press_cb)

        self.song_scroller.connect('notify::visible', self.__show_or)
        self.qexpander.connect('notify::visible', self.__show_or)
        self.qexpander.connect('notify::expanded', self.__expand_or)
        self.qexpander.connect('draw', self.__qex_size_allocate)
        self.songpane.connect('notify', self.__moved_pane_handle)

        try:
            orders = []
            for e in config.getstringlist('memory', 'sortby', []):
                orders.append((e[1:], int(e[0])))
        except ValueError:
            pass
        else:
            self.songlist.set_sort_orders(orders)

        self.browser = None
        self.ui = ui

        main_box.show_all()

        self._playback_error_dialog = None
        connect_destroy(player, 'song-started', self.__song_started)
        connect_destroy(player, 'paused', self.__update_paused, True)
        connect_destroy(player, 'unpaused', self.__update_paused, False)
        # make sure we redraw all error indicators before opening
        # a dialog (blocking the main loop), so connect after default handlers
        connect_after_destroy(player, 'error', self.__player_error)
        # connect after to let SongTracker update stats
        connect_after_destroy(player, "song-ended", self.__song_ended)

        # set at least the playlist. the song should be restored
        # after the browser emits the song list
        player.setup(self.playlist, None, 0)
        self.__restore_cb = restore_cb
        self.__first_browser_set = True

        restore_browser = not headless
        try:
            self.select_browser(self, config.get("memory", "browser"), library,
                                player, restore_browser)
        except:
            config.set("memory", "browser", browsers.name(0))
            config.save()
            raise

        self.showhide_playlist(ui.get_widget("/Menu/View/SongList"))
        self.showhide_playqueue(ui.get_widget("/Menu/View/Queue"))

        self.songlist.connect('popup-menu', self.__songs_popup_menu)
        self.songlist.connect('columns-changed', self.__cols_changed)
        self.songlist.connect('columns-changed', self.__hide_headers)
        self.songlist.info.connect("changed", self.__set_time)

        lib = library.librarian
        connect_destroy(lib, 'changed', self.__song_changed, player)

        targets = [("text/uri-list", Gtk.TargetFlags.OTHER_APP, DND_URI_LIST)]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        self.drag_dest_set(Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY)
        self.connect('drag-data-received', self.__drag_data_received)

        if not headless:
            on_first_map(self, self.__configure_scan_dirs, library)

        if config.getboolean('library', 'refresh_on_start'):
            self.__rebuild(None, False)

        self.connect("key-press-event", self.__key_pressed, player)

        self.connect("destroy", self.__destroy)

        self.enable_window_tracking("quodlibet")
Beispiel #33
0
 def test_get_home_dir(self):
     self.assertTrue(is_fsnative(get_home_dir()))
     self.assertTrue(os.path.isabs(get_home_dir()))
Beispiel #34
0
    def lyric_filename(self):
        """Returns the validated, or default, lyrics filename for this
        file. User defined '[memory] lyric_rootpaths' and
        '[memory] lyric_filenames' matches take precedence"""

        from quodlibet.pattern \
            import ArbitraryExtensionFileFromPattern as expand_patterns

        rx_params = re.compile(r'[^\\]<[^' + re.escape(os.sep) + r']*[^\\]>')

        def expand_pathfile(rpf):
            """Return the expanded RootPathFile"""
            expanded = []
            root = expanduser(rpf.root)
            pathfile = expanduser(rpf.pathfile)
            if rx_params.search(pathfile):
                root = expand_patterns(root).format(self)
                pathfile = expand_patterns(pathfile).format(self)
            rpf = RootPathFile(root, pathfile)
            expanded.append(rpf)
            if not os.path.exists(pathfile) and is_windows():
                # prioritise a special character encoded version
                #
                # most 'alien' chars are supported for 'nix fs paths, and we
                # only pass the proposed path through 'escape_filename' (which
                # apparently doesn't respect case) if we don't care about case!
                #
                # FIX: assumes 'nix build used on a case-sensitive fs, nt case
                # insensitive. clearly this is not biting anyone though (yet!)
                pathfile = os.path.sep.join([rpf.root, rpf.end_escaped])
                rpf = RootPathFile(rpf.root, pathfile)
                expanded.insert(len(expanded) - 1, rpf)
            return expanded

        def sanitise(sep, parts):
            """Return a santisied version of a path's parts"""
            return sep.join(
                part.replace(os.path.sep, u'')[:128] for part in parts)

        # setup defaults (user-defined take precedence)
        # root search paths
        lyric_paths = \
            config.getstringlist("memory", "lyric_rootpaths", [])
        # ensure default paths
        lyric_paths.append(os.path.join(get_home_dir(), ".lyrics"))
        lyric_paths.append(
            os.path.join(os.path.dirname(self.comma('~filename'))))
        # search pathfile names
        lyric_filenames = \
            config.getstringlist("memory", "lyric_filenames", [])
        # ensure some default pathfile names
        lyric_filenames.append(
            sanitise(os.sep, [(self.comma("lyricist") or self.comma("artist")),
                              self.comma("title")]) + u'.lyric')
        lyric_filenames.append(
            sanitise(' - ', [(self.comma("lyricist") or self.comma("artist")),
                             self.comma("title")]) + u'.lyric')

        # generate all potential paths (unresolved/unexpanded)
        pathfiles = OrderedDict()
        for r in lyric_paths:
            for f in lyric_filenames:
                pathfile = os.path.join(r, os.path.dirname(f),
                                        fsnative(os.path.basename(f)))
                rpf = RootPathFile(r, pathfile)
                if not pathfile in pathfiles:
                    pathfiles[pathfile] = rpf

        #print_d("searching for lyrics in:\n%s" % '\n'.join(pathfiles.keys()))

        # expand each raw pathfile in turn and test for existence
        match_ = ""
        pathfiles_expanded = OrderedDict()
        for pf, rpf in pathfiles.items():
            for rpf in expand_pathfile(rpf):  # resolved as late as possible
                pathfile = rpf.pathfile
                pathfiles_expanded[pathfile] = rpf
                if os.path.exists(pathfile):
                    match_ = pathfile
                    break
            if match_ != "":
                break

        if not match_:
            # search even harder!
            lyric_extensions = ['lyric', 'lyrics', '', 'txt']

            #print_d("extending search to extensions: %s" % lyric_extensions)

            def generate_mod_ext_paths(pathfile):
                # separate pathfile's extension (if any)
                ext = os.path.splitext(pathfile)[1][1:]
                path = pathfile[:-1 * len(ext)].strip('.') if ext else pathfile
                # skip the proposed lyric extension if it is the same as
                # the original for a given search pathfile stub - it has
                # already been tested without success!
                extra_extensions = [x for x in lyric_extensions if x != ext]

                # join valid new extensions to pathfile stub and return
                return [
                    '.'.join([path, ext]) if ext else path
                    for ext in extra_extensions
                ]

            # look for a match by modifying the extension for each of the
            # (now fully resolved) 'pathfiles_expanded' search items
            for pathfile in pathfiles_expanded.keys():
                # get alternatives for existence testing
                paths_mod_ext = generate_mod_ext_paths(pathfile)
                for path_ext in paths_mod_ext:
                    if os.path.exists(path_ext):
                        # persistence has paid off!
                        #print_d("extended search match!")
                        match_ = path_ext
                        break
                if match_:
                    break

        if not match_:
            # default
            match_ = list(pathfiles_expanded.keys())[0]

        return match_
Beispiel #35
0
class ExportSavedSearches(EventPlugin, PluginConfigMixin):
    PLUGIN_ID = 'ExportSavedSearches'
    PLUGIN_NAME = _('Export saved searches')
    PLUGIN_DESC = _('Exports saved searches to M3U playlists.')
    PLUGIN_ICON = Icons.DOCUMENT_SAVE_AS
    REQUIRES_ACTION = True

    lastfolder = get_home_dir()

    @classmethod
    def PluginPreferences(self, parent):
        vbox = Gtk.VBox(spacing=6)

        queries = {}

        query_path = os.path.join(get_user_dir(), 'lists', 'queries.saved')
        with open(query_path, 'rU', encoding='utf-8') as query_file:
            for query_string in query_file:
                name = next(query_file).strip()
                queries[name] = Query(query_string.strip())

        for query_name, query in queries.items():
            check_button = ConfigCheckButton((query_name), "plugins",
                                             self._config_key(query_name))
            check_button.set_active(self.config_get_bool(query_name))
            vbox.pack_start(check_button, False, True, 0)

        chooserButton = Gtk.FileChooserButton(_('Select destination folder'))
        chooserButton.set_current_folder(self.lastfolder)
        chooserButton.set_action(Gtk.FileChooserAction.SELECT_FOLDER)

        # https://stackoverflow.com/a/14742779/109813
        def get_actual_filename(name):
            # Do nothing except on Windows
            if os.name != 'nt':
                return name

            dirs = name.split('\\')
            # disk letter
            test_name = [dirs[0].upper()]
            for d in dirs[1:]:
                test_name += ["%s[%s]" % (d[:-1], d[-1])]
            res = glob.glob('\\'.join(test_name))
            if not res:
                # File not found, return the input
                return name
            return res[0]

        def __file_error(file_path):
            qltk.ErrorMessage(
                None, _("Unable to export playlist"),
                _("Writing to <b>%s</b> failed.") %
                util.escape(file_path)).run()

        def __m3u_export(dir_path, query_name, songs):
            file_path = os.path.join(dir_path, query_name + '.m3u')
            try:
                fhandler = open(file_path, "wb")
            except IOError:
                __file_error(file_path)
            else:
                text = "#EXTM3U\n"

                for song in songs:
                    title = "%s - %s" % (song('~people').replace(
                        "\n", ", "), song('~title~version'))
                    path = song('~filename')
                    path = get_actual_filename(path)
                    try:
                        path = relpath(path, dir_path)
                    except ValueError:
                        # Keep absolute path
                        pass
                    text += "#EXTINF:%d,%s\n" % (song('~#length'), title)
                    text += path + "\n"

                fhandler.write(text.encode("utf-8"))
                fhandler.close()

        def __start(button):
            target_folder = chooserButton.get_filename()

            songs = app.library.get_content()
            for query_name, query in queries.items():
                if self.config_get_bool(query_name):
                    # Query is enabled
                    songs_for_query = query.filter(songs)
                    __m3u_export(target_folder, query_name, songs_for_query)

            message = qltk.Message(Gtk.MessageType.INFO, app.window, _("Done"),
                                   _("Export finished."))
            message.run()

        start_button = Gtk.Button(label=("Export"))
        start_button.connect('clicked', __start)

        vbox.pack_start(chooserButton, True, True, 0)
        vbox.pack_start(start_button, True, True, 0)
        label = Gtk.Label(
            label=
            "Quod Libet may become unresponsive. You will get a message when finished."
        )
        vbox.pack_start(label, True, True, 0)
        return qltk.Frame(("Select the saved searches to copy:"), child=vbox)
    def test_get_scan_dirs(self):
        some_path = os.path.join(unexpand(get_home_dir()), "foo")
        config.set('settings', 'scan', some_path)
        assert expanduser(some_path) in get_scan_dirs()

        assert all([isinstance(p, fsnative) for p in get_scan_dirs()])
Beispiel #37
0
 def test_get_home_dir(self):
     self.assertTrue(is_fsnative(get_home_dir()))
     self.assertTrue(os.path.isabs(get_home_dir()))
    def __init__(self, library, player, headless=False, restore_cb=None):
        super(QuodLibetWindow, self).__init__(dialog=False)
        self.last_dir = get_home_dir()

        self.__destroyed = False
        self.__update_title(player)
        self.set_default_size(550, 450)

        main_box = Gtk.VBox()
        self.add(main_box)

        # create main menubar, load/restore accelerator groups
        self.__library = library
        ui = self.__create_menu(player, library)
        accel_group = ui.get_accel_group()
        self.add_accel_group(accel_group)

        def scroll_and_jump(*args):
            self.__jump_to_current(True, True)

        keyval, mod = Gtk.accelerator_parse("<control><shift>J")
        accel_group.connect(keyval, mod, 0, scroll_and_jump)

        # dbus app menu
        # Unity puts the app menu next to our menu bar. Since it only contains
        # menu items also available in the menu bar itself, don't add it.
        if not util.is_unity():
            AppMenu(self, ui.get_action_groups()[0])

        # custom accel map
        accel_fn = os.path.join(quodlibet.get_user_dir(), "accels")
        Gtk.AccelMap.load(accel_fn)
        # save right away so we fill the file with example comments of all
        # accels
        Gtk.AccelMap.save(accel_fn)

        menubar = ui.get_widget("/Menu")

        # Since https://git.gnome.org/browse/gtk+/commit/?id=b44df22895c79
        # toplevel menu items show an empty 16x16 image. While we don't
        # need image items there UIManager creates them by default.
        # Work around by removing the empty GtkImages
        for child in menubar.get_children():
            if isinstance(child, Gtk.ImageMenuItem):
                child.set_image(None)

        main_box.pack_start(menubar, False, True, 0)

        # get the playlist up before other stuff
        self.songlist = MainSongList(library, player)
        self.songlist.show_all()
        self.songlist.connect("key-press-event", self.__songlist_key_press)
        self.songlist.connect_after(
            'drag-data-received', self.__songlist_drag_data_recv)
        self.song_scroller = SongListScroller(
            ui.get_widget("/Menu/View/SongList"))
        self.song_scroller.add(self.songlist)
        self.qexpander = QueueExpander(
            ui.get_widget("/Menu/View/Queue"), library, player)
        self.playlist = PlaylistMux(
            player, self.qexpander.model, self.songlist.model)

        top_bar = TopBar(self, player, library)
        main_box.pack_start(top_bar, False, True, 0)
        self.top_bar = top_bar

        self.__browserbox = Align(bottom=3)
        main_box.pack_start(self.__browserbox, True, True, 0)

        statusbox = StatusBarBox(self.songlist.model, player)
        self.order = statusbox.order
        self.repeat = statusbox.repeat
        self.statusbar = statusbox.statusbar

        main_box.pack_start(
            Align(statusbox, border=3, top=-3, right=3),
            False, True, 0)

        self.songpane = ConfigRVPaned("memory", "queue_position", 0.75)
        self.songpane.pack1(self.song_scroller, resize=True, shrink=False)
        self.songpane.pack2(self.qexpander, resize=True, shrink=False)
        self.__handle_position = self.songpane.get_property("position")

        def songpane_button_press_cb(pane, event):
            """If we start to drag the pane handle while the
            queue expander is unexpanded, expand it and move the handle
            to the bottom, so we can 'drag' the queue out
            """

            if event.window != pane.get_handle_window():
                return False

            if not self.qexpander.get_expanded():
                self.qexpander.set_expanded(True)
                pane.set_relative(1.0)
            return False

        self.songpane.connect("button-press-event", songpane_button_press_cb)

        self.song_scroller.connect('notify::visible', self.__show_or)
        self.qexpander.connect('notify::visible', self.__show_or)
        self.qexpander.connect('notify::expanded', self.__expand_or)
        self.qexpander.connect('draw', self.__qex_size_allocate)
        self.songpane.connect('notify', self.__moved_pane_handle)

        try:
            orders = []
            for e in config.getstringlist('memory', 'sortby', []):
                orders.append((e[1:], int(e[0])))
        except ValueError:
            pass
        else:
            self.songlist.set_sort_orders(orders)

        self.browser = None
        self.ui = ui

        main_box.show_all()

        self._playback_error_dialog = None
        connect_destroy(player, 'song-started', self.__song_started)
        connect_destroy(player, 'paused', self.__update_paused, True)
        connect_destroy(player, 'unpaused', self.__update_paused, False)
        # make sure we redraw all error indicators before opening
        # a dialog (blocking the main loop), so connect after default handlers
        connect_after_destroy(player, 'error', self.__player_error)
        # connect after to let SongTracker update stats
        connect_after_destroy(player, "song-ended", self.__song_ended)

        # set at least the playlist. the song should be restored
        # after the browser emits the song list
        player.setup(self.playlist, None, 0)
        self.__restore_cb = restore_cb
        self.__first_browser_set = True

        restore_browser = not headless
        try:
            self.select_browser(
                self, config.get("memory", "browser"), library, player,
                restore_browser)
        except:
            config.set("memory", "browser", browsers.name(0))
            config.save()
            raise

        self.showhide_playlist(ui.get_widget("/Menu/View/SongList"))
        self.showhide_playqueue(ui.get_widget("/Menu/View/Queue"))

        self.songlist.connect('popup-menu', self.__songs_popup_menu)
        self.songlist.connect('columns-changed', self.__cols_changed)
        self.songlist.connect('columns-changed', self.__hide_headers)
        self.songlist.info.connect("changed", self.__set_time)

        lib = library.librarian
        connect_destroy(lib, 'changed', self.__song_changed, player)

        targets = [("text/uri-list", Gtk.TargetFlags.OTHER_APP, DND_URI_LIST)]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        self.drag_dest_set(
            Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY)
        self.connect('drag-data-received', self.__drag_data_received)

        if not headless:
            on_first_map(self, self.__configure_scan_dirs, library)

        if config.getboolean('library', 'refresh_on_start'):
            self.__rebuild(None, False)

        self.connect("key-press-event", self.__key_pressed, player)

        self.connect("destroy", self.__destroy)

        self.enable_window_tracking("quodlibet")
Beispiel #39
0
from senf import fsn2bytes, extsep

from quodlibet import _
from quodlibet import app
from quodlibet.plugins.songshelpers import each_song, is_writable, is_a_file, \
    is_finite
from quodlibet.qltk import ErrorMessage, Icons
from quodlibet.util.path import get_home_dir
from quodlibet.plugins.songsmenu import SongsMenuPlugin
from quodlibet.compat import iteritems

__all__ = ['Export', 'Import']


lastfolder = get_home_dir()


def filechooser(save, title):
    chooser = Gtk.FileChooserDialog(
        title=(save and "Export %s Metadata to ..." or
               "Import %s Metadata from ...") % title,
        action=(save and Gtk.FileChooserAction.SAVE or
                Gtk.FileChooserAction.OPEN))

    chooser.add_button(_("_OK"), Gtk.ResponseType.ACCEPT)
    chooser.add_button(_("_Cancel"), Gtk.ResponseType.REJECT)

    for name, pattern in [('Tag files (*.tags)', '*.tags'),
                          ('All Files', '*')]:
        filter = Gtk.FileFilter()
Beispiel #40
0
class AudioFeeds(Browser):
    __feeds = Gtk.ListStore(object)  # unread

    headers = ("title artist performer ~people album date website language "
               "copyright organization license contact").split()

    name = _("Audio Feeds")
    accelerated_name = _("_Audio Feeds")
    keys = ["AudioFeeds"]
    priority = 20
    uses_main_library = False

    __last_folder = get_home_dir()

    def pack(self, songpane):
        container = qltk.ConfigRHPaned("browsers", "audiofeeds_pos", 0.4)
        self.show()
        container.pack1(self, True, False)
        container.pack2(songpane, True, False)
        return container

    def unpack(self, container, songpane):
        container.remove(songpane)
        container.remove(self)

    @staticmethod
    def cell_data(col, render, model, iter, data):
        if model[iter][0].changed:
            render.markup = "<b>%s</b>" % util.escape(model[iter][0].name)
        else:
            render.markup = util.escape(model[iter][0].name)
        render.set_property('markup', render.markup)

    @classmethod
    def changed(klass, feeds):
        for row in klass.__feeds:
            if row[0] in feeds:
                row[0].changed = True
                row[0] = row[0]
        AudioFeeds.write()

    @classmethod
    def write(klass):
        feeds = [row[0] for row in klass.__feeds]
        with open(FEEDS, "wb") as f:
            pickle_dump(feeds, f, 2)

    @classmethod
    def init(klass, library):
        uris = set()
        try:
            with open(FEEDS, "rb") as fileobj:
                feeds = pickle_load(fileobj)
        except (PickleError, EnvironmentError):
            pass
        else:
            for feed in feeds:
                if feed.uri in uris:
                    continue
                klass.__feeds.append(row=[feed])
                uris.add(feed.uri)
        GLib.idle_add(klass.__do_check)

    @classmethod
    def reload(klass, library):
        klass.__feeds = Gtk.ListStore(object)  # unread
        klass.init(library)

    @classmethod
    def __do_check(klass):
        thread = threading.Thread(target=klass.__check, args=())
        thread.setDaemon(True)
        thread.start()

    @classmethod
    def __check(klass):
        for row in klass.__feeds:
            feed = row[0]
            if feed.get_age() < 2 * 60 * 60:
                continue
            elif feed.parse():
                feed.changed = True
                row[0] = feed
        klass.write()
        GLib.timeout_add(60 * 60 * 1000, klass.__do_check)

    def Menu(self, songs, library, items):
        if len(songs) == 1:
            item = qltk.MenuItem(_(u"_Download…"), Icons.NETWORK_WORKGROUP)
            item.connect('activate', self.__download, songs[0]("~uri"))
            item.set_sensitive(not songs[0].is_file)
        else:
            uris = [song("~uri") for song in songs if not song.is_file]
            item = qltk.MenuItem(_(u"_Download…"), Icons.NETWORK_WORKGROUP)
            item.connect('activate', self.__download_many, uris)
            item.set_sensitive(bool(uris))

        items.append([item])
        menu = SongsMenu(library, songs, items=items)
        return menu

    def __download_many(self, activator, sources):
        chooser = Gtk.FileChooserDialog(
            title=_("Download Files"), parent=qltk.get_top_parent(self),
            action=Gtk.FileChooserAction.CREATE_FOLDER)
        chooser.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
        chooser.add_button(_("_Save"), Gtk.ResponseType.OK)
        chooser.set_current_folder(self.__last_folder)
        resp = chooser.run()
        if resp == Gtk.ResponseType.OK:
            target = chooser.get_filename()
            if target:
                type(self).__last_folder = os.path.dirname(target)
                for i, source in enumerate(sources):
                    base = os.path.basename(source)
                    if not base:
                        base = ("file%d" % i) + (
                            os.path.splitext(source)[1] or ".audio")
                    fulltarget = os.path.join(target, base)
                    DownloadWindow.download(source, fulltarget, self)
        chooser.destroy()

    def __download(self, activator, source):
        chooser = Gtk.FileChooserDialog(
            title=_("Download File"), parent=qltk.get_top_parent(self),
            action=Gtk.FileChooserAction.SAVE)
        chooser.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
        chooser.add_button(_("_Save"), Gtk.ResponseType.OK)
        chooser.set_current_folder(self.__last_folder)
        name = os.path.basename(source)
        if name:
            chooser.set_current_name(name)
        resp = chooser.run()
        if resp == Gtk.ResponseType.OK:
            target = chooser.get_filename()
            if target:
                type(self).__last_folder = os.path.dirname(target)
                DownloadWindow.download(source, target, self)
        chooser.destroy()

    def __init__(self, library):
        super(AudioFeeds, self).__init__(spacing=6)
        self.set_orientation(Gtk.Orientation.VERTICAL)

        self.__view = view = AllTreeView()
        self.__render = render = Gtk.CellRendererText()
        render.set_property('ellipsize', Pango.EllipsizeMode.END)
        col = Gtk.TreeViewColumn("Audio Feeds", render)
        col.set_cell_data_func(render, AudioFeeds.cell_data)
        view.append_column(col)
        view.set_model(self.__feeds)
        view.set_rules_hint(True)
        view.set_headers_visible(False)
        swin = ScrolledWindow()
        swin.set_shadow_type(Gtk.ShadowType.IN)
        swin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        swin.add(view)
        self.pack_start(swin, True, True, 0)

        new = Button(_("_New"), Icons.LIST_ADD, Gtk.IconSize.MENU)
        new.connect('clicked', self.__new_feed)
        view.get_selection().connect('changed', self.__changed)
        view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
        view.connect('popup-menu', self.__popup_menu)

        targets = [
            ("text/uri-list", 0, DND_URI_LIST),
            ("text/x-moz-url", 0, DND_MOZ_URL)
        ]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        view.drag_dest_set(Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY)
        view.connect('drag-data-received', self.__drag_data_received)
        view.connect('drag-motion', self.__drag_motion)
        view.connect('drag-leave', self.__drag_leave)

        connect_obj(self, 'destroy', self.__save, view)

        self.pack_start(Align(new, left=3, bottom=3), False, True, 0)

        for child in self.get_children():
            child.show_all()

    def __drag_motion(self, view, ctx, x, y, time):
        targets = [t.name() for t in ctx.list_targets()]
        if "text/x-quodlibet-songs" not in targets:
            view.get_parent().drag_highlight()
            return True
        return False

    def __drag_leave(self, view, ctx, time):
        view.get_parent().drag_unhighlight()

    def __drag_data_received(self, view, ctx, x, y, sel, tid, etime):
        view.emit_stop_by_name('drag-data-received')
        targets = [
            ("text/uri-list", 0, DND_URI_LIST),
            ("text/x-moz-url", 0, DND_MOZ_URL)
        ]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        view.drag_dest_set(Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY)
        if tid == DND_URI_LIST:
            uri = sel.get_uris()[0]
        elif tid == DND_MOZ_URL:
            uri = sel.data.decode('utf16', 'replace').split('\n')[0]
        else:
            ctx.finish(False, False, etime)
            return

        ctx.finish(True, False, etime)

        feed = Feed(uri.encode("ascii", "replace"))
        feed.changed = feed.parse()
        if feed:
            self.__feeds.append(row=[feed])
            AudioFeeds.write()
        else:
            ErrorMessage(
                self, _("Unable to add feed"),
                _("%s could not be added. The server may be down, "
                  "or the location may not be an audio feed.") %
                util.bold(util.escape(feed.uri))).run()

    def __popup_menu(self, view):
        model, paths = view.get_selection().get_selected_rows()
        menu = Gtk.Menu()
        refresh = MenuItem(_("_Refresh"), Icons.VIEW_REFRESH)
        delete = MenuItem(_("_Delete"), Icons.EDIT_DELETE)

        connect_obj(refresh,
            'activate', self.__refresh, [model[p][0] for p in paths])
        connect_obj(delete,
            'activate', map, model.remove, map(model.get_iter, paths))

        menu.append(refresh)
        menu.append(delete)
        menu.show_all()
        menu.connect('selection-done', lambda m: m.destroy())

        # XXX: keep the menu around
        self.__menu = menu

        return view.popup_menu(menu, 0, Gtk.get_current_event_time())

    def __save(self, view):
        AudioFeeds.write()

    def __refresh(self, feeds):
        changed = listfilter(Feed.parse, feeds)
        AudioFeeds.changed(changed)

    def activate(self):
        self.__changed(self.__view.get_selection())

    def __changed(self, selection):
        model, paths = selection.get_selected_rows()
        if model and paths:
            songs = []
            for path in paths:
                model[path][0].changed = False
                songs.extend(model[path][0])
            self.songs_selected(songs, True)
            config.set("browsers", "audiofeeds",
                       "\t".join([model[path][0].name for path in paths]))

    def __new_feed(self, activator):
        feed = AddFeedDialog(self).run()
        if feed is not None:
            feed.changed = feed.parse()
            if feed:
                self.__feeds.append(row=[feed])
                AudioFeeds.write()
            else:
                ErrorMessage(
                    self, _("Unable to add feed"),
                    _("%s could not be added. The server may be down, "
                      "or the location may not be an audio feed.") %
                    util.bold(util.escape(feed.uri))).run()

    def restore(self):
        try:
            names = config.get("browsers", "audiofeeds").split("\t")
        except:
            pass
        else:
            self.__view.select_by_func(lambda r: r[0].name in names)
Beispiel #41
0
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation

from gi.repository import Gtk
from os.path import splitext, extsep, dirname

from quodlibet import app
from quodlibet.qltk import ErrorMessage
from quodlibet.util.path import get_home_dir
from quodlibet.plugins.songsmenu import SongsMenuPlugin

__all__ = ['Export', 'Import']


lastfolder = get_home_dir()


def filechooser(save, title):
    chooser = Gtk.FileChooserDialog(
        title=(save and "Export %s Metadata to ..." or
               "Import %s Metadata from ...") % title,
        action=(save and Gtk.FileChooserAction.SAVE or
                Gtk.FileChooserAction.OPEN),
        buttons=(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT,
                 Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT))

    for name, pattern in [('Tag files (*.tags)', '*.tags'),
                          ('All Files', '*')]:
        filter = Gtk.FileFilter()
        filter.set_name(name)
Beispiel #42
0
 def test_get_home_dir(self):
     self.assertTrue(isinstance(get_home_dir(), fsnative))
     self.assertTrue(os.path.isabs(get_home_dir()))