1# This file is part of Tautulli. 2# 3# Tautulli is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7# 8# Tautulli is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with Tautulli. If not, see <http://www.gnu.org/licenses/>. 15 16from __future__ import unicode_literals 17from future.builtins import object 18from future.builtins import str 19 20import os 21import re 22import shutil 23import time 24import threading 25 26from configobj import ConfigObj, ParseError 27from hashing_passwords import make_hash 28 29import plexpy 30if plexpy.PYTHON2: 31 import helpers 32 import logger 33else: 34 from plexpy import helpers 35 from plexpy import logger 36 37 38def bool_int(value): 39 """ 40 Casts a config value into a 0 or 1 41 """ 42 if isinstance(value, str): 43 if value.lower() in ('', '0', 'false', 'f', 'no', 'n', 'off'): 44 value = 0 45 return int(bool(value)) 46 47 48FILENAME = "config.ini" 49 50_CONFIG_DEFINITIONS = { 51 'ALLOW_GUEST_ACCESS': (int, 'General', 0), 52 'DATE_FORMAT': (str, 'General', 'YYYY-MM-DD'), 53 'PMS_CLIENT_ID': (str, 'PMS', ''), 54 'PMS_IDENTIFIER': (str, 'PMS', ''), 55 'PMS_IP': (str, 'PMS', '127.0.0.1'), 56 'PMS_IS_CLOUD': (int, 'PMS', 0), 57 'PMS_IS_REMOTE': (int, 'PMS', 0), 58 'PMS_LOGS_FOLDER': (str, 'PMS', ''), 59 'PMS_LOGS_LINE_CAP': (int, 'PMS', 1000), 60 'PMS_NAME': (str, 'PMS', ''), 61 'PMS_PORT': (int, 'PMS', 32400), 62 'PMS_TOKEN': (str, 'PMS', ''), 63 'PMS_SSL': (int, 'PMS', 0), 64 'PMS_URL': (str, 'PMS', ''), 65 'PMS_URL_OVERRIDE': (str, 'PMS', ''), 66 'PMS_URL_MANUAL': (int, 'PMS', 0), 67 'PMS_USE_BIF': (int, 'PMS', 0), 68 'PMS_UUID': (str, 'PMS', ''), 69 'PMS_TIMEOUT': (int, 'Advanced', 15), 70 'PMS_PLEXPASS': (int, 'PMS', 0), 71 'PMS_PLATFORM': (str, 'PMS', ''), 72 'PMS_VERSION': (str, 'PMS', ''), 73 'PMS_UPDATE_CHANNEL': (str, 'PMS', 'plex'), 74 'PMS_UPDATE_DISTRO': (str, 'PMS', ''), 75 'PMS_UPDATE_DISTRO_BUILD': (str, 'PMS', ''), 76 'PMS_UPDATE_CHECK_INTERVAL': (int, 'Advanced', 24), 77 'PMS_WEB_URL': (str, 'PMS', 'https://app.plex.tv/desktop'), 78 'TIME_FORMAT': (str, 'General', 'HH:mm'), 79 'ANON_REDIRECT': (str, 'General', ''), 80 'ANON_REDIRECT_DYNAMIC': (int, 'General', 1), 81 'API_ENABLED': (int, 'General', 1), 82 'API_KEY': (str, 'General', ''), 83 'API_SQL': (int, 'General', 0), 84 'BUFFER_THRESHOLD': (int, 'Monitoring', 10), 85 'BUFFER_WAIT': (int, 'Monitoring', 900), 86 'BACKUP_DAYS': (int, 'General', 3), 87 'BACKUP_DIR': (str, 'General', ''), 88 'BACKUP_INTERVAL': (int, 'General', 6), 89 'CACHE_DIR': (str, 'General', ''), 90 'CACHE_IMAGES': (int, 'General', 1), 91 'CACHE_SIZEMB': (int, 'Advanced', 32), 92 'CHECK_DOCKER_MOUNT': (int, 'Advanced', 1), 93 'CHECK_GITHUB': (int, 'General', 0), 94 'CHECK_GITHUB_INTERVAL': (int, 'General', 360), 95 'CHECK_GITHUB_ON_STARTUP': (int, 'General', 1), 96 'CHECK_GITHUB_CACHE_SECONDS': (int, 'Advanced', 3600), 97 'CLEANUP_FILES': (int, 'General', 0), 98 'CLOUDINARY_CLOUD_NAME': (str, 'Cloudinary', ''), 99 'CLOUDINARY_API_KEY': (str, 'Cloudinary', ''), 100 'CLOUDINARY_API_SECRET': (str, 'Cloudinary', ''), 101 'CONFIG_VERSION': (int, 'Advanced', 0), 102 'DO_NOT_OVERRIDE_GIT_BRANCH': (int, 'General', 0), 103 'ENABLE_HTTPS': (int, 'General', 0), 104 'EXPORT_DIR': (str, 'General', ''), 105 'EXPORT_THREADS': (int, 'Advanced', 8), 106 'FIRST_RUN_COMPLETE': (int, 'General', 0), 107 'FREEZE_DB': (int, 'General', 0), 108 'GET_FILE_SIZES': (int, 'General', 0), 109 'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}), 110 'GIT_BRANCH': (str, 'General', 'master'), 111 'GIT_PATH': (str, 'General', ''), 112 'GIT_REMOTE': (str, 'General', 'origin'), 113 'GIT_TOKEN': (str, 'General', ''), 114 'GIT_USER': (str, 'General', 'Tautulli'), 115 'GIT_REPO': (str, 'General', 'Tautulli'), 116 'GROUP_HISTORY_TABLES': (int, 'General', 1), 117 'HISTORY_TABLE_ACTIVITY': (int, 'General', 1), 118 'HOME_SECTIONS': (list, 'General', ['current_activity', 'watch_stats', 'library_stats', 'recently_added']), 119 'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']), 120 'HOME_STATS_CARDS': (list, 'General', ['top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 121 'popular_music', 'last_watched', 'top_libraries', 'top_users', 'top_platforms', 'most_concurrent']), 122 'HOME_REFRESH_INTERVAL': (int, 'General', 10), 123 'HTTPS_CREATE_CERT': (int, 'General', 1), 124 'HTTPS_CERT': (str, 'General', ''), 125 'HTTPS_CERT_CHAIN': (str, 'General', ''), 126 'HTTPS_KEY': (str, 'General', ''), 127 'HTTPS_DOMAIN': (str, 'General', 'localhost'), 128 'HTTPS_IP': (str, 'General', '127.0.0.1'), 129 'HTTP_BASIC_AUTH': (int, 'General', 0), 130 'HTTP_ENVIRONMENT': (str, 'General', 'production'), 131 'HTTP_HASH_PASSWORD': (int, 'General', 1), 132 'HTTP_HASHED_PASSWORD': (int, 'General', 1), 133 'HTTP_HOST': (str, 'General', '0.0.0.0'), 134 'HTTP_PASSWORD': (str, 'General', ''), 135 'HTTP_PORT': (int, 'General', 8181), 136 'HTTP_PROXY': (int, 'General', 0), 137 'HTTP_ROOT': (str, 'General', ''), 138 'HTTP_USERNAME': (str, 'General', ''), 139 'HTTP_PLEX_ADMIN': (int, 'General', 0), 140 'HTTP_BASE_URL': (str, 'General', ''), 141 'HTTP_RATE_LIMIT_ATTEMPTS': (int, 'General', 10), 142 'HTTP_RATE_LIMIT_ATTEMPTS_INTERVAL': (int, 'General', 300), 143 'HTTP_RATE_LIMIT_LOCKOUT_TIME': (int, 'General', 300), 144 'HTTP_THREAD_POOL': (int, 'General', 10), 145 'INTERFACE': (str, 'General', 'default'), 146 'IMGUR_CLIENT_ID': (str, 'Monitoring', ''), 147 'JOURNAL_MODE': (str, 'Advanced', 'WAL'), 148 'LAUNCH_BROWSER': (int, 'General', 1), 149 'LAUNCH_STARTUP': (int, 'General', 1), 150 'LOG_BLACKLIST': (int, 'General', 1), 151 'LOG_DIR': (str, 'General', ''), 152 'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120), 153 'METADATA_CACHE_SECONDS': (int, 'Advanced', 1800), 154 'MOVIE_WATCHED_PERCENT': (int, 'Monitoring', 85), 155 'MUSIC_WATCHED_PERCENT': (int, 'Monitoring', 85), 156 'MUSICBRAINZ_LOOKUP': (int, 'General', 0), 157 'MONITOR_PMS_UPDATES': (int, 'Monitoring', 0), 158 'MONITORING_INTERVAL': (int, 'Monitoring', 60), 159 'NEWSLETTER_AUTH': (int, 'Newsletter', 0), 160 'NEWSLETTER_PASSWORD': (str, 'Newsletter', ''), 161 'NEWSLETTER_CUSTOM_DIR': (str, 'Newsletter', ''), 162 'NEWSLETTER_INLINE_STYLES': (int, 'Newsletter', 1), 163 'NEWSLETTER_TEMPLATES': (str, 'Newsletter', 'newsletters'), 164 'NEWSLETTER_DIR': (str, 'Newsletter', ''), 165 'NEWSLETTER_SELF_HOSTED': (int, 'Newsletter', 0), 166 'NOTIFICATION_THREADS': (int, 'Advanced', 2), 167 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 168 'NOTIFY_CONTINUED_SESSION_THRESHOLD': (int, 'Monitoring', 15), 169 'NOTIFY_GROUP_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 1), 170 'NOTIFY_GROUP_RECENTLY_ADDED_PARENT': (int, 'Monitoring', 1), 171 'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0), 172 'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 300), 173 'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0), 174 'NOTIFY_RECENTLY_ADDED_UPGRADE': (int, 'Monitoring', 0), 175 'NOTIFY_REMOTE_ACCESS_THRESHOLD': (int, 'Monitoring', 60), 176 'NOTIFY_CONCURRENT_BY_IP': (int, 'Monitoring', 0), 177 'NOTIFY_CONCURRENT_THRESHOLD': (int, 'Monitoring', 2), 178 'NOTIFY_NEW_DEVICE_INITIAL_ONLY': (int, 'Monitoring', 1), 179 'NOTIFY_SERVER_CONNECTION_THRESHOLD': (int, 'Monitoring', 60), 180 'NOTIFY_SERVER_UPDATE_REPEAT': (int, 'Monitoring', 0), 181 'NOTIFY_PLEXPY_UPDATE_REPEAT': (int, 'Monitoring', 0), 182 'NOTIFY_TEXT_EVAL': (int, 'Advanced', 0), 183 'PLEXPY_AUTO_UPDATE': (int, 'General', 0), 184 'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12), 185 'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1), 186 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), 187 'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1), 188 'SESSION_DB_WRITE_ATTEMPTS': (int, 'Advanced', 5), 189 'SHOW_ADVANCED_SETTINGS': (int, 'General', 0), 190 'SYNCHRONOUS_MODE': (str, 'Advanced', 'NORMAL'), 191 'THEMOVIEDB_APIKEY': (str, 'General', 'e9a6655bae34bf694a0f3e33338dc28e'), 192 'THEMOVIEDB_LOOKUP': (int, 'General', 0), 193 'TVMAZE_LOOKUP': (int, 'General', 0), 194 'TV_WATCHED_PERCENT': (int, 'Monitoring', 85), 195 'UPDATE_DB_INTERVAL': (int, 'General', 24), 196 'UPDATE_SHOW_CHANGELOG': (int, 'General', 1), 197 'UPGRADE_FLAG': (int, 'Advanced', 0), 198 'VERBOSE_LOGS': (int, 'Advanced', 1), 199 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), 200 'WEBSOCKET_MONITOR_PING_PONG': (int, 'Advanced', 0), 201 'WEBSOCKET_CONNECTION_ATTEMPTS': (int, 'Advanced', 5), 202 'WEBSOCKET_CONNECTION_TIMEOUT': (int, 'Advanced', 5), 203 'WEEK_START_MONDAY': (int, 'General', 0), 204 'JWT_SECRET': (str, 'Advanced', ''), 205 'JWT_UPDATE_SECRET': (bool_int, 'Advanced', 0), 206 'SYSTEM_ANALYTICS': (int, 'Advanced', 1), 207 'SYS_TRAY_ICON': (int, 'General', 1), 208} 209 210_BLACKLIST_KEYS = ['_APITOKEN', '_TOKEN', '_KEY', '_SECRET', '_PASSWORD', '_APIKEY', '_ID', '_HOOK'] 211_WHITELIST_KEYS = ['HTTPS_KEY'] 212 213_DO_NOT_IMPORT_KEYS = [ 214 'FIRST_RUN_COMPLETE', 'GET_FILE_SIZES_HOLD', 'GIT_PATH', 'PMS_LOGS_FOLDER', 215 'BACKUP_DIR', 'CACHE_DIR', 'EXPORT_DIR', 'LOG_DIR', 'NEWSLETTER_DIR', 'NEWSLETTER_CUSTOM_DIR', 216 'HTTP_HOST', 'HTTP_PORT', 'HTTP_ROOT', 217 'HTTP_USERNAME', 'HTTP_PASSWORD', 'HTTP_HASH_PASSWORD', 'HTTP_HASHED_PASSWORD', 218 'ENABLE_HTTPS', 'HTTPS_CREATE_CERT', 'HTTPS_CERT', 'HTTPS_CERT_CHAIN', 'HTTPS_KEY' 219] 220_DO_NOT_IMPORT_KEYS_DOCKER = [ 221 'PLEXPY_AUTO_UPDATE', 'GIT_REMOTE', 'GIT_BRANCH' 222] 223 224IS_IMPORTING = False 225IMPORT_THREAD = None 226 227 228def set_is_importing(value): 229 global IS_IMPORTING 230 IS_IMPORTING = value 231 232 233def set_import_thread(config=None, backup=False): 234 global IMPORT_THREAD 235 if config: 236 if IMPORT_THREAD: 237 return 238 IMPORT_THREAD = threading.Thread(target=import_tautulli_config, 239 kwargs={'config': config, 'backup': backup}) 240 else: 241 IMPORT_THREAD = None 242 243 244def import_tautulli_config(config=None, backup=False): 245 if IS_IMPORTING: 246 logger.warn("Tautulli Config :: Another Tautulli config is currently being imported. " 247 "Please wait until it is complete before importing another config.") 248 return False 249 250 if backup: 251 # Make a backup of the current config first 252 logger.info("Tautulli Config :: Creating a config backup before importing.") 253 if not make_backup(): 254 logger.error("Tautulli Config :: Failed to import Tautulli config: failed to create config backup") 255 return False 256 257 # Create a new Config object with the imported config file 258 try: 259 imported_config = Config(config, is_import=True) 260 except: 261 logger.error("Tautulli Config :: Failed to import Tautulli config: error reading imported config file") 262 return False 263 264 logger.info("Tautulli Config :: Importing Tautulli config '%s'...", config) 265 set_is_importing(True) 266 267 # Remove keys that should not be imported 268 for key in _DO_NOT_IMPORT_KEYS: 269 delattr(imported_config, key) 270 if plexpy.DOCKER or plexpy.SNAP: 271 for key in _DO_NOT_IMPORT_KEYS_DOCKER: 272 delattr(imported_config, key) 273 274 # Merge the imported config file into the current config file 275 plexpy.CONFIG._config.merge(imported_config._config) 276 plexpy.CONFIG.write() 277 278 logger.info("Tautulli Config :: Tautulli config import complete.") 279 set_import_thread(None) 280 set_is_importing(False) 281 282 # Restart to apply changes 283 plexpy.SIGNAL = 'restart' 284 285 286def make_backup(cleanup=False, scheduler=False): 287 """ Makes a backup of config file, removes all but the last 5 backups """ 288 289 if scheduler: 290 backup_file = 'config.backup-{}.sched.ini'.format(helpers.now()) 291 else: 292 backup_file = 'config.backup-{}.ini'.format(helpers.now()) 293 backup_folder = plexpy.CONFIG.BACKUP_DIR 294 backup_file_fp = os.path.join(backup_folder, backup_file) 295 296 # In case the user has deleted it manually 297 if not os.path.exists(backup_folder): 298 os.makedirs(backup_folder) 299 300 plexpy.CONFIG.write() 301 shutil.copyfile(plexpy.CONFIG_FILE, backup_file_fp) 302 303 if cleanup: 304 now = time.time() 305 # Delete all scheduled backup older than BACKUP_DAYS. 306 for root, dirs, files in os.walk(backup_folder): 307 ini_files = [os.path.join(root, f) for f in files if f.endswith('.sched.ini')] 308 for file_ in ini_files: 309 if os.stat(file_).st_mtime < now - plexpy.CONFIG.BACKUP_DAYS * 86400: 310 try: 311 os.remove(file_) 312 except OSError as e: 313 logger.error("Tautulli Config :: Failed to delete %s from the backup folder: %s" % (file_, e)) 314 315 if backup_file in os.listdir(backup_folder): 316 logger.debug("Tautulli Config :: Successfully backed up %s to %s" % (plexpy.CONFIG_FILE, backup_file)) 317 return True 318 else: 319 logger.error("Tautulli Config :: Failed to backup %s to %s" % (plexpy.CONFIG_FILE, backup_file)) 320 return False 321 322 323# pylint:disable=R0902 324# it might be nice to refactor for fewer instance variables 325class Config(object): 326 """ Wraps access to particular values in a config file """ 327 328 def __init__(self, config_file, is_import=False): 329 """ Initialize the config with values from a file """ 330 self._config_file = config_file 331 try: 332 self._config = ConfigObj(self._config_file, encoding='utf-8') 333 except ParseError as e: 334 logger.error("Tautulli Config :: Error reading configuration file: %s", e) 335 raise 336 337 for key in _CONFIG_DEFINITIONS: 338 self.check_setting(key) 339 if not is_import: 340 self._upgrade() 341 self._blacklist() 342 343 def _blacklist(self): 344 """ Add tokens and passwords to blacklisted words in logger """ 345 blacklist = set() 346 347 for key, subkeys in self._config.items(): 348 for subkey, value in subkeys.items(): 349 if isinstance(value, str) and len(value.strip()) > 5 and \ 350 subkey.upper() not in _WHITELIST_KEYS and any(bk in subkey.upper() for bk in _BLACKLIST_KEYS): 351 blacklist.add(value.strip()) 352 353 logger._BLACKLIST_WORDS.update(blacklist) 354 355 def _define(self, name): 356 key = name.upper() 357 ini_key = name.lower() 358 definition = _CONFIG_DEFINITIONS[key] 359 if len(definition) == 3: 360 definition_type, section, default = definition 361 else: 362 definition_type, section, _, default = definition 363 return key, definition_type, section, ini_key, default 364 365 def check_section(self, section): 366 """ Check if INI section exists, if not create it """ 367 if section not in self._config: 368 self._config[section] = {} 369 return True 370 else: 371 return False 372 373 def check_setting(self, key): 374 """ Cast any value in the config to the right type or use the default """ 375 key, definition_type, section, ini_key, default = self._define(key) 376 self.check_section(section) 377 try: 378 my_val = definition_type(self._config[section][ini_key]) 379 except Exception: 380 my_val = definition_type(default) 381 self._config[section][ini_key] = my_val 382 return my_val 383 384 def write(self): 385 """ Make a copy of the stored config and write it to the configured file """ 386 new_config = ConfigObj(encoding="UTF-8") 387 new_config.filename = self._config_file 388 389 # first copy over everything from the old config, even if it is not 390 # correctly defined to keep from losing data 391 for key, subkeys in self._config.items(): 392 if key not in new_config: 393 new_config[key] = {} 394 for subkey, value in subkeys.items(): 395 new_config[key][subkey] = value 396 397 # next make sure that everything we expect to have defined is so 398 for key in _CONFIG_DEFINITIONS: 399 key, definition_type, section, ini_key, default = self._define(key) 400 self.check_setting(key) 401 if section not in new_config: 402 new_config[section] = {} 403 new_config[section][ini_key] = self._config[section][ini_key] 404 405 # Write it to file 406 logger.info("Tautulli Config :: Writing configuration to file") 407 408 try: 409 new_config.write() 410 except IOError as e: 411 logger.error("Tautulli Config :: Error writing configuration file: %s", e) 412 413 self._blacklist() 414 415 def __getattr__(self, name): 416 """ 417 Returns something from the ini unless it is a real property 418 of the configuration object or is not all caps. 419 """ 420 if not re.match(r'[A-Z_]+$', name): 421 return super(Config, self).__getattr__(name) 422 else: 423 return self.check_setting(name) 424 425 def __setattr__(self, name, value): 426 """ 427 Maps all-caps properties to ini values unless they exist on the 428 configuration object. 429 """ 430 if not re.match(r'[A-Z_]+$', name): 431 super(Config, self).__setattr__(name, value) 432 return value 433 else: 434 key, definition_type, section, ini_key, default = self._define(name) 435 self._config[section][ini_key] = definition_type(value) 436 return self._config[section][ini_key] 437 438 def __delattr__(self, name): 439 """ 440 Deletes a key from the configuration object. 441 """ 442 if not re.match(r'[A-Z_]+$', name): 443 return super(Config, self).__delattr__(name) 444 else: 445 key, definition_type, section, ini_key, default = self._define(name) 446 del self._config[section][ini_key] 447 448 def process_kwargs(self, kwargs): 449 """ 450 Given a big bunch of key value pairs, apply them to the ini. 451 """ 452 for name, value in kwargs.items(): 453 key, definition_type, section, ini_key, default = self._define(name) 454 self._config[section][ini_key] = definition_type(value) 455 456 def _upgrade(self): 457 """ 458 Upgrades config file from previous verisions and bumps up config version 459 """ 460 if self.CONFIG_VERSION == 0: 461 self.CONFIG_VERSION = 1 462 463 if self.CONFIG_VERSION == 1: 464 # Change home_stats_cards to list 465 if self.HOME_STATS_CARDS: 466 home_stats_cards = ''.join(self.HOME_STATS_CARDS).split(', ') 467 if 'watch_statistics' in home_stats_cards: 468 home_stats_cards.remove('watch_statistics') 469 self.HOME_STATS_CARDS = home_stats_cards 470 # Change home_library_cards to list 471 if self.HOME_LIBRARY_CARDS: 472 home_library_cards = ''.join(self.HOME_LIBRARY_CARDS).split(', ') 473 if 'library_statistics' in home_library_cards: 474 home_library_cards.remove('library_statistics') 475 self.HOME_LIBRARY_CARDS = home_library_cards 476 477 self.CONFIG_VERSION = 2 478 479 if self.CONFIG_VERSION == 2: 480 self.CONFIG_VERSION = 3 481 482 if self.CONFIG_VERSION == 3: 483 if self.HTTP_ROOT == '/': 484 self.HTTP_ROOT = '' 485 486 self.CONFIG_VERSION = 4 487 488 if self.CONFIG_VERSION == 4: 489 if not len(self.HOME_STATS_CARDS) and 'watch_stats' in self.HOME_SECTIONS: 490 home_sections = self.HOME_SECTIONS 491 home_sections.remove('watch_stats') 492 self.HOME_SECTIONS = home_sections 493 if not len(self.HOME_LIBRARY_CARDS) and 'library_stats' in self.HOME_SECTIONS: 494 home_sections = self.HOME_SECTIONS 495 home_sections.remove('library_stats') 496 self.HOME_SECTIONS = home_sections 497 498 self.CONFIG_VERSION = 5 499 500 if self.CONFIG_VERSION == 5: 501 self.MONITOR_PMS_UPDATES = 0 502 503 self.CONFIG_VERSION = 6 504 505 if self.CONFIG_VERSION == 6: 506 if self.GIT_USER.lower() == 'drzoidberg33': 507 self.GIT_USER = 'JonnyWong16' 508 509 self.CONFIG_VERSION = 7 510 511 if self.CONFIG_VERSION == 7: 512 self.CONFIG_VERSION = 8 513 514 if self.CONFIG_VERSION == 8: 515 self.CONFIG_VERSION = 9 516 517 if self.CONFIG_VERSION == 9: 518 if self.PMS_UPDATE_CHANNEL == 'plexpass': 519 self.PMS_UPDATE_CHANNEL = 'beta' 520 521 self.CONFIG_VERSION = 10 522 523 if self.CONFIG_VERSION == 10: 524 self.GIT_USER = 'Tautulli' 525 self.GIT_REPO = 'Tautulli' 526 527 self.CONFIG_VERSION = 11 528 529 if self.CONFIG_VERSION == 11: 530 self.ANON_REDIRECT = self.ANON_REDIRECT.replace('http://www.nullrefer.com/?', 531 'https://www.nullrefer.com/?') 532 self.CONFIG_VERSION = 12 533 534 if self.CONFIG_VERSION == 12: 535 self.BUFFER_THRESHOLD = max(self.BUFFER_THRESHOLD, 10) 536 537 self.CONFIG_VERSION = 13 538 539 if self.CONFIG_VERSION == 13: 540 self.CONFIG_VERSION = 14 541 542 if self.CONFIG_VERSION == 14: 543 if plexpy.DOCKER: 544 self.PLEXPY_AUTO_UPDATE = 0 545 546 self.CONFIG_VERSION = 15 547 548 if self.CONFIG_VERSION == 15: 549 if self.HTTP_ROOT and self.HTTP_ROOT != '/': 550 self.JWT_UPDATE_SECRET = True 551 552 self.CONFIG_VERSION = 16 553 554 if self.CONFIG_VERSION == 16: 555 if plexpy.SNAP: 556 self.PLEXPY_AUTO_UPDATE = 0 557 558 self.CONFIG_VERSION = 17 559 560 if self.CONFIG_VERSION == 17: 561 home_stats_cards = self.HOME_STATS_CARDS 562 if 'top_users' in home_stats_cards: 563 top_users_index = home_stats_cards.index('top_users') 564 home_stats_cards.insert(top_users_index, 'top_libraries') 565 else: 566 home_stats_cards.append('top_libraries') 567 self.HOME_STATS_CARDS = home_stats_cards 568 569 self.CONFIG_VERSION = 18 570 571 if self.CONFIG_VERSION == 18: 572 self.CHECK_GITHUB_INTERVAL = ( 573 int(self.CHECK_GITHUB_INTERVAL // 60) 574 + (self.CHECK_GITHUB_INTERVAL % 60 > 0) 575 ) 576 577 self.CONFIG_VERSION = 19 578 579 if self.CONFIG_VERSION == 19: 580 if self.HTTP_PASSWORD and not self.HTTP_HASHED_PASSWORD: 581 self.HTTP_PASSWORD = make_hash(self.HTTP_PASSWORD) 582 self.HTTP_HASH_PASSWORD = 1 583 self.HTTP_HASHED_PASSWORD = 1 584 585 self.CONFIG_VERSION = 20 586