REFACTOR: Make registry & tag handling simpler
This commit is contained in:
parent
022db5998f
commit
68cce201b0
|
@ -11,46 +11,62 @@ import os
|
|||
import json
|
||||
from logging import getLogger
|
||||
|
||||
from mopidy_pummeluff import tags
|
||||
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class RegistryDict(dict):
|
||||
'''
|
||||
Simple tag registry based on Python's internal :py:class:`dict` class,
|
||||
which reads and writes the registry from/to disk.
|
||||
Class which can be used to retreive and write RFID tags to the registry.
|
||||
'''
|
||||
|
||||
registry_path = '/var/lib/mopidy/pummeluff/tags.json'
|
||||
|
||||
def __init__(self):
|
||||
super(RegistryDict, self).__init__(self)
|
||||
'''
|
||||
Constructor.
|
||||
|
||||
Automatically reads the registry if it exists.
|
||||
'''
|
||||
super().__init__()
|
||||
|
||||
if os.path.exists(self.registry_path):
|
||||
self.read()
|
||||
else:
|
||||
LOGGER.warning('Registry not existing yet on "%s"', self.registry_path)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return super(RegistryDict, self).__getitem__(str(key))
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
super(RegistryDict, self).__setitem__(str(key), item)
|
||||
self.write()
|
||||
|
||||
def get(self, key, default=None):
|
||||
@classmethod
|
||||
def unserialize_item(cls, item):
|
||||
'''
|
||||
Return the value for ``key`` if ``key`` is in the dictionary, else
|
||||
``default``.
|
||||
Unserialize an item from the persistent storage on filesystem to a
|
||||
native tag.
|
||||
|
||||
:param str key: The key
|
||||
:param default: The default value
|
||||
:type default: mixed
|
||||
:param tuple item: The item
|
||||
|
||||
:return: The value
|
||||
:rtype: mixed
|
||||
:return: The tag
|
||||
:rtype: tags.tag
|
||||
'''
|
||||
return super(RegistryDict, self).get(str(key), default)
|
||||
return item['uid'], cls.init_tag(**item)
|
||||
|
||||
@classmethod
|
||||
def init_tag(cls, tag_class, uid, alias=None, parameter=None):
|
||||
'''
|
||||
Initialise a new tag instance.
|
||||
|
||||
:param str tag_class: The tag class
|
||||
:param str uid: The RFID UID
|
||||
:param str alias: The alias
|
||||
:param str parameter: The parameter
|
||||
|
||||
:return: The tag instance
|
||||
:rtype: tags.Tag
|
||||
'''
|
||||
uid = str(uid).strip()
|
||||
tag_class = getattr(tags, tag_class)
|
||||
|
||||
return tag_class(uid, alias, parameter)
|
||||
|
||||
def read(self):
|
||||
'''
|
||||
|
@ -63,7 +79,7 @@ class RegistryDict(dict):
|
|||
with open(self.registry_path) as f:
|
||||
data = json.load(f)
|
||||
self.clear()
|
||||
self.update(data)
|
||||
self.update((self.unserialize_item(item) for item in data))
|
||||
|
||||
def write(self):
|
||||
'''
|
||||
|
@ -78,7 +94,35 @@ class RegistryDict(dict):
|
|||
os.makedirs(directory)
|
||||
|
||||
with open(config, 'w') as f:
|
||||
json.dump(self, f, indent=4)
|
||||
json.dump([tag.dict for tag in self.values()], f, indent=4)
|
||||
|
||||
def register(self, tag_class, uid, alias=None, parameter=None):
|
||||
'''
|
||||
Register a new tag in the registry.
|
||||
|
||||
:param str tag_class: The tag class
|
||||
:param str uid: The UID
|
||||
:param str alias: The alias
|
||||
:param str parameter: The parameter (optional)
|
||||
|
||||
:return: The tag
|
||||
:rtype: tags.Tag
|
||||
'''
|
||||
LOGGER.info('Registering %s tag %s with parameter "%s"', tag_class, uid, parameter)
|
||||
|
||||
tag = self.init_tag(
|
||||
tag_class=tag_class,
|
||||
uid=uid,
|
||||
alias=alias,
|
||||
parameter=parameter
|
||||
)
|
||||
|
||||
tag.validate()
|
||||
|
||||
self[uid] = tag
|
||||
self.write()
|
||||
|
||||
return tag
|
||||
|
||||
|
||||
REGISTRY = RegistryDict()
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff tags.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Tag',
|
||||
'TracklistTag',
|
||||
'VolumeTag',
|
||||
'PlayPauseTag',
|
||||
'StopTag',
|
||||
'ShutdownTag',
|
||||
)
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from .registry import REGISTRY
|
||||
from . import actions
|
||||
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class InvalidTagType(Exception):
|
||||
'''
|
||||
Exception which is thrown when an invalid tag type is defined.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class Tag:
|
||||
'''
|
||||
Base RFID tag class, which will implement the factory pattern in Python's
|
||||
own :py:meth:`__new__` method.
|
||||
'''
|
||||
parameter_allowed = True
|
||||
|
||||
def __new__(cls, uid):
|
||||
'''
|
||||
Implement factory pattern and return correct tag instance.
|
||||
'''
|
||||
tag = REGISTRY.get(uid, {})
|
||||
new_cls = cls.get_class(tag.get('type', ''))
|
||||
|
||||
if cls is Tag and cls is not new_cls:
|
||||
instance = new_cls(uid=uid)
|
||||
else:
|
||||
instance = super().__new__(cls, uid=uid)
|
||||
|
||||
instance.registered = bool(tag)
|
||||
instance.alias = tag.get('alias')
|
||||
instance.parameter = tag.get('parameter')
|
||||
|
||||
return instance
|
||||
|
||||
def __init__(self, uid):
|
||||
self.uid = uid
|
||||
self.scanned = None
|
||||
|
||||
def __str__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
identifier = self.alias or self.uid
|
||||
return '<{}: {}>'.format(cls_name, identifier)
|
||||
|
||||
def __call__(self, core):
|
||||
'''
|
||||
Action method which is called when the tag is detected on the RFID
|
||||
reader.
|
||||
|
||||
:param mopidy.core.Core core: The mopidy core instance
|
||||
'''
|
||||
args = [core]
|
||||
if self.parameter:
|
||||
args.append(self.parameter)
|
||||
getattr(actions, self.action)(*args)
|
||||
|
||||
@staticmethod
|
||||
def get_class(tag_type):
|
||||
'''
|
||||
Return class for specific tag type.
|
||||
|
||||
:param str tag_type: The tag type
|
||||
|
||||
:return: The tag class
|
||||
:rtype: type
|
||||
'''
|
||||
try:
|
||||
name = tag_type.title() + 'Tag'
|
||||
cls = globals()[name]
|
||||
assert issubclass(cls, Tag)
|
||||
except (KeyError, AssertionError):
|
||||
raise InvalidTagType('Tag class for type "{}" does\'t exist.'.format(tag_type))
|
||||
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
def get_type(cls, tag_class=None):
|
||||
'''
|
||||
Return the type for a specific tag class.
|
||||
|
||||
:param type tag_class: The tag class
|
||||
|
||||
:return: The tag type
|
||||
:rtype: str
|
||||
'''
|
||||
return (tag_class or cls).__name__[0:-3].lower()
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
'''
|
||||
Return all registered tags in a list.
|
||||
|
||||
:return: Registered tags
|
||||
:rtype: list[Tag]
|
||||
'''
|
||||
return {uid: Tag(uid=uid) for uid in REGISTRY}
|
||||
|
||||
@classmethod
|
||||
def register(cls, uid, alias=None, parameter=None, tag_type=None):
|
||||
'''
|
||||
Register tag in the registry.
|
||||
|
||||
:param str uid: The tag's UID
|
||||
:param str alias: The tag's alias
|
||||
:param str parameter: The optional parameter
|
||||
:param str tag_type: The tag type
|
||||
|
||||
:return: The registered tag
|
||||
:rtype: Tag
|
||||
'''
|
||||
|
||||
if tag_type is None:
|
||||
tag_type = cls.get_type(cls)
|
||||
|
||||
uid = uid.strip()
|
||||
if not uid:
|
||||
error = 'Invalid UID defined'
|
||||
LOGGER.error(error)
|
||||
raise ValueError(error)
|
||||
|
||||
LOGGER.info('Registering %s tag %s with parameter "%s"', tag_type, uid, parameter)
|
||||
|
||||
real_cls = cls.get_class(tag_type)
|
||||
|
||||
if real_cls == Tag:
|
||||
error = 'Registering tags without explicit types are not allowed. ' \
|
||||
'Set tag_type argument on Tag.register() ' \
|
||||
'or use register() method of explicit tag classes.'
|
||||
raise InvalidTagType(error)
|
||||
|
||||
if not real_cls.parameter_allowed and parameter:
|
||||
raise ValueError('No parameter allowed for this tag')
|
||||
elif hasattr(real_cls, 'validate_parameter'):
|
||||
real_cls.validate_parameter(parameter)
|
||||
|
||||
REGISTRY[uid] = {
|
||||
'type': tag_type,
|
||||
'alias': alias.strip(),
|
||||
'parameter': parameter.strip()
|
||||
}
|
||||
|
||||
return Tag.all().get(uid)
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
'''
|
||||
Return the dict version of this tag.
|
||||
|
||||
:return: The dict version of this tag
|
||||
:rtype: dict
|
||||
'''
|
||||
tag_dict = {
|
||||
'uid': self.uid,
|
||||
'alias': self.alias,
|
||||
'type': self.get_type(),
|
||||
'parameter': self.parameter,
|
||||
}
|
||||
|
||||
tag_dict['scanned'] = self.scanned
|
||||
|
||||
return tag_dict
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
'''
|
||||
Return a name of an action (function) defined in the
|
||||
:py:mod:`mopidy_pummeluff.actions` Python module.
|
||||
|
||||
:return: An action name
|
||||
:rtype: str
|
||||
:raises NotImplementedError: When action property isn't defined
|
||||
'''
|
||||
cls = self.__class__.__name__
|
||||
error = 'Missing action property in the %s class'
|
||||
LOGGER.error(error, cls)
|
||||
raise NotImplementedError(error % cls)
|
||||
|
||||
|
||||
class TracklistTag(Tag):
|
||||
'''
|
||||
Replaces the current tracklist with the URI retreived from the tag's
|
||||
parameter.
|
||||
'''
|
||||
action = 'replace_tracklist'
|
||||
|
||||
|
||||
class VolumeTag(Tag):
|
||||
'''
|
||||
Sets the volume to the percentage value retreived from the tag's parameter.
|
||||
'''
|
||||
action = 'set_volume'
|
||||
|
||||
@staticmethod
|
||||
def validate_parameter(parameter):
|
||||
'''
|
||||
Validates if the parameter is an integer between 0 and 100.
|
||||
|
||||
:param mixed parameter: The parameter
|
||||
|
||||
:raises ValueError: When parameter is invalid
|
||||
'''
|
||||
try:
|
||||
number = int(parameter)
|
||||
assert number >= 0 and number <= 100
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError('Volume parameter has to be a number between 0 and 100')
|
||||
|
||||
|
||||
class PlayPauseTag(Tag):
|
||||
'''
|
||||
Pauses or resumes the playback, based on the current state.
|
||||
'''
|
||||
action = 'play_pause'
|
||||
parameter_allowed = False
|
||||
|
||||
|
||||
class StopTag(Tag):
|
||||
'''
|
||||
Stops the playback.
|
||||
'''
|
||||
action = 'stop'
|
||||
parameter_allowed = False
|
||||
|
||||
|
||||
class ShutdownTag(Tag):
|
||||
'''
|
||||
Shutting down the system.
|
||||
'''
|
||||
action = 'shutdown'
|
||||
parameter_allowed = False
|
17
mopidy_pummeluff/tags/__init__.py
Normal file
17
mopidy_pummeluff/tags/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff tags.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Tracklist',
|
||||
'Volume',
|
||||
'PlayPause',
|
||||
'Stop',
|
||||
'Shutdown',
|
||||
)
|
||||
|
||||
from .tracklist import Tracklist
|
||||
from .volume import Volume
|
||||
from .play_pause import PlayPause
|
||||
from .stop import Stop
|
||||
from .shutdown import Shutdown
|
97
mopidy_pummeluff/tags/base.py
Normal file
97
mopidy_pummeluff/tags/base.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff base tag.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Tag',
|
||||
)
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class Tag:
|
||||
'''
|
||||
Base RFID tag class, which will implement the factory pattern in Python's
|
||||
own :py:meth:`__new__` method.
|
||||
'''
|
||||
parameterised = True
|
||||
|
||||
def __init__(self, uid, alias=None, parameter=None):
|
||||
'''
|
||||
Concstructor.
|
||||
'''
|
||||
self.uid = uid
|
||||
self.alias = alias
|
||||
self.parameter = parameter
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
String representation of tag.
|
||||
|
||||
:return: The alias
|
||||
:rtype: str
|
||||
'''
|
||||
return self.alias or self.uid
|
||||
|
||||
def __repr__(self):
|
||||
'''
|
||||
Instance representation of tag.
|
||||
|
||||
:return: The class name and UID
|
||||
:rtype: str
|
||||
'''
|
||||
identifier = self.alias or self.uid
|
||||
return f'<{self.__class__.__name__} {identifier}>'
|
||||
|
||||
def __call__(self, core):
|
||||
'''
|
||||
Action method which is called when the tag is detected on the RFID
|
||||
reader.
|
||||
|
||||
:param mopidy.core.Core core: The mopidy core instance
|
||||
'''
|
||||
args = [core]
|
||||
if self.parameter:
|
||||
args.append(self.parameter)
|
||||
self.action(*args)
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
'''
|
||||
Dict representation of the tag.
|
||||
'''
|
||||
return {
|
||||
'tag_class': self.__class__.__name__,
|
||||
'uid': self.uid,
|
||||
'alias': self.alias or '',
|
||||
'parameter': self.parameter or ''
|
||||
}
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
'''
|
||||
Return a name of an action (function) defined in the
|
||||
:py:mod:`mopidy_pummeluff.actions` Python module.
|
||||
|
||||
:return: An action name
|
||||
:rtype: str
|
||||
:raises NotImplementedError: When action property isn't defined
|
||||
'''
|
||||
cls = self.__class__.__name__
|
||||
error = 'Missing action property in the %s class'
|
||||
LOGGER.error(error, cls)
|
||||
raise NotImplementedError(error % cls)
|
||||
|
||||
def validate(self):
|
||||
'''
|
||||
Validate parameter.
|
||||
|
||||
:raises ValueError: When parameter is not allowed but defined
|
||||
'''
|
||||
if self.parameterised and not self.parameter:
|
||||
raise ValueError('Parameter required for this tag')
|
||||
|
||||
if not self.parameterised and self.parameter:
|
||||
raise ValueError('No parameter allowed for this tag')
|
18
mopidy_pummeluff/tags/play_pause.py
Normal file
18
mopidy_pummeluff/tags/play_pause.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff play pause tag.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'PlayPause',
|
||||
)
|
||||
|
||||
from mopidy_pummeluff.actions import play_pause
|
||||
from .base import Tag
|
||||
|
||||
|
||||
class PlayPause(Tag):
|
||||
'''
|
||||
Pauses or resumes the playback, based on the current state.
|
||||
'''
|
||||
action = play_pause
|
||||
parameterised = False
|
18
mopidy_pummeluff/tags/shutdown.py
Normal file
18
mopidy_pummeluff/tags/shutdown.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff shutdown tag.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Shutdown',
|
||||
)
|
||||
|
||||
from mopidy_pummeluff.actions import shutdown
|
||||
from .base import Tag
|
||||
|
||||
|
||||
class Shutdown(Tag):
|
||||
'''
|
||||
Shutting down the system.
|
||||
'''
|
||||
action = shutdown
|
||||
parameterised = False
|
18
mopidy_pummeluff/tags/stop.py
Normal file
18
mopidy_pummeluff/tags/stop.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff stop tag.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Stop',
|
||||
)
|
||||
|
||||
from mopidy_pummeluff.actions import stop
|
||||
from .base import Tag
|
||||
|
||||
|
||||
class Stop(Tag):
|
||||
'''
|
||||
Stops the playback.
|
||||
'''
|
||||
action = stop
|
||||
parameterised = False
|
19
mopidy_pummeluff/tags/tracklist.py
Normal file
19
mopidy_pummeluff/tags/tracklist.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff tracklist tag.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Tracklist',
|
||||
)
|
||||
|
||||
from mopidy_pummeluff.actions import replace_tracklist
|
||||
from .base import Tag
|
||||
|
||||
|
||||
class Tracklist(Tag):
|
||||
'''
|
||||
Replaces the current tracklist with the URI retreived from the tag's
|
||||
parameter.
|
||||
'''
|
||||
|
||||
action = replace_tracklist
|
34
mopidy_pummeluff/tags/volume.py
Normal file
34
mopidy_pummeluff/tags/volume.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
'''
|
||||
Python module for Mopidy Pummeluff volume tag.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'Volume',
|
||||
)
|
||||
|
||||
from mopidy_pummeluff.actions import set_volume
|
||||
from .base import Tag
|
||||
|
||||
|
||||
class Volume(Tag):
|
||||
'''
|
||||
Sets the volume to the percentage value retreived from the tag's parameter.
|
||||
'''
|
||||
|
||||
action = set_volume
|
||||
|
||||
def validate(self):
|
||||
'''
|
||||
Validates if the parameter is an integer between 0 and 100.
|
||||
|
||||
:param mixed parameter: The parameter
|
||||
|
||||
:raises ValueError: When parameter is invalid
|
||||
'''
|
||||
super().validate()
|
||||
|
||||
try:
|
||||
number = int(self.parameter)
|
||||
assert number >= 0 and number <= 100
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError('Volume parameter has to be a number between 0 and 100')
|
Loading…
Reference in a new issue