small refactor
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 15 Apr 2020 22:40:01 +0000 (00:40 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 15 Apr 2020 22:40:01 +0000 (00:40 +0200)
mass.py [deleted file]
music_assistant/__init__.py
music_assistant/__main__.py [new file with mode: 0755]
music_assistant/mass.py [new file with mode: 0644]
requirements.txt
setup.py [new file with mode: 0644]

diff --git a/mass.py b/mass.py
deleted file mode 100755 (executable)
index 8769261..0000000
--- a/mass.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-
-import sys
-import os
-import logging
-from aiorun import run
-import asyncio
-
-logger = logging.getLogger()
-logformat = logging.Formatter('%(asctime)-15s %(levelname)-5s %(name)s.%(module)s -- %(message)s')
-consolehandler = logging.StreamHandler()
-consolehandler.setFormatter(logformat)
-logger.addHandler(consolehandler)
-
-
-def get_config():
-    ''' start config handling '''
-    data_dir = ''
-    debug = False
-    update_latest = False
-    # prefer command line args
-    if len(sys.argv) > 1:
-        data_dir = sys.argv[1]
-    if len(sys.argv) > 2:
-        debug = sys.argv[2] == "debug"
-    if len(sys.argv) > 3:
-        update_latest = sys.argv[3] == "update"
-    # fall back to environment variables (for plain docker)
-    if os.environ.get('mass_datadir'):
-        data_dir = os.environ['mass_datadir']
-    if os.environ.get('mass_debug'):
-        debug = os.environ['mass_debug'].lower() != 'false'
-    if os.environ.get('mass_update'):
-        update_latest = os.environ['mass_update'].lower() != 'false'
-    # hassio config file found
-    conf_file = '/data/options.json'
-    if os.path.isfile(conf_file):
-        try:
-            import json
-            with open(conf_file) as f:
-                conf = json.loads(f.read())
-                data_dir = conf['data_dir']
-                debug = conf['debug_messages']
-                update_latest = conf['use_nightly']
-        except:
-            logger.exception('could not load options.json')
-    return data_dir, debug, update_latest
-
-def do_update():
-    ''' auto update to latest git version '''
-    base_dir = os.path.dirname(os.path.abspath(__file__))
-    if os.path.isdir(".git") or os.path.isdir("%s/.git" % base_dir):
-        # dev environment
-        return
-    logger.info("Updating to latest Git version!")
-    import subprocess
-    # TODO: handle this properly
-    args = """
-        cd %s
-        curl -LOks "https://github.com/marcelveldt/musicassistant/archive/master.zip"
-        unzip -q master.zip
-        pip install -r musicassistant-master/requirements.txt
-        cp -rf musicassistant-master/music_assistant .
-        cp -rf musicassistant-master/mass.py .
-        rm -R musicassistant-master
-    """ % (base_dir, )
-    if subprocess.call(args, shell=True) == 0:
-        logger.info("Update succesfull")
-    else:
-        logger.error("Update failed - do you have curl and zip installed ?")
-
-
-if __name__ == "__main__":
-    # get config
-    data_dir, debug, update_latest = get_config()
-    if update_latest:
-        do_update()
-    # create event_loop with uvloop
-    event_loop = asyncio.get_event_loop()
-    try:
-        import uvloop
-        uvloop.install()
-    except ImportError:
-        # uvloop is not available on Windows so safe to ignore this
-        logger.warning("uvloop support is disabled")
-    # config debug settings if needed
-    if debug:
-        event_loop.set_debug(True)
-        logger.setLevel(logging.DEBUG)
-        logging.getLogger('aiosqlite').setLevel(logging.INFO)
-        logging.getLogger('asyncio').setLevel(logging.WARNING)
-    else:
-        logger.setLevel(logging.INFO)
-    # start music assistant!
-    from music_assistant import MusicAssistant
-    mass = MusicAssistant(data_dir, event_loop)
-    run(mass.start(), loop=event_loop)
-    
\ No newline at end of file
index 628faf69fed7cb5d083cde55f3e93486b1c21235..feeeef76978edba5598ad073ac49d798b1d21312 100644 (file)
@@ -1,111 +1 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-
-import asyncio
-import re
-import os
-import shutil
-import slugify as unicode_slug
-import uuid
-import json
-import time
-import logging
-import threading
-
-from .database import Database
-from .config import MassConfig
-from .utils import run_periodic, LOGGER, try_parse_bool, serialize_values
-from .metadata import MetaData
-from .cache import Cache
-from .music_manager import MusicManager
-from .player_manager import PlayerManager
-from .http_streamer import HTTPStreamer
-from .homeassistant import HomeAssistant
-from .web import Web
-
-
-class MusicAssistant():
-
-    def __init__(self, datapath, event_loop):
-        ''' 
-            Create an instance of MusicAssistant
-            :param datapath: file location to store the data
-            :param event_loop: asyncio event_loop
-        '''
-        self.event_loop = event_loop
-        self.event_loop.set_exception_handler(self.handle_exception)
-        self.datapath = datapath
-        self.event_listeners = {}
-        self.config = MassConfig(self)
-        # init modules
-        self.db = Database(self)
-        self.cache = Cache(self)
-        self.metadata = MetaData(self)
-        self.web = Web(self)
-        self.hass = HomeAssistant(self)
-        self.music = MusicManager(self)
-        self.players = PlayerManager(self)
-        self.http_streamer = HTTPStreamer(self)
-
-    async def start(self):
-        ''' start running the music assistant server '''
-        await self.db.setup()
-        await self.cache.setup()
-        await self.metadata.setup()
-        await self.hass.setup()
-        await self.music.setup()
-        await self.players.setup()
-        await self.web.setup()
-        await self.http_streamer.setup()
-        # wait for exit
-        try:
-            while True:
-                await asyncio.sleep(3600)
-        except asyncio.CancelledError:
-            LOGGER.info("Application shutdown")
-            await self.signal_event("shutdown")
-            self.config.save()
-            await self.db.close()
-            await self.cache.close()
-
-    def handle_exception(self, loop, context):
-        ''' global exception handler '''
-        LOGGER.debug(f"Caught exception: {context}")
-        loop.default_exception_handler(context)
-
-    async def signal_event(self, msg, msg_details=None):
-        ''' signal (systemwide) event '''
-        if not (msg_details == None or isinstance(msg_details, (str, dict))):
-            msg_details = serialize_values(msg_details)
-        listeners = list(self.event_listeners.values())
-        for callback, eventfilter in listeners:
-            if not eventfilter or eventfilter in msg:
-                if msg == 'shutdown':
-                    # the shutdown event should be awaited
-                    await callback(msg, msg_details)
-                else:
-                    self.event_loop.create_task(callback(msg, msg_details))
-
-    async def add_event_listener(self, cb, eventfilter=None):
-        ''' add callback to our event listeners '''
-        cb_id = str(uuid.uuid4())
-        self.event_listeners[cb_id] = (cb, eventfilter)
-        return cb_id
-
-    async def remove_event_listener(self, cb_id):
-        ''' remove callback from our event listeners '''
-        self.event_listeners.pop(cb_id, None)
-
-    def run_task(self, corofcn, wait_for_result=False, ignore_exception=None):
-        ''' helper to run a task on the main event loop from another thread '''
-        if threading.current_thread() is threading.main_thread():
-            raise Exception("Can not be called from main event loop!")
-        future = asyncio.run_coroutine_threadsafe(corofcn, self.event_loop)
-        if wait_for_result:
-            try:
-                return future.result()
-            except Exception as exc:
-                if ignore_exception and isinstance(exc, ignore_exception):
-                    return None
-                raise exc
-        return future
+"""Init file for Music Assistant."""
\ No newline at end of file
diff --git a/music_assistant/__main__.py b/music_assistant/__main__.py
new file mode 100755 (executable)
index 0000000..1639fb7
--- /dev/null
@@ -0,0 +1,80 @@
+"""Start Music Assistant."""
+import argparse
+import platform
+import sys
+import os
+import logging
+import asyncio
+from aiorun import run
+
+from music_assistant.mass import MusicAssistant
+
+
+def get_arguments():
+    """Arguments handling."""
+    parser = argparse.ArgumentParser(description="MusicAssistant")
+
+    data_dir = os.getenv("APPDATA") if os.name == "nt" else os.path.expanduser("~")
+    data_dir = os.path.join(data_dir, ".musicassistant")
+    if not os.path.isdir(data_dir):
+        os.makedirs(data_dir)
+
+    parser.add_argument(
+        "-c",
+        "--config",
+        metavar="path_to_config_dir",
+        default=data_dir,
+        help="Directory that contains the MusicAssistant configuration",
+    )
+    parser.add_argument(
+        "--debug", default=False, help="Start MusicAssistant with verbose debug logging"
+    )
+    arguments = parser.parse_args()
+    return arguments
+
+
+def main():
+    """Start MusicAssistant."""
+    # setup logger
+    logger = logging.getLogger()
+    logformat = logging.Formatter(
+        "%(asctime)-15s %(levelname)-5s %(name)s.%(module)s -- %(message)s"
+    )
+    consolehandler = logging.StreamHandler()
+    consolehandler.setFormatter(logformat)
+    logger.addHandler(consolehandler)
+
+    # parse arguments
+    args = get_arguments()
+    data_dir = args.config
+    # create event_loop with uvloop
+    event_loop = asyncio.get_event_loop()
+    try:
+        import uvloop
+
+        uvloop.install()
+    except ImportError:
+        # uvloop is not available on Windows so safe to ignore this
+        logger.warning("uvloop support is disabled")
+    # config debug settings if needed
+    if args.debug:
+        event_loop.set_debug(True)
+        logger.setLevel(logging.DEBUG)
+        logging.getLogger("aiosqlite").setLevel(logging.INFO)
+        logging.getLogger("asyncio").setLevel(logging.WARNING)
+    else:
+        logger.setLevel(logging.INFO)
+
+    mass = MusicAssistant(data_dir, event_loop)
+
+    # run UI in browser on windows and macos only
+    if platform.system() in ["Windows", "Darwin"]:
+        import webbrowser
+
+        webbrowser.open(f"http://localhost:{mass.web.http_port}")
+
+    run(mass.start(), loop=event_loop)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/music_assistant/mass.py b/music_assistant/mass.py
new file mode 100644 (file)
index 0000000..628faf6
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+import asyncio
+import re
+import os
+import shutil
+import slugify as unicode_slug
+import uuid
+import json
+import time
+import logging
+import threading
+
+from .database import Database
+from .config import MassConfig
+from .utils import run_periodic, LOGGER, try_parse_bool, serialize_values
+from .metadata import MetaData
+from .cache import Cache
+from .music_manager import MusicManager
+from .player_manager import PlayerManager
+from .http_streamer import HTTPStreamer
+from .homeassistant import HomeAssistant
+from .web import Web
+
+
+class MusicAssistant():
+
+    def __init__(self, datapath, event_loop):
+        ''' 
+            Create an instance of MusicAssistant
+            :param datapath: file location to store the data
+            :param event_loop: asyncio event_loop
+        '''
+        self.event_loop = event_loop
+        self.event_loop.set_exception_handler(self.handle_exception)
+        self.datapath = datapath
+        self.event_listeners = {}
+        self.config = MassConfig(self)
+        # init modules
+        self.db = Database(self)
+        self.cache = Cache(self)
+        self.metadata = MetaData(self)
+        self.web = Web(self)
+        self.hass = HomeAssistant(self)
+        self.music = MusicManager(self)
+        self.players = PlayerManager(self)
+        self.http_streamer = HTTPStreamer(self)
+
+    async def start(self):
+        ''' start running the music assistant server '''
+        await self.db.setup()
+        await self.cache.setup()
+        await self.metadata.setup()
+        await self.hass.setup()
+        await self.music.setup()
+        await self.players.setup()
+        await self.web.setup()
+        await self.http_streamer.setup()
+        # wait for exit
+        try:
+            while True:
+                await asyncio.sleep(3600)
+        except asyncio.CancelledError:
+            LOGGER.info("Application shutdown")
+            await self.signal_event("shutdown")
+            self.config.save()
+            await self.db.close()
+            await self.cache.close()
+
+    def handle_exception(self, loop, context):
+        ''' global exception handler '''
+        LOGGER.debug(f"Caught exception: {context}")
+        loop.default_exception_handler(context)
+
+    async def signal_event(self, msg, msg_details=None):
+        ''' signal (systemwide) event '''
+        if not (msg_details == None or isinstance(msg_details, (str, dict))):
+            msg_details = serialize_values(msg_details)
+        listeners = list(self.event_listeners.values())
+        for callback, eventfilter in listeners:
+            if not eventfilter or eventfilter in msg:
+                if msg == 'shutdown':
+                    # the shutdown event should be awaited
+                    await callback(msg, msg_details)
+                else:
+                    self.event_loop.create_task(callback(msg, msg_details))
+
+    async def add_event_listener(self, cb, eventfilter=None):
+        ''' add callback to our event listeners '''
+        cb_id = str(uuid.uuid4())
+        self.event_listeners[cb_id] = (cb, eventfilter)
+        return cb_id
+
+    async def remove_event_listener(self, cb_id):
+        ''' remove callback from our event listeners '''
+        self.event_listeners.pop(cb_id, None)
+
+    def run_task(self, corofcn, wait_for_result=False, ignore_exception=None):
+        ''' helper to run a task on the main event loop from another thread '''
+        if threading.current_thread() is threading.main_thread():
+            raise Exception("Can not be called from main event loop!")
+        future = asyncio.run_coroutine_threadsafe(corofcn, self.event_loop)
+        if wait_for_result:
+            try:
+                return future.result()
+            except Exception as exc:
+                if ignore_exception and isinstance(exc, ignore_exception):
+                    return None
+                raise exc
+        return future
index 3389454e1f7c53c9d6d7a096f32745310f04aa57..efbc9cd739c64b07e5b3a421b0cf9f2a565a2463 100755 (executable)
@@ -1,10 +1,10 @@
+argparse
 cytoolz
 aiohttp[speedups]
 requests
 spotify_token
 protobuf
 pychromecast
-uvloop
 asyncio_throttle
 aiocometd
 aiosqlite
@@ -19,4 +19,5 @@ aiorun
 soco
 pillow
 aiohttp_cors
-unidecode
\ No newline at end of file
+unidecode
+webbrowser
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..d037761
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,34 @@
+# Upload to PyPI Live
+# sudo python3 setup.py sdist bdist_wheel
+# sudo python3 -m twine upload dist/*
+
+import setuptools
+import os
+
+VERSION = "0.0.20"
+NAME = "music_assistant"
+
+with open("README.md", "r") as fh:
+    LONG_DESC = fh.read()
+
+with open('requirements.txt') as f:
+    INSTALL_REQUIRES = f.read().splitlines()
+if os.name != "nt":
+    INSTALL_REQUIRES.append("uvloop")
+
+setuptools.setup(
+    name=NAME,
+    version=VERSION,
+    author='Marcel van der Veldt',
+    author_email='marcelveldt@users.noreply.github.com',
+    description='Music library manager and player based on sox.',
+    long_description=LONG_DESC,
+    long_description_content_type="text/markdown",
+    url = 'https://github.com/marcelveldt/musicassistant.git',
+    packages=['music_assistant'],
+    classifiers=(
+        "Programming Language :: Python :: 3",
+        "Operating System :: OS Independent",
+    ),
+    install_requires=INSTALL_REQUIRES,
+    )
\ No newline at end of file