From 925906bec6e623e1b1e2736eb607bd7650b9579c Mon Sep 17 00:00:00 2001 From: Aurora Lahtela <24460436+AuroraLS3@users.noreply.github.com> Date: Tue, 13 Sep 2022 18:26:54 +0300 Subject: [PATCH] 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 --- .../plan/identification/ServerInfo.java | 1 - Plan/plugin/build.gradle | 5 +- Plan/standalone/build.gradle | 12 +- .../playeranalytics/plan/PlanStandalone.java | 165 +++++++++++++++++- .../plan/PlanStandaloneComponent.java | 20 +++ .../playeranalytics/plan/ScannerPrompter.java | 81 +++++++++ .../plan/gathering/NoOpListenerSystem.java | 41 +++++ .../plan/gathering/NoOpServerSensor.java | 35 ++++ .../plan/module/StandaloneBindingModule.java | 57 ++++++ .../module/StandaloneProvidingModule.java | 142 +++++++++++++++ .../StandaloneServerPropertiesModule.java | 45 +++++ .../logging/StandaloneErrorLogger.java | 56 ++++++ .../StandalonePlatformAbstractionLayer.java | 70 +++----- .../StandalonePluginInformation.java | 71 ++++++++ .../scheduling/StandaloneRunnableFactory.java | 60 +++++++ .../plugin/scheduling/StandaloneTask.java | 38 ++++ .../scheduling/UnscheduledStandaloneTask.java | 79 +++++++++ .../src/main/resources/logging.properties | 5 + 18 files changed, 923 insertions(+), 60 deletions(-) create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/ScannerPrompter.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpListenerSystem.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpServerSensor.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneBindingModule.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneProvidingModule.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneServerPropertiesModule.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plan/utilities/logging/StandaloneErrorLogger.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plugin/information/StandalonePluginInformation.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneRunnableFactory.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneTask.java create mode 100644 Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/UnscheduledStandaloneTask.java create mode 100644 Plan/standalone/src/main/resources/logging.properties diff --git a/Plan/common/src/main/java/com/djrapitops/plan/identification/ServerInfo.java b/Plan/common/src/main/java/com/djrapitops/plan/identification/ServerInfo.java index f8ebc794b..a0847795b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/identification/ServerInfo.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/identification/ServerInfo.java @@ -48,7 +48,6 @@ public abstract class ServerInfo implements SubSystem { } public ServerIdentifier getServerIdentifier() { - Server server = getServer(); return new ServerIdentifier(server.getUuid(), server.getIdentifiableName()); } diff --git a/Plan/plugin/build.gradle b/Plan/plugin/build.gradle index 92e215daf..34b19e1f5 100644 --- a/Plan/plugin/build.gradle +++ b/Plan/plugin/build.gradle @@ -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' ]) } diff --git a/Plan/standalone/build.gradle b/Plan/standalone/build.gradle index 45185d9bc..7321c8203 100644 --- a/Plan/standalone/build.gradle +++ b/Plan/standalone/build.gradle @@ -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] } \ No newline at end of file diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandalone.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandalone.java index 3683db082..2cae3f933 100644 --- a/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandalone.java +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandalone.java @@ -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 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 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(); + } } diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandaloneComponent.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandaloneComponent.java index 07907fafa..5ac5b3cb9 100644 --- a/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandaloneComponent.java +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/PlanStandaloneComponent.java @@ -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(); } diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/ScannerPrompter.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/ScannerPrompter.java new file mode 100644 index 000000000..458028b72 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/ScannerPrompter.java @@ -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 . + */ +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 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 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 + } +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpListenerSystem.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpListenerSystem.java new file mode 100644 index 000000000..f58e4f136 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpListenerSystem.java @@ -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 . + */ +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 + } +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpServerSensor.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpServerSensor.java new file mode 100644 index 000000000..4bac9bc61 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/gathering/NoOpServerSensor.java @@ -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 . + */ +package net.playeranalytics.plan.gathering; + +import com.djrapitops.plan.gathering.ServerSensor; + +/** + * @author AuroraLS3 + */ +public class NoOpServerSensor implements ServerSensor { + + @Override + public boolean supportsDirectTPS() { + return false; + } + + @Override + public int getOnlinePlayerCount() { + return 0; + } +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneBindingModule.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneBindingModule.java new file mode 100644 index 000000000..98a5f741f --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneBindingModule.java @@ -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 . + */ +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); +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneProvidingModule.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneProvidingModule.java new file mode 100644 index 000000000..94b047d40 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneProvidingModule.java @@ -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 . + */ +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 provideEmptyImporterSet() { + return new HashSet<>(); + } + + @Provides + @ElementsIntoSet + Set 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 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(); + } + +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneServerPropertiesModule.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneServerPropertiesModule.java new file mode 100644 index 000000000..691cd4f9c --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/module/StandaloneServerPropertiesModule.java @@ -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 . + */ +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 + ); + } + +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plan/utilities/logging/StandaloneErrorLogger.java b/Plan/standalone/src/main/java/net/playeranalytics/plan/utilities/logging/StandaloneErrorLogger.java new file mode 100644 index 000000000..f99042838 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plan/utilities/logging/StandaloneErrorLogger.java @@ -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 . + */ +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); + } +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plugin/StandalonePlatformAbstractionLayer.java b/Plan/standalone/src/main/java/net/playeranalytics/plugin/StandalonePlatformAbstractionLayer.java index c5a750e53..b150840b1 100644 --- a/Plan/standalone/src/main/java/net/playeranalytics/plugin/StandalonePlatformAbstractionLayer.java +++ b/Plan/standalone/src/main/java/net/playeranalytics/plugin/StandalonePlatformAbstractionLayer.java @@ -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; } } diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plugin/information/StandalonePluginInformation.java b/Plan/standalone/src/main/java/net/playeranalytics/plugin/information/StandalonePluginInformation.java new file mode 100644 index 000000000..d896f5c2b --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plugin/information/StandalonePluginInformation.java @@ -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 . + */ +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); + } + +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneRunnableFactory.java b/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneRunnableFactory.java new file mode 100644 index 000000000..12d4b2efd --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneRunnableFactory.java @@ -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 . + */ +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 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(); + } +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneTask.java b/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneTask.java new file mode 100644 index 000000000..54eaea610 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/StandaloneTask.java @@ -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 . + */ +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); + } +} diff --git a/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/UnscheduledStandaloneTask.java b/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/UnscheduledStandaloneTask.java new file mode 100644 index 000000000..3b226e821 --- /dev/null +++ b/Plan/standalone/src/main/java/net/playeranalytics/plugin/scheduling/UnscheduledStandaloneTask.java @@ -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 . + */ +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 cancellableConsumer; + + public UnscheduledStandaloneTask(ScheduledExecutorService scheduler, Runnable runnable, Consumer 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); + } +} diff --git a/Plan/standalone/src/main/resources/logging.properties b/Plan/standalone/src/main/resources/logging.properties new file mode 100644 index 000000000..660625312 --- /dev/null +++ b/Plan/standalone/src/main/resources/logging.properties @@ -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 \ No newline at end of file