diff --git a/bazarr.py b/bazarr.py index f5fc76bb8..ef4d0bea0 100644 --- a/bazarr.py +++ b/bazarr.py @@ -9,7 +9,11 @@ import atexit from bazarr.app.get_args import args +from bazarr.literals import * +def exit_program(status_code): + print(f'Bazarr exited with status code {status_code}.') + raise SystemExit(status_code) def check_python_version(): python_version = platform.python_version_tuple() @@ -19,7 +23,7 @@ def check_python_version(): if int(python_version[0]) < minimum_py3_tuple[0]: print("Python " + minimum_py3_str + " or greater required. " "Current version is " + platform.python_version() + ". Please upgrade Python.") - sys.exit(1) + exit_program(EXIT_PYTHON_UPGRADE_NEEDED) elif int(python_version[0]) == 3 and int(python_version[1]) > 11: print("Python version greater than 3.11.x is unsupported. Current version is " + platform.python_version() + ". Keep in mind that even if it works, you're on your own.") @@ -27,7 +31,7 @@ def check_python_version(): (int(python_version[0]) != minimum_py3_tuple[0]): print("Python " + minimum_py3_str + " or greater required. " "Current version is " + platform.python_version() + ". Please upgrade Python.") - sys.exit(1) + exit_program(EXIT_PYTHON_UPGRADE_NEEDED) def get_python_path(): @@ -73,31 +77,57 @@ def start_bazarr(): ep = subprocess.Popen(script, stdout=None, stderr=None, stdin=subprocess.DEVNULL) atexit.register(end_child_process, ep=ep) signal.signal(signal.SIGTERM, lambda signal_no, frame: end_child_process(ep)) + print(f"Bazarr starting child process with PID {ep.pid}...") + return ep + + +def get_stop_status_code(input_file): + try: + with open(input_file,'r') as file: + # read status code from file, if it exists + line = file.readline() + try: + status_code = int(line) + except (ValueError, TypeError): + status_code = EXIT_NORMAL + file.close() + except: + status_code = EXIT_NORMAL + return status_code def check_status(): + global child_process if os.path.exists(stopfile): + status_code = get_stop_status_code(stopfile) try: + print(f"Deleting stop file...") os.remove(stopfile) - except Exception: + except Exception as e: print('Unable to delete stop file.') finally: - print('Bazarr exited.') - sys.exit(0) + print(f"Terminating child process with PID {child_process.pid}") + child_process.terminate() + exit_program(status_code) if os.path.exists(restartfile): try: + print(f"Deleting restart file...") os.remove(restartfile) except Exception: print('Unable to delete restart file.') - else: - print("Bazarr is restarting...") - start_bazarr() + finally: + print(f"Terminating child process with PID {child_process.pid}") + child_process.terminate() + print(f"Bazarr is restarting...") + child_process = start_bazarr() if __name__ == '__main__': - restartfile = os.path.join(args.config_dir, 'bazarr.restart') - stopfile = os.path.join(args.config_dir, 'bazarr.stop') + restartfile = os.path.join(args.config_dir, FILE_RESTART) + stopfile = os.path.join(args.config_dir, FILE_STOP) + os.environ[ENV_STOPFILE] = stopfile + os.environ[ENV_RESTARTFILE] = restartfile # Cleanup leftover files try: @@ -111,10 +141,9 @@ def check_status(): pass # Initial start of main bazarr process - print("Bazarr starting...") - start_bazarr() + child_process = start_bazarr() - # Keep the script running forever until stop is requested through term or keyboard interrupt + # Keep the script running forever until stop is requested through term, special files or keyboard interrupt while True: check_status() try: @@ -124,5 +153,5 @@ def check_status(): os.wait() time.sleep(1) except (KeyboardInterrupt, SystemExit, ChildProcessError): - print('Bazarr exited.') - sys.exit(0) + print(f'Bazarr exited main script file via keyboard interrupt.') + exit_program(EXIT_NORMAL) diff --git a/bazarr/app/config.py b/bazarr/app/config.py index 955270640..8895347c7 100644 --- a/bazarr/app/config.py +++ b/bazarr/app/config.py @@ -6,6 +6,8 @@ import logging from urllib.parse import quote_plus +from literals import EXIT_VALIDATION_ERROR +from utilities.central import stop_bazarr from subliminal.cache import region from dynaconf import Dynaconf, Validator as OriginalValidator from dynaconf.loaders.yaml_loader import write @@ -393,8 +395,9 @@ def convert_ini_to_yaml(config_file): settings[current_validator_details.names[0]] = current_validator_details.default else: logging.critical(f"Value for {current_validator_details.names[0]} doesn't pass validation and there's no " - f"default value. This issue must be reported. Bazarr won't works until it's been fixed.") - os._exit(0) + f"default value. This issue must be reported to and fixed by the development team. " + f"Bazarr won't work until it's been fixed.") + stop_bazarr(EXIT_VALIDATION_ERROR) def write_config(): diff --git a/bazarr/app/server.py b/bazarr/app/server.py index 52d711fbe..f1ee789d9 100644 --- a/bazarr/app/server.py +++ b/bazarr/app/server.py @@ -2,9 +2,9 @@ import warnings import logging -import os -import io import errno +from literals import EXIT_NORMAL +from utilities.central import restart_bazarr, stop_bazarr from waitress.server import create_server from time import sleep @@ -72,31 +72,19 @@ def start(self): except Exception: pass + def close_all(self): + print(f"Closing database...") + close_database() + print(f"Closing webserver...") + self.server.close() + def shutdown(self): - try: - stop_file = io.open(os.path.join(args.config_dir, "bazarr.stop"), "w", encoding='UTF-8') - except Exception as e: - logging.error(f'BAZARR Cannot create stop file: {repr(e)}') - else: - logging.info('Bazarr is being shutdown...') - stop_file.write(str('')) - stop_file.close() - close_database() - self.server.close() - os._exit(0) + self.close_all() + stop_bazarr(EXIT_NORMAL, False) def restart(self): - try: - restart_file = io.open(os.path.join(args.config_dir, "bazarr.restart"), "w", encoding='UTF-8') - except Exception as e: - logging.error(f'BAZARR Cannot create restart file: {repr(e)}') - else: - logging.info('Bazarr is being restarted...') - restart_file.write(str('')) - restart_file.close() - close_database() - self.server.close() - os._exit(0) + self.close_all() + restart_bazarr() webserver = Server() diff --git a/bazarr/init.py b/bazarr/init.py index 0a2496df0..5a2c2b346 100644 --- a/bazarr/init.py +++ b/bazarr/init.py @@ -1,7 +1,6 @@ # coding=utf-8 import os -import io import sys import subprocess import subliminal @@ -20,6 +19,9 @@ from app.database import init_db +from literals import * +from utilities.central import make_bazarr_dir, restart_bazarr, stop_bazarr + # set start time global variable as epoch global startTime startTime = time.time() @@ -37,20 +39,15 @@ os.mkdir(os.path.join(args.config_dir)) except OSError: print("BAZARR The configuration directory doesn't exist and Bazarr cannot create it (permission issue?).") - exit(2) - -if not os.path.exists(os.path.join(args.config_dir, 'config')): - os.mkdir(os.path.join(args.config_dir, 'config')) -if not os.path.exists(os.path.join(args.config_dir, 'db')): - os.mkdir(os.path.join(args.config_dir, 'db')) -if not os.path.exists(os.path.join(args.config_dir, 'log')): - os.mkdir(os.path.join(args.config_dir, 'log')) -if not os.path.exists(os.path.join(args.config_dir, 'cache')): - os.mkdir(os.path.join(args.config_dir, 'cache')) -if not os.path.exists(os.path.join(args.config_dir, 'backup')): - os.mkdir(os.path.join(args.config_dir, 'backup')) -if not os.path.exists(os.path.join(args.config_dir, 'restore')): - os.mkdir(os.path.join(args.config_dir, 'restore')) + stop_bazarr(EXIT_CONFIG_CREATE_ERROR) + +os.environ[ENV_BAZARR_ROOT_DIR] = os.path.join(args.config_dir) +make_bazarr_dir(DIR_BACKUP) +make_bazarr_dir(DIR_CACHE) +make_bazarr_dir(DIR_CONFIG) +make_bazarr_dir(DIR_DB) +make_bazarr_dir(DIR_LOG) +make_bazarr_dir(DIR_RESTORE) # set subliminal_patch hearing-impaired extension to use when naming subtitles os.environ["SZ_HI_EXTENSION"] = settings.general.hi_extension @@ -99,19 +96,20 @@ def is_virtualenv(): subprocess.check_output(pip_command, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: logging.exception(f'BAZARR requirements.txt installation result: {e.stdout}') - os._exit(1) + os._exit(EXIT_REQUIREMENTS_ERROR) else: logging.info('BAZARR requirements installed.') - try: - restart_file = io.open(os.path.join(args.config_dir, "bazarr.restart"), "w", encoding='UTF-8') - except Exception as e: - logging.error(f'BAZARR Cannot create restart file: {repr(e)}') - else: - logging.info('Bazarr is being restarted...') - restart_file.write(str('')) - restart_file.close() - os._exit(0) + restart_bazarr() + # try: + # restart_file = io.open(get_restart_file_path(), "w", encoding='UTF-8') + # except Exception as e: + # logging.error(f'BAZARR Cannot create restart file: {repr(e)}') + # else: + # logging.info('Bazarr is being restarted...') + # restart_file.write(str('')) + # restart_file.close() + # os._exit(0) # change default base_url to '' settings.general.base_url = settings.general.base_url.rstrip('/') diff --git a/bazarr/main.py b/bazarr/main.py index f73832d13..540101dde 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -42,16 +42,8 @@ from app.announcements import get_announcements_to_file # noqa E402 if args.create_db_revision: - try: - stop_file = io.open(os.path.join(args.config_dir, "bazarr.stop"), "w", encoding='UTF-8') - except Exception as e: - logging.error(f'BAZARR Cannot create stop file: {repr(e)}') - else: - create_db_revision(app) - logging.info('Bazarr is being shutdown...') - stop_file.write(str('')) - stop_file.close() - os._exit(0) + create_db_revision(app) + stop_bazarr(EXIT_NORMAL) else: migrate_db(app) diff --git a/bazarr/utilities/backup.py b/bazarr/utilities/backup.py index 8088a50a2..136a959b1 100644 --- a/bazarr/utilities/backup.py +++ b/bazarr/utilities/backup.py @@ -1,7 +1,6 @@ # coding=utf-8 import os -import io import sqlite3 import shutil import logging @@ -12,6 +11,7 @@ from app.get_args import args from app.config import settings +from utilities.central import restart_bazarr def get_backup_path(): @@ -133,16 +133,7 @@ def restore_from_backup(): logging.exception(f'Unable to delete {dest_database_path}') logging.info('Backup restored successfully. Bazarr will restart.') - - try: - restart_file = io.open(os.path.join(args.config_dir, "bazarr.restart"), "w", encoding='UTF-8') - except Exception as e: - logging.error(f'BAZARR Cannot create restart file: {repr(e)}') - else: - logging.info('Bazarr is being restarted...') - restart_file.write('') - restart_file.close() - os._exit(0) + restart_bazarr() elif os.path.isfile(restore_config_path) or os.path.isfile(restore_database_path): logging.debug('Cannot restore a partial backup. You must have both config and database.') else: