From f9e0db4499c8b34b4f27ea203294b4e823d51340 Mon Sep 17 00:00:00 2001 From: alban Date: Fri, 9 Oct 2020 18:29:28 +0200 Subject: [PATCH] [enh] adds a command line runner for clitools --- clitools/.gitignore | 1 + clitools/_run.sh | 11 ++ clitools/runner.py | 116 +++++++++++++ clitools/runner_lib.py | 374 ++++++++++++++++++++++++++++++++++++++++ clitools/runner_midi.py | 12 ++ 5 files changed, 514 insertions(+) create mode 100644 clitools/.gitignore create mode 100755 clitools/_run.sh create mode 100755 clitools/runner.py create mode 100644 clitools/runner_lib.py create mode 100644 clitools/runner_midi.py diff --git a/clitools/.gitignore b/clitools/.gitignore new file mode 100644 index 0000000..b4c4cfb --- /dev/null +++ b/clitools/.gitignore @@ -0,0 +1 @@ +playlists diff --git a/clitools/_run.sh b/clitools/_run.sh new file mode 100755 index 0000000..96014a0 --- /dev/null +++ b/clitools/_run.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +killexit(){ + pkill -9 -s $$ + } + +trap killexit SIGTERM SIGINT SIGKILL SIGSTOP + +bash -c "$@" + +killbill(){ pkill -9 -s $(awk '{print $6}' /proc/$1/stat) } diff --git a/clitools/runner.py b/clitools/runner.py new file mode 100755 index 0000000..32f5a75 --- /dev/null +++ b/clitools/runner.py @@ -0,0 +1,116 @@ +#!/usr/bin/python3 + +import sys +import os +import signal +import subprocess +import time +import tty,termios +import re +import json +from pathlib import Path +import runner_lib as runner + + +bindings={ + "Show playlist" : "l", + "Launch [0-x] cmd" : "0-x", + "Previous command" : "p", + "Next command" : "o", + "New command" : "a", + "Edit command" : "e", + "Delete command" : "d", + "Load playlist" : "L", + "Save playlist" : "S", + "Save as new" : "A", + "New playlist" : "N", + "Command help" : "H", + "Kill process Id" : "K", + "Edit Laser Id" : "i", + "Edit Laser Scene" : "s", + "Information" : "I", + "Help" : "h", + "Quit" : "q", + +} + + +## Init user contact + + + +# Main Loop +runner.action_info() +runner.action_help() +print("\n\nLoad a playlist? [Y/n]: ") +if "y" == runner.inkey() : + runner.action_loadPlaylist() + +while True: + # Fuck zombies + runner._killBill() + runner._ok("> Next Action?") + k = runner.inkey() + + if bindings["Next command"] == k: + runner.action_changeCommand( 1 ) + runner.action_runCommand() + elif bindings["Previous command"] == k: + runner.action_changeCommand( -1 ) + runner.action_runCommand() + elif re.match( r'^\d+$',k): + runner.action_match(k) + runner.action_runCommand() + elif bindings["New command"] == k: + runner.action_newCommand() + continue + elif bindings["Show playlist"] == k: + runner.action_listAll() + continue + elif bindings["Delete command"] == k: + runner.action_deleteCommand() + continue + elif bindings["Edit command"] == k: + runner.action_listAll() + runner.action_edit() + continue + elif bindings["Load playlist"] == k: + if runner.action_loadPlaylist(): + runner.action_listAll() + continue + elif bindings["Save playlist"] == k: + runner.action_savePlaylist() + continue + elif bindings["Save as new"] == k: + runner.action_savePlaylist() + continue + elif bindings["New playlist"] == k: + runner.action_newPlaylist() + continue + elif bindings["Command help"] == k: + runner.action_commandHelp() + continue + elif bindings["Edit Laser Id"] == k: + runner.action_laserId() + continue + elif bindings["Edit Laser Scene"] == k: + runner.action_laserScene() + continue + elif bindings["Kill process Id"] == k: + runner.action_killPid() + continue + elif bindings["Help"] == k: + runner.action_help() + continue + elif bindings["Information"] == k: + runner.action_info() + continue + elif bindings["Quit"] == k: + runner.action_quit() + else: + runner._err("Unexpected key:{}".format(k)) + continue + + + + diff --git a/clitools/runner_lib.py b/clitools/runner_lib.py new file mode 100644 index 0000000..ef7b49c --- /dev/null +++ b/clitools/runner_lib.py @@ -0,0 +1,374 @@ + +import sys +import os +import signal +import subprocess +import time +import tty,termios +import re +import json +from pathlib import Path +import redis + +class bcolors: + HL = '\033[31m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +class _Getch: + def __call__(self): + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + return ch +inkey = _Getch() + +def intkey(): + try: + i = int( inkey() ) + return(i) + except ValueError: + print("Error.") + +current_id=0 +current_cmd="" +process = None +current_filename = "" +currentPlayList = [] +playlistsDir = Path("./playlists") +if not playlistsDir.is_dir() : playlistsDir.mkdir() + +def ask(q): + print(q) + return inkey() + +def _ok(msg): + print( bcolors.BOLD+bcolors.OKBLUE+ msg + bcolors.ENDC) + +def _err(msg): + print( bcolors.HL + msg + bcolors.ENDC) + +def _kill(process): + if process : + try: + pid = os.getpgid(process.pid) + os.killpg(pid, signal.SIGTERM) + os.killpg(pid, signal.SIGKILL) + os.kill(pid, signal.SIGTERM) + os.kill(pid, signal.SIGKILL) + process.terminate() + process.kill() + except Exception as e: + print("woops:{}".format(e)) + + + +def _killBill(): + subprocess.run("ps --ppid 1 -fo pid,sess,ppid,cmd | grep 'toRedis.py' | while read pid sid other; do pkill -9 -s $sid; done", shell=True,executable='/bin/bash') + + + +def action_info(): + print(""" +Welcome to LJ playlist manager + +Currently running on + +IP : {} +Port : {} +Key : {} +Scene : {} +Laser : {} +""".format( + environ["REDIS_IP"], + environ["REDIS_PORT"], + environ["REDIS_KEY"], + environ["REDIS_SCENE"], + environ["REDIS_LASER"] +)) + + +def action_changeCommand( inc ): + global currentPlayList + global current_id + if 0 == len(currentPlayList): + _err("Empty playlist") + return False + current_id = (current_id + 1) % len(currentPlayList) + return True + +def action_match( k ): + if int(k) > (len(currentPlayList) - 1): + print( bcolors.HL + "This key does not exist" + bcolors.ENDC ) + return False + else : + current_id = int(k) + +def action_runCommand(): + global currentPlayList + global current_id + global process + + # Get new command + try: + current_cmd = currentPlayList[current_id] + except IndexError as e: + _err("woops:{}".format(e)) + return False + + print("\n[!]New command:'{}'\n".format(current_cmd)) + + # Start subprocess + try : + _kill(process) + process = subprocess.Popen("./_run.sh '"+current_cmd+" | exports/toRedis.py -i $REDIS_IP -k $REDIS_KEY'", shell=True, executable='/bin/bash', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=environ, preexec_fn=os.setsid) + + except Exception as e: + print("woops:{}".format(e)) + + + +def action_newCommand(): + global currentPlayList + print("Enter new command or e(x)it.") + k = input() + # Exit early + if "x" == k: + return(False) + currentPlayList.append(k) + print(bcolors.OKBLUE + "Command added" + bcolors.ENDC) + return True + + + + +def action_deleteCommand(): + global currentPlayList + print("Select sequence to delete or e(x)it.") + action_listAll() + key = int(input()) + # Exit early + if "x" == k: + return(False) + del currentPlayList[key] + return True + + + +def action_listAll(): + global currentPlayList, current_cmd + print("\n--------------------------------------") + for i,seq in enumerate(currentPlayList): + pre="" + suf="" + if current_cmd == seq : + pre = bcolors.HL + suf = bcolors.ENDC + print( pre + "{}\t{}".format(i,seq) + suf ) + print("--------------------------------------\n") + + + + + +def action_help(): + global bindings + print("\nKey\tAction\n--------------------------------------") + for i in bindings: + print(" {}\t{}".format(bindings[i],i)) + print("--------------------------------------\n") + + + + + +def action_edit(): + print("Enter the command number to edit, or 'x' to abort.") + k = intkey() + if 'x' == k: + return + print("Enter the next value, or 'x' to abort.") + value = input() + if 'x' == value: + return + currentPlayList[k] = value + + + + + + + + +def action_loadPlaylist(): + + global playlistsDir + global currentPlayList + global current_playlist_name + # list files + i=0 + file_list = [x for x in playlistsDir.glob("*json")] + if 0 == len(file_list ): + print( bcolors.HL + "Error. No file in path '{}'\n".format(playlistsDir.name)) + return False + + print("\n Id\tName") + for k,name in enumerate(file_list) : + print(" {}\t{}".format(k,name),flush=True) + + # ask file + print("\nChoose a file or e(x)it:") + k = intkey() + if '' == k: + print("Invalid choice: '{}'".format(k)) + return + + # Exit early + if "x" == k: return(False) + + # todo : helper for detecting invalid keys + try: + if k > (len(file_list) - 1): + print( bcolors.HL + "This key '{}' does not exist".format(k) + bcolors.ENDC ) + return False + except TypeError: + print( bcolors.HL + "This key '{}' is not valid".format(k) + bcolors.ENDC ) + return False + + # Load file + playlistFile = Path("./playlists/"+file_list[k].name) + currentPlayList = json.loads(playlistFile.read_text()) + current_playlist_name = file_list[k].name + current_id = 0 + print( bcolors.OKBLUE + "Playlist loaded: {}\n".format(current_playlist_name)+ bcolors.ENDC) + return True + + + + + + + + + + +def action_newPlaylist(): + global playlistsDir + global currentPlayList + # ask for name + print("Enter new playlist name (without.json) or e(x)it question?") + k = input() + + # Exit early + if "x" == k: + return(False) + + # save file + currentPlayList = [] + _savePlaylist( k+".json" ) + currentPlayList = [] + + pass + + + + +def _savePlaylist( playlistname ): + global currentPlayList + filepath = Path("playlists/{}".format(playlistname)) + with filepath.open("w", encoding ="utf-8") as f: + f.write(json.dumps(currentPlayList, indent=4, sort_keys=True)) + return(True) + + + + + +def action_savePlaylist( name=False ): + global current_playlist_name + playlist_name = name if name else current_playlist_name + if not playlist_name : + _err("No name found.") + return False + try: + _savePlaylist(playlist_name) + print( bcolors.OKBLUE + "\nSaved as '{}'.\n".format(playlist_name) + bcolors.ENDC) + except Exception as e: + print("woops:{}".format(e)) + return False + + + + + +def action_commandHelp(): + global playlistsDir + + # iterate through files + file_list=[] + for folder in ["generators","filters","exports"]: + p = Path("./"+folder) + for plFile in Path("./"+folder).iterdir() : + if re.match("^.*py$",plFile.name): + file_list.append(os.path.join(folder,plFile.name)) + print("\n Id\tFile") + for k,filename in enumerate(file_list): + print(" {}\t{}".format(k,filename)) + print("\nChoose a file:") + k = int(input()) + print("\n-----------------------------------------------\n") + subprocess.run("python3 "+file_list[k]+" -h", shell=True, executable='/bin/bash') + print("\n-----------------------------------------------\n") + + + + +def _setKey( laser=0, scene=0 ): + global environ + laser = laser if laser else environ["REDIS_LASER"] + scene = scene if scene else environ["REDIS_SCENE"] + new_key = "/pl/{}/{}".format(scene,laser) + environ["REDIS_KEY"] = new_key + print("Sending new key '{}'".format(new_key)) + + +def action_laserId(): + k = int(ask("Enter the LJ Laser id [0-3]")) + _setKey( laser = k ) + + + +def action_laserScene(): + k = int(ask("Enter the LJ Scene id [0-3]")) + _setKey( scene = k ) + + + +def action_killPid(): + print("Enter pid to kill") + kill_pid = input() + subprocess.run("pkill -9 -s $(awk '{print $6}' /proc/$kill_pid/stat)", shell=True,executable='/bin/bash', env={"kill_pid":kill_pid}) + + +def action_quit(): + print("Quit [Y/n]?") + quit = inkey() + if quit != "n": + _kill(process) + sys.exit(1) + +environ = { +# "REDIS_IP" : "127.0.0.1", + "REDIS_IP" : "192.168.2.44", + "REDIS_PORT" : "6379", + "REDIS_KEY" : "/pl/0/0", + "REDIS_SCENE" : "0", + "REDIS_LASER" : "0" +} diff --git a/clitools/runner_midi.py b/clitools/runner_midi.py new file mode 100644 index 0000000..ecf4956 --- /dev/null +++ b/clitools/runner_midi.py @@ -0,0 +1,12 @@ +#!/usr/bin/python3 + +import sys +import os +import signal +import subprocess +import time +import tty,termios +import re +import json +from pathlib import Path +import runner_lib as runner