Compare commits

..

12 Commits

Author SHA1 Message Date
560c85ea61 added listRunning to service 2023-05-29 21:46:05 +02:00
cb7e284313 added safeguard to ps run 2023-05-29 21:10:22 +02:00
563211af6f fixed missing null check in ps handle 2023-05-29 21:09:39 +02:00
5985b0c8be added service bin 2023-05-29 12:50:11 +02:00
f35c98f912 added SerciceManager 2023-05-29 12:50:00 +02:00
a343db133e improved KVStore 2023-05-29 12:49:42 +02:00
dd2e9a4993 added kill to ProcessManager 2023-05-29 12:49:17 +02:00
eb39131056 added removeNamespace to KVStore 2023-05-25 23:44:08 +02:00
c4260aa719 added bin: lsps 2023-05-23 14:51:56 +02:00
e73b4c4d14 use BinStore in Terminal 2023-05-23 14:36:51 +02:00
94d02d60af added BinStore 2023-05-23 14:35:53 +02:00
16b7db779f use new lib: compiletime 2023-05-22 22:36:11 +02:00
14 changed files with 382 additions and 47 deletions

View File

@@ -2,6 +2,7 @@
--library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git --library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git
--library tink_core --library tink_core
--library compiletime
--dce full --dce full

21
src/bin/LSPS.hx Normal file
View File

@@ -0,0 +1,21 @@
package bin;
import kernel.ps.ProcessManager;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
class LSPS implements Process {
public function new() {}
public function run(handle:ProcessHandle) {
var pids = ProcessManager.listProcesses();
handle.writeLine('Count: ${pids.length}');
for (pid in pids) {
handle.writeLine('${pid}');
}
handle.close();
}
}

63
src/bin/Service.hx Normal file
View File

@@ -0,0 +1,63 @@
package bin;
import kernel.service.ServiceManager;
import lib.CLIAppBase;
using tink.CoreApi;
class Service extends CLIAppBase {
public function new() {
registerSyncSubcommand("start", (args) ->{
if (args.length < 1) {
return false;
}
var name = args[0];
var result = ServiceManager.instace.start(name);
return handleResult(result);
},"Start a service");
registerSyncSubcommand("stop", (args) ->{
if (args.length < 1) {
return false;
}
var name = args[0];
var result = ServiceManager.instace.stop(name);
return handleResult(result);
},"Stop a service");
registerSyncSubcommand("register", (args) ->{
if (args.length < 2) {
return false;
}
var name = args[0];
var binName = args[1];
var rest = args.slice(2);
var result = ServiceManager.instace.register(name, binName, rest);
return handleResult(result);
},"Register a new service");
registerSyncSubcommand("unregister", (args) ->{
return true;
},"Unregister a service");
registerSyncSubcommand("list", (args) ->{
return true;
},"List all services");
}
private function handleResult(res: Outcome<Noise,String>): Bool {
switch (res) {
case Success(_):
return true;
case Failure(e):
this.handle.write(e);
return false;
}
}
}

View File

@@ -1,5 +1,6 @@
package bin; package bin;
import kernel.binstore.BinStore;
import kernel.ps.ProcessHandle; import kernel.ps.ProcessHandle;
import kernel.ps.Process; import kernel.ps.Process;
import kernel.ps.ProcessManager; import kernel.ps.ProcessManager;
@@ -146,22 +147,12 @@ class Terminal implements Process {
} }
private function getProgByName(name:String):Process { private function getProgByName(name:String):Process {
switch (name) { var bin = BinStore.instance.getBinByAlias(name);
case "hello": if (bin == null) {
return new HelloWorld(); return null;
case "net":
return new Net();
case "rs":
return new Redstone();
case "disk":
return new Disk();
case "gps":
return new GPS();
case "turtle":
return new Turtle();
default:
return null;
} }
return Type.createInstance(bin.c,[]);
} }
private function moveCursorToInput() { private function moveCursorToInput() {

View File

@@ -1,5 +1,7 @@
package kernel; package kernel;
import kernel.service.ServiceManager;
import kernel.binstore.BinStore;
import kernel.gps.INS; import kernel.gps.INS;
import kernel.fs.FS; import kernel.fs.FS;
import kernel.gps.GPS; import kernel.gps.GPS;
@@ -27,6 +29,8 @@ class Init {
WindowManager.instance = new WindowManager(); WindowManager.instance = new WindowManager();
MainTerm.instance = new MainTerm(); MainTerm.instance = new MainTerm();
BinStore.instance = new BinStore();
if (Turtle.isTurtle()){ if (Turtle.isTurtle()){
Turtle.instance = new Turtle(); Turtle.instance = new Turtle();
} }
@@ -53,5 +57,7 @@ class Init {
Init.mainEvent = MainLoop.add(()->{ Init.mainEvent = MainLoop.add(()->{
KernelEvents.instance.startEventLoop(); KernelEvents.instance.startEventLoop();
}); });
ServiceManager.instace = new ServiceManager();
} }
} }

View File

@@ -0,0 +1,12 @@
package kernel.binstore;
import kernel.ps.Process;
/**
Represents a callable program.
**/
typedef Bin = {
c: Class<Process>,
name: String,
aliases: Array<String>,
}

View File

@@ -0,0 +1,55 @@
package kernel.binstore;
import bin.Service;
import bin.LSPS;
import bin.Turtle;
import bin.Terminal;
import bin.Redstone;
import bin.Net;
import bin.KernelLog;
import bin.HelloWorld;
import bin.GPS;
import bin.Disk;
import haxe.ds.ReadOnlyArray;
class BinStore {
public static var instance: BinStore;
private final store:ReadOnlyArray<Bin> = [
{c: Disk, name: "Disk", aliases: ["disk"]},
{c: GPS, name: "GPS", aliases: ["gps"]},
{c: HelloWorld, name: "HelloWorld", aliases: ["hello"]},
{c: KernelLog, name: "KernelLog", aliases: ["log"]},
{c: Net, name: "Net", aliases: ["net"]},
{c: Redstone, name: "Redstone", aliases: ["redstone","rs"]},
{c: Terminal, name: "Terminal", aliases: ["terminal","term"]},
{c: Turtle, name: "Turtle", aliases: ["turtle"]},
{c: LSPS, name: "PM", aliases: ["lsps"]},
{c: Service, name: "Service", aliases: ["service","srv"]}
];
@:allow(kernel.Init)
private function new() {
}
public function getBinByName(name:String):Null<Bin> {
for (bin in store) {
if (bin.name == name) {
return bin;
}
}
return null;
}
public function getBinByAlias(alias:String):Null<Bin> {
for (bin in store) {
for (a in bin.aliases) {
if (a == alias) {
return bin;
}
}
}
return null;
}
}

View File

@@ -49,7 +49,9 @@ class ProcessHandle {
} }
public function write(message: String): Void { public function write(message: String): Void {
this.config.onWrite.invoke(message); if (this.config.onWrite != null){
this.config.onWrite.invoke(message);
}
} }
public function writeLine(message: String): Void { public function writeLine(message: String): Void {

View File

@@ -1,7 +1,10 @@
package kernel.ps; package kernel.ps;
import kernel.log.Log;
import kernel.ps.ProcessHandle.HandleConfig; import kernel.ps.ProcessHandle.HandleConfig;
using tink.CoreApi;
typedef PID = Int; typedef PID = Int;
class ProcessManager { class ProcessManager {
@@ -13,11 +16,26 @@ class ProcessManager {
processList.set(pid, handle); processList.set(pid, handle);
process.run(handle); try{
process.run(handle);
}catch(e:Dynamic){
Log.error("Error while running process: " + e);
handle.close(false);
}
return pid; return pid;
} }
public static function kill(pid: PID) {
if (!processList.exists(pid)){
throw new Error("Process with PID " + pid + " does not exist");
}
var handle = processList.get(pid);
handle.close();
}
private static function createPID(): PID { private static function createPID(): PID {
// TODO: better PID generation // TODO: better PID generation
@@ -34,4 +52,8 @@ class ProcessManager {
private static function removeProcess(pid:PID):Void { private static function removeProcess(pid:PID):Void {
processList.remove(pid); processList.remove(pid);
} }
public static function listProcesses():Array<PID> {
return [for (pid in processList.keys()) pid];
}
} }

View File

@@ -0,0 +1,38 @@
package kernel.service;
import kernel.ps.ProcessManager;
import kernel.ps.ProcessHandle;
import kernel.binstore.BinStore;
using tink.CoreApi;
class Service {
private final binName:String;
private final name:String;
private final args:Array<String>;
private var pid:PID;
@:allow(kernel.service.ServiceManager)
private function new(binName: String,name: String,?args: Array<String> ) {
this.binName = binName;
this.name = name;
this.args = args ?? [];
}
public function start() {
var bin = BinStore.instance.getBinByAlias(this.binName);
if (bin == null){
throw new Error('Bin ${this.binName} not found');
}
var ps = Type.createInstance(bin.c,this.args);
this.pid = ProcessManager.run(ps,{});
}
public function stop() {
ProcessManager.kill(this.pid);
}
}

View File

@@ -0,0 +1,131 @@
package kernel.service;
import kernel.log.Log;
import kernel.binstore.BinStore;
import lib.KVStore;
using tink.CoreApi;
class ServiceManager {
public static var instace: ServiceManager;
private final services:Map<String,Service> = new Map();
@:allow(kernel.Init)
private function new() {
this.startAllEnabled();
}
/**
Add a service to be automatically started.
**/
private function enable(name: String) {
if (!this.services.exists(name)){
return; // Service must be started
}
var store = KVStore.getStoreForClass();
var enabled = store.get("enabled",[]);
enabled.push(name);
store.set("enabled",enabled);
store.save();
}
/**
Remove a service from being automatically started.
**/
private function disable(name: String) {
var store = KVStore.getStoreForClass();
var enabled: Array<String> = store.get("enabled");
var index = enabled.indexOf(name);
if (index == -1){
return;
}
enabled.splice(index,1);
store.save();
}
private function startAllEnabled() {
var store = KVStore.getStoreForClass();
var enabled: Array<String> = store.get("enabled",[]);
for (name in enabled){
this.start(name);
}
}
private function load(name: String): Null<Service> {
var store = new KVStore('service/${name}');
store.load();
if (!store.exists("service")){
return null;
}
return store.get("service");
}
public function register(name: String, binName: String,args: Array<String>): Outcome<Noise,String> {
if (BinStore.instance.getBinByAlias(binName) == null){
return Failure("bin not found");
}
if (this.load(name) != null){
return Failure("service already exists");
}
var service = new Service(binName,name,args);
var store = new KVStore('service/${name}');
store.set("service",service);
store.save();
Log.info('Service ${name} registered');
return Success(Noise);
}
public function unregister(name: String): Outcome<Noise,String> {
if (this.services.exists(name)){
return Failure("service is running");
}
KVStore.removeNamespace('service/${name}');
Log.info('Service ${name} unregistered');
return Success(Noise);
}
public function start(name: String): Outcome<Noise,String> {
var service = this.load(name);
if (service == null){
return Failure("service not found");
}
service.start();
this.services.set(name,service);
Log.info('Service ${name} started');
return Success(Noise);
}
public function stop(name: String): Outcome<Noise,String> {
if (!this.services.exists(name)){
return Failure("service not found");
}
var service = this.services.get(name);
service.stop();
this.services.remove(name);
Log.info('Service ${name} stopped');
return Success(Noise);
}
public function listRunning(): Array<String> {
var running = [];
for (name in this.services.keys()){
running.push(name);
}
return running;
}
}

View File

@@ -7,36 +7,21 @@ class BuildInfo {
/** /**
Get the latest git commit. Get the latest git commit.
**/ **/
public static macro function getGitCommitHash():haxe.macro.Expr.ExprOf<String> { public static function getGitCommitHash():String {
#if !display #if !display
var process = new sys.io.Process('git', ['rev-parse', 'HEAD']); return CompileTime.buildGitCommitSha();
if (process.exitCode() != 0) {
var message = process.stderr.readAll().toString();
var pos = haxe.macro.Context.currentPos();
haxe.macro.Context.error("Cannot execute `git rev-parse HEAD`. " + message, pos);
}
// read the output of the process
var commitHash:String = process.stdout.readLine();
// Generates a string expression
return macro $v{commitHash};
#else
// `#if display` is used for code completion. In this case returning an
// empty string is good enough; We don't want to call git on every hint.
var commitHash:String = "";
return macro $v{commitHash};
#end #end
return "";
} }
/** /**
Get the time the file was build. Get the time the file was build.
**/ **/
public static macro function buildTime():haxe.macro.Expr.ExprOf<Int> { public static function buildTime():Date {
#if !display #if !display
return macro $v{Math.floor(Date.now().getTime())}; return CompileTime.buildDate();
#else #else
return macro $v{0}; return 0;
#end #end
} }
} }

View File

@@ -14,9 +14,7 @@ class Debug {
public static function printBuildInfo() { public static function printBuildInfo() {
Log.debug("Commit: " + BuildInfo.getGitCommitHash()); Log.debug("Commit: " + BuildInfo.getGitCommitHash());
var time:Date = Date.fromTime(BuildInfo.buildTime()); Log.debug("Build time: " + BuildInfo.buildTime().toString());
Log.debug("Build time: " + time.toString());
Log.debug("CC/MC version:" + ComputerCraft._HOST); Log.debug("CC/MC version:" + ComputerCraft._HOST);
} }

View File

@@ -10,12 +10,22 @@ import haxe.ds.StringMap;
**/ **/
class KVStore { class KVStore {
private var kvStore: StringMap<Dynamic> = new StringMap(); private var kvStore: StringMap<Dynamic> = new StringMap();
private final namespace:String; public final namespace:String;
public function new(namespace: String) { public function new(namespace: String) {
this.namespace = namespace; this.namespace = namespace;
} }
public static function removeNamespace(namespace: String): Void {
var nsFile = getNamespaceFile(namespace);
FS.delete(nsFile);
}
public static function getStoreForClass(?pos:haxe.PosInfos) {
var className = pos.className;
return new KVStore(className);
}
private static function getNamespaceFile(namespace: String): String { private static function getNamespaceFile(namespace: String): String {
return '/var/ns/$namespace'; return '/var/ns/$namespace';
} }
@@ -45,8 +55,8 @@ class KVStore {
this.kvStore.set(key,value); this.kvStore.set(key,value);
} }
public inline function get<T>(key: String): Null<T> { public inline function get<T>(key: String,?orElse:T = null): Null<T> {
return this.kvStore.get(key); return this.kvStore.get(key) ?? orElse;
} }
public inline function exists(key: String): Bool { public inline function exists(key: String): Bool {