diff --git a/.gitignore b/.gitignore index 57c56ef..5c99f15 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,68 @@ -*.pyc -.*.sw[a-z] -*~ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ build/ +develop-eggs/ dist/ -MANIFEST +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# ---> Vim +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + diff --git a/bin/supysonic-cli b/bin/supysonic-cli index c6d70bc..2aba005 100755 --- a/bin/supysonic-cli +++ b/bin/supysonic-cli @@ -20,7 +20,7 @@ # along with this program. If not, see . import sys, cmd, argparse, getpass, time -from supysonic.config import Config +from supysonic import config from supysonic.db import get_store, Folder, User from supysonic.managers.folder import FolderManager @@ -225,10 +225,10 @@ class CLI(cmd.Cmd): print "Successfully changed '{}' password".format(name) if __name__ == "__main__": - if not Config().check(): + if not config.check(): sys.exit(1) - cli = CLI(get_store(Config().get('base', 'database_uri'))) + cli = CLI(get_store(config.get('base', 'database_uri'))) if len(sys.argv) > 1: cli.onecmd(' '.join(sys.argv[1:])) else: diff --git a/supysonic/api/media.py b/supysonic/api/media.py index b880875..87baf65 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -27,7 +27,7 @@ import codecs from xml.etree import ElementTree from supysonic import scanner -from supysonic.config import Config +from supysonic import config from supysonic.web import app, store from supysonic.db import Track, Album, Artist, Folder, User, ClientPrefs, now from . import get_entity @@ -71,14 +71,14 @@ def stream_media(): if format and format != 'raw' and format != src_suffix: dst_suffix = format - dst_mimetype = Config().get_mime(dst_suffix) + dst_mimetype = config.get_mime(dst_suffix) if format != 'raw' and (dst_suffix != src_suffix or dst_bitrate != res.bitrate): - transcoder = Config().get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix)) - decoder = Config().get('transcoding', 'decoder_' + src_suffix) or Config().get('transcoding', 'decoder') - encoder = Config().get('transcoding', 'encoder_' + dst_suffix) or Config().get('transcoding', 'encoder') + transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix)) + decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder') + encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder') if not transcoder and (not decoder or not encoder): - transcoder = Config().get('transcoding', 'transcoder') + transcoder = config.get('transcoding', 'transcoder') if not transcoder: message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix) app.logger.info(message) @@ -154,7 +154,7 @@ def cover_art(): if size > im.size[0] and size > im.size[1]: return send_file(os.path.join(res.path, 'cover.jpg')) - size_path = os.path.join(Config().get('webapp', 'cache_dir'), str(size)) + size_path = os.path.join(config.get('webapp', 'cache_dir'), str(size)) path = os.path.join(size_path, str(res.id)) if os.path.exists(path): return send_file(path) diff --git a/supysonic/config.py b/supysonic/config.py index fc01e33..01013e6 100644 --- a/supysonic/config.py +++ b/supysonic/config.py @@ -15,59 +15,57 @@ import mimetypes import os import tempfile -class Config(object): +# Seek for standard locations +config_file = [ + 'supysonic.conf', + os.path.expanduser('~/.config/supysonic/supysonic.conf'), + os.path.expanduser('~/.supysonic'), + '/etc/supysonic' + ] + +config = ConfigParser({ 'cache_dir': os.path.join(tempfile.gettempdir(), 'supysonic') }) + + +def check(): """ - Config object to work with config file + Checks the config file and mandatory fields """ - def __init__(self): - # Seek for standard locations - config_file = [ - 'supysonic.conf', - os.path.expanduser('~/.config/supysonic/supysonic.conf'), - os.path.expanduser('~/.supysonic'), - '/etc/supysonic' - ] - self.config = ConfigParser({ 'cache_dir': os.path.join(tempfile.gettempdir(), 'supysonic') }) - # Try read config file or raise error - try: - self.config.read(config_file) - except Exception as e: - err = 'Config file is corrupted.\n{0}'.format(e) - raise SystemExit(err) + try: + config.read(config_file) + except Exception as e: + err = 'Config file is corrupted.\n{0}'.format(e) + raise SystemExit(err) - def check(self): - """ - Checks the config for mandatory fields - """ - try: - self.config.get('base', 'database_uri') - except (NoSectionError, NoOptionError): - raise SystemExit('No database URI set') - return True + try: + config.get('base', 'database_uri') + except (NoSectionError, NoOptionError): + raise SystemExit('No database URI set') - def get(self, section, option): - """ - Returns a config option value from config file + return True - :param section: section where the option is stored - :param option: option name - :return: a config option value - :rtype: string - """ - try: - return self.config.get(section, option) - except (NoSectionError, NoOptionError): - return None +def get(section, option): + """ + Returns a config option value from config file - def get_mime(self, extension): - """ - Returns mimetype of an extension based on config file + :param section: section where the option is stored + :param option: option name + :return: a config option value + :rtype: string + """ + try: + return config.get(section, option) + except (NoSectionError, NoOptionError): + return None - :param extension: extension string - :return: mimetype - :rtype: string - """ - guessed_mime = mimetypes.guess_type('dummy.' + extension, False)[0] - config_mime = self.get('mimetypes', extension) - default_mime = 'application/octet-stream' - return guessed_mime or config_mime or default_mime +def get_mime(extension): + """ + Returns mimetype of an extension based on config file + + :param extension: extension string + :return: mimetype + :rtype: string + """ + guessed_mime = mimetypes.guess_type('dummy.' + extension, False)[0] + config_mime = get('mimetypes', extension) + default_mime = 'application/octet-stream' + return guessed_mime or config_mime or default_mime diff --git a/supysonic/db.py b/supysonic/db.py index 59340fd..4daad71 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -27,7 +27,7 @@ from storm.variables import Variable import uuid, datetime, time import os.path -from supysonic.config import Config +from supysonic import config def now(): return datetime.datetime.now().replace(microsecond = 0) @@ -213,7 +213,7 @@ class Track(object): if prefs and prefs.format and prefs.format != self.suffix(): info['transcodedSuffix'] = prefs.format - info['transcodedContentType'] = Config().get_mime(prefs.format) + info['transcodedContentType'] = config.get_mime(prefs.format) return info diff --git a/supysonic/frontend/user.py b/supysonic/frontend/user.py index a427735..86d237d 100644 --- a/supysonic/frontend/user.py +++ b/supysonic/frontend/user.py @@ -24,7 +24,7 @@ from supysonic.web import app, store from supysonic.managers.user import UserManager from supysonic.db import User, ClientPrefs import uuid, csv -from supysonic.config import Config +from supysonic import config from supysonic.lastfm import LastFm @app.before_request @@ -43,12 +43,12 @@ def user_index(): def user_profile(uid): if uid == 'me': prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(session.get('userid'))) - return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = Config().get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) else: if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: return redirect(url_for('index')) prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(uid)) - return render_template('profile.html', user = UserManager.get(store, uid)[1], api_key = Config().get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('profile.html', user = UserManager.get(store, uid)[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user/', methods = [ 'POST' ]) def update_clients(uid): diff --git a/supysonic/lastfm.py b/supysonic/lastfm.py index 0d90da3..5a799f3 100644 --- a/supysonic/lastfm.py +++ b/supysonic/lastfm.py @@ -19,13 +19,13 @@ # along with this program. If not, see . import requests, hashlib -from supysonic.config import Config +from supysonic import config class LastFm: def __init__(self, user, logger): self.__user = user - self.__api_key = Config().get('lastfm', 'api_key') - self.__api_secret = Config().get('lastfm', 'secret') + self.__api_key = config.get('lastfm', 'api_key') + self.__api_secret = config.get('lastfm', 'secret') self.__enabled = self.__api_key is not None and self.__api_secret is not None self.__logger = logger diff --git a/supysonic/scanner.py b/supysonic/scanner.py index c5589d8..30d85f3 100644 --- a/supysonic/scanner.py +++ b/supysonic/scanner.py @@ -25,7 +25,7 @@ import mutagen from storm.expr import ComparableExpr, compile, Like from storm.exceptions import NotSupportedError -from supysonic.config import Config +from supysonic import config from supysonic.db import Folder, Artist, Album, Track, User, PlaylistTrack from supysonic.db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack from supysonic.db import RatingFolder, RatingTrack @@ -63,7 +63,7 @@ class Scanner: self.__deleted_albums = 0 self.__deleted_tracks = 0 - extensions = Config().get('base', 'scanner_extensions') + extensions = config.get('base', 'scanner_extensions') self.__extensions = map(str.lower, extensions.split()) if extensions else None self.__folders_to_check = set() @@ -166,7 +166,7 @@ class Scanner: tr.duration = int(tag.info.length) tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000 - tr.content_type = Config().get_mime(os.path.splitext(path)[1][1:]) + tr.content_type = config.get_mime(os.path.splitext(path)[1][1:]) tr.last_modification = os.path.getmtime(path) tralbum = self.__find_album(albumartist, album) diff --git a/supysonic/watcher.py b/supysonic/watcher.py index 7a43d71..2e43109 100644 --- a/supysonic/watcher.py +++ b/supysonic/watcher.py @@ -27,7 +27,7 @@ from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler from supysonic import db -from supysonic.config import Config +from supysonic import config from supysonic.scanner import Scanner OP_SCAN = 1 @@ -36,7 +36,7 @@ OP_MOVE = 4 class SupysonicWatcherEventHandler(PatternMatchingEventHandler): def __init__(self, queue, logger): - extensions = Config().get('base', 'scanner_extensions') + extensions = config.get('base', 'scanner_extensions') patterns = map(lambda e: "*." + e.lower(), extensions.split()) if extensions else None super(SupysonicWatcherEventHandler, self).__init__(patterns = patterns, ignore_directories = True) @@ -133,7 +133,7 @@ class ScannerProcessingQueue(Thread): continue self.__logger.debug("Instantiating scanner") - store = db.get_store(Config().get('base', 'database_uri')) + store = db.get_store(config.get('base', 'database_uri')) scanner = Scanner(store) item = self.__next_item() @@ -201,17 +201,17 @@ class ScannerProcessingQueue(Thread): class SupysonicWatcher(object): def run(self): - if not Config().check(): + if not config.check(): return logger = logging.getLogger(__name__) - if Config().get('daemon', 'log_file'): - log_handler = TimedRotatingFileHandler(Config().get('daemon', 'log_file'), when = 'midnight') + if config.get('daemon', 'log_file'): + log_handler = TimedRotatingFileHandler(config.get('daemon', 'log_file'), when = 'midnight') else: log_handler = logging.NullHandler() log_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")) logger.addHandler(log_handler) - if Config().get('daemon', 'log_level'): + if config.get('daemon', 'log_level'): mapping = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, @@ -219,9 +219,9 @@ class SupysonicWatcher(object): 'ERROR': logging.ERROR, 'CRTICAL': logging.CRITICAL } - logger.setLevel(mapping.get(Config().get('daemon', 'log_level').upper(), logging.NOTSET)) + logger.setLevel(mapping.get(config.get('daemon', 'log_level').upper(), logging.NOTSET)) - store = db.get_store(Config().get('base', 'database_uri')) + store = db.get_store(config.get('base', 'database_uri')) folders = store.find(db.Folder, db.Folder.root == True) if not folders.count(): diff --git a/supysonic/web.py b/supysonic/web.py index eb1be93..1260882 100644 --- a/supysonic/web.py +++ b/supysonic/web.py @@ -13,13 +13,13 @@ from flask import Flask, g from os import makedirs, path from werkzeug.local import LocalProxy -from supysonic.config import Config +from supysonic import config from supysonic.db import get_store # Supysonic database open def get_db(): if not hasattr(g, 'database'): - g.database = get_store(Config().get('base', 'database_uri')) + g.database = get_store(config.get('base', 'database_uri')) return g.database # Supysonic database close @@ -33,17 +33,17 @@ def create_application(): global app # Check config for mandatory fields - Config().check() + config.check() # Test for the cache directory - if not path.exists(Config().get('webapp', 'cache_dir')): - os.makedirs(Config().get('webapp', 'cache_dir')) + if not path.exists(config.get('webapp', 'cache_dir')): + os.makedirs(config.get('webapp', 'cache_dir')) # Flask! app = Flask(__name__) # Set a secret key for sessions - secret_key = Config().get('base', 'secret_key') + secret_key = config.get('base', 'secret_key') # If secret key is not defined in config, set develop key if secret_key is None: app.secret_key = 'd3v3l0p' @@ -54,11 +54,11 @@ def create_application(): app.teardown_appcontext(close_db) # Set loglevel - if Config().get('webapp', 'log_file'): + if config.get('webapp', 'log_file'): import logging from logging.handlers import TimedRotatingFileHandler - handler = TimedRotatingFileHandler(Config().get('webapp', 'log_file'), when = 'midnight') - if Config().get('webapp', 'log_level'): + handler = TimedRotatingFileHandler(config.get('webapp', 'log_file'), when = 'midnight') + if config.get('webapp', 'log_level'): mapping = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, @@ -66,7 +66,7 @@ def create_application(): 'ERROR': logging.ERROR, 'CRTICAL': logging.CRITICAL } - handler.setLevel(mapping.get(Config().get('webapp', 'log_level').upper(), logging.NOTSET)) + handler.setLevel(mapping.get(config.get('webapp', 'log_level').upper(), logging.NOTSET)) app.logger.addHandler(handler) # Import app sections