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