diff --git a/src/main/angular/public/webmine/block/activator/blue_false.png b/src/main/angular/public/webmine/block/activator/blue_false.png new file mode 100644 index 0000000..1b253e1 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/blue_false.png differ diff --git a/src/main/angular/public/webmine/block/activator/blue_null.png b/src/main/angular/public/webmine/block/activator/blue_null.png new file mode 100644 index 0000000..54812cb Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/blue_null.png differ diff --git a/src/main/angular/public/webmine/block/activator/blue_true.png b/src/main/angular/public/webmine/block/activator/blue_true.png new file mode 100644 index 0000000..31270f9 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/blue_true.png differ diff --git a/src/main/angular/public/webmine/block/activator/cyan_false.png b/src/main/angular/public/webmine/block/activator/cyan_false.png new file mode 100644 index 0000000..652450a Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/cyan_false.png differ diff --git a/src/main/angular/public/webmine/block/activator/cyan_null.png b/src/main/angular/public/webmine/block/activator/cyan_null.png new file mode 100644 index 0000000..4fd272e Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/cyan_null.png differ diff --git a/src/main/angular/public/webmine/block/activator/cyan_true.png b/src/main/angular/public/webmine/block/activator/cyan_true.png new file mode 100644 index 0000000..6528b3e Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/cyan_true.png differ diff --git a/src/main/angular/public/webmine/block/activator/green_false.png b/src/main/angular/public/webmine/block/activator/green_false.png new file mode 100644 index 0000000..e1f528c Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/green_false.png differ diff --git a/src/main/angular/public/webmine/block/activator/green_null.png b/src/main/angular/public/webmine/block/activator/green_null.png new file mode 100644 index 0000000..3913422 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/green_null.png differ diff --git a/src/main/angular/public/webmine/block/activator/green_true.png b/src/main/angular/public/webmine/block/activator/green_true.png new file mode 100644 index 0000000..8dab2e2 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/green_true.png differ diff --git a/src/main/angular/public/webmine/block/activator/magenta_false.png b/src/main/angular/public/webmine/block/activator/magenta_false.png new file mode 100644 index 0000000..cb7004f Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/magenta_false.png differ diff --git a/src/main/angular/public/webmine/block/activator/magenta_null.png b/src/main/angular/public/webmine/block/activator/magenta_null.png new file mode 100644 index 0000000..7d3d327 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/magenta_null.png differ diff --git a/src/main/angular/public/webmine/block/activator/magenta_true.png b/src/main/angular/public/webmine/block/activator/magenta_true.png new file mode 100644 index 0000000..2b6b913 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/magenta_true.png differ diff --git a/src/main/angular/public/webmine/block/activator/red_false.png b/src/main/angular/public/webmine/block/activator/red_false.png new file mode 100644 index 0000000..3c99809 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/red_false.png differ diff --git a/src/main/angular/public/webmine/block/activator/red_null.png b/src/main/angular/public/webmine/block/activator/red_null.png new file mode 100644 index 0000000..2ba5856 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/red_null.png differ diff --git a/src/main/angular/public/webmine/block/activator/red_true.png b/src/main/angular/public/webmine/block/activator/red_true.png new file mode 100644 index 0000000..91b54df Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/red_true.png differ diff --git a/src/main/angular/public/webmine/block/activator/yellow_false.png b/src/main/angular/public/webmine/block/activator/yellow_false.png new file mode 100644 index 0000000..1571368 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/yellow_false.png differ diff --git a/src/main/angular/public/webmine/block/activator/yellow_null.png b/src/main/angular/public/webmine/block/activator/yellow_null.png new file mode 100644 index 0000000..f018347 Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/yellow_null.png differ diff --git a/src/main/angular/public/webmine/block/activator/yellow_true.png b/src/main/angular/public/webmine/block/activator/yellow_true.png new file mode 100644 index 0000000..6b42e7c Binary files /dev/null and b/src/main/angular/public/webmine/block/activator/yellow_true.png differ diff --git a/src/main/angular/public/webmine/block/observer/blue_false.png b/src/main/angular/public/webmine/block/observer/blue_false.png new file mode 100644 index 0000000..76bbf55 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/blue_false.png differ diff --git a/src/main/angular/public/webmine/block/observer/blue_null.png b/src/main/angular/public/webmine/block/observer/blue_null.png new file mode 100644 index 0000000..2872a5b Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/blue_null.png differ diff --git a/src/main/angular/public/webmine/block/observer/blue_true.png b/src/main/angular/public/webmine/block/observer/blue_true.png new file mode 100644 index 0000000..8ca2a03 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/blue_true.png differ diff --git a/src/main/angular/public/webmine/block/observer/cyan_false.png b/src/main/angular/public/webmine/block/observer/cyan_false.png new file mode 100644 index 0000000..32db097 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/cyan_false.png differ diff --git a/src/main/angular/public/webmine/block/observer/cyan_null.png b/src/main/angular/public/webmine/block/observer/cyan_null.png new file mode 100644 index 0000000..43e7a1b Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/cyan_null.png differ diff --git a/src/main/angular/public/webmine/block/observer/cyan_true.png b/src/main/angular/public/webmine/block/observer/cyan_true.png new file mode 100644 index 0000000..e563d7d Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/cyan_true.png differ diff --git a/src/main/angular/public/webmine/block/observer/green_false.png b/src/main/angular/public/webmine/block/observer/green_false.png new file mode 100644 index 0000000..fd80426 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/green_false.png differ diff --git a/src/main/angular/public/webmine/block/observer/green_null.png b/src/main/angular/public/webmine/block/observer/green_null.png new file mode 100644 index 0000000..49f83d1 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/green_null.png differ diff --git a/src/main/angular/public/webmine/block/observer/green_true.png b/src/main/angular/public/webmine/block/observer/green_true.png new file mode 100644 index 0000000..180775b Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/green_true.png differ diff --git a/src/main/angular/public/webmine/block/observer/magenta_false.png b/src/main/angular/public/webmine/block/observer/magenta_false.png new file mode 100644 index 0000000..6d8a836 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/magenta_false.png differ diff --git a/src/main/angular/public/webmine/block/observer/magenta_null.png b/src/main/angular/public/webmine/block/observer/magenta_null.png new file mode 100644 index 0000000..cdac118 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/magenta_null.png differ diff --git a/src/main/angular/public/webmine/block/observer/magenta_true.png b/src/main/angular/public/webmine/block/observer/magenta_true.png new file mode 100644 index 0000000..c3d3a79 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/magenta_true.png differ diff --git a/src/main/angular/public/webmine/block/observer/red_false.png b/src/main/angular/public/webmine/block/observer/red_false.png new file mode 100644 index 0000000..93c5c6b Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/red_false.png differ diff --git a/src/main/angular/public/webmine/block/observer/red_null.png b/src/main/angular/public/webmine/block/observer/red_null.png new file mode 100644 index 0000000..bb81d2e Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/red_null.png differ diff --git a/src/main/angular/public/webmine/block/observer/red_true.png b/src/main/angular/public/webmine/block/observer/red_true.png new file mode 100644 index 0000000..b1169f1 Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/red_true.png differ diff --git a/src/main/angular/public/webmine/block/observer/yellow_false.png b/src/main/angular/public/webmine/block/observer/yellow_false.png new file mode 100644 index 0000000..4d9e07b Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/yellow_false.png differ diff --git a/src/main/angular/public/webmine/block/observer/yellow_null.png b/src/main/angular/public/webmine/block/observer/yellow_null.png new file mode 100644 index 0000000..f08f22b Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/yellow_null.png differ diff --git a/src/main/angular/public/webmine/block/observer/yellow_true.png b/src/main/angular/public/webmine/block/observer/yellow_true.png new file mode 100644 index 0000000..b4581df Binary files /dev/null and b/src/main/angular/public/webmine/block/observer/yellow_true.png differ diff --git a/src/main/angular/src/app/app.routes.ts b/src/main/angular/src/app/app.routes.ts index 7b8a2e9..e05f6dc 100644 --- a/src/main/angular/src/app/app.routes.ts +++ b/src/main/angular/src/app/app.routes.ts @@ -1,5 +1,5 @@ import {Routes} from '@angular/router'; -import {ServerListComponent} from './server-list/server-list.component'; +import {ServerListComponent} from './server/list/server-list.component'; export const routes: Routes = [ {path: '**', component: ServerListComponent}, diff --git a/src/main/angular/src/app/crud/CrudHelpers.ts b/src/main/angular/src/app/crud/CrudHelpers.ts index 9835a6f..ef4e18d 100644 --- a/src/main/angular/src/app/crud/CrudHelpers.ts +++ b/src/main/angular/src/app/crud/CrudHelpers.ts @@ -2,39 +2,61 @@ export type FromJson = (json: any) => T; export type Next = (t: T) => any; -export function validateString(json: any) { - if (typeof json === 'string') { - return json; - } - throw new Error('Not a string: ' + JSON.stringify(json)); -} - export function orNull(t: T | null, map: (t: T) => R): R | null { - if (t === null) { + if (t === null || t === undefined) { return null; } return map(t); } -export function orElse(t: T | null, map: (t: T) => R, orElse: R): R { - if (t === null) { +export function orElse(t: T | null | undefined, map: (t: T) => R, orElse: R): R { + if (t === null || t === undefined) { return orElse; } return map(t); } export function validateBoolean(json: any) { - if (typeof json === 'boolean') { - return json; + if (typeof json !== 'boolean') { + throw new Error(`Not a Boolean: type=${typeof json}, value=${JSON.stringify(json)}`); } - throw new Error('Not a boolean: ' + JSON.stringify(json)); + return json; } export function validateNumber(json: any) { - if (typeof json === 'number') { - return json; + if (typeof json !== 'number') { + throw new Error(`Not a Number: type=${typeof json}, value=${JSON.stringify(json)}`); } - throw new Error('Not a number: ' + JSON.stringify(json)); + return json; +} + +export function validateString(json: any) { + if (typeof json !== 'string') { + throw new Error(`Not a String: type=${typeof json}, value=${JSON.stringify(json)}`); + } + return json; +} + +export function validateList(json: any, fromJson: FromJson): T[] { + if (!Array.isArray(json)) { + throw new Error(`Not a list: type=${typeof json}, value=${JSON.stringify(json)}`); + } + return json.map(fromJson); +} + +export function validateMap(json: any, key: FromJson, value: FromJson): Map { + if (typeof json !== 'object') { + throw new Error(`Not a Map: type=${typeof json}, value=${JSON.stringify(json)}`); + } + + const result = new Map(); + for (const [rawKey, rawValue] of Object.entries(json)) { + const parsedKey = key(rawKey); + const parsedValue = value(rawValue); + result.set(parsedKey, parsedValue); + } + + return result; } export function url(protocol: string, path: any[]): string { diff --git a/src/main/angular/src/app/server-list/Server.ts b/src/main/angular/src/app/server-list/Server.ts deleted file mode 100644 index 3b91d8a..0000000 --- a/src/main/angular/src/app/server-list/Server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {Mode} from "./Mode"; -import {validateBoolean, validateNumber, validateString} from "../crud/CrudHelpers"; - -export class Server { - - constructor( - readonly name: string, - readonly motd: string, - readonly mode: Mode, - readonly port: number, - readonly running: boolean, - readonly icon: boolean, - ) { - // - } - - static fromJson(json: any): Server { - return new Server( - validateString(json.name), - validateString(json.motd), - validateString(json.mode) as Mode, - validateNumber(json.port), - validateBoolean(json.running), - validateBoolean(json.icon), - ); - } - -} diff --git a/src/main/angular/src/app/server-list/server-list.component.html b/src/main/angular/src/app/server-list/server-list.component.html deleted file mode 100644 index d0c1a48..0000000 --- a/src/main/angular/src/app/server-list/server-list.component.html +++ /dev/null @@ -1,34 +0,0 @@ -
- Minecraft -
- -
-
-
- {{server.mode}} - {{server.mode}} -
-
- {{ server.motd }} -
- 10.255.0.1:{{ server.port }} -
-
-
- - Karte - -
-
-  An  -
-
- Aus -
-
-
- -
- (i) - Beim Starten eines Servers werden alle anderen gestoppt. -
diff --git a/src/main/angular/src/app/server-list/Mode.ts b/src/main/angular/src/app/server/Mode.ts similarity index 100% rename from src/main/angular/src/app/server-list/Mode.ts rename to src/main/angular/src/app/server/Mode.ts diff --git a/src/main/angular/src/app/server/Server.ts b/src/main/angular/src/app/server/Server.ts new file mode 100644 index 0000000..644e8fc --- /dev/null +++ b/src/main/angular/src/app/server/Server.ts @@ -0,0 +1,73 @@ +import {Mode} from "./Mode"; +import {orElse, validateBoolean, validateList, validateNumber, validateString} from "../crud/CrudHelpers"; + +export enum Channel { + red = 'red', + blue = 'blue', + green = 'green', + yellow = 'yellow', + magenta = 'magenta', + cyan = 'cyan', +} + +export function validateChannel(json: any): Channel { + return validateString(json) as Channel; +} + +export class ChannelInstance { + + constructor( + readonly channel: Channel, + readonly signal: number, + ) { + // + } + + static fromJson(json: any): ChannelInstance { + return new ChannelInstance( + validateChannel(json.channel), + validateNumber(json.signal), + ); + } + +} + +export class Server { + + constructor( + readonly name: string, + readonly motd: string, + readonly mode: Mode, + readonly port: number, + readonly running: boolean, + readonly webmine: boolean, + readonly icon: boolean, + readonly activators: ChannelInstance[], + readonly observers: ChannelInstance[], + ) { + // + } + + static fromJson(json: any): Server { + return new Server( + validateString(json.name), + validateString(json.motd), + validateString(json.mode) as Mode, + validateNumber(json.port), + validateBoolean(json.running), + validateBoolean(json.webmine), + validateBoolean(json.icon), + validateList(json['activators'], ChannelInstance.fromJson), + validateList(json['observers'], ChannelInstance.fromJson), + ); + } + + isActivatorPowered(channel: Channel): boolean | null { + return orElse(this.activators.filter(c => c.channel === channel).map(c => c.signal > 0)[0], v => v, null); + } + + isObserverPowered(channel: Channel): boolean | null { + return orElse(this.observers.filter(c => c.channel === channel).map(c => c.signal > 0)[0], v => v, null); + } + +} diff --git a/src/main/angular/src/app/server/list/server-list.component.html b/src/main/angular/src/app/server/list/server-list.component.html new file mode 100644 index 0000000..5681e40 --- /dev/null +++ b/src/main/angular/src/app/server/list/server-list.component.html @@ -0,0 +1,52 @@ +
+ Minecraft +
+ +
+
+
+ {{server.mode}} + {{server.mode}} +
+
+ {{ server.motd }} +
+ 10.255.0.1:{{ server.port }} +
+
+
+ + Karte + +
+
+  An  +
+
+ Aus +
+ + +
+
+ {{channel}} + {{channel}} + {{channel}} +
+
+
+
+ {{channel}} + {{channel}} + {{channel}} +
+
+
+ +
+
+ +
+ (x) + Nicht verbunden! +
diff --git a/src/main/angular/src/app/server-list/server-list.component.less b/src/main/angular/src/app/server/list/server-list.component.less similarity index 68% rename from src/main/angular/src/app/server-list/server-list.component.less rename to src/main/angular/src/app/server/list/server-list.component.less index d0fe915..28283f4 100644 --- a/src/main/angular/src/app/server-list/server-list.component.less +++ b/src/main/angular/src/app/server/list/server-list.component.less @@ -3,16 +3,22 @@ .server { display: flex; + flex-wrap: wrap; flex-direction: row; align-items: center; + border-bottom: 1px solid #464d55; > div { padding: 0.25em; } .icon { + height: 2em; + width: 2em; + text-align: center; + img { - height: 2em; + height: 100%; vertical-align: middle; } } @@ -25,6 +31,7 @@ .map { font-weight: bold; + img { height: 2em; vertical-align: middle; @@ -61,6 +68,23 @@ color: white; } + .channelInstanceList { + width: 100%; + display: flex; + flex-direction: row; + + .channelInstance { + display: block; + width: calc(100% / 6); + padding-left: 0.25em; + padding-right: 0.25em; + + img { + width: 100%; + } + } + } + } } diff --git a/src/main/angular/src/app/server-list/server-list.component.ts b/src/main/angular/src/app/server/list/server-list.component.ts similarity index 68% rename from src/main/angular/src/app/server-list/server-list.component.ts rename to src/main/angular/src/app/server/list/server-list.component.ts index 1712474..d0d271a 100644 --- a/src/main/angular/src/app/server-list/server-list.component.ts +++ b/src/main/angular/src/app/server/list/server-list.component.ts @@ -1,9 +1,9 @@ import {Component} from '@angular/core'; import {NgForOf, NgIf} from '@angular/common'; -import {Server} from './Server'; -import {CrudListComponent} from '../crud/CrudListComponent'; -import {ServerService} from './server.service'; -import {url} from '../crud/CrudHelpers'; +import {Channel, Server} from '../Server'; +import {CrudListComponent} from '../../crud/CrudListComponent'; +import {ServerService} from '../server.service'; +import {url} from '../../crud/CrudHelpers'; @Component({ selector: 'app-server-list', @@ -36,4 +36,8 @@ export class ServerListComponent extends CrudListComponent a.port - b.port); } + channels(): Channel[] { + return Object.keys(Channel).filter(key => isNaN(Number(key))).map(key => key as Channel); + } + } diff --git a/src/main/angular/src/app/server-list/server.service.ts b/src/main/angular/src/app/server/server.service.ts similarity index 72% rename from src/main/angular/src/app/server-list/server.service.ts rename to src/main/angular/src/app/server/server.service.ts index b9927d3..8d14469 100644 --- a/src/main/angular/src/app/server-list/server.service.ts +++ b/src/main/angular/src/app/server/server.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {CrudService} from '../crud/CrudService'; -import {Server} from './Server'; +import {Channel, Server} from './Server'; import {ApiService} from '../crud/ApiService'; import {Next} from '../crud/CrudHelpers'; @@ -23,4 +23,8 @@ export class ServerService extends CrudService { this.getSingle([server.name, 'stop'], next); } + webmineActivatorToggle(server: Server, channel: Channel) { + this.getNone(['Activator', server.name, channel, server.isActivatorPowered(channel) === true ? 0 : 15]) + } + } diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index 36e18d7..53a3c56 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -33,3 +33,7 @@ body { background-color: #fdaaaa; border: 0.1em solid red; } + +.hidden { + display: none; +} diff --git a/src/main/java/de/ph87/mc/server/ChannelInstanceDto.java b/src/main/java/de/ph87/mc/server/ChannelInstanceDto.java new file mode 100644 index 0000000..63fecaf --- /dev/null +++ b/src/main/java/de/ph87/mc/server/ChannelInstanceDto.java @@ -0,0 +1,22 @@ +package de.ph87.mc.server; + +import de.ph87.mc.webmine.Channel; +import lombok.Data; +import lombok.NonNull; + +import java.util.Map; + +@Data +public class ChannelInstanceDto { + + @NonNull + public final Channel channel; + + public final int signal; + + public ChannelInstanceDto(@NonNull final Map.Entry entry) { + this.channel = entry.getKey(); + this.signal = entry.getValue(); + } + +} diff --git a/src/main/java/de/ph87/mc/server/EConsumer.java b/src/main/java/de/ph87/mc/server/EConsumer.java new file mode 100644 index 0000000..3e6b3d1 --- /dev/null +++ b/src/main/java/de/ph87/mc/server/EConsumer.java @@ -0,0 +1,8 @@ +package de.ph87.mc.server; + +@FunctionalInterface +public interface EConsumer { + + void accept(final T t) throws E; + +} diff --git a/src/main/java/de/ph87/mc/server/InvalidChannelSignal.java b/src/main/java/de/ph87/mc/server/InvalidChannelSignal.java new file mode 100644 index 0000000..ba2108b --- /dev/null +++ b/src/main/java/de/ph87/mc/server/InvalidChannelSignal.java @@ -0,0 +1,12 @@ +package de.ph87.mc.server; + +import de.ph87.mc.webmine.Channel; +import lombok.NonNull; + +public class InvalidChannelSignal extends Exception { + + public InvalidChannelSignal(@NonNull final Server server, @NonNull final Channel channel, final int signal) { + super("Invalid signal received: server=%s, channel=%s, signal=%s".formatted(server.properties.name, channel, signal)); + } + +} diff --git a/src/main/java/de/ph87/mc/server/Properties.java b/src/main/java/de/ph87/mc/server/Properties.java index fcdc4a1..86e3edb 100644 --- a/src/main/java/de/ph87/mc/server/Properties.java +++ b/src/main/java/de/ph87/mc/server/Properties.java @@ -31,7 +31,7 @@ public class Properties { final java.util.Properties properties = new java.util.Properties(); try (final FileReader reader = new FileReader(file)) { properties.load(reader); - name = properties.getProperty("level-name"); + name = directory.getName(); motd = properties.getProperty("motd"); mode = Mode.valueOf(properties.getProperty("gamemode").toUpperCase(Locale.ROOT)); port = Integer.parseInt(properties.getProperty("server-port")); diff --git a/src/main/java/de/ph87/mc/server/Server.java b/src/main/java/de/ph87/mc/server/Server.java index d36783e..8a6b1cc 100644 --- a/src/main/java/de/ph87/mc/server/Server.java +++ b/src/main/java/de/ph87/mc/server/Server.java @@ -1,11 +1,17 @@ package de.ph87.mc.server; +import de.ph87.mc.webmine.Channel; import jakarta.annotation.Nullable; import lombok.Data; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; @Data @Slf4j @@ -30,6 +36,12 @@ public class Server { public boolean shutdown = false; + public boolean webmine = false; + + private final Map activators = new HashMap<>(); + + private final Map observers = new HashMap<>(); + public Server(@NonNull final File directory) throws NoMinecraftServer { this.directory = directory; this.pidFile = new File(directory, "pid"); @@ -37,19 +49,47 @@ public class Server { this.properties = new Properties(directory); } - public boolean isRunning() { - synchronized (lock) { - return process != null && process.isAlive(); - } - } - @Override public String toString() { - return "Server(%s, \"%s\", %s)".formatted(properties.mode, properties.motd, isRunning() ? "RUNNING" : "stopped"); + return "Server(%s, \"%s\", %s)".formatted(properties.mode, properties.motd, process != null ? "RUNNING" : "stopped"); } public boolean eq(@NonNull final Server other) { return properties.name.equals(other.properties.name); } + public void setActivator(final Channel channel, final int signal) throws InvalidChannelSignal { + if (signal < 0 || signal > 15) { + throw new InvalidChannelSignal(this, channel, signal); + } + synchronized (activators) { + activators.put(channel, signal); + publish(channel, signal); + log.info("Activator changed: name={}, channel={}, signal={}", properties.name, channel, signal); + } + } + + public void setObserver(final Channel channel, final int signal) throws InvalidChannelSignal { + if (signal < 0 || signal > 15) { + throw new InvalidChannelSignal(this, channel, signal); + } + synchronized (observers) { + observers.put(channel, signal); + log.info("Observer changed: name={}: {}={}", properties.name, channel, signal); + } + } + + private void publish(@NonNull final Channel channel, final int signal) { + new Thread(() -> { + try { + final URLConnection con = URI.create("http://localhost:8123/set?channel=%s&signal=%d".formatted(channel, signal)).toURL().openConnection(); + con.setConnectTimeout(500); + con.setReadTimeout(500); + con.connect(); + } catch (IOException e) { + log.error("Failed to publish Activator to Server: name={}, channel={}, signal={}, error={}", properties.name, channel, signal, e.getMessage()); + } + }).start(); + } + } diff --git a/src/main/java/de/ph87/mc/server/ServerDto.java b/src/main/java/de/ph87/mc/server/ServerDto.java index 58c10c0..9b752e2 100644 --- a/src/main/java/de/ph87/mc/server/ServerDto.java +++ b/src/main/java/de/ph87/mc/server/ServerDto.java @@ -4,28 +4,44 @@ import de.ph87.mc.websocket.IWebsocketMessage; import lombok.Data; import lombok.NonNull; +import java.util.List; + @Data public class ServerDto implements IWebsocketMessage { + @NonNull public final String name; + @NonNull public final String motd; + @NonNull public final Mode mode; public final int port; public final boolean running; + public final boolean webmine; + public boolean icon; - public ServerDto(final @NonNull Server server) { + @NonNull + public final List activators; + + @NonNull + public final List observers; + + public ServerDto(@NonNull final Server server) { this.name = server.properties.name; this.motd = server.properties.motd; this.mode = server.properties.mode; this.port = server.properties.port; - this.running = server.isRunning(); + this.running = server.process != null; + this.webmine = server.webmine; this.icon = server.iconFile.isFile(); + this.activators = server.getActivators().entrySet().stream().map(ChannelInstanceDto::new).toList(); + this.observers = server.getObservers().entrySet().stream().map(ChannelInstanceDto::new).toList(); } } diff --git a/src/main/java/de/ph87/mc/server/ServerService.java b/src/main/java/de/ph87/mc/server/ServerService.java index 48549f5..3ca98a0 100644 --- a/src/main/java/de/ph87/mc/server/ServerService.java +++ b/src/main/java/de/ph87/mc/server/ServerService.java @@ -1,10 +1,13 @@ package de.ph87.mc.server; +import de.ph87.mc.webmine.Channel; import jakarta.annotation.PostConstruct; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.io.File; @@ -14,10 +17,12 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @Slf4j @Service +@EnableScheduling @RequiredArgsConstructor public class ServerService { @@ -37,6 +42,21 @@ public class ServerService { } } + @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) + public void schedule() { + synchronized (serversLock) { + servers.forEach(this::refresh); + } + } + + private void refresh(@NonNull final Server server) { + if (server.process != null && !server.process.isAlive()) { + log.error("Server crash detected: name={}", server.properties.name); + server.process = null; + publish(server); + } + } + @NonNull private Optional _tryLoadingFromDir(@NonNull final File directory) { try { @@ -61,13 +81,14 @@ public class ServerService { private void start(@NonNull final Server server) { synchronized (server.lock) { - if (server.isRunning()) { + if (server.process != null) { log.warn("Server is already running: name={}", server.properties.name); return; } stopAll(); + waitForAllToBeStopped(); log.info("Starting server: name={}", server.properties.name); - final ProcessBuilder builder = new ProcessBuilder("java", "-jar", "server.jar"); + final ProcessBuilder builder = new ProcessBuilder("java", "-Xms2G", "-Xmx2G", "-jar", "server.jar", "nogui"); builder.directory(server.directory); try { server.process = builder.start(); @@ -78,9 +99,21 @@ public class ServerService { } } + private void waitForAllToBeStopped() { + synchronized (serversLock) { + try { + while (servers.stream().anyMatch(server -> server.process != null)) { + serversLock.wait(1000); + } + } catch (InterruptedException e) { + log.error("Interrupted while waiting for ALL servers to stop"); + } + } + } + private void stop(@NonNull final Server server) { synchronized (server.lock) { - if (!server.isRunning()) { + if (server.process == null) { log.warn("Server is not running: name={}", server.properties.name); return; } @@ -121,6 +154,14 @@ public class ServerService { return publish(server); } + @NonNull + @SuppressWarnings("UnusedReturnValue") + private ServerDto setE(final @NonNull String name, @NonNull final EConsumer modifier) throws E { + final Server server = getByName(name); + modifier.accept(server); + return publish(server); + } + @NonNull private ServerDto publish(@NonNull final Server server) { final ServerDto dto = new ServerDto(server); @@ -131,7 +172,7 @@ public class ServerService { @NonNull private Server getByName(@NonNull final String name) { synchronized (serversLock) { - return servers.stream().filter(server -> server.properties.name.equals(name)).findFirst().orElseThrow(); + return servers.stream().filter(server -> server.properties.name.equals(name)).peek(this::refresh).findFirst().orElseThrow(); } } @@ -156,4 +197,12 @@ public class ServerService { } } + public void setActivator(final @NonNull String name, final Channel channel, final int signal) throws InvalidChannelSignal { + setE(name, server -> server.setActivator(channel, signal)); + } + + public void setObserver(final @NonNull String name, final Channel channel, final int signal) throws InvalidChannelSignal { + setE(name, server -> server.setObserver(channel, signal)); + } + } diff --git a/src/main/java/de/ph87/mc/webmine/Channel.java b/src/main/java/de/ph87/mc/webmine/Channel.java new file mode 100644 index 0000000..ba488b3 --- /dev/null +++ b/src/main/java/de/ph87/mc/webmine/Channel.java @@ -0,0 +1,5 @@ +package de.ph87.mc.webmine; + +public enum Channel { + red, blue, green, yellow, magenta, cyan, +} diff --git a/src/main/java/de/ph87/mc/webmine/WebmineController.java b/src/main/java/de/ph87/mc/webmine/WebmineController.java new file mode 100644 index 0000000..3535d08 --- /dev/null +++ b/src/main/java/de/ph87/mc/webmine/WebmineController.java @@ -0,0 +1,57 @@ +package de.ph87.mc.webmine; + +import de.ph87.mc.server.InvalidChannelSignal; +import de.ph87.mc.server.ServerService; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Arrays; + +@Slf4j +@CrossOrigin +@RestController +@RequestMapping("Server") +public class WebmineController { + + private final ServerService serverService; + + public WebmineController(final ServerService serverService) { + this.serverService = serverService; + } + + @GetMapping("Activator/{serverName}/{channelName}/{signal}") + public void activator(@NonNull @PathVariable final String serverName, @NonNull @PathVariable String channelName, @PathVariable int signal) { + final Channel channel = Arrays.stream(Channel.values()).filter(c -> c.name().equals(channelName)).findFirst().orElseThrow(() -> { + final String message = "No such channel: %s".formatted(channelName); + log.error(message); + return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); + }); + try { + serverService.setActivator(serverName, channel, signal); + } catch (InvalidChannelSignal e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + @GetMapping("Observer/{serverName}/{channelName}/{signal}") + public void observer(@NonNull @PathVariable final String serverName, @NonNull @PathVariable String channelName, @PathVariable int signal) { + final Channel channel = Arrays.stream(Channel.values()).filter(c -> c.name().equals(channelName)).findFirst().orElseThrow(() -> { + final String message = "No such channel: %s".formatted(channelName); + log.error(message); + return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); + }); + try { + serverService.setObserver(serverName, channel, signal); + } catch (InvalidChannelSignal e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + +}