McManager/src/main/java/de/ph87/mc/server/ServerService.java

160 lines
4.7 KiB
Java

package de.ph87.mc.server;
import jakarta.annotation.PostConstruct;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@Slf4j
@Service
@RequiredArgsConstructor
public class ServerService {
private final ApplicationEventPublisher applicationEventPublisher;
private final ServerConfig serverConfig;
private final Object serversLock = new Object();
private List<Server> servers = new ArrayList<>();
@PostConstruct
public void startup() {
final File ROOT = new File(serverConfig.getPath());
synchronized (serversLock) {
servers = Arrays.stream(Objects.requireNonNull(ROOT.listFiles())).map(this::_tryLoadingFromDir).filter(Optional::isPresent).map(Optional::get).toList();
}
}
@NonNull
private Optional<Server> _tryLoadingFromDir(@NonNull final File directory) {
try {
final Server server = new Server(directory);
servers.stream().filter(server::eq).findFirst().ifPresent(old -> server.process = old.process);
return Optional.of(server);
} catch (NoMinecraftServer e) {
log.warn(e.getMessage());
}
return Optional.empty();
}
@NonNull
public ServerDto start(@NonNull final String name) {
return set(name, this::start);
}
@NonNull
public ServerDto stop(@NonNull final String name) {
return set(name, this::stop);
}
private void start(@NonNull final Server server) {
synchronized (server.lock) {
if (server.isRunning()) {
log.warn("Server is already running: name={}", server.properties.name);
return;
}
stopAll();
log.info("Starting server: name={}", server.properties.name);
final ProcessBuilder builder = new ProcessBuilder("java", "-jar", "server.jar");
builder.directory(server.directory);
try {
server.process = builder.start();
log.info("Server started: name={}", server.properties.name);
} catch (IOException e) {
log.error("Failed to start Server: name={}, error={}", server.properties.name, e.getMessage());
}
}
}
private void stop(@NonNull final Server server) {
synchronized (server.lock) {
if (!server.isRunning()) {
log.warn("Server is not running: name={}", server.properties.name);
return;
}
if (server.shutdown) {
log.warn("Server is already shutting down: name={}", server.properties.name);
return;
}
server.shutdown = true;
log.info("Stopping Server: name={}", server.properties.name);
new Thread(() -> _stop_async(server), "STOP-" + server.properties.name).start();
}
}
private void _stop_async(@NonNull final Server server) {
log.debug("Thread spawned: name={}", server.properties.name);
synchronized (server.lock) {
assert server.process != null;
log.info("Stopping server: name={}", server.properties.name);
server.process.destroy();
try {
server.process.waitFor();
log.info("Server stopped: name={}", server.properties.name);
} catch (InterruptedException e) {
log.error("Interrupted while waiting for server to stop: name={}", server.properties.name);
} finally {
server.process = null;
server.shutdown = false;
publish(server);
log.debug("Thread terminated: name={}", server.properties.name);
}
}
}
@NonNull
private ServerDto set(final @NonNull String name, @NonNull final Consumer<Server> modifier) {
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);
applicationEventPublisher.publishEvent(dto);
return dto;
}
@NonNull
private Server getByName(@NonNull final String name) {
synchronized (serversLock) {
return servers.stream().filter(server -> server.properties.name.equals(name)).findFirst().orElseThrow();
}
}
@NonNull
public List<ServerDto> findAll() {
synchronized (serversLock) {
return servers.stream().map(ServerDto::new).toList();
}
}
@NonNull
public File getIconFileByName(@NonNull final String name) {
synchronized (serversLock) {
return getByName(name).iconFile;
}
}
@NonNull
public List<ServerDto> stopAll() {
synchronized (serversLock) {
return servers.stream().peek(this::stop).map(ServerDto::new).toList();
}
}
}