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