diff --git a/beets/library.py b/beets/library.py index 25f019040c..823f10b9e6 100644 --- a/beets/library.py +++ b/beets/library.py @@ -766,8 +766,10 @@ class BaseAlbum(object): album-level metadata or use distinct backing stores. """ def __init__(self, library, record): - self._library = library - self._record = record + # Need to use object.__setattr__ here, since we're overriding + # it for this class + object.__setattr__(self, '_library', library) + object.__setattr__(self, '_record', record) def __getattr__(self, key): """Get the value for an album attribute.""" @@ -1087,10 +1089,16 @@ def add_album(self, items): """ # Set the metadata from the first item. #fixme: check for consensus? + item_values = dict( + (key, getattr(items[0], key)) for key in ALBUM_KEYS_ITEM) + if not item_values['albumartist']: + item_values['albumartist'] = getattr(items[0], 'artist') + + sql = 'INSERT INTO albums (%s) VALUES (%s)' % \ (', '.join(ALBUM_KEYS_ITEM), ', '.join(['?'] * len(ALBUM_KEYS_ITEM))) - subvals = [getattr(items[0], key) for key in ALBUM_KEYS_ITEM] + subvals = [item_values[key] for key in ALBUM_KEYS_ITEM] c = self.conn.execute(sql, subvals) album_id = c.lastrowid diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 7b3af442c9..5add78075e 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -466,7 +466,7 @@ def apply_choices(lib, copy, write, art, delete): if info is not CHOICE_ASIS: autotag.apply_metadata(items, info) if copy and delete: - old_paths = [item.path for item in items] + old_paths = [os.path.realpath(item.path) for item in items] for item in items: if copy: item.move(lib, True) @@ -488,8 +488,11 @@ def apply_choices(lib, copy, write, art, delete): # Finally, delete old files. if copy and delete: + new_paths = [os.path.realpath(item.path) for item in items] for old_path in old_paths: - os.remove(library._syspath(old_path)) + if old_path not in new_paths: + os.remove(library._syspath(old_path)) + os.remove(library._syspath(old_path)) # Update progress. progress_set(toppath, path) @@ -506,7 +509,7 @@ def simple_import(lib, paths, copy, delete): if copy: if delete: - old_paths = [item.path for item in items] + old_paths = [os.path.realpath(item.path) for item in items] for item in items: item.move(lib, True) @@ -515,10 +518,14 @@ def simple_import(lib, paths, copy, delete): progress_set(toppath, path) if copy and delete: + new_paths = [os.path.realpath(item.path) for item in items] for old_path in old_paths: - os.remove(library._syspath(old_path)) + # Only delete the path if it isn't a file we just created. + if old_path not in new_paths: + os.remove(library._syspath(old_path)) + - log.info('added album: %s - %s' % (album.artist, album.album)) + log.info('added album: %s - %s' % (album.albumartist, album.album)) # The import command. diff --git a/test/test_library.py b/test/test_library.py new file mode 100644 index 0000000000..9b1243db79 --- /dev/null +++ b/test/test_library.py @@ -0,0 +1,45 @@ +# This file is part of beets. +# Copyright 2010, 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. + +"""Basic tests for the objects that are used to represent the beets +library and the items in it. +""" + +import unittest +import sys +sys.path.append('..') +from beets.library import BaseLibrary, BaseAlbum + +class AlbumTest(unittest.TestCase): + + def test_field_access(self): + album = BaseAlbum(None, {'artist':'foo', 'albumartist':'bar'}) + self.assertEqual(album.artist, 'foo') + self.assertEqual(album.albumartist, 'bar') + + def test_field_access_unset_values(self): + """ + This is how things work currently. Trying to access unset album + metadata raises an AttributeError. + """ + album = BaseAlbum(None, {}) + self.assertRaises(AttributeError, getattr, album, 'albumartist') + self.assertRaises(AttributeError, getattr, album, 'artist') + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == '__main__': + unittest.main(defaultTest='suite') + diff --git a/test/test_ui.py b/test/test_ui.py index da94f5c689..8cce37f16c 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -18,6 +18,7 @@ import unittest import sys import os +import shutil import textwrap from StringIO import StringIO import _common @@ -26,8 +27,86 @@ from beets import ui from beets.ui import commands from beets import autotag +from beets import mediafile import test_db +class ImportTest(unittest.TestCase): + def setUp(self): + self.io = _common.DummyIO() + self.io.install() + + self.lib = library.Library(':memory:') + self.libdir = os.path.join('rsrc', 'testlibdir') + self.lib.directory = self.libdir + self.lib.path_formats = {'default': os.path.join('$artist', '$album', '$title')} + + self.srcdir = os.path.join('rsrc', 'testsrcdir') + + def tearDown(self): + self.io.restore() + if os.path.exists(self.libdir): + shutil.rmtree(self.libdir) + if os.path.exists(self.srcdir): + shutil.rmtree(self.srcdir) + + def create_test_file(self, filepath, metadata): + """ + Creates an mp3 file at the given path within self.srcdir. filepath is + given as an array of folder names, ending with the file name. Sets the + file's metadata from the provided dict. Returns the full, real path to + the file. + """ + realpath = os.path.join(self.srcdir, *filepath) + if not os.path.exists(os.path.dirname(realpath)): + os.makedirs(os.path.dirname(realpath)) + realpath = os.path.join(self.srcdir, *filepath) + shutil.copy(os.path.join('rsrc', 'full.mp3'), realpath) + f = mediafile.MediaFile(realpath) + for attr in metadata: + setattr(f, attr, metadata[attr]) + f.save() + return realpath + + def test_import_copy_arrives(self): + track_names = ['The Opener', 'The Second Track', 'The Last Track'] + + for i, title in enumerate(track_names): + path = self.create_test_file(['the_album', 'track_%s.mp3' % (i+1)], { + 'track': (i+1), + 'artist': 'The Artist', + 'album': 'The Album', + 'title': title}) + + sources = [os.path.dirname(path)] + + commands.import_files( + lib=self.lib, + paths=sources, + copy=True, + write=True, + autot=False, + logpath=None, + art=False, + threaded=False, + color=False, + delete=False, + quiet=True) + + albums = self.lib.albums() + self.assertEqual(len(albums), 1) + self.assertEqual(albums[0].albumartist, 'The Artist') + + artist_folder = os.path.join(self.libdir, 'The Artist') + album_folder = os.path.join(artist_folder, 'The Album') + self.assertEqual(len(os.listdir(artist_folder)), 1) + self.assertEqual(len(os.listdir(album_folder)), 3) + + files = sorted(os.listdir(album_folder)) + names = sorted(track_names) + for file, name in zip(files, names): + self.assertEqual(file, name + ".mp3") + + class ListTest(unittest.TestCase): def setUp(self): self.io = _common.DummyIO()