diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 53cd95667..70db77dc1 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -33,9 +33,12 @@ from beets import util # Constants. CONFIG_PATH_VAR = 'BEETSCONFIG' -DEFAULT_CONFIG_FILE = os.path.expanduser('~/.beetsconfig') -DEFAULT_LIBRARY = '~/.beetsmusic.blb' -DEFAULT_DIRECTORY = '~/Music' +DEFAULT_CONFIG_FILENAME_UNIX = '.beetsconfig' +DEFAULT_CONFIG_FILENAME_WINDOWS = 'beetsconfig.ini' +DEFAULT_LIBRARY_FILENAME_UNIX = '.beetsmusic.blb' +DEFAULT_LIBRARY_FILENAME_WINDOWS = 'beetsmusic.blb' +DEFAULT_DIRECTORY_NAME = 'Music' +WINDOWS_BASEDIR = os.environ.get('APPDATA') or '~' DEFAULT_PATH_FORMATS = { 'default': '$albumartist/$album/$track $title', 'comp': 'Compilations/$album/$track $title', @@ -373,6 +376,33 @@ def colordiff(a, b, highlight='red'): return u''.join(a_out), u''.join(b_out) +def default_paths(pathmod=None): + """Produces the appropriate default config, library, and directory + paths for the current system. On Unix, this is always in ~. On + Windows, tries ~ first and then $APPDATA for the config and library + files (for backwards compatibility). + """ + pathmod = pathmod or os.path + windows = pathmod.__name__ == 'ntpath' + if windows: + windata = os.environ.get('APPDATA') or '~' + + # Shorthand for joining paths. + def exp(*vals): + return pathmod.expanduser(pathmod.join(*vals)) + + config = exp('~', DEFAULT_CONFIG_FILENAME_UNIX) + if windows and not pathmod.exists(config): + config = exp(windata, DEFAULT_CONFIG_FILENAME_WINDOWS) + + libpath = exp('~', DEFAULT_LIBRARY_FILENAME_UNIX) + if windows and not pathmod.exists(libpath): + libpath = exp(windata, DEFAULT_LIBRARY_FILENAME_WINDOWS) + + libdir = exp('~', DEFAULT_DIRECTORY_NAME) + + return config, libpath, libdir + # Subcommand parsing infrastructure. @@ -542,6 +572,9 @@ def main(args=None, configfh=None): # Get the default subcommands. from beets.ui.commands import default_commands + # Get default file paths. + default_config, default_libpath, default_dir = default_paths() + # Read defaults from config file. config = ConfigParser.SafeConfigParser() if configfh: @@ -549,7 +582,7 @@ def main(args=None, configfh=None): elif CONFIG_PATH_VAR in os.environ: configpath = os.path.expanduser(os.environ[CONFIG_PATH_VAR]) else: - configpath = DEFAULT_CONFIG_FILE + configpath = default_config if configpath: configpath = util.syspath(configpath) if os.path.exists(util.syspath(configpath)): @@ -588,9 +621,9 @@ def main(args=None, configfh=None): # Open library file. libpath = options.libpath or \ - config_val(config, 'beets', 'library', DEFAULT_LIBRARY) + config_val(config, 'beets', 'library', default_libpath) directory = options.directory or \ - config_val(config, 'beets', 'directory', DEFAULT_DIRECTORY) + config_val(config, 'beets', 'directory', default_dir) legacy_path_format = config_val(config, 'beets', 'path_format', None) if options.path_format: # If given, -p overrides all path format settings @@ -621,6 +654,9 @@ def main(args=None, configfh=None): log.setLevel(logging.DEBUG) else: log.setLevel(logging.INFO) + log.debug(u'config file: %s' % configpath) + log.debug(u'library database: %s' % lib.path) + log.debug(u'library directory: %s' % lib.directory) # Invoke the subcommand. try: diff --git a/docs/changelog.rst b/docs/changelog.rst index eac4e8457..c0fd9ab8d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -25,6 +25,11 @@ Changelog levels in ReplayGain-aware music players. * Albums are now tagged with their *original* release date rather than the date of any reissue, remaster, "special edition", or the like. +* The config file and library databases are now given better names and locations + on Windows. Namely, both files now reside in ``%APPDATA%``; the config file is + named ``beetsconfig.ini`` and the database is called ``beetslibrary.blb`` + (neither has a leading dot as on Unix). For backwards compatibility, beets + will check the old locations first. * When entering an ID manually during tagging, beets now searches for anything that looks like an MBID in the entered string. This means that full MusicBrainz URLs now work as IDs at the prompt. (Thanks to derwin.) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index 2304f01b2..9fc868d17 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -85,9 +85,11 @@ trouble or you have more detail to contribute here, please `let me know`_. Configuring ----------- -You'll want to set a few basic options before you start using beets. To do this, -create and edit the file ``~/.beetsconfig`` with your favorite text editor. This -file will start out empty, but here's good place to start:: +You'll want to set a few basic options before you start using beets. The +configuration is stored in a text file: on Unix-like OSes, the config file is at +``~/.beetsconfig``; on Windows, it's at ``%APPDATA%\beetsconfig.ini``. Create +and edit the appropriate file with your favorite text editor. This file will +start out empty, but here's good place to start:: [beets] directory: ~/music diff --git a/docs/reference/config.rst b/docs/reference/config.rst index 0465267fa..e87931d41 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -1,7 +1,8 @@ .beetsconfig ============ -The ``beet`` command reads configuration information from ``~/.beetsconfig``. +The ``beet`` command reads configuration information from ``~/.beetsconfig`` on +Unix-like OSes (inluding Mac OS X) and ``%APPDATA%\beetsconfig.ini`` on Windows. The file is in INI format. Options @@ -11,7 +12,8 @@ These options are available, all of which must appear under the ``[beets]`` section header: ``library`` - Path to the beets library file. Defaults to ``~/.beetsmusic.blb``. + Path to the beets library file. Defaults to ``~/.beetsmusic.blb`` on Unix + and ``%APPDATA\beetsmusic.blb`` on Windows. ``directory`` The directory to which files will be copied/moved when adding them to the diff --git a/test/test_ui.py b/test/test_ui.py index 17d1b65f0..80b5bce56 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -668,6 +668,36 @@ class ShowChangeTest(unittest.TestCase): msg = self.io.getoutput().lower() self.assertTrue(u'caf\xe9.mp3 -> the title' in msg.decode('utf8')) +class DefaultPathTest(unittest.TestCase): + def setUp(self): + self.old_home = os.environ.get('HOME') + self.old_appdata = os.environ.get('APPDATA') + os.environ['HOME'] = 'xhome' + os.environ['APPDATA'] = 'xappdata' + def tearDown(self): + if self.old_home is None: + del os.environ['HOME'] + else: + os.environ['HOME'] = self.old_home + if self.old_appdata is None: + del os.environ['APPDATA'] + else: + os.environ['APPDATA'] = self.old_appdata + + def test_unix_paths_in_home(self): + import posixpath + config, lib, libdir = ui.default_paths(posixpath) + self.assertEqual(config, 'xhome/.beetsconfig') + self.assertEqual(lib, 'xhome/.beetsmusic.blb') + self.assertEqual(libdir, 'xhome/Music') + + def test_windows_paths_in_home_and_appdata(self): + import ntpath + config, lib, libdir = ui.default_paths(ntpath) + self.assertEqual(config, 'xappdata\\beetsconfig.ini') + self.assertEqual(lib, 'xappdata\\beetsmusic.blb') + self.assertEqual(libdir, 'xhome\\Music') + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)