/**
*
*/
package com.teefun.service.teeworlds.impl;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.SocketUtils;
import com.google.common.eventbus.EventBus;
import com.teefun.events.event.ServerFreeEvent;
import com.teefun.exception.TeeFunRuntimeException;
import com.teefun.model.teeworlds.TeeworldsConfig;
import com.teefun.model.teeworlds.TeeworldsServer;
import com.teefun.service.teeworlds.TeeworldsServerHandler;
/**
* Default impl for {@link TeeworldsServerHandler}.
*
* @author Rajh
*
*/
@Service
public class TeeworldsServerHandlerImpl implements TeeworldsServerHandler {
/**
* Class logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(TeeworldsServerHandlerImpl.class);
/**
* Default server ttl.
*/
@Value("${teeworlds.server.ttl}")
private Long SERVER_TTL;
/**
* Start server script.
*/
@Value("${teeworlds.server.serverPath}")
private String TEEWORLDS_SERVER_PATH;
/**
* Pattern for config filename generation.
*/
@Value("${teeworlds.server.configFilenamePattern}")
private String CONFIG_FILENAME_PATTERN;
/**
* Pattern for log filename generation.
*/
@Value("${teeworlds.server.logFilenamePattern}")
private String LOG_FILENAME_PATTERN;
/**
* Min port available.
*/
@Value("${teeworlds.server.minPort}")
private Integer TEEWORLDS_MIN_PORT;
/**
* Max port available.
*/
@Value("${teeworlds.server.maxPort}")
private Integer TEEWORLDS_MAX_PORT;
/**
* Start server script.
*/
@Value("${teeworlds.server.cleanUpScriptPath}")
private String TEEWORLDS_CLEANUP_SERVERS_SCRIPT;
/**
* Number of maximum running servers.
*/
@Value("${teeworlds.server.nbServerSlot}")
private Integer MAX_SERVER_AVAILABLE;
/**
* List of currently borrowed servers.
*/
private final List<TeeworldsServer> borrowedServers = new CopyOnWriteArrayList<TeeworldsServer>();
/**
* Event bus.
*/
@Resource
private EventBus eventBus;
/**
* Clean servers at startup. For remaining servers.
*
* @throws IOException
*/
@PostConstruct
@PreDestroy
public void cleanupServers() throws IOException {
try {
LOGGER.debug("Cleaning zombie servers.");
new ProcessBuilder(TEEWORLDS_CLEANUP_SERVERS_SCRIPT).start();
} catch (final Exception exception) {
LOGGER.error("Error while cleaning zombie servers.", exception);
}
}
@Override
public TeeworldsServer createAndBorrowServer(final TeeworldsConfig configuration) {
if (this.borrowedServers.size() >= MAX_SERVER_AVAILABLE) {
throw new TeeFunRuntimeException("Maximum server size reached.");
}
final String serverId = this.generateUUID();
try {
final Integer port = SocketUtils.findAvailableUdpPort(TEEWORLDS_MIN_PORT, TEEWORLDS_MAX_PORT);
configuration.setVariable("sv_port", port);
} catch (final IllegalStateException exception) {
LOGGER.error("Could not find any port.", exception);
throw new TeeFunRuntimeException("Could not find any port.", exception);
}
configuration.setVariable("logfile", String.format(LOG_FILENAME_PATTERN, serverId));
configuration.generatePassword();
final TeeworldsServer server = new TeeworldsServer(configuration, serverId, System.currentTimeMillis(), this.SERVER_TTL);
this.borrowedServers.add(server);
LOGGER.debug("Created server : " + server.getServerId() + ". " + this.getNbFreeServers() + "/" + MAX_SERVER_AVAILABLE + " .");
return server;
}
@Override
public void freeServer(final TeeworldsServer server) {
this.borrowedServers.remove(server);
this.eventBus.post(new ServerFreeEvent(server));
}
@Override
public void startServer(final TeeworldsServer server) {
if (!this.borrowedServers.contains(server)) {
final String msg = "Trying to start an unknown server. Please make you you borrowed it first and it did not timedout";
LOGGER.error(msg);
throw new TeeFunRuntimeException(msg);
}
try {
final Path configPath = Paths.get(String.format(CONFIG_FILENAME_PATTERN, server.getServerId()));
server.getConfig().generateConfigFile(configPath);
final Process process = new ProcessBuilder(TEEWORLDS_SERVER_PATH, "-f", configPath.toAbsolutePath().toString()).start();
server.setProcess(process);
LOGGER.debug("Server : " + server.getServerId() + " started.");
} catch (final IOException e) {
LOGGER.error("Error while running server.", e);
throw new TeeFunRuntimeException("Error while running server.", e);
}
}
/**
* Generate an UUID for the server.
*
* @return the UUID
*/
private String generateUUID() {
return UUID.randomUUID().toString();
}
@Override
public List<TeeworldsServer> getBorrowedServers() {
return this.borrowedServers;
}
@Override
public boolean hasServerAvailable() {
return this.borrowedServers.size() < MAX_SERVER_AVAILABLE;
}
@Override
public Integer getNbFreeServers() {
return MAX_SERVER_AVAILABLE - this.borrowedServers.size();
}
}