currently refactoring a lot
This commit is contained in:
parent
701a5ff620
commit
7fe7409a15
49 changed files with 856 additions and 767 deletions
|
|
@ -21,6 +21,9 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/org.mongodb/bson
|
||||
implementation("org.mongodb:bson:5.8.0")
|
||||
api("org.mongodb:bson:5.8.0")
|
||||
|
||||
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core
|
||||
api("de.kentoj.scrow:kencommandapi-core:0.21")
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package de.kentoj.scrowlib.convention;
|
||||
|
||||
import de.kentoj.kencommandapi.api.platform.MessageStyle;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ScrowMessageStyle implements MessageStyle {
|
||||
|
||||
@Getter
|
||||
private final Component prefix;
|
||||
@Getter
|
||||
private final String prefixText;
|
||||
@Getter
|
||||
private final TextColor prefixColor;
|
||||
|
||||
public ScrowMessageStyle(String prefixText, TextColor prefixColor) {
|
||||
this.prefixText = prefixText;
|
||||
this.prefixColor = prefixColor;
|
||||
this.prefix = Component.text(prefixText)
|
||||
.color(prefixColor)
|
||||
.decorate(TextDecoration.BOLD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component ok(Component msg) {
|
||||
return prefix.append(Component.text(" ☆ ").color(NamedTextColor.GRAY))
|
||||
.append(msg.colorIfAbsent(NamedTextColor.GRAY));
|
||||
}
|
||||
|
||||
public Component list(Component header, List<? extends Component> list) {
|
||||
var res = ok(header);
|
||||
for (Component component : list) {
|
||||
res = res.appendNewline()
|
||||
.append(Component.text(" - ").color(NamedTextColor.GRAY))
|
||||
.append(component.colorIfAbsent(NamedTextColor.GRAY));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component err(Component msg) {
|
||||
return ok(msg.color(NamedTextColor.RED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component exception(Component msg) {
|
||||
return ok(Component.text("ERROR: ")
|
||||
.color(NamedTextColor.DARK_RED)
|
||||
.append(msg)
|
||||
.decorate(TextDecoration.BOLD));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package de.kentoj.scrowlib.messaging;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import io.nats.client.Connection;
|
||||
import io.nats.client.Dispatcher;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Slf4j
|
||||
public class MessageBroker {
|
||||
|
||||
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
private final Connection nats;
|
||||
protected final Dispatcher dispatcher;
|
||||
|
||||
public MessageBroker(Connection nats) {
|
||||
this.nats = nats;
|
||||
this.dispatcher = nats.createDispatcher();
|
||||
|
||||
}
|
||||
|
||||
public void publish(String subject, Result<Document, String> result) {
|
||||
nats.publish(subject, ResultRepository.toBytes(result));
|
||||
}
|
||||
|
||||
public void handle(String subject, RequestHandler handler) {
|
||||
dispatcher.subscribe(subject, msg -> {
|
||||
if (msg.getReplyTo() == null) {
|
||||
log.warn("request in {} has replyTo unset; ignoring", subject);
|
||||
return;
|
||||
}
|
||||
handler.handle(DocumentRepository.fromMessage(msg))
|
||||
.thenAccept(result ->
|
||||
executor.submit(() -> publish(msg.getReplyTo(), result))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public Result<Document, String> request(String subject, Document document) {
|
||||
var response = nats.request(subject, DocumentRepository.toBytes(document)).join();
|
||||
return ResultRepository.fromDocument(DocumentRepository.fromMessage(response));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package de.kentoj.scrowlib.messaging;
|
||||
|
||||
import de.kentoj.scrowlib.messaging.respose.SafeResult;
|
||||
import io.nats.client.Connection;
|
||||
import io.nats.client.Dispatcher;
|
||||
import io.nats.client.Message;
|
||||
import org.bson.Document;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class NatsRepository {
|
||||
|
||||
private final Connection nats;
|
||||
|
||||
private final Dispatcher dispatcher;
|
||||
|
||||
public NatsRepository(Connection nats) {
|
||||
this.nats = nats;
|
||||
this.dispatcher = nats.createDispatcher();
|
||||
}
|
||||
|
||||
public Mono<@NotNull Void> publishSafeResult(String subject, SafeResult safeResult) {
|
||||
return this.publish(subject, safeResult.toDocument());
|
||||
}
|
||||
|
||||
public Mono<@NotNull Void> publish(String subject, Document document) {
|
||||
return publishRaw(subject, DocumentRepository.toBytes(document));
|
||||
}
|
||||
|
||||
public Mono<@NotNull Void> publishRaw(String subject, byte[] data) {
|
||||
return Mono.fromRunnable(() -> nats.publish(subject, data));
|
||||
}
|
||||
|
||||
public Flux<@NotNull SubscriptionContext> subscribe(String subject) {
|
||||
return Flux.create(sink -> {
|
||||
dispatcher.subscribe(subject, msg -> {
|
||||
try {
|
||||
sink.next(new SubscriptionContext(msg));
|
||||
} catch (Throwable e) {
|
||||
sink.error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<@NotNull Message> requestRaw(String subject, Document document) {
|
||||
return Mono.fromFuture(nats.request(subject, DocumentRepository.toBytes(document)));
|
||||
}
|
||||
|
||||
public Mono<@NotNull Message> requestRaw(String subject, byte[] data) {
|
||||
return Mono.fromFuture(nats.request(subject, data));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package de.kentoj.scrowlib.messaging;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RequestHandler {
|
||||
/**
|
||||
* Processes a request and replies
|
||||
*
|
||||
* @return result to send back to the requesting client.
|
||||
*/
|
||||
CompletionStage<Result<Document, String>> handle(Document request);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package de.kentoj.scrowlib.messaging;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.bson.Document;
|
||||
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.NONE)
|
||||
public class ResultRepository {
|
||||
|
||||
public static byte[] toBytes(Result<Document, String> result) {
|
||||
var doc = result.hasSuccess()
|
||||
? new Document("data", result.getSuccess().orElseThrow())
|
||||
: new Document("error", result.getFailure().orElseThrow());
|
||||
return DocumentRepository.toBytes(doc);
|
||||
}
|
||||
|
||||
public static Result<Document, String> fromDocument(Document doc) {
|
||||
var error = doc.getString("error");
|
||||
return error != null
|
||||
? Results.failure(error)
|
||||
: Results.success(doc.get("data", Document.class));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package de.kentoj.scrowlib.messaging;
|
||||
|
||||
import io.nats.client.Message;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.bson.Document;
|
||||
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
public class SubscriptionContext {
|
||||
Message msg;
|
||||
Document document;
|
||||
|
||||
public SubscriptionContext(Message msg) {
|
||||
this(msg, DocumentRepository.fromMessage(msg));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package de.kentoj.scrowlib.messaging.respose;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bson.Document;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public final class SafeErrorResult extends SafeResult {
|
||||
private final String type;
|
||||
private final String message;
|
||||
|
||||
public SafeErrorResult(Throwable throwable) {
|
||||
this.type = throwable.getClass().getCanonicalName();
|
||||
this.message = throwable.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document unwrap() {
|
||||
throw this.toException();
|
||||
}
|
||||
|
||||
public RuntimeException toException() {
|
||||
return new RuntimeException(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document toDocument() {
|
||||
return new Document("error", new Document()
|
||||
.append("type", this.type)
|
||||
.append("message", this.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package de.kentoj.scrowlib.messaging.respose;
|
||||
|
||||
import de.kentoj.scrowlib.messaging.DocumentRepository;
|
||||
import io.nats.client.Message;
|
||||
import org.bson.Document;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public abstract sealed class SafeResult permits SafeErrorResult, SafeSuccessResult {
|
||||
|
||||
/**
|
||||
* @return the SafeResult as a Document for transit
|
||||
*/
|
||||
public abstract Document toDocument();
|
||||
|
||||
/**
|
||||
* @return the data stored in the SafeResult
|
||||
* @throws RuntimeException if the SafeResult represents an error
|
||||
*/
|
||||
public abstract Document unwrap();
|
||||
|
||||
public boolean isError() {
|
||||
return this instanceof SafeErrorResult;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return this instanceof SafeSuccessResult;
|
||||
}
|
||||
|
||||
public SafeErrorResult asError() {
|
||||
return (SafeErrorResult) this;
|
||||
}
|
||||
|
||||
public SafeSuccessResult asSuccess() {
|
||||
return (SafeSuccessResult) this;
|
||||
}
|
||||
|
||||
public static Document unwrap(Message message) {
|
||||
return fromMessage(message).unwrap();
|
||||
}
|
||||
|
||||
public static SafeResult wrap(Document document) {
|
||||
return new SafeSuccessResult(document);
|
||||
}
|
||||
|
||||
public static SafeResult wrap(Throwable throwable) {
|
||||
return new SafeErrorResult(throwable);
|
||||
}
|
||||
|
||||
public static SafeResult fromMessage(Message message) {
|
||||
return fromDocument(DocumentRepository.fromMessage(message));
|
||||
}
|
||||
|
||||
public static SafeResult fromDocument(Document document) {
|
||||
if (document.containsKey("error")) {
|
||||
var doc = document.get("error", Document.class);
|
||||
return new SafeErrorResult(
|
||||
doc.getString("type"),
|
||||
doc.getString("message")
|
||||
);
|
||||
} else if (document.containsKey("data")) {
|
||||
return new SafeSuccessResult(document.get("data", Document.class));
|
||||
}
|
||||
throw new IllegalArgumentException("document does not conform to SafeDocument format");
|
||||
}
|
||||
|
||||
public static Mono<@NotNull SafeResult> mono(Mono<@NotNull Document> mono) {
|
||||
return mono.map(SafeResult::wrap)
|
||||
.onErrorResume(err -> Mono.just(SafeResult.wrap(err)));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package de.kentoj.scrowlib.messaging.respose;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bson.Document;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public final class SafeSuccessResult extends SafeResult {
|
||||
private final Document data;
|
||||
|
||||
@Override
|
||||
public Document unwrap() {
|
||||
return this.getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document toDocument() {
|
||||
return new Document("data", this.getData());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package de.kentoj.scrowlib.servermanager;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface InstanceManager {
|
||||
|
||||
/**
|
||||
* @return Mono with handle of deployed server
|
||||
*/
|
||||
Result<ServerInstance, String> deployInstance(String template);
|
||||
|
||||
Result<Void, String> destroyInstance(String handle);
|
||||
|
||||
/**
|
||||
* @return Flux with handles of running servers
|
||||
*/
|
||||
Result<List<ServerInstance>, String> getInstances();
|
||||
|
||||
Result<@Nullable ServerInstance, String> getInstance(String handle);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package de.kentoj.scrowlib.servermanager;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import de.kentoj.scrowlib.messaging.MessageBroker;
|
||||
import io.nats.client.Connection;
|
||||
import org.bson.Document;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class InstanceManagerImpl implements InstanceManager {
|
||||
|
||||
private final MessageBroker messageBroker;
|
||||
|
||||
public InstanceManagerImpl(Connection natsCon) {
|
||||
messageBroker = new MessageBroker(natsCon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<ServerInstance, String> deployInstance(String template) {
|
||||
var result = messageBroker.request("instance.deploy", new Document("template", template));
|
||||
return result.mapSuccess(ServerInstance::fromDocument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void, String> destroyInstance(String handle) {
|
||||
var result = messageBroker.request("instance.destroy", new Document("handle", handle));
|
||||
return result.mapSuccess(__ -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<ServerInstance>, String> getInstances() {
|
||||
var result = messageBroker.request("instance.list", new Document());
|
||||
return result.mapSuccess(doc -> doc.getList("instances", Document.class)
|
||||
.stream()
|
||||
.map(ServerInstance::fromDocument)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<@Nullable ServerInstance, String> getInstance(String handle) {
|
||||
var result = messageBroker.request("instance.get", new Document("handle", handle));
|
||||
return result.mapSuccess(ServerInstance::fromDocument);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,25 +2,27 @@ package de.kentoj.scrowlib.servermanager;
|
|||
|
||||
import com.google.common.base.Preconditions;
|
||||
import lombok.Value;
|
||||
import org.bson.Document;
|
||||
|
||||
@Value
|
||||
public class ServerInstance {
|
||||
String templateName;
|
||||
String handle;
|
||||
String template;
|
||||
int port;
|
||||
String state;
|
||||
|
||||
public ServerInstance(String templateName, String handle, int port, String state) {
|
||||
Preconditions.checkNotNull(templateName);
|
||||
public ServerInstance(String handle, String template, int port) {
|
||||
Preconditions.checkNotNull(template);
|
||||
Preconditions.checkNotNull(handle);
|
||||
Preconditions.checkNotNull(state);
|
||||
this.templateName = templateName;
|
||||
this.template = template;
|
||||
this.handle = handle;
|
||||
this.port = port;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public ServerInstance(String templateName, String handle, int port) {
|
||||
this(templateName, handle, port, "STARTING");
|
||||
static ServerInstance fromDocument(Document document) {
|
||||
return new ServerInstance(
|
||||
document.getString("handle"),
|
||||
document.getString("template"),
|
||||
document.getInteger("port")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
package de.kentoj.scrowlib.servermanager;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ServerManager {
|
||||
|
||||
/**
|
||||
* @return Mono with handle of deployed server
|
||||
*/
|
||||
Mono<@NotNull ServerInstance> deployServer(String template);
|
||||
|
||||
/**
|
||||
* Attempts a clean shutdown, if that doesn't succeed, kills the server.
|
||||
*/
|
||||
Mono<@NotNull Void> destroyServer(String handle);
|
||||
|
||||
/**
|
||||
* @return Flux with handles of running servers
|
||||
*/
|
||||
Flux<@NotNull ServerInstance> getRunningServers();
|
||||
|
||||
Mono<@NotNull ServerInstance> getInfo(String handle);
|
||||
|
||||
Mono<@NotNull Void> setState(String handle, String state);
|
||||
|
||||
Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle);
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package de.kentoj.scrowlib.servermanager;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import de.kentoj.scrowlib.messaging.NatsRepository;
|
||||
import de.kentoj.scrowlib.messaging.respose.SafeResult;
|
||||
import io.nats.client.Connection;
|
||||
import org.bson.Document;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ServerManagerImpl implements ServerManager {
|
||||
|
||||
private final NatsRepository natsRepo;
|
||||
|
||||
public ServerManagerImpl(Connection nats) {
|
||||
this.natsRepo = new NatsRepository(nats);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<@NotNull ServerInstance> deployServer(String template) {
|
||||
return natsRepo.requestRaw("crown.deploy-server", new Document("template", template))
|
||||
.map(SafeResult::unwrap)
|
||||
.map(doc -> new ServerInstance(
|
||||
template,
|
||||
doc.getString("handle"),
|
||||
doc.getInteger("port")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<@NotNull ServerInstance> getRunningServers() {
|
||||
return natsRepo.requestRaw("crown.list-servers", new Document())
|
||||
.map(SafeResult::unwrap)
|
||||
.map(doc ->
|
||||
doc.getList("instances", Document.class)
|
||||
)
|
||||
.flatMapIterable(l -> l)
|
||||
.map(doc -> new ServerInstance(
|
||||
doc.getString("templateName"),
|
||||
doc.getString("handle"),
|
||||
doc.getInteger("port"),
|
||||
doc.getString("state")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<@NotNull Void> destroyServer(String handle) {
|
||||
return natsRepo.requestRaw("crown.destroy-server", new Document("handle", handle))
|
||||
.map(SafeResult::unwrap)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<@NotNull ServerInstance> getInfo(String handle) {
|
||||
return natsRepo.requestRaw("crown.get-info", new Document("handle", handle))
|
||||
.map(SafeResult::unwrap)
|
||||
.map(doc -> new ServerInstance(
|
||||
doc.getString("templateName"),
|
||||
handle,
|
||||
doc.getInteger("port"),
|
||||
doc.getString("state")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<@NotNull Void> setState(String handle, String state) {
|
||||
Preconditions.checkArgument(!state.contains(" "), "state may not contain spaces");
|
||||
var payload = new Document()
|
||||
.append("handle", handle)
|
||||
.append("state", state);
|
||||
return natsRepo.requestRaw("crown.set-state", payload)
|
||||
.map(SafeResult::unwrap)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle) {
|
||||
var payload = new Document()
|
||||
.append("playerId", playerId.toString())
|
||||
.append("handle", serverHandle);
|
||||
return natsRepo.requestRaw("proxy.send-player", payload)
|
||||
.map(SafeResult::unwrap)
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.kentoj.scrowlib.utils;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.NONE)
|
||||
public class EnvUtils {
|
||||
public static String envOrThrow(String name) {
|
||||
var env = System.getenv(name);
|
||||
if (env == null) throw new IllegalStateException("environment variable " + name + " not set");
|
||||
return env;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue