Merge branch 'develop' into feature/taglist-converter
Conflicts: docs/changelog.rst
This commit is contained in:
commit
656f7e976f
2
.mailmap
2
.mailmap
@ -16,4 +16,4 @@ Janez Troha <janez.troha@gmail.com> <dz0ny@ubuntu.si>
|
||||
Luke Giuliani <luke@giuliani.com.au>
|
||||
Colin Montgomerie <kiteflyingmonkey@gmail.com>
|
||||
Ignasi Fosch <natx@y10k.ws> <ifosch@serenity-2.local>
|
||||
Christopher Schirner <schinken@hackerspace-bamberg.de>
|
||||
Christopher Schirner <christopher@hackerspace-bamberg.de> <schinken@hackerspace-bamberg.de>
|
||||
|
||||
3
AUTHORS
3
AUTHORS
@ -42,7 +42,8 @@
|
||||
- Sam Willcocks <sam@wlcx.cc>
|
||||
- Ignasi Fosch <natx@y10k.ws>
|
||||
- Arjun Naik <arjun@arjunnaik.in>
|
||||
- Christopher Schirner <schinken@hackerspace-bamberg.de>
|
||||
- Christopher Schirner <christopher@hackerspace-bamberg.de>
|
||||
- Dmitry Sandalov <dmitry@sandalov.org>
|
||||
- Lukas Vogel <lukas@vogelnest.org>
|
||||
- Thomas Amland <thomas.amland@gmail.com>
|
||||
- Deni Bertovic <deni@kset.org>
|
||||
|
||||
@ -39,8 +39,6 @@ v0.20.0 (UNRELEASED)
|
||||
- Add symlink support with loop protection to file finder (Fixes: :issue:`858`,
|
||||
PR: :isusue:`874`)
|
||||
|
||||
- Fix scanning of modified files. (PR: :issue:`904`)
|
||||
|
||||
**MPD frontend**
|
||||
|
||||
- In stored playlist names, replace "/", which are illegal, with "|" instead of
|
||||
@ -49,15 +47,11 @@ v0.20.0 (UNRELEASED)
|
||||
- Enable browsing of artist references, in addition to albums and playlists.
|
||||
(PR: :issue:`884`)
|
||||
|
||||
- Re-enable browsing of empty directories. (PR: :issue:`906`)
|
||||
|
||||
- Quick workaround for :issue:`881`, which allows for newlines in comments.
|
||||
(PR: :issue:`882`)
|
||||
|
||||
**Audio**
|
||||
|
||||
- Deprecated :meth:`mopidy.audio.Audio.emit_end_of_stream`. Pass a
|
||||
:class:`None` buffer to :meth:`mopidy.audio.Audio.emit_data` end the stream.
|
||||
:class:`None` buffer to :meth:`mopidy.audio.Audio.emit_data` to end the
|
||||
stream.
|
||||
|
||||
- Internal code cleanup within audio subsystem:
|
||||
|
||||
@ -94,16 +88,26 @@ v0.20.0 (UNRELEASED)
|
||||
make sense for a server such as Mopidy. Currently the only way to find out if
|
||||
it is in use and will be missed is to go ahead and remove it.
|
||||
|
||||
|
||||
**Stream backend**
|
||||
|
||||
- Add basic tests for the stream library provider.
|
||||
|
||||
|
||||
v0.19.5 (UNRELEASED)
|
||||
v0.19.5 (2014-12-23)
|
||||
====================
|
||||
|
||||
Bug fix release.
|
||||
Today is Mopidy's five year anniversary. We're celebrating with a bugfix
|
||||
release and are looking forward to the next five years!
|
||||
|
||||
- Config: Support UTF-8 in extension's default config. If an extension with
|
||||
non-ASCII characters in its default config was installed, and Mopidy didn't
|
||||
already have a config file, Mopidy would crashed when trying to create the
|
||||
initial config file based on the default config of all available extensions.
|
||||
(Fixes: :discuss:`428`)
|
||||
|
||||
- Extensions: Fix crash when unpacking data from
|
||||
:exc:`pkg_resources.VersionConflict` created with a single argument. (Fixes:
|
||||
:issue:`911`)
|
||||
|
||||
- Models: Hide empty collections from :func:`repr()` representations.
|
||||
|
||||
@ -117,6 +121,20 @@ Bug fix release.
|
||||
:attr:`mopidy.models.Track.track_no`, and
|
||||
:attr:`mopidy.models.Track.last_modified` from ``0`` to :class:`None`.
|
||||
|
||||
- Core: When skipping to the next track in consume mode, remove the skipped
|
||||
track from the tracklist. This is consistent with the original MPD server's
|
||||
behavior. (Fixes: :issue:`902`)
|
||||
|
||||
- Local: Fix scanning of modified files. (PR: :issue:`904`)
|
||||
|
||||
- MPD: Re-enable browsing of empty directories. (PR: :issue:`906`)
|
||||
|
||||
- MPD: Remove track comments from responses. They are not included by the
|
||||
original MPD server, and this works around :issue:`881`. (PR: :issue:`882`)
|
||||
|
||||
- HTTP: Errors while starting HTTP apps are logged instead of crashing the HTTP
|
||||
server. (Fixes: :issue:`875`)
|
||||
|
||||
|
||||
v0.19.4 (2014-09-01)
|
||||
====================
|
||||
|
||||
@ -18,7 +18,7 @@ DLNA Digital Media Server (DMS) / UPnP AV MediaServer:
|
||||
A MediaServer provides a library of media and is capable of streaming that
|
||||
media to a MediaRenderer. If Mopidy was a MediaServer, you could browse and
|
||||
play Mopidy's music on a TV, smartphone, or tablet supporting UPnP. Mopidy
|
||||
does not currently support this, but we may in the future. :issue:`52` is
|
||||
does not currently support this, but we may in the future. :issue:`52` is
|
||||
the relevant wishlist issue.
|
||||
|
||||
DLNA Digital Media Renderer (DMR) / UPnP AV MediaRenderer:
|
||||
@ -27,13 +27,51 @@ DLNA Digital Media Renderer (DMR) / UPnP AV MediaRenderer:
|
||||
given media, typically served by a MediaServer. If Mopidy was a
|
||||
MediaRenderer, you could use e.g. your smartphone or tablet to make Mopidy
|
||||
play media. Mopidy *does already* have experimental support for being a
|
||||
MediaRenderer with the help of Rygel, as you can read more about below.
|
||||
MediaRenderer, as you can read more about below.
|
||||
|
||||
|
||||
Mopidy as an UPnP MediaRenderer
|
||||
===============================
|
||||
|
||||
There are two ways Mopidy can be made available as an UPnP MediaRenderer:
|
||||
Using Mopidy-MPRIS and Rygel, or using Mopidy-MPD and upmpdcli.
|
||||
|
||||
|
||||
.. _upmpdcli:
|
||||
|
||||
upmpdcli
|
||||
--------
|
||||
|
||||
`upmpdcli <http://www.lesbonscomptes.com/upmpdcli/>`_ is recommended, since it
|
||||
is easier to setup, and offers `OpenHome <http://www.openhome.org> ohMedia`_
|
||||
compatibility. upmpdcli exposes a UPnP MediaRenderer to the network, while
|
||||
using the MPD protocol to control Mopidy.
|
||||
|
||||
1. Install upmpdcli. On Debian/Ubuntu::
|
||||
|
||||
apt-get install upmpdcli
|
||||
|
||||
Alternatively, follow the instructions from the upmpdcli website.
|
||||
|
||||
2. The default settings of upmpdcli will work with the default settings of
|
||||
:ref:`ext-mpd`. Edit :file:`/etc/upmpdcli.conf` if you want to use different
|
||||
ports, hosts, or other settings.
|
||||
|
||||
3. Start upmpdcli using the command::
|
||||
|
||||
upmpdcli
|
||||
|
||||
Or, run it in the background as a service::
|
||||
|
||||
sudo service upmpdcli start
|
||||
|
||||
4. A UPnP renderer should be available now.
|
||||
|
||||
|
||||
.. _rygel:
|
||||
|
||||
How to make Mopidy available as an UPnP MediaRenderer
|
||||
=====================================================
|
||||
Rygel
|
||||
-----
|
||||
|
||||
With the help of `the Rygel project <https://live.gnome.org/Rygel>`_ Mopidy can
|
||||
be made available as an UPnP MediaRenderer. Rygel will interface with the MPRIS
|
||||
@ -41,15 +79,15 @@ interface provided by the `Mopidy-MPRIS extension
|
||||
<https://github.com/mopidy/mopidy-mpris>`_, and make Mopidy available as a
|
||||
MediaRenderer on the local network. Since this depends on the MPRIS frontend,
|
||||
which again depends on D-Bus being available, this will only work on Linux, and
|
||||
not OS X. MPRIS/D-Bus is only available to other applications on the same
|
||||
not OS X. MPRIS/D-Bus is only available to other applications on the same
|
||||
host, so Rygel must be running on the same machine as Mopidy.
|
||||
|
||||
1. Start Mopidy and make sure the MPRIS frontend is working. It is activated
|
||||
1. Start Mopidy and make sure the MPRIS frontend is working. It is activated
|
||||
by default when the Mopidy-MPRIS extension is installed, but you may miss
|
||||
dependencies or be using OS X, in which case it will not work. Check the
|
||||
console output when Mopidy is started for any errors related to the MPRIS
|
||||
frontend. If you're unsure it is working, there are instructions for how to
|
||||
test it on in the `Mopidy-MPRIS readme
|
||||
test it in the `Mopidy-MPRIS readme
|
||||
<https://github.com/mopidy/mopidy-mpris>`_.
|
||||
|
||||
2. Install Rygel. On Debian/Ubuntu::
|
||||
|
||||
@ -157,6 +157,7 @@ extlinks = {
|
||||
'commit': ('https://github.com/mopidy/mopidy/commit/%s', 'commit '),
|
||||
'mpris': (
|
||||
'https://github.com/mopidy/mopidy-mpris/issues/%s', 'mopidy-mpris#'),
|
||||
'discuss': ('https://discuss.mopidy.com/t/%s', 'discuss.mopidy.com/t/'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -74,13 +74,13 @@ you a lot better performance.
|
||||
#. Install Mopidy and its dependencies as described in :ref:`debian-install`.
|
||||
|
||||
#. Finally, you need to set a couple of :doc:`config values </config>`, and
|
||||
then you're ready to :doc:`run Mopidy </running>`. Alternatively you may
|
||||
then you're ready to :doc:`run Mopidy </running>`. Alternatively you may
|
||||
want to have Mopidy run as a :doc:`system service </debian>`, automatically
|
||||
starting at boot.
|
||||
|
||||
|
||||
Appendix: Fixing audio quality issues
|
||||
=====================================
|
||||
Appendix A: Fixing audio quality issues
|
||||
=======================================
|
||||
|
||||
As of about April 2013 the following steps should resolve any audio
|
||||
issues for HDMI and analog without the use of an external USB sound
|
||||
@ -141,3 +141,35 @@ not determined the exact revision that fixed this::
|
||||
The only remaining known issue is a slight gap in playback at track changes
|
||||
this is likely due to gapless playback not being implemented and is being
|
||||
worked on irrespective of Raspberry Pi related work.
|
||||
|
||||
|
||||
Appendix B: Raspbmc not booting
|
||||
===============================
|
||||
|
||||
Due to a dependency version problem where XBMC uses another version of libtag
|
||||
than what Debian originally ships with, you might have to make some minor
|
||||
changes for Raspbmc to start properly after installing Mopidy.
|
||||
|
||||
If you notice that XBMC is not starting but gets stuck in a loop,
|
||||
you need to make the following changes::
|
||||
|
||||
sudo ln -sf /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system/libtag.so.1 \
|
||||
/usr/lib/arm-linux-gnueabihf/libtag.so.1
|
||||
|
||||
However, this will not persist the changes. To persist the changes edit
|
||||
:file:`/etc/ld.so.conf.d/arm-linux-gnueabihf.conf` and add the following at the
|
||||
top::
|
||||
|
||||
/home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system
|
||||
|
||||
It's very important to add it at the top of the file as this indicates the
|
||||
priority of the folder in which to look for shared libraries.
|
||||
|
||||
XBMC doesn't play nicely with the system wide installed version of libtag that
|
||||
got installed together with Mopidy, but rather vendors in its own version.
|
||||
|
||||
More info about this issue can be found in `this post
|
||||
<http://geeks.noeit.com/xbmc-library-dependency-error/>`_.
|
||||
|
||||
Please note that if you're running Xbian or another XBMC distribution these
|
||||
instructions might vary for your system.
|
||||
|
||||
@ -30,4 +30,4 @@ except ImportError:
|
||||
warnings.filterwarnings('ignore', 'could not open display')
|
||||
|
||||
|
||||
__version__ = '0.19.4'
|
||||
__version__ = '0.19.5'
|
||||
|
||||
@ -2,9 +2,11 @@ from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import glib
|
||||
|
||||
@ -63,6 +65,13 @@ class _HelpAction(argparse.Action):
|
||||
raise _HelpError()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _startup_timer(name):
|
||||
start = time.time()
|
||||
yield
|
||||
logger.debug('%s startup took %dms', name, (time.time() - start) * 1000)
|
||||
|
||||
|
||||
class Command(object):
|
||||
"""Command parser and runner for building trees of commands.
|
||||
|
||||
@ -339,8 +348,9 @@ class RootCommand(Command):
|
||||
backends = []
|
||||
for backend_class in backend_classes:
|
||||
try:
|
||||
backend = backend_class.start(
|
||||
config=config, audio=audio).proxy()
|
||||
with _startup_timer(backend_class.__name__):
|
||||
backend = backend_class.start(
|
||||
config=config, audio=audio).proxy()
|
||||
backends.append(backend)
|
||||
except exceptions.BackendError as exc:
|
||||
logger.error(
|
||||
@ -361,7 +371,8 @@ class RootCommand(Command):
|
||||
|
||||
for frontend_class in frontend_classes:
|
||||
try:
|
||||
frontend_class.start(config=config, core=core)
|
||||
with _startup_timer(frontend_class.__name__):
|
||||
frontend_class.start(config=config, core=core)
|
||||
except exceptions.FrontendError as exc:
|
||||
logger.error(
|
||||
'Frontend (%s) initialization error: %s',
|
||||
|
||||
@ -98,8 +98,12 @@ def format_initial(extensions):
|
||||
versions = ['Mopidy %s' % versioning.get_version()]
|
||||
for extension in sorted(extensions, key=lambda ext: ext.dist_name):
|
||||
versions.append('%s %s' % (extension.dist_name, extension.version))
|
||||
description = _INITIAL_HELP.strip() % {'versions': '\n# '.join(versions)}
|
||||
return description + '\n\n' + _format(config, {}, schemas, False, True)
|
||||
|
||||
header = _INITIAL_HELP.strip() % {'versions': '\n# '.join(versions)}
|
||||
formatted_config = _format(
|
||||
config=config, comments={}, schemas=schemas,
|
||||
display=False, disable=True).decode('utf-8')
|
||||
return header + '\n\n' + formatted_config
|
||||
|
||||
|
||||
def _load(files, defaults, overrides):
|
||||
|
||||
@ -185,16 +185,20 @@ class PlaybackController(object):
|
||||
The current playback state will be kept. If it was playing, playing
|
||||
will continue. If it was paused, it will still be paused, etc.
|
||||
"""
|
||||
tl_track = self.core.tracklist.next_track(self.current_tl_track)
|
||||
if tl_track:
|
||||
original_tl_track = self.current_tl_track
|
||||
next_tl_track = self.core.tracklist.next_track(original_tl_track)
|
||||
|
||||
if next_tl_track:
|
||||
# TODO: switch to:
|
||||
# backend.play(track)
|
||||
# wait for state change?
|
||||
self.change_track(tl_track)
|
||||
self.change_track(next_tl_track)
|
||||
else:
|
||||
self.stop()
|
||||
self.current_tl_track = None
|
||||
|
||||
self.core.tracklist.mark_played(original_tl_track)
|
||||
|
||||
def pause(self):
|
||||
"""Pause playback."""
|
||||
backend = self._get_backend()
|
||||
|
||||
@ -449,10 +449,10 @@ class TracklistController(object):
|
||||
|
||||
def mark_played(self, tl_track):
|
||||
"""Private method used by :class:`mopidy.core.PlaybackController`."""
|
||||
if not self.consume:
|
||||
return False
|
||||
self.remove(tlid=[tl_track.tlid])
|
||||
return True
|
||||
if self.consume and tl_track is not None:
|
||||
self.remove(tlid=[tl_track.tlid])
|
||||
return True
|
||||
return False
|
||||
|
||||
def _trigger_tracklist_changed(self):
|
||||
if self.random:
|
||||
|
||||
@ -188,10 +188,13 @@ def validate_extension(extension):
|
||||
extension.ext_name, ex)
|
||||
return False
|
||||
except pkg_resources.VersionConflict as ex:
|
||||
found, required = ex.args
|
||||
logger.info(
|
||||
'Disabled extension %s: %s required, but found %s at %s',
|
||||
extension.ext_name, required, found, found.location)
|
||||
if len(ex.args) == 2:
|
||||
found, required = ex.args
|
||||
logger.info(
|
||||
'Disabled extension %s: %s required, but found %s at %s',
|
||||
extension.ext_name, required, found, found.location)
|
||||
else:
|
||||
logger.info('Disabled extension %s: %s', extension.ext_name, ex)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
@ -129,11 +129,16 @@ class HttpServer(threading.Thread):
|
||||
def _get_app_request_handlers(self):
|
||||
result = []
|
||||
for app in self.apps:
|
||||
try:
|
||||
request_handlers = app['factory'](self.config, self.core)
|
||||
except Exception:
|
||||
logger.exception('Loading %s failed.', app['name'])
|
||||
continue
|
||||
|
||||
result.append((
|
||||
r'/%s' % app['name'],
|
||||
handlers.AddSlashHandler
|
||||
))
|
||||
request_handlers = app['factory'](self.config, self.core)
|
||||
for handler in request_handlers:
|
||||
handler = list(handler)
|
||||
handler[0] = '/%s%s' % (app['name'], handler[0])
|
||||
|
||||
@ -196,17 +196,12 @@ class MpdDispatcher(object):
|
||||
|
||||
def _format_lines(self, line):
|
||||
if isinstance(line, dict):
|
||||
return [self._escape_newlines('%s: %s' % (key, value))
|
||||
for (key, value)
|
||||
in line.items()]
|
||||
return ['%s: %s' % (key, value) for (key, value) in line.items()]
|
||||
if isinstance(line, tuple):
|
||||
(key, value) = line
|
||||
return [self._escape_newlines('%s: %s' % (key, value))]
|
||||
return ['%s: %s' % (key, value)]
|
||||
return [line]
|
||||
|
||||
def _escape_newlines(self, text):
|
||||
return text.replace('\n', '\\n')
|
||||
|
||||
|
||||
class MpdContext(object):
|
||||
"""
|
||||
|
||||
@ -81,9 +81,6 @@ def track_to_mpd_format(track, position=None):
|
||||
if track.disc_no:
|
||||
result.append(('Disc', track.disc_no))
|
||||
|
||||
if track.comment:
|
||||
result.append(('Comment', track.comment))
|
||||
|
||||
if track.musicbrainz_id is not None:
|
||||
result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id))
|
||||
return result
|
||||
|
||||
@ -46,12 +46,14 @@ def get_or_create_file(file_path, mkdir=True, content=None):
|
||||
if not isinstance(file_path, bytes):
|
||||
raise ValueError('Path is not a bytestring.')
|
||||
file_path = expand_path(file_path)
|
||||
if isinstance(content, compat.text_type):
|
||||
content = content.encode('utf-8')
|
||||
if mkdir:
|
||||
get_or_create_dir(os.path.dirname(file_path))
|
||||
if not os.path.isfile(file_path):
|
||||
logger.info('Creating file %s', file_path)
|
||||
with open(file_path, 'w') as fh:
|
||||
if content:
|
||||
with open(file_path, 'wb') as fh:
|
||||
if content is not None:
|
||||
fh.write(content)
|
||||
return file_path
|
||||
|
||||
|
||||
@ -239,6 +239,23 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
|
||||
# TODO Test next() more
|
||||
|
||||
def test_next_keeps_finished_track_in_tracklist(self):
|
||||
tl_track = self.tl_tracks[0]
|
||||
self.core.playback.play(tl_track)
|
||||
|
||||
self.core.playback.next()
|
||||
|
||||
self.assertIn(tl_track, self.core.tracklist.tl_tracks)
|
||||
|
||||
def test_next_in_consume_mode_removes_finished_track(self):
|
||||
tl_track = self.tl_tracks[0]
|
||||
self.core.playback.play(tl_track)
|
||||
self.core.tracklist.consume = True
|
||||
|
||||
self.core.playback.next()
|
||||
|
||||
self.assertNotIn(tl_track, self.core.tracklist.tl_tracks)
|
||||
|
||||
@mock.patch(
|
||||
'mopidy.core.playback.listener.CoreListener', spec=core.CoreListener)
|
||||
def test_next_emits_events(self, listener_mock):
|
||||
@ -265,6 +282,23 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
|
||||
# TODO Test previous() more
|
||||
|
||||
def test_previous_keeps_finished_track_in_tracklist(self):
|
||||
tl_track = self.tl_tracks[1]
|
||||
self.core.playback.play(tl_track)
|
||||
|
||||
self.core.playback.previous()
|
||||
|
||||
self.assertIn(tl_track, self.core.tracklist.tl_tracks)
|
||||
|
||||
def test_previous_keeps_finished_track_even_in_consume_mode(self):
|
||||
tl_track = self.tl_tracks[1]
|
||||
self.core.playback.play(tl_track)
|
||||
self.core.tracklist.consume = True
|
||||
|
||||
self.core.playback.previous()
|
||||
|
||||
self.assertIn(tl_track, self.core.tracklist.tl_tracks)
|
||||
|
||||
@mock.patch(
|
||||
'mopidy.core.playback.listener.CoreListener', spec=core.CoreListener)
|
||||
def test_previous_emits_events(self, listener_mock):
|
||||
@ -291,6 +325,23 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
|
||||
# TODO Test on_end_of_track() more
|
||||
|
||||
def test_on_end_of_track_keeps_finished_track_in_tracklist(self):
|
||||
tl_track = self.tl_tracks[0]
|
||||
self.core.playback.play(tl_track)
|
||||
|
||||
self.core.playback.on_end_of_track()
|
||||
|
||||
self.assertIn(tl_track, self.core.tracklist.tl_tracks)
|
||||
|
||||
def test_on_end_of_track_in_consume_mode_removes_finished_track(self):
|
||||
tl_track = self.tl_tracks[0]
|
||||
self.core.playback.play(tl_track)
|
||||
self.core.tracklist.consume = True
|
||||
|
||||
self.core.playback.on_end_of_track()
|
||||
|
||||
self.assertNotIn(tl_track, self.core.tracklist.tl_tracks)
|
||||
|
||||
@mock.patch(
|
||||
'mopidy.core.playback.listener.CoreListener', spec=core.CoreListener)
|
||||
def test_on_end_of_track_emits_events(self, listener_mock):
|
||||
|
||||
@ -347,7 +347,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.assertIn(self.tracks[0], self.tracklist.tracks)
|
||||
self.assertNotIn(self.tracks[0], self.tracklist.tracks)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_with_single_and_repeat(self):
|
||||
|
||||
@ -73,10 +73,10 @@ class TrackMpdFormatTest(unittest.TestCase):
|
||||
self.assertIn(('Track', '7/13'), result)
|
||||
self.assertIn(('Date', datetime.date(1977, 1, 1)), result)
|
||||
self.assertIn(('Disc', '1'), result)
|
||||
self.assertIn(('Comment', 'a comment'), result)
|
||||
self.assertIn(('Pos', 9), result)
|
||||
self.assertIn(('Id', 122), result)
|
||||
self.assertEqual(len(result), 15)
|
||||
self.assertNotIn(('Comment', 'a comment'), result)
|
||||
self.assertEqual(len(result), 14)
|
||||
|
||||
def test_track_to_mpd_format_musicbrainz_trackid(self):
|
||||
track = self.track.copy(musicbrainz_id='foo')
|
||||
|
||||
@ -50,5 +50,6 @@ class VersionTest(unittest.TestCase):
|
||||
self.assertLess(SV('0.19.0'), SV('0.19.1'))
|
||||
self.assertLess(SV('0.19.1'), SV('0.19.2'))
|
||||
self.assertLess(SV('0.19.2'), SV('0.19.3'))
|
||||
self.assertLess(SV('0.19.3'), SV(__version__))
|
||||
self.assertLess(SV(__version__), SV('0.19.5'))
|
||||
self.assertLess(SV('0.19.3'), SV('0.19.4'))
|
||||
self.assertLess(SV('0.19.4'), SV(__version__))
|
||||
self.assertLess(SV(__version__), SV('0.19.6'))
|
||||
|
||||
@ -107,12 +107,12 @@ class GetOrCreateFileTest(unittest.TestCase):
|
||||
with self.assertRaises(IOError):
|
||||
path.get_or_create_file(conflicting_dir)
|
||||
|
||||
def test_create_dir_with_unicode(self):
|
||||
def test_create_dir_with_unicode_filename_throws_value_error(self):
|
||||
with self.assertRaises(ValueError):
|
||||
file_path = compat.text_type(os.path.join(self.parent, b'test'))
|
||||
path.get_or_create_file(file_path)
|
||||
|
||||
def test_create_file_with_none(self):
|
||||
def test_create_file_with_none_filename_throws_value_error(self):
|
||||
with self.assertRaises(ValueError):
|
||||
path.get_or_create_file(None)
|
||||
|
||||
@ -121,12 +121,18 @@ class GetOrCreateFileTest(unittest.TestCase):
|
||||
with self.assertRaises(IOError):
|
||||
path.get_or_create_file(file_path, mkdir=False)
|
||||
|
||||
def test_create_dir_with_default_content(self):
|
||||
def test_create_dir_with_bytes_content(self):
|
||||
file_path = os.path.join(self.parent, b'test')
|
||||
created = path.get_or_create_file(file_path, content=b'foobar')
|
||||
with open(created) as fh:
|
||||
self.assertEqual(fh.read(), b'foobar')
|
||||
|
||||
def test_create_dir_with_unicode_content(self):
|
||||
file_path = os.path.join(self.parent, b'test')
|
||||
created = path.get_or_create_file(file_path, content='foobaræøå')
|
||||
with open(created) as fh:
|
||||
self.assertEqual(fh.read(), b'foobaræøå')
|
||||
|
||||
|
||||
class PathToFileURITest(unittest.TestCase):
|
||||
def test_simple_path(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user