diff --git a/src/iSponsorBlockTV/helpers.py b/src/iSponsorBlockTV/helpers.py index 6b4880e..d5f2984 100644 --- a/src/iSponsorBlockTV/helpers.py +++ b/src/iSponsorBlockTV/helpers.py @@ -9,6 +9,7 @@ from appdirs import user_data_dir from . import config_setup, main, setup_wizard from .constants import config_file_blacklist_keys +from .service.service_helpers import service class Device: @@ -213,6 +214,7 @@ pyapp_group.add_command( if os.getenv("PYAPP"): cli.add_command(pyapp_group) +cli.add_command(service) def app_start(): cli(obj={}) diff --git a/src/iSponsorBlockTV/macos_install.py b/src/iSponsorBlockTV/macos_install.py deleted file mode 100644 index 2dc723e..0000000 --- a/src/iSponsorBlockTV/macos_install.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import plistlib - -from . import config_setup - -"""Not updated to V2 yet, should still work. Here be dragons""" -default_plist = { - "Label": "com.dmunozv04iSponsorBlockTV", - "RunAtLoad": True, - "StartInterval": 20, - "EnvironmentVariables": {"PYTHONUNBUFFERED": "YES"}, - "StandardErrorPath": "", # Fill later - "StandardOutPath": "", - "ProgramArguments": "", - "WorkingDirectory": "", -} - - -def create_plist(path): - plist = default_plist - plist["ProgramArguments"] = [path + "/iSponsorBlockTV-macos"] - plist["StandardErrorPath"] = path + "/iSponsorBlockTV.error.log" - plist["StandardOutPath"] = path + "/iSponsorBlockTV.out.log" - plist["WorkingDirectory"] = path - launchd_path = os.path.expanduser("~/Library/LaunchAgents/") - path_to_save = launchd_path + "com.dmunozv04.iSponsorBlockTV.plist" - - with open(path_to_save, "wb") as fp: - plistlib.dump(plist, fp) - - -def run_setup(file): - config = {} - config_setup.main(config, file, debug=False) - - -def main(): - correct_path = os.path.expanduser("~/iSponsorBlockTV") - if os.path.isfile(correct_path + "/iSponsorBlockTV-macos"): - print("Program is on the right path") - print("The launch daemon will now be installed") - create_plist(correct_path) - run_setup(correct_path + "/config.json") - print( - "Launch daemon installed. Please restart the computer to enable it or" - " use:\n launchctl load" - " ~/Library/LaunchAgents/com.dmunozv04.iSponsorBlockTV.plist" - ) - else: - if not os.path.exists(correct_path): - os.makedirs(correct_path) - print( - "Please move the program to the correct path: " - + correct_path - + "opening now on finder..." - ) - os.system("open -R " + correct_path) diff --git a/src/iSponsorBlockTV/service/__init__.py b/src/iSponsorBlockTV/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/iSponsorBlockTV/service/service_helpers.py b/src/iSponsorBlockTV/service/service_helpers.py new file mode 100644 index 0000000..454c51e --- /dev/null +++ b/src/iSponsorBlockTV/service/service_helpers.py @@ -0,0 +1,63 @@ +import os +import sys +import rich_click as click +from .service_managers import select_service_manager + +@click.group() +@click.pass_context +def service(ctx): + """Manage the program as a service (executable only)""" + ctx.ensure_object(dict) + if os.getenv("PYAPP") is None: + print("Service commands are only available in the executable version of the program") + sys.exit(1) + ctx.obj["service_manager"] = select_service_manager()(os.getenv("PYAPP")) + +@service.command() +@click.pass_context +def start(ctx): + """Start the service""" + ctx.obj["service_manager"].start() + +@service.command() +@click.pass_context +def stop(ctx): + """Stop the service""" + ctx.obj["service_manager"].stop() + +@service.command() +@click.pass_context +def restart(ctx): + """Restart the service""" + ctx.obj["service_manager"].restart() + +@service.command() +@click.pass_context +def status(ctx): + """Get the status of the service""" + ctx.obj["service_manager"].status() + +@service.command() +@click.pass_context +def install(ctx): + """Install the service""" + ctx.obj["service_manager"].install() + +@service.command() +@click.pass_context +def uninstall(ctx): + """Uninstall the service""" + ctx.obj["service_manager"].uninstall() + +@service.command() +@click.pass_context +def enable(ctx): + """Enable the service""" + ctx.obj["service_manager"].enable() + +@service.command() +@click.pass_context +def disable(ctx): + """Disable the service""" + ctx.obj["service_manager"].disable() + diff --git a/src/iSponsorBlockTV/service/service_managers.py b/src/iSponsorBlockTV/service/service_managers.py new file mode 100644 index 0000000..32d4e78 --- /dev/null +++ b/src/iSponsorBlockTV/service/service_managers.py @@ -0,0 +1,87 @@ +import os +import plistlib +import subprocess + +from appdirs import user_log_dir +from platform import system + +def select_service_manager() -> "ServiceManager": + platform = system() + if platform == "Darwin": + return Launchd + elif platform == "Linux": + return Systemd + else: + raise NotImplementedError("Unsupported platform") + +class ServiceManager(): + def __init__(self, executable_path, *args, **kwargs): + self.executable_path = executable_path + def start(self): + pass + def stop(self): + pass + def restart(self): + pass + def status(self): + pass + def install(self): + pass + def uninstall(self): + pass + def enable(self): + pass + def disable(self): + pass + + +class Launchd(ServiceManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.service_name = "com.dmunozv04.iSponsorBlockTV" + self.service_path = os.path.expanduser("~/Library/LaunchAgents/") + self.service_name + ".plist" + def start(self): + subprocess.run(["launchctl", "start", self.service_name]) + def stop(self): + subprocess.run(["launchctl", "stop", self.service_name]) + def restart(self): + subprocess.run(["launchctl", "restart", self.service_name]) + def status(self): + subprocess.run(["launchctl", "list", self.service_name]) + def install(self): + if os.path.exists(self.service_path): + print("Service already installed") + return + logs_dir = user_log_dir("iSponsorBlockTV", "dmunozv04") + # ensure the logs directory exists + os.makedirs(logs_dir, exist_ok=True) + plist = { + "Label": "com.dmunozv04.iSponsorBlockTV", + "RunAtLoad": True, + "StartInterval": 20, + "EnvironmentVariables": {"PYTHONUNBUFFERED": "YES"}, + "StandardErrorPath": logs_dir + "/iSponsorBlockTV.err", + "StandardOutPath": logs_dir + "/iSponsorBlockTV.out", + "Program": self.executable_path, + } + with open(self.service_path, "wb") as fp: + plistlib.dump(plist, fp) + print("Service installed") + self.enable() + def uninstall(self): + self.disable() + # Remove the file + try: + os.remove(self.service_path) + print("Service uninstalled") + except FileNotFoundError: + print("Service not found") + def enable(self): + subprocess.run(["launchctl", "load", self.service_path]) + def disable(self): + subprocess.run(["launchctl", "stop", self.service_name]) + subprocess.run(["launchctl", "unload", self.service_path]) + + +class Systemd(ServiceManager): + pass \ No newline at end of file