From b75c2a67f8dc7243dfedd5a5492ef6176488c15d Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Wed, 23 Oct 2019 17:03:00 +1300 Subject: [PATCH] Beginning the upgrade of Snapcast with docker --- docker-compose.example.yml | 4 +- docker/mopidy.example.conf | 7 +- mopidy_iris/__init__.py | 14 +-- mopidy_iris/core.py | 42 +------- mopidy_iris/ext.conf | 4 - mopidy_iris/snapcast.py | 188 --------------------------------- mopidy_iris/snapcast_thread.py | 31 ------ src/js/store/index.js | 5 + 8 files changed, 17 insertions(+), 278 deletions(-) delete mode 100755 mopidy_iris/snapcast.py delete mode 100755 mopidy_iris/snapcast_thread.py diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 12ac8c02..1caa6f35 100755 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -1,10 +1,10 @@ version: "3" services: snapserver: - image: currucuxu/snapcastserver + image: jaedb/snapserver ports: - 1704:1704 - - 1705:1705 + - 1780:1780 volumes: - HOST_SNAPCAST_TEMP:/tmp - ./docker/snapserver.conf:/etc/default/snapserver.conf diff --git a/docker/mopidy.example.conf b/docker/mopidy.example.conf index 3fc01c47..20efc919 100755 --- a/docker/mopidy.example.conf +++ b/docker/mopidy.example.conf @@ -16,9 +16,4 @@ library = images media_dir = /var/lib/mopidy/media [local-images] -library = sqlite - -[iris] -snapcast_enabled = true -snapcast_host = snapserver -snapcast_port = 1705 \ No newline at end of file +library = sqlite \ No newline at end of file diff --git a/mopidy_iris/__init__.py b/mopidy_iris/__init__.py index 3e493946..4fb6e4d0 100755 --- a/mopidy_iris/__init__.py +++ b/mopidy_iris/__init__.py @@ -37,14 +37,10 @@ class Extension( ext.Extension ): schema['spotify_authorization_url'] = config.String() schema['lastfm_authorization_url'] = config.String() schema['genius_authorization_url'] = config.String() - schema['snapcast_enabled'] = config.Boolean() - schema['snapcast_host'] = config.String() - schema['snapcast_port'] = config.Integer() - schema['snapcast_buffer_size'] = config.Integer() return schema def setup(self, registry): - + # Add web extension registry.add('http:app', { 'name': self.ext_name, @@ -78,10 +74,10 @@ class ReactRouterHandler(tornado.web.StaticFileHandler): def iris_factory(config, core): path = os.path.join( os.path.dirname(__file__), 'static') - + return [ ( - r"/images/(.*)", + r"/images/(.*)", tornado.web.StaticFileHandler, { 'path': config['local-images']['image_dir'] @@ -89,7 +85,7 @@ def iris_factory(config, core): ), ( r'/http/([^/]*)', - handlers.HttpHandler, + handlers.HttpHandler, { 'core': core, 'config': config @@ -98,7 +94,7 @@ def iris_factory(config, core): ( r'/ws/?', handlers.WebsocketHandler, - { + { 'core': core, 'config': config } diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py index 5b70d50a..e84350fd 100755 --- a/mopidy_iris/core.py +++ b/mopidy_iris/core.py @@ -14,8 +14,6 @@ from pkg_resources import parse_version from tornado.escape import json_encode, json_decode from .system import IrisSystemThread -from .snapcast import IrisSnapcast -from .snapcast_thread import IrisSnapcastThread if sys.platform == 'win32': import ctypes @@ -37,7 +35,6 @@ class IrisCore(pykka.ThreadingActor): "seed_tracks": [], "results": [] } - snapcast_daemon = False ## @@ -49,43 +46,13 @@ class IrisCore(pykka.ThreadingActor): # Load our commands from file self.commands = self.load_from_file('commands') - # Start our TCP watcher with no request, so it becomes our - # long-running socket connection - if self.config['iris'].get('snapcast_enabled'): - self.snapcast_daemon = IrisSnapcastThread(self.config, self.broadcast) - self.snapcast_daemon.start() - - ## + ## # Mopidy is shutting down ## def stop(self): logger.info('Stopping Iris') - if self.snapcast_daemon: - logger.info('Stoppping Snapcast daemon') - self.snapcast_daemon.close() - - - ## - # Make a request to snapcast - ## - def snapcast(self, *args, **kwargs): - callback = kwargs.get('callback', None) - request_id = kwargs.get('request_id', None) - data = kwargs.get('data', {}) - - # Start a new thread, just for this request - socket = IrisSnapcast(self.config) - socket.connect() - - response = socket.request(data) - - if (callback): - callback(response) - else: - return response - ## # Save dict object to disk @@ -431,8 +398,7 @@ class IrisCore(pykka.ThreadingActor): "locale": self.config['iris']['locale'], "spotify_authorization_url": self.config['iris']['spotify_authorization_url'], "lastfm_authorization_url": self.config['iris']['lastfm_authorization_url'], - "genius_authorization_url": self.config['iris']['genius_authorization_url'], - "snapcast_enabled": self.config['iris']['snapcast_enabled'] + "genius_authorization_url": self.config['iris']['genius_authorization_url'] } } @@ -939,7 +905,7 @@ class IrisCore(pykka.ThreadingActor): request = tornado.httpclient.HTTPRequest(command['url'], connect_timeout=5, method='POST', body=post_data, validate_cert=False, headers=headers) else: request = tornado.httpclient.HTTPRequest(command['url'], connect_timeout=5, validate_cert=False, headers=headers) - + # Make the request, and handle any request errors try: command_response = http_client.fetch(request) @@ -969,7 +935,7 @@ class IrisCore(pykka.ThreadingActor): 'message': 'Command run', 'response': command_response_body } - + if (callback): callback(response) return diff --git a/mopidy_iris/ext.conf b/mopidy_iris/ext.conf index 90826452..0e308beb 100755 --- a/mopidy_iris/ext.conf +++ b/mopidy_iris/ext.conf @@ -5,7 +5,3 @@ locale = en_NZ spotify_authorization_url = https://jamesbarnsley.co.nz/iris/auth_spotify.php lastfm_authorization_url = https://jamesbarnsley.co.nz/iris/auth_lastfm.php genius_authorization_url = https://jamesbarnsley.co.nz/iris/auth_genius.php -snapcast_enabled = false -snapcast_host = localhost -snapcast_port = 1705 -snapcast_buffer_size = 8192 diff --git a/mopidy_iris/snapcast.py b/mopidy_iris/snapcast.py deleted file mode 100755 index 004004c2..00000000 --- a/mopidy_iris/snapcast.py +++ /dev/null @@ -1,188 +0,0 @@ - -from threading import Thread -import os, logging, subprocess, socket, json, random, string, select - -# import logger -logger = logging.getLogger(__name__) - -class IrisSnapcast(object): - - socket = None - listen = False - - def __init__(self, config): - self.config = config - self.path = os.path.dirname(__file__) - - - ## - # This thread has been started - ## - def connect(self): - - host = str(self.config['iris']['snapcast_host']) - port = int(self.config['iris']['snapcast_port']) - logger.debug("Connecting to Snapcast on "+host+":"+str(port)) - - try: - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.settimeout(5) - self.sock.connect((host, port)) - - except socket.gaierror, e: - raise Exception(e); - - except socket.error, e: - raise Exception(e); - - - ## - # Listen for socket activity - # Broadcast events to the piped broadcaster - # - # @param broadcast = Method - ## - def listen(self, broadcast): - - try: - self.connect() - except Exception, e: - logger.error("Could not connect to Snapcast: "+str(e)) - return - - self.listen = True - buffer_size = int(self.config['iris']['snapcast_buffer_size']) - - logger.info("Snapcast listener established") - broadcast(data={'method':'snapcast_connected'}) - - messages = [] - data = "" - select.select([], [self.sock], []) - - while self.listen: - - # This loop will run constantly in the background, so we need a relatively short - # timeout as this would otherwise block the shutdown sequence - timeout = 1 - - readlist, writelist, exceptionlist = select.select([self.sock], [], [], timeout) - - # Check if we've got any lists - if [readlist, writelist, exceptionlist] != [[], [], []]: - - # Rread and print the available data on any of the read list - for socket in readlist: - - message = socket.recv(buffer_size) - - try: - message = json.loads(message) - - # Prefix with snapcast - message['method'] = "snapcast_"+message['method'] - - # Broadcast to all clients - broadcast(data=message) - except: - logger.error("Malformed Snapcast message: "+message) - - - ## - # Stop our listener - # - # @param broadcast = Method - ## - def stop_listening(self): - self.listen = False - - - ## - # Send a single request to Snapcast - # - # @param data = Dict - ## - def request(self, data): - - # No socket to make request on - if self.sock == None: - raise Exception("Socket not established"); - - # Construct our request, based on the provided data - request = { - 'id': self.generateGuid(), - 'jsonrpc': '2.0', - 'method': data['method'], - 'params': data['params'] if 'params' in data else {} - } - - logger.debug("Outgoing Snapcast message") - logger.debug(request) - - # Convert to string. For some really nuts reason we need an extra trailing curly brace... - request = json.dumps(request)+'}' - - # Attempt to send the request - try: - self.sock.send(request.encode('ascii')+b"\n") - - except socket.error, e: - logger.error("Iris could not send request to Snapcast: %s" % e) - - socket_response = { - 'error': { - 'message': "Failed to send request to Snapcast", - 'data': str(e) - } - } - - # Wait for response - while True: - - try: - response = self.sock.recv(8192) - - except socket.error, e: - logger.error("Iris failed to receive Snapcast response: %s" % e) - socket_response = { - 'error': { - 'message': "Failed to receive Snapcast response", - 'data': str(e) - } - } - - if not len(response): - break - - try: - response = json.loads(response) - logger.debug("Incoming Snapcast message") - logger.debug(response) - - if 'result' in response: - socket_response = response['result'] - else: - socket_response = response['error'] - - except: - logger.error("Iris received malformed Snapcast response: "+response) - socket_response = { - 'error': { - 'message': "Malformed Snapcast response", - 'data': response - } - } - - return socket_response - - - ## - # Generate a random string - # - # Used for connection_ids where none is provided by client - # @return string - ## - def generateGuid(self): - length = 12 - return ''.join(random.choice(string.lowercase) for i in range(length)) - diff --git a/mopidy_iris/snapcast_thread.py b/mopidy_iris/snapcast_thread.py deleted file mode 100755 index c6de9092..00000000 --- a/mopidy_iris/snapcast_thread.py +++ /dev/null @@ -1,31 +0,0 @@ - -from threading import Thread -import os, logging, subprocess, socket, json, random, string -from .snapcast import IrisSnapcast - -# import logger -logger = logging.getLogger(__name__) - -class IrisSnapcastThread(Thread): - - sock = None - - def __init__(self, config, broadcast): - Thread.__init__(self) - self.config = config - self.broadcast = broadcast - - - ## - # This thread has been started - ## - def run(self): - self.sock = IrisSnapcast(self.config) - self.sock.listen(self.broadcast) - - - ## - # Instruction to stop this thread - ## - def close(self): - self.sock.stop_listening() diff --git a/src/js/store/index.js b/src/js/store/index.js index bd146426..098f8684 100755 --- a/src/js/store/index.js +++ b/src/js/store/index.js @@ -95,6 +95,11 @@ let state = { enabled: false, }, snapcast: { + enabled: false, + connected: false, + host: window.location.hostname, + port: '1780', + ssl: (window.location.protocol === 'https:'), streams: {}, groups: {}, clients: {},