def test_sanitize_py2_fixup(self): if PY3: return old = dict.__new__(AudioFile) dict.__init__(old, { b"foo": b"bar", u"öäü": b"bla", "~#num": u"1", "~#num2": u"1.25", "~#num3": u"bla", "~filename": u"text", "~mountpoint": b"bytes", "~somethingdifferent": b"hello", }) fixed = { b"foo": u"bar", u"öäü": u"bla", "~#num": 1, "~#num2": 1.25, "~#num3": 0, "~filename": fsnative(u"text"), "~mountpoint": fsnative(u"bytes"), "~somethingdifferent": u"hello", } data = dump_audio_files([old]) new = load_audio_files(data) assert dict(new[0]) == fixed for v1, v2 in zip(sorted(new[0].values()), sorted(fixed.values())): assert type(v1) is type(v2)
def init_test_environ(): """This needs to be called before any test can be run. Before exiting the process call exit_test_environ() to clean up any resources created. """ global _TEMP_DIR, _BUS_INFO, _VDISPLAY, _faulthandler_fobj # create a user dir in /tmp and set env vars _TEMP_DIR = tempfile.mkdtemp(prefix=fsnative(u"QL-TEST-")) # needed for dbus/dconf runtime_dir = tempfile.mkdtemp(prefix=fsnative(u"RUNTIME-"), dir=_TEMP_DIR) os.chmod(runtime_dir, 0o700) environ["XDG_RUNTIME_DIR"] = runtime_dir # force the old cache dir so that GStreamer can re-use the GstRegistry # cache file environ["XDG_CACHE_HOME"] = xdg_get_cache_home() # GStreamer will update the cache if the environment has changed # (in Gst.init()). Since it takes 0.5s here and doesn't add much, # disable it. If the registry cache is missing it will be created # despite this setting. environ["GST_REGISTRY_UPDATE"] = fsnative(u"no") # set HOME and remove all XDG vars that default to it if not set home_dir = tempfile.mkdtemp(prefix=fsnative(u"HOME-"), dir=_TEMP_DIR) environ["HOME"] = home_dir # set to new default environ.pop("XDG_DATA_HOME", None) if xvfbwrapper is not None: _VDISPLAY = xvfbwrapper.Xvfb() _VDISPLAY.start() _BUS_INFO = None if os.name != "nt" and "DBUS_SESSION_BUS_ADDRESS" in environ: _BUS_INFO = dbus_launch_user() environ.update(_BUS_INFO) quodlibet.init(no_translations=True, no_excepthook=True) quodlibet.app.name = "QL Tests" # to get around pytest silencing _faulthandler_fobj = os.fdopen(os.dup(sys.__stderr__.fileno()), "w") faulthandler.enable(_faulthandler_fobj) # try to make things the same in case a different locale is active. # LANG for gettext, setlocale for number formatting etc. # Note: setlocale has to be called after Gtk.init() try: if os.name != "nt": environ["LANG"] = locale.setlocale(locale.LC_ALL, "en_US.UTF-8") else: environ["LANG"] = "en_US.utf8" locale.setlocale(locale.LC_ALL, "english") except locale.Error: pass
def test_types(self): from quodlibet.util.path import normalize_path assert isinstance(normalize_path(fsnative(u"foo"), False), fsnative) assert isinstance(normalize_path("foo", False), fsnative) assert isinstance(normalize_path(fsnative(u"foo"), True), fsnative) assert isinstance(normalize_path("foo", True), fsnative)
def test_selection_set_songs(self): song = AudioFile() song["~filename"] = fsnative(u"foo") sel = MockSelData() qltk.selection_set_songs(sel, [song]) assert sel.data == fsn2bytes(fsnative(u"foo"), "utf-8") assert qltk.selection_get_filenames(sel) == [fsnative(u"foo")]
def test_disctrack(self): pat = TagsFromPattern('<discnumber><tracknumber>. <title>') self.assertEquals(pat.match_path(fsnative(u'101. T1.ogg')), dict(discnumber='1', tracknumber='01', title='T1')) self.assertEquals(pat.match_path(fsnative(u'1318. T18.ogg')), dict(discnumber='13', tracknumber='18', title='T18')) self.assertEquals(pat.match_path(fsnative(u'24. T4.ogg')), dict(discnumber='2', tracknumber='4', title='T4'))
def sanitize(self, filename=None): """Fill in metadata defaults. Find ~mountpoint, ~#mtime, ~#filesize and ~#added. Check for null bytes in tags. Does not raise. """ # Replace nulls with newlines, trimming zero-length segments for key, val in listitems(self): self[key] = val if isinstance(val, string_types) and '\0' in val: self[key] = '\n'.join(filter(lambda s: s, val.split('\0'))) # Remove unnecessary defaults if key in NUMERIC_ZERO_DEFAULT and val == 0: del self[key] if filename: self["~filename"] = filename elif "~filename" not in self: raise ValueError("Unknown filename!") assert isinstance(self["~filename"], fsnative) if self.is_file: self["~filename"] = normalize_path( self["~filename"], canonicalise=True) # Find mount point (terminating at "/" if necessary) head = self["~filename"] while "~mountpoint" not in self: head, tail = os.path.split(head) # Prevent infinite loop without a fully-qualified filename # (the unit tests use these). head = head or fsnative(u"/") if ismount(head): self["~mountpoint"] = head else: self["~mountpoint"] = fsnative(u"/") # Fill in necessary values. self.setdefault("~#added", int(time.time())) # For efficiency, do a single stat here. See Issue 504 try: stat = os.stat(self['~filename']) self["~#mtime"] = stat.st_mtime self["~#filesize"] = stat.st_size # Issue 342. This is a horrible approximation (due to headers) but # on FLACs, the most common case, this should be close enough if "~#bitrate" not in self: try: # kbps = bytes * 8 / seconds / 1000 self["~#bitrate"] = int(stat.st_size / (self["~#length"] * (1000 / 8))) except (KeyError, ZeroDivisionError): pass except OSError: self["~#mtime"] = 0
def test_uri2fsn(self): if os.name != "nt": path = uri2fsn("file:///home/piman/cr%21azy") self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(u"/home/piman/cr!azy")) else: path = uri2fsn("file:///C:/foo") self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(u"C:\\foo"))
def test_sort_albums(self): # Make sure we have more than one album, one having a null date f = AF({"~filename": fsnative(u"/1"), "album": "one"}) f2 = AF({"~filename": fsnative(u"/2"), "album": "one"}) f3 = AF({"~filename": fsnative(u"/3"), "album": "two", "date": "2009"}) f4 = AF({"~filename": fsnative(u"/4")}) albums, count = _sort_albums([f, f2, f3, f4]) self.failUnlessEqual(count, 1) self.failUnlessEqual(len(albums), 2)
def atomic_save(filename, mode): """Try to replace the content of a file in the safest way possible. A temporary file will be created in the same directory where the replacement data can be written into. After writing is done the data will be flushed to disk and the original file replaced atomically. In case of an error this raises IOError and OSError and the original file will be untouched. In case the computer crashes or any other non-recoverable error happens the temporary file will be left behind and has to be deleted manually. with atomic_save("config.cfg", "wb") as f: f.write(data) """ assert isinstance(filename, fsnative) dir_ = os.path.dirname(filename) basename = os.path.basename(filename) fileobj = tempfile.NamedTemporaryFile( mode=mode, dir=dir_, prefix=basename + fsnative(u"_"), suffix=fsnative(u".tmp"), delete=False) try: yield fileobj fileobj.flush() fileno = fileobj.fileno() if os.name != "nt" and hasattr(fcntl, "F_FULLFSYNC"): # on OSX fsync doesn't sync all the way.. # https://lists.apple.com/archives/darwin-dev/2005/Feb/msg00072.html # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/fsync.2.html fcntl.fcntl(fileno, fcntl.F_FULLFSYNC) else: # on linux fsync goes all the way by default # http://linux.die.net/man/2/fsync os.fsync(fileno) fileobj.close() if os.name == "nt": _windows_rename(fileobj.name, filename) else: os.rename(fileobj.name, filename) except: try: os.unlink(fileobj.name) except OSError: pass raise finally: fileobj.close()
def test_roundtrip(self): if os.name == "nt": paths = [u"C:\\öäü.txt"] else: paths = [u"/öäü.txt", u"/a/foo/bar", u"/a/b/foo/bar"] for source in paths: path = uri2fsn(fsn2uri(fsnative(source))) self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(source))
def test_fsn2uri(self): if os.name != "nt": uri = fsn2uri(fsnative(u"/öäü.txt")) self.assertEqual(uri, u"file:///%C3%B6%C3%A4%C3%BC.txt") else: uri = fsn2uri(fsnative(u"C:\\öäü.txt")) self.assertEqual( uri, "file:///C:/%C3%B6%C3%A4%C3%BC.txt") self.assertEqual( fsn2uri(u"C:\\SomeDir\xe4"), "file:///C:/SomeDir%C3%A4")
def test_conv(self): empty = fsnative(u"") v = self.c.filter(empty, fsnative(u"foobar baz")) self.failUnlessEqual(v, fsnative(u"foobar baz")) self.failUnless(isinstance(v, fsnative)) v = self.c.filter(empty, fsnative(u"Foobar.BAZ")) self.failUnlessEqual(v, fsnative(u"foobar.baz")) self.failUnless(isinstance(v, fsnative))
def test_types(self): v = strip_win32_incompat_from_path(fsnative(u"")) self.assertTrue(isinstance(v, fsnative)) v = strip_win32_incompat_from_path(fsnative(u"foo")) self.assertTrue(isinstance(v, fsnative)) v = strip_win32_incompat_from_path(u"") self.assertTrue(isinstance(v, text_type)) v = strip_win32_incompat_from_path(u"foo") self.assertTrue(isinstance(v, text_type))
def test_ends_with_dots_or_spaces(self): empty = fsnative(u"") v = self.c.filter(empty, fsnative(u'foo. . ')) self.failUnlessEqual(v, fsnative(u"foo. ._")) self.assertTrue(isinstance(v, fsnative)) if os.name == "nt": self.failUnlessEqual( self.c.filter(empty, u'foo. \\bar .'), u"foo._\\bar _") else: self.failUnlessEqual( self.c.filter(empty, u'foo. /bar .'), "foo._/bar _")
def set_scan_dirs(dirs): """Saves a list of fs paths which should be scanned Args: list """ assert all(isinstance(d, fsnative) for d in dirs) if is_windows(): joined = fsnative(u":").join(dirs) else: joined = join_escape(dirs, fsnative(u":")) config.setbytes("settings", "scan", fsn2bytes(joined, "utf-8"))
def test_main(self): self.assertEqual(decode_value("~#foo", 0.25), u"0.25") self.assertEqual(decode_value("~#foo", 4), u"4") self.assertEqual(decode_value("~#foo", "bar"), u"bar") self.assertTrue(isinstance(decode_value("~#foo", "bar"), text_type)) path = fsnative(u"/foobar") self.assertEqual(decode_value("~filename", path), fsn2text(path))
def limit_path(path, ellipsis=True): """Reduces the filename length of all filenames in the given path to the common maximum length for current platform. While the limits are depended on the file system and more restrictions may apply, this covers the common case. """ assert isinstance(path, fsnative) main, ext = os.path.splitext(path) parts = main.split(sep) for i, p in enumerate(parts): # Limit each path section to 255 (bytes on linux, chars on win). # http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits limit = 255 if i == len(parts) - 1: limit -= len(ext) if len(p) > limit: if ellipsis: p = p[:limit - 2] + fsnative(u"..") else: p = p[:limit] parts[i] = p return sep.join(parts) + ext
def test_conv(self): empty = fsnative(u"") in_ = u"foo \u00c1 \u1234" out = u"foo _ _" v = self.c.filter(empty, in_) self.failUnlessEqual(v, out) self.failUnless(isinstance(v, text_type))
def test_embedded_special_cover_words(self): """Tests that words incidentally containing embedded "special" words album keywords (e.g. cover, disc, back) don't trigger See Issue 818""" song = AudioFile({ "~filename": fsnative(os.path.join(self.dir, u"asong.ogg")), "album": "foobar", "title": "Ode to Baz", "artist": "Q-Man", }) data = [('back.jpg', False), ('discovery.jpg', False), ("Pharell - frontin'.jpg", False), ('nickelback - Curb.jpg', False), ('foobar.jpg', True), ('folder.jpg', True), # Though this order is debatable ('Q-Man - foobar.jpg', True), ('Q-man - foobar (cover).jpg', True)] for fn, should_find in data: f = self.add_file(fn) cover = self._find_cover(song) if cover: actual = os.path.abspath(cover.name) assert path_equal( actual, f, "\"%s\" should trump \"%s\"" % (f, actual)) else: self.failIf(should_find, msg="Couldn't find %s for %s" % (f, song("~filename")))
def test_masked_handling(self): if os.name == "nt": # FIXME: masking isn't properly implemented on Windows return # playlists can contain songs and paths for masked handling.. lib = FileLibrary("foobar") with self.wrap("playlist", lib) as pl: song = Fakesong({"date": "2038", "~filename": fsnative(u"/fake")}) song.sanitize() lib.add([song]) # mask and update lib.mask("/") pl.append(song) pl.remove_songs([song]) self.failUnless("/fake" in pl) pl.extend(self.TWO_SONGS) # check if collections can handle the mix self.failUnlessEqual(pl("date"), "2038") # unmask and update lib.unmask("/") pl.add_songs(["/fake"], lib) self.failUnless(song in pl) lib.destroy()
def test_drag_data_get(self): b = self.bar song = AudioFile() song["~filename"] = fsnative(u"foo") sel = MockSelData() qltk.selection_set_songs(sel, [song]) b._drag_data_get(None, None, sel, DND_QL, None)
def test_conv(self): empty = fsnative(u"") test = u"\u00c1 test" out = u"A test" v = self.c.filter(empty, test) self.failUnlessEqual(v, out) self.failUnless(isinstance(v, text_type))
def test_rename_to_existing(self): quux.rename(quux("~basename")) if os.name != "nt": self.failUnlessRaises( ValueError, quux.rename, fsnative(u"/dev/null")) self.failUnlessRaises(ValueError, quux.rename, get_data_path("silence-44-s.ogg"))
def test_tag_text_types(self): for t in format_types: i = AudioFile.__new__(t) i["~filename"] = fsnative(u"foo") for tag in TAGS.values(): name = tag.name # brute force variants = [ name, "~" + name, name + "sort", "~" + name + "sort", name + ":role", "~" + name + ":role", "~" + name + "sort:role", name + "sort:role", ] for name in variants: if name in FILESYSTEM_TAGS: assert isinstance(i(name, fsnative()), fsnative) else: assert isinstance(i(name), text_type)
def unexpand(filename): """Replace the user's home directory with ~ or %USERPROFILE%, if it appears at the start of the path name. Args: filename (fsnative): The file path Returns: fsnative: The path with the home directory replaced """ sub = (os.name == "nt" and fsnative(u"%USERPROFILE%")) or fsnative(u"~") home = expanduser("~") if filename == home: return sub elif filename.startswith(home + os.path.sep): filename = filename.replace(home, sub, 1) return filename
def setUp(self): config.init() self.s1 = AudioFile( {"album": "I Hate: Tests", "artist": "piman", "title": "Quuxly", "version": "cake mix", "~filename": fsnative(u"/dir1/foobar.ogg"), "~#length": 224, "~#skipcount": 13, "~#playcount": 24, "date": "2007-05-24"}) self.s2 = AudioFile( {"album": "Foo the Bar", "artist": "mu", "title": "Rockin' Out", "~filename": fsnative(u"/dir2/something.mp3"), "tracknumber": "12/15"}) self.s3 = AudioFile( {"artist": "piman\nmu", "~filename": fsnative(u"/test/\xf6\xe4\xfc/fo\xfc.ogg")}) self.s4 = AudioFile({"title": u"Ångström", "utf8": "Ångström"}) self.s5 = AudioFile({"title": "oh&blahhh", "artist": "!ohno"})
def test_currentsong_length(self): app.player.go_to(AudioFile({ "~filename": fsnative(), "~#length": 12.25, })) response = self._cmd(b"currentsong\n") assert b"Time: 12\n" in response
def setUp(self): config.RATINGS = config.HardCodedRatingsPrefs() self.failUnlessEqual(config.RATINGS.number, NUM_RATINGS) self.library = SongLibrary() self.library.librarian = SongLibrarian() self.af = AudioFile({"~filename": fsnative(u"/foo"), "~#rating": 1.0}) self.af.sanitize() self.rmi = RatingsMenuItem([self.af], self.library)
def unexpand(filename): """Replace the user's home directory with ~ or %USERPROFILE%, if it appears at the start of the path name. Args: filename (fsnative): The file path Returns: fsnative: The path with the home directory replaced """ sub = (os.name == "nt" and fsnative(u"%USERPROFILE%")) or fsnative(u"~") home = os.path.normcase(get_home_dir()).rstrip(os.path.sep) norm = os.path.normcase(filename) if norm == home: return sub elif norm.startswith(home + os.path.sep): filename = sub + filename[len(home):] return filename
def test_sanitize_py2_normal(self): if PY3: return af = AudioFile({ b"foo": u"bar", u"öäü": u"bla", "~#num": 1, "~#num2": long(2), "~#num3": 1.25, "~filename": fsnative(u"filename"), "~mountpoint": fsnative(u"mount"), "~somethingdifferent": u"hello", }) data = dump_audio_files([af]) new = load_audio_files(data) assert dict(new[0]) == dict(af)
def test_write(self): with self.wrap("playlist") as pl: pl.extend(NUMERIC_SONGS) pl.extend([fsnative(u"xf0xf0")]) pl.write() with open(pl.path, "rb") as h: self.assertEqual(len(h.read().splitlines()), len(NUMERIC_SONGS) + 1)
def test_currentsong_length(self): app.player.go_to( AudioFile({ "~filename": fsnative(), "~#length": 12.25, })) response = self._cmd(b"currentsong\n") assert b"Time: 12\n" in response
def test_lyric_filename(self): song = AudioFile() song["~filename"] = fsnative(u"filename") self.assertTrue(isinstance(song.lyric_filename, fsnative)) song["title"] = u"Title" song["artist"] = u"Artist" self.assertTrue(isinstance(song.lyric_filename, fsnative)) song["lyricist"] = u"Lyricist" self.assertTrue(isinstance(song.lyric_filename, fsnative))
def __init__(self, target, num): super(Song, self).__init__() self["title"] = "title_%d" % (num + 1) self["artist"] = "artist" self["album"] = "album" self["labelid"] = self["album"] self["~filename"] = \ fsnative(os.path.join(target, self["title"] + ".mp3"))
def test_rename_to_existing(self): self.quux.rename(self.quux("~filename")) if os.name != "nt": self.failUnlessRaises(ValueError, self.quux.rename, fsnative(u"/dev/null")) with temp_filename() as new_file: with self.assertRaises(ValueError): self.quux.rename(new_file)
def setUp(self): config.init() player = NullPlayer() song = AudioFile() song.bookmarks = [(10, "bla")] song.sanitize(fsnative(u"/")) player.song = song self.player = player self.library = SongLibrary()
def test_write(self): with self.wrap("playlist") as pl: pl.extend(NUMERIC_SONGS) pl.extend([fsnative(u"xf0xf0")]) pl.write() with open(pl.filename, "rb") as h: self.assertEqual(len(h.read().splitlines()), len(NUMERIC_SONGS) + 1)
def sanitize(self, filename=None): """Fill in metadata defaults. Find ~mountpoint, ~#mtime, ~#filesize and ~#added. Check for null bytes in tags. Does not raise. """ # Replace nulls with newlines, trimming zero-length segments for key, val in listitems(self): if isinstance(val, string_types) and '\0' in val: self[key] = '\n'.join(filter(lambda s: s, val.split('\0'))) # Remove unnecessary defaults if key in NUMERIC_ZERO_DEFAULT and val == 0: del self[key] if filename: self["~filename"] = filename elif "~filename" not in self: raise ValueError("Unknown filename!") assert isinstance(self["~filename"], fsnative) if self.is_file: self["~filename"] = normalize_path(self["~filename"], canonicalise=True) # Find mount point (terminating at "/" if necessary) head = self["~filename"] while "~mountpoint" not in self: head, tail = os.path.split(head) # Prevent infinite loop without a fully-qualified filename # (the unit tests use these). head = head or "/" if os.path.ismount(head): self["~mountpoint"] = head else: self["~mountpoint"] = fsnative(u"/") # Fill in necessary values. self.setdefault("~#added", int(time.time())) # For efficiency, do a single stat here. See Issue 504 try: stat = os.stat(self['~filename']) self["~#mtime"] = stat.st_mtime self["~#filesize"] = stat.st_size # Issue 342. This is a horrible approximation (due to headers) but # on FLACs, the most common case, this should be close enough if "~#bitrate" not in self: try: # kbps = bytes * 8 / seconds / 1000 self["~#bitrate"] = int(stat.st_size / (self["~#length"] * (1000 / 8))) except (KeyError, ZeroDivisionError): pass except OSError: self["~#mtime"] = 0
def setUp(self): config.init() self.library = SongLibrary() backend = quodlibet.player.init_backend("nullbe") self.device = backend.init(self.library) self.songs = [AudioFile({"title": x}) for x in ["song1", "song2", "song3"]] for song in self.songs: song.sanitize(fsnative(unicode(song["title"])))
def test_ext_case_preservation(s): x = AudioFile({'~filename': fsnative(u'/tmp/Xx.Flac'), 'title': 'Xx'}) # If pattern has a particular ext, preserve case of ext p1 = s._create('<~basename>') s.assertEquals(p1.format(x), 'Xx.Flac') p2 = s._create('<title>.FLAC') s.assertEquals(p2.format(x), 'Xx.FLAC') # If pattern doesn't have a particular ext, lowercase ext p3 = s._create('<title>') s.assertEquals(p3.format(x), 'Xx.flac')
def __init__(self, num, album=None): super(AlbumSong, self).__init__() self["~filename"] = fsnative(u"file_%d.mp3" % (num + 1)) self["title"] = "Song %d" % (num + 1) self["artist"] = "Fakeman" if album is None: self["album"] = "Album %d" % (num % 3 + 1) else: self["album"] = album self["labelid"] = self["album"]
def test_rename(self): old_fn = quux("~basename") new_fn = fsnative(u"anothersong.mp3") dir = os.path.dirname(get_data_path("")) self.failUnless(quux.exists()) quux.rename(new_fn) self.failIf(os.path.exists(dir + old_fn), "%s already exists" % (dir + old_fn)) self.failUnless(quux.exists()) quux.rename(old_fn) self.failIf(os.path.exists(dir + new_fn)) self.failUnless(quux.exists()) # move out of parent dir and back quux.rename(fsnative(u"/tmp/more_test_data")) self.failIf(os.path.exists(dir + old_fn)) self.failUnless(quux.exists()) quux.rename(dir + old_fn) self.failUnless(quux.exists())
def lyric_filename(self): """Returns the (potential) lyrics filename for this file""" filename = self.comma("title").replace(u'/', u'')[:128] + u'.lyric' sub_dir = ((self.comma("lyricist") or self.comma("artist")).replace(u'/', u'')[:128]) if os.name == "nt": # this was added at a later point. only use escape_filename here # to keep the linux case the same as before filename = escape_filename(filename) sub_dir = escape_filename(sub_dir) else: filename = fsnative(filename) sub_dir = fsnative(sub_dir) path = os.path.join(expanduser(fsnative(u"~/.lyrics")), sub_dir, filename) return path
def _print_playing(app, fstring="<artist~album~tracknumber~title>"): from quodlibet.formats import AudioFile from quodlibet.pattern import Pattern song = app.player.info if song is None: song = AudioFile({"~filename": fsnative(u"/")}) song.sanitize() return Pattern(fstring).format(song) + "\n"
def escape_filename(s): """Escape a string in a manner suitable for a filename. Takes unicode or str and returns a fsnative path. """ if isinstance(s, unicode): s = s.encode("utf-8") return fsnative(urllib.quote(s, safe="").decode("utf-8"))
def test_main(self): v = fsnative(u"foo") self.assertTrue(isinstance(v, fsnative)) v2 = glib2fsn(fsn2glib(v)) self.assertTrue(isinstance(v2, fsnative)) self.assertEqual(v, v2) v3 = bytes2fsn(fsn2bytes(v, "utf-8"), "utf-8") self.assertTrue(isinstance(v3, fsnative)) self.assertEqual(v, v3)
def main(argv=None): if argv is None: argv = sys_argv import quodlibet config_file = os.path.join(quodlibet.get_user_dir(), "config") quodlibet.init(config_file=config_file) from quodlibet.qltk import add_signal_watch add_signal_watch(app.quit) opts = util.OptionParser("Ex Falso", const.VERSION, _("an audio tag editor"), "[%s]" % _("directory")) argv.append(os.path.abspath(fsnative(u"."))) opts, args = opts.parse(argv[1:]) args[0] = os.path.realpath(args[0]) app.name = "Ex Falso" app.description = _("Audio metadata editor") app.id = "exfalso" app.process_name = "exfalso" quodlibet.set_application_info(app) import quodlibet.library import quodlibet.player app.library = quodlibet.library.init() app.player = quodlibet.player.init_player("nullbe", app.librarian) from quodlibet.qltk.songlist import PlaylistModel app.player.setup(PlaylistModel(), None, 0) pm = quodlibet.init_plugins() pm.rescan() from quodlibet.qltk.exfalsowindow import ExFalsoWindow dir_ = args[0] app.window = ExFalsoWindow(app.library, dir_) app.window.init_plugins() from quodlibet.util.cover import CoverManager app.cover_manager = CoverManager() app.cover_manager.init_plugins() from quodlibet import session session_client = session.init(app) quodlibet.enable_periodic_save(save_library=False) quodlibet.run(app.window) quodlibet.finish_first_session("exfalso") config.save() session_client.close() util.print_d("Finished shutdown.")
def test_sanitize(self): q = AudioFile(quux) b = AudioFile(bar_1_1) q.sanitize() b.pop('~filename') self.failUnlessRaises(ValueError, b.sanitize) n = AudioFile({"artist": u"foo\0bar", "title": u"baz\0", "~filename": fsnative(u"whatever")}) n.sanitize() self.failUnlessEqual(n["artist"], "foo\nbar") self.failUnlessEqual(n["title"], "baz")
def setUp(self): config.init() self.s1 = AudioFile( {"album": u"I Hate: Tests", "artist": u"piman", "title": u"Quuxly", "version": u"cake mix", "~filename": fsnative(u"/dir1/foobar.ogg"), "~#length": 224, "~#skipcount": 13, "~#playcount": 24, "date": u"2007-05-24"}) self.s2 = AudioFile( {"album": u"Foo the Bar", "artist": u"mu", "title": u"Rockin' Out", "~filename": fsnative(u"/dir2/something.mp3"), "tracknumber": u"12/15"}) self.s3 = AudioFile({ "artist": u"piman\nmu", "~filename": fsnative(u"/test/\xf6\xe4\xfc/fo\xfc.ogg"), "~mountpoint": fsnative(u"/bla/\xf6\xe4\xfc/fo\xfc"), }) self.s4 = AudioFile({"title": u"Ångström", "utf8": u"Ångström"}) self.s5 = AudioFile({"title": u"oh&blahhh", "artist": u"!ohno"})
class TSongProperties(TestCase): af1 = AudioFile({"title": "woo"}) af1.sanitize(fsnative(u"invalid")) af2 = AudioFile({"title": "bar", "album": "quux"}) af2.sanitize(fsnative(u"alsoinvalid")) def setUp(self): SongProperties.plugins = DummyPlugins() config.init() self.library = SongLibrary() def test_onesong(self): self.window = SongProperties(self.library, [self.af1]) def test_twosong(self): self.window = SongProperties(self.library, [self.af2, self.af1]) def test_changed(self): self.test_twosong() self.window.hide() self.library.emit('changed', [self.af2]) while Gtk.events_pending(): Gtk.main_iteration() def test_removed(self): self.test_twosong() self.window.hide() self.library.emit('removed', [self.af2]) while Gtk.events_pending(): Gtk.main_iteration() def tearDown(self): try: self.window.destroy() except AttributeError: pass else: del (self.window) self.library.destroy() del (SongProperties.plugins) config.quit()
def test_list(self): for key in bar_1_1.realkeys(): self.failUnlessEqual(bar_1_1.list(key), [bar_1_1(key)]) af = AudioFile({"~filename": fsnative(u"foo")}) self.failUnlessEqual(af.list("artist"), []) self.failUnlessEqual(af.list("title"), [af("title")]) self.failUnlessEqual(af.list("not a key"), []) self.failUnlessEqual(len(bar_2_1.list("artist")), 2) self.failUnlessEqual(bar_2_1.list("artist"), bar_2_1["artist"].split("\n"))
def test_write(self): with self.wrap("playlist") as pl: pl.extend(NUMERIC_SONGS) pl.extend([fsnative(u"xf0xf0")]) pl.write() assert exists(pl.path), "File doesn't exist" root = ElementTree().parse(pl.path) assert root.tag == 'playlist' tracks = root.findall(".//track") assert len(tracks) == 4, "Hmm found %s" % tracks assert tracks[-1].find('location').text == "xf0xf0"
def getline(key, value): song = AudioFile({"~filename": fsnative(u"/dev/null")}) song.sanitize() song[key] = value lines = format_tags(song).splitlines() if not lines: return "" if len(lines) == 1: return lines[0] # hackery since title defaults to the filename.. for l in lines: if not l.startswith("Title"): return l
def get_temp_cover_file(data): """Returns a file object or None""" try: # pass fsnative so that mkstemp() uses unicode on Windows fn = NamedTemporaryFile(prefix=fsnative(u"tmp")) fn.write(data) fn.flush() fn.seek(0, 0) except EnvironmentError: return else: return fn
def test_fsn2text(): assert fsn2text(fsnative(u"foo")) == u"foo" with pytest.raises(TypeError): fsn2text(object()) with pytest.raises(TypeError): fsn2text(notfsnative(u"foo")) for path in iternotfsn(): with pytest.raises(TypeError): fsn2text(path) if os.name == "nt": assert fsn2text(u"\uD800\uDC01") == u"\U00010001"
def test_tag_strs(self): for t in format_types: i = AudioFile.__new__(t) i["~filename"] = fsnative(u"foo") for tag in TAGS.values(): name = tag.name # brute force variants = [ name, "~" + name, name + "sort", "~" + name + "sort", name + ":role", "~" + name + ":role", "~" + name + "sort:role", name + "sort:role", ] for name in variants: if name in FILESYSTEM_TAGS: assert isinstance(i(name, fsnative()), fsnative) else: assert isinstance(i(name), str)
def go_to(self, path_to_go): assert isinstance(path_to_go, fsnative) # The path should be normalized in normal situations. # On some systems and special environments (pipenv) there might be # a non-normalized path at least during tests, though. path_to_go = os.path.normpath(path_to_go) model = self.get_model() # Find the top level row which has the largest common # path with the path we want to go to roots = dict([(p, i) for (i, p) in model.iterrows(None)]) head, tail = path_to_go, fsnative(u"") to_find = [] while head and head not in roots: new_head, tail = os.path.split(head) # this can happen for invalid paths on Windows if head == new_head: break head = new_head to_find.append(tail) if head not in roots: return start_iter = roots[head] # expand until we find the right directory or the last valid one # and select/scroll to it def search(view, model, iter_, to_find): tree_path = model.get_path(iter_) # we are where we want, select and scroll if not to_find: view.set_cursor(tree_path) view.scroll_to_cell(tree_path) return # expand the row view.expand_row(tree_path, False) next_ = to_find.pop(-1) for sub_iter, path in model.iterrows(iter_): if os.path.basename(path) == next_: search(view, model, sub_iter, to_find) break else: # we haven't found the right sub folder, select the parent # and stop search(view, model, iter_, []) search(self, model, start_iter, to_find)
def test_fsnative(): assert isinstance(fsnative(u"foo"), fsnative) fsntype = type(fsnative(u"")) assert issubclass(fsntype, fsnative) with pytest.raises(TypeError): fsnative(b"") assert fsnative(u"\x00") == fsnative(u"\uFFFD") assert isinstance(fsnative(u"\x00"), fsnative) for inst in iternotfsn(): assert not isinstance(inst, fsnative)
def test_album_key(self): album_key_tests = [ ({}, ((), (), '')), ({'album': 'foo'}, (('foo',), (), '')), ({'labelid': 'foo'}, ((), (), 'foo')), ({'musicbrainz_albumid': 'foo'}, ((), (), 'foo')), ({'album': 'foo', 'labelid': 'bar'}, (('foo',), (), 'bar')), ({'album': 'foo', 'labelid': 'bar', 'musicbrainz_albumid': 'quux'}, (('foo',), (), 'bar')), ({'albumartist': 'a'}, ((), ('a',), '')), ] for tags, expected in album_key_tests: afile = AudioFile(**tags) afile.sanitize(fsnative(u'/dir/fn')) self.failUnlessEqual(afile.album_key, expected)
def __init__(self, uri: str, track_id: int, client, favorite: bool = False): # Don't call super, it invokes __getitem__ self["~uri"] = uri self.sanitize(fsnative(uri)) self.client = client if not self.client: raise EnvironmentError("Must have a Soundcloud client") self["soundcloud_track_id"] = track_id self.favorite = favorite if self.favorite: self['~#rating'] = 1.0