Source code for retroarcher

#!/usr/bin/env python3
"""
..
   retroarcher.py

Responsible for starting RetroArcher.
"""
# future imports
from __future__ import annotations

# standard imports
import argparse
import os
import sys
import time
from typing import Union

# local imports
import pyra
from pyra import config
from pyra import definitions
from pyra import helpers
from pyra import locales
from pyra import logger
from pyra import threads

py_name = 'pyra'

# locales
_ = locales.get_text()

# get logger
log = logger.get_logger(name=py_name)


[docs] class IntRange(object): """ Custom IntRange class for argparse. Prevents printing out large list of possible choices for integer ranges. Parameters ---------- stop : int Range maximum value. start : int, default = 0 Range minimum value. Methods ------- __call__: Validate that value is within accepted range. Examples -------- >>> IntRange(0, 10) <retroarcher.IntRange object at 0x...> """ def __init__(self, stop: int, start: int = 0,): """ Initialize the IntRange class object. If stop is less than start, the values will be corrected automatically. """ if stop < start: stop, start = start, stop self.start, self.stop = start, stop def __call__(self, value: Union[int, str]) -> int: """ Validate that value is within accepted range. Validate the provided value is within the range of the `IntRange()` object. Parameters ---------- value : Union[int, str] The value to validate. Returns ------- int The original value. Raises ------ argparse.ArgumentTypeError If provided value is outside the accepted range. Examples -------- >>> IntRange(0, 10).__call__(5) 5 >>> IntRange(0, 10).__call__(15) Traceback (most recent call last): ... argparse.ArgumentTypeError: Value outside of range: (0, 10) """ value = int(value) if value < self.start or value >= self.stop: raise argparse.ArgumentTypeError(f'Value outside of range: ({self.start}, {self.stop})') return value
[docs] def main(): """ Application entry point. Parses arguments and initializes the application. Examples -------- >>> if __name__ == "__main__": ... main() """ # Fixed paths to RetroArcher if definitions.Modes.FROZEN: # only when using the pyinstaller build if definitions.Modes.SPLASH: import pyi_splash # module cannot be installed outside of pyinstaller builds pyi_splash.update_text("Attempting to start RetroArcher") # Set up and gather command line arguments # todo... fix translations for '--help' command parser = argparse.ArgumentParser(description=_('RetroArcher is a Python based game streaming server.\n' 'Arguments supplied here are meant to be temporary.')) parser.add_argument('--config', help=_('Specify a config file to use')) parser.add_argument('--debug', action='store_true', help=_('Use debug logging level')) parser.add_argument('--dev', action='store_true', help=_('Start RetroArcher in the development environment')) parser.add_argument('--docker_healthcheck', action='store_true', help=_('Health check the container and exit')) parser.add_argument('--nolaunch', action='store_true', help=_('Do not open RetroArcher in browser')) parser.add_argument('-p', '--port', default=9696, type=IntRange(21, 65535), help=_('Force RetroArcher to run on a specified port, default=9696') ) parser.add_argument('-q', '--quiet', action='store_true', help=_('Turn off console logging')) parser.add_argument('-v', '--version', action='store_true', help=_('Print the version details and exit')) args = parser.parse_args() if args.docker_healthcheck: status = helpers.docker_healthcheck() exit_code = int(not status) sys.exit(exit_code) if args.version: print('version arg is not yet implemented') sys.exit() if args.config: config_file = args.config else: config_file = os.path.join(definitions.Paths.DATA_DIR, definitions.Files.CONFIG) if args.debug: pyra.DEBUG = True if args.dev: pyra.DEV = True if args.quiet: pyra.QUIET = True # initialize retroarcher # logging should not occur until after initialize # any submodules that require translations need to be imported after config is initialize pyra.initialize(config_file=config_file) if args.config: log.info(msg=f"RetroArcher is using custom config file: {config_file}.") if args.debug: log.info(msg="RetroArcher will log debug messages.") if args.dev: log.info(msg="RetroArcher is running in the dev environment.") if args.quiet: log.info(msg="RetroArcher is running in quiet mode. Nothing will be printed to console.") if args.port: config.CONFIG['Network']['HTTP_PORT'] = args.port config.CONFIG.write() if config.CONFIG['General']['SYSTEM_TRAY']: from pyra import tray_icon # submodule requires translations so importing after initialization # also do not import if not required by config options tray_icon.tray_run_threaded() # start the webapp if definitions.Modes.SPLASH: # pyinstaller build only, not darwin platforms pyi_splash.update_text("Starting the webapp") time.sleep(3) # show splash screen for a min of 3 seconds pyi_splash.close() # close the splash screen from pyra import webapp # import at use due to translations threads.run_in_thread(target=webapp.start_webapp, name='Flask', daemon=True).start() # this should be after starting flask app if config.CONFIG['General']['LAUNCH_BROWSER'] and not args.nolaunch: url = f"http://127.0.0.1:{config.CONFIG['Network']['HTTP_PORT']}" helpers.open_url_in_browser(url=url) wait() # wait for signal
[docs] def wait(): """ Wait for signal. Endlessly loop while `pyra.SIGNAL = None`. If `pyra.SIGNAL` is changed to `shutdown` or `restart` `pyra.stop()` will be executed. If KeyboardInterrupt signal is detected `pyra.stop()` will be executed. Examples -------- >>> wait() """ from pyra import hardware # submodule requires translations so importing after initialization log.info("RetroArcher is ready!") while True: # wait endlessly for a signal if not pyra.SIGNAL: hardware.update() # update dashboard resource values try: time.sleep(1) except KeyboardInterrupt: pyra.SIGNAL = 'shutdown' else: log.info(f'Received signal: {pyra.SIGNAL}') if pyra.SIGNAL == 'shutdown': pyra.stop() elif pyra.SIGNAL == 'restart': pyra.stop(restart=True) else: log.error('Unknown signal. Shutting down...') pyra.stop() break
if __name__ == "__main__": main()