Fixes reload of locales from addons

This was a bigger job than expected. I moved the addon loading locale
stuff into the LocalesManager class from the AddonsManager and put a jar
file lister in Utils. There could be some more combining of plugin jar
and addon jar file finding there. Finally, I added a sophisticated test
that creates a temporary addon jar with a locale file and checks that it
is saved correctly. Phew!
This commit is contained in:
tastybento 2018-12-25 23:17:06 -08:00
parent b82f59e948
commit d7851b923e
5 changed files with 173 additions and 48 deletions

View File

@ -6,7 +6,6 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -33,7 +32,6 @@ import world.bentobox.bentobox.api.events.addon.AddonEvent;
*/ */
public class AddonsManager { public class AddonsManager {
private static final String LOCALE_FOLDER = "locales";
private List<Addon> addons; private List<Addon> addons;
private Map<Addon, AddonClassLoader> loaders; private Map<Addon, AddonClassLoader> loaders;
private final Map<String, Class<?>> classes = new HashMap<>(); private final Map<String, Class<?>> classes = new HashMap<>();
@ -125,26 +123,30 @@ public class AddonsManager {
// try loading the addon // try loading the addon
// Get description in the addon.yml file // Get description in the addon.yml file
YamlConfiguration data = addonDescription(jar); YamlConfiguration data = addonDescription(jar);
// Load the addon // Load the addon
AddonClassLoader addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader()); AddonClassLoader addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader());
// Get the addon itself // Get the addon itself
addon = addonClassLoader.getAddon(); addon = addonClassLoader.getAddon();
// Initialize some settings // Initialize some settings
addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName())); addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName()));
addon.setFile(f); addon.setFile(f);
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName()); // Locales
// Obtain any locale files and save them plugin.getLocalesManager().copyLocalesFromAddonJar(addon);
for (String localeFile : listJarFiles(jar, LOCALE_FOLDER, ".yml")) {
addon.saveResource(localeFile, localeDir, false, true);
}
plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName()); plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName());
// Fire the load event // Fire the load event
Bukkit.getPluginManager().callEvent(new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.LOAD).build()); Bukkit.getPluginManager().callEvent(new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.LOAD).build());
// Add it to the list of addons // Add it to the list of addons
addons.add(addon); addons.add(addon);
// Add to the list of loaders // Add to the list of loaders
loaders.put(addon, addonClassLoader); loaders.put(addon, addonClassLoader);
// Run the onLoad. // Run the onLoad.
addon.onLoad(); addon.onLoad();
} catch (Exception e) { } catch (Exception e) {
@ -205,33 +207,6 @@ public class AddonsManager {
classes.putIfAbsent(name, clazz); classes.putIfAbsent(name, clazz);
} }
/**
* Lists files found in the jar in the folderPath with the suffix given
* @param jar - the jar file
* @param folderPath - the path within the jar
* @param suffix - the suffix required
* @return a list of files
*/
public List<String> listJarFiles(JarFile jar, String folderPath, String suffix) {
List<String> result = new ArrayList<>();
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String path = entry.getName();
if (!path.startsWith(folderPath)) {
continue;
}
if (entry.getName().endsWith(suffix)) {
result.add(entry.getName());
}
}
return result;
}
private void sortAddons() { private void sortAddons() {
// Lists all available addons as names. // Lists all available addons as names.
List<String> names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList()); List<String> names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList());

View File

@ -11,13 +11,16 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.jar.JarFile;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.localization.BentoBoxLocale; import world.bentobox.bentobox.api.localization.BentoBoxLocale;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.FileLister; import world.bentobox.bentobox.util.FileLister;
import world.bentobox.bentobox.util.Util;
/** /**
* @author tastybento, Poslovitch * @author tastybento, Poslovitch
@ -31,7 +34,7 @@ public class LocalesManager {
public LocalesManager(BentoBox plugin) { public LocalesManager(BentoBox plugin) {
this.plugin = plugin; this.plugin = plugin;
copyLocalesFromJar(BENTOBOX); copyLocalesFromPluginJar(BENTOBOX);
loadLocalesFromFile(BENTOBOX); // Default loadLocalesFromFile(BENTOBOX); // Default
} }
@ -101,12 +104,29 @@ public class LocalesManager {
return result == null ? defaultText : result; return result == null ? defaultText : result;
} }
/**
* Copies locale files from the addon jar to the file system
* @param addon - addon
*/
void copyLocalesFromAddonJar(Addon addon) {
try (JarFile jar = new JarFile(addon.getFile())) {
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName());
if (!localeDir.exists()) {
localeDir.mkdirs();
// Obtain any locale files and save them
Util.listJarFiles(jar, LOCALE_FOLDER, ".yml").forEach(lf -> addon.saveResource(lf, localeDir, false, true));
}
} catch (Exception e) {
plugin.logError(e.getMessage());
}
}
/** /**
* Copies all the locale files from the plugin jar to the filesystem. * Copies all the locale files from the plugin jar to the filesystem.
* Only done if the locale folder does not already exist. * Only done if the locale folder does not already exist.
* @param folderName - the name of the destination folder * @param folderName - the name of the destination folder
*/ */
private void copyLocalesFromJar(String folderName) { private void copyLocalesFromPluginJar(String folderName) {
// Run through the files and store the locales // Run through the files and store the locales
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + folderName); File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + folderName);
// If the folder does not exist, then make it and fill with the locale files from the jar // If the folder does not exist, then make it and fill with the locale files from the jar
@ -210,9 +230,12 @@ public class LocalesManager {
*/ */
public void reloadLanguages() { public void reloadLanguages() {
languages.clear(); languages.clear();
copyLocalesFromJar(BENTOBOX); copyLocalesFromPluginJar(BENTOBOX);
loadLocalesFromFile(BENTOBOX); loadLocalesFromFile(BENTOBOX);
plugin.getAddonsManager().getAddons().forEach(addon -> loadLocalesFromFile(addon.getDescription().getName())); plugin.getAddonsManager().getAddons().forEach(addon -> {
copyLocalesFromAddonJar(addon);
loadLocalesFromFile(addon.getDescription().getName());
});
} }

View File

@ -16,6 +16,7 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.schems.Clipboard; import world.bentobox.bentobox.schems.Clipboard;
import world.bentobox.bentobox.util.Util;
public class SchemsManager { public class SchemsManager {
@ -42,9 +43,7 @@ public class SchemsManager {
} }
// Save any schems that // Save any schems that
try (JarFile jar = new JarFile(addon.getFile())) { try (JarFile jar = new JarFile(addon.getFile())) {
plugin.getAddonsManager().listJarFiles(jar, "schems", ".schem").forEach(name -> { Util.listJarFiles(jar, "schems", ".schem").forEach(name -> addon.saveResource(name, false));
addon.saveResource(name, false);
});
} catch (IOException e) { } catch (IOException e) {
plugin.logError("Could not load schem files from addon jar " + e.getMessage()); plugin.logError("Could not load schem files from addon jar " + e.getMessage());
} }

View File

@ -1,7 +1,10 @@
package world.bentobox.bentobox.util; package world.bentobox.bentobox.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -195,6 +198,33 @@ public class Util {
return world.getEnvironment().equals(Environment.NORMAL) ? world : Bukkit.getWorld(world.getName().replaceAll(NETHER, "").replaceAll(THE_END, "")); return world.getEnvironment().equals(Environment.NORMAL) ? world : Bukkit.getWorld(world.getName().replaceAll(NETHER, "").replaceAll(THE_END, ""));
} }
/**
* Lists files found in the jar in the folderPath with the suffix given
* @param jar - the jar file
* @param folderPath - the path within the jar
* @param suffix - the suffix required
* @return a list of files
*/
public static List<String> listJarFiles(JarFile jar, String folderPath, String suffix) {
List<String> result = new ArrayList<>();
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String path = entry.getName();
if (!path.startsWith(folderPath)) {
continue;
}
if (entry.getName().endsWith(suffix)) {
result.add(entry.getName());
}
}
return result;
}
/** /**
* Converts block face direction to radial degrees. Returns 0 if block face * Converts block face direction to radial degrees. Returns 0 if block face
* is not radial. * is not radial.

View File

@ -9,13 +9,20 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@ -79,7 +86,7 @@ public class LocalesManagerTest {
*/ */
@After @After
public void cleanUp() throws Exception { public void cleanUp() throws Exception {
// Delete locale folder
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER); File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER);
if (localeDir.exists()) { if (localeDir.exists()) {
// Remove it // Remove it
@ -90,6 +97,16 @@ public class LocalesManagerTest {
} }
// Delete addon folder
localeDir = new File(plugin.getDataFolder(), "addons");
if (localeDir.exists()) {
// Remove it
Files.walk(localeDir.toPath())
.map(Path::toFile)
.sorted((o1, o2) -> -o1.compareTo(o2))
.forEach(File::delete);
}
} }
/** /**
@ -123,7 +140,7 @@ public class LocalesManagerTest {
LocalesManager lm = new LocalesManager(plugin); LocalesManager lm = new LocalesManager(plugin);
assertNull(lm.get("test.test.test")); assertNull(lm.get("test.test.test"));
} }
/** /**
* Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(java.lang.String, java.lang.String)}. * Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(java.lang.String, java.lang.String)}.
* @throws IOException * @throws IOException
@ -170,7 +187,7 @@ public class LocalesManagerTest {
when(user.getLocale()).thenReturn(Locale.US); when(user.getLocale()).thenReturn(Locale.US);
assertEquals("test string", lm.get(user, "test.test")); assertEquals("test string", lm.get(user, "test.test"));
} }
/** /**
* Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException * @throws IOException
@ -283,17 +300,98 @@ public class LocalesManagerTest {
List<Addon> none = new ArrayList<>(); List<Addon> none = new ArrayList<>();
Addon addon = mock(Addon.class); Addon addon = mock(Addon.class);
AddonDescription desc = new AddonDescription(); AddonDescription desc = new AddonDescription();
desc.setName(BENTOBOX); desc.setName("AcidIsland");
when(addon.getDescription()).thenReturn(desc); when(addon.getDescription()).thenReturn(desc);
// Create a tmp folder to jar up
File localeDir = new File(LOCALE_FOLDER);
localeDir.mkdirs();
// Create a fake locale file for this jar
File english = new File(localeDir, Locale.US.toLanguageTag() + ".yml");
YamlConfiguration yaml = new YamlConfiguration();
yaml.set("test.test", "test string");
yaml.save(english);
// Create a temporary jar file
File jar = new File("addons", "AcidIsland.jar");
jar.getParentFile().mkdirs();
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
JarOutputStream target = new JarOutputStream(new FileOutputStream("addons" + File.separator + "AcidIsland.jar"), manifest);
add(english, target);
target.close();
// When the file is requested, return it
when(addon.getFile()).thenReturn(jar);
none.add(addon); none.add(addon);
when(am.getAddons()).thenReturn(none); when(am.getAddons()).thenReturn(none);
when(plugin.getAddonsManager()).thenReturn(am); when(plugin.getAddonsManager()).thenReturn(am);
makeFakeLocaleFile(); makeFakeLocaleFile();
LocalesManager lm = new LocalesManager(plugin); LocalesManager lm = new LocalesManager(plugin);
// RELOAD!!!
lm.reloadLanguages(); lm.reloadLanguages();
Mockito.verify(addon).getDescription();
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + BENTOBOX); // Verify that the resources have been saved (note that they are not actually saved because addon is a mock)
assertTrue(localeDir.exists()); Mockito.verify(addon).saveResource(
Mockito.eq("locales/en-US.yml"),
Mockito.any(),
Mockito.eq(false),
Mockito.eq(true)
);
// Clean up
// Delete the temp folder we made. Other clean up is done globally
localeDir = new File(plugin.getDataFolder(), "AcidIsland");
if (localeDir.exists()) {
// Remove it
Files.walk(localeDir.toPath())
.map(Path::toFile)
.sorted((o1, o2) -> -o1.compareTo(o2))
.forEach(File::delete);
}
}
private void add(File source, JarOutputStream target) throws IOException
{
BufferedInputStream in = null;
try
{
if (source.isDirectory())
{
String name = source.getPath().replace("\\", "/");
if (!name.isEmpty())
{
if (!name.endsWith("/"))
name += "/";
JarEntry entry = new JarEntry(name);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
target.closeEntry();
}
for (File nestedFile: source.listFiles())
add(nestedFile, target);
return;
}
JarEntry entry = new JarEntry(source.getPath().replace("\\", "/"));
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[1024];
while (true)
{
int count = in.read(buffer);
if (count == -1)
break;
target.write(buffer, 0, count);
}
target.closeEntry();
}
finally
{
if (in != null)
in.close();
}
} }
/** /**