mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-21 02:55:31 +01:00
Merge remote-tracking branch 'maxkoryukov/feature/softchroot' into develop
This commit is contained in:
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,3 +1,13 @@
|
||||
[Tt]est[Rr]esult*
|
||||
/cache
|
||||
/logs
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# coverage generated:
|
||||
/cover-html/
|
||||
.coverage
|
||||
.coveralls.yml
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
@@ -61,9 +71,4 @@ Thumbs.db
|
||||
obj/
|
||||
[Rr]elease*/
|
||||
_ReSharper*/
|
||||
[Tt]est[Rr]esult*
|
||||
/cache
|
||||
/logs
|
||||
.project
|
||||
.pydevproject
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
23
.travis.yml
23
.travis.yml
@@ -13,14 +13,25 @@ cache:
|
||||
# http://about.travis-ci.org/docs/user/ci-environment/#Python-VM-images
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
matrix:
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: SENDCOVERAGE=1
|
||||
|
||||
# pylint 1.4 does not run under python 2.6
|
||||
install:
|
||||
- pip install pyOpenSSL
|
||||
- pip install pylint==1.3.1
|
||||
- pip install pyflakes
|
||||
- pip install pep8
|
||||
- pip install pyOpenSSL
|
||||
- pip install pylint==1.3.1
|
||||
- pip install pyflakes
|
||||
- pip install pep8
|
||||
# coverage stuff:
|
||||
- pip install coveralls
|
||||
- pip install coverage
|
||||
script:
|
||||
- pep8 headphones
|
||||
- pyflakes headphones
|
||||
- nosetests headphones
|
||||
- nosetests
|
||||
|
||||
after_success:
|
||||
# coverage stuff:
|
||||
- if [ $SENDCOVERAGE ]; then coveralls; fi
|
||||
|
||||
@@ -152,7 +152,10 @@ def main():
|
||||
headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db')
|
||||
|
||||
# Read config and start logging
|
||||
headphones.initialize(config_file)
|
||||
try:
|
||||
headphones.initialize(config_file)
|
||||
except headphones.exceptions.SoftChrootError as e:
|
||||
raise SystemExit('FATAL ERROR')
|
||||
|
||||
if headphones.DAEMON:
|
||||
headphones.daemonize()
|
||||
|
||||
@@ -1095,7 +1095,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Plex Token</label><input type="text" name="plex_token" value="${config['plex_token']}" size="30">
|
||||
<small>Plex Token (for use with Plex Home)</small>
|
||||
<small>Plex Token (for use with Plex Home)</small>
|
||||
</div>
|
||||
<div class="checkbox row">
|
||||
<input type="checkbox" name="plex_update" value="1" ${config['plex_update']} /><label>Update Plex Library</label>
|
||||
|
||||
@@ -29,7 +29,8 @@ from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from headphones import versioncheck, logger
|
||||
import headphones.config
|
||||
|
||||
from headphones.softchroot import SoftChroot
|
||||
import headphones.exceptions
|
||||
|
||||
# (append new extras to the end)
|
||||
POSSIBLE_EXTRAS = [
|
||||
@@ -74,6 +75,7 @@ started = False
|
||||
DATA_DIR = None
|
||||
|
||||
CONFIG = None
|
||||
SOFT_CHROOT = None
|
||||
|
||||
DB_FILE = None
|
||||
|
||||
@@ -92,11 +94,11 @@ MIRRORLIST = ["musicbrainz.org", "headphones", "custom"]
|
||||
|
||||
UMASK = None
|
||||
|
||||
|
||||
def initialize(config_file):
|
||||
with INIT_LOCK:
|
||||
|
||||
global CONFIG
|
||||
global SOFT_CHROOT
|
||||
global _INITIALIZED
|
||||
global CURRENT_VERSION
|
||||
global LATEST_VERSION
|
||||
@@ -136,6 +138,14 @@ def initialize(config_file):
|
||||
logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR,
|
||||
verbose=VERBOSE)
|
||||
|
||||
try:
|
||||
SOFT_CHROOT = SoftChroot(str(CONFIG.SOFT_CHROOT))
|
||||
if SOFT_CHROOT.isEnabled():
|
||||
logger.info("Soft-chroot enabled for dir: %s", str(CONFIG.SOFT_CHROOT))
|
||||
except exceptions.SoftChrootError as e:
|
||||
logger.error("SoftChroot error: %s", e)
|
||||
raise e
|
||||
|
||||
if not CONFIG.CACHE_DIR:
|
||||
# Put the cache dir in the data dir for now
|
||||
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
|
||||
|
||||
13
headphones/albumart_test.py
Normal file
13
headphones/albumart_test.py
Normal file
@@ -0,0 +1,13 @@
|
||||
#import unittest
|
||||
#import mock
|
||||
from headphones.unittestcompat import TestCase
|
||||
|
||||
import headphones.albumart
|
||||
|
||||
# no tests...
|
||||
class AlbumArtTest(TestCase):
|
||||
def test_nothing(self):
|
||||
x = 100 - 2 * 50
|
||||
if x:
|
||||
headphones.albumart.getAlbumArt('asdf')
|
||||
self.assertTrue(True)
|
||||
@@ -15,6 +15,19 @@ def bool_int(value):
|
||||
value = 0
|
||||
return int(bool(value))
|
||||
|
||||
class path(str):
|
||||
"""Internal 'marker' type for paths in config."""
|
||||
|
||||
@staticmethod
|
||||
def __call__(val):
|
||||
return path(val)
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
hstr = str.__new__(cls, *args, **kw)
|
||||
return hstr
|
||||
|
||||
def __repr__(self):
|
||||
return 'headphones.config.path(%s)' % self
|
||||
|
||||
_CONFIG_DEFINITIONS = {
|
||||
'ADD_ALBUM_ART': (int, 'General', 0),
|
||||
@@ -31,11 +44,11 @@ _CONFIG_DEFINITIONS = {
|
||||
'AUTO_ADD_ARTISTS': (int, 'General', 1),
|
||||
'BITRATE': (int, 'General', 192),
|
||||
'BLACKHOLE': (int, 'General', 0),
|
||||
'BLACKHOLE_DIR': (str, 'General', ''),
|
||||
'BLACKHOLE_DIR': (path, 'General', ''),
|
||||
'BOXCAR_ENABLED': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ONSNATCH': (int, 'Boxcar', 0),
|
||||
'BOXCAR_TOKEN': (str, 'Boxcar', ''),
|
||||
'CACHE_DIR': (str, 'General', ''),
|
||||
'CACHE_DIR': (path, 'General', ''),
|
||||
'CACHE_SIZEMB': (int, 'Advanced', 32),
|
||||
'CHECK_GITHUB': (int, 'General', 1),
|
||||
'CHECK_GITHUB_INTERVAL': (int, 'General', 360),
|
||||
@@ -44,8 +57,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'CONFIG_VERSION': (str, 'General', '0'),
|
||||
'CORRECT_METADATA': (int, 'General', 0),
|
||||
'CUE_SPLIT': (int, 'General', 1),
|
||||
'CUE_SPLIT_FLAC_PATH': (str, 'General', ''),
|
||||
'CUE_SPLIT_SHNTOOL_PATH': (str, 'General', ''),
|
||||
'CUE_SPLIT_FLAC_PATH': (path, 'General', ''),
|
||||
'CUE_SPLIT_SHNTOOL_PATH': (path, 'General', ''),
|
||||
'CUSTOMAUTH': (int, 'General', 0),
|
||||
'CUSTOMHOST': (str, 'General', 'localhost'),
|
||||
'CUSTOMPASS': (str, 'General', ''),
|
||||
@@ -53,12 +66,12 @@ _CONFIG_DEFINITIONS = {
|
||||
'CUSTOMSLEEP': (int, 'General', 1),
|
||||
'CUSTOMUSER': (str, 'General', ''),
|
||||
'DELETE_LOSSLESS_FILES': (int, 'General', 1),
|
||||
'DESTINATION_DIR': (str, 'General', ''),
|
||||
'DESTINATION_DIR': (path, 'General', ''),
|
||||
'DETECT_BITRATE': (int, 'General', 0),
|
||||
'DO_NOT_PROCESS_UNMATCHED': (int, 'General', 0),
|
||||
'DOWNLOAD_DIR': (str, 'General', ''),
|
||||
'DOWNLOAD_DIR': (path, 'General', ''),
|
||||
'DOWNLOAD_SCAN_INTERVAL': (int, 'General', 5),
|
||||
'DOWNLOAD_TORRENT_DIR': (str, 'General', ''),
|
||||
'DOWNLOAD_TORRENT_DIR': (path, 'General', ''),
|
||||
'DO_NOT_OVERRIDE_GIT_BRANCH': (int, 'General', 0),
|
||||
'EMAIL_ENABLED': (int, 'Email', 0),
|
||||
'EMAIL_FROM': (str, 'Email', ''),
|
||||
@@ -74,14 +87,14 @@ _CONFIG_DEFINITIONS = {
|
||||
'EMBED_LYRICS': (int, 'General', 0),
|
||||
'ENABLE_HTTPS': (int, 'General', 0),
|
||||
'ENCODER': (str, 'General', 'ffmpeg'),
|
||||
'ENCODERFOLDER': (str, 'General', ''),
|
||||
'ENCODERFOLDER': (path, 'General', ''),
|
||||
'ENCODERLOSSLESS': (int, 'General', 1),
|
||||
'ENCODEROUTPUTFORMAT': (str, 'General', 'mp3'),
|
||||
'ENCODERQUALITY': (int, 'General', 2),
|
||||
'ENCODERVBRCBR': (str, 'General', 'cbr'),
|
||||
'ENCODER_MULTICORE': (int, 'General', 0),
|
||||
'ENCODER_MULTICORE_COUNT': (int, 'General', 0),
|
||||
'ENCODER_PATH': (str, 'General', ''),
|
||||
'ENCODER_PATH': (path, 'General', ''),
|
||||
'EXTRAS': (str, 'General', ''),
|
||||
'EXTRA_NEWZNABS': (list, 'Newznab', ''),
|
||||
'EXTRA_TORZNABS': (list, 'Torznab', ''),
|
||||
@@ -94,7 +107,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'FOLDER_PERMISSIONS': (str, 'General', '0755'),
|
||||
'FREEZE_DB': (int, 'General', 0),
|
||||
'GIT_BRANCH': (str, 'General', 'master'),
|
||||
'GIT_PATH': (str, 'General', ''),
|
||||
'GIT_PATH': (path, 'General', ''),
|
||||
'GIT_USER': (str, 'General', 'rembo10'),
|
||||
'GROWL_ENABLED': (int, 'Growl', 0),
|
||||
'GROWL_HOST': (str, 'Growl', ''),
|
||||
@@ -103,8 +116,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'HEADPHONES_INDEXER': (bool_int, 'General', False),
|
||||
'HPPASS': (str, 'General', ''),
|
||||
'HPUSER': (str, 'General', ''),
|
||||
'HTTPS_CERT': (str, 'General', ''),
|
||||
'HTTPS_KEY': (str, 'General', ''),
|
||||
'HTTPS_CERT': (path, 'General', ''),
|
||||
'HTTPS_KEY': (path, 'General', ''),
|
||||
'HTTP_HOST': (str, 'General', 'localhost'),
|
||||
'HTTP_PASSWORD': (str, 'General', ''),
|
||||
'HTTP_PORT': (int, 'General', 8181),
|
||||
@@ -114,8 +127,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'IDTAG': (int, 'Beets', 0),
|
||||
'IGNORE_CLEAN_RELEASES': (int, 'General', 0),
|
||||
'IGNORED_WORDS': (str, 'General', ''),
|
||||
'IGNORED_FOLDERS': (list, 'Advanced', []),
|
||||
'IGNORED_FILES': (list, 'Advanced', []),
|
||||
'IGNORED_FOLDERS': (list, 'Advanced', []), # path
|
||||
'IGNORED_FILES': (list, 'Advanced', []), # path
|
||||
'INCLUDE_EXTRAS': (int, 'General', 0),
|
||||
'INTERFACE': (str, 'General', 'default'),
|
||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
||||
@@ -130,17 +143,17 @@ _CONFIG_DEFINITIONS = {
|
||||
'LIBRARYSCAN_INTERVAL': (int, 'General', 300),
|
||||
'LMS_ENABLED': (int, 'LMS', 0),
|
||||
'LMS_HOST': (str, 'LMS', ''),
|
||||
'LOG_DIR': (str, 'General', ''),
|
||||
'LOG_DIR': (path, 'General', ''),
|
||||
'LOSSLESS_BITRATE_FROM': (int, 'General', 0),
|
||||
'LOSSLESS_BITRATE_TO': (int, 'General', 0),
|
||||
'LOSSLESS_DESTINATION_DIR': (str, 'General', ''),
|
||||
'LOSSLESS_DESTINATION_DIR': (path, 'General', ''),
|
||||
'MB_IGNORE_AGE': (int, 'General', 365),
|
||||
'MININOVA': (int, 'Mininova', 0),
|
||||
'MININOVA_RATIO': (str, 'Mininova', ''),
|
||||
'MIRROR': (str, 'General', 'musicbrainz.org'),
|
||||
'MOVE_FILES': (int, 'General', 0),
|
||||
'MPC_ENABLED': (bool_int, 'MPC', False),
|
||||
'MUSIC_DIR': (str, 'General', ''),
|
||||
'MUSIC_DIR': (path, 'General', ''),
|
||||
'MUSIC_ENCODER': (int, 'General', 0),
|
||||
'NEWZNAB': (int, 'Newznab', 0),
|
||||
'NEWZNAB_APIKEY': (str, 'Newznab', ''),
|
||||
@@ -223,6 +236,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'SAB_USERNAME': (str, 'SABnzbd', ''),
|
||||
'SAMPLINGFREQUENCY': (int, 'General', 44100),
|
||||
'SEARCH_INTERVAL': (int, 'General', 1440),
|
||||
'SOFT_CHROOT': (path, 'General', ''),
|
||||
'SONGKICK_APIKEY': (str, 'Songkick', 'nd1We7dFW2RqxPw8'),
|
||||
'SONGKICK_ENABLED': (int, 'Songkick', 1),
|
||||
'SONGKICK_FILTER_ENABLED': (int, 'Songkick', 0),
|
||||
@@ -234,7 +248,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'SUBSONIC_PASSWORD': (str, 'Subsonic', ''),
|
||||
'SUBSONIC_USERNAME': (str, 'Subsonic', ''),
|
||||
'SYNOINDEX_ENABLED': (int, 'Synoindex', 0),
|
||||
'TORRENTBLACKHOLE_DIR': (str, 'General', ''),
|
||||
'TORRENTBLACKHOLE_DIR': (path, 'General', ''),
|
||||
'TORRENT_DOWNLOADER': (int, 'General', 0),
|
||||
'TORRENT_REMOVAL_INTERVAL': (int, 'General', 720),
|
||||
'TORZNAB': (int, 'Torznab', 0),
|
||||
@@ -295,7 +309,7 @@ class Config(object):
|
||||
definition = _CONFIG_DEFINITIONS[key]
|
||||
if len(definition) == 3:
|
||||
definition_type, section, default = definition
|
||||
else:
|
||||
elif len(definition) == 4:
|
||||
definition_type, section, _, default = definition
|
||||
return key, definition_type, section, ini_key, default
|
||||
|
||||
|
||||
@@ -24,3 +24,9 @@ class NewzbinAPIThrottled(HeadphonesException):
|
||||
"""
|
||||
Newzbin has throttled us, deal with it
|
||||
"""
|
||||
|
||||
class SoftChrootError(HeadphonesException):
|
||||
"""
|
||||
Fatal errors in SoftChroot module
|
||||
"""
|
||||
pass
|
||||
|
||||
70
headphones/softchroot.py
Normal file
70
headphones/softchroot.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
from headphones.exceptions import SoftChrootError
|
||||
|
||||
class SoftChroot(object):
|
||||
""" SoftChroot provides SOFT chrooting for UI
|
||||
|
||||
IMPORTANT: call methods of this class just in modules, which generates data for client UI. Try to avoid unnecessary usage.
|
||||
"""
|
||||
|
||||
enabled = False
|
||||
chroot = None
|
||||
|
||||
def __init__(self, path):
|
||||
if not path:
|
||||
#disabled
|
||||
return
|
||||
|
||||
path = path.strip()
|
||||
if not path:
|
||||
return
|
||||
|
||||
if (not os.path.exists(path) or
|
||||
not os.path.isdir(path)):
|
||||
raise SoftChrootError('No such directory: %s' % path)
|
||||
|
||||
path = path.rstrip(os.path.sep) + os.path.sep
|
||||
|
||||
self.enabled = True
|
||||
self.chroot = path
|
||||
|
||||
def isEnabled(self):
|
||||
return self.enabled
|
||||
|
||||
def getRoot(self):
|
||||
return self.chroot
|
||||
|
||||
def apply(self, path):
|
||||
if not self.enabled:
|
||||
return path
|
||||
|
||||
if not path:
|
||||
return path
|
||||
|
||||
p = path.strip()
|
||||
if not p:
|
||||
return path
|
||||
|
||||
if path.startswith(self.chroot):
|
||||
p = os.path.sep + path[len(self.chroot):]
|
||||
else:
|
||||
p = os.path.sep
|
||||
|
||||
return p
|
||||
|
||||
def revoke(self, path):
|
||||
if not self.enabled:
|
||||
return path
|
||||
|
||||
if not path:
|
||||
return path
|
||||
|
||||
p = path.strip()
|
||||
if not p:
|
||||
return path
|
||||
|
||||
if os.path.sep == p[0]:
|
||||
p = p[1:]
|
||||
|
||||
p = self.chroot + p
|
||||
return p
|
||||
121
headphones/softchroot_test.py
Normal file
121
headphones/softchroot_test.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import os
|
||||
import mock
|
||||
from headphones.unittestcompat import TestCase, TestArgs
|
||||
#from mock import MagicMock
|
||||
|
||||
from headphones.softchroot import SoftChroot
|
||||
from headphones.exceptions import SoftChrootError
|
||||
|
||||
class SoftChrootTest(TestCase):
|
||||
def test_create(self):
|
||||
""" create headphones.SoftChroot """
|
||||
|
||||
cf = SoftChroot('/tmp/')
|
||||
self.assertIsInstance(cf, SoftChroot)
|
||||
self.assertTrue(cf.isEnabled())
|
||||
self.assertEqual(cf.getRoot(), '/tmp/')
|
||||
|
||||
@TestArgs(
|
||||
(None),
|
||||
(''),
|
||||
(' '),
|
||||
)
|
||||
def test_create_disabled(self, empty_path):
|
||||
""" create DISABLED SoftChroot """
|
||||
|
||||
cf = SoftChroot(empty_path)
|
||||
self.assertIsInstance(cf, SoftChroot)
|
||||
self.assertFalse(cf.isEnabled())
|
||||
self.assertIsNone(cf.getRoot())
|
||||
|
||||
def test_create_on_not_exists_dir(self):
|
||||
""" create SoftChroot on non existent dir """
|
||||
|
||||
path = os.path.join('/tmp', 'notexist', 'asdf', '11', '12', 'np', 'itsssss')
|
||||
|
||||
cf = None
|
||||
with self.assertRaises(SoftChrootError) as exc:
|
||||
cf = SoftChroot(path)
|
||||
self.assertIsNone(cf)
|
||||
|
||||
self.assertRegexpMatches(str(exc.exception), r'No such directory')
|
||||
self.assertRegexpMatches(str(exc.exception), path)
|
||||
|
||||
@mock.patch('headphones.softchroot.os', wrap=os, name='OsMock')
|
||||
def test_create_on_file(self, os_mock):
|
||||
""" create SoftChroot on file, not a directory """
|
||||
|
||||
path = os.path.join('/tmp', 'notexist', 'asdf', '11', '12', 'np', 'itsssss')
|
||||
|
||||
os_mock.path.sep = os.path.sep
|
||||
os_mock.path.isdir.side_effect = lambda x: x != path
|
||||
|
||||
cf = None
|
||||
with self.assertRaises(SoftChrootError) as exc:
|
||||
cf = SoftChroot(path)
|
||||
self.assertIsNone(cf)
|
||||
|
||||
self.assertTrue(os_mock.path.isdir.called)
|
||||
|
||||
self.assertRegexpMatches(str(exc.exception), r'No such directory')
|
||||
self.assertRegexpMatches(str(exc.exception), path)
|
||||
|
||||
@TestArgs(
|
||||
(None, None),
|
||||
('', ''),
|
||||
(' ', ' '),
|
||||
('/tmp/', '/'),
|
||||
('/tmp/asdf', '/asdf'),
|
||||
)
|
||||
def test_apply(self, p, e):
|
||||
""" apply SoftChroot """
|
||||
sc = SoftChroot('/tmp/')
|
||||
a = sc.apply(p)
|
||||
self.assertEqual(a, e)
|
||||
|
||||
@TestArgs(
|
||||
('/'),
|
||||
('/nonch/path/asdf'),
|
||||
('tmp/asdf'),
|
||||
)
|
||||
def test_apply_out_of_root(self, p):
|
||||
""" apply SoftChroot to paths outside of the chroot """
|
||||
sc = SoftChroot('/tmp/')
|
||||
a = sc.apply(p)
|
||||
self.assertEqual(a, '/')
|
||||
|
||||
@TestArgs(
|
||||
(None, None),
|
||||
('', ''),
|
||||
(' ', ' '),
|
||||
('/', '/tmp/'),
|
||||
('/asdf', '/tmp/asdf'),
|
||||
('/asdf/', '/tmp/asdf/'),
|
||||
('localdir/adf', '/tmp/localdir/adf'),
|
||||
('localdir/adf/', '/tmp/localdir/adf/'),
|
||||
)
|
||||
def test_revoke(self, p, e):
|
||||
""" revoke SoftChroot """
|
||||
sc = SoftChroot('/tmp/')
|
||||
a = sc.revoke(p)
|
||||
self.assertEqual(a, e)
|
||||
|
||||
@TestArgs(
|
||||
(None),
|
||||
(''),
|
||||
(' '),
|
||||
('/tmp'),
|
||||
('/tmp/'),
|
||||
('/tmp/asdf'),
|
||||
('/tmp/localdir/adf'),
|
||||
('localdir/adf'),
|
||||
('localdir/adf/'),
|
||||
)
|
||||
def test_actions_on_disabled(self, p):
|
||||
""" disabled SoftChroot should not change args on apply and revoke """
|
||||
sc = SoftChroot(None)
|
||||
a = sc.apply(p)
|
||||
self.assertEqual(a, p)
|
||||
|
||||
r = sc.revoke(p)
|
||||
self.assertEqual(r, p)
|
||||
90
headphones/unittestcompat.py
Normal file
90
headphones/unittestcompat.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import sys
|
||||
from unittest import TestCase as TC
|
||||
|
||||
def _is26():
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] == 6:
|
||||
return True
|
||||
return False
|
||||
|
||||
_dummy = _is26()
|
||||
|
||||
def _d(f):
|
||||
def decorate(self, *args, **kw):
|
||||
if _dummy:
|
||||
return self.assertTrue(True)
|
||||
return f(self, *args, **kw)
|
||||
return decorate
|
||||
|
||||
class TestCase(TC):
|
||||
"""
|
||||
Wrapper for python 2.6 stubs
|
||||
"""
|
||||
|
||||
def assertIsInstance(self, obj, cls, msg=None):
|
||||
if not _dummy:
|
||||
return super(TestCase, self).assertIsInstance(obj, cls, msg)
|
||||
tst = isinstance(obj, cls)
|
||||
return self.assertTrue(tst, msg)
|
||||
|
||||
@_d
|
||||
def assertNotIsInstance(self, *args, **kw):
|
||||
return super(TestCase, self).assertNotIsInstance(*args, **kw)
|
||||
|
||||
@_d
|
||||
def assertIn(self, *args, **kw):
|
||||
return super(TestCase, self).assertIn(*args, **kw)
|
||||
|
||||
@_d
|
||||
def assertRegexpMatches(self, *args, **kw):
|
||||
return super(TestCase, self).assertRegexpMatches(*args, **kw)
|
||||
|
||||
def assertIsNone(self, val, msg=None):
|
||||
if not _dummy:
|
||||
return super(TestCase, self).assertIsNone(val, msg)
|
||||
tst = val is None
|
||||
return super(TestCase, self).assertTrue(tst, msg)
|
||||
|
||||
def assertIsNotNone(self, val, msg=None):
|
||||
if not _dummy:
|
||||
return super(TestCase, self).assertIsNotNone(val, msg)
|
||||
tst = val is not None
|
||||
return super(TestCase, self).assertTrue(tst, msg)
|
||||
|
||||
class _TestCaseRaiseStub:
|
||||
def __init__(self, exc, tc):
|
||||
self.exc = exc
|
||||
self.tc = tc
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, tp, value, traceback):
|
||||
tst = tp is self.exc
|
||||
self.tc.assertTrue(tst)
|
||||
self.exception = value
|
||||
return True
|
||||
|
||||
def assertRaises(self, exc, msg=None):
|
||||
if not _dummy:
|
||||
return super(TestCase, self).assertRaises(exc, msg)
|
||||
return TestCase._TestCaseRaiseStub(exc, self)
|
||||
|
||||
def TestArgs(*parameters):
|
||||
def tuplify(x):
|
||||
if not isinstance(x, tuple):
|
||||
return (x,)
|
||||
return x
|
||||
|
||||
def decorator(method, parameters=parameters):
|
||||
for parameter in (tuplify(x) for x in parameters):
|
||||
|
||||
def method_for_parameter(self, method=method, parameter=parameter):
|
||||
method(self, *parameter)
|
||||
args_for_parameter = ",".join(repr(v) for v in parameter)
|
||||
name_for_parameter = method.__name__ + "(" + args_for_parameter + ")"
|
||||
frame = sys._getframe(1) # pylint: disable-msg=W0212
|
||||
frame.f_locals[name_for_parameter] = method_for_parameter
|
||||
frame.f_locals[name_for_parameter].__doc__ = method.__doc__ + '(' + args_for_parameter + ')'
|
||||
method_for_parameter.__name__ = name_for_parameter + '(' + args_for_parameter + ')'
|
||||
return None
|
||||
return decorator
|
||||
@@ -1367,8 +1367,16 @@ class WebInterface(object):
|
||||
"idtag": checked(headphones.CONFIG.IDTAG)
|
||||
}
|
||||
|
||||
for k, v in config.iteritems():
|
||||
if isinstance(v, headphones.config.path):
|
||||
# need to apply SoftChroot to paths:
|
||||
nv = headphones.SOFT_CHROOT.apply(v)
|
||||
if v != nv:
|
||||
config[k] = headphones.config.path(nv)
|
||||
|
||||
# Need to convert EXTRAS to a dictionary we can pass to the config:
|
||||
# it'll come in as a string like 2,5,6,8
|
||||
|
||||
extra_munges = {
|
||||
"dj-mix": "dj_mix",
|
||||
"mixtape/street": "mixtape_street"
|
||||
@@ -1435,6 +1443,17 @@ class WebInterface(object):
|
||||
kwargs[plain_config] = kwargs[use_config]
|
||||
del kwargs[use_config]
|
||||
|
||||
for k, v in kwargs.iteritems():
|
||||
# TODO : HUGE crutch. It is all because there is no way to deal with options...
|
||||
_conf = headphones.CONFIG._define(k)
|
||||
conftype = _conf[1]
|
||||
|
||||
#print '===>', conftype
|
||||
if conftype is headphones.config.path:
|
||||
nv = headphones.SOFT_CHROOT.revoke(v)
|
||||
if nv != v:
|
||||
kwargs[k] = nv
|
||||
|
||||
# Check if encoderoutputformat is set multiple times
|
||||
if len(kwargs['encoderoutputformat'][-1]) > 1:
|
||||
kwargs['encoderoutputformat'] = kwargs['encoderoutputformat'][-1]
|
||||
|
||||
Reference in New Issue
Block a user