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 045 return modules.containsKey(moduleName); 046 } 047 048 public boolean hasModuleWithoutCase(String moduleName) { 049 050 for(String key : modules.keySet()) { 051 if(key.equalsIgnoreCase(moduleName)) return true; 052 } 053 return false; 054 } 055 056 public ModuleWrapper getModule(String name) { 057 058 return modules.get(name); 059 } 060 061 public Map<String, ModuleWrapper> getModules() { 062 063 return modules; 064 } 065 066 public void load() { 067 068 final File directory = new File(PluginCore.directory(), "modules"); 069 070 if(directory.exists()) { 071 072 final File[] jars = directory.listFiles((dir, name)->name.endsWith(".jar")); 073 074 if(jars != null) { 075 for(File jar : jars) { 076 077 try { 078 final ModuleWrapper wrapper = loadModuleWrapper(jar.getAbsolutePath()); 079 080 if(wrapper.getModule() == null) { 081 PluginCore.log().inform("Skipping file due to invalid module: " + jar.getName()); 082 continue; 083 } 084 085 if(jar.getName().contains("old-")) continue; 086 087 if(!wrapper.getModule().getClass().isAnnotationPresent(ModuleInfo.class)) { 088 PluginCore.log().inform("Invalid module format! ModuleOld File: " + jar.getName()); 089 continue; 090 } 091 092 wrapper.setInfo(wrapper.getModule().getClass().getAnnotation(ModuleInfo.class)); 093 PluginCore.log().inform("Found module: " + wrapper.name() + " version: " + wrapper.version()); 094 modules.put(wrapper.name(), wrapper); 095 096 if(!wrapper.getInfo().updateURL().trim().equalsIgnoreCase("")) { 097 PluginCore.log().inform("Checking for updates for module " + wrapper.info.name()); 098 ModuleUpdateChecker checker = new ModuleUpdateChecker(wrapper.info.name(), wrapper.info.updateURL(), wrapper.version()); 099 checker.check(); 100 } 101 } catch(Exception ignore) { 102 PluginCore.log().inform("Unable to load module file: " + jar.getName() + ". Are you sure it's up to date?"); 103 } 104 } 105 } 106 } else { 107 if(directory.mkdir()) { 108 PluginCore.log().inform("Created module directory: " + directory.getAbsolutePath()); 109 } 110 } 111 } 112 113 public boolean load(String moduleName) { 114 115 final String path = findPath(moduleName); 116 if(path != null) { 117 try { 118 final ModuleWrapper wrapper = loadModuleWrapper(path); 119 if(!wrapper.getModule().getClass().isAnnotationPresent(ModuleInfo.class)) { 120 PluginCore.log().inform("Invalid module format! ModuleOld File: " + moduleName); 121 return false; 122 } 123 124 wrapper.setInfo(wrapper.getModule().getClass().getAnnotation(ModuleInfo.class)); 125 126 if(!wrapper.getInfo().pluginVersion().equalsIgnoreCase("0.0.0.0") && 127 new Semver(wrapper.info.pluginVersion()).isGreaterThan(PluginCore.engine().version())) { 128 129 PluginCore.log().error("Unable to load module: " + wrapper.name() + " Requires a higher plugin version. Required version: " + PluginCore.engine().version()); 130 return false; 131 } 132 133 PluginCore.log().inform("Found module: " + wrapper.name() + " version: " + wrapper.version()); 134 modules.put(wrapper.name(), wrapper); 135 136 /*if(!wrapper.getInfo().updateURL().trim().equalsIgnoreCase("")) { 137 PluginCore.log().inform("Checking for updates for module " + moduleName); 138 ModuleUpdateChecker checker = new ModuleUpdateChecker(moduleName, wrapper.info.updateURL(), wrapper.version()); 139 checker.check(); 140 }*/ 141 return true; 142 } catch(Exception ignore) { 143 PluginCore.log().inform("Unable to load module: " + moduleName + ". Are you sure it exists?"); 144 } 145 } 146 return false; 147 } 148 149 public void unload(String moduleName) { 150 151 if(hasModule(moduleName)) { 152 ModuleWrapper wrapper = getModule(moduleName); 153 //TODO: Command and configuration unloading. 154 wrapper.getModule().disable(PluginCore.instance()); 155 156 try { 157 Field f = ClassLoader.class.getDeclaredField("classes"); 158 f.setAccessible(true); 159 160 Vector<Class> classes = (Vector<Class>)f.get(PluginCore.loader().getModule(moduleName).getLoader()); 161 for(Class clazz : classes) { 162 PluginCore.log().debug("Loaded: " + clazz.getName()); 163 } 164 } catch(Exception e) { 165 e.printStackTrace(); 166 } 167 wrapper.unload(); 168 wrapper.setModule(null); 169 wrapper.setInfo(null); 170 wrapper.setLoader(null); 171 wrapper = null; 172 System.gc(); 173 174 modules.remove(moduleName); 175 } 176 } 177 178 protected String findPath(String moduleName) { 179 180 final File directory = new File(PluginCore.directory(), "modules"); 181 final File[] jars = directory.listFiles((dir, name)->name.endsWith(".jar")); 182 183 if(jars != null) { 184 for(File jar : jars) { 185 if(jar.getAbsolutePath().toLowerCase().contains(moduleName.toLowerCase() + ".jar")) { 186 return jar.getAbsolutePath(); 187 } 188 } 189 } 190 return null; 191 } 192 193 private ModuleWrapper loadModuleWrapper(String modulePath) { 194 195 ModuleWrapper wrapper; 196 197 Module module = null; 198 199 final File file = new File(modulePath); 200 Class<? extends Module> moduleClass; 201 202 URLClassLoader classLoader = null; 203 try { 204 classLoader = new URLClassLoader(new URL[]{ file.toURI().toURL() }, PluginCore.instance().getClass().getClassLoader()); 205 moduleClass = classLoader.loadClass(getModuleMain(new File(modulePath))).asSubclass(Module.class); 206 module = moduleClass.newInstance(); 207 } catch(Exception ignore) { 208 PluginCore.log().inform("Unable to locate module main class for file " + file.getName()); 209 } 210 wrapper = new ModuleWrapper(module); 211 wrapper.setLoader(classLoader); 212 return wrapper; 213 } 214 215 public boolean downloadModule(String module) { 216 217 if(modules.containsKey(module)) { 218 try { 219 final String fileURL = modules.get(module).info.updateURL(); 220 final URL url = new URL(fileURL); 221 final HttpURLConnection httpConn = (HttpURLConnection)url.openConnection(); 222 final int responseCode = httpConn.getResponseCode(); 223 if(responseCode == HttpURLConnection.HTTP_OK) { 224 final String fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1, fileURL.length()); 225 226 final InputStream in = httpConn.getInputStream(); 227 final File file = new File(PluginCore.directory() + File.separator + "modules", fileName); 228 229 if(file.exists()) { 230 if(!file.renameTo(new File(PluginCore.directory() + File.separator + "modules", "outdated-" + fileName))) { 231 return false; 232 } 233 } 234 235 final FileOutputStream out = new FileOutputStream(file); 236 237 int bytesRead = -1; 238 final byte[] buffer = new byte[4096]; 239 while((bytesRead = in.read(buffer)) != -1) { 240 out.write(buffer, 0, bytesRead); 241 } 242 243 out.close(); 244 in.close(); 245 } 246 } catch(Exception ignore) { 247 return false; 248 } 249 return true; 250 } 251 return false; 252 } 253 254 private String getModuleMain(File jarFile) { 255 256 String main = ""; 257 JarFile jar = null; 258 InputStream in = null; 259 BufferedReader reader = null; 260 261 try { 262 jar = new JarFile(jarFile); 263 final JarEntry infoFile = jar.getJarEntry("module.tne"); 264 265 if(infoFile == null) { 266 PluginCore.log().inform("TNE encountered an error while loading a module! No module.tne file!"); 267 return ""; 268 } 269 270 in = jar.getInputStream(infoFile); 271 reader = new BufferedReader(new InputStreamReader(in)); 272 273 main = reader.readLine().split("=")[1].trim(); 274 } catch(IOException e) { 275 PluginCore.log().debug(e.toString()); 276 } finally { 277 if(jar != null) { 278 try { 279 jar.close(); 280 } catch(IOException e) { 281 PluginCore.log().debug(e.toString()); 282 } 283 } 284 285 if(in != null) { 286 try { 287 in.close(); 288 } catch(IOException e) { 289 PluginCore.log().debug(e.toString()); 290 } 291 } 292 293 if(reader != null) { 294 try { 295 reader.close(); 296 } catch(IOException e) { 297 PluginCore.log().debug(e.toString()); 298 } 299 } 300 } 301 return main; 302 } 303}