mylar/mylar/config.py

856 lines
35 KiB
Python

import itertools
from collections import OrderedDict
from operator import itemgetter
import os
import codecs
import shutil
import re
import ConfigParser
import mylar
from mylar import logger, helpers
config = ConfigParser.SafeConfigParser()
_CONFIG_DEFINITIONS = OrderedDict({
#keyname, type, section, default
'CONFIG_VERSION': (int, 'General', 6),
'MINIMAL_INI': (bool, 'General', False),
'OLDCONFIG_VERSION': (str, 'General', None),
'AUTO_UPDATE': (bool, 'General', False),
'CACHE_DIR': (str, 'General', None),
'DYNAMIC_UPDATE': (int, 'General', 0),
'REFRESH_CACHE': (int, 'General', 7),
'ANNUALS_ON': (bool, 'General', False),
'SYNO_FIX': (bool, 'General', False),
'LAUNCH_BROWSER' : (bool, 'General', False),
'WANTED_TAB_OFF': (bool, 'General', False),
'ENABLE_RSS': (bool, 'General', False),
'SEARCH_DELAY' : (int, 'General', 1),
'GRABBAG_DIR': (str, 'General', None),
'HIGHCOUNT': (int, 'General', 0),
'MAINTAINSERIESFOLDER': (bool, 'General', False),
'DESTINATION_DIR': (str, 'General', None), #if M_D_D_ is enabled, this will be the DEFAULT for writing
'MULTIPLE_DEST_DIRS': (str, 'General', None), #Nothing will ever get written to these dirs - just for scanning, unless it's metatagging/renaming.
'CREATE_FOLDERS': (bool, 'General', True),
'DELETE_REMOVE_DIR': (bool, 'General', False),
'UPCOMING_SNATCHED': (bool, 'General', True),
'UPDATE_ENDED': (bool, 'General', False),
'LOCMOVE': (bool, 'General', False),
'NEWCOM_DIR': (str, 'General', None),
'FFTONEWCOM_DIR': (bool, 'General', False),
'FOLDER_SCAN_LOG_VERBOSE': (bool, 'General', False),
'INTERFACE': (str, 'General', None),
'CORRECT_METADATA': (bool, 'General', False),
'MOVE_FILES': (bool, 'General', False),
'RENAME_FILES': (bool, 'General', False),
'FOLDER_FORMAT': (str, 'General', None),
'FILE_FORMAT': (str, 'General', None),
'REPLACE_SPACES': (bool, 'General', False),
'REPLACE_CHAR': (str, 'General', None),
'ZERO_LEVEL': (bool, 'General', False),
'ZERO_LEVEL_N': (str, 'General', None),
'LOWERCASE_FILENAMES': (bool, 'General', False),
'IGNORE_HAVETOTAL': (bool, 'General', False),
'SNATCHED_HAVETOTAL': (bool, 'General', False),
'FAILED_DOWNLOAD_HANDLING': (bool, 'General', False),
'FAILED_AUTO': (bool, 'General',False),
'PREFERRED_QUALITY': (int, 'General', 0),
'USE_MINSIZE': (bool, 'General', False),
'MINSIZE': (str, 'General', None),
'USE_MAXSIZE': (bool, 'General', False),
'MAXSIZE': (str, 'General', None),
'AUTOWANT_UPCOMING': (bool, 'General', True),
'AUTOWANT_ALL': (bool, 'General', False),
'COMIC_COVER_LOCAL': (bool, 'General', False),
'ADD_TO_CSV': (bool, 'General', True),
'SKIPPED2WANTED': (bool, 'General', False),
'READ2FILENAME': (bool, 'General', False),
'SEND2READ': (bool, 'General', False),
'NZB_STARTUP_SEARCH': (bool, 'General', False),
'UNICODE_ISSUENUMBER': (bool, 'General', False),
'RSS_CHECKINTERVAL': (int, 'Scheduler', 20),
'SEARCH_INTERVAL': (int, 'Scheduler', 360),
'DOWNLOAD_SCAN_INTERVAL': (int, 'Scheduler', 5),
'CHECK_GITHUB_INTERVAL' : (int, 'Scheduler', 360),
'ALT_PULL' : (int, 'Weekly', 2),
'PULL_REFRESH': (str, 'Weekly', None),
'WEEKFOLDER': (bool, 'Weekly', False),
'WEEKFOLDER_LOC': (str, 'Weekly', None),
'WEEKFOLDER_FORMAT': (int, 'Weekly', 0),
'INDIE_PUB': (int, 'Weekly', 75),
'BIGGIE_PUB': (int, 'Weekly', 55),
'HTTP_PORT' : (int, 'Interface', 8090),
'HTTP_HOST' : (str, 'Interface', None),
'HTTP_USERNAME' : (str, 'Interface', None),
'HTTP_PASSWORD' : (str, 'Interface', None),
'HTTP_ROOT' : (str, 'Interface', None),
'ENABLE_HTTPS' : (bool, 'Interface', False),
'HTTPS_CERT' : (str, 'Interface', None),
'HTTPS_KEY' : (str, 'Interface', None),
'HTTPS_CHAIN' : (str, 'Interface', None),
'HTTPS_FORCE_ON' : (bool, 'Interface', False),
'HOST_RETURN' : (str, 'Interface', None),
'API_ENABLED' : (bool, 'API', False),
'API_KEY' : (str, 'API', None),
'CVAPI_RATE' : (int, 'CV', 2),
'COMICVINE_API': (str, 'CV', None),
'BLACKLISTED_PUBLISHERS' : (str, 'CV', None),
'CV_VERIFY': (bool, 'CV', True),
'CV_ONLY': (bool, 'CV', False),
'CV_ONETIMER': (bool, 'CV', True),
'CVINFO': (bool, 'CV', False),
'LOG_DIR' : (str, 'Logs', None),
'MAX_LOGSIZE' : (int, 'Logs', 10000000),
'LOG_LEVEL': (int, 'Logs', 0),
'GIT_PATH' : (str, 'Git', None),
'GIT_USER' : (str, 'Git', 'evilhero'),
'GIT_BRANCH' : (str, 'Git', None),
'CHECK_GITHUB' : (bool, 'Git', False),
'CHECK_GITHUB_ON_STARTUP' : (bool, 'Git', False),
'ENFORCE_PERMS': (bool, 'Perms', True),
'CHMOD_DIR': (str, 'Perms', 0777),
'CHMOD_FILE': (str, 'Perms', 0660),
'CHOWNER': (str, 'Perms', None),
'CHGROUP': (str, 'Perms', None),
'ADD_COMICS': (bool, 'Import', False),
'COMIC_DIR': (str, 'Import', None),
'IMP_MOVE': (bool, 'Import', False),
'IMP_RENAME': (bool, 'Import', False),
'IMP_METADATA': (bool, 'Import', False), # should default to False - this is enabled for testing only.
'DUPECONSTRAINT': (str, 'Duplicates', None),
'DDUMP': (bool, 'Duplicates', False),
'DUPLICATE_DUMP': (str, 'Duplicates', None),
'PROWL_ENABLED': (bool, 'Prowl', False),
'PROWL_PRIORITY': (int, 'Prowl', 0),
'PROWL_KEYS': (str, 'Prowl', None),
'PROWL_ONSNATCH': (bool, 'Prowl', False),
'NMA_ENABLED': (bool, 'NMA', False),
'NMA_APIKEY': (str, 'NMA', None),
'NMA_PRIORITY': (int, 'NMA', 0),
'NMA_ONSNATCH': (bool, 'NMA', False),
'PUSHOVER_ENABLED': (bool, 'PUSHOVER', False),
'PUSHOVER_PRIORITY': (int, 'PUSHOVER', 0),
'PUSHOVER_APIKEY': (str, 'PUSHOVER', None),
'PUSHOVER_USERKEY': (str, 'PUSHOVER', None),
'PUSHOVER_ONSNATCH': (bool, 'PUSHOVER', False),
'BOXCAR_ENABLED': (bool, 'BOXCAR', False),
'BOXCAR_ONSNATCH': (bool, 'BOXCAR', False),
'BOXCAR_TOKEN': (str, 'BOXCAR', None),
'PUSHBULLET_ENABLED': (bool, 'PUSHBULLET', False),
'PUSHBULLET_APIKEY': (str, 'PUSHBULLET', None),
'PUSHBULLET_DEVICEID': (str, 'PUSHBULLET', None),
'PUSHBULLET_CHANNEL_TAG': (str, 'PUSHBULLET', None),
'PUSHBULLET_ONSNATCH': (bool, 'PUSHBULLET', False),
'TELEGRAM_ENABLED': (bool, 'TELEGRAM', False),
'TELEGRAM_TOKEN': (str, 'TELEGRAM', None),
'TELEGRAM_USERID': (str, 'TELEGRAM', None),
'TELEGRAM_ONSNATCH': (bool, 'TELEGRAM', False),
'SLACK_ENABLED': (bool, 'SLACK', False),
'SLACK_WEBHOOK_URL': (str, 'SLACK', None),
'SLACK_ONSNATCH': (bool, 'SLACK', False),
'POST_PROCESSING': (bool, 'PostProcess', False),
'FILE_OPTS': (str, 'PostProcess', 'move'),
'SNATCHEDTORRENT_NOTIFY': (bool, 'PostProcess', False),
'LOCAL_TORRENT_PP': (bool, 'PostProcess', False),
'POST_PROCESSING_SCRIPT': (str, 'PostProcess', None),
'ENABLE_EXTRA_SCRIPTS': (bool, 'PostProcess', False),
'EXTRA_SCRIPTS': (str, 'PostProcess', None),
'ENABLE_SNATCH_SCRIPT': (bool, 'PostProcess', False),
'SNATCH_SCRIPT': (str, 'PostProcess', None),
'ENABLE_PRE_SCRIPTS': (bool, 'PostProcess', False),
'PRE_SCRIPTS': (str, 'PostProcess', None),
'ENABLE_CHECK_FOLDER': (bool, 'PostProcess', False),
'CHECK_FOLDER': (str, 'PostProcess', None),
'PROVIDER_ORDER': (str, 'Providers', None),
'USENET_RETENTION': (int, 'Providers', 1500),
'NZB_DOWNLOADER': (int, 'Client', 0), #0': sabnzbd, #1': nzbget, #2': blackhole
'TORRENT_DOWNLOADER': (int, 'Client', 0), #0': watchfolder, #1': uTorrent, #2': rTorrent, #3': transmission, #4': deluge, #5': qbittorrent
'SAB_HOST': (str, 'SABnzbd', None),
'SAB_USERNAME': (str, 'SABnzbd', None),
'SAB_PASSWORD': (str, 'SABnzbd', None),
'SAB_APIKEY': (str, 'SABnzbd', None),
'SAB_CATEGORY': (str, 'SABnzbd', None),
'SAB_PRIORITY': (str, 'SABnzbd', None),
'SAB_TO_MYLAR': (bool, 'SABnzbd', False),
'SAB_DIRECTORY': (str, 'SABnzbd', None),
'NZBGET_HOST': (str, 'NZBGet', None),
'NZBGET_PORT': (str, 'NZBGet', None),
'NZBGET_USERNAME': (str, 'NZBGet', None),
'NZBGET_PASSWORD': (str, 'NZBGet', None),
'NZBGET_PRIORITY': (str, 'NZBGet', None),
'NZBGET_CATEGORY': (str, 'NZBGet', None),
'NZBGET_DIRECTORY': (str, 'NZBGet', None),
'BLACKHOLE_DIR': (str, 'Blackhole', None),
'ENABLE_TPSE': (bool, 'TPSE', False),
'TPSE_PROXY': (str, 'TPSE', None),
'TPSE_VERIFY': (bool, 'TPSE', True),
'NZBSU': (bool, 'NZBsu', False),
'NZBSU_UID': (str, 'NZBsu', None),
'NZBSU_APIKEY': (str, 'NZBsu', None),
'NZBSU_VERIFY': (bool, 'NZBsu', True),
'DOGNZB': (bool, 'DOGnzb', False),
'DOGNZB_APIKEY': (str, 'DOGnzb', None),
'DOGNZB_VERIFY': (bool, 'DOGnzb', True),
'NEWZNAB': (bool, 'Newznab', False),
'EXTRA_NEWZNABS': (str, 'Newznab', None),
'ENABLE_TORZNAB': (bool, 'Torznab', False),
'TORZNAB_NAME': (str, 'Torznab', None),
'TORZNAB_HOST': (str, 'Torznab', None),
'TORZNAB_APIKEY': (str, 'Torznab', None),
'TORZNAB_CATEGORY': (str, 'Torznab', None),
'TORZNAB_VERIFY': (bool, 'Torznab', False),
'EXPERIMENTAL': (bool, 'Experimental', False),
'ALTEXPERIMENTAL': (bool, 'Experimental', False),
'TAB_ENABLE': (bool, 'Tablet', False),
'TAB_HOST': (str, 'Tablet', None),
'TAB_USER': (str, 'Tablet', None),
'TAB_PASS': (str, 'Tablet', None),
'TAB_DIRECTORY': (str, 'Tablet', None),
'STORYARCDIR': (bool, 'StoryArc', False),
'COPY2ARCDIR': (bool, 'StoryArc', False),
'ARC_FOLDERFORMAT': (str, 'StoryArc', None),
'ARC_FILEOPS': (str, 'StoryArc', 'copy'),
'LOCMOVE': (bool, 'Update', False),
'NEWCOM_DIR': (str, 'Update', None),
'FFTONEWCOM_DIR': (bool, 'Update', False),
'ENABLE_META': (bool, 'Metatagging', False),
'CMTAGGER_PATH': (str, 'Metatagging', None),
'CBR2CBZ_ONLY': (bool, 'Metatagging', False),
'CT_TAG_CR': (bool, 'Metatagging', True),
'CT_TAG_CBL': (bool, 'Metatagging', True),
'CT_CBZ_OVERWRITE': (bool, 'Metatagging', False),
'UNRAR_CMD': (str, 'Metatagging', None),
'CT_SETTINGSPATH': (str, 'Metatagging', None),
'CMTAG_VOLUME': (bool, 'Metatagging', True),
'CMTAG_START_YEAR_AS_VOLUME': (bool, 'Metatagging', False),
'SETDEFAULTVOLUME': (bool, 'Metatagging', False),
'ENABLE_TORRENTS': (bool, 'Torrents', False),
'ENABLE_TORRENT_SEARCH': (bool, 'Torrents', False),
'MINSEEDS': (int, 'Torrents', 0),
'AUTO_SNATCH': (bool, 'Torrents', False),
'AUTO_SNATCH_SCRIPT': (str, 'Torrents', None),
'ALLOW_PACKS': (bool, 'Torrents', False),
'TORRENT_LOCAL': (bool, 'Watchdir', False),
'LOCAL_WATCHDIR': (str, 'Watchdir', None),
'TORRENT_SEEDBOX': (bool, 'Seedbox', False),
'SEEDBOX_HOST': (str, 'Seedbox', None),
'SEEDBOX_PORT': (str, 'Seedbox', None),
'SEEDBOX_USER': (str, 'Seedbox', None),
'SEEDBOX_PASS': (str, 'Seedbox', None),
'SEEDBOX_WATCHDIR': (str, 'Seedbox', None),
'ENABLE_32P': (bool, '32P', False),
'SEARCH_32P': (bool, '32P', False), #0': use WS to grab torrent groupings, #1': use 32P to grab torrent groupings
'DEEP_SEARCH_32P': (bool, '32P', False), #0': do not take multiple search series results & use ref32p if available, #1= search each search series result for valid $
'MODE_32P': (bool, '32P', False), #0': legacymode, #1': authmode
'RSSFEED_32P': (str, '32P', None),
'PASSKEY_32P': (str, '32P', None),
'USERNAME_32P': (str, '32P', None),
'PASSWORD_32P': (str, '32P', None),
'VERIFY_32P': (bool, '32P', True),
'RTORRENT_HOST': (str, 'Rtorrent', None),
'RTORRENT_AUTHENTICATION': (str, 'Rtorrent', 'basic'),
'RTORRENT_RPC_URL': (str, 'Rtorrent', None),
'RTORRENT_SSL': (bool, 'Rtorrent', False),
'RTORRENT_VERIFY': (bool, 'Rtorrent', False),
'RTORRENT_CA_BUNDLE': (str, 'Rtorrent', None),
'RTORRENT_USERNAME': (str, 'Rtorrent', None),
'RTORRENT_PASSWORD': (str, 'Rtorrent', None),
'RTORRENT_STARTONLOAD': (bool, 'Rtorrent', False),
'RTORRENT_LABEL': (str, 'Rtorrent', None),
'RTORRENT_DIRECTORY': (str, 'Rtorrent', None),
'UTORRENT_HOST': (str, 'uTorrent', None),
'UTORRENT_USERNAME': (str, 'uTorrent', None),
'UTORRENT_PASSWORD': (str, 'uTorrent', None),
'UTORRENT_LABEL': (str, 'uTorrent', None),
'TRANSMISSION_HOST': (str, 'Transmission', None),
'TRANSMISSION_USERNAME': (str, 'Transmission', None),
'TRANSMISSION_PASSWORD': (str, 'Transmission', None),
'TRANSMISSION_DIRECTORY': (str, 'Transmission', None),
'DELUGE_HOST': (str, 'Deluge', None),
'DELUGE_USERNAME': (str, 'Deluge', None),
'DELUGE_PASSWORD': (str, 'Deluge', None),
'DELUGE_LABEL': (str, 'Deluge', None),
'QBITTORRENT_HOST': (str, 'qBittorrent', None),
'QBITTORRENT_USERNAME': (str, 'qBittorrent', None),
'QBITTORRENT_PASSWORD': (str, 'qBittorrent', None),
'QBITTORRENT_LABEL': (str, 'qBittorrent', None),
'QBITTORRENT_FOLDER': (str, 'qBittorrent', None),
'QBITTORRENT_STARTONLOAD': (bool, 'qBittorrent', False),
})
class Config(object):
def __init__(self, config_file):
# initalize the config...
self._config_file = config_file
def config_vals(self, update=False):
if update is False:
self.config = config.readfp(codecs.open(self._config_file, 'r', 'utf8')) #read(self._config_file)
#check for empty config / new config
count = sum(1 for line in open(self._config_file))
self.newconfig = 7
if count == 0:
CONFIG_VERSION = 0
MINIMALINI = False
else:
# get the config version first, since we need to know.
try:
CONFIG_VERSION = config.getint('General', 'config_version')
except:
CONFIG_VERSION = 0
try:
MINIMALINI = config.getboolean('General', 'minimal_ini')
except:
MINIMALINI = False
setattr(self, 'CONFIG_VERSION', CONFIG_VERSION)
setattr(self, 'MINIMAL_INI', MINIMALINI)
config_values = []
for k,v in _CONFIG_DEFINITIONS.iteritems():
xv = []
xv.append(k)
for x in v:
if x is None:
x = 'None'
xv.append(x)
value = self.check_setting(xv)
if all([k != 'CONFIG_VERSION', k != 'MINIMAL_INI']):
try:
if v[0] == str and any([value == "", value is None, len(value) == 0, value == 'None']):
value = v[2]
except:
value = v[2]
try:
if v[0] == bool:
value = self.argToBool(value)
except:
value = self.argToBool(v[2])
try:
if all([v[0] == int, str(value).isdigit()]):
value = int(value)
except:
value = v[2]
setattr(self, k, value)
#just to ensure defaults are properly set...
if any([value is None, value == 'None']):
value = v[0](v[2])
if all([self.MINIMAL_INI is True, str(value) != str(v[2])]) or self.MINIMAL_INI is False:
try:
config.add_section(v[1])
except ConfigParser.DuplicateSectionError:
pass
else:
try:
if config.has_section(v[1]):
config.remove_option(v[1], k.lower())
except ConfigParser.NoSectionError:
continue
if all([config.has_section(v[1]), self.MINIMAL_INI is False]) or all([self.MINIMAL_INI is True, str(value) != str(v[2]), config.has_section(v[1])]):
config.set(v[1], k.lower(), str(value))
else:
try:
if config.has_section(v[1]):
config.remove_option(v[1], k.lower())
if len(dict(config.items(v[1]))) == 0:
config.remove_section(v[1])
except ConfigParser.NoSectionError:
continue
else:
if k == 'CONFIG_VERSION':
config.remove_option('General', 'dbuser')
config.remove_option('General', 'dbpass')
config.remove_option('General', 'dbchoice')
config.remove_option('General', 'dbname')
elif k == 'MINIMAL_INI':
config.set(v[1], k.lower(), str(self.MINIMAL_INI))
def read(self):
self.config_vals()
setattr(self, 'EXTRA_NEWZNABS', self.get_extra_newznabs())
if any([self.CONFIG_VERSION == 0, self.CONFIG_VERSION < self.newconfig]):
try:
shutil.move(self._config_file, os.path.join(mylar.DATA_DIR, 'config.ini.backup'))
except:
logger.warn('Unable to make proper backup of config file in %s' % os.path.join(mylar.DATA_DIR, 'config.ini.backup'))
setattr(self, 'CONFIG_VERSION', str(self.newconfig))
config.set('General', 'CONFIG_VERSION', str(self.newconfig))
print('Updating config to newest version : %s' % self.newconfig)
self.writeconfig()
else:
self.provider_sequence()
self.configure()
return self
def check_section(self, section, key):
""" Check if INI section exists, if not create it """
if config.has_section(section):
return True
else:
return False
def argToBool(self, argument):
_arg = argument.strip().lower() if isinstance(argument, basestring) else argument
if _arg in (1, '1', 'on', 'true', True):
return True
elif _arg in (0, '0', 'off', 'false', False):
return False
return argument
def check_setting(self, key):
""" Cast any value in the config to the right type or use the default """
keyname = key[0].upper()
inikey = key[0].lower()
definition_type = key[1]
section = key[2]
default = key[3]
myval = self.check_config(definition_type, section, inikey, default)
if myval['status'] is False:
if self.CONFIG_VERSION == 6:
chkstatus = False
if config.has_section('Torrents'):
myval = self.check_config(definition_type, 'Torrents', inikey, default)
if myval['status'] is True:
chkstatus = True
try:
config.remove_option('Torrents', inikey)
except ConfigParser.NoSectionError:
pass
if all([chkstatus is False, config.has_section('General')]):
myval = self.check_config(definition_type, 'General', inikey, default)
if myval['status'] is True:
config.remove_option('General', inikey)
else:
#print 'no key found in ini - setting to default value of %s' % definition_type(default)
#myval = {'value': definition_type(default)}
pass
else:
myval = {'value': definition_type(default)}
#if all([myval['value'] is not None, myval['value'] != '', myval['value'] != 'None']):
#if default != myval['value']:
# print '%s : %s' % (keyname, myval['value'])
#else:
# print 'NEW CONFIGURATION SETTING %s : %s' % (keyname, myval['value'])
return myval['value']
def check_config(self, definition_type, section, inikey, default):
try:
if definition_type == str:
myval = {'status': True, 'value': config.get(section, inikey)}
elif definition_type == int:
myval = {'status': True, 'value': config.getint(section, inikey)}
elif definition_type == bool:
myval = {'status': True, 'value': config.getboolean(section, inikey)}
except Exception:
myval = {'status': False, 'value': None}
return myval
def _define(self, name):
key = name.upper()
ini_key = name.lower()
definition = _CONFIG_DEFINITIONS[key]
if len(definition) == 3:
definition_type, section, default = definition
elif len(definition) == 4:
definition_type, section, _, default = definition
return key, definition_type, section, ini_key, default
def process_kwargs(self, kwargs):
"""
Given a big bunch of key value pairs, apply them to the ini.
"""
for name, value in kwargs.items():
if not any([(name.startswith('newznab') and name[-1].isdigit()), name.startswith('Torznab')]):
key, definition_type, section, ini_key, default = self._define(name)
try:
if any([value == "", value is None, len(value) == 0]) and definition_type == str:
value = default
else:
value = str(value)
except:
value = default
try:
if definition_type == bool:
value = self.argToBool(value)
except:
value = self.argToBool(default)
try:
if all([definition_type == int, str(value).isdigit()]):
value = int(value)
except:
value = default
#just to ensure defaults are properly set...
if any([value is None, value == 'None']):
value = definition_type(default)
if key != 'MINIMAL_INI':
if value == 'None': nv = None
else: nv = definition_type(value)
setattr(self, key, nv)
#print('writing config value...[%s][%s] key: %s / ini_key: %s / value: %s [%s]' % (definition_type, section, key, ini_key, value, default))
if all([self.MINIMAL_INI is True, definition_type(value) != definition_type(default)]) or self.MINIMAL_INI is False:
try:
config.add_section(section)
except ConfigParser.DuplicateSectionError:
pass
else:
try:
if config.has_section(section):
config.remove_option(section, ini_key)
if len(dict(config.items(section))) == 0:
config.remove_section(section)
except ConfigParser.NoSectionError:
continue
if any([value is None, value == ""]):
value = definition_type(default)
if config.has_section(section) and (all([self.MINIMAL_INI is True, definition_type(value) != definition_type(default)]) or self.MINIMAL_INI is False):
config.set(section, ini_key, str(value))
else:
config.set(section, ini_key, str(self.MINIMAL_INI))
else:
pass
def writeconfig(self):
logger.fdebug("Writing configuration to file")
self.provider_sequence()
config.set('Newznab', 'extra_newznabs', ', '.join(self.write_extra_newznabs()))
config.set('General', 'dynamic_update', str(self.DYNAMIC_UPDATE))
try:
with codecs.open(self._config_file, encoding='utf8', mode='w+') as configfile:
config.write(configfile)
except IOError as e:
logger.warn("Error writing configuration file: %s", e)
def configure(self, update=False):
if all(['http://' not in self.SAB_HOST[:7], 'https://' not in self.SAB_HOST[:8], self.SAB_HOST != '', self.SAB_HOST is not None]):
self.SAB_HOST = 'http://' + self.SAB_HOST
if not update:
logger.fdebug('Log dir: %s' % self.LOG_DIR)
if self.LOG_DIR is None:
self.LOG_DIR = os.path.join(mylar.DATA_DIR, 'logs')
if not os.path.exists(self.LOG_DIR):
try:
os.makedirs(self.LOG_DIR)
except OSError:
if not QUIET:
logger.warn('Unable to create the log directory. Logging to screen only.')
if not update:
logger.fdebug('[Cache Check] Cache directory currently set to : ' + self.CACHE_DIR)
# Put the cache dir in the data dir for now
if not self.CACHE_DIR:
self.CACHE_DIR = os.path.join(str(mylar.DATA_DIR), 'cache')
if not update:
logger.fdebug('[Cache Check] Cache directory not found in configuration. Defaulting location to : ' + self.CACHE_DIR)
if not os.path.exists(self.CACHE_DIR):
try:
os.makedirs(self.CACHE_DIR)
except OSError:
logger.error('[Cache Check] Could not create cache dir. Check permissions of datadir: ' + mylar.DATA_DIR)
## Sanity checking
if any([self.COMICVINE_API is None, self.COMICVINE_API == 'None', self.COMICVINE_API == '']):
logger.error('No User Comicvine API key specified. I will not work very well due to api limits - http://api.comicvine.com/ and get your own free key.')
mylar.CONFIG.COMICVINE_API = None
if self.SEARCH_INTERVAL < 360:
logger.fdebug('Search interval too low. Resetting to 6 hour minimum')
self.SEARCH_INTERVAL = 360
if self.SEARCH_DELAY < 1:
logger.fdebug("Minimum search delay set for 1 minute to avoid hammering.")
self.SEARCH_DELAY = 1
if self.RSS_CHECKINTERVAL < 20:
logger.fdebug("Minimum RSS Interval Check delay set for 20 minutes to avoid hammering.")
self.RSS_CHECKINTERVAL = 20
if not helpers.is_number(self.CHMOD_DIR):
logger.fdebug("CHMOD Directory value is not a valid numeric - please correct. Defaulting to 0777")
self.CHMOD_DIR = '0777'
if not helpers.is_number(self.CHMOD_FILE):
logger.fdebug("CHMOD File value is not a valid numeric - please correct. Defaulting to 0660")
self.CHMOD_FILE = '0660'
if self.SAB_HOST.endswith('/'):
logger.fdebug("Auto-correcting trailing slash in SABnzbd url (not required)")
self.SAB_HOST = self.SAB_HOST[:-1]
if self.FILE_OPTS is None:
self.FILE_OPTS = 'move'
if any([self.FILE_OPTS == 'hardlink', self.FILE_OPTS == 'softlink']):
#we can't have metatagging enabled with hard/soft linking. Forcibly disable it here just in case it's set on load.
self.ENABLE_META = False
#comictagger - force to use included version if option is enabled.
if self.ENABLE_META:
mylar.CMTAGGER_PATH = mylar.PROG_DIR
#we need to make sure the default folder setting for the comictagger settings exists so things don't error out
mylar.CT_SETTINGSPATH = os.path.join(mylar.PROG_DIR, 'lib', 'comictaggerlib', 'ct_settings')
if not update:
logger.fdebug('Setting ComicTagger settings default path to : ' + mylar.CT_SETTINGSPATH)
if not os.path.exists(mylar.CT_SETTINGSPATH):
try:
os.mkdir(mylar.CT_SETTINGSPATH)
except OSError,e:
if e.errno != errno.EEXIST:
logger.error('Unable to create setting directory for ComicTagger. This WILL cause problems when tagging.')
else:
logger.fdebug('Successfully created ComicTagger Settings location.')
mylar.USE_SABNZBD = False
mylar.USE_NZBGET = False
mylar.USE_BLACKHOLE = False
if self.NZB_DOWNLOADER == 0:
mylar.USE_SABNZBD = True
elif self.NZB_DOWNLOADER == 1:
mylar.USE_NZBGET = True
elif self.NZB_DOWNLOADER == 2:
mylar.USE_BLACKHOLE = True
else:
#default to SABnzbd
self.NZB_DOWNLOADER = 0
mylar.USE_SABNZBD = True
if self.SAB_PRIORITY.isdigit():
if self.SAB_PRIORITY == "0": self.SAB_PRIORITY = "Default"
elif self.SAB_PRIORITY == "1": self.SAB_PRIORITY = "Low"
elif self.SAB_PRIORITY == "2": self.SAB_PRIORITY = "Normal"
elif self.SAB_PRIORITY == "3": self.SAB_PRIORITY = "High"
elif self.SAB_PRIORITY == "4": self.SAB_PRIORITY = "Paused"
else: self.SAB_PRIORITY = "Default"
setattr(self, 'TORRENT_LOCAL', False)
mylar.USE_WATCHDIR = False
mylar.USE_UTORRENT = False
mylar.USE_RTORRENT = False
mylar.USE_TRANSMISSION = False
mylar.USE_DELUGE = False
mylar.USE_QBITTORRENT = False
if self.TORRENT_DOWNLOADER == 0:
mylar.USE_WATCHDIR = True
elif self.TORRENT_DOWNLOADER == 1:
mylar.USE_UTORRENT = True
elif self.TORRENT_DOWNLOADER == 2:
mylar.USE_RTORRENT = True
elif self.TORRENT_DOWNLOADER == 3:
mylar.USE_TRANSMISSION = True
elif self.TORRENT_DOWNLOADER == 4:
mylar.USE_DELUGE = True
elif self.TORRENT_DOWNLOADER == 5:
mylar.USE_QBITTORRENT = True
else:
self.TORRENT_DOWNLOADER = 0
mylar.USE_WATCHDIR = True
def get_extra_newznabs(self):
extra_newznabs = zip(*[iter(self.EXTRA_NEWZNABS.split(', '))]*6)
return extra_newznabs
def provider_sequence(self):
PR = []
PR_NUM = 0
if self.ENABLE_TORRENT_SEARCH:
if self.ENABLE_32P:
PR.append('32p')
PR_NUM +=1
if self.ENABLE_TPSE:
PR.append('tpse')
PR_NUM +=1
if self.NZBSU:
PR.append('nzb.su')
PR_NUM +=1
if self.DOGNZB:
PR.append('dognzb')
PR_NUM +=1
if self.EXPERIMENTAL:
PR.append('Experimental')
PR_NUM +=1
if self.ENABLE_TORZNAB:
PR.append('Torznab')
PR_NUM +=1
PPR = ['32p', 'tpse', 'nzb.su', 'dognzb', 'Experimental', 'Torzanb']
if self.NEWZNAB:
for ens in self.EXTRA_NEWZNABS:
if str(ens[5]) == '1': # if newznabs are enabled
if ens[0] == "":
en_name = ens[1]
else:
en_name = ens[0]
PR.append(en_name)
PPR.append(en_name)
PR_NUM +=1
if self.PROVIDER_ORDER is not None:
try:
PRO_ORDER = zip(*[iter(self.PROVIDER_ORDER.split(', '))]*2)
except:
PO = []
for k, v in self.PROVIDER_ORDER.iteritems():
PO.append(k)
PO.append(v)
POR = ', '.join(PO)
PRO_ORDER = zip(*[iter(POR.split(', '))]*2)
logger.info('provider_order: %s' % self.PROVIDER_ORDER)
#if provider order exists already, load it and then append to end any NEW entries.
logger.fdebug('Provider sequence already pre-exists. Re-loading and adding/remove any new entries')
TMPPR_NUM = 0
PROV_ORDER = []
#load original sequence
for PRO in PRO_ORDER:
PROV_ORDER.append({"order_seq": PRO[0],
"provider": str(PRO[1])})
TMPPR_NUM +=1
#calculate original sequence to current sequence for discrepancies
#print('TMPPR_NUM: %s --- PR_NUM: %s' % (TMPPR_NUM, PR_NUM))
if PR_NUM != TMPPR_NUM:
logger.fdebug('existing Order count does not match New Order count')
if PR_NUM > TMPPR_NUM:
logger.fdebug('%s New entries exist, appending to end as default ordering' % (PR_NUM - TMPPR_NUM))
TOTALPR = (TMPPR_NUM + PR_NUM)
else:
logger.fdebug('%s Disabled entries exist, removing from ordering sequence' % (TMPPR_NUM - PR_NUM))
TOTALPR = TMPPR_NUM
if PR_NUM > 0:
logger.fdebug('%s entries are enabled.' % PR_NUM)
NEW_PROV_ORDER = []
i = 0
#this should loop over ALL possible entries
while i < len(PR):
found = False
for d in PPR:
#logger.fdebug('checking entry %s against %s' % (PR[i], d) #d['provider'])
if d == PR[i]:
x = [p['order_seq'] for p in PROV_ORDER if p['provider'] == PR[i]]
if x:
ord = x[0]
else:
ord = i
found = {'provider': PR[i],
'order': ord}
break
else:
found = False
if found is not False:
new_order_seqnum = len(NEW_PROV_ORDER)
if new_order_seqnum <= found['order']:
seqnum = found['order']
else:
seqnum = new_order_seqnum
NEW_PROV_ORDER.append({"order_seq": len(NEW_PROV_ORDER),
"provider": found['provider'],
"orig_seq": int(seqnum)})
i+=1
#now we reorder based on priority of orig_seq, but use a new_order seq
xa = 0
NPROV = []
for x in sorted(NEW_PROV_ORDER, key=itemgetter('orig_seq'), reverse=False):
NPROV.append(str(xa))
NPROV.append(x['provider'])
xa+=1
PROVIDER_ORDER = NPROV
else:
#priority provider sequence in order#, ProviderName
logger.fdebug('creating provider sequence order now...')
TMPPR_NUM = 0
PROV_ORDER = []
while TMPPR_NUM < PR_NUM:
PROV_ORDER.append(str(TMPPR_NUM))
PROV_ORDER.append(PR[TMPPR_NUM])
#{"order_seq": TMPPR_NUM,
#"provider": str(PR[TMPPR_NUM])})
TMPPR_NUM +=1
PROVIDER_ORDER = PROV_ORDER
ll = ', '.join(PROVIDER_ORDER)
if not config.has_section('Providers'):
config.add_section('Providers')
config.set('Providers', 'PROVIDER_ORDER', ll)
PROVIDER_ORDER = dict(zip(*[PROVIDER_ORDER[i::2] for i in range(2)]))
setattr(self, 'PROVIDER_ORDER', PROVIDER_ORDER)
logger.fdebug('Provider Order is now set : %s ' % self.PROVIDER_ORDER)
def write_extra_newznabs(self):
flattened_newznabs = []
for item in self.EXTRA_NEWZNABS:
for i in item:
flattened_newznabs.append(str(i))
return flattened_newznabs