Initial version of standalone

- Prompts settings on enable
- Runs with MySQL pretending to be a new game server in the database
- Works pretty well for first attempt at it. A lot of inspiration was taken from the
  mock version of the system used during tests.

TODO:
- Doesn't have commands implemented yet
- Doesn't log errors properly
- Doesn't log console contents to a file
- Network page needs to be accessible if proxy server is in database
This commit is contained in:
Aurora Lahtela 2022-09-13 18:26:54 +03:00
parent 3b09fd8da2
commit 925906bec6
18 changed files with 923 additions and 60 deletions

View File

@ -48,7 +48,6 @@ public abstract class ServerInfo implements SubSystem {
}
public ServerIdentifier getServerIdentifier() {
Server server = getServer();
return new ServerIdentifier(server.getUuid(), server.getIdentifiableName());
}

View File

@ -8,18 +8,21 @@ dependencies {
shadow project(path: ":sponge")
shadow project(path: ":bungeecord")
shadow project(path: ":velocity")
shadow project(path: ":standalone")
testImplementation project(path: ":common", configuration: 'testArtifacts')
testImplementation project(path: ":bukkit", configuration: 'testArtifacts')
testImplementation project(path: ":nukkit", configuration: 'testArtifacts')
testImplementation project(path: ":sponge", configuration: 'testArtifacts')
testImplementation project(path: ":bungeecord", configuration: 'testArtifacts')
testImplementation project(path: ":velocity", configuration: 'testArtifacts')
testImplementation project(path: ":standalone", configuration: 'testArtifacts')
}
jar {
// Add the sponge mixin into the manifest
manifest.attributes([
'MixinConfigs': 'plan-sponge.mixins.json'
'MixinConfigs': 'plan-sponge.mixins.json',
'Main-Class' : 'net.playeranalytics.plan.PlanStandalone'
])
}

View File

@ -1,14 +1,10 @@
plugins {
id "net.kyori.blossom" version "1.3.0"
}
dependencies {
compileOnly project(":common")
implementation project(path: ":common", configuration: 'shadow')
compileOnly project(":api")
implementation project(":api")
implementation project(":common")
testImplementation project(path: ":common", configuration: 'testArtifacts')
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
}
shadowJar {
configurations = [project.configurations.shadow]
}

View File

@ -1,14 +1,173 @@
package net.playeranalytics.plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.commands.use.ColorScheme;
import com.djrapitops.plan.commands.use.Subcommand;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.*;
import com.djrapitops.plan.settings.config.paths.key.Setting;
import net.playeranalytics.plugin.StandalonePlatformAbstractionLayer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class PlanStandalone {
public class PlanStandalone implements PlanPlugin {
private static final Logger LOGGER = Logger.getLogger("Plan");
private static final Logger LOGGER = Logger.getGlobal();
public static void main(String[] args) {
private static final ScannerPrompter SCANNER_PROMPTER = new ScannerPrompter();
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
private static PlanStandalone pluginInstance;
private PlanSystem system;
public static void main(String[] args) throws Exception {
LogManager.getLogManager().readConfiguration(PlanStandalone.class.getResourceAsStream("/logging.properties"));
LOGGER.info("Starting Plan..");
LOGGER.info("Type 'exit' at any time to stop the program.");
LOGGER.info(() -> "Java version: " + System.getProperty("java.version"));
LOGGER.info("");
pluginInstance = new PlanStandalone();
EXECUTOR_SERVICE.submit(pluginInstance::onEnable);
// Blocks and waits user input to the console
SCANNER_PROMPTER.enable();
}
public static void shutdown(int status) {
LOGGER.info("Stopping the program...");
if (pluginInstance != null) pluginInstance.onDisable();
SCANNER_PROMPTER.disable();
EXECUTOR_SERVICE.shutdown();
try {
if (!EXECUTOR_SERVICE.awaitTermination(5, TimeUnit.SECONDS)) {
EXECUTOR_SERVICE.shutdownNow();
}
} catch (InterruptedException e) {
LOGGER.info("Press enter to exit..");
System.exit(131);
Thread.currentThread().interrupt();
return;
}
if (status != 0) LOGGER.info("Press enter to exit..");
System.exit(status);
}
@Override
public InputStream getResource(String resource) {
try {
return pluginInstance.getSystem().getPlanFiles().getResourceFromJar(resource).asInputStream();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public ColorScheme getColorScheme() {
return new ColorScheme("", "", "");
}
@Override
public PlanSystem getSystem() {
return system;
}
@Override
public void registerCommand(Subcommand command) {
// no-op, unused
}
@Override
public void onEnable() {
try {
PlanStandaloneComponent component = DaggerPlanStandaloneComponent.builder()
.plan(this)
.abstractionLayer(new StandalonePlatformAbstractionLayer(LOGGER))
.build();
system = component.system();
system.enableForCommands();
PlanConfig config = system.getConfigSystem().getConfig();
config.set(WebserverSettings.DISABLED, false);
config.set(DataGatheringSettings.GEOLOCATIONS, false);
config.set(DataGatheringSettings.DISK_SPACE, false);
config.set(DataGatheringSettings.PING, false);
String ip = config.get(ProxySettings.IP);
if ("0.0.0.0".equals(ip)) {
// First installation, prompt for settings
LOGGER.info("\n--------------\n");
promptSetting(config, ProxySettings.IP, "Please enter IP / address to access this server");
promptSetting(config, DatabaseSettings.MYSQL_HOST, "Please enter MySQL address");
promptSetting(config, DatabaseSettings.MYSQL_PORT, "Please enter MySQL port");
promptSetting(config, DatabaseSettings.MYSQL_DATABASE, "Please enter MySQL database name/schema name");
promptSetting(config, DatabaseSettings.MYSQL_USER, "Please enter MySQL user");
promptSetting(config, DatabaseSettings.MYSQL_PASS, "Please enter MySQL password");
promptSettingInt(config, WebserverSettings.PORT, "Please enter Webserver port to use");
config.set(PluginSettings.SERVER_NAME, "Standalone Plan Instance");
LOGGER.info("Saving config..");
config.save();
LOGGER.info("\n--------------\n");
LOGGER.info("Config saved - proceeding with plugin enable..");
}
SCANNER_PROMPTER.insertCommands(component.planCommand());
} catch (Exception | Error e) {
LOGGER.log(Level.SEVERE, "Failed to enable commands" + e + ", program will exit\n", e);
shutdown(1);
}
try {
system.enable();
LOGGER.info("-- Startup complete, Plan enabled successfully!");
} catch (Exception | Error e) {
LOGGER.log(Level.SEVERE, "Failed to enable plugin " + e + ", you can try 'plan reload' after changing config settings.\n", e);
}
}
private void promptSetting(PlanConfig config, Setting<String> setting, String prompt) {
LOGGER.info(() -> prompt + " (" + setting.getPath() + " setting):");
String inputValue = SCANNER_PROMPTER.waitAndGetInput()
.orElseThrow(() -> new IllegalStateException("KeyboardInterrupt"));
config.set(setting, inputValue);
LOGGER.info(() -> "Set '" + setting.getPath() + "' as '" + inputValue + "'");
}
private void promptSettingInt(PlanConfig config, Setting<Integer> setting, String prompt) {
LOGGER.info(() -> prompt + " (" + setting.getPath() + " setting):");
String inputValue = SCANNER_PROMPTER.waitAndGetInput()
.orElseThrow(() -> new IllegalStateException("KeyboardInterrupt"));
try {
config.set(setting, Integer.parseInt(inputValue));
} catch (NumberFormatException invalid) {
LOGGER.warning("'" + inputValue + "' is not a valid number, try again");
promptSettingInt(config, setting, prompt);
return;
}
LOGGER.info(() -> "Set '" + setting.getPath() + "' as '" + inputValue + "'");
}
@Override
public void onDisable() {
if (system != null) system.disable();
}
@Override
public File getDataFolder() {
return pluginInstance.getSystem().getPlanFiles().getDataFolder();
}
}

View File

@ -2,9 +2,26 @@ package net.playeranalytics.plan;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.commands.PlanCommand;
import com.djrapitops.plan.modules.FiltersModule;
import com.djrapitops.plan.modules.PlatformAbstractionLayerModule;
import dagger.BindsInstance;
import dagger.Component;
import net.playeranalytics.plan.module.StandaloneBindingModule;
import net.playeranalytics.plan.module.StandaloneProvidingModule;
import net.playeranalytics.plan.module.StandaloneServerPropertiesModule;
import net.playeranalytics.plugin.PlatformAbstractionLayer;
import javax.inject.Singleton;
@Singleton
@Component(modules = {
PlatformAbstractionLayerModule.class,
FiltersModule.class,
StandaloneBindingModule.class,
StandaloneProvidingModule.class,
StandaloneServerPropertiesModule.class
})
public interface PlanStandaloneComponent {
PlanCommand planCommand();
@ -17,6 +34,9 @@ public interface PlanStandaloneComponent {
@BindsInstance
Builder plan(PlanStandalone plan);
@BindsInstance
Builder abstractionLayer(PlatformAbstractionLayer abstractionLayer);
PlanStandaloneComponent build();
}

View File

@ -0,0 +1,81 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan;
import com.djrapitops.plan.SubSystem;
import com.djrapitops.plan.commands.PlanCommand;
import org.apache.commons.lang3.StringUtils;
import java.util.Optional;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author AuroraLS3
*/
public class ScannerPrompter implements SubSystem {
private final AtomicBoolean shutdown = new AtomicBoolean(false);
private final AtomicReference<String> scannedLine = new AtomicReference<>();
private Scanner scanner;
@Override
public void enable() {
scanner = new Scanner(System.in);
while (!shutdown.get() && scanner.hasNext()) {
synchronized (scannedLine) {
String scanned = scanner.nextLine();
if (StringUtils.equalsAny(scanned, "stop", "end", "quit", "exit")) {
PlanStandalone.shutdown(0);
return; // Ends the loop
}
scannedLine.set(scanned);
scannedLine.notify();
}
}
}
@Override
public void disable() {
scanner.close();
shutdown.set(true);
}
public Optional<String> waitAndGetInput() {
try {
String scanned = "";
while (scanned.trim().isEmpty()) {
synchronized (scannedLine) {
scannedLine.wait();
scanned = scannedLine.get();
}
}
return Optional.of(scanned);
} catch (InterruptedException e) {
PlanStandalone.shutdown(132);
Thread.currentThread().interrupt();
return Optional.empty();
}
}
public void insertCommands(PlanCommand planCommand) {
// TODO enable commands
}
}

View File

@ -0,0 +1,41 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
/**
* @author AuroraLS3
*/
public class NoOpListenerSystem extends ListenerSystem {
@Override
protected void registerListeners() {
// no-op
}
@Override
protected void unregisterListeners() {
// no-op
}
@Override
public void callEnableEvent(PlanPlugin plugin) {
// no-op
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering;
import com.djrapitops.plan.gathering.ServerSensor;
/**
* @author AuroraLS3
*/
public class NoOpServerSensor implements ServerSensor<Object> {
@Override
public boolean supportsDirectTPS() {
return false;
}
@Override
public int getOnlinePlayerCount() {
return 0;
}
}

View File

@ -0,0 +1,57 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.module;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
import com.djrapitops.plan.delivery.webserver.http.JettyWebserver;
import com.djrapitops.plan.delivery.webserver.http.WebServer;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerServerInfo;
import com.djrapitops.plan.settings.ConfigSystem;
import com.djrapitops.plan.settings.ProxyConfigSystem;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import dagger.Binds;
import dagger.Module;
import net.playeranalytics.plan.PlanStandalone;
import net.playeranalytics.plan.utilities.logging.StandaloneErrorLogger;
/**
* @author AuroraLS3
*/
@Module
public interface StandaloneBindingModule {
@Binds
PlanPlugin bindPlugin(PlanStandalone plugin);
@Binds
ServerInfo bindServerInfo(ServerServerInfo serverServerInfo);
@Binds
ConfigSystem bindConfigSystem(ProxyConfigSystem configSystem);
@Binds
JSONStorage bindJSONStorage(JSONFileStorage jsonFileStorage);
@Binds
WebServer bindWebserver(JettyWebserver webServer);
@Binds
ErrorLogger bindErrorLogger(StandaloneErrorLogger standaloneErrorLogger);
}

View File

@ -0,0 +1,142 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.module;
import com.djrapitops.plan.TaskSystem;
import com.djrapitops.plan.gathering.ServerSensor;
import com.djrapitops.plan.gathering.importing.importers.Importer;
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
import com.djrapitops.plan.settings.config.ExtensionSettings;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.LocaleSystem;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.DBType;
import com.djrapitops.plan.storage.database.MySQLDB;
import com.djrapitops.plan.storage.database.SQLiteDB;
import com.djrapitops.plan.storage.file.JarResource;
import com.google.gson.Gson;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import net.playeranalytics.plan.gathering.NoOpListenerSystem;
import net.playeranalytics.plan.gathering.NoOpServerSensor;
import net.playeranalytics.plugin.PluginInformation;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
/**
* @author AuroraLS3
*/
@Module
public class StandaloneProvidingModule {
@Provides
@Singleton
DBSystem provideDatabaseSystem(
PlanConfig config,
Locale locale,
SQLiteDB.Factory sqLiteDB,
MySQLDB mySQLDB,
PluginLogger logger
) {
return new DBSystem(config, locale, sqLiteDB, logger) {
@Override
public void enable() {
databases.add(mySQLDB);
db = getActiveDatabaseByName(DBType.MYSQL.getConfigName());
super.enable();
}
};
}
@Provides
@ElementsIntoSet
Set<Importer> provideEmptyImporterSet() {
return new HashSet<>();
}
@Provides
@ElementsIntoSet
Set<TaskSystem.Task> provideEmptyTaskSet() {
return new HashSet<>();
}
@Provides
@Singleton
ListenerSystem provideListenerSystem() {
return new NoOpListenerSystem();
}
@Provides
@Singleton
ServerSensor<?> provideServerSensor() {
return new NoOpServerSensor();
}
@Provides
@Singleton
@Named("mainCommandName")
String provideMainCommandName() {
return "plan";
}
@Provides
@Singleton
Gson provideGson() {
return new Gson();
}
@Provides
@Singleton
Locale provideLocale(LocaleSystem localeSystem) {
return localeSystem.getLocale();
}
@Provides
@Singleton
ExtensionSettings providePluginsConfigSection(PlanConfig config) {
return config.getExtensionSettings();
}
@Provides
@Singleton
@Named("isExtensionEnabled")
Predicate<String> provideExtensionEnabledConfigCheck(PlanConfig config) {
return config.getExtensionSettings()::isEnabled;
}
@Provides
@Singleton
JarResource.StreamFunction provideJarStreamFunction(PluginInformation information) {
return information::getResourceFromJar;
}
@Provides
@Singleton
@Named("dataFolder")
File provideDataFolder(PluginInformation information) {
return information.getDataFolder();
}
}

View File

@ -0,0 +1,45 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.module;
import com.djrapitops.plan.identification.properties.ServerProperties;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
import java.net.InetSocketAddress;
/**
* @author AuroraLS3
*/
@Module
public class StandaloneServerPropertiesModule {
@Provides
@Singleton
ServerProperties provideServerProperties() {
return new ServerProperties(
"Standalone Java",
0,
"Java " + System.getProperty("java.version"),
"",
() -> new InetSocketAddress(25565).getAddress().getHostAddress(),
0
);
}
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.utilities.logging;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.playeranalytics.plan.PlanStandalone;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* @author AuroraLS3
*/
@Singleton
public class StandaloneErrorLogger implements ErrorLogger {
private final PluginLogger logger;
@Inject
public StandaloneErrorLogger(PluginLogger logger) {
this.logger = logger;
// TODO Extract file logging properties of PluginErrorLogger without PlanPlugin as dependency.
}
@Override
public void critical(Throwable throwable, ErrorContext context) {
error(throwable, context);
PlanStandalone.shutdown(1);
}
@Override
public void error(Throwable throwable, ErrorContext context) {
logger.error("", throwable);
}
@Override
public void warn(Throwable throwable, ErrorContext context) {
logger.warn("", throwable);
}
}

View File

@ -1,23 +1,36 @@
package net.playeranalytics.plugin;
import net.playeranalytics.plugin.information.StandalonePluginInformation;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import net.playeranalytics.plugin.scheduling.StandaloneRunnableFactory;
import net.playeranalytics.plugin.server.JavaUtilPluginLogger;
import net.playeranalytics.plugin.server.Listeners;
import net.playeranalytics.plugin.server.PluginLogger;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.logging.Logger;
public class StandalonePlatformAbstractionLayer implements PlatformAbstractionLayer {
private final PluginLogger logger;
private final StandaloneRunnableFactory runnableFactory;
private final StandalonePluginInformation pluginInformation;
private final Listeners listeners;
public StandalonePlatformAbstractionLayer(Logger logger) {this.logger = new JavaUtilPluginLogger(logger);}
public StandalonePlatformAbstractionLayer(Logger logger) {
this.logger = new JavaUtilPluginLogger(logger);
runnableFactory = new StandaloneRunnableFactory();
pluginInformation = new StandalonePluginInformation();
listeners = new Listeners() {
@Override
public void registerListener(Object o) {/*no-op*/}
@Override
public void unregisterListener(Object o) {/*no-op*/}
@Override
public void unregisterListeners() {/*no-op*/}
};
}
@Override
public PluginLogger getPluginLogger() {
@ -26,53 +39,16 @@ public class StandalonePlatformAbstractionLayer implements PlatformAbstractionLa
@Override
public Listeners getListeners() {
return new Listeners() {
@Override
public void registerListener(Object o) {
}
@Override
public void unregisterListener(Object o) {
}
@Override
public void unregisterListeners() {
}
};
return listeners;
}
@Override
public RunnableFactory getRunnableFactory() {
return null;
return runnableFactory;
}
@Override
public PluginInformation getPluginInformation() {
return new PluginInformation() {
@Override
public InputStream getResourceFromJar(String s) {
return getClass().getResourceAsStream(s);
}
@Override
public File getDataFolder() {
return new File("Plan");
}
@Override
public String getVersion() {
try {
return readVersionFromPluginYml();
} catch (IOException | URISyntaxException e) {
return e.toString();
}
}
private String readVersionFromPluginYml() throws IOException, URISyntaxException {
String pluginYmlContents = new String(Files.readAllBytes(new File(getClass().getResource("plugin.yml").toURI()).toPath()));
String versionHalf = StringUtils.split(pluginYmlContents, "version:")[1];
return StringUtils.split(versionHalf, "\n")[0];
}
};
return pluginInformation;
}
}

View File

@ -0,0 +1,71 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.information;
import net.playeranalytics.plugin.PluginInformation;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* @author AuroraLS3
*/
public class StandalonePluginInformation implements PluginInformation {
@Override
public InputStream getResourceFromJar(String resourceName) {
return getClass().getResourceAsStream("/" + resourceName);
}
@Override
public File getDataFolder() {
return new File("Plan");
}
@Override
public String getVersion() {
return readVersionFromPluginYml();
}
private String readVersionFromPluginYml() {
String pluginYmlContents = readAllBytes("plugin.yml");
for (String line : StringUtils.split(pluginYmlContents, "\n")) {
if (line.contains("version")) {
return StringUtils.split(line, ":")[1].trim();
}
}
return "Missing plugin.yml";
}
private String readAllBytes(String resource) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (InputStream inStream = getResourceFromJar(resource); buffer) {
int nRead;
byte[] data = new byte[16384];
while ((nRead = inStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return buffer.toString(StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.scheduling;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class StandaloneRunnableFactory implements RunnableFactory {
private final Set<StandaloneTask> tasks;
private ScheduledExecutorService executorService;
public StandaloneRunnableFactory() {
this.executorService = Executors.newScheduledThreadPool(8);
this.tasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
}
@Override
public UnscheduledTask create(Runnable runnable) {
return new UnscheduledStandaloneTask(getExecutorService(), runnable, task -> {
});
}
private ScheduledExecutorService getExecutorService() {
if (executorService.isShutdown() || executorService.isTerminated()) {
// Hacky way of fixing tasks when plugin is disabled, leaks one thread every reload.
executorService = Executors.newSingleThreadScheduledExecutor();
}
return executorService;
}
@Override
public UnscheduledTask create(PluginRunnable runnable) {
return new UnscheduledStandaloneTask(getExecutorService(), runnable, runnable::setCancellable);
}
@Override
public void cancelAllKnownTasks() {
this.tasks.forEach(Task::cancel);
this.tasks.clear();
executorService.shutdown();
}
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.scheduling;
import java.util.concurrent.Future;
public class StandaloneTask implements Task {
private final Future<?> task;
public StandaloneTask(Future<?> task) {
this.task = task;
}
@Override
public boolean isGameThread() {
return false;
}
@Override
public void cancel() {
task.cancel(false);
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.scheduling;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class UnscheduledStandaloneTask implements UnscheduledTask {
private final ScheduledExecutorService scheduler;
private final Runnable runnable;
private final Consumer<Task> cancellableConsumer;
public UnscheduledStandaloneTask(ScheduledExecutorService scheduler, Runnable runnable, Consumer<Task> cancellableConsumer) {
this.scheduler = scheduler;
this.runnable = runnable;
this.cancellableConsumer = cancellableConsumer;
}
@Override
public Task runTaskAsynchronously() {
StandaloneTask task = new StandaloneTask(this.scheduler.submit(this.runnable));
cancellableConsumer.accept(task);
return task;
}
@Override
public Task runTaskLaterAsynchronously(long delayTicks) {
StandaloneTask task = new StandaloneTask(this.scheduler.schedule(
this.runnable,
delayTicks * 50,
TimeUnit.MILLISECONDS
));
cancellableConsumer.accept(task);
return task;
}
@Override
public Task runTaskTimerAsynchronously(long delayTicks, long periodTicks) {
StandaloneTask task = new StandaloneTask(this.scheduler.scheduleAtFixedRate(
runnable,
delayTicks * 50,
periodTicks * 50,
TimeUnit.MILLISECONDS
));
cancellableConsumer.accept(task);
return task;
}
@Override
public Task runTask() {
return runTaskAsynchronously();
}
@Override
public Task runTaskLater(long delayTicks) {
return runTaskLaterAsynchronously(delayTicks);
}
@Override
public Task runTaskTimer(long delayTicks, long periodTicks) {
return runTaskTimerAsynchronously(delayTicks, periodTicks);
}
}

View File

@ -0,0 +1,5 @@
handlers=java.util.logging.ConsoleHandler
.level=INFO
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] %4$-7s : %5$s %n