diff --git a/plextraktsync/cli.py b/plextraktsync/cli.py index e8a5372237d..07603d8d262 100644 --- a/plextraktsync/cli.py +++ b/plextraktsync/cli.py @@ -244,6 +244,14 @@ def subdl(): pass +@click.group() +def launchctl(): + """ + Installs launchctl wrapper + """ + pass + + @command() @click.option( "--pr", @@ -286,12 +294,24 @@ def imdb_import(): pass +def launchctl_available(): + import shutil + + return shutil.which("launchctl") is not None + + cli.add_command(bug_report) cli.add_command(cache) cli.add_command(clear_collections) cli.add_command(imdb_import) cli.add_command(info) cli.add_command(inspect) +if launchctl_available(): + cli.add_command(launchctl) + from .commands.launchctl import load, unload + + launchctl.add_command(load) + launchctl.add_command(unload) cli.add_command(login) cli.add_command(plex_login) if enable_self_update(): diff --git a/plextraktsync/com.github.plextraktsync.watch.plist b/plextraktsync/com.github.plextraktsync.watch.plist new file mode 100644 index 00000000000..a9ed0cdb121 --- /dev/null +++ b/plextraktsync/com.github.plextraktsync.watch.plist @@ -0,0 +1,32 @@ + + + + + Label + com.github.plextraktsync.watch + ProgramArguments + + plextraktsync + watch + + WorkingDirectory + / + LimitLoadToSessionType + Aqua + RunAtLoad + + ExitTimeOut + 0 + ProcessType + Background + + + StandardErrorPath + /tmp/plextraktsync.err + StandardOutPath + /tmp/plextraktsync.out + + diff --git a/plextraktsync/commands/launchctl.py b/plextraktsync/commands/launchctl.py new file mode 100644 index 00000000000..b39f93fa477 --- /dev/null +++ b/plextraktsync/commands/launchctl.py @@ -0,0 +1,82 @@ +import click + +from plextraktsync.decorators.cached_property import cached_property + + +class Plist: + plist_file = "com.github.plextraktsync.watch.plist" + + def load(self, plist_path: str): + from os import system + + system(f"launchctl load {plist_path}") + + def unload(self, plist_path: str): + from os import system + from os.path import exists + + # Skip if file does not exist. + if not exists(plist_path): + return + system(f"launchctl unload {plist_path}") + + def create(self, plist_path: str): + from plextraktsync.util.packaging import program_path + + with open(self.plist_default_path, encoding='utf-8') as f: + contents = "".join(f.readlines()) + + def encode(f): + return f'{f}' + + program = "\n".join(map(encode, program_path().split(' '))) + contents = contents.replace('plextraktsync', program) + with open(plist_path, "w+") as fw: + fw.writelines(contents) + + def remove(self, plist_path: str): + from os import unlink + from os.path import exists + + # Skip if file does not exist. + if not exists(plist_path): + return + unlink(plist_path) + + @cached_property + def plist_default_path(self): + from os.path import join + + from plextraktsync.path import module_path + + return join(module_path, self.plist_file) + + @cached_property + def plist_path(self): + from os.path import expanduser + + return expanduser(f'~/Library/LaunchAgents/{self.plist_file}') + + +@click.command() +def load(): + """ + Load the service. + """ + p = Plist() + p.create(p.plist_path) + click.echo(f"Created: {p.plist_path}") + p.load(p.plist_path) + click.echo(f"Loaded: {p.plist_path}") + + +@click.command() +def unload(): + """ + Unload the service. + """ + p = Plist() + p.unload(p.plist_path) + click.echo(f"Unloaded: {p.plist_path}") + p.remove(p.plist_path) + click.echo(f"Removed: {p.plist_path}") diff --git a/plextraktsync/path.py b/plextraktsync/path.py index 545f8c5fb2e..b800c3427c5 100644 --- a/plextraktsync/path.py +++ b/plextraktsync/path.py @@ -59,6 +59,7 @@ def ensure_dir(directory): config_dir = p.config_dir log_dir = p.log_dir +module_path = p.module_path default_config_file = p.default_config_file config_file = p.config_file config_yml = p.config_yml diff --git a/plextraktsync/util/packaging.py b/plextraktsync/util/packaging.py index a21073ec3ca..33c77dce0ad 100644 --- a/plextraktsync/util/packaging.py +++ b/plextraktsync/util/packaging.py @@ -59,6 +59,17 @@ def pipx_installed(package: str): return package +def program_path(): + """ + Return path to currently executed script + """ + import sys + + absdir = dirname(dirname(dirname(__file__))) + + return f"env PYTHONPATH={absdir} {sys.executable} -m plextraktsync" + + def program_name(): """ Return current program name: