New CoreState to hold all core states
- Introduce a CoreState class that holds all core states - Move xState classes to internal - Use validation.check_instance for consistent error messages - Store tlid instead of TlTrack to restore last played track
This commit is contained in:
parent
74344f2b19
commit
4869619bb9
@ -18,9 +18,9 @@ from mopidy.core.mixer import MixerController
|
|||||||
from mopidy.core.playback import PlaybackController
|
from mopidy.core.playback import PlaybackController
|
||||||
from mopidy.core.playlists import PlaylistsController
|
from mopidy.core.playlists import PlaylistsController
|
||||||
from mopidy.core.tracklist import TracklistController
|
from mopidy.core.tracklist import TracklistController
|
||||||
from mopidy.internal import versioning
|
from mopidy.internal import storage, validation, versioning
|
||||||
from mopidy.internal.deprecation import deprecated_property
|
from mopidy.internal.deprecation import deprecated_property
|
||||||
from mopidy.models import storage
|
from mopidy.internal.models import CoreState
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -163,7 +163,7 @@ class Core(
|
|||||||
if len(coverage):
|
if len(coverage):
|
||||||
self.load_state('persistent', coverage)
|
self.load_state('persistent', coverage)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn('setup: Unexpected error: %s', str(e))
|
logger.warn('Restore state: Unexpected error: %s', str(e))
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
try:
|
try:
|
||||||
@ -172,7 +172,7 @@ class Core(
|
|||||||
if amount and 'off' != amount:
|
if amount and 'off' != amount:
|
||||||
self.save_state('persistent')
|
self.save_state('persistent')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn('teardown: Unexpected error: %s', str(e))
|
logger.warn('Export state: Unexpected error: %s', str(e))
|
||||||
|
|
||||||
def save_state(self, name):
|
def save_state(self, name):
|
||||||
"""
|
"""
|
||||||
@ -191,12 +191,13 @@ class Core(
|
|||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
data['version'] = mopidy.__version__
|
data['version'] = mopidy.__version__
|
||||||
data['tracklist'] = self.tracklist._export_state()
|
data['state'] = CoreState(
|
||||||
data['history'] = self.history._export_state()
|
tracklist=self.tracklist._export_state(),
|
||||||
data['playback'] = self.playback._export_state()
|
history=self.history._export_state(),
|
||||||
data['mixer'] = self.mixer._export_state()
|
playback=self.playback._export_state(),
|
||||||
|
mixer=self.mixer._export_state())
|
||||||
storage.save(file_name, data)
|
storage.save(file_name, data)
|
||||||
logger.debug('Save state done')
|
logger.debug('Save state done.')
|
||||||
|
|
||||||
def load_state(self, name, coverage):
|
def load_state(self, name, coverage):
|
||||||
"""
|
"""
|
||||||
@ -226,15 +227,14 @@ class Core(
|
|||||||
logger.info('Load state from %s', file_name)
|
logger.info('Load state from %s', file_name)
|
||||||
|
|
||||||
data = storage.load(file_name)
|
data = storage.load(file_name)
|
||||||
if 'history' in data:
|
if 'state' in data:
|
||||||
self.history._restore_state(data['history'], coverage)
|
core_state = data['state']
|
||||||
if 'tracklist' in data:
|
validation.check_instance(core_state, CoreState)
|
||||||
self.tracklist._restore_state(data['tracklist'], coverage)
|
self.history._restore_state(core_state.history, coverage)
|
||||||
if 'playback' in data:
|
self.tracklist._restore_state(core_state.tracklist, coverage)
|
||||||
# playback after tracklist
|
# playback after tracklist
|
||||||
self.playback._restore_state(data['playback'], coverage)
|
self.playback._restore_state(core_state.playback, coverage)
|
||||||
if 'mixer' in data:
|
self.mixer._restore_state(core_state.mixer, coverage)
|
||||||
self.mixer._restore_state(data['mixer'], coverage)
|
|
||||||
logger.debug('Load state done.')
|
logger.debug('Load state done.')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from mopidy import models
|
from mopidy import models
|
||||||
|
from mopidy.internal.models import HistoryState, HistoryTrack
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -62,15 +62,13 @@ class HistoryController(object):
|
|||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
history_list = []
|
history_list = []
|
||||||
for timestamp, track in self._history:
|
for timestamp, track in self._history:
|
||||||
history_list.append(models.HistoryTrack(
|
history_list.append(HistoryTrack(
|
||||||
timestamp=timestamp, track=track))
|
timestamp=timestamp, track=track))
|
||||||
return models.HistoryState(history=history_list)
|
return HistoryState(history=history_list)
|
||||||
|
|
||||||
def _restore_state(self, state, coverage):
|
def _restore_state(self, state, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if state:
|
if state:
|
||||||
if not isinstance(state, models.HistoryState):
|
|
||||||
raise TypeError('Expect an argument of type "HistoryState"')
|
|
||||||
if 'history' in coverage:
|
if 'history' in coverage:
|
||||||
self._history = []
|
self._history = []
|
||||||
for htrack in state.history:
|
for htrack in state.history:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import logging
|
|||||||
|
|
||||||
from mopidy import exceptions
|
from mopidy import exceptions
|
||||||
from mopidy.internal import validation
|
from mopidy.internal import validation
|
||||||
from mopidy.models import MixerState
|
from mopidy.internal.models import MixerState
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -108,8 +108,6 @@ class MixerController(object):
|
|||||||
def _restore_state(self, state, coverage):
|
def _restore_state(self, state, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if state:
|
if state:
|
||||||
if not isinstance(state, MixerState):
|
|
||||||
raise TypeError('Expect an argument of type "MixerState"')
|
|
||||||
if 'volume' in coverage:
|
if 'volume' in coverage:
|
||||||
if state.volume:
|
if state.volume:
|
||||||
self.set_volume(state.volume)
|
self.set_volume(state.volume)
|
||||||
|
|||||||
@ -2,11 +2,10 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mopidy import models
|
|
||||||
from mopidy.audio import PlaybackState
|
from mopidy.audio import PlaybackState
|
||||||
from mopidy.compat import urllib
|
from mopidy.compat import urllib
|
||||||
from mopidy.core import listener
|
from mopidy.core import listener
|
||||||
from mopidy.internal import deprecation, validation
|
from mopidy.internal import deprecation, models, validation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -552,14 +551,13 @@ class PlaybackController(object):
|
|||||||
def _restore_state(self, state, coverage):
|
def _restore_state(self, state, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if state:
|
if state:
|
||||||
if not isinstance(state, models.PlaybackState):
|
|
||||||
raise TypeError('Expect an argument of type "PlaybackState"')
|
|
||||||
new_state = None
|
new_state = None
|
||||||
if 'play-always' in coverage:
|
if 'play-always' in coverage:
|
||||||
new_state = PlaybackState.PLAYING
|
new_state = PlaybackState.PLAYING
|
||||||
if 'play-last' in coverage:
|
if 'play-last' in coverage:
|
||||||
new_state = state.state
|
new_state = state.state
|
||||||
if state.tlid is not None:
|
if state.tlid is not None:
|
||||||
|
# TODO: restore to 'paused' state
|
||||||
if PlaybackState.PLAYING == new_state:
|
if PlaybackState.PLAYING == new_state:
|
||||||
self.play(tlid=state.tlid)
|
self.play(tlid=state.tlid)
|
||||||
# TODO: seek to state.position?
|
# TODO: seek to state.position?
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import random
|
|||||||
from mopidy import exceptions
|
from mopidy import exceptions
|
||||||
from mopidy.core import listener
|
from mopidy.core import listener
|
||||||
from mopidy.internal import deprecation, validation
|
from mopidy.internal import deprecation, validation
|
||||||
from mopidy.models import TlTrack, Track, TracklistState
|
from mopidy.internal.models import TracklistState
|
||||||
|
from mopidy.models import TlTrack, Track
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -658,8 +659,6 @@ class TracklistController(object):
|
|||||||
def _restore_state(self, state, coverage):
|
def _restore_state(self, state, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if state:
|
if state:
|
||||||
if not isinstance(state, TracklistState):
|
|
||||||
raise TypeError('Expect an argument of type "TracklistState"')
|
|
||||||
if 'mode' in coverage:
|
if 'mode' in coverage:
|
||||||
self.set_consume(state.consume)
|
self.set_consume(state.consume)
|
||||||
self.set_random(state.random)
|
self.set_random(state.random)
|
||||||
@ -670,5 +669,9 @@ class TracklistController(object):
|
|||||||
self._next_tlid = state.next_tlid
|
self._next_tlid = state.next_tlid
|
||||||
self._tl_tracks = []
|
self._tl_tracks = []
|
||||||
for track in state.tl_tracks:
|
for track in state.tl_tracks:
|
||||||
|
# TODO: check if any backend will play the track.
|
||||||
|
# Could be an issue with music streaming services
|
||||||
|
# (login), disabled extensions and automatically
|
||||||
|
# generated playlists (pandora).
|
||||||
self._tl_tracks.append(track)
|
self._tl_tracks.append(track)
|
||||||
self._trigger_tracklist_changed()
|
self._trigger_tracklist_changed()
|
||||||
|
|||||||
142
mopidy/internal/models.py
Normal file
142
mopidy/internal/models.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from mopidy.internal import validation
|
||||||
|
from mopidy.models import Ref, TlTrack, fields
|
||||||
|
from mopidy.models.immutable import ValidatedImmutableObject
|
||||||
|
|
||||||
|
_MODELS = ['HistoryTrack', 'HistoryState', 'MixerState', 'PlaybackState',
|
||||||
|
'TracklistState', 'CoreState']
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryTrack(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
A history track. Wraps a :class:`Ref` and it's timestamp.
|
||||||
|
|
||||||
|
:param timestamp: the timestamp
|
||||||
|
:type timestamp: int
|
||||||
|
:param track: the track reference
|
||||||
|
:type track: :class:`Ref`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The timestamp. Read-only.
|
||||||
|
timestamp = fields.Integer()
|
||||||
|
|
||||||
|
# The track reference. Read-only.
|
||||||
|
track = fields.Field(type=Ref)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryState(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
State of the history controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param history: the track history
|
||||||
|
:type history: list of :class:`HistoryTrack`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The tracks. Read-only.
|
||||||
|
history = fields.Collection(type=HistoryTrack, container=tuple)
|
||||||
|
|
||||||
|
|
||||||
|
class MixerState(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
State of the mixer controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param volume: the volume
|
||||||
|
:type volume: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The volume. Read-only.
|
||||||
|
volume = fields.Integer(min=0, max=100)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackState(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
State of the playback controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param tlid: current track tlid
|
||||||
|
:type tlid: int
|
||||||
|
:param position: play position
|
||||||
|
:type position: int
|
||||||
|
:param state: playback state
|
||||||
|
:type state: :class:`validation.PLAYBACK_STATES`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The tlid of current playing track. Read-only.
|
||||||
|
tlid = fields.Integer(min=1)
|
||||||
|
|
||||||
|
# The playback position. Read-only.
|
||||||
|
position = fields.Integer(min=0)
|
||||||
|
|
||||||
|
# The playback state. Read-only.
|
||||||
|
state = fields.Field(choices=validation.PLAYBACK_STATES)
|
||||||
|
|
||||||
|
|
||||||
|
class TracklistState(ValidatedImmutableObject):
|
||||||
|
|
||||||
|
"""
|
||||||
|
State of the tracklist controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param repeat: the repeat mode
|
||||||
|
:type repeat: bool
|
||||||
|
:param consume: the consume mode
|
||||||
|
:type consume: bool
|
||||||
|
:param random: the random mode
|
||||||
|
:type random: bool
|
||||||
|
:param single: the single mode
|
||||||
|
:type single: bool
|
||||||
|
:param next_tlid: the single mode
|
||||||
|
:type next_tlid: bool
|
||||||
|
:param tl_tracks: the single mode
|
||||||
|
:type tl_tracks: list of :class:`TlTrack`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The repeat mode. Read-only.
|
||||||
|
repeat = fields.Boolean()
|
||||||
|
|
||||||
|
# The consume mode. Read-only.
|
||||||
|
consume = fields.Boolean()
|
||||||
|
|
||||||
|
# The random mode. Read-only.
|
||||||
|
random = fields.Boolean()
|
||||||
|
|
||||||
|
# The single mode. Read-only.
|
||||||
|
single = fields.Boolean()
|
||||||
|
|
||||||
|
# The repeat mode. Read-only.
|
||||||
|
next_tlid = fields.Integer(min=0)
|
||||||
|
|
||||||
|
# The list of tracks. Read-only.
|
||||||
|
tl_tracks = fields.Collection(type=TlTrack, container=tuple)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreState(ValidatedImmutableObject):
|
||||||
|
|
||||||
|
"""
|
||||||
|
State of all Core controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param history: State of the history controller
|
||||||
|
:type history: :class:`HistorState`
|
||||||
|
:param mixer: State of the mixer controller
|
||||||
|
:type mixer: :class:`MixerState`
|
||||||
|
:param playback: State of the playback controller
|
||||||
|
:type playback: :class:`PlaybackState`
|
||||||
|
:param tracklist: State of the tracklist controller
|
||||||
|
:type tracklist: :class:`TracklistState`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# State of the history controller.
|
||||||
|
history = fields.Field(type=HistoryState)
|
||||||
|
|
||||||
|
# State of the mixer controller.
|
||||||
|
mixer = fields.Field(type=MixerState)
|
||||||
|
|
||||||
|
# State of the playback controller.
|
||||||
|
playback = fields.Field(type=PlaybackState)
|
||||||
|
|
||||||
|
# State of the tracklist controller.
|
||||||
|
tracklist = fields.Field(type=TracklistState)
|
||||||
@ -9,6 +9,7 @@ import sys
|
|||||||
import mopidy
|
import mopidy
|
||||||
|
|
||||||
from mopidy import compat, local, models
|
from mopidy import compat, local, models
|
||||||
|
from mopidy import internal
|
||||||
from mopidy.internal import timer
|
from mopidy.internal import timer
|
||||||
from mopidy.local import search, storage, translator
|
from mopidy.local import search, storage, translator
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ class JsonLibrary(local.Library):
|
|||||||
self._json_file)
|
self._json_file)
|
||||||
self._tracks = {}
|
self._tracks = {}
|
||||||
else:
|
else:
|
||||||
library = models.storage.load(self._json_file)
|
library = internal.storage.load(self._json_file)
|
||||||
self._tracks = dict((t.uri, t) for t in
|
self._tracks = dict((t.uri, t) for t in
|
||||||
library.get('tracks', []))
|
library.get('tracks', []))
|
||||||
with timer.time_logger('Building browse cache'):
|
with timer.time_logger('Building browse cache'):
|
||||||
@ -166,9 +167,9 @@ class JsonLibrary(local.Library):
|
|||||||
self._tracks.pop(uri, None)
|
self._tracks.pop(uri, None)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
models.storage.save(self._json_file,
|
internal.storage.save(self._json_file,
|
||||||
{'version': mopidy.__version__,
|
{'version': mopidy.__version__,
|
||||||
'tracks': self._tracks.values()})
|
'tracks': self._tracks.values()})
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from mopidy import compat
|
from mopidy import compat
|
||||||
from mopidy.internal import validation
|
|
||||||
from mopidy.models import fields
|
from mopidy.models import fields
|
||||||
from mopidy.models.immutable import ImmutableObject, ValidatedImmutableObject
|
from mopidy.models.immutable import ImmutableObject, ValidatedImmutableObject
|
||||||
from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
|
from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
|
||||||
@ -9,8 +8,7 @@ from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'ImmutableObject', 'Ref', 'Image', 'Artist', 'Album', 'track', 'TlTrack',
|
'ImmutableObject', 'Ref', 'Image', 'Artist', 'Album', 'track', 'TlTrack',
|
||||||
'Playlist', 'SearchResult', 'model_json_decoder', 'ModelJSONEncoder',
|
'Playlist', 'SearchResult', 'model_json_decoder', 'ModelJSONEncoder',
|
||||||
'ValidatedImmutableObject', 'HistoryTrack', 'HistoryState', 'MixerState',
|
'ValidatedImmutableObject']
|
||||||
'PlaybackState', 'TracklistState']
|
|
||||||
|
|
||||||
|
|
||||||
class Ref(ValidatedImmutableObject):
|
class Ref(ValidatedImmutableObject):
|
||||||
@ -366,108 +364,3 @@ class SearchResult(ValidatedImmutableObject):
|
|||||||
|
|
||||||
# The albums matching the search query. Read-only.
|
# The albums matching the search query. Read-only.
|
||||||
albums = fields.Collection(type=Album, container=tuple)
|
albums = fields.Collection(type=Album, container=tuple)
|
||||||
|
|
||||||
|
|
||||||
class HistoryTrack(ValidatedImmutableObject):
|
|
||||||
"""
|
|
||||||
A history track. Wraps a :class:`Ref` and it's timestamp.
|
|
||||||
|
|
||||||
:param timestamp: the timestamp
|
|
||||||
:type timestamp: int
|
|
||||||
:param track: the track reference
|
|
||||||
:type track: :class:`Ref`
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The timestamp. Read-only.
|
|
||||||
timestamp = fields.Integer()
|
|
||||||
|
|
||||||
# The track reference. Read-only.
|
|
||||||
track = fields.Field(type=Ref)
|
|
||||||
|
|
||||||
|
|
||||||
class HistoryState(ValidatedImmutableObject):
|
|
||||||
"""
|
|
||||||
State of the history controller.
|
|
||||||
Internally used for import/export of current state.
|
|
||||||
|
|
||||||
:param history: the track history
|
|
||||||
:type history: list of :class:`HistoryTrack`
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The tracks. Read-only.
|
|
||||||
history = fields.Collection(type=HistoryTrack, container=tuple)
|
|
||||||
|
|
||||||
|
|
||||||
class MixerState(ValidatedImmutableObject):
|
|
||||||
"""
|
|
||||||
State of the mixer controller.
|
|
||||||
Internally used for import/export of current state.
|
|
||||||
|
|
||||||
:param volume: the volume
|
|
||||||
:type volume: int
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The volume. Read-only.
|
|
||||||
volume = fields.Integer(min=0, max=100)
|
|
||||||
|
|
||||||
|
|
||||||
class PlaybackState(ValidatedImmutableObject):
|
|
||||||
"""
|
|
||||||
State of the playback controller.
|
|
||||||
Internally used for import/export of current state.
|
|
||||||
|
|
||||||
:param tlid: current track tlid
|
|
||||||
:type tlid: int
|
|
||||||
:param position: play position
|
|
||||||
:type position: int
|
|
||||||
:param state: playback state
|
|
||||||
:type state: :class:`validation.PLAYBACK_STATES`
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The tlid of current playing track. Read-only.
|
|
||||||
tlid = fields.Integer(min=1)
|
|
||||||
|
|
||||||
# The playback position. Read-only.
|
|
||||||
position = fields.Integer(min=0)
|
|
||||||
|
|
||||||
# The playback state. Read-only.
|
|
||||||
state = fields.Field(choices=validation.PLAYBACK_STATES)
|
|
||||||
|
|
||||||
|
|
||||||
class TracklistState(ValidatedImmutableObject):
|
|
||||||
|
|
||||||
"""
|
|
||||||
State of the tracklist controller.
|
|
||||||
Internally used for import/export of current state.
|
|
||||||
|
|
||||||
:param repeat: the repeat mode
|
|
||||||
:type repeat: bool
|
|
||||||
:param consume: the consume mode
|
|
||||||
:type consume: bool
|
|
||||||
:param random: the random mode
|
|
||||||
:type random: bool
|
|
||||||
:param single: the single mode
|
|
||||||
:type single: bool
|
|
||||||
:param next_tlid: the single mode
|
|
||||||
:type next_tlid: bool
|
|
||||||
:param tl_tracks: the single mode
|
|
||||||
:type tl_tracks: list of :class:`TlTrack`
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The repeat mode. Read-only.
|
|
||||||
repeat = fields.Boolean()
|
|
||||||
|
|
||||||
# The consume mode. Read-only.
|
|
||||||
consume = fields.Boolean()
|
|
||||||
|
|
||||||
# The random mode. Read-only.
|
|
||||||
random = fields.Boolean()
|
|
||||||
|
|
||||||
# The single mode. Read-only.
|
|
||||||
single = fields.Boolean()
|
|
||||||
|
|
||||||
# The repeat mode. Read-only.
|
|
||||||
next_tlid = fields.Integer(min=0)
|
|
||||||
|
|
||||||
# The list of tracks. Read-only.
|
|
||||||
tl_tracks = fields.Collection(type=TlTrack, container=tuple)
|
|
||||||
|
|||||||
@ -4,9 +4,7 @@ import json
|
|||||||
|
|
||||||
from mopidy.models import immutable
|
from mopidy.models import immutable
|
||||||
|
|
||||||
_MODELS = ['Ref', 'Artist', 'Album', 'Track', 'TlTrack', 'Playlist',
|
_MODELS = ['Ref', 'Artist', 'Album', 'Track', 'TlTrack', 'Playlist']
|
||||||
'HistoryTrack', 'HistoryState', 'MixerState', 'PlaybackState',
|
|
||||||
'TracklistState']
|
|
||||||
|
|
||||||
|
|
||||||
class ModelJSONEncoder(json.JSONEncoder):
|
class ModelJSONEncoder(json.JSONEncoder):
|
||||||
@ -46,4 +44,7 @@ def model_json_decoder(dct):
|
|||||||
model_name = dct.pop('__model__')
|
model_name = dct.pop('__model__')
|
||||||
if model_name in _MODELS:
|
if model_name in _MODELS:
|
||||||
return getattr(models, model_name)(**dct)
|
return getattr(models, model_name)(**dct)
|
||||||
|
from mopidy import internal
|
||||||
|
if model_name in internal.models._MODELS:
|
||||||
|
return getattr(internal.models, model_name)(**dct)
|
||||||
return dct
|
return dct
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import unittest
|
|||||||
|
|
||||||
from mopidy import compat
|
from mopidy import compat
|
||||||
from mopidy.core import HistoryController
|
from mopidy.core import HistoryController
|
||||||
from mopidy.models import Artist, HistoryState, HistoryTrack, Ref, Track
|
from mopidy.internal.models import HistoryState, HistoryTrack
|
||||||
|
from mopidy.models import Artist, Ref, Track
|
||||||
|
|
||||||
|
|
||||||
class PlaybackHistoryTest(unittest.TestCase):
|
class PlaybackHistoryTest(unittest.TestCase):
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import mock
|
|||||||
import pykka
|
import pykka
|
||||||
|
|
||||||
from mopidy import core, mixer
|
from mopidy import core, mixer
|
||||||
from mopidy.models import MixerState
|
from mopidy.internal.models import MixerState
|
||||||
from tests import dummy_mixer
|
from tests import dummy_mixer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import pykka
|
|||||||
|
|
||||||
from mopidy import backend, core
|
from mopidy import backend, core
|
||||||
from mopidy.internal import deprecation
|
from mopidy.internal import deprecation
|
||||||
from mopidy.models import PlaybackState, Track
|
from mopidy.internal.models import PlaybackState
|
||||||
|
from mopidy.models import Track
|
||||||
|
|
||||||
from tests import dummy_audio
|
from tests import dummy_audio
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import mock
|
|||||||
|
|
||||||
from mopidy import backend, core
|
from mopidy import backend, core
|
||||||
from mopidy.internal import deprecation
|
from mopidy.internal import deprecation
|
||||||
from mopidy.models import TlTrack, Track, TracklistState
|
from mopidy.internal.models import TracklistState
|
||||||
|
from mopidy.models import TlTrack, Track
|
||||||
|
|
||||||
|
|
||||||
class TracklistTest(unittest.TestCase):
|
class TracklistTest(unittest.TestCase):
|
||||||
|
|||||||
200
tests/internal/test_models.py
Normal file
200
tests/internal/test_models.py
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from mopidy.internal.models import (
|
||||||
|
HistoryState, HistoryTrack, MixerState, PlaybackState, TracklistState)
|
||||||
|
from mopidy.models import (
|
||||||
|
ModelJSONEncoder, Ref, TlTrack, Track, model_json_decoder)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryTrackTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_track(self):
|
||||||
|
track = Ref.track()
|
||||||
|
result = HistoryTrack(track=track)
|
||||||
|
self.assertEqual(result.track, track)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.track = None
|
||||||
|
|
||||||
|
def test_timestamp(self):
|
||||||
|
timestamp = 1234
|
||||||
|
result = HistoryTrack(timestamp=timestamp)
|
||||||
|
self.assertEqual(result.timestamp, timestamp)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.timestamp = None
|
||||||
|
|
||||||
|
def test_to_json_and_back(self):
|
||||||
|
result = HistoryTrack(track=Ref.track(), timestamp=1234)
|
||||||
|
serialized = json.dumps(result, cls=ModelJSONEncoder)
|
||||||
|
deserialized = json.loads(serialized, object_hook=model_json_decoder)
|
||||||
|
self.assertEqual(result, deserialized)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_history_list(self):
|
||||||
|
history = (HistoryTrack(),
|
||||||
|
HistoryTrack())
|
||||||
|
result = HistoryState(history=history)
|
||||||
|
self.assertEqual(result.history, history)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.history = None
|
||||||
|
|
||||||
|
def test_history_string_fail(self):
|
||||||
|
history = 'not_a_valid_history'
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
HistoryState(history=history)
|
||||||
|
|
||||||
|
def test_to_json_and_back(self):
|
||||||
|
result = HistoryState(history=(HistoryTrack(), HistoryTrack()))
|
||||||
|
serialized = json.dumps(result, cls=ModelJSONEncoder)
|
||||||
|
deserialized = json.loads(serialized, object_hook=model_json_decoder)
|
||||||
|
self.assertEqual(result, deserialized)
|
||||||
|
|
||||||
|
|
||||||
|
class MixerStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_volume(self):
|
||||||
|
volume = 37
|
||||||
|
result = MixerState(volume=volume)
|
||||||
|
self.assertEqual(result.volume, volume)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.volume = None
|
||||||
|
|
||||||
|
def test_volume_invalid(self):
|
||||||
|
volume = 105
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
MixerState(volume=volume)
|
||||||
|
|
||||||
|
def test_to_json_and_back(self):
|
||||||
|
result = MixerState(volume=77)
|
||||||
|
serialized = json.dumps(result, cls=ModelJSONEncoder)
|
||||||
|
deserialized = json.loads(serialized, object_hook=model_json_decoder)
|
||||||
|
self.assertEqual(result, deserialized)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_position(self):
|
||||||
|
position = 123456
|
||||||
|
result = PlaybackState(position=position)
|
||||||
|
self.assertEqual(result.position, position)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.position = None
|
||||||
|
|
||||||
|
def test_position_invalid(self):
|
||||||
|
position = -1
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PlaybackState(position=position)
|
||||||
|
|
||||||
|
def test_tl_track(self):
|
||||||
|
tlid = 42
|
||||||
|
result = PlaybackState(tlid=tlid)
|
||||||
|
self.assertEqual(result.tlid, tlid)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.tlid = None
|
||||||
|
|
||||||
|
def test_tl_track_none(self):
|
||||||
|
tlid = None
|
||||||
|
result = PlaybackState(tlid=tlid)
|
||||||
|
self.assertEqual(result.tlid, tlid)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.tl_track = None
|
||||||
|
|
||||||
|
def test_tl_track_invalid(self):
|
||||||
|
tl_track = Track()
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PlaybackState(tlid=tl_track)
|
||||||
|
|
||||||
|
def test_state(self):
|
||||||
|
state = 'playing'
|
||||||
|
result = PlaybackState(state=state)
|
||||||
|
self.assertEqual(result.state, state)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.state = None
|
||||||
|
|
||||||
|
def test_state_invalid(self):
|
||||||
|
state = 'not_a_state'
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PlaybackState(state=state)
|
||||||
|
|
||||||
|
def test_to_json_and_back(self):
|
||||||
|
result = PlaybackState(state='playing', tlid=4321)
|
||||||
|
serialized = json.dumps(result, cls=ModelJSONEncoder)
|
||||||
|
deserialized = json.loads(serialized, object_hook=model_json_decoder)
|
||||||
|
self.assertEqual(result, deserialized)
|
||||||
|
|
||||||
|
|
||||||
|
class TracklistStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_repeat_true(self):
|
||||||
|
repeat = True
|
||||||
|
result = TracklistState(repeat=repeat)
|
||||||
|
self.assertEqual(result.repeat, repeat)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.repeat = None
|
||||||
|
|
||||||
|
def test_repeat_false(self):
|
||||||
|
repeat = False
|
||||||
|
result = TracklistState(repeat=repeat)
|
||||||
|
self.assertEqual(result.repeat, repeat)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.repeat = None
|
||||||
|
|
||||||
|
def test_repeat_invalid(self):
|
||||||
|
repeat = 33
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
TracklistState(repeat=repeat)
|
||||||
|
|
||||||
|
def test_consume_true(self):
|
||||||
|
val = True
|
||||||
|
result = TracklistState(consume=val)
|
||||||
|
self.assertEqual(result.consume, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.repeat = None
|
||||||
|
|
||||||
|
def test_random_true(self):
|
||||||
|
val = True
|
||||||
|
result = TracklistState(random=val)
|
||||||
|
self.assertEqual(result.random, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.random = None
|
||||||
|
|
||||||
|
def test_single_true(self):
|
||||||
|
val = True
|
||||||
|
result = TracklistState(single=val)
|
||||||
|
self.assertEqual(result.single, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.single = None
|
||||||
|
|
||||||
|
def test_next_tlid(self):
|
||||||
|
val = 654
|
||||||
|
result = TracklistState(next_tlid=val)
|
||||||
|
self.assertEqual(result.next_tlid, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.next_tlid = None
|
||||||
|
|
||||||
|
def test_next_tlid_invalid(self):
|
||||||
|
val = -1
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
TracklistState(next_tlid=val)
|
||||||
|
|
||||||
|
def test_tracks(self):
|
||||||
|
tracks = (TlTrack(), TlTrack())
|
||||||
|
result = TracklistState(tl_tracks=tracks)
|
||||||
|
self.assertEqual(result.tl_tracks, tracks)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.tl_tracks = None
|
||||||
|
|
||||||
|
def test_tracks_invalid(self):
|
||||||
|
tracks = (Track(), Track())
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
TracklistState(tl_tracks=tracks)
|
||||||
|
|
||||||
|
def test_to_json_and_back(self):
|
||||||
|
result = TracklistState(tl_tracks=(TlTrack(), TlTrack()), next_tlid=4)
|
||||||
|
serialized = json.dumps(result, cls=ModelJSONEncoder)
|
||||||
|
deserialized = json.loads(serialized, object_hook=model_json_decoder)
|
||||||
|
self.assertEqual(result, deserialized)
|
||||||
@ -4,9 +4,8 @@ import json
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mopidy.models import (
|
from mopidy.models import (
|
||||||
Album, Artist, HistoryState, HistoryTrack, Image, MixerState,
|
Album, Artist, Image, ModelJSONEncoder, Playlist,
|
||||||
ModelJSONEncoder, PlaybackState, Playlist,
|
Ref, SearchResult, TlTrack, Track, model_json_decoder)
|
||||||
Ref, SearchResult, TlTrack, Track, TracklistState, model_json_decoder)
|
|
||||||
|
|
||||||
|
|
||||||
class InheritanceTest(unittest.TestCase):
|
class InheritanceTest(unittest.TestCase):
|
||||||
@ -1169,164 +1168,3 @@ class SearchResultTest(unittest.TestCase):
|
|||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
{'__model__': 'SearchResult', 'uri': 'uri'},
|
{'__model__': 'SearchResult', 'uri': 'uri'},
|
||||||
SearchResult(uri='uri').serialize())
|
SearchResult(uri='uri').serialize())
|
||||||
|
|
||||||
|
|
||||||
class HistoryTrackTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_track(self):
|
|
||||||
track = Ref.track()
|
|
||||||
result = HistoryTrack(track=track)
|
|
||||||
self.assertEqual(result.track, track)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.track = None
|
|
||||||
|
|
||||||
def test_timestamp(self):
|
|
||||||
timestamp = 1234
|
|
||||||
result = HistoryTrack(timestamp=timestamp)
|
|
||||||
self.assertEqual(result.timestamp, timestamp)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.timestamp = None
|
|
||||||
|
|
||||||
|
|
||||||
class HistoryStateTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_history_list(self):
|
|
||||||
history = (HistoryTrack(),
|
|
||||||
HistoryTrack())
|
|
||||||
result = HistoryState(history=history)
|
|
||||||
self.assertEqual(result.history, history)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.history = None
|
|
||||||
|
|
||||||
def test_history_string_fail(self):
|
|
||||||
history = 'not_a_valid_history'
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
HistoryState(history=history)
|
|
||||||
|
|
||||||
|
|
||||||
class MixerStateTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_volume(self):
|
|
||||||
volume = 37
|
|
||||||
result = MixerState(volume=volume)
|
|
||||||
self.assertEqual(result.volume, volume)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.volume = None
|
|
||||||
|
|
||||||
def test_volume_invalid(self):
|
|
||||||
volume = 105
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
MixerState(volume=volume)
|
|
||||||
|
|
||||||
|
|
||||||
class PlaybackStateTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_position(self):
|
|
||||||
position = 123456
|
|
||||||
result = PlaybackState(position=position)
|
|
||||||
self.assertEqual(result.position, position)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.position = None
|
|
||||||
|
|
||||||
def test_position_invalid(self):
|
|
||||||
position = -1
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
PlaybackState(position=position)
|
|
||||||
|
|
||||||
def test_tl_track(self):
|
|
||||||
tl_track = TlTrack()
|
|
||||||
result = PlaybackState(tl_track=tl_track)
|
|
||||||
self.assertEqual(result.tl_track, tl_track)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.tl_track = None
|
|
||||||
|
|
||||||
def test_tl_track_none(self):
|
|
||||||
tl_track = None
|
|
||||||
result = PlaybackState(tl_track=tl_track)
|
|
||||||
self.assertEqual(result.tl_track, tl_track)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.tl_track = None
|
|
||||||
|
|
||||||
def test_tl_track_invalid(self):
|
|
||||||
tl_track = Track()
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
PlaybackState(tl_track=tl_track)
|
|
||||||
|
|
||||||
def test_state(self):
|
|
||||||
state = 'playing'
|
|
||||||
result = PlaybackState(state=state)
|
|
||||||
self.assertEqual(result.state, state)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.state = None
|
|
||||||
|
|
||||||
def test_state_invalid(self):
|
|
||||||
state = 'not_a_state'
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
PlaybackState(state=state)
|
|
||||||
|
|
||||||
|
|
||||||
class TracklistStateTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_repeat_true(self):
|
|
||||||
repeat = True
|
|
||||||
result = TracklistState(repeat=repeat)
|
|
||||||
self.assertEqual(result.repeat, repeat)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.repeat = None
|
|
||||||
|
|
||||||
def test_repeat_false(self):
|
|
||||||
repeat = False
|
|
||||||
result = TracklistState(repeat=repeat)
|
|
||||||
self.assertEqual(result.repeat, repeat)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.repeat = None
|
|
||||||
|
|
||||||
def test_repeat_invalid(self):
|
|
||||||
repeat = 33
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
TracklistState(repeat=repeat)
|
|
||||||
|
|
||||||
def test_consume_true(self):
|
|
||||||
val = True
|
|
||||||
result = TracklistState(consume=val)
|
|
||||||
self.assertEqual(result.consume, val)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.repeat = None
|
|
||||||
|
|
||||||
def test_random_true(self):
|
|
||||||
val = True
|
|
||||||
result = TracklistState(random=val)
|
|
||||||
self.assertEqual(result.random, val)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.random = None
|
|
||||||
|
|
||||||
def test_single_true(self):
|
|
||||||
val = True
|
|
||||||
result = TracklistState(single=val)
|
|
||||||
self.assertEqual(result.single, val)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.single = None
|
|
||||||
|
|
||||||
def test_next_tlid(self):
|
|
||||||
val = 654
|
|
||||||
result = TracklistState(next_tlid=val)
|
|
||||||
self.assertEqual(result.next_tlid, val)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.next_tlid = None
|
|
||||||
|
|
||||||
def test_next_tlid_invalid(self):
|
|
||||||
val = -1
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
TracklistState(next_tlid=val)
|
|
||||||
|
|
||||||
def test_tracks(self):
|
|
||||||
tracks = (TlTrack(), TlTrack())
|
|
||||||
result = TracklistState(tl_tracks=tracks)
|
|
||||||
self.assertEqual(result.tl_tracks, tracks)
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
result.tl_tracks = None
|
|
||||||
|
|
||||||
def test_tracks_invalid(self):
|
|
||||||
tracks = (Track(), Track())
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
TracklistState(tl_tracks=tracks)
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user