Refactor shutdown / restart code

- centralize and simplify code
- eliminate code duplication
- hide unnecessary details from rest of code by abstraction
- standardize exit codes
- shorten shutdown / restart time
- improve console messaging
This commit is contained in:
JayZed 2024-02-17 17:07:52 -05:00
parent 65622196b7
commit 5d01d54dab
6 changed files with 87 additions and 86 deletions

View File

@ -9,7 +9,11 @@ import time
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 @@ if __name__ == '__main__':
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 @@ if __name__ == '__main__':
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)

View File

@ -6,6 +6,8 @@ import ast
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 @@ while failed_validator:
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():

View File

@ -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 @@ class Server:
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()

View File

@ -1,7 +1,6 @@
# coding=utf-8
import os
import io
import sys
import subprocess
import subliminal
@ -20,6 +19,9 @@ from utilities.backup import restore_from_backup
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 @@ if not os.path.exists(args.config_dir):
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)
stop_bazarr(EXIT_CONFIG_CREATE_ERROR)
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'))
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 @@ if not args.no_update:
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('/')

View File

@ -42,16 +42,8 @@ from app.server import webserver, app # noqa E402
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)

View File

@ -1,7 +1,6 @@
# coding=utf-8
import os
import io
import sqlite3
import shutil
import logging
@ -12,6 +11,7 @@ from glob import glob
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: