diff --git a/beets/library.py b/beets/library.py index 9b48425bc..51b9d1a66 100644 --- a/beets/library.py +++ b/beets/library.py @@ -757,10 +757,12 @@ class Library(BaseLibrary): self.conn.executescript(setup_sql) self.conn.commit() - def destination(self, item, pathmod=None, in_album=False): + def destination(self, item, pathmod=None, in_album=False, noroot=False): """Returns the path in the library directory designated for item item (i.e., where the file ought to be). in_album forces the - item to be treated as part of an album. + item to be treated as part of an album. noroot makes this + method return just the path fragment underneath the root library + directory. """ pathmod = pathmod or os.path @@ -817,7 +819,10 @@ class Library(BaseLibrary): _, extension = pathmod.splitext(item.path) subpath += extension - return normpath(os.path.join(self.directory, subpath)) + if noroot: + return subpath + else: + return normpath(os.path.join(self.directory, subpath)) # Main interface. diff --git a/beets/vfs.py b/beets/vfs.py new file mode 100644 index 000000000..6bae80b20 --- /dev/null +++ b/beets/vfs.py @@ -0,0 +1,48 @@ +# This file is part of beets. +# Copyright 2011, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""A simple utility for constructing filesystem-like trees from beets +libraries. +""" +from collections import namedtuple +from beets import util + +Node = namedtuple('Node', ['files', 'dirs']) + +def _insert(node, path, itemid): + """Insert an item into a virtual filesystem node.""" + if len(path) == 1: + # Last component. Insert file. + node.files[path[0]] = itemid + else: + # In a directory. + dirname = path[0] + rest = path[1:] + if dirname not in node.dirs: + node.dirs[dirname] = Node({}, {}) + _insert(node.dirs[dirname], rest, itemid) + +def libtree(lib): + """Generates a filesystem-like directory tree for the files + contained in `lib`. Filesystem nodes are (files, dirs) named + tuples in which both components are dictionaries. The first + maps filenames to Item ids. The second maps directory names to + child node tuples. + """ + root = Node({}, {}) + for item in lib.items(): + dest = lib.destination(item, noroot=True) + parts = util.components(dest) + _insert(root, parts, item.id) + return root diff --git a/test/test_vfs.py b/test/test_vfs.py new file mode 100644 index 000000000..51fe8231b --- /dev/null +++ b/test/test_vfs.py @@ -0,0 +1,45 @@ +# This file is part of beets. +# Copyright 2011, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Tests for the virtual filesystem builder..""" +import unittest + +import _common +from beets import library +from beets import vfs + +class VFSTest(unittest.TestCase): + def setUp(self): + self.lib = library.Library(':memory:', path_formats={ + 'default': 'albums/$album/$title', + 'singleton': 'tracks/$artist/$title', + }) + self.lib.add(_common.item()) + self.lib.add_album([_common.item()]) + self.lib.save() + self.tree = vfs.libtree(self.lib) + + def test_singleton_item(self): + self.assertEqual(self.tree.dirs['tracks'].dirs['the artist']. + files['the title'], 1) + + def test_album_item(self): + self.assertEqual(self.tree.dirs['albums'].dirs['the album']. + files['the title'], 2) + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == '__main__': + unittest.main(defaultTest='suite')