179 lines
6.3 KiB
Java
179 lines
6.3 KiB
Java
|
package tc.oc.api.minecraft;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.lang.reflect.Method;
|
||
|
import java.util.concurrent.ExecutionException;
|
||
|
import java.util.logging.Level;
|
||
|
import java.util.logging.Logger;
|
||
|
import javax.annotation.Nullable;
|
||
|
import javax.inject.Inject;
|
||
|
import javax.inject.Singleton;
|
||
|
|
||
|
import com.google.common.base.Function;
|
||
|
import com.google.common.eventbus.EventBus;
|
||
|
import com.google.common.util.concurrent.Futures;
|
||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||
|
import tc.oc.api.connectable.Connectable;
|
||
|
import tc.oc.api.docs.Server;
|
||
|
import tc.oc.api.docs.virtual.ServerDoc;
|
||
|
import tc.oc.api.exceptions.ApiNotConnected;
|
||
|
import tc.oc.api.message.MessageListener;
|
||
|
import tc.oc.api.message.MessageQueue;
|
||
|
import tc.oc.api.message.types.ModelUpdate;
|
||
|
import tc.oc.api.minecraft.config.MinecraftApiConfiguration;
|
||
|
import tc.oc.api.minecraft.servers.LocalServerDocument;
|
||
|
import tc.oc.api.minecraft.servers.LocalServerReconfigureEvent;
|
||
|
import tc.oc.api.minecraft.servers.StartupServerDocument;
|
||
|
import tc.oc.api.servers.ServerService;
|
||
|
import tc.oc.commons.core.logging.Loggers;
|
||
|
import tc.oc.commons.core.reflect.Methods;
|
||
|
import tc.oc.commons.core.util.MethodHandleInvoker;
|
||
|
import tc.oc.commons.core.util.ProxyUtils;
|
||
|
import tc.oc.minecraft.scheduler.SyncExecutor;
|
||
|
|
||
|
@Singleton
|
||
|
public class MinecraftServiceImpl implements MinecraftService, MessageListener, Connectable {
|
||
|
|
||
|
private final Logger logger;
|
||
|
private final EventBus eventBus;
|
||
|
private final SyncExecutor syncExecutor;
|
||
|
private final ServerService serverService;
|
||
|
private final MinecraftApiConfiguration apiConfiguration;
|
||
|
private final StartupServerDocument startupDocument;
|
||
|
private final MessageQueue serverQueue;
|
||
|
private final Server everfreshLocalServer;
|
||
|
|
||
|
private @Nullable Server server;
|
||
|
|
||
|
@Inject MinecraftServiceImpl(Loggers loggers,
|
||
|
EventBus eventBus,
|
||
|
SyncExecutor syncExecutor,
|
||
|
ServerService serverService,
|
||
|
MinecraftApiConfiguration apiConfiguration,
|
||
|
MessageQueue serverQueue,
|
||
|
LocalServerDocument localServerDocument,
|
||
|
StartupServerDocument startupDocument) {
|
||
|
|
||
|
this.logger = loggers.get(getClass());
|
||
|
this.eventBus = eventBus;
|
||
|
this.syncExecutor = syncExecutor;
|
||
|
this.serverService = serverService;
|
||
|
this.apiConfiguration = apiConfiguration;
|
||
|
this.serverQueue = serverQueue;
|
||
|
|
||
|
this.everfreshLocalServer = ProxyUtils.newProxy(Server.class, new MethodHandleInvoker() {
|
||
|
@Override
|
||
|
protected Object targetFor(Method method) {
|
||
|
if(server != null) return server;
|
||
|
if(Methods.respondsTo(localServerDocument, method)) return localServerDocument;
|
||
|
throw new ApiNotConnected();
|
||
|
}
|
||
|
});
|
||
|
this.startupDocument = startupDocument;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean listenWhileSuspended() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void assertConnected() throws ApiNotConnected {
|
||
|
if(server == null) {
|
||
|
throw new ApiNotConnected();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Server getLocalServer() {
|
||
|
assertConnected();
|
||
|
return server;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isLocalServer(ServerDoc.Identity server) {
|
||
|
return getLocalServer()._id().equals(server._id());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a magic {@link Server} document for the local server that
|
||
|
* always has the most recent data. If the API is not connected,
|
||
|
* the fields provided by {@link LocalServerDocument} will work,
|
||
|
* but trying to read any other field will throw {@link ApiNotConnected}.
|
||
|
*/
|
||
|
@Override
|
||
|
public Server everfreshLocalServer() {
|
||
|
return everfreshLocalServer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a server configuration change to the remote API. The API will respond with
|
||
|
* the latest version of the server document, and only at that point will the local
|
||
|
* document be modified. This is done by calling {@link #handleReconfigure},
|
||
|
* and subclasses can override that method if they want to fire a reconfigure event.
|
||
|
*
|
||
|
* This method returns a future that completes after the API responds to the update
|
||
|
* AND the local server document has been replaced with the result.
|
||
|
*/
|
||
|
@Override
|
||
|
public ListenableFuture<Server> updateLocalServer(ServerDoc.Partial update) {
|
||
|
return Futures.transform(
|
||
|
serverService.update(apiConfiguration.serverId(), update),
|
||
|
(Function<? super Server, ? extends Server>) result -> {
|
||
|
handleLocalReconfigure(result);
|
||
|
return result;
|
||
|
},
|
||
|
syncExecutor
|
||
|
);
|
||
|
}
|
||
|
|
||
|
@HandleMessage
|
||
|
public void handleReconfigure(ModelUpdate<Server> message) {
|
||
|
if(server != null && server._id().equals(message.document()._id())) {
|
||
|
handleLocalReconfigure(message.document());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void handleLocalReconfigure(Server newConfig) {
|
||
|
Server oldConfig = this.server;
|
||
|
this.server = newConfig;
|
||
|
if(logger.isLoggable(Level.FINE)) {
|
||
|
logger.fine("Local server reconfigured: " + newConfig);
|
||
|
}
|
||
|
eventBus.post(new LocalServerReconfigureEvent(oldConfig, newConfig));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void connect() throws IOException {
|
||
|
try {
|
||
|
serverQueue.subscribe(this, syncExecutor);
|
||
|
|
||
|
handleLocalReconfigure(serverService.update(apiConfiguration.serverId(), startupDocument).get());
|
||
|
|
||
|
logger.info("Connected to API as server." + getLocalServer()._id());
|
||
|
} catch (Exception e) {
|
||
|
this.processIntoIOException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void disconnect() throws IOException {
|
||
|
try {
|
||
|
serverService.update(
|
||
|
apiConfiguration.serverId(),
|
||
|
(ServerDoc.Online) () -> false
|
||
|
).get();
|
||
|
|
||
|
serverQueue.unsubscribe(this);
|
||
|
} catch (Exception e) {
|
||
|
processIntoIOException(e);
|
||
|
}
|
||
|
this.server = null;
|
||
|
}
|
||
|
|
||
|
private void processIntoIOException(Throwable t) throws IOException {
|
||
|
if (t instanceof IOException) throw (IOException)t;
|
||
|
if (t instanceof ExecutionException) this.processIntoIOException(t.getCause());
|
||
|
throw new IOException(t);
|
||
|
}
|
||
|
}
|