001package net.tnemc.plugincore.core.module; 002 003import com.vdurmont.semver4j.Semver; 004import net.tnemc.plugincore.PluginCore; 005 006import java.io.BufferedReader; 007import java.io.File; 008import java.io.FileOutputStream; 009import java.io.IOException; 010import java.io.InputStream; 011import java.io.InputStreamReader; 012import java.lang.reflect.Field; 013import java.net.HttpURLConnection; 014import java.net.URL; 015import java.net.URLClassLoader; 016import java.util.HashMap; 017import java.util.Map; 018import java.util.Vector; 019import java.util.jar.JarEntry; 020import java.util.jar.JarFile; 021 022/* 023 * The New Plugin Core 024 * Copyright (C) 2022 - 2024 Daniel "creatorfromhell" Vidmar 025 * 026 * This program is free software: you can redistribute it and/or modify 027 * it under the terms of the GNU Affero General Public License as published by 028 * the Free Software Foundation, either version 3 of the License, or 029 * (at your option) any later version. 030 * 031 * This program is distributed in the hope that it will be useful, 032 * but WITHOUT ANY WARRANTY; without even the implied warranty of 033 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 034 * GNU Affero General Public License for more details. 035 * 036 * You should have received a copy of the GNU Affero General Public License 037 * along with this program. If not, see <http://www.gnu.org/licenses/>. 038 */ 039public class ModuleLoader { 040 041 private final Map<String, ModuleWrapper> modules = new HashMap<>(); 042 043 public boolean hasModule(String moduleName) { 044 return modules.containsKey(moduleName); 045 } 046 047 public boolean hasModuleWithoutCase(String moduleName) { 048 for (String key : modules.keySet()) { 049 if(key.equalsIgnoreCase(moduleName)) return true; 050 } 051 return false; 052 } 053 054 public ModuleWrapper getModule(String name) { 055 return modules.get(name); 056 } 057 058 public Map<String, ModuleWrapper> getModules() { 059 return modules; 060 } 061 062 public void load() { 063 final File directory = new File(PluginCore.directory(), "modules"); 064 065 if(directory.exists()) { 066 067 final File[] jars = directory.listFiles((dir, name) -> name.endsWith(".jar")); 068 069 if(jars != null) { 070 for(File jar : jars) { 071 072 try { 073 final ModuleWrapper wrapper = loadModuleWrapper(jar.getAbsolutePath()); 074 075 if(wrapper.getModule() == null) { 076 PluginCore.log().inform("Skipping file due to invalid module: " + jar.getName()); 077 continue; 078 } 079 080 if(jar.getName().contains("old-")) continue; 081 082 if(!wrapper.getModule().getClass().isAnnotationPresent(ModuleInfo.class)) { 083 PluginCore.log().inform("Invalid module format! ModuleOld File: " + jar.getName()); 084 continue; 085 } 086 087 wrapper.setInfo(wrapper.getModule().getClass().getAnnotation(ModuleInfo.class)); 088 PluginCore.log().inform("Found module: " + wrapper.name() + " version: " + wrapper.version()); 089 modules.put(wrapper.name(), wrapper); 090 091 if (!wrapper.getInfo().updateURL().trim().equalsIgnoreCase("")) { 092 PluginCore.log().inform("Checking for updates for module " + wrapper.info.name()); 093 ModuleUpdateChecker checker = new ModuleUpdateChecker(wrapper.info.name(), wrapper.info.updateURL(), wrapper.version()); 094 checker.check(); 095 } 096 } catch(Exception ignore) { 097 PluginCore.log().inform("Unable to load module file: " + jar.getName() + ". Are you sure it's up to date?"); 098 } 099 } 100 } 101 } else { 102 if(directory.mkdir()) { 103 PluginCore.log().inform("Created module directory: " + directory.getAbsolutePath()); 104 } 105 } 106 } 107 108 public boolean load(String moduleName) { 109 final String path = findPath(moduleName); 110 if(path != null) { 111 try { 112 final ModuleWrapper wrapper = loadModuleWrapper(path); 113 if(!wrapper.getModule().getClass().isAnnotationPresent(ModuleInfo.class)) { 114 PluginCore.log().inform("Invalid module format! ModuleOld File: " + moduleName); 115 return false; 116 } 117 118 wrapper.setInfo(wrapper.getModule().getClass().getAnnotation(ModuleInfo.class)); 119 120 if(!wrapper.getInfo().pluginVersion().equalsIgnoreCase("0.0.0.0") && 121 new Semver(wrapper.info.pluginVersion()).isGreaterThan(PluginCore.engine().version())) { 122 123 PluginCore.log().error("Unable to load module: " + wrapper.name() + " Requires a higher plugin version. Required version: " + PluginCore.engine().version()); 124 return false; 125 } 126 127 PluginCore.log().inform("Found module: " + wrapper.name() + " version: " + wrapper.version()); 128 modules.put(wrapper.name(), wrapper); 129 130 /*if(!wrapper.getInfo().updateURL().trim().equalsIgnoreCase("")) { 131 PluginCore.log().inform("Checking for updates for module " + moduleName); 132 ModuleUpdateChecker checker = new ModuleUpdateChecker(moduleName, wrapper.info.updateURL(), wrapper.version()); 133 checker.check(); 134 }*/ 135 return true; 136 } catch(Exception ignore) { 137 PluginCore.log().inform("Unable to load module: " + moduleName + ". Are you sure it exists?"); 138 } 139 } 140 return false; 141 } 142 143 public void unload(String moduleName) { 144 if(hasModule(moduleName)) { 145 ModuleWrapper wrapper = getModule(moduleName); 146 //TODO: Command and configuration unloading. 147 wrapper.getModule().disable(PluginCore.instance()); 148 149 try { 150 Field f = ClassLoader.class.getDeclaredField("classes"); 151 f.setAccessible(true); 152 153 Vector<Class> classes = (Vector<Class>) f.get(PluginCore.loader().getModule(moduleName).getLoader()); 154 for(Class clazz : classes) { 155 PluginCore.log().debug("Loaded: " + clazz.getName()); 156 } 157 } catch (Exception e) { 158 e.printStackTrace(); 159 } 160 wrapper.unload(); 161 wrapper.setModule(null); 162 wrapper.setInfo(null); 163 wrapper.setLoader(null); 164 wrapper = null; 165 System.gc(); 166 167 modules.remove(moduleName); 168 } 169 } 170 171 protected String findPath(String moduleName) { 172 final File directory = new File(PluginCore.directory(), "modules"); 173 final File[] jars = directory.listFiles((dir, name) -> name.endsWith(".jar")); 174 175 if(jars != null) { 176 for(File jar : jars) { 177 if(jar.getAbsolutePath().toLowerCase().contains(moduleName.toLowerCase() + ".jar")) { 178 return jar.getAbsolutePath(); 179 } 180 } 181 } 182 return null; 183 } 184 185 private ModuleWrapper loadModuleWrapper(String modulePath) { 186 ModuleWrapper wrapper; 187 188 Module module = null; 189 190 final File file = new File(modulePath); 191 Class<? extends Module> moduleClass; 192 193 URLClassLoader classLoader = null; 194 try { 195 classLoader = new URLClassLoader(new URL[]{ file.toURI().toURL() }, PluginCore.instance().getClass().getClassLoader()); 196 moduleClass = classLoader.loadClass(getModuleMain(new File(modulePath))).asSubclass(Module.class); 197 module = moduleClass.newInstance(); 198 } catch (Exception ignore) { 199 PluginCore.log().inform("Unable to locate module main class for file " + file.getName()); 200 } 201 wrapper = new ModuleWrapper(module); 202 wrapper.setLoader(classLoader); 203 return wrapper; 204 } 205 206 public boolean downloadModule(String module) { 207 if(modules.containsKey(module)) { 208 try { 209 final String fileURL = modules.get(module).info.updateURL(); 210 final URL url = new URL(fileURL); 211 final HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); 212 final int responseCode = httpConn.getResponseCode(); 213 if(responseCode == HttpURLConnection.HTTP_OK) { 214 final String fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1, fileURL.length()); 215 216 final InputStream in = httpConn.getInputStream(); 217 final File file = new File(PluginCore.directory() + File.separator + "modules", fileName); 218 219 if(file.exists()) { 220 if(!file.renameTo(new File(PluginCore.directory() + File.separator + "modules", "outdated-" + fileName))) { 221 return false; 222 } 223 } 224 225 final FileOutputStream out = new FileOutputStream(file); 226 227 int bytesRead = -1; 228 final byte[] buffer = new byte[4096]; 229 while ((bytesRead = in.read(buffer)) != -1) { 230 out.write(buffer, 0, bytesRead); 231 } 232 233 out.close(); 234 in.close(); 235 } 236 } catch(Exception ignore) { 237 return false; 238 } 239 return true; 240 } 241 return false; 242 } 243 244 private String getModuleMain(File jarFile) { 245 String main = ""; 246 JarFile jar = null; 247 InputStream in = null; 248 BufferedReader reader = null; 249 250 try { 251 jar = new JarFile(jarFile); 252 final JarEntry infoFile = jar.getJarEntry("module.tne"); 253 254 if(infoFile == null) { 255 PluginCore.log().inform("TNE encountered an error while loading a module! No module.tne file!"); 256 return ""; 257 } 258 259 in = jar.getInputStream(infoFile); 260 reader = new BufferedReader(new InputStreamReader(in)); 261 262 main = reader.readLine().split("=")[1].trim(); 263 } catch(IOException e) { 264 PluginCore.log().debug(e.toString()); 265 } finally { 266 if(jar != null) { 267 try { 268 jar.close(); 269 } catch(IOException e) { 270 PluginCore.log().debug(e.toString()); 271 } 272 } 273 274 if(in != null) { 275 try { 276 in.close(); 277 } catch(IOException e) { 278 PluginCore.log().debug(e.toString()); 279 } 280 } 281 282 if(reader != null) { 283 try { 284 reader.close(); 285 } catch(IOException e) { 286 PluginCore.log().debug(e.toString()); 287 } 288 } 289 } 290 return main; 291 } 292}