diff --git a/beets/library.py b/beets/library.py index 043043927..d81757e20 100644 --- a/beets/library.py +++ b/beets/library.py @@ -62,6 +62,14 @@ ITEM_KEYS_WRITABLE = [f[0] for f in ITEM_FIELDS if f[3] and f[2]] ITEM_KEYS_META = [f[0] for f in ITEM_FIELDS if f[3]] ITEM_KEYS = [f[0] for f in ITEM_FIELDS] +# Database fields for the "albums" table. +ALBUM_FIELDS = [ + ('id', 'integer primary key'), + ('artist', 'text'), + ('album', 'text'), + ('artpath', 'text'), +] + # Default search fields for various granularities. ARTIST_DEFAULT_FIELDS = ('artist',) ALBUM_DEFAULT_FIELDS = ARTIST_DEFAULT_FIELDS + ('album', 'genre') @@ -644,7 +652,8 @@ class Library(BaseLibrary): def __init__(self, path='library.blb', directory='~/Music', path_format='$artist/$album/$track $title', - fields=ITEM_FIELDS): + item_fields=ITEM_FIELDS, + album_fields=ALBUM_FIELDS): self.path = path self.directory = directory self.path_format = path_format @@ -653,16 +662,17 @@ class Library(BaseLibrary): self.conn.row_factory = sqlite3.Row # this way we can access our SELECT results like dictionaries - self._setup(fields) + self._make_table('items', item_fields) + self._make_table('albums', album_fields) - def _setup(self, fields): - """Set up the schema of the library file. fields is a list - of all the fields that should be present in the table. Columns - are added if necessary. + def _make_table(self, table, fields): + """Set up the schema of the library file. fields is a list of + all the fields that should be present in the indicated table. + Columns are added if necessary. """ # Get current schema. cur = self.conn.cursor() - cur.execute('PRAGMA table_info(items)') + cur.execute('PRAGMA table_info(%s)' % table) current_fields = set([row[1] for row in cur]) field_names = set([f[0] for f in fields]) @@ -672,7 +682,7 @@ class Library(BaseLibrary): if not current_fields: # No table exists. - setup_sql = 'CREATE TABLE items (' + setup_sql = 'CREATE TABLE %s (' % table setup_sql += ', '.join(['%s %s' % f[:2] for f in fields]) setup_sql += ');' @@ -685,8 +695,8 @@ class Library(BaseLibrary): break else: assert False - setup_sql += 'ALTER TABLE items ADD COLUMN ' \ - '%s %s;\n' % field[:2] + setup_sql += 'ALTER TABLE %s ' % table + setup_sql += 'ADD COLUMN %s %s;\n' % field[:2] self.conn.executescript(setup_sql) self.conn.commit() diff --git a/test/rsrc/test.blb b/test/rsrc/test.blb index d3dd76ba8..bdbf362bc 100644 Binary files a/test/rsrc/test.blb and b/test/rsrc/test.blb differ diff --git a/test/test_db.py b/test/test_db.py index 7ad3a0e68..8ddc54a26 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -15,7 +15,10 @@ """Tests for non-query database functions of Item. """ -import unittest, sys, os +import unittest +import sys +import os +import sqlite3 sys.path.append('..') import beets.library @@ -257,7 +260,8 @@ class MigrationTest(unittest.TestCase): # Set up a library with old_fields. self.libfile = os.path.join('rsrc', 'templib.blb') - old_lib = beets.library.Library(self.libfile, fields=self.old_fields) + old_lib = beets.library.Library(self.libfile, + item_fields=self.old_fields) # Add an item to the old library. old_lib.conn.execute( 'insert into items (field_one, field_two) values (4, 2)' @@ -269,36 +273,57 @@ class MigrationTest(unittest.TestCase): os.unlink(self.libfile) def test_open_with_same_fields_leaves_untouched(self): - new_lib = beets.library.Library(self.libfile, fields=self.old_fields) + new_lib = beets.library.Library(self.libfile, + item_fields=self.old_fields) c = new_lib.conn.cursor() c.execute("select * from items") row = c.fetchone() self.assertEqual(len(row), len(self.old_fields)) def test_open_with_new_field_adds_column(self): - new_lib = beets.library.Library(self.libfile, fields=self.new_fields) + new_lib = beets.library.Library(self.libfile, + item_fields=self.new_fields) c = new_lib.conn.cursor() c.execute("select * from items") row = c.fetchone() self.assertEqual(len(row), len(self.new_fields)) def test_open_with_fewer_fields_leaves_untouched(self): - new_lib = beets.library.Library(self.libfile, fields=self.older_fields) + new_lib = beets.library.Library(self.libfile, + item_fields=self.older_fields) c = new_lib.conn.cursor() c.execute("select * from items") row = c.fetchone() self.assertEqual(len(row), len(self.old_fields)) def test_open_with_multiple_new_fields(self): - new_lib = beets.library.Library(self.libfile, fields=self.newer_fields) + new_lib = beets.library.Library(self.libfile, + item_fields=self.newer_fields) c = new_lib.conn.cursor() c.execute("select * from items") row = c.fetchone() self.assertEqual(len(row), len(self.newer_fields)) - + + def test_open_old_db_adds_album_table(self): + conn = sqlite3.connect(self.libfile) + conn.execute('drop table albums') + conn.close() + + conn = sqlite3.connect(self.libfile) + self.assertRaises(sqlite3.OperationalError, conn.execute, + 'select * from albums') + conn.close() + + new_lib = beets.library.Library(self.libfile, + item_fields=self.newer_fields) + try: + new_lib.conn.execute("select * from albums") + except sqlite3.OperationalError: + self.fail("select failed") def suite(): return unittest.TestLoader().loadTestsFromName(__name__) if __name__ == '__main__': unittest.main(defaultTest='suite') +