# # This file is part of SickGear. # # SickGear is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # SickGear is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with SickGear. If not, see . from collections import OrderedDict from threading import Lock import copy import datetime import io import os import re import signal import socket import time import webbrowser # apparently py2exe won't build these unless they're imported somewhere import os.path import sys import threading import uuid import zlib from . import classes, db, helpers, image_cache, indexermapper, logger, metadata, naming, people_queue, providers, \ scene_exceptions, scene_numbering, scheduler, search_backlog, search_propers, search_queue, search_recent, \ show_queue, show_updater, subtitles, trakt_helpers, version_checker, watchedstate_queue from . import auto_media_process, properFinder # must come after the above imports from .common import SD, SKIPPED, USER_AGENT from .config import check_section, check_setting_int, check_setting_str, ConfigMigrator, minimax from .databases import cache_db, failed_db, mainDB from .event_queue import ConfigEvents from .indexers.indexer_api import TVInfoAPI from .indexers.indexer_config import TVINFO_IMDB, TVINFO_TVDB, TmdbIndexer from .providers.generic import GenericProvider from .providers.newznab import NewznabConstants from .tv import TVidProdid from .watchedstate import EmbyWatchedStateUpdater, PlexWatchedStateUpdater from .webserve import History from adba.aniDBerrors import AniDBError # noinspection PyProtectedMember from browser_ua import get_ua from configobj import ConfigObj from api_trakt import TraktAPI from _23 import b64encodestring, decode_bytes, scandir from sg_helpers import remove_file_perm from six import iteritems, string_types import sg_helpers # noinspection PyUnreachableCode if False: from typing import AnyStr, Dict, List, Optional from adba import Connection from .event_queue import Events from .tv import TVShow from lib.api_trakt.trakt import TraktAccount PID = None ENV = {} # noinspection PyTypeChecker CFG = None # type: ConfigObj CONFIG_FILE = '' CONFIG_VERSION = None CONFIG_OLD = None CONFIG_LOADED = False # Default encryption version (0 for None) ENCRYPTION_VERSION = 0 PROG_DIR = '.' MY_FULLNAME = None MY_ARGS = [] SYS_ENCODING = '' DATA_DIR = '' # system events # noinspection PyTypeChecker events = None # type: Events config_events = None # type: ConfigEvents show_queue_scheduler = None # type: Optional[scheduler.Scheduler] search_queue_scheduler = None # type: Optional[scheduler.Scheduler] people_queue_scheduler = None # type: Optional[scheduler.Scheduler] watched_state_queue_scheduler = None # type: Optional[scheduler.Scheduler] update_software_scheduler = None # type: Optional[scheduler.Scheduler] update_packages_scheduler = None # type: Optional[scheduler.Scheduler] update_show_scheduler = None # type: Optional[scheduler.Scheduler] update_release_mappings_scheduler = None # type: Optional[scheduler.Scheduler] search_recent_scheduler = None # type: Optional[scheduler.Scheduler] search_backlog_scheduler = None # type: Optional[search_backlog.BacklogSearchScheduler] search_propers_scheduler = None # type: Optional[scheduler.Scheduler] search_subtitles_scheduler = None # type: Optional[scheduler.Scheduler] emby_watched_state_scheduler = None # type: Optional[scheduler.Scheduler] plex_watched_state_scheduler = None # type: Optional[scheduler.Scheduler] process_media_scheduler = None # type: Optional[scheduler.Scheduler] # noinspection PyTypeChecker background_mapping_task = None # type: threading.Thread # deprecated # trakt_checker_scheduler = None provider_ping_thread_pool = {} showList = [] # type: List[TVShow] showDict = {} # type: Dict[int, TVShow] switched_shows = {} # type: Dict[AnyStr, AnyStr] UPDATE_SHOWS_ON_START = False SHOW_UPDATE_HOUR = 3 # non ui settings REMOVE_FILENAME_CHARS = None IMPORT_DEFAULT_CHECKED_SHOWS = 0 # /non ui settings provider_list = [] newznab_providers = [] torrent_rss_providers = [] metadata_provider_dict = {} MODULE_UPDATE_STRING = None NEWEST_VERSION_STRING = None MIN_UPDATE_INTERVAL = 1 DEFAULT_UPDATE_INTERVAL = 12 UPDATE_NOTIFY = False UPDATE_AUTO = False UPDATE_INTERVAL = DEFAULT_UPDATE_INTERVAL NOTIFY_ON_UPDATE = False MIN_UPDATE_PACKAGES_INTERVAL = 1 MAX_UPDATE_PACKAGES_INTERVAL = 9999 DEFAULT_UPDATE_PACKAGES_INTERVAL = 24 UPDATE_PACKAGES_NOTIFY = False UPDATE_PACKAGES_AUTO = False UPDATE_PACKAGES_MENU = False UPDATE_PACKAGES_INTERVAL = DEFAULT_UPDATE_PACKAGES_INTERVAL CUR_COMMIT_HASH = None EXT_UPDATES = False BRANCH = '' GIT_REMOTE = '' CUR_COMMIT_BRANCH = '' INIT_LOCK = Lock() started = False ACTUAL_LOG_DIR = None LOG_DIR = None FILE_LOGGING_PRESET = 'DEBUG' SOCKET_TIMEOUT = None WEB_PORT = None WEB_LOG = 0 WEB_ROOT = None WEB_USERNAME = None WEB_PASSWORD = None WEB_HOST = None WEB_IPV6 = 0 WEB_IPV64 = 0 HANDLE_REVERSE_PROXY = False SEND_SECURITY_HEADERS = True ALLOWED_HOSTS = None ALLOW_ANYIP = True PROXY_SETTING = None PROXY_INDEXERS = True CPU_PRESET = 'DISABLED' ANON_REDIRECT = None USE_API = False API_KEYS = [] ENABLE_HTTPS = False HTTPS_CERT = None HTTPS_KEY = None LAUNCH_BROWSER = False CACHE_DIR = None ACTUAL_CACHE_DIR = None ZONEINFO_DIR = None ROOT_DIRS = None TRASH_REMOVE_SHOW = False TRASH_ROTATE_LOGS = False HOME_SEARCH_FOCUS = True DISPLAY_FREESPACE = True SORT_ARTICLE = False DEBUG = False SHOW_TAGS = [] SHOW_TAG_DEFAULT = '' SHOWLIST_TAGVIEW = '' METADATA_XBMC = None METADATA_XBMC_12PLUS = None METADATA_MEDIABROWSER = None METADATA_PS3 = None METADATA_WDTV = None METADATA_TIVO = None METADATA_MEDE8ER = None METADATA_KODI = None RESULTS_SORTBY = None TVINFO_DEFAULT = 0 TVINFO_TIMEOUT = 20 QUALITY_DEFAULT = SD WANTED_BEGIN_DEFAULT = 0 WANTED_LATEST_DEFAULT = 0 PAUSE_DEFAULT = False STATUS_DEFAULT = SKIPPED SCENE_DEFAULT = False SUBTITLES_DEFAULT = False FLATTEN_FOLDERS_DEFAULT = False ANIME_DEFAULT = False USE_IMDB_INFO = True IMDB_ACCOUNTS = [] IMDB_DEFAULT_LIST_ID = '64552276' IMDB_DEFAULT_LIST_NAME = 'SickGear' PROVIDER_ORDER = [] PROVIDER_HOMES = {} NAMING_MULTI_EP = False NAMING_ANIME_MULTI_EP = False NAMING_PATTERN = None NAMING_ABD_PATTERN = None NAMING_CUSTOM_ABD = False NAMING_SPORTS_PATTERN = None NAMING_CUSTOM_SPORTS = False NAMING_ANIME_PATTERN = None NAMING_CUSTOM_ANIME = False NAMING_FORCE_FOLDERS = False NAMING_STRIP_YEAR = False NAMING_ANIME = 3 USE_NZBS = False USE_TORRENTS = False NZB_METHOD = None NZB_DIR = None USENET_RETENTION = 500 TORRENT_METHOD = None TORRENT_DIR = None DOWNLOAD_PROPERS = False PROPERS_WEBDL_ONEGRP = True WEBDL_TYPES = [] ALLOW_HIGH_PRIORITY = False NEWZNAB_DATA = '' DEFAULT_MEDIAPROCESS_INTERVAL = 10 DEFAULT_BACKLOG_PERIOD = 21 DEFAULT_RECENTSEARCH_INTERVAL = 40 DEFAULT_WATCHEDSTATE_INTERVAL = 10 MEDIAPROCESS_INTERVAL = DEFAULT_MEDIAPROCESS_INTERVAL BACKLOG_PERIOD = DEFAULT_BACKLOG_PERIOD BACKLOG_LIMITED_PERIOD = 7 RECENTSEARCH_INTERVAL = DEFAULT_RECENTSEARCH_INTERVAL RECENTSEARCH_STARTUP = False BACKLOG_NOFULL = False MIN_MEDIAPROCESS_INTERVAL = 1 MIN_RECENTSEARCH_INTERVAL = 10 MIN_BACKLOG_PERIOD = 7 MAX_BACKLOG_PERIOD = 42 MIN_WATCHEDSTATE_INTERVAL = 10 MAX_WATCHEDSTATE_INTERVAL = 60 SEARCH_UNAIRED = False UNAIRED_RECENT_SEARCH_ONLY = True FLARESOLVERR_HOST = None ADD_SHOWS_WO_DIR = False ADD_SHOWS_METALANG = 'en' CREATE_MISSING_SHOW_DIRS = False SHOW_DIRS_WITH_DOTS = False RENAME_EPISODES = False RENAME_TBA_EPISODES = True RENAME_NAME_CHANGED_EPISODES = False AIRDATE_EPISODES = False PROCESS_AUTOMATICALLY = False KEEP_PROCESSED_DIR = False PROCESS_LAST_DIR = None PROCESS_LAST_METHOD = None PROCESS_LAST_CLEANUP = False PROCESS_METHOD = None MOVE_ASSOCIATED_FILES = False POSTPONE_IF_SYNC_FILES = True PROCESS_POSITIVE_LOG = True NFO_RENAME = True TV_DOWNLOAD_DIR = None UNPACK = False SKIP_REMOVED_FILES = False NZBGET_USERNAME = None NZBGET_PASSWORD = None NZBGET_CATEGORY = None NZBGET_HOST = None NZBGET_USE_HTTPS = False NZBGET_PRIORITY = 100 NZBGET_SCRIPT_VERSION = None NZBGET_MAP = None NZBGET_SKIP_PM = False SAB_USERNAME = None SAB_PASSWORD = None SAB_APIKEY = None SAB_CATEGORY = None SAB_HOST = '' TORRENT_USERNAME = None TORRENT_PASSWORD = None TORRENT_HOST = '' TORRENT_PATH = '' TORRENT_SEED_TIME = 0 TORRENT_PAUSED = False TORRENT_HIGH_BANDWIDTH = False TORRENT_LABEL = '' TORRENT_LABEL_VAR = 1 TORRENT_VERIFY_CERT = False USE_EMBY = False EMBY_UPDATE_LIBRARY = False EMBY_PARENT_MAPS = None EMBY_HOST = None EMBY_APIKEY = None EMBY_WATCHEDSTATE_SCHEDULED = False EMBY_WATCHEDSTATE_INTERVAL = DEFAULT_WATCHEDSTATE_INTERVAL USE_KODI = False KODI_ALWAYS_ON = True KODI_NOTIFY_ONSNATCH = False KODI_NOTIFY_ONDOWNLOAD = False KODI_NOTIFY_ONSUBTITLEDOWNLOAD = False KODI_UPDATE_LIBRARY = False KODI_UPDATE_FULL = False KODI_UPDATE_ONLYFIRST = False KODI_PARENT_MAPS = None KODI_HOST = '' KODI_USERNAME = None KODI_PASSWORD = None USE_PLEX = False PLEX_NOTIFY_ONSNATCH = False PLEX_NOTIFY_ONDOWNLOAD = False PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = False PLEX_UPDATE_LIBRARY = False PLEX_PARENT_MAPS = None PLEX_SERVER_HOST = None PLEX_HOST = None PLEX_USERNAME = None PLEX_PASSWORD = None PLEX_WATCHEDSTATE_SCHEDULED = False PLEX_WATCHEDSTATE_INTERVAL = DEFAULT_WATCHEDSTATE_INTERVAL USE_XBMC = False XBMC_ALWAYS_ON = True XBMC_NOTIFY_ONSNATCH = False XBMC_NOTIFY_ONDOWNLOAD = False XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = False XBMC_UPDATE_LIBRARY = False XBMC_UPDATE_FULL = False XBMC_UPDATE_ONLYFIRST = False XBMC_HOST = '' XBMC_USERNAME = None XBMC_PASSWORD = None QUEUE_UPDATE_LIBRARY = [] USE_NMJ = False NMJ_HOST = None NMJ_DATABASE = None NMJ_MOUNT = None USE_NMJv2 = False NMJv2_HOST = None NMJv2_DATABASE = None NMJv2_DBLOC = None USE_SYNOINDEX = False SYNOINDEX_UPDATE_LIBRARY = True USE_SYNOLOGYNOTIFIER = False SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH = False SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = False SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = False USE_PYTIVO = False PYTIVO_HOST = '' PYTIVO_SHARE_NAME = '' PYTIVO_TIVO_NAME = '' USE_BOXCAR2 = False BOXCAR2_NOTIFY_ONSNATCH = False BOXCAR2_NOTIFY_ONDOWNLOAD = False BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = False BOXCAR2_ACCESSTOKEN = None BOXCAR2_SOUND = None USE_PUSHBULLET = False PUSHBULLET_NOTIFY_ONSNATCH = False PUSHBULLET_NOTIFY_ONDOWNLOAD = False PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = False PUSHBULLET_ACCESS_TOKEN = None PUSHBULLET_DEVICE_IDEN = None USE_PUSHOVER = False PUSHOVER_NOTIFY_ONSNATCH = False PUSHOVER_NOTIFY_ONDOWNLOAD = False PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = False PUSHOVER_USERKEY = None PUSHOVER_APIKEY = None PUSHOVER_PRIORITY = '0' PUSHOVER_DEVICE = None PUSHOVER_SOUND = None USE_GROWL = False GROWL_NOTIFY_ONSNATCH = False GROWL_NOTIFY_ONDOWNLOAD = False GROWL_NOTIFY_ONSUBTITLEDOWNLOAD = False GROWL_HOST = '' USE_PROWL = False PROWL_NOTIFY_ONSNATCH = False PROWL_NOTIFY_ONDOWNLOAD = False PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = False PROWL_API = None PROWL_PRIORITY = '0' USE_LIBNOTIFY = False LIBNOTIFY_NOTIFY_ONSNATCH = False LIBNOTIFY_NOTIFY_ONDOWNLOAD = False LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD = False USE_PUSHALOT = False PUSHALOT_NOTIFY_ONSNATCH = False PUSHALOT_NOTIFY_ONDOWNLOAD = False PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = False PUSHALOT_AUTHORIZATIONTOKEN = None USE_TRAKT = False TRAKT_REMOVE_WATCHLIST = False TRAKT_REMOVE_SERIESLIST = False TRAKT_USE_WATCHLIST = False TRAKT_METHOD_ADD = 0 TRAKT_START_PAUSED = False TRAKT_SYNC = False TRAKT_DEFAULT_INDEXER = None TRAKT_UPDATE_COLLECTION = {} USE_SLACK = False SLACK_NOTIFY_ONSNATCH = False SLACK_NOTIFY_ONDOWNLOAD = False SLACK_NOTIFY_ONSUBTITLEDOWNLOAD = False SLACK_CHANNEL = None SLACK_AS_AUTHED = False SLACK_BOT_NAME = None SLACK_ICON_URL = None SLACK_ACCESS_TOKEN = None USE_DISCORD = False DISCORD_NOTIFY_ONSNATCH = False DISCORD_NOTIFY_ONDOWNLOAD = False DISCORD_NOTIFY_ONSUBTITLEDOWNLOAD = False DISCORD_AS_AUTHED = False DISCORD_USERNAME = None DISCORD_ICON_URL = None DISCORD_AS_TTS = 0 DISCORD_ACCESS_TOKEN = None USE_GITTER = False GITTER_NOTIFY_ONSNATCH = False GITTER_NOTIFY_ONDOWNLOAD = False GITTER_NOTIFY_ONSUBTITLEDOWNLOAD = False GITTER_ROOM = None GITTER_ACCESS_TOKEN = None USE_TELEGRAM = False TELEGRAM_NOTIFY_ONSNATCH = False TELEGRAM_NOTIFY_ONDOWNLOAD = False TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD = False TELEGRAM_SEND_IMAGE = True TELEGRAM_QUIET = False TELEGRAM_ACCESS_TOKEN = None TELEGRAM_CHATID = None USE_EMAIL = False EMAIL_OLD_SUBJECTS = False EMAIL_NOTIFY_ONSNATCH = False EMAIL_NOTIFY_ONDOWNLOAD = False EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD = False EMAIL_HOST = None EMAIL_PORT = 25 EMAIL_TLS = False EMAIL_USER = None EMAIL_PASSWORD = None EMAIL_FROM = None EMAIL_LIST = None USE_ANIDB = False ANIDB_USERNAME = None ANIDB_PASSWORD = None ANIDB_USE_MYLIST = False # noinspection PyTypeChecker ADBA_CONNECTION = None # type: Connection ANIME_TREAT_AS_HDTV = False GUI_NAME = '' DEFAULT_HOME = None FANART_LIMIT = None FANART_PANEL = None FANART_RATINGS = {} HOME_LAYOUT = None FOOTER_TIME_LAYOUT = 0 POSTER_SORTBY = None POSTER_SORTDIR = None DISPLAY_SHOW_GLIDE = {} DISPLAY_SHOW_GLIDE_SLIDETIME = 3000 DISPLAY_SHOW_VIEWMODE = 0 DISPLAY_SHOW_BACKGROUND = False DISPLAY_SHOW_BACKGROUND_TRANSLUCENT = False DISPLAY_SHOW_VIEWART = 0 DISPLAY_SHOW_MINIMUM = True DISPLAY_SHOW_SPECIALS = False EPISODE_VIEW_VIEWMODE = 0 EPISODE_VIEW_BACKGROUND = False EPISODE_VIEW_BACKGROUND_TRANSLUCENT = False EPISODE_VIEW_LAYOUT = None EPISODE_VIEW_SORT = None EPISODE_VIEW_DISPLAY_PAUSED = 0 EPISODE_VIEW_POSTERS = True EPISODE_VIEW_MISSED_RANGE = 7 HISTORY_LAYOUT = None BROWSELIST_HIDDEN = [] BROWSELIST_MRU = {} FUZZY_DATING = False TRIM_ZERO = False DATE_PRESET = None TIME_PRESET = None TIME_PRESET_W_SECONDS = None TIMEZONE_DISPLAY = None THEME_NAME = None USE_SUBTITLES = False SUBTITLES_LANGUAGES = [] SUBTITLES_DIR = '' SUBTITLES_OS_HASH = True SUBTITLES_SERVICES_LIST = [] SUBTITLES_SERVICES_ENABLED = [] SUBTITLES_SERVICES_AUTH = [['', '']] SUBTITLES_HISTORY = False SUBTITLES_FINDER_INTERVAL = 1 USE_FAILED_DOWNLOADS = False DELETE_FAILED = False BACKUP_DB_PATH = '' # type: AnyStr BACKUP_DB_ONEDAY = False # type: bool BACKUP_DB_MAX_COUNT = 14 # type: int BACKUP_DB_DEFAULT_COUNT = 14 # type: int UPDATES_TODO = {} EXTRA_SCRIPTS = [] SG_EXTRA_SCRIPTS = [] GIT_PATH = None IGNORE_WORDS = { r'^(?=.*?\bspanish\b)((?!spanish.?princess).)*$', 'core2hd', 'hevc', 'MrLss', 'reenc', 'x265', 'danish', 'deutsch', 'dutch', 'flemish', 'french', 'german', 'italian', 'nordic', 'norwegian', 'portuguese', 'spanish', 'swedish', 'turkish' } IGNORE_WORDS_REGEX = True REQUIRE_WORDS = set() REQUIRE_WORDS_REGEX = False WANTEDLIST_CACHE = None CALENDAR_UNPROTECTED = False TMDB_API_KEY = TmdbIndexer.API_KEY FANART_API_KEY = '3728ca1a2a937ba0c93b6e63cc86cecb' # to switch between staging and production TRAKT environment TRAKT_STAGING = False TRAKT_TIMEOUT = 60 TRAKT_VERIFY = True TRAKT_CONNECTED_ACCOUNT = None TRAKT_ACCOUNTS = {} # type: Dict[int, TraktAccount] TRAKT_MRU = '' if TRAKT_STAGING: # staging trakt values: TRAKT_CLIENT_ID = '2aae3052f90b14235d184cc8f709b12b4fd8ae35f339a060a890c70db92be87a' TRAKT_CLIENT_SECRET = '900e03471220503843d4a856bfbef17080cddb630f2b7df6a825e96e3ff3c39e' TRAKT_PIN_URL = 'https://staging.trakt.tv/pin/638' TRAKT_BASE_URL = 'http' + '://api.staging.trakt.tv/' else: # production trakt values: TRAKT_CLIENT_ID = 'f1c453c67d81f1307f9118172c408a883eb186b094d5ea33080d59ddedb7fc7c' TRAKT_CLIENT_SECRET = '12efb6fb6e863a08934d9904032a90008325df7e23514650cade55e7e7c118c5' TRAKT_PIN_URL = 'https://trakt.tv/pin/6314' TRAKT_BASE_URL = 'https://api.trakt.tv/' IMDB_MRU = '' MC_MRU = '' NE_MRU = '' TMDB_MRU = '' TVC_MRU = '' TVM_MRU = '' COOKIE_SECRET = b64encodestring(uuid.uuid4().bytes + uuid.uuid4().bytes) CACHE_IMAGE_URL_LIST = classes.ImageUrlList() __INITIALIZED__ = False __INIT_STAGE__ = 0 # don't reassign MEMCACHE var without reassigning sg_helpers.MEMCACHE # and scene_exceptions.MEMCACHE # as long as the pointer is the same (dict only modified) all is fine MEMCACHE = {} sg_helpers.MEMCACHE = MEMCACHE scene_exceptions.MEMCACHE = MEMCACHE MEMCACHE_FLAG_IMAGES = {} def get_backlog_cycle_time(): cycletime = RECENTSEARCH_INTERVAL * 2 + 7 return max([cycletime, 720]) def initialize(console_logging=True): with INIT_LOCK: # Misc global __INITIALIZED__, __INIT_STAGE__ if __INITIALIZED__: return False __INIT_STAGE__ += 1 if 1 == __INIT_STAGE__: init_stage_1(console_logging) else: return init_stage_2() def init_stage_1(console_logging): # Misc global showList, showDict, switched_shows, provider_list, newznab_providers, torrent_rss_providers, \ WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, ADD_SHOWS_METALANG, \ CREATE_MISSING_SHOW_DIRS, SHOW_DIRS_WITH_DOTS, \ RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, TVINFO_DEFAULT, \ CONFIG_FILE, CONFIG_VERSION, CONFIG_OLD, CONFIG_LOADED, \ REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS, WANTEDLIST_CACHE, MODULE_UPDATE_STRING, EXT_UPDATES # Add Show Search global RESULTS_SORTBY # Add Show Defaults global QUALITY_DEFAULT, WANTED_BEGIN_DEFAULT, WANTED_LATEST_DEFAULT, SHOW_TAG_DEFAULT, PAUSE_DEFAULT, \ STATUS_DEFAULT, SCENE_DEFAULT, SUBTITLES_DEFAULT, FLATTEN_FOLDERS_DEFAULT, ANIME_DEFAULT # Post-processing global KEEP_PROCESSED_DIR, PROCESS_LAST_DIR, PROCESS_LAST_METHOD, PROCESS_LAST_CLEANUP # Views global GUI_NAME, HOME_LAYOUT, FOOTER_TIME_LAYOUT, POSTER_SORTBY, POSTER_SORTDIR, DISPLAY_SHOW_SPECIALS, \ EPISODE_VIEW_LAYOUT, EPISODE_VIEW_SORT, EPISODE_VIEW_DISPLAY_PAUSED, \ EPISODE_VIEW_MISSED_RANGE, EPISODE_VIEW_POSTERS, FANART_PANEL, FANART_RATINGS, \ EPISODE_VIEW_VIEWMODE, EPISODE_VIEW_BACKGROUND, EPISODE_VIEW_BACKGROUND_TRANSLUCENT, \ DISPLAY_SHOW_GLIDE, DISPLAY_SHOW_GLIDE_SLIDETIME, \ DISPLAY_SHOW_VIEWMODE, DISPLAY_SHOW_BACKGROUND, DISPLAY_SHOW_BACKGROUND_TRANSLUCENT, \ DISPLAY_SHOW_VIEWART, DISPLAY_SHOW_MINIMUM, DISPLAY_SHOW_SPECIALS, HISTORY_LAYOUT, \ BROWSELIST_HIDDEN, BROWSELIST_MRU # Gen Config/Misc global LAUNCH_BROWSER, UPDATE_SHOWS_ON_START, SHOW_UPDATE_HOUR, \ TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, ACTUAL_LOG_DIR, LOG_DIR, TVINFO_TIMEOUT, ROOT_DIRS, \ UPDATE_NOTIFY, UPDATE_AUTO, UPDATE_INTERVAL, NOTIFY_ON_UPDATE,\ UPDATE_PACKAGES_NOTIFY, UPDATE_PACKAGES_AUTO, UPDATE_PACKAGES_MENU, UPDATE_PACKAGES_INTERVAL # Gen Config/Interface global THEME_NAME, DEFAULT_HOME, FANART_LIMIT, SHOWLIST_TAGVIEW, SHOW_TAGS, \ HOME_SEARCH_FOCUS, USE_IMDB_INFO, IMDB_ACCOUNTS, DISPLAY_FREESPACE, SORT_ARTICLE, FUZZY_DATING, TRIM_ZERO, \ DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, TIMEZONE_DISPLAY, \ WEB_USERNAME, WEB_PASSWORD, CALENDAR_UNPROTECTED, USE_API, API_KEYS, WEB_PORT, WEB_LOG, \ ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, WEB_IPV6, WEB_IPV64, HANDLE_REVERSE_PROXY, \ SEND_SECURITY_HEADERS, ALLOWED_HOSTS, ALLOW_ANYIP # Gen Config/Advanced global BRANCH, CUR_COMMIT_BRANCH, GIT_REMOTE, CUR_COMMIT_HASH, GIT_PATH, CPU_PRESET, ANON_REDIRECT, \ ENCRYPTION_VERSION, PROXY_SETTING, PROXY_INDEXERS, FILE_LOGGING_PRESET # Search Settings/Episode global DOWNLOAD_PROPERS, PROPERS_WEBDL_ONEGRP, WEBDL_TYPES, RECENTSEARCH_INTERVAL, \ BACKLOG_LIMITED_PERIOD, BACKLOG_NOFULL, BACKLOG_PERIOD, USENET_RETENTION, IGNORE_WORDS, REQUIRE_WORDS, \ IGNORE_WORDS, IGNORE_WORDS_REGEX, REQUIRE_WORDS, REQUIRE_WORDS_REGEX, \ ALLOW_HIGH_PRIORITY, SEARCH_UNAIRED, UNAIRED_RECENT_SEARCH_ONLY, FLARESOLVERR_HOST # Search Settings/NZB search global USE_NZBS, NZB_METHOD, NZB_DIR, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBGET_USE_HTTPS, NZBGET_HOST, NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, \ NZBGET_SCRIPT_VERSION, NZBGET_MAP, NZBGET_SKIP_PM # Search Settings/Torrent search global USE_TORRENTS, TORRENT_METHOD, TORRENT_DIR, TORRENT_HOST, TORRENT_USERNAME, TORRENT_PASSWORD, \ TORRENT_LABEL, TORRENT_LABEL_VAR, TORRENT_PATH, TORRENT_SEED_TIME, TORRENT_PAUSED, \ TORRENT_HIGH_BANDWIDTH, TORRENT_VERIFY_CERT # Media Providers global PROVIDER_ORDER, NEWZNAB_DATA, PROVIDER_HOMES # Subtitles global USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_FINDER_INTERVAL, SUBTITLES_OS_HASH, \ SUBTITLES_HISTORY, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_SERVICES_AUTH # Media Process/Post-Processing global TV_DOWNLOAD_DIR, PROCESS_METHOD, PROCESS_AUTOMATICALLY, MEDIAPROCESS_INTERVAL, \ POSTPONE_IF_SYNC_FILES, PROCESS_POSITIVE_LOG, EXTRA_SCRIPTS, SG_EXTRA_SCRIPTS, \ DEFAULT_MEDIAPROCESS_INTERVAL, MIN_MEDIAPROCESS_INTERVAL, \ UNPACK, SKIP_REMOVED_FILES, MOVE_ASSOCIATED_FILES, NFO_RENAME, \ RENAME_EPISODES, RENAME_TBA_EPISODES, RENAME_NAME_CHANGED_EPISODES, \ AIRDATE_EPISODES, USE_FAILED_DOWNLOADS, DELETE_FAILED # Media Process/Episode Naming global NAMING_PATTERN, NAMING_MULTI_EP, NAMING_STRIP_YEAR, NAMING_CUSTOM_ABD, NAMING_ABD_PATTERN, \ NAMING_CUSTOM_SPORTS, NAMING_SPORTS_PATTERN, \ NAMING_CUSTOM_ANIME, NAMING_ANIME_PATTERN, NAMING_ANIME_MULTI_EP, NAMING_ANIME # Media Process/Metadata global METADATA_KODI, METADATA_MEDE8ER, METADATA_XBMC, METADATA_MEDIABROWSER, \ METADATA_PS3, METADATA_TIVO, METADATA_WDTV, METADATA_XBMC_12PLUS # Notification Settings/HT and NAS global USE_EMBY, EMBY_UPDATE_LIBRARY, EMBY_PARENT_MAPS, EMBY_HOST, EMBY_APIKEY, \ EMBY_WATCHEDSTATE_SCHEDULED, EMBY_WATCHEDSTATE_INTERVAL, \ USE_KODI, KODI_ALWAYS_ON, KODI_UPDATE_LIBRARY, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, \ KODI_PARENT_MAPS, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, KODI_NOTIFY_ONSNATCH, \ KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, \ XBMC_UPDATE_LIBRARY, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \ USE_PLEX, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE_LIBRARY, PLEX_PARENT_MAPS, PLEX_SERVER_HOST, \ PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_HOST, \ PLEX_WATCHEDSTATE_SCHEDULED, PLEX_WATCHEDSTATE_INTERVAL, \ USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, \ USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, \ USE_SYNOINDEX, \ USE_SYNOLOGYNOTIFIER, SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH, \ SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD, SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_PYTIVO, PYTIVO_HOST, PYTIVO_SHARE_NAME, PYTIVO_TIVO_NAME # Notification Settings/Devices global USE_GROWL, GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, GROWL_NOTIFY_ONSUBTITLEDOWNLOAD, \ GROWL_HOST, \ USE_PROWL, PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_NOTIFY_ONSUBTITLEDOWNLOAD, \ PROWL_API, PROWL_PRIORITY, \ USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, \ LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_PUSHOVER, PUSHOVER_NOTIFY_ONSNATCH, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD, \ PUSHOVER_USERKEY, PUSHOVER_APIKEY, PUSHOVER_PRIORITY, PUSHOVER_DEVICE, PUSHOVER_SOUND, \ USE_BOXCAR2, BOXCAR2_NOTIFY_ONSNATCH, BOXCAR2_NOTIFY_ONDOWNLOAD, BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD, \ BOXCAR2_ACCESSTOKEN, BOXCAR2_SOUND, \ USE_PUSHALOT, PUSHALOT_NOTIFY_ONSNATCH, PUSHALOT_NOTIFY_ONDOWNLOAD, \ PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD, PUSHALOT_AUTHORIZATIONTOKEN, \ USE_PUSHBULLET, PUSHBULLET_NOTIFY_ONSNATCH, PUSHBULLET_NOTIFY_ONDOWNLOAD, \ PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD, PUSHBULLET_ACCESS_TOKEN, PUSHBULLET_DEVICE_IDEN # Notification Settings/Social global USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, \ TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \ TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \ MC_MRU, NE_MRU, TMDB_MRU, TVC_MRU, TVM_MRU, \ USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \ SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \ USE_DISCORD, DISCORD_NOTIFY_ONSNATCH, DISCORD_NOTIFY_ONDOWNLOAD, \ DISCORD_NOTIFY_ONSUBTITLEDOWNLOAD, \ DISCORD_AS_AUTHED, DISCORD_USERNAME, DISCORD_ICON_URL, DISCORD_AS_TTS, DISCORD_ACCESS_TOKEN,\ USE_GITTER, GITTER_NOTIFY_ONSNATCH, GITTER_NOTIFY_ONDOWNLOAD, GITTER_NOTIFY_ONSUBTITLEDOWNLOAD,\ GITTER_ROOM, GITTER_ACCESS_TOKEN, \ USE_TELEGRAM, TELEGRAM_NOTIFY_ONSNATCH, TELEGRAM_NOTIFY_ONDOWNLOAD, TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD, \ TELEGRAM_SEND_IMAGE, TELEGRAM_QUIET, TELEGRAM_ACCESS_TOKEN, TELEGRAM_CHATID, \ USE_EMAIL, EMAIL_NOTIFY_ONSNATCH, EMAIL_NOTIFY_ONDOWNLOAD, EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD, EMAIL_FROM, \ EMAIL_HOST, EMAIL_PORT, EMAIL_TLS, EMAIL_USER, EMAIL_PASSWORD, EMAIL_LIST, EMAIL_OLD_SUBJECTS # Anime Settings global ANIME_TREAT_AS_HDTV, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST # db backup settings global BACKUP_DB_PATH, BACKUP_DB_ONEDAY, BACKUP_DB_MAX_COUNT, BACKUP_DB_DEFAULT_COUNT # pip update states global UPDATES_TODO for stanza in ('General', 'Blackhole', 'SABnzbd', 'NZBGet', 'Emby', 'Kodi', 'XBMC', 'PLEX', 'Growl', 'Prowl', 'Slack', 'Discord', 'Boxcar2', 'NMJ', 'NMJv2', 'Synology', 'SynologyNotifier', 'pyTivo', 'Pushalot', 'Pushbullet', 'Subtitles'): check_section(CFG, stanza) update_config = False WANTEDLIST_CACHE = common.WantedQualities() # wanted branch BRANCH = check_setting_str(CFG, 'General', 'branch', '') # git_remote GIT_REMOTE = check_setting_str(CFG, 'General', 'git_remote', 'origin') ACTUAL_CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', 'cache') # unless they specify, put the cache dir inside the data dir if not os.path.isabs(ACTUAL_CACHE_DIR): CACHE_DIR = os.path.join(DATA_DIR, ACTUAL_CACHE_DIR) else: CACHE_DIR = ACTUAL_CACHE_DIR if not helpers.make_dir(CACHE_DIR): logger.error('!!! creating local cache dir failed, using system default') CACHE_DIR = None # clean cache folders if CACHE_DIR: helpers.clear_cache() ZONEINFO_DIR = os.path.join(CACHE_DIR, 'zoneinfo') if not os.path.isdir(ZONEINFO_DIR) and not helpers.make_path(ZONEINFO_DIR): logger.error('!!! creating local zoneinfo dir failed') sg_helpers.CACHE_DIR = CACHE_DIR sg_helpers.DATA_DIR = DATA_DIR THEME_NAME = check_setting_str(CFG, 'GUI', 'theme_name', 'dark') GUI_NAME = check_setting_str(CFG, 'GUI', 'gui_name', 'slick') DEFAULT_HOME = check_setting_str(CFG, 'GUI', 'default_home', 'episodes') FANART_LIMIT = check_setting_int(CFG, 'GUI', 'fanart_limit', 3) FANART_PANEL = check_setting_str(CFG, 'GUI', 'fanart_panel', 'highlight2') FANART_RATINGS = sg_helpers.ast_eval(check_setting_str(CFG, 'GUI', 'fanart_ratings', None), {}) USE_IMDB_INFO = bool(check_setting_int(CFG, 'GUI', 'use_imdb_info', 1)) IMDB_ACCOUNTS = CFG.get('GUI', []).get('imdb_accounts', [IMDB_DEFAULT_LIST_ID, IMDB_DEFAULT_LIST_NAME]) HOME_SEARCH_FOCUS = bool(check_setting_int(CFG, 'General', 'home_search_focus', HOME_SEARCH_FOCUS)) DISPLAY_FREESPACE = bool(check_setting_int(CFG, 'General', 'display_freespace', 1)) SORT_ARTICLE = bool(check_setting_int(CFG, 'General', 'sort_article', 0)) FUZZY_DATING = bool(check_setting_int(CFG, 'GUI', 'fuzzy_dating', 0)) TRIM_ZERO = bool(check_setting_int(CFG, 'GUI', 'trim_zero', 0)) DATE_PRESET = check_setting_str(CFG, 'GUI', 'date_preset', '%x') TIME_PRESET_W_SECONDS = check_setting_str(CFG, 'GUI', 'time_preset', '%I:%M:%S %p') TIME_PRESET = TIME_PRESET_W_SECONDS.replace(':%S', '') TIMEZONE_DISPLAY = check_setting_str(CFG, 'GUI', 'timezone_display', 'network') SHOW_TAGS = check_setting_str(CFG, 'GUI', 'show_tags', 'Show List').split(',') SHOW_TAG_DEFAULT = check_setting_str(CFG, 'GUI', 'show_tag_default', check_setting_str(CFG, 'GUI', 'default_show_tag', 'Show List')) SHOWLIST_TAGVIEW = check_setting_str(CFG, 'GUI', 'showlist_tagview', 'standard') ACTUAL_LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', 'Logs') # put the log dir inside the data dir, unless an absolute path LOG_DIR = os.path.normpath(os.path.join(DATA_DIR, ACTUAL_LOG_DIR)) if not helpers.make_dir(LOG_DIR): logger.error('!!! no log folder, logging to screen only!') FILE_LOGGING_PRESET = check_setting_str(CFG, 'General', 'file_logging_preset', 'DEBUG') if bool(check_setting_int(CFG, 'General', 'file_logging_db', 0)): FILE_LOGGING_PRESET = 'DB' elif 'DB' == FILE_LOGGING_PRESET: FILE_LOGGING_PRESET = 'DEBUG' SOCKET_TIMEOUT = check_setting_int(CFG, 'General', 'socket_timeout', 30) socket.setdefaulttimeout(SOCKET_TIMEOUT) WEB_HOST = check_setting_str(CFG, 'General', 'web_host', '0.0.0.0') WEB_PORT = minimax(check_setting_int(CFG, 'General', 'web_port', 8081), 8081, 21, 65535) WEB_ROOT = check_setting_str(CFG, 'General', 'web_root', '').rstrip('/') WEB_IPV6 = bool(check_setting_int(CFG, 'General', 'web_ipv6', 0)) WEB_IPV64 = bool(check_setting_int(CFG, 'General', 'web_ipv64', 0)) WEB_LOG = bool(check_setting_int(CFG, 'General', 'web_log', 0)) ENCRYPTION_VERSION = check_setting_int(CFG, 'General', 'encryption_version', 0) WEB_USERNAME = check_setting_str(CFG, 'General', 'web_username', '') WEB_PASSWORD = check_setting_str(CFG, 'General', 'web_password', '') LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1)) CPU_PRESET = check_setting_str(CFG, 'General', 'cpu_preset', 'DISABLED') ANON_REDIRECT = check_setting_str(CFG, 'General', 'anon_redirect', '') PROXY_SETTING = check_setting_str(CFG, 'General', 'proxy_setting', '') sg_helpers.PROXY_SETTING = PROXY_SETTING sg_helpers.USER_AGENT = USER_AGENT from . import notifiers sg_helpers.NOTIFIERS = notifiers PROXY_INDEXERS = bool(check_setting_int(CFG, 'General', 'proxy_indexers', 1)) # attempt to help prevent users from breaking links by using a bad url if not ANON_REDIRECT.endswith('?'): ANON_REDIRECT = '' UPDATE_SHOWS_ON_START = bool(check_setting_int(CFG, 'General', 'update_shows_on_start', 0)) SHOW_UPDATE_HOUR = check_setting_int(CFG, 'General', 'show_update_hour', 3) SHOW_UPDATE_HOUR = minimax(SHOW_UPDATE_HOUR, 3, 0, 23) TRASH_REMOVE_SHOW = bool(check_setting_int(CFG, 'General', 'trash_remove_show', 0)) sg_helpers.TRASH_REMOVE_SHOW = TRASH_REMOVE_SHOW TRASH_ROTATE_LOGS = bool(check_setting_int(CFG, 'General', 'trash_rotate_logs', 0)) USE_API = bool(check_setting_int(CFG, 'General', 'use_api', 0)) API_KEYS = [k.split(':::') for k in check_setting_str(CFG, 'General', 'api_keys', '').split('|||') if k] if not API_KEYS: tmp_api_key = check_setting_str(CFG, 'General', 'api_key', None) if None is not tmp_api_key: API_KEYS = [['app name (old key)', tmp_api_key]] DEBUG = bool(check_setting_int(CFG, 'General', 'debug', 0)) ENABLE_HTTPS = bool(check_setting_int(CFG, 'General', 'enable_https', 0)) HTTPS_CERT = check_setting_str(CFG, 'General', 'https_cert', 'server.crt') HTTPS_KEY = check_setting_str(CFG, 'General', 'https_key', 'server.key') HANDLE_REVERSE_PROXY = bool(check_setting_int(CFG, 'General', 'handle_reverse_proxy', 0)) SEND_SECURITY_HEADERS = bool(check_setting_int(CFG, 'General', 'send_security_headers', 1)) ALLOWED_HOSTS = check_setting_str(CFG, 'General', 'allowed_hosts', '') ALLOW_ANYIP = bool(check_setting_int(CFG, 'General', 'allow_anyip', 1)) ROOT_DIRS = check_setting_str(CFG, 'General', 'root_dirs', '') if not re.match(r'\d+\|[^|]+(?:\|[^|]+)*', ROOT_DIRS): ROOT_DIRS = '' RESULTS_SORTBY = check_setting_str(CFG, 'General', 'results_sortby', '') QUALITY_DEFAULT = check_setting_int(CFG, 'General', 'quality_default', SD) STATUS_DEFAULT = check_setting_int(CFG, 'General', 'status_default', SKIPPED) WANTED_BEGIN_DEFAULT = check_setting_int(CFG, 'General', 'wanted_begin_default', 0) WANTED_LATEST_DEFAULT = check_setting_int(CFG, 'General', 'wanted_latest_default', 0) UPDATE_NOTIFY = bool(check_setting_int(CFG, 'General', 'update_notify', None)) if None is UPDATE_NOTIFY: UPDATE_NOTIFY = check_setting_int(CFG, 'General', 'version_notify', 1) # deprecated 2020.11.21 no config update UPDATE_AUTO = bool(check_setting_int(CFG, 'General', 'update_auto', None)) if None is UPDATE_AUTO: UPDATE_AUTO = check_setting_int(CFG, 'General', 'auto_update', 0) # deprecated 2020.11.21 no config update UPDATE_INTERVAL = max( MIN_UPDATE_INTERVAL, check_setting_int(CFG, 'General', 'update_interval', DEFAULT_UPDATE_INTERVAL)) NOTIFY_ON_UPDATE = bool(check_setting_int(CFG, 'General', 'notify_on_update', 1)) UPDATE_PACKAGES_NOTIFY = bool( check_setting_int(CFG, 'General', 'update_packages_notify', 'win' == sys.platform[0:3])) UPDATE_PACKAGES_AUTO = bool(check_setting_int(CFG, 'General', 'update_packages_auto', 0)) UPDATE_PACKAGES_MENU = bool(check_setting_int(CFG, 'General', 'update_packages_menu', 0)) UPDATE_PACKAGES_INTERVAL = max( MIN_UPDATE_PACKAGES_INTERVAL, check_setting_int(CFG, 'General', 'update_packages_interval', DEFAULT_UPDATE_PACKAGES_INTERVAL)) TVINFO_DEFAULT = check_setting_int(CFG, 'General', 'indexer_default', 0) if TVINFO_DEFAULT and not TVInfoAPI(TVINFO_DEFAULT).config['active']: TVINFO_DEFAULT = TVINFO_TVDB TVINFO_TIMEOUT = check_setting_int(CFG, 'General', 'indexer_timeout', 20) PAUSE_DEFAULT = bool(check_setting_int(CFG, 'General', 'pause default', 0)) SCENE_DEFAULT = bool(check_setting_int(CFG, 'General', 'scene_default', 0)) FLATTEN_FOLDERS_DEFAULT = bool(check_setting_int(CFG, 'General', 'flatten_folders_default', 0)) ANIME_DEFAULT = bool(check_setting_int(CFG, 'General', 'anime_default', 0)) PROVIDER_ORDER = check_setting_str(CFG, 'General', 'provider_order', '').split() PROVIDER_HOMES = sg_helpers.ast_eval(check_setting_str(CFG, 'General', 'provider_homes', None), {}) NAMING_PATTERN = check_setting_str(CFG, 'General', 'naming_pattern', 'Season %0S/%SN - S%0SE%0E - %EN') NAMING_ABD_PATTERN = check_setting_str(CFG, 'General', 'naming_abd_pattern', '%SN - %A.D - %EN') NAMING_CUSTOM_ABD = bool(check_setting_int(CFG, 'General', 'naming_custom_abd', 0)) NAMING_SPORTS_PATTERN = check_setting_str(CFG, 'General', 'naming_sports_pattern', '%SN - %A-D - %EN') NAMING_ANIME_PATTERN = check_setting_str(CFG, 'General', 'naming_anime_pattern', 'Season %0S/%SN - S%0SE%0E - %EN') NAMING_ANIME = check_setting_int(CFG, 'General', 'naming_anime', 3) NAMING_CUSTOM_SPORTS = bool(check_setting_int(CFG, 'General', 'naming_custom_sports', 0)) NAMING_CUSTOM_ANIME = bool(check_setting_int(CFG, 'General', 'naming_custom_anime', 0)) NAMING_MULTI_EP = check_setting_int(CFG, 'General', 'naming_multi_ep', 1) NAMING_ANIME_MULTI_EP = check_setting_int(CFG, 'General', 'naming_anime_multi_ep', 1) NAMING_FORCE_FOLDERS = naming.check_force_season_folders() NAMING_STRIP_YEAR = bool(check_setting_int(CFG, 'General', 'naming_strip_year', 0)) USE_NZBS = bool(check_setting_int(CFG, 'General', 'use_nzbs', 0)) USE_TORRENTS = bool(check_setting_int(CFG, 'General', 'use_torrents', 0)) NZB_METHOD = check_setting_str(CFG, 'General', 'nzb_method', 'blackhole') if NZB_METHOD not in ('blackhole', 'sabnzbd', 'nzbget'): NZB_METHOD = 'blackhole' TORRENT_METHOD = check_setting_str(CFG, 'General', 'torrent_method', 'blackhole') if TORRENT_METHOD not in ('blackhole', 'deluge', 'download_station', 'qbittorrent', 'rtorrent', 'transmission', 'utorrent'): TORRENT_METHOD = 'blackhole' DOWNLOAD_PROPERS = bool(check_setting_int(CFG, 'General', 'download_propers', 1)) PROPERS_WEBDL_ONEGRP = bool(check_setting_int(CFG, 'General', 'propers_webdl_onegrp', 1)) ALLOW_HIGH_PRIORITY = bool(check_setting_int(CFG, 'General', 'allow_high_priority', 1)) RECENTSEARCH_STARTUP = bool(check_setting_int(CFG, 'General', 'recentsearch_startup', 0)) BACKLOG_NOFULL = bool(check_setting_int(CFG, 'General', 'backlog_nofull', 0)) SKIP_REMOVED_FILES = check_setting_int(CFG, 'General', 'skip_removed_files', 0) USENET_RETENTION = check_setting_int(CFG, 'General', 'usenet_retention', 500) MEDIAPROCESS_INTERVAL = check_setting_int(CFG, 'General', 'mediaprocess_interval', DEFAULT_MEDIAPROCESS_INTERVAL) if MEDIAPROCESS_INTERVAL < MIN_MEDIAPROCESS_INTERVAL: MEDIAPROCESS_INTERVAL = MIN_MEDIAPROCESS_INTERVAL RECENTSEARCH_INTERVAL = check_setting_int(CFG, 'General', 'recentsearch_interval', DEFAULT_RECENTSEARCH_INTERVAL) if RECENTSEARCH_INTERVAL < MIN_RECENTSEARCH_INTERVAL: RECENTSEARCH_INTERVAL = MIN_RECENTSEARCH_INTERVAL # special case during dev to migrate backlog_interval to backlog_period BACKLOG_PERIOD = check_setting_int(CFG, 'General', 'backlog_period', check_setting_int(CFG, 'General', 'backlog_interval', DEFAULT_BACKLOG_PERIOD)) BACKLOG_PERIOD = minimax(BACKLOG_PERIOD, DEFAULT_BACKLOG_PERIOD, MIN_BACKLOG_PERIOD, MAX_BACKLOG_PERIOD) BACKLOG_LIMITED_PERIOD = check_setting_int(CFG, 'General', 'backlog_limited_period', 7) SEARCH_UNAIRED = bool(check_setting_int(CFG, 'General', 'search_unaired', 0)) UNAIRED_RECENT_SEARCH_ONLY = bool(check_setting_int(CFG, 'General', 'unaired_recent_search_only', 1)) FLARESOLVERR_HOST = check_setting_str(CFG, 'General', 'flaresolverr_host', '') sg_helpers.FLARESOLVERR_HOST = FLARESOLVERR_HOST NZB_DIR = check_setting_str(CFG, 'Blackhole', 'nzb_dir', '') TORRENT_DIR = check_setting_str(CFG, 'Blackhole', 'torrent_dir', '') TV_DOWNLOAD_DIR = check_setting_str(CFG, 'General', 'tv_download_dir', '') PROCESS_AUTOMATICALLY = bool(check_setting_int(CFG, 'General', 'process_automatically', 0)) UNPACK = bool(check_setting_int(CFG, 'General', 'unpack', 0)) RENAME_EPISODES = bool(check_setting_int(CFG, 'General', 'rename_episodes', 1)) RENAME_TBA_EPISODES = bool(check_setting_int(CFG, 'General', 'rename_tba_episodes', 1)) RENAME_NAME_CHANGED_EPISODES = bool(check_setting_int(CFG, 'General', 'rename_name_changed_episodes', 0)) AIRDATE_EPISODES = bool(check_setting_int(CFG, 'General', 'airdate_episodes', 0)) KEEP_PROCESSED_DIR = bool(check_setting_int(CFG, 'General', 'keep_processed_dir', 1)) PROCESS_METHOD = check_setting_str(CFG, 'General', 'process_method', 'copy' if KEEP_PROCESSED_DIR else 'move') PROCESS_LAST_DIR = check_setting_str(CFG, 'General', 'process_last_dir', TV_DOWNLOAD_DIR) PROCESS_LAST_METHOD = check_setting_str(CFG, 'General', 'process_last_method', PROCESS_METHOD) PROCESS_LAST_CLEANUP = bool(check_setting_int(CFG, 'General', 'process_last_cleanup', 0)) MOVE_ASSOCIATED_FILES = bool(check_setting_int(CFG, 'General', 'move_associated_files', 0)) POSTPONE_IF_SYNC_FILES = bool(check_setting_int(CFG, 'General', 'postpone_if_sync_files', 1)) PROCESS_POSITIVE_LOG = bool(check_setting_int(CFG, 'General', 'process_positive_log', 0)) NFO_RENAME = bool(check_setting_int(CFG, 'General', 'nfo_rename', 1)) CREATE_MISSING_SHOW_DIRS = bool(check_setting_int(CFG, 'General', 'create_missing_show_dirs', 0)) SHOW_DIRS_WITH_DOTS = bool(check_setting_int(CFG, 'General', 'show_dirs_with_dots', 0)) ADD_SHOWS_WO_DIR = bool(check_setting_int(CFG, 'General', 'add_shows_wo_dir', 0)) ADD_SHOWS_METALANG = check_setting_str(CFG, 'General', 'add_shows_metalang', 'en') REMOVE_FILENAME_CHARS = check_setting_str(CFG, 'General', 'remove_filename_chars', '') sg_helpers.REMOVE_FILENAME_CHARS = REMOVE_FILENAME_CHARS IMPORT_DEFAULT_CHECKED_SHOWS = bool(check_setting_int(CFG, 'General', 'import_default_checked_shows', 0)) SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '') SAB_PASSWORD = check_setting_str(CFG, 'SABnzbd', 'sab_password', '') SAB_APIKEY = check_setting_str(CFG, 'SABnzbd', 'sab_apikey', '') SAB_CATEGORY = check_setting_str(CFG, 'SABnzbd', 'sab_category', 'tv') SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '') # first check using official name case, then with case of legacy # todo: migrate config, (just not atm due to testing map feature) NZBGET_USERNAME = (check_setting_str(CFG, 'NZBGet', 'nzbget_username', '') or check_setting_str(CFG, 'NZBget', 'nzbget_username', 'nzbget')) NZBGET_PASSWORD = (check_setting_str(CFG, 'NZBGet', 'nzbget_password', '') or check_setting_str(CFG, 'NZBget', 'nzbget_password', 'tegbzn6789')) NZBGET_CATEGORY = (check_setting_str(CFG, 'NZBGet', 'nzbget_category', '') or check_setting_str(CFG, 'NZBget', 'nzbget_category', 'tv')) NZBGET_HOST = (check_setting_str(CFG, 'NZBGet', 'nzbget_host', '') or check_setting_str(CFG, 'NZBget', 'nzbget_host', '')) NZBGET_USE_HTTPS = (bool(check_setting_int(CFG, 'NZBGet', 'nzbget_use_https', 0)) or bool(check_setting_int(CFG, 'NZBget', 'nzbget_use_https', 0))) NZBGET_PRIORITY = check_setting_int(CFG, 'NZBGet', 'nzbget_priority', None) if None is NZBGET_PRIORITY: NZBGET_PRIORITY = check_setting_int(CFG, 'NZBget', 'nzbget_priority', 100) NZBGET_MAP = check_setting_str(CFG, 'NZBGet', 'nzbget_map', '') NZBGET_SKIP_PM = bool(check_setting_int(CFG, 'NZBGet', 'nzbget_skip_process_media', 0)) try: ng_script_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'autoProcessTV', 'SickGear-NG', 'SickGear-NG.py') with io.open(ng_script_file, 'r', encoding='utf8') as ng: text = ng.read() NZBGET_SCRIPT_VERSION = re.search(r""".*version: (\d+\.\d+)""", text, flags=re.M).group(1) except (BaseException, Exception): NZBGET_SCRIPT_VERSION = None TORRENT_USERNAME = check_setting_str(CFG, 'TORRENT', 'torrent_username', '') TORRENT_PASSWORD = check_setting_str(CFG, 'TORRENT', 'torrent_password', '') TORRENT_HOST = check_setting_str(CFG, 'TORRENT', 'torrent_host', '') TORRENT_PATH = check_setting_str(CFG, 'TORRENT', 'torrent_path', '') TORRENT_SEED_TIME = check_setting_int(CFG, 'TORRENT', 'torrent_seed_time', 0) TORRENT_PAUSED = bool(check_setting_int(CFG, 'TORRENT', 'torrent_paused', 0)) TORRENT_HIGH_BANDWIDTH = bool(check_setting_int(CFG, 'TORRENT', 'torrent_high_bandwidth', 0)) TORRENT_LABEL = check_setting_str(CFG, 'TORRENT', 'torrent_label', '') TORRENT_LABEL_VAR = check_setting_int(CFG, 'TORRENT', 'torrent_label_var', 1) TORRENT_VERIFY_CERT = bool(check_setting_int(CFG, 'TORRENT', 'torrent_verify_cert', 0)) USE_EMBY = bool(check_setting_int(CFG, 'Emby', 'use_emby', 0)) EMBY_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Emby', 'emby_update_library', 0)) EMBY_PARENT_MAPS = check_setting_str(CFG, 'Emby', 'emby_parent_maps', '') EMBY_HOST = check_setting_str(CFG, 'Emby', 'emby_host', '') EMBY_APIKEY = check_setting_str(CFG, 'Emby', 'emby_apikey', '') EMBY_WATCHEDSTATE_SCHEDULED = bool(check_setting_int(CFG, 'Emby', 'emby_watchedstate_scheduled', 0)) EMBY_WATCHEDSTATE_INTERVAL = minimax(check_setting_int( CFG, 'Emby', 'emby_watchedstate_interval', DEFAULT_WATCHEDSTATE_INTERVAL), DEFAULT_WATCHEDSTATE_INTERVAL, MIN_WATCHEDSTATE_INTERVAL, MAX_WATCHEDSTATE_INTERVAL) USE_KODI = bool(check_setting_int(CFG, 'Kodi', 'use_kodi', 0)) KODI_ALWAYS_ON = bool(check_setting_int(CFG, 'Kodi', 'kodi_always_on', 1)) KODI_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Kodi', 'kodi_notify_onsnatch', 0)) KODI_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Kodi', 'kodi_notify_ondownload', 0)) KODI_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Kodi', 'kodi_notify_onsubtitledownload', 0)) KODI_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Kodi', 'kodi_update_library', 0)) KODI_UPDATE_FULL = bool(check_setting_int(CFG, 'Kodi', 'kodi_update_full', 0)) KODI_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'Kodi', 'kodi_update_onlyfirst', 0)) KODI_PARENT_MAPS = check_setting_str(CFG, 'Kodi', 'kodi_parent_maps', '') KODI_HOST = check_setting_str(CFG, 'Kodi', 'kodi_host', '') KODI_USERNAME = check_setting_str(CFG, 'Kodi', 'kodi_username', '') KODI_PASSWORD = check_setting_str(CFG, 'Kodi', 'kodi_password', '') USE_XBMC = bool(check_setting_int(CFG, 'XBMC', 'use_xbmc', 0)) XBMC_ALWAYS_ON = bool(check_setting_int(CFG, 'XBMC', 'xbmc_always_on', 1)) XBMC_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsnatch', 0)) XBMC_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_ondownload', 0)) XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsubtitledownload', 0)) XBMC_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_library', 0)) XBMC_UPDATE_FULL = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_full', 0)) XBMC_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_onlyfirst', 0)) XBMC_HOST = check_setting_str(CFG, 'XBMC', 'xbmc_host', '') XBMC_USERNAME = check_setting_str(CFG, 'XBMC', 'xbmc_username', '') XBMC_PASSWORD = check_setting_str(CFG, 'XBMC', 'xbmc_password', '') USE_PLEX = bool(check_setting_int(CFG, 'Plex', 'use_plex', 0)) PLEX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Plex', 'plex_notify_onsnatch', 0)) PLEX_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Plex', 'plex_notify_ondownload', 0)) PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Plex', 'plex_notify_onsubtitledownload', 0)) PLEX_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Plex', 'plex_update_library', 0)) PLEX_PARENT_MAPS = check_setting_str(CFG, 'Plex', 'plex_parent_maps', '') PLEX_SERVER_HOST = check_setting_str(CFG, 'Plex', 'plex_server_host', '') PLEX_HOST = check_setting_str(CFG, 'Plex', 'plex_host', '') PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '') PLEX_PASSWORD = check_setting_str(CFG, 'Plex', 'plex_password', '') PLEX_WATCHEDSTATE_SCHEDULED = bool(check_setting_int(CFG, 'Plex', 'plex_watchedstate_scheduled', 0)) PLEX_WATCHEDSTATE_INTERVAL = minimax(check_setting_int( CFG, 'Plex', 'plex_watchedstate_interval', DEFAULT_WATCHEDSTATE_INTERVAL), DEFAULT_WATCHEDSTATE_INTERVAL, MIN_WATCHEDSTATE_INTERVAL, MAX_WATCHEDSTATE_INTERVAL) USE_GROWL = bool(check_setting_int(CFG, 'Growl', 'use_growl', 0)) GROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Growl', 'growl_notify_onsnatch', 0)) GROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Growl', 'growl_notify_ondownload', 0)) GROWL_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Growl', 'growl_notify_onsubtitledownload', 0)) GROWL_HOST = check_setting_str(CFG, 'Growl', 'growl_host', '') # GROWL_PASSWORD = check_setting_str(CFG, 'Growl', 'growl_password', '') USE_PROWL = bool(check_setting_int(CFG, 'Prowl', 'use_prowl', 0)) PROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_onsnatch', 0)) PROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_ondownload', 0)) PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_onsubtitledownload', 0)) PROWL_API = check_setting_str(CFG, 'Prowl', 'prowl_api', '') PROWL_PRIORITY = check_setting_str(CFG, 'Prowl', 'prowl_priority', '0') USE_BOXCAR2 = bool(check_setting_int(CFG, 'Boxcar2', 'use_boxcar2', 0)) BOXCAR2_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar2', 'boxcar2_notify_onsnatch', 0)) BOXCAR2_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar2', 'boxcar2_notify_ondownload', 0)) BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'Boxcar2', 'boxcar2_notify_onsubtitledownload', 0)) BOXCAR2_ACCESSTOKEN = check_setting_str(CFG, 'Boxcar2', 'boxcar2_accesstoken', '') BOXCAR2_SOUND = check_setting_str(CFG, 'Boxcar2', 'boxcar2_sound', 'default') USE_PUSHOVER = bool(check_setting_int(CFG, 'Pushover', 'use_pushover', 0)) PUSHOVER_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Pushover', 'pushover_notify_onsnatch', 0)) PUSHOVER_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushover', 'pushover_notify_ondownload', 0)) PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'Pushover', 'pushover_notify_onsubtitledownload', 0)) PUSHOVER_USERKEY = check_setting_str(CFG, 'Pushover', 'pushover_userkey', '') PUSHOVER_APIKEY = check_setting_str(CFG, 'Pushover', 'pushover_apikey', '') PUSHOVER_PRIORITY = check_setting_str(CFG, 'Pushover', 'pushover_priority', '0') PUSHOVER_DEVICE = check_setting_str(CFG, 'Pushover', 'pushover_device', 'all') PUSHOVER_SOUND = check_setting_str(CFG, 'Pushover', 'pushover_sound', 'pushover') USE_LIBNOTIFY = bool(check_setting_int(CFG, 'Libnotify', 'use_libnotify', 0)) LIBNOTIFY_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_onsnatch', 0)) LIBNOTIFY_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_ondownload', 0)) LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'Libnotify', 'libnotify_notify_onsubtitledownload', 0)) USE_NMJ = bool(check_setting_int(CFG, 'NMJ', 'use_nmj', 0)) NMJ_HOST = check_setting_str(CFG, 'NMJ', 'nmj_host', '') NMJ_DATABASE = check_setting_str(CFG, 'NMJ', 'nmj_database', '') NMJ_MOUNT = check_setting_str(CFG, 'NMJ', 'nmj_mount', '') USE_NMJv2 = bool(check_setting_int(CFG, 'NMJv2', 'use_nmjv2', 0)) NMJv2_HOST = check_setting_str(CFG, 'NMJv2', 'nmjv2_host', '') NMJv2_DATABASE = check_setting_str(CFG, 'NMJv2', 'nmjv2_database', '') NMJv2_DBLOC = check_setting_str(CFG, 'NMJv2', 'nmjv2_dbloc', '') USE_SYNOINDEX = bool(check_setting_int(CFG, 'Synology', 'use_synoindex', 0)) USE_SYNOLOGYNOTIFIER = bool(check_setting_int(CFG, 'SynologyNotifier', 'use_synologynotifier', 0)) SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH = bool( check_setting_int(CFG, 'SynologyNotifier', 'synologynotifier_notify_onsnatch', 0)) SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = bool( check_setting_int(CFG, 'SynologyNotifier', 'synologynotifier_notify_ondownload', 0)) SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'SynologyNotifier', 'synologynotifier_notify_onsubtitledownload', 0)) USE_TRAKT = bool(check_setting_int(CFG, 'Trakt', 'use_trakt', 0)) TRAKT_REMOVE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_remove_watchlist', 0)) TRAKT_REMOVE_SERIESLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_remove_serieslist', 0)) TRAKT_USE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_use_watchlist', 0)) TRAKT_METHOD_ADD = check_setting_int(CFG, 'Trakt', 'trakt_method_add', 0) TRAKT_START_PAUSED = bool(check_setting_int(CFG, 'Trakt', 'trakt_start_paused', 0)) TRAKT_SYNC = bool(check_setting_int(CFG, 'Trakt', 'trakt_sync', 0)) TRAKT_DEFAULT_INDEXER = check_setting_int(CFG, 'Trakt', 'trakt_default_indexer', 1) TRAKT_UPDATE_COLLECTION = trakt_helpers.read_config_string( check_setting_str(CFG, 'Trakt', 'trakt_update_collection', '')) TRAKT_ACCOUNTS = TraktAPI.read_config_string(check_setting_str(CFG, 'Trakt', 'trakt_accounts', '')) TRAKT_MRU = check_setting_str(CFG, 'Trakt', 'trakt_mru', '') MC_MRU = check_setting_str(CFG, 'Metacritic', 'mc_mru', '') NE_MRU = check_setting_str(CFG, 'NextEpisode', 'ne_mru', '') TMDB_MRU = check_setting_str(CFG, 'TMDB', 'tmdb_mru', '') TVC_MRU = check_setting_str(CFG, 'TVCalendar', 'tvc_mru', '') TVM_MRU = check_setting_str(CFG, 'TVmaze', 'tvm_mru', '') USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0)) PYTIVO_HOST = check_setting_str(CFG, 'pyTivo', 'pytivo_host', '') PYTIVO_SHARE_NAME = check_setting_str(CFG, 'pyTivo', 'pytivo_share_name', '') PYTIVO_TIVO_NAME = check_setting_str(CFG, 'pyTivo', 'pytivo_tivo_name', '') USE_PUSHALOT = bool(check_setting_int(CFG, 'Pushalot', 'use_pushalot', 0)) PUSHALOT_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Pushalot', 'pushalot_notify_onsnatch', 0)) PUSHALOT_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushalot', 'pushalot_notify_ondownload', 0)) PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'Pushalot', 'pushalot_notify_onsubtitledownload', 0)) PUSHALOT_AUTHORIZATIONTOKEN = check_setting_str(CFG, 'Pushalot', 'pushalot_authorizationtoken', '') USE_PUSHBULLET = bool(check_setting_int(CFG, 'Pushbullet', 'use_pushbullet', 0)) PUSHBULLET_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Pushbullet', 'pushbullet_notify_onsnatch', 0)) PUSHBULLET_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushbullet', 'pushbullet_notify_ondownload', 0)) PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'Pushbullet', 'pushbullet_notify_onsubtitledownload', 0)) PUSHBULLET_ACCESS_TOKEN = check_setting_str(CFG, 'Pushbullet', 'pushbullet_access_token', '') PUSHBULLET_DEVICE_IDEN = check_setting_str(CFG, 'Pushbullet', 'pushbullet_device_iden', '') USE_SLACK = bool(check_setting_int(CFG, 'Slack', 'use_slack', 0)) SLACK_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Slack', 'slack_notify_onsnatch', 0)) SLACK_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Slack', 'slack_notify_ondownload', 0)) SLACK_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Slack', 'slack_notify_onsubtitledownload', 0)) SLACK_CHANNEL = check_setting_str(CFG, 'Slack', 'slack_channel', '') SLACK_AS_AUTHED = bool(check_setting_int(CFG, 'Slack', 'slack_as_authed', 0)) SLACK_BOT_NAME = check_setting_str(CFG, 'Slack', 'slack_bot_name', '') SLACK_ICON_URL = check_setting_str(CFG, 'Slack', 'slack_icon_url', '') SLACK_ACCESS_TOKEN = check_setting_str(CFG, 'Slack', 'slack_access_token', '') USE_DISCORD = bool(check_setting_int(CFG, 'Discord', 'use_discord', 0)) DISCORD_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Discord', 'discord_notify_onsnatch', 0)) DISCORD_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Discord', 'discord_notify_ondownload', 0)) DISCORD_NOTIFY_ONSUBTITLEDOWNLOAD = bool( check_setting_int(CFG, 'Discord', 'discord_notify_onsubtitledownload', 0)) DISCORD_AS_AUTHED = bool(check_setting_int(CFG, 'Discord', 'discord_as_authed', 0)) DISCORD_USERNAME = check_setting_str(CFG, 'Discord', 'discord_username', '') DISCORD_ICON_URL = check_setting_str(CFG, 'Discord', 'discord_icon_url', '') DISCORD_AS_TTS = bool(check_setting_str(CFG, 'Discord', 'discord_as_tts', 0)) DISCORD_ACCESS_TOKEN = check_setting_str(CFG, 'Discord', 'discord_access_token', '') USE_GITTER = bool(check_setting_int(CFG, 'Gitter', 'use_gitter', 0)) GITTER_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Gitter', 'gitter_notify_onsnatch', 0)) GITTER_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Gitter', 'gitter_notify_ondownload', 0)) GITTER_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Gitter', 'gitter_notify_onsubtitledownload', 0)) GITTER_ROOM = check_setting_str(CFG, 'Gitter', 'gitter_room', '') GITTER_ACCESS_TOKEN = check_setting_str(CFG, 'Gitter', 'gitter_access_token', '') USE_TELEGRAM = bool(check_setting_int(CFG, 'Telegram', 'use_telegram', 0)) TELEGRAM_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Telegram', 'telegram_notify_onsnatch', 0)) TELEGRAM_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Telegram', 'telegram_notify_ondownload', 0)) TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int( CFG, 'Telegram', 'telegram_notify_onsubtitledownload', 0)) TELEGRAM_SEND_IMAGE = bool(check_setting_int(CFG, 'Telegram', 'telegram_send_image', 1)) TELEGRAM_QUIET = bool(check_setting_int(CFG, 'Telegram', 'telegram_quiet', 0)) TELEGRAM_ACCESS_TOKEN = check_setting_str(CFG, 'Telegram', 'telegram_access_token', '') TELEGRAM_CHATID = check_setting_str(CFG, 'Telegram', 'telegram_chatid', '') USE_EMAIL = bool(check_setting_int(CFG, 'Email', 'use_email', 0)) EMAIL_OLD_SUBJECTS = bool(check_setting_int(CFG, 'Email', 'email_old_subjects', None is not EMAIL_HOST and any(EMAIL_HOST))) EMAIL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Email', 'email_notify_onsnatch', 0)) EMAIL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Email', 'email_notify_ondownload', 0)) EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Email', 'email_notify_onsubtitledownload', 0)) EMAIL_HOST = check_setting_str(CFG, 'Email', 'email_host', '') EMAIL_PORT = check_setting_int(CFG, 'Email', 'email_port', 25) EMAIL_TLS = bool(check_setting_int(CFG, 'Email', 'email_tls', 0)) EMAIL_USER = check_setting_str(CFG, 'Email', 'email_user', '') EMAIL_PASSWORD = check_setting_str(CFG, 'Email', 'email_password', '') EMAIL_FROM = check_setting_str(CFG, 'Email', 'email_from', '') EMAIL_LIST = check_setting_str(CFG, 'Email', 'email_list', '') USE_SUBTITLES = bool(check_setting_int(CFG, 'Subtitles', 'use_subtitles', 0)) SUBTITLES_LANGUAGES = check_setting_str(CFG, 'Subtitles', 'subtitles_languages', '').split(',') if SUBTITLES_LANGUAGES[0] == '': SUBTITLES_LANGUAGES = [] SUBTITLES_DIR = check_setting_str(CFG, 'Subtitles', 'subtitles_dir', '') SUBTITLES_SERVICES_LIST = check_setting_str(CFG, 'Subtitles', 'SUBTITLES_SERVICES_LIST', '').split(',') SUBTITLES_SERVICES_ENABLED = [int(x) for x in check_setting_str(CFG, 'Subtitles', 'SUBTITLES_SERVICES_ENABLED', '').split('|') if x] SUBTITLES_SERVICES_AUTH = [k.split(':::') for k in check_setting_str(CFG, 'Subtitles', 'subtitles_services_auth', ':::').split('|||') if k] try: # unlikely to happen, but did happen once, so added here as defensive dct_cfg = sg_helpers.ast_eval(check_setting_str(CFG, 'Subtitles', 'subtitles_services_auth', ':::'), {}) SUBTITLES_SERVICES_AUTH = [[dct_cfg['os_user'], dct_cfg['os_pass']]] except (BaseException, Exception): pass SUBTITLES_DEFAULT = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_default', 0)) SUBTITLES_HISTORY = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_history', 0)) SUBTITLES_FINDER_INTERVAL = check_setting_int(CFG, 'Subtitles', 'subtitles_finder_interval', 1) SUBTITLES_OS_HASH = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_os_hash', 1)) USE_FAILED_DOWNLOADS = bool(check_setting_int(CFG, 'FailedDownloads', 'use_failed_downloads', 0)) DELETE_FAILED = bool(check_setting_int(CFG, 'FailedDownloads', 'delete_failed', 0)) GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '') IGNORE_WORDS, IGNORE_WORDS_REGEX = helpers.split_word_str( check_setting_str(CFG, 'General', 'ignore_words', helpers.generate_word_str(IGNORE_WORDS, IGNORE_WORDS_REGEX))) REQUIRE_WORDS, REQUIRE_WORDS_REGEX = helpers.split_word_str( check_setting_str(CFG, 'General', 'require_words', helpers.generate_word_str(REQUIRE_WORDS, REQUIRE_WORDS_REGEX))) CALENDAR_UNPROTECTED = bool(check_setting_int(CFG, 'General', 'calendar_unprotected', 0)) EXTRA_SCRIPTS = [x.strip() for x in check_setting_str(CFG, 'General', 'extra_scripts', '').split('|') if x.strip()] SG_EXTRA_SCRIPTS = [x.strip() for x in check_setting_str(CFG, 'General', 'sg_extra_scripts', '').split('|') if x.strip()] USE_ANIDB = bool(check_setting_int(CFG, 'ANIDB', 'use_anidb', 0)) ANIDB_USERNAME = check_setting_str(CFG, 'ANIDB', 'anidb_username', '') ANIDB_PASSWORD = check_setting_str(CFG, 'ANIDB', 'anidb_password', '') ANIDB_USE_MYLIST = bool(check_setting_int(CFG, 'ANIDB', 'anidb_use_mylist', 0)) ANIME_TREAT_AS_HDTV = bool(check_setting_int(CFG, 'ANIME', 'anime_treat_as_hdtv', 0)) METADATA_XBMC = check_setting_str(CFG, 'General', 'metadata_xbmc', '0|0|0|0|0|0|0|0|0|0') METADATA_XBMC_12PLUS = check_setting_str(CFG, 'General', 'metadata_xbmc_12plus', '0|0|0|0|0|0|0|0|0|0') METADATA_MEDIABROWSER = check_setting_str(CFG, 'General', 'metadata_mediabrowser', '0|0|0|0|0|0|0|0|0|0') METADATA_PS3 = check_setting_str(CFG, 'General', 'metadata_ps3', '0|0|0|0|0|0|0|0|0|0') METADATA_WDTV = check_setting_str(CFG, 'General', 'metadata_wdtv', '0|0|0|0|0|0|0|0|0|0') METADATA_TIVO = check_setting_str(CFG, 'General', 'metadata_tivo', '0|0|0|0|0|0|0|0|0|0') METADATA_MEDE8ER = check_setting_str(CFG, 'General', 'metadata_mede8er', '0|0|0|0|0|0|0|0|0|0') METADATA_KODI = check_setting_str(CFG, 'General', 'metadata_kodi', '0|0|0|0|0|0|0|0|0|0') HOME_LAYOUT = check_setting_str(CFG, 'GUI', 'home_layout', 'poster') FOOTER_TIME_LAYOUT = check_setting_int(CFG, 'GUI', 'footer_time_layout', 0) POSTER_SORTBY = check_setting_str(CFG, 'GUI', 'poster_sortby', 'name') POSTER_SORTDIR = check_setting_int(CFG, 'GUI', 'poster_sortdir', 1) DISPLAY_SHOW_GLIDE = sg_helpers.ast_eval(check_setting_str(CFG, 'GUI', 'display_show_glide', None), {}) DISPLAY_SHOW_GLIDE_SLIDETIME = check_setting_int(CFG, 'GUI', 'display_show_glide_slidetime', 3000) DISPLAY_SHOW_VIEWMODE = check_setting_int(CFG, 'GUI', 'display_show_viewmode', 2) DISPLAY_SHOW_BACKGROUND = bool(check_setting_int(CFG, 'GUI', 'display_show_background', 1)) DISPLAY_SHOW_BACKGROUND_TRANSLUCENT = bool(check_setting_int( CFG, 'GUI', 'display_show_background_translucent', 1)) DISPLAY_SHOW_VIEWART = check_setting_int(CFG, 'GUI', 'display_show_viewart', 0) DISPLAY_SHOW_MINIMUM = bool(check_setting_int(CFG, 'GUI', 'display_show_minimum', 1)) DISPLAY_SHOW_SPECIALS = bool(check_setting_int(CFG, 'GUI', 'display_show_specials', 0)) EPISODE_VIEW_VIEWMODE = check_setting_int(CFG, 'GUI', 'episode_view_viewmode', 2) EPISODE_VIEW_BACKGROUND = bool(check_setting_int(CFG, 'GUI', 'episode_view_background', 1)) EPISODE_VIEW_BACKGROUND_TRANSLUCENT = bool(check_setting_int( CFG, 'GUI', 'episode_view_background_translucent', 1)) EPISODE_VIEW_LAYOUT = check_setting_str(CFG, 'GUI', 'episode_view_layout', 'daybyday') EPISODE_VIEW_SORT = check_setting_str(CFG, 'GUI', 'episode_view_sort', 'time') EPISODE_VIEW_DISPLAY_PAUSED = check_setting_int(CFG, 'GUI', 'episode_view_display_paused', 1) EPISODE_VIEW_POSTERS = bool(check_setting_int(CFG, 'GUI', 'episode_view_posters', 1)) EPISODE_VIEW_MISSED_RANGE = check_setting_int(CFG, 'GUI', 'episode_view_missed_range', 7) HISTORY_LAYOUT = check_setting_str(CFG, 'GUI', 'history_layout', 'detailed') BROWSELIST_HIDDEN = list(map( lambda y: TVidProdid.glue in y and y or '%s%s%s' % ( (TVINFO_TVDB, TVINFO_IMDB)[bool(helpers.parse_imdb_id(y))], TVidProdid.glue, y), [x.strip() for x in check_setting_str(CFG, 'GUI', 'browselist_hidden', '').split('|~|') if x.strip()])) BROWSELIST_MRU = sg_helpers.ast_eval(check_setting_str(CFG, 'GUI', 'browselist_prefs', None), {}) BACKUP_DB_PATH = check_setting_str(CFG, 'Backup', 'backup_db_path', '') BACKUP_DB_ONEDAY = bool(check_setting_int(CFG, 'Backup', 'backup_db_oneday', 0)) BACKUP_DB_MAX_COUNT = minimax(check_setting_int(CFG, 'Backup', 'backup_db_max_count', BACKUP_DB_DEFAULT_COUNT), BACKUP_DB_DEFAULT_COUNT, 0, 90) UPDATES_TODO = sg_helpers.ast_eval(check_setting_str(CFG, 'Updates', 'updates_todo', None), {}) sg_helpers.db = db sg_helpers.DOMAIN_FAILURES.load_from_db() # initialize NZB and TORRENT providers provider_list = providers.provider_modules() NEWZNAB_DATA = check_setting_str(CFG, 'Newznab', 'newznab_data', '') newznab_providers = providers.newznab_source_list(NEWZNAB_DATA) torrentrss_data = check_setting_str(CFG, 'TorrentRss', 'torrentrss_data', '') torrent_rss_providers = providers.torrent_rss_source_list(torrentrss_data) # dynamically load provider settings for torrent_prov in [curProvider for curProvider in providers.sorted_sources() if GenericProvider.TORRENT == curProvider.providerType]: prov_id = torrent_prov.get_id() prov_id_uc = torrent_prov.get_id().upper() torrent_prov.enabled = bool(check_setting_int(CFG, prov_id_uc, prov_id, False)) # check str with a def of list, don't add to block settings if getattr(torrent_prov, 'url_edit', None): torrent_prov.url_home = check_setting_str(CFG, prov_id_uc, prov_id + '_url_home', []) # check int with a default of str, don't add to block settings attr = 'seed_time' if hasattr(torrent_prov, attr): torrent_prov.seed_time = check_setting_int(CFG, prov_id_uc, '%s_%s' % (prov_id, attr), '') # custom cond, don't add to block settings attr = 'enable_recentsearch' if hasattr(torrent_prov, attr): torrent_prov.enable_recentsearch = bool(check_setting_int( CFG, prov_id_uc, '%s_%s' % (prov_id, attr), True)) or not getattr(torrent_prov, 'supports_backlog') # check str with a default of list, don't add to block settings if hasattr(torrent_prov, 'filter'): torrent_prov.filter = check_setting_str(CFG, prov_id_uc, prov_id + '_filter', []) for (attr, default) in [ ('enable_backlog', True), ('enable_scheduled_backlog', True), ('api_key', ''), ('hash', ''), ('digest', ''), ('username', ''), ('uid', ''), ('password', ''), ('passkey', ''), ('options', ''), ('_seed_ratio', ''), ('minseed', 0), ('minleech', 0), ('scene_only', False), ('scene_or_contain', ''), ('scene_loose', False), ('scene_loose_active', False), ('scene_rej_nuked', False), ('scene_nuked_active', False), ('freeleech', False), ('confirmed', False), ('reject_m2ts', False), ('use_after_get_data', True), ('search_mode', 'eponly'), ('search_fallback', False) ]: if hasattr(torrent_prov, attr): attr_check = '%s_%s' % (prov_id, attr.strip('_')) if isinstance(default, bool): setattr(torrent_prov, attr, bool(check_setting_int(CFG, prov_id_uc, attr_check, default))) elif isinstance(default, string_types): setattr(torrent_prov, attr, check_setting_str(CFG, prov_id_uc, attr_check, default)) elif isinstance(default, int): setattr(torrent_prov, attr, check_setting_int(CFG, prov_id_uc, attr_check, default)) for nzb_prov in [curProvider for curProvider in providers.sorted_sources() if GenericProvider.NZB == curProvider.providerType]: prov_id = nzb_prov.get_id() prov_id_uc = nzb_prov.get_id().upper() nzb_prov.enabled = bool(check_setting_int(CFG, prov_id_uc, prov_id, False)) attr = 'enable_recentsearch' if hasattr(nzb_prov, attr): nzb_prov.enable_recentsearch = bool(check_setting_int( CFG, prov_id_uc, '%s_%s' % (prov_id, attr), True)) or not getattr(nzb_prov, 'supports_backlog') for (attr, default) in [ ('enable_backlog', True), ('enable_scheduled_backlog', True), ('api_key', ''), ('digest', ''), ('username', ''), ('scene_only', False), ('scene_or_contain', ''), ('scene_loose', False), ('scene_loose_active', False), ('scene_rej_nuked', False), ('scene_nuked_active', False), ('search_mode', 'eponly'), ('search_fallback', False), ('server_type', NewznabConstants.SERVER_DEFAULT) ]: if hasattr(nzb_prov, attr): attr_check = '%s_%s' % (prov_id, attr.strip('_')) if isinstance(default, bool): setattr(nzb_prov, attr, bool(check_setting_int(CFG, prov_id_uc, attr_check, default))) elif isinstance(default, string_types): setattr(nzb_prov, attr, check_setting_str(CFG, prov_id_uc, attr_check, default)) elif isinstance(default, int): setattr(nzb_prov, attr, check_setting_int(CFG, prov_id_uc, attr_check, default)) for cur_provider in filter(lambda p: abs(zlib.crc32(decode_bytes(p.name))) + 40000400 in ( 1449593765, 1597250020, 1524942228, 160758496, 2925374331 ) or (p.url and abs(zlib.crc32(decode_bytes(re.sub(r'[./]', '', p.url[-10:])))) + 40000400 in ( 2417143804,)), providers.sorted_sources()): header = {'User-Agent': get_ua()} if hasattr(cur_provider, 'nn'): cur_provider.nn = False cur_provider.ui_string() # noinspection PyProtectedMember header = callable(getattr(cur_provider, '_init_api', False)) and False is cur_provider._init_api() \ and header or {} cur_provider.headers.update(header) update_config |= cur_provider.should_save_config() # current commit hash CUR_COMMIT_HASH = check_setting_str(CFG, 'General', 'cur_commit_hash', '') # current commit branch CUR_COMMIT_BRANCH = check_setting_str(CFG, 'General', 'cur_commit_branch', '') if not CUR_COMMIT_HASH or '00000000000000000000000000000000000' == CUR_COMMIT_HASH: tmp_v = version_checker.SoftwareUpdater() if 'git' == tmp_v.install_type: # noinspection PyProtectedMember tmp_v.updater._find_installed_version() if not CUR_COMMIT_HASH: # in case git fails for some reason, set dummy hash to enable update for next start CUR_COMMIT_HASH = '00000000000000000000000000000000000' if not CUR_COMMIT_BRANCH: # noinspection PyProtectedMember tmp_branch = tmp_v.updater._find_installed_branch() if tmp_branch: CUR_COMMIT_BRANCH = tmp_branch update_config = True EXT_UPDATES = (35 > len(CUR_COMMIT_HASH) or not bool(re.match('^[a-z0-9]+$', CUR_COMMIT_HASH))) and \ ('docker/other', 'snap')['snap' in CUR_COMMIT_HASH] if not os.path.isfile(CONFIG_FILE): logger.debug(f'Unable to find \'{CONFIG_FILE}\', all settings will be default!') update_config = True # Get expected config version CONFIG_VERSION = max(ConfigMigrator(CFG).migration_names) # we have fully loaded all config settings into vars CONFIG_LOADED = True if update_config: _save_config(force=True) # start up all the threads old_log = os.path.join(LOG_DIR, 'sickgear.log') if os.path.isfile(old_log): try: os.rename(old_log, os.path.join(LOG_DIR, logger.sb_log_instance.log_file)) except (BaseException, Exception): pass logger.sb_log_instance.init_logging(console_logging=console_logging) showList = [] showDict = {} # dict of switched shows for web redirects switched_shows = {} def init_stage_2(): # Misc global __INITIALIZED__, MEMCACHE, MEMCACHE_FLAG_IMAGES, RECENTSEARCH_STARTUP # Schedulers # global trakt_checker_scheduler global update_software_scheduler, update_packages_scheduler, \ update_show_scheduler, update_release_mappings_scheduler, \ search_backlog_scheduler, search_propers_scheduler, \ search_recent_scheduler, search_subtitles_scheduler, \ search_queue_scheduler, show_queue_scheduler, people_queue_scheduler, \ watched_state_queue_scheduler, emby_watched_state_scheduler, plex_watched_state_scheduler, \ process_media_scheduler, background_mapping_task, config_events # Gen Config/Misc global SHOW_UPDATE_HOUR, UPDATE_INTERVAL, UPDATE_PACKAGES_INTERVAL # Search Settings/Episode global RECENTSEARCH_INTERVAL # Subtitles global USE_SUBTITLES, SUBTITLES_FINDER_INTERVAL # Media Process/Post-Processing global PROCESS_AUTOMATICALLY, MEDIAPROCESS_INTERVAL # Media Process/Metadata global metadata_provider_dict, METADATA_KODI, METADATA_MEDE8ER, METADATA_MEDIABROWSER, \ METADATA_PS3, METADATA_TIVO, METADATA_WDTV, METADATA_XBMC, METADATA_XBMC_12PLUS # Notification Settings/HT and NAS global EMBY_WATCHEDSTATE_INTERVAL, PLEX_WATCHEDSTATE_INTERVAL # initialize main database my_db = db.DBConnection() db.migration_code(my_db) # initialize the cache database my_db = db.DBConnection('cache.db') db.upgrade_database(my_db, cache_db.InitialSchema) # initialize the failed downloads database my_db = db.DBConnection('failed.db') db.upgrade_database(my_db, failed_db.InitialSchema) # fix up any db problems my_db = db.DBConnection() db.sanity_check_db(my_db, mainDB.MainSanityCheck) # initialize metadata_providers metadata_provider_dict = metadata.get_metadata_generator_dict() for cur_metadata_tuple in [(METADATA_KODI, metadata.kodi), (METADATA_MEDE8ER, metadata.mede8er), (METADATA_MEDIABROWSER, metadata.mediabrowser), (METADATA_PS3, metadata.ps3), (METADATA_TIVO, metadata.tivo), (METADATA_WDTV, metadata.wdtv), (METADATA_XBMC, metadata.xbmc), (METADATA_XBMC_12PLUS, metadata.xbmc_12plus), ]: (cur_metadata_config, cur_metadata_class) = cur_metadata_tuple tmp_provider = cur_metadata_class.metadata_class() tmp_provider.set_config(cur_metadata_config) metadata_provider_dict[tmp_provider.name] = tmp_provider # initialize schedulers # / # queues must be first show_queue_scheduler = scheduler.Scheduler( show_queue.ShowQueue(), cycle_time=datetime.timedelta(seconds=3), thread_name='SHOWQUEUE') search_queue_scheduler = scheduler.Scheduler( search_queue.SearchQueue(), cycle_time=datetime.timedelta(seconds=3), thread_name='SEARCHQUEUE') people_queue_scheduler = scheduler.Scheduler( people_queue.PeopleQueue(), cycle_time=datetime.timedelta(seconds=3), thread_name='PEOPLEQUEUE' ) watched_state_queue_scheduler = scheduler.Scheduler( watchedstate_queue.WatchedStateQueue(), cycle_time=datetime.timedelta(seconds=3), thread_name='WATCHEDSTATEQUEUE') # / # updaters update_software_scheduler = scheduler.Scheduler( version_checker.SoftwareUpdater(), cycle_time=datetime.timedelta(hours=UPDATE_INTERVAL), thread_name='SOFTWAREUPDATE', silent=False) update_packages_scheduler = scheduler.Scheduler( version_checker.PackagesUpdater(), cycle_time=datetime.timedelta(hours=UPDATE_PACKAGES_INTERVAL), # run_delay=datetime.timedelta(minutes=2), thread_name='PACKAGESUPDATE', silent=False) update_show_scheduler = scheduler.Scheduler( show_updater.ShowUpdater(), cycle_time=datetime.timedelta(hours=1), start_time=datetime.time(hour=SHOW_UPDATE_HOUR), thread_name='SHOWDATAUPDATE', prevent_cycle_run=show_queue_scheduler.action.is_show_update_running) # 3AM classes.loading_msg.message = 'Loading show maps' update_release_mappings_scheduler = scheduler.Scheduler( scene_exceptions.ReleaseMap(), cycle_time=datetime.timedelta(hours=2), thread_name='SHOWMAPSUPDATE', silent=False) # / # searchers init_search_delay = int(os.environ.get('INIT_SEARCH_DELAY', 0)) # enter 4499 (was 4489) for experimental internal provider intervals update_interval = datetime.timedelta(minutes=(RECENTSEARCH_INTERVAL, 1)[4499 == RECENTSEARCH_INTERVAL]) update_now = datetime.timedelta(minutes=0) search_recent_scheduler = scheduler.Scheduler( search_recent.RecentSearcher(), cycle_time=update_interval, run_delay=update_now if RECENTSEARCH_STARTUP else datetime.timedelta(minutes=init_search_delay or 5), thread_name='RECENTSEARCH', prevent_cycle_run=search_queue_scheduler.action.is_recentsearch_in_progress) if [x for x in providers.sorted_sources() if x.is_active() and getattr(x, 'enable_backlog', None) and GenericProvider.NZB == x.providerType]: nextbacklogpossible = datetime.datetime.fromtimestamp( search_backlog.BacklogSearcher().last_runtime) + datetime.timedelta(hours=23) now = datetime.datetime.now() if nextbacklogpossible > now: time_diff = nextbacklogpossible - now if (time_diff > datetime.timedelta(hours=12) and nextbacklogpossible - datetime.timedelta(hours=12) > now): time_diff = time_diff - datetime.timedelta(hours=12) else: time_diff = datetime.timedelta(minutes=0) backlogdelay = helpers.try_int((time_diff.total_seconds() / 60) + 10, 10) else: backlogdelay = 10 search_backlog_scheduler = search_backlog.BacklogSearchScheduler( search_backlog.BacklogSearcher(), cycle_time=datetime.timedelta(minutes=get_backlog_cycle_time()), run_delay=datetime.timedelta(minutes=init_search_delay or backlogdelay), thread_name='BACKLOGSEARCH', prevent_cycle_run=search_queue_scheduler.action.is_standard_backlog_in_progress) last_proper_search = datetime.datetime.fromtimestamp(properFinder.get_last_proper_search()) time_diff = datetime.timedelta(days=1) - (datetime.datetime.now() - last_proper_search) if time_diff < datetime.timedelta(seconds=0): properdelay = 20 else: properdelay = helpers.try_int((time_diff.total_seconds() / 60) + 5, 20) search_propers_scheduler = scheduler.Scheduler( search_propers.ProperSearcher(), cycle_time=datetime.timedelta(days=1), run_delay=datetime.timedelta(minutes=init_search_delay or properdelay), thread_name='PROPERSSEARCH', prevent_cycle_run=search_queue_scheduler.action.is_propersearch_in_progress) search_subtitles_scheduler = scheduler.Scheduler( subtitles.SubtitlesFinder(), cycle_time=datetime.timedelta(hours=SUBTITLES_FINDER_INTERVAL), thread_name='SUBTITLESEARCH', silent=not USE_SUBTITLES) # / # others emby_watched_state_scheduler = scheduler.Scheduler( EmbyWatchedStateUpdater(), cycle_time=datetime.timedelta(minutes=EMBY_WATCHEDSTATE_INTERVAL), run_delay=datetime.timedelta(minutes=5), thread_name='EMBYWATCHEDSTATE') plex_watched_state_scheduler = scheduler.Scheduler( PlexWatchedStateUpdater(), cycle_time=datetime.timedelta(minutes=PLEX_WATCHEDSTATE_INTERVAL), run_delay=datetime.timedelta(minutes=5), thread_name='PLEXWATCHEDSTATE') process_media_scheduler = scheduler.Scheduler( auto_media_process.MediaProcess(), cycle_time=datetime.timedelta(minutes=MEDIAPROCESS_INTERVAL), thread_name='PROCESSMEDIA', silent=not PROCESS_AUTOMATICALLY) background_mapping_task = threading.Thread(name='MAPPINGUPDATES', target=indexermapper.load_mapped_ids, kwargs={'load_all': True}) MEMCACHE['history_tab_limit'] = 13 MEMCACHE['history_tab'] = History.menu_tab(MEMCACHE['history_tab_limit']) try: with scandir(os.path.join(PROG_DIR, 'gui', GUI_NAME, 'images', 'flags')) as s_d: for f in s_d: if f.is_file(): MEMCACHE_FLAG_IMAGES[os.path.splitext(f.name)[0].lower()] = True except (BaseException, Exception): pass config_events = ConfigEvents(_save_config) config_events.start() __INITIALIZED__ = True return True def enabled_schedulers(is_init=False): # ([], [trakt_checker_scheduler])[USE_TRAKT] + \ return ([], [events])[is_init] \ + ([], [update_software_scheduler, update_packages_scheduler, update_show_scheduler, update_release_mappings_scheduler, search_recent_scheduler, search_backlog_scheduler, search_propers_scheduler, search_subtitles_scheduler, show_queue_scheduler, search_queue_scheduler, people_queue_scheduler, watched_state_queue_scheduler, emby_watched_state_scheduler, plex_watched_state_scheduler, process_media_scheduler ] )[not MEMCACHE.get('update_restart')] \ + ([events], [])[is_init] def start(): global started with INIT_LOCK: if __INITIALIZED__: # Load all Indexer mappings in background indexermapper.defunct_indexer = [ i for i in TVInfoAPI().all_sources if TVInfoAPI(i).config.get('defunct')] indexermapper.indexer_list = [i for i in TVInfoAPI().all_sources if TVInfoAPI(i).config.get('show_url') and True is not TVInfoAPI(i).config.get('people_only')] background_mapping_task.start() for p in providers.sorted_sources(): if p.is_active() and getattr(p, 'ping_iv', None): # noinspection PyProtectedMember provider_ping_thread_pool[p.get_id()] = threading.Thread( name='PING-PROVIDER %s' % p.name, target=p._ping) provider_ping_thread_pool[p.get_id()].start() for thread in enabled_schedulers(is_init=True): # type: threading.Thread thread.start() started = True def restart(soft=True, update_pkg=None): if not soft: if update_pkg: MY_ARGS.append('--update-pkg') logger.log('Trigger event restart') events.put(events.SystemEvent.RESTART) else: halt() save_all() logger.log('Re-initializing all data') initialize() def sig_handler(signum=None, _=None): is_ctrlbreak = 'win32' == sys.platform and signal.SIGBREAK == signum msg = 'Signal "%s" found' % (signal.SIGINT == signum and 'CTRL-C' or is_ctrlbreak and 'CTRL+BREAK' or signal.SIGTERM == signum and 'Termination' or signum) if None is signum or signum in (signal.SIGINT, signal.SIGTERM) or is_ctrlbreak: logger.log('%s, saving and exiting...' % msg) events.put(events.SystemEvent.SHUTDOWN) else: logger.log('%s, not exiting' % msg) def halt(): global __INITIALIZED__, started, config_events logger.debug('Check INIT_LOCK on halt') with INIT_LOCK: logger.debug(f'Check __INITIALIZED__ on halt: {__INITIALIZED__}') if __INITIALIZED__: logger.log('Exiting threads') try: config_events.stopit() except (BaseException, Exception): pass for p in provider_ping_thread_pool: provider_ping_thread_pool[p].stop = True for p in provider_ping_thread_pool: try: provider_ping_thread_pool[p].join(10) logger.log('Thread %s has exit' % provider_ping_thread_pool[p].name) except RuntimeError: logger.log('Fail, thread %s did not exit' % provider_ping_thread_pool[p].name) pass if ADBA_CONNECTION: try: ADBA_CONNECTION.logout() except AniDBError: pass try: ADBA_CONNECTION.join(10) logger.log('Thread %s has exit' % ADBA_CONNECTION.name) except (BaseException, Exception): logger.log('Fail, thread %s did not exit' % ADBA_CONNECTION.name) for thread in enabled_schedulers(): # type: scheduler.Scheduler try: thread.stopit() if getattr(thread, 'action', None) and getattr(thread.action, 'save_queue', None): try: thread.action.save_queue() except (BaseException, Exception): pass except Exception as e: logger.log('Thread %s stop failed with: %s' % (thread.name, e)) for thread in enabled_schedulers(): # type: threading.Thread try: thread.join(10) logger.log('Thread %s has exit' % thread.name) except RuntimeError: # this just means it's the current thread, can be ignored logger.log('Thread %s is exiting' % thread.name) except (BaseException, Exception) as e: logger.log('Thread %s exception %s' % (thread.name, e)) try: config_events.join(10) except RuntimeError: pass __INITIALIZED__ = False started = False def save_all(): if not MEMCACHE.get('update_restart'): global showList # write all shows logger.log('Saving all shows to the database') for show_obj in showList: # type: tv.TVShow show_obj.save_to_db() # save config logger.log('Saving config file to disk') _save_config(force=True) def save_config(force=False): # type: (bool) -> None """ add queue request for saving the config.ini :param force: force save config even if unchanged """ global config_events, CONFIG_LOADED if not CONFIG_LOADED: return # use queue if it's available, otherwise, call save_config directly hasattr(config_events, 'put') and config_events.put(force) or _save_config(force) def _save_config(force=False, **kwargs): # type: (bool, ...) -> None global CONFIG_OLD, CONFIG_LOADED if not CONFIG_LOADED: return new_config = ConfigObj() new_config.filename = CONFIG_FILE # For passwords, you must include the word `password` in the item_name and # add `helpers.encrypt(ITEM_NAME, ENCRYPTION_VERSION)` in save_config() new_config['General'] = dict() s_z = check_setting_int(CFG, 'General', 'stack_size', 0) if s_z: new_config['General']['stack_size'] = s_z new_config['General']['config_version'] = CONFIG_VERSION new_config['General']['branch'] = BRANCH new_config['General']['git_remote'] = GIT_REMOTE new_config['General']['cur_commit_hash'] = CUR_COMMIT_HASH new_config['General']['cur_commit_branch'] = CUR_COMMIT_BRANCH new_config['General']['encryption_version'] = int(ENCRYPTION_VERSION) new_config['General']['log_dir'] = ACTUAL_LOG_DIR if ACTUAL_LOG_DIR else 'Logs' new_config['General']['file_logging_preset'] = FILE_LOGGING_PRESET \ if FILE_LOGGING_PRESET and 'DB' != FILE_LOGGING_PRESET else 'DEBUG' new_config['General']['file_logging_db'] = 0 new_config['General']['socket_timeout'] = SOCKET_TIMEOUT new_config['General']['web_host'] = WEB_HOST new_config['General']['web_port'] = WEB_PORT new_config['General']['web_ipv6'] = int(WEB_IPV6) new_config['General']['web_ipv64'] = int(WEB_IPV64) new_config['General']['web_log'] = int(WEB_LOG) new_config['General']['web_root'] = WEB_ROOT new_config['General']['web_username'] = WEB_USERNAME new_config['General']['web_password'] = helpers.encrypt(WEB_PASSWORD, ENCRYPTION_VERSION) new_config['General']['cpu_preset'] = CPU_PRESET new_config['General']['anon_redirect'] = ANON_REDIRECT new_config['General']['use_api'] = int(USE_API) new_config['General']['api_keys'] = '|||'.join([':::'.join(a) for a in API_KEYS]) new_config['General']['debug'] = int(DEBUG) new_config['General']['enable_https'] = int(ENABLE_HTTPS) new_config['General']['https_cert'] = HTTPS_CERT new_config['General']['https_key'] = HTTPS_KEY new_config['General']['handle_reverse_proxy'] = int(HANDLE_REVERSE_PROXY) new_config['General']['send_security_headers'] = int(SEND_SECURITY_HEADERS) new_config['General']['allowed_hosts'] = ALLOWED_HOSTS new_config['General']['allow_anyip'] = int(ALLOW_ANYIP) new_config['General']['use_nzbs'] = int(USE_NZBS) new_config['General']['use_torrents'] = int(USE_TORRENTS) new_config['General']['nzb_method'] = NZB_METHOD new_config['General']['torrent_method'] = TORRENT_METHOD new_config['General']['usenet_retention'] = int(USENET_RETENTION) new_config['General']['mediaprocess_interval'] = int(MEDIAPROCESS_INTERVAL) new_config['General']['recentsearch_interval'] = int(RECENTSEARCH_INTERVAL) new_config['General']['backlog_period'] = int(BACKLOG_PERIOD) new_config['General']['backlog_limited_period'] = int(BACKLOG_LIMITED_PERIOD) new_config['General']['download_propers'] = int(DOWNLOAD_PROPERS) new_config['General']['propers_webdl_onegrp'] = int(PROPERS_WEBDL_ONEGRP) new_config['General']['allow_high_priority'] = int(ALLOW_HIGH_PRIORITY) new_config['General']['recentsearch_startup'] = int(RECENTSEARCH_STARTUP) new_config['General']['backlog_nofull'] = int(BACKLOG_NOFULL) new_config['General']['skip_removed_files'] = int(SKIP_REMOVED_FILES) new_config['General']['results_sortby'] = str(RESULTS_SORTBY) new_config['General']['indexer_default'] = int(TVINFO_DEFAULT) new_config['General']['indexer_timeout'] = int(TVINFO_TIMEOUT) new_config['General']['quality_default'] = int(QUALITY_DEFAULT) new_config['General']['wanted_begin_default'] = int(WANTED_BEGIN_DEFAULT) new_config['General']['wanted_latest_default'] = int(WANTED_LATEST_DEFAULT) new_config['General']['pause default'] = int(PAUSE_DEFAULT) new_config['General']['status_default'] = int(STATUS_DEFAULT) new_config['General']['scene_default'] = int(SCENE_DEFAULT) new_config['General']['flatten_folders_default'] = int(FLATTEN_FOLDERS_DEFAULT) new_config['General']['anime_default'] = int(ANIME_DEFAULT) new_config['General']['provider_order'] = ' '.join(PROVIDER_ORDER) new_config['General']['provider_homes'] = '%s' % dict([(pid, v) for pid, v in list(PROVIDER_HOMES.items()) if pid in [ p.get_id() for p in [x for x in providers.sorted_sources() if GenericProvider.TORRENT == x.providerType]]]) new_config['General']['update_notify'] = int(UPDATE_NOTIFY) new_config['General']['update_auto'] = int(UPDATE_AUTO) new_config['General']['update_interval'] = int(UPDATE_INTERVAL) new_config['General']['notify_on_update'] = int(NOTIFY_ON_UPDATE) new_config['General']['update_packages_notify'] = int(UPDATE_PACKAGES_NOTIFY) new_config['General']['update_packages_auto'] = int(UPDATE_PACKAGES_AUTO) new_config['General']['update_packages_menu'] = int(UPDATE_PACKAGES_MENU) new_config['General']['update_packages_interval'] = int(UPDATE_PACKAGES_INTERVAL) new_config['General']['naming_strip_year'] = int(NAMING_STRIP_YEAR) new_config['General']['naming_pattern'] = NAMING_PATTERN new_config['General']['naming_custom_abd'] = int(NAMING_CUSTOM_ABD) new_config['General']['naming_abd_pattern'] = NAMING_ABD_PATTERN new_config['General']['naming_custom_sports'] = int(NAMING_CUSTOM_SPORTS) new_config['General']['naming_sports_pattern'] = NAMING_SPORTS_PATTERN new_config['General']['naming_custom_anime'] = int(NAMING_CUSTOM_ANIME) new_config['General']['naming_anime_pattern'] = NAMING_ANIME_PATTERN new_config['General']['naming_multi_ep'] = int(NAMING_MULTI_EP) new_config['General']['naming_anime_multi_ep'] = int(NAMING_ANIME_MULTI_EP) new_config['General']['naming_anime'] = int(NAMING_ANIME) new_config['General']['launch_browser'] = int(LAUNCH_BROWSER) new_config['General']['update_shows_on_start'] = int(UPDATE_SHOWS_ON_START) new_config['General']['show_update_hour'] = int(SHOW_UPDATE_HOUR) new_config['General']['trash_remove_show'] = int(TRASH_REMOVE_SHOW) new_config['General']['trash_rotate_logs'] = int(TRASH_ROTATE_LOGS) new_config['General']['home_search_focus'] = int(HOME_SEARCH_FOCUS) new_config['General']['display_freespace'] = int(DISPLAY_FREESPACE) new_config['General']['sort_article'] = int(SORT_ARTICLE) new_config['General']['proxy_setting'] = PROXY_SETTING sg_helpers.PROXY_SETTING = PROXY_SETTING new_config['General']['proxy_indexers'] = int(PROXY_INDEXERS) new_config['General']['metadata_xbmc'] = METADATA_XBMC new_config['General']['metadata_xbmc_12plus'] = METADATA_XBMC_12PLUS new_config['General']['metadata_mediabrowser'] = METADATA_MEDIABROWSER new_config['General']['metadata_ps3'] = METADATA_PS3 new_config['General']['metadata_wdtv'] = METADATA_WDTV new_config['General']['metadata_tivo'] = METADATA_TIVO new_config['General']['metadata_mede8er'] = METADATA_MEDE8ER new_config['General']['metadata_kodi'] = METADATA_KODI new_config['General']['search_unaired'] = int(SEARCH_UNAIRED) new_config['General']['unaired_recent_search_only'] = int(UNAIRED_RECENT_SEARCH_ONLY) new_config['General']['flaresolverr_host'] = FLARESOLVERR_HOST new_config['General']['cache_dir'] = ACTUAL_CACHE_DIR if ACTUAL_CACHE_DIR else 'cache' sg_helpers.CACHE_DIR = CACHE_DIR sg_helpers.DATA_DIR = DATA_DIR new_config['General']['root_dirs'] = ROOT_DIRS if ROOT_DIRS else '' new_config['General']['tv_download_dir'] = TV_DOWNLOAD_DIR new_config['General']['keep_processed_dir'] = int(KEEP_PROCESSED_DIR) new_config['General']['process_method'] = PROCESS_METHOD new_config['General']['process_last_dir'] = PROCESS_LAST_DIR new_config['General']['process_last_method'] = PROCESS_LAST_METHOD new_config['General']['process_last_cleanup'] = int(PROCESS_LAST_CLEANUP) new_config['General']['move_associated_files'] = int(MOVE_ASSOCIATED_FILES) new_config['General']['postpone_if_sync_files'] = int(POSTPONE_IF_SYNC_FILES) new_config['General']['process_positive_log'] = int(PROCESS_POSITIVE_LOG) new_config['General']['nfo_rename'] = int(NFO_RENAME) new_config['General']['process_automatically'] = int(PROCESS_AUTOMATICALLY) new_config['General']['unpack'] = int(UNPACK) new_config['General']['rename_episodes'] = int(RENAME_EPISODES) new_config['General']['rename_tba_episodes'] = int(RENAME_TBA_EPISODES) new_config['General']['rename_name_changed_episodes'] = int(RENAME_NAME_CHANGED_EPISODES) new_config['General']['airdate_episodes'] = int(AIRDATE_EPISODES) new_config['General']['create_missing_show_dirs'] = int(CREATE_MISSING_SHOW_DIRS) new_config['General']['show_dirs_with_dots'] = int(SHOW_DIRS_WITH_DOTS) new_config['General']['add_shows_wo_dir'] = int(ADD_SHOWS_WO_DIR) new_config['General']['add_shows_metalang'] = ADD_SHOWS_METALANG new_config['General']['remove_filename_chars'] = REMOVE_FILENAME_CHARS new_config['General']['import_default_checked_shows'] = int(IMPORT_DEFAULT_CHECKED_SHOWS) new_config['General']['extra_scripts'] = '|'.join(EXTRA_SCRIPTS) new_config['General']['sg_extra_scripts'] = '|'.join(SG_EXTRA_SCRIPTS) new_config['General']['git_path'] = GIT_PATH new_config['General']['ignore_words'] = helpers.generate_word_str(IGNORE_WORDS, IGNORE_WORDS_REGEX) new_config['General']['require_words'] = helpers.generate_word_str(REQUIRE_WORDS, REQUIRE_WORDS_REGEX) new_config['General']['calendar_unprotected'] = int(CALENDAR_UNPROTECTED) new_config['Updates'] = {} new_config['Updates']['updates_todo'] = '%s' % (UPDATES_TODO or {}) new_config['Backup'] = {} if BACKUP_DB_PATH: new_config['Backup']['backup_db_path'] = BACKUP_DB_PATH new_config['Backup']['backup_db_oneday'] = int(BACKUP_DB_ONEDAY) new_config['Backup']['backup_db_max_count'] = BACKUP_DB_MAX_COUNT default_not_zero = ('enable_recentsearch', 'enable_backlog', 'enable_scheduled_backlog', 'use_after_get_data') for src in filter(lambda px: GenericProvider.TORRENT == px.providerType, providers.sorted_sources()): src_id = src.get_id() src_id_uc = src_id.upper() new_config[src_id_uc] = {} if int(src.enabled): new_config[src_id_uc][src_id] = int(src.enabled) if getattr(src, 'url_edit', None): new_config[src_id_uc][src_id + '_url_home'] = src.url_home if getattr(src, 'password', None): new_config[src_id_uc][src_id + '_password'] = helpers.encrypt(src.password, ENCRYPTION_VERSION) for (attr, value) in [ (k, getattr(src, k, v) if not v else helpers.try_int(getattr(src, k, None))) for (k, v) in [ ('enable_recentsearch', 1), ('enable_backlog', 1), ('enable_scheduled_backlog', 1), ('api_key', None), ('passkey', None), ('digest', None), ('hash', None), ('username', ''), ('uid', ''), ('minseed', 1), ('minleech', 1), ('seed_time', None), ('confirmed', 1), ('freeleech', 1), ('reject_m2ts', 1), ('use_after_get_data', 1), ('scene_only', None), ('scene_or_contain', ''), ('scene_loose', None), ('scene_loose_active', None), ('scene_rej_nuked', None), ('scene_nuked_active', None), ('search_mode', None), ('search_fallback', 1) ] if hasattr(src, k)]: if (value and not ('search_mode' == attr and 'eponly' == value) # must allow the following to save '0' not '1' because default is enable (1) instead of disable (0) and (attr not in default_not_zero) or not value and (attr in default_not_zero)): new_config[src_id_uc]['%s_%s' % (src_id, attr)] = value if getattr(src, '_seed_ratio', None): new_config[src_id_uc][src_id + '_seed_ratio'] = src.seed_ratio() if getattr(src, 'filter', None): new_config[src_id_uc][src_id + '_filter'] = src.filter if not new_config[src_id_uc]: del new_config[src_id_uc] default_not_zero = ('enable_recentsearch', 'enable_backlog', 'enable_scheduled_backlog') for src in filter(lambda px: GenericProvider.NZB == px.providerType, providers.sorted_sources()): src_id = src.get_id() src_id_uc = src.get_id().upper() new_config[src_id_uc] = {} if int(src.enabled): new_config[src_id_uc][src_id] = int(src.enabled) for attr in filter(lambda _a: None is not getattr(src, _a, None), ('api_key', 'digest', 'username', 'search_mode')): if 'search_mode' != attr or 'eponly' != getattr(src, attr): new_config[src_id_uc]['%s_%s' % (src_id, attr)] = getattr(src, attr) for attr in filter(lambda _a: None is not getattr(src, _a, None), ( 'enable_recentsearch', 'enable_backlog', 'enable_scheduled_backlog', 'scene_only', 'scene_loose', 'scene_loose_active', 'scene_rej_nuked', 'scene_nuked_active', 'search_fallback', 'server_type')): value = helpers.try_int(getattr(src, attr, None)) # must allow the following to save '0' not '1' because default is enable (1) instead of disable (0) if (value and (attr not in default_not_zero)) or (not value and (attr in default_not_zero)): new_config[src_id_uc]['%s_%s' % (src_id, attr)] = value attr = 'scene_or_contain' if getattr(src, attr, None): new_config[src_id_uc]['%s_%s' % (src_id, attr)] = getattr(src, attr, '') if not new_config[src_id_uc]: del new_config[src_id_uc] cfg_keys = [] for (cfg, items) in iteritems(OrderedDict([ # ----------------------------------- # Config/Search # ----------------------------------- ('Blackhole', [ ('nzb_dir', NZB_DIR), ('torrent_dir', TORRENT_DIR)]), ('NZBGet', [ ('username', NZBGET_USERNAME), ('password', helpers.encrypt(NZBGET_PASSWORD, ENCRYPTION_VERSION)), ('host', NZBGET_HOST), ('category', NZBGET_CATEGORY), ('use_https', int(NZBGET_USE_HTTPS)), ('priority', NZBGET_PRIORITY), ('map', NZBGET_MAP), ('skip_process_media', int(NZBGET_SKIP_PM)), ]), ('SABnzbd', [ ('username', SAB_USERNAME), ('password', helpers.encrypt(SAB_PASSWORD, ENCRYPTION_VERSION)), ('apikey', SAB_APIKEY), ('host', SAB_HOST), ('category', SAB_CATEGORY), ]), ('TORRENT', [ ('username', TORRENT_USERNAME), ('password', helpers.encrypt(TORRENT_PASSWORD, ENCRYPTION_VERSION)), ('host', TORRENT_HOST), ('path', TORRENT_PATH), ('seed_time', int(TORRENT_SEED_TIME)), ('paused', int(TORRENT_PAUSED)), ('high_bandwidth', int(TORRENT_HIGH_BANDWIDTH)), ('label', TORRENT_LABEL), ('label_var', int(TORRENT_LABEL_VAR)), ('verify_cert', int(TORRENT_VERIFY_CERT)), ]), # ----------------------------------- # Config/Notifications # ----------------------------------- ('Emby', [ ('use_%s', int(USE_EMBY)), ('apikey', EMBY_APIKEY), ('host', EMBY_HOST), ('update_library', int(EMBY_UPDATE_LIBRARY)), ('watchedstate_scheduled', int(EMBY_WATCHEDSTATE_SCHEDULED)), ('watchedstate_interval', int(EMBY_WATCHEDSTATE_INTERVAL)), ('parent_maps', EMBY_PARENT_MAPS), ]), ('Kodi', [ ('use_%s', int(USE_KODI)), ('username', KODI_USERNAME), ('password', helpers.encrypt(KODI_PASSWORD, ENCRYPTION_VERSION)), ('host', KODI_HOST), ('always_on', int(KODI_ALWAYS_ON)), ('update_library', int(KODI_UPDATE_LIBRARY)), ('update_full', int(KODI_UPDATE_FULL)), ('update_onlyfirst', int(KODI_UPDATE_ONLYFIRST)), ('parent_maps', KODI_PARENT_MAPS), ]), ('Plex', [ ('use_%s', int(USE_PLEX)), ('username', PLEX_USERNAME), ('password', helpers.encrypt(PLEX_PASSWORD, ENCRYPTION_VERSION)), ('host', PLEX_HOST), ('update_library', int(PLEX_UPDATE_LIBRARY)), ('watchedstate_scheduled', int(PLEX_WATCHEDSTATE_SCHEDULED)), ('watchedstate_interval', int(PLEX_WATCHEDSTATE_INTERVAL)), ('parent_maps', PLEX_PARENT_MAPS), ('server_host', PLEX_SERVER_HOST), ]), ('XBMC', [ ('use_%s', int(USE_XBMC)), ('username', XBMC_USERNAME), ('password', helpers.encrypt(XBMC_PASSWORD, ENCRYPTION_VERSION)), ('host', XBMC_HOST), ('always_on', int(XBMC_ALWAYS_ON)), ('update_library', int(XBMC_UPDATE_LIBRARY)), ('update_full', int(XBMC_UPDATE_FULL)), ('update_onlyfirst', int(XBMC_UPDATE_ONLYFIRST)), ]), ('NMJ', [ ('use_%s', int(USE_NMJ)), ('host', NMJ_HOST), ('database', NMJ_DATABASE), ('mount', NMJ_MOUNT), ]), ('NMJv2', [ ('use_%s', int(USE_NMJv2)), ('host', NMJv2_HOST), ('database', NMJv2_DATABASE), ('dbloc', NMJv2_DBLOC), ]), ('Synology', [ ('use_synoindex', int(USE_SYNOINDEX)), ]), ('SynologyNotifier', [ ('use_%s', int(USE_SYNOLOGYNOTIFIER)), ]), ('pyTivo', [ ('use_%s', int(USE_PYTIVO)), ('host', PYTIVO_HOST), ('share_name', PYTIVO_SHARE_NAME), ('tivo_name', PYTIVO_TIVO_NAME), ]), ('Boxcar2', [ ('use_%s', int(USE_BOXCAR2)), ('accesstoken', BOXCAR2_ACCESSTOKEN), ('sound', BOXCAR2_SOUND if 'default' != BOXCAR2_SOUND else None), ]), ('Pushbullet', [ ('use_%s', int(USE_PUSHBULLET)), ('access_token', PUSHBULLET_ACCESS_TOKEN), ('device_iden', PUSHBULLET_DEVICE_IDEN), ]), ('Pushover', [ ('use_%s', int(USE_PUSHOVER)), ('userkey', PUSHOVER_USERKEY), ('apikey', PUSHOVER_APIKEY), ('priority', PUSHOVER_PRIORITY if '0' != PUSHOVER_PRIORITY else None), ('device', PUSHOVER_DEVICE if 'all' != PUSHOVER_DEVICE else None), ('sound', PUSHOVER_SOUND if 'pushover' != PUSHOVER_SOUND else None), ]), ('Growl', [ ('use_%s', int(USE_GROWL)), ('host', GROWL_HOST), # ('password', helpers.encrypt(GROWL_PASSWORD, ENCRYPTION_VERSION)), ]), ('Prowl', [ ('use_%s', int(USE_PROWL)), ('api', PROWL_API), ('priority', PROWL_PRIORITY if '0' != PROWL_PRIORITY else None), ]), ('Libnotify', [ ('use_%s', int(USE_LIBNOTIFY)) ]), # deprecated service # new_config['Pushalot'] = {} # new_config['Pushalot']['use_pushalot'] = int(USE_PUSHALOT) # new_config['Pushalot']['pushalot_authorizationtoken'] = PUSHALOT_AUTHORIZATIONTOKEN ('Trakt', [ ('use_%s', int(USE_TRAKT)), ('update_collection', TRAKT_UPDATE_COLLECTION and trakt_helpers.build_config_string(TRAKT_UPDATE_COLLECTION)), ('accounts', TraktAPI.build_config_string(TRAKT_ACCOUNTS)), ('mru', TRAKT_MRU), # new_config['Trakt'] = {} # new_config['Trakt']['trakt_remove_watchlist'] = int(TRAKT_REMOVE_WATCHLIST) # new_config['Trakt']['trakt_remove_serieslist'] = int(TRAKT_REMOVE_SERIESLIST) # new_config['Trakt']['trakt_use_watchlist'] = int(TRAKT_USE_WATCHLIST) # new_config['Trakt']['trakt_method_add'] = int(TRAKT_METHOD_ADD) # new_config['Trakt']['trakt_start_paused'] = int(TRAKT_START_PAUSED) # new_config['Trakt']['trakt_sync'] = int(TRAKT_SYNC) # new_config['Trakt']['trakt_default_indexer'] = int(TRAKT_DEFAULT_INDEXER) ]), ('Metacritic', [ ('mru', MC_MRU) ]), ('NextEpisode', [ ('mru', NE_MRU) ]), ('TMDB', [ ('mru', TMDB_MRU) ]), ('TVCalendar', [ ('mru', TVC_MRU) ]), ('TVmaze', [ ('mru', TVM_MRU) ]), ('Slack', [ ('use_%s', int(USE_SLACK)), ('channel', SLACK_CHANNEL), ('as_authed', int(SLACK_AS_AUTHED)), ('bot_name', SLACK_BOT_NAME), ('icon_url', SLACK_ICON_URL), ('access_token', SLACK_ACCESS_TOKEN), ]), ('Discord', [ ('use_%s', int(USE_DISCORD)), ('as_authed', int(DISCORD_AS_AUTHED)), ('username', DISCORD_USERNAME), ('icon_url', DISCORD_ICON_URL), ('as_tts', int(DISCORD_AS_TTS)), ('access_token', DISCORD_ACCESS_TOKEN), ]), ('Gitter', [ ('use_%s', int(USE_GITTER)), ('room', GITTER_ROOM), ('access_token', GITTER_ACCESS_TOKEN), ]), ('Telegram', [ ('use_%s', int(USE_TELEGRAM)), ('send_image', int(TELEGRAM_SEND_IMAGE)), ('quiet', int(TELEGRAM_QUIET)), ('access_token', TELEGRAM_ACCESS_TOKEN), ('chatid', TELEGRAM_CHATID), ]), ('Email', [ ('use_%s', int(USE_EMAIL)), ('old_subjects', int(EMAIL_OLD_SUBJECTS)), ('host', EMAIL_HOST), ('port', int(EMAIL_PORT) if 25 != int(EMAIL_PORT) else None), ('tls', int(EMAIL_TLS)), ('user', EMAIL_USER), ('password', helpers.encrypt(EMAIL_PASSWORD, ENCRYPTION_VERSION)), ('from', EMAIL_FROM), ('list', EMAIL_LIST), ]), # (, [(, )]), ])): cfg_lc = cfg.lower() cfg_keys += [cfg] new_config[cfg] = {} for (k, v) in filter(lambda arg: any([arg[1]]) or ( # allow saving where item value default is non-zero but 0 is a required setting value cfg_lc in ('kodi', 'xbmc', 'synoindex', 'nzbget', 'torrent', 'telegram') and arg[0] in ('always_on', 'priority', 'send_image')) or ('rtorrent' == new_config['General']['torrent_method'] and 'label_var' == arg[0]), items): k = '%s' in k and (k % cfg_lc) or (cfg_lc + '_' + k) # correct for cases where keys are named in an inconsistent manner to parent stanza k = k.replace('blackhole_', '').replace('sabnzbd_', 'sab_') new_config[cfg].update({k: v}) for (notifier, onsnatch, ondownload, onsubtitledownload) in [ ('Kodi', KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD), ('Plex', PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD), ('XBMC', XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD), ('SynologyNotifier', SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH, SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD, SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD), ('Boxcar2', BOXCAR2_NOTIFY_ONSNATCH, BOXCAR2_NOTIFY_ONDOWNLOAD, BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD), ('Pushbullet', PUSHBULLET_NOTIFY_ONSNATCH, PUSHBULLET_NOTIFY_ONDOWNLOAD, PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD), ('Pushover', PUSHOVER_NOTIFY_ONSNATCH, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD), ('Growl', GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, GROWL_NOTIFY_ONSUBTITLEDOWNLOAD), ('Prowl', PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_NOTIFY_ONSUBTITLEDOWNLOAD), ('Libnotify', LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD), # ('Pushalot', PUSHALOT_NOTIFY_ONSNATCH, PUSHALOT_NOTIFY_ONDOWNLOAD, PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD), ('Slack', SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD), ('Discord', DISCORD_NOTIFY_ONSNATCH, DISCORD_NOTIFY_ONDOWNLOAD, DISCORD_NOTIFY_ONSUBTITLEDOWNLOAD), ('Gitter', GITTER_NOTIFY_ONSNATCH, GITTER_NOTIFY_ONDOWNLOAD, GITTER_NOTIFY_ONSUBTITLEDOWNLOAD), ('Telegram', TELEGRAM_NOTIFY_ONSNATCH, TELEGRAM_NOTIFY_ONDOWNLOAD, TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD), ('Email', EMAIL_NOTIFY_ONSNATCH, EMAIL_NOTIFY_ONDOWNLOAD, EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD), ]: if any([onsnatch, ondownload, onsubtitledownload]): if onsnatch: new_config[notifier]['%s_notify_onsnatch' % notifier.lower()] = int(onsnatch) if ondownload: new_config[notifier]['%s_notify_ondownload' % notifier.lower()] = int(ondownload) if onsubtitledownload: new_config[notifier]['%s_notify_onsubtitledownload' % notifier.lower()] = int(onsubtitledownload) # remove empty stanzas for k in filter(lambda c: not new_config[c], cfg_keys): del new_config[k] new_config['Newznab'] = {} new_config['Newznab']['newznab_data'] = NEWZNAB_DATA torrent_rss = '!!!'.join([x.config_str() for x in torrent_rss_providers]) if torrent_rss: new_config['TorrentRss'] = {} new_config['TorrentRss']['torrentrss_data'] = torrent_rss new_config['GUI'] = {} new_config['GUI']['gui_name'] = GUI_NAME new_config['GUI']['theme_name'] = THEME_NAME new_config['GUI']['default_home'] = DEFAULT_HOME new_config['GUI']['fanart_limit'] = FANART_LIMIT new_config['GUI']['fanart_panel'] = FANART_PANEL new_config['GUI']['fanart_ratings'] = '%s' % (FANART_RATINGS or {}) new_config['GUI']['use_imdb_info'] = int(USE_IMDB_INFO) new_config['GUI']['imdb_accounts'] = IMDB_ACCOUNTS new_config['GUI']['fuzzy_dating'] = int(FUZZY_DATING) new_config['GUI']['trim_zero'] = int(TRIM_ZERO) new_config['GUI']['date_preset'] = DATE_PRESET new_config['GUI']['time_preset'] = TIME_PRESET_W_SECONDS new_config['GUI']['timezone_display'] = TIMEZONE_DISPLAY new_config['GUI']['show_tags'] = ','.join(SHOW_TAGS) new_config['GUI']['showlist_tagview'] = SHOWLIST_TAGVIEW new_config['GUI']['home_layout'] = HOME_LAYOUT new_config['GUI']['footer_time_layout'] = FOOTER_TIME_LAYOUT new_config['GUI']['poster_sortby'] = POSTER_SORTBY new_config['GUI']['poster_sortdir'] = POSTER_SORTDIR new_config['GUI']['display_show_glide'] = '%s' % (DISPLAY_SHOW_GLIDE or {}) new_config['GUI']['display_show_glide_slidetime'] = int(DISPLAY_SHOW_GLIDE_SLIDETIME) new_config['GUI']['display_show_viewmode'] = int(DISPLAY_SHOW_VIEWMODE) new_config['GUI']['display_show_background'] = int(DISPLAY_SHOW_BACKGROUND) new_config['GUI']['display_show_background_translucent'] = int(DISPLAY_SHOW_BACKGROUND_TRANSLUCENT) new_config['GUI']['display_show_viewart'] = int(DISPLAY_SHOW_VIEWART) new_config['GUI']['display_show_minimum'] = int(DISPLAY_SHOW_MINIMUM) new_config['GUI']['display_show_specials'] = int(DISPLAY_SHOW_SPECIALS) new_config['GUI']['episode_view_viewmode'] = int(EPISODE_VIEW_VIEWMODE) new_config['GUI']['episode_view_background'] = int(EPISODE_VIEW_BACKGROUND) new_config['GUI']['episode_view_background_translucent'] = int(EPISODE_VIEW_BACKGROUND_TRANSLUCENT) new_config['GUI']['episode_view_layout'] = EPISODE_VIEW_LAYOUT new_config['GUI']['episode_view_sort'] = EPISODE_VIEW_SORT new_config['GUI']['episode_view_display_paused'] = int(EPISODE_VIEW_DISPLAY_PAUSED) new_config['GUI']['episode_view_posters'] = int(EPISODE_VIEW_POSTERS) new_config['GUI']['episode_view_missed_range'] = int(EPISODE_VIEW_MISSED_RANGE) new_config['GUI']['poster_sortby'] = POSTER_SORTBY new_config['GUI']['poster_sortdir'] = POSTER_SORTDIR new_config['GUI']['show_tags'] = ','.join(SHOW_TAGS) new_config['GUI']['showlist_tagview'] = SHOWLIST_TAGVIEW new_config['GUI']['show_tag_default'] = SHOW_TAG_DEFAULT new_config['GUI']['history_layout'] = HISTORY_LAYOUT new_config['GUI']['browselist_hidden'] = '|~|'.join(BROWSELIST_HIDDEN) new_config['GUI']['browselist_prefs'] = '%s' % (BROWSELIST_MRU or {}) new_config['Subtitles'] = {} new_config['Subtitles']['use_subtitles'] = int(USE_SUBTITLES) new_config['Subtitles']['subtitles_languages'] = ','.join(SUBTITLES_LANGUAGES) new_config['Subtitles']['SUBTITLES_SERVICES_LIST'] = ','.join(SUBTITLES_SERVICES_LIST) new_config['Subtitles']['SUBTITLES_SERVICES_ENABLED'] = '|'.join([str(x) for x in SUBTITLES_SERVICES_ENABLED]) new_config['Subtitles']['subtitles_services_auth'] = '|||'.join([':::'.join(a) for a in SUBTITLES_SERVICES_AUTH]) new_config['Subtitles']['subtitles_dir'] = SUBTITLES_DIR new_config['Subtitles']['subtitles_default'] = int(SUBTITLES_DEFAULT) new_config['Subtitles']['subtitles_history'] = int(SUBTITLES_HISTORY) new_config['Subtitles']['subtitles_finder_interval'] = int(SUBTITLES_FINDER_INTERVAL) new_config['Subtitles']['subtitles_os_hash'] = SUBTITLES_OS_HASH new_config['FailedDownloads'] = {} new_config['FailedDownloads']['use_failed_downloads'] = int(USE_FAILED_DOWNLOADS) new_config['FailedDownloads']['delete_failed'] = int(DELETE_FAILED) new_config['ANIDB'] = {} new_config['ANIDB']['use_anidb'] = int(USE_ANIDB) new_config['ANIDB']['anidb_username'] = ANIDB_USERNAME new_config['ANIDB']['anidb_password'] = helpers.encrypt(ANIDB_PASSWORD, ENCRYPTION_VERSION) new_config['ANIDB']['anidb_use_mylist'] = int(ANIDB_USE_MYLIST) new_config['ANIME'] = {} new_config['ANIME']['anime_treat_as_hdtv'] = int(ANIME_TREAT_AS_HDTV) if not force and CONFIG_OLD == new_config and os.path.isfile(new_config.filename): logger.debug('config.ini not dirty, not saving.') return from sg_helpers import copy_file backup_config = re.sub(r'\.ini$', '.bak', CONFIG_FILE) from .config import check_valid_config try: copy_file(CONFIG_FILE, backup_config) if not check_valid_config(backup_config): logger.error('config file seams to be invalid, not backing up.') remove_file_perm(backup_config) backup_config = None except (BaseException, Exception): backup_config = None for _ in range(0, 3): new_config.write() if check_valid_config(CONFIG_FILE): CONFIG_OLD = copy.deepcopy(new_config) return logger.warning('saving config file failed, retrying...') remove_file_perm(CONFIG_FILE) time.sleep(3) # we only get here if the config saving failed multiple times if None is not backup_config and os.path.isfile(backup_config): logger.error('saving config file failed, using backup file') try: copy_file(backup_config, CONFIG_FILE) logger.log('using old backup config file') return except (BaseException, Exception): logger.error('failed to use backup config file') from sg_helpers import scantree try: target_base = os.path.join(BACKUP_DB_PATH or os.path.join(DATA_DIR, 'backup')) file_list = [f for f in scantree(target_base, include='config', filter_kind=False)] if file_list: logger.log('trying to use latest config.ini backup') # sort newest to oldest backup file_list.sort(key=lambda _f: _f.stat(follow_symlinks=False).st_mtime) import zipfile try: with zipfile.ZipFile(file_list[0].path, mode='r') as zf: zf.extractall(target_base) backup_config_file = os.path.join(target_base, 'config.ini') if os.path.isfile(backup_config_file): os.replace(backup_config_file, CONFIG_FILE) if check_valid_config(CONFIG_FILE): logger.log(f'used latest config.ini backup file: {file_list[0].name}') return else: logger.error(f'failed to use latest config.ini backup file: {file_list[0].name}') remove_file_perm(CONFIG_FILE) except (BaseException, Exception): pass finally: try: remove_file_perm(backup_config_file) except (BaseException, Exception): pass logger.error('failed to use latest config.ini') except (BaseException, Exception): pass logger.error('saving config file failed and no backup available') def launch_browser(start_port=None): if not start_port: start_port = WEB_PORT browser_url = 'http%s://localhost:%d%s' % (('s', '')[not ENABLE_HTTPS], start_port, WEB_ROOT) try: webbrowser.open(browser_url, 2, True) except (BaseException, Exception): try: webbrowser.open(browser_url, 1, True) except (BaseException, Exception): logger.error('Unable to launch a browser')