Adds auto-updating of locale files

https://github.com/BentoBoxWorld/BentoBox/issues/960
This commit is contained in:
tastybento 2019-09-28 17:10:30 -07:00
parent 6799c43a0a
commit f8c4ea568f
2 changed files with 102 additions and 23 deletions

View File

@ -3,6 +3,7 @@ package world.bentobox.bentobox.api.addons;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -12,6 +13,7 @@ import java.util.logging.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@ -244,8 +246,9 @@ public abstract class Addon {
* - if true, will overwrite previous file * - if true, will overwrite previous file
* @param noPath * @param noPath
* - if true, the resource's path will be ignored when saving * - if true, the resource's path will be ignored when saving
* @return file written, or null if none
*/ */
public void saveResource(String jarResource, File destinationFolder, boolean replace, boolean noPath) { public File saveResource(String jarResource, File destinationFolder, boolean replace, boolean noPath) {
if (jarResource == null || jarResource.equals("")) { if (jarResource == null || jarResource.equals("")) {
throw new IllegalArgumentException("ResourcePath cannot be null or empty"); throw new IllegalArgumentException("ResourcePath cannot be null or empty");
} }
@ -269,6 +272,7 @@ public abstract class Addon {
if (!outFile.exists() || replace) { if (!outFile.exists() || replace) {
java.nio.file.Files.copy(in, outFile.toPath()); java.nio.file.Files.copy(in, outFile.toPath());
} }
return outFile;
} }
} else { } else {
// No file in the jar // No file in the jar
@ -279,6 +283,31 @@ public abstract class Addon {
BentoBox.getInstance().logError( BentoBox.getInstance().logError(
"Could not save from jar file. From " + jarResource + " to " + destinationFolder.getAbsolutePath()); "Could not save from jar file. From " + jarResource + " to " + destinationFolder.getAbsolutePath());
} }
return null;
}
/**
* Tries to load a YAML file from the Jar
* @param jarResource - YAML file in jar
* @return YamlConfiguration - may be empty
* @throws IOException - if the file cannot be found or loaded from the Jar
* @throws InvalidConfigurationException - if the yaml is malformed
*/
public YamlConfiguration getYamlFromJar(String jarResource) throws IOException, InvalidConfigurationException {
if (jarResource == null || jarResource.equals("")) {
throw new IllegalArgumentException("jarResource cannot be null or empty");
}
YamlConfiguration result = new YamlConfiguration();
jarResource = jarResource.replace('\\', '/');
try (JarFile jar = new JarFile(file)) {
JarEntry jarConfig = jar.getJarEntry(jarResource);
if (jarConfig != null) {
try (InputStreamReader in = new InputStreamReader(jar.getInputStream(jarConfig))) {
result.load(in);
}
}
}
return result;
} }
/** /**

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
@ -15,6 +16,7 @@ import java.util.jar.JarFile;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
@ -108,7 +110,7 @@ public class LocalesManager {
} }
/** /**
* Copies locale files from the addon jar to the file system * Copies locale files from the addon jar to the file system and updates current locales with the latest references
* @param addon - addon * @param addon - addon
*/ */
void copyLocalesFromAddonJar(Addon addon) { void copyLocalesFromAddonJar(Addon addon) {
@ -116,14 +118,39 @@ public class LocalesManager {
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName()); File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName());
if (!localeDir.exists()) { if (!localeDir.exists()) {
localeDir.mkdirs(); localeDir.mkdirs();
// Obtain any locale files and save them
Util.listJarFiles(jar, LOCALE_FOLDER, ".yml").forEach(lf -> addon.saveResource(lf, localeDir, false, true));
} }
// Obtain any locale files, save them and update
Util.listJarFiles(jar, LOCALE_FOLDER, ".yml").forEach(lf -> {
File file = addon.saveResource(lf, localeDir, false, true);
// Update
if (file != null) {
updateLocale(addon, file, lf);
}
});
} catch (Exception e) { } catch (Exception e) {
plugin.logError(e.getMessage()); plugin.logError(e.getMessage());
} }
} }
private void updateLocale(Addon addon, File fileLocaleFile, String lf) {
try {
// Load the JAR locale file
YamlConfiguration jarLocale = addon.getYamlFromJar(lf);
// Load the locale file system locale file
YamlConfiguration fileLocale = new YamlConfiguration();
fileLocale.load(fileLocaleFile);
// Copy new keys to file
jarLocale.getKeys(true).stream().filter(k -> !fileLocale.contains(k, false)).forEach(k -> fileLocale.set(k, jarLocale.get(k)));
// Save file
fileLocale.save(fileLocaleFile);
} catch (Exception e) {
plugin.logError("Error updating locale file: " + lf + " : " + e.getMessage());
plugin.logStacktrace(e);
}
}
/** /**
* 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.
@ -135,6 +162,7 @@ public class LocalesManager {
// If it does exist, then new files will NOT be written! // If it does exist, then new files will NOT be written!
if (!localeDir.exists()) { if (!localeDir.exists()) {
localeDir.mkdirs(); localeDir.mkdirs();
}
FileLister lister = new FileLister(plugin); FileLister lister = new FileLister(plugin);
try { try {
for (String name : lister.listJar(LOCALE_FOLDER)) { for (String name : lister.listJar(LOCALE_FOLDER)) {
@ -143,12 +171,29 @@ public class LocalesManager {
int lastIndex = name.lastIndexOf('/'); int lastIndex = name.lastIndexOf('/');
File targetFile = new File(localeDir, name.substring(lastIndex >= 0 ? lastIndex : 0)); File targetFile = new File(localeDir, name.substring(lastIndex >= 0 ? lastIndex : 0));
copyFile(name, targetFile); copyFile(name, targetFile);
// Update the locale file if it exists already
try (InputStreamReader in = new InputStreamReader(plugin.getResource(name))) {
YamlConfiguration jarLocale = new YamlConfiguration();
jarLocale.load(in);
YamlConfiguration fileLocale = new YamlConfiguration();
fileLocale.load(targetFile);
for (String k : jarLocale.getKeys(true)) {
if (!fileLocale.contains(k, false)) {
fileLocale.set(k, jarLocale.get(k));
}
}
// Save it
fileLocale.save(targetFile);
} catch (InvalidConfigurationException e) {
plugin.logError("Could not update locale files from jar " + e.getMessage());
}
} }
} catch (IOException e) { } catch (IOException e) {
plugin.logError("Could not copy locale files from jar " + e.getMessage()); plugin.logError("Could not copy locale files from jar " + e.getMessage());
} }
} }
}
/** /**
* Loads all the locales available in the locale folder given. Used for loading all locales from plugin and addons * Loads all the locales available in the locale folder given. Used for loading all locales from plugin and addons
@ -180,7 +225,7 @@ public class LocalesManager {
languages.put(localeObject, new BentoBoxLocale(localeObject, languageYaml)); languages.put(localeObject, new BentoBoxLocale(localeObject, languageYaml));
} }
} catch (Exception e) { } catch (Exception e) {
BentoBox.getInstance().logError("Could not load '" + language.getName() + "' : " + e.getMessage() plugin.logError("Could not load '" + language.getName() + "' : " + e.getMessage()
+ " with the following cause '" + e.getCause() + "'." + + " with the following cause '" + e.getCause() + "'." +
" The file has likely an invalid YML format or has been made unreadable during the process."); " The file has likely an invalid YML format or has been made unreadable during the process.");
} }
@ -256,7 +301,11 @@ public class LocalesManager {
plugin.log(ChatColor.AQUA + "Analyzing BentoBox locale files"); plugin.log(ChatColor.AQUA + "Analyzing BentoBox locale files");
user.sendRawMessage(ChatColor.AQUA + SPACER); user.sendRawMessage(ChatColor.AQUA + SPACER);
loadLocalesFromFile(BENTOBOX); loadLocalesFromFile(BENTOBOX);
analyze(fix); if (languages.containsKey(Locale.US)) {
analyze(user, fix);
} else {
user.sendRawMessage(ChatColor.RED + "No US English in BentoBox to use for analysis!");
}
user.sendRawMessage(ChatColor.AQUA + "Analyzing Addon locale files"); user.sendRawMessage(ChatColor.AQUA + "Analyzing Addon locale files");
plugin.getAddonsManager().getAddons().forEach(addon -> { plugin.getAddonsManager().getAddons().forEach(addon -> {
user.sendRawMessage(ChatColor.AQUA + SPACER); user.sendRawMessage(ChatColor.AQUA + SPACER);
@ -264,22 +313,23 @@ public class LocalesManager {
user.sendRawMessage(ChatColor.AQUA + SPACER); user.sendRawMessage(ChatColor.AQUA + SPACER);
languages.clear(); languages.clear();
loadLocalesFromFile(addon.getDescription().getName()); loadLocalesFromFile(addon.getDescription().getName());
analyze(fix); if (languages.containsKey(Locale.US)) {
analyze(user, fix);
} else {
user.sendRawMessage(ChatColor.RED + "No US English to use for analysis!");
}
}); });
reloadLanguages(); reloadLanguages();
} }
/** /**
* *
* @param user
* @param fix whether or not locale files with missing translations should be fixed. * @param fix whether or not locale files with missing translations should be fixed.
* Not currently supported. * Not currently supported.
* @since 1.5.0 * @since 1.5.0
*/ */
private void analyze(boolean fix) { private void analyze(User user, boolean fix) {
if (!languages.containsKey(Locale.US)) {
return;
}
User user = User.getInstance(Bukkit.getConsoleSender());
user.sendRawMessage(ChatColor.GREEN + "The following locales are supported:"); user.sendRawMessage(ChatColor.GREEN + "The following locales are supported:");
languages.forEach((k,v) -> user.sendRawMessage(ChatColor.GOLD + k.toLanguageTag() + " " + k.getDisplayLanguage() + " " + k.getDisplayCountry())); languages.forEach((k,v) -> user.sendRawMessage(ChatColor.GOLD + k.toLanguageTag() + " " + k.getDisplayLanguage() + " " + k.getDisplayCountry()));