Add attach-import command and detect globs

This commit is contained in:
Thomas Scholtes 2014-08-18 17:49:01 +02:00
parent c463c6ed84
commit f98d8619ae
4 changed files with 86 additions and 33 deletions

View file

@ -18,6 +18,7 @@ import os.path
import collections
import logging
from argparse import ArgumentParser
from fnmatch import fnmatch
from beets import dbcore
from beets.dbcore.query import Query, AndQuery, MatchQuery, OrQuery, FalseQuery
@ -457,7 +458,7 @@ class AttachmentFactory(object):
return self.find(queries[Attachment], queries[Album], queries[Item])
def discover(self, entity_or_prefix, local=None):
"""Return a list of non-audio files whose path start with the
"""Return a list of non-audio file paths that start with the
entity prefix.
For albums the entity prefix is the album directory. For items it
@ -469,6 +470,9 @@ class AttachmentFactory(object):
type. For albums the only separator is the directory separator.
For items the separtors are configured by `attachments.item_sep`
"""
# TODO return attachments with create()
# FIXME we need to handle paths as `entity_prefix` because of
# the importer.
prefix, dir = self.path_prefix(entity_or_prefix)
if local is None:
return self._discover_full(prefix, dir)
@ -507,7 +511,11 @@ class AttachmentFactory(object):
a list of types for `path`. For each type it yields an attachment
through `create`.
"""
for type in self._detect_types(path):
# TODO entity should not be optional
# TODO update doc
types = self._detect_plugin_types(path)
types.update(self._detect_config_types(path))
for type in types:
yield self.create(path, type, entity)
@classmethod
@ -599,27 +607,33 @@ class AttachmentFactory(object):
if hasattr(plugin, 'attachment_collector'):
self.register_collector(plugin.attachment_collector)
def _detect_types(self, path):
"""Yield a list of types registered for the path.
Uses the functions from `register_detector` and the
`attachments.types` configuration.
"""
def _detect_plugin_types(self, path):
types = set()
# TODO Make list unique
for detector in self._detectors:
try:
type = detector(path)
if type:
yield type
types.add(type)
except:
# TODO logging?
pass
return types
def _detect_config_types(self, path):
types = set()
types_config = config('types')
if types_config.exists():
for matcher, type in types_config.get(dict).items():
if re.match(matcher, path):
yield type
if not types_config.exists():
return types
basename = os.path.basename(path)
for pattern, type in types_config.get(dict).items():
if ((pattern[0] == '/' and pattern[-1] == '/'
and re.match(pattern[1:-1] + '$', basename))
or fnmatch(basename, pattern)):
types.add(type)
return types
def _collect_meta(self, type, path):
all_meta = {}

View file

@ -80,8 +80,6 @@ def _do_query(lib, query, album, also_items=True):
class AttachCommand(ui.Subcommand):
"""Duck type for ui.Subcommand
"""
def __init__(self):
super(AttachCommand, self).__init__(
@ -172,12 +170,37 @@ class AttachListCommand(ui.Subcommand):
args = decargs(args)
factory = AttachmentFactory(lib)
for a in factory.parse_and_find(*args):
print('{0}: {1}'.format(a.type, displayable_path(a.path)))
print(u'{0}: {1}'.format(a.type, displayable_path(a.path)))
default_commands.append(AttachListCommand())
class AttachImportCommand(ui.Subcommand):
"""Search files in album directories and create attachments for them.
"""
def __init__(self):
super(AttachImportCommand, self).__init__(
'attach-import',
help='create attachments for albums already in the library'
)
def func(self, lib, opts, args):
args = decargs(args)
factory = AttachmentFactory(lib)
for album in lib.albums(decargs(args)):
for path in factory.discover(album):
for attachment in factory.detect(path, album):
print(u"add {0} attachment {1} to '{2} - {3}'"
.format(attachment.type, path,
album.albumartist, album.album))
attachment.add()
default_commands.append(AttachImportCommand())
# fields: Shows a list of available fields for queries and format strings.
def fields_func(lib, opts, args):

View file

@ -81,13 +81,13 @@ def capture_stdout():
'spam'
"""
org = sys.stdout
sys.stdout = StringIO()
sys.stdout = captured = StringIO()
sys.stdout.encoding = 'utf8'
try:
yield sys.stdout
yield captured
finally:
sys.stdout = org
print(org.getvalue())
print(captured.getvalue())
@contextmanager

View file

@ -197,7 +197,24 @@ class AttachmentDocTest(unittest.TestCase, AttachmentTestHelper):
self.assertIn("add booklet attachment {0} to 'Artist - Album 0'"
.format(booklet_path), output)
# TODO attach-import
def test_attach_import(self):
self.config['attachments']['types'] = {'cover.jpg': 'cover'}
album1 = self.add_album(name='Revolver', artist='The Beatles')
album2 = self.add_album(name='Abbey Road', artist='The Beatles')
cover_path1 = self.touch(os.path.join(album1.item_dir(), 'cover.jpg'))
cover_path2 = self.touch(os.path.join(album2.item_dir(), 'cover.jpg'))
output = self.cli_output('attach-import')
self.assertIn("add cover attachment {0} to 'The Beatles - Revolver'"
.format(cover_path1), output)
self.assertIn("add cover attachment {0} to 'The Beatles - Abbey Road'"
.format(cover_path2), output)
album1.load()
self.assertEqual(len(album1.attachments()), 1)
album2.load()
self.assertEqual(len(album2.attachments()), 1)
class AttachmentDestinationTest(unittest.TestCase, AttachmentTestHelper):
@ -484,13 +501,10 @@ class AttachmentFactoryTest(unittest.TestCase, AttachmentTestHelper):
self.assertEqual(len(attachments), 1)
self.assertEqual(attachments[0].type, 'image')
# TODO Glob and RegExp.
# * Globs dont match files starting with a dot
# * Add extended bash globs.
# * Regexp must match full basename
def test_detect_config_types(self):
# TODO add extended bash globs
def test_detect_config_glob_types(self):
self.config['attachments']['types'] = {
'.*\.jpg': 'image'
'*.jpg': 'image'
}
attachments = list(self.factory.detect('/path/to/cover.jpg'))
@ -500,15 +514,17 @@ class AttachmentFactoryTest(unittest.TestCase, AttachmentTestHelper):
attachments = list(self.factory.detect('/path/to/cover.png'))
self.assertEqual(len(attachments), 0)
def test_detect_multiple_types(self):
self.factory.register_detector(lambda _: 'a')
self.factory.register_detector(lambda _: 'b')
def test_detect_config_regexp_types(self):
self.config['attachments']['types'] = {
'.*\.jpg$': 'c',
'.*/cover.jpg': 'd'
'/[abc]+.*\.(txt|md)/': 'xxx'
}
attachments = list(self.factory.detect('/path/to/cover.jpg'))
self.assertItemsEqual(map(lambda a: a.type, attachments), 'abcd')
attachments = list(self.factory.detect('/path/to/aabbcc.md'))
self.assertEqual(len(attachments), 1)
self.assertEqual(attachments[0].type, 'xxx')
attachments = list(self.factory.detect('/path/to/aabbcc.mdx'))
self.assertEqual(len(attachments), 0)
# factory.discover(album)