diff --git a/build.gradle b/build.gradle index b5d17fc..c616864 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group = 'me.skymc' -version = '5.26' +version = '5.27' sourceCompatibility = 1.8 targetCompatibility = 1.8 diff --git a/src/main/scala/io/izzel/taboolib/TabooLib.java b/src/main/scala/io/izzel/taboolib/TabooLib.java index 510e808..b4702a3 100644 --- a/src/main/scala/io/izzel/taboolib/TabooLib.java +++ b/src/main/scala/io/izzel/taboolib/TabooLib.java @@ -52,6 +52,12 @@ public class TabooLib { logger = TLogger.getUnformatted("TabooLib"); // 配置文件从 config.yml 修改为 settings.yml 防止与老版本插件冲突 config = TConfig.create(getPlugin(), "settings.yml"); + // 配置更新 + try { + config.migrate(); + } catch (Throwable t) { + t.printStackTrace(); + } // 加载版本号 try { version = NumberConversions.toDouble(IO.readFully(Files.getResource("__resources__/version"), StandardCharsets.UTF_8)); diff --git a/src/main/scala/io/izzel/taboolib/module/config/TConfig.java b/src/main/scala/io/izzel/taboolib/module/config/TConfig.java index 2fe6e33..6262900 100644 --- a/src/main/scala/io/izzel/taboolib/module/config/TConfig.java +++ b/src/main/scala/io/izzel/taboolib/module/config/TConfig.java @@ -1,5 +1,6 @@ package io.izzel.taboolib.module.config; +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import io.izzel.taboolib.TabooLib; @@ -7,12 +8,14 @@ import io.izzel.taboolib.TabooLibAPI; import io.izzel.taboolib.module.locale.TLocale; import io.izzel.taboolib.module.locale.logger.TLogger; import io.izzel.taboolib.util.Files; +import io.izzel.taboolib.util.KV; import io.izzel.taboolib.util.Ref; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -24,12 +27,16 @@ import java.util.Map; */ public class TConfig extends YamlConfiguration { - private static Map> files = Maps.newHashMap(); - private File file; - private List runnable = Lists.newArrayList(); + private static final Map> files = Maps.newHashMap(); + private final Plugin plugin; + private final File file; + private final List runnable = Lists.newArrayList(); + private final List> migrate = Lists.newArrayList(); + private String path; private TConfig(File file, Plugin plugin) { files.computeIfAbsent(plugin.getName(), name -> new ArrayList<>()).add(file); + this.plugin = plugin; this.file = file; reload(); TConfigWatcher.getInst().addSimpleListener(this.file, this::reload); @@ -53,7 +60,9 @@ public class TConfig extends YamlConfiguration { if (!file.exists()) { Files.releaseResource(plugin, path, false); } - return create(file, plugin); + TConfig conf = create(file, plugin); + conf.path = path; + return conf; } public String getStringColored(String path) { @@ -89,6 +98,25 @@ public class TConfig extends YamlConfiguration { } } + public TConfig migrate() { + Preconditions.checkNotNull(path, "path not exists"); + try (FileInputStream fileInputStream = new FileInputStream(file)) { + List migrate = TConfigMigrate.migrate(fileInputStream, Files.getResourceChecked(plugin, path)); + if (migrate != null) { + Files.write(file, w -> { + for (String line : migrate) { + w.write(line); + w.newLine(); + } + }); + load(file); + } + } catch (Throwable t) { + t.printStackTrace(); + } + return this; + } + // ********************************* // // Getter and Setter diff --git a/src/main/scala/io/izzel/taboolib/module/config/TConfigMigrate.java b/src/main/scala/io/izzel/taboolib/module/config/TConfigMigrate.java new file mode 100644 index 0000000..5c58301 --- /dev/null +++ b/src/main/scala/io/izzel/taboolib/module/config/TConfigMigrate.java @@ -0,0 +1,152 @@ +package io.izzel.taboolib.module.config; + +import com.google.common.collect.Lists; +import io.izzel.taboolib.module.db.local.SecuredFile; +import io.izzel.taboolib.util.ArrayUtil; +import io.izzel.taboolib.util.Files; +import io.izzel.taboolib.util.KV; +import io.izzel.taboolib.util.Strings; +import org.bukkit.configuration.ConfigurationSection; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.IntStream; + +/** + * @Author sky + * @Since 2020-04-27 21:02 + */ +public class TConfigMigrate { + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm"); + + public static List migrate(InputStream current, InputStream source) { + boolean migrated = false; + List content = Files.readToList(current); + String cc = String.join("\n", content); + String cs = String.join("\n", Files.readToList(source)); + SecuredFile c = SecuredFile.loadConfiguration(cc); + SecuredFile s = SecuredFile.loadConfiguration(cs); + String hash1 = Strings.hashKeyForDisk(cs, "sha-1"); + String hash2 = ""; + for (String line : content) { + if (line.startsWith("# HASH ")) { + hash2 = line.substring("# HASH ".length()).trim().split(" ")[0]; + break; + } + } + if (Objects.equals(hash1, hash2)) { + return null; + } + List> update = Lists.newArrayList(); + List> contrast = contrast(c.getValues(true), s.getValues(true)); + for (KV pair : contrast) { + int index = pair.getKey().lastIndexOf("."); + if (pair.getValue() == null) { + Object data = s.get(pair.getKey()); + String[] nodes = pair.getKey().split("\\."); + int regex = 0; + int match = 0; + int find = -1; + for (int i = 0; i < content.size(); i++) { + String line = content.get(i); + for (int j = regex; j < nodes.length; j++) { + if (line.matches("( *)(['\"])?(" + nodes[j] + ")(['\"])?:(.*)")) { + match++; + find = i; + regex = j; + break; + } + } + } + if (find == -1) { + update.add(new KV<>(pair.getKey(), SecuredFile.dump(data).split("\n"))); + } else { + String space = Strings.copy(" ", nodes.length - 1); + String[] dumpList = SecuredFile.dump(data).split("\n"); + if (dumpList.length > 1) { + IntStream.range(0, dumpList.length).forEach(j -> dumpList[j] = space + " # " + dumpList[j]); + } + ArrayUtil.addAutoExpand(content, find + 1, space + "# ------------------------- #\n" + space + "# UPDATE " + dateFormat.format(System.currentTimeMillis()) + " #\n" + space + "# ------------------------- #", ""); + if (dumpList.length == 1) { + ArrayUtil.addAutoExpand(content, find + 2, space + "# " + pair.getKey().substring(index + 1) + ": " + dumpList[0] + "\n", ""); + } else { + ArrayUtil.addAutoExpand(content, find + 2, space + "# " + pair.getKey().substring(index + 1) + ":", ""); + ArrayUtil.addAutoExpand(content, find + 3, String.join("\n# ", dumpList) + "\n", ""); + } + migrated = true; + } + } + } + if (update.size() > 0) { + content.add(""); + content.add("# ------------------------- #\n" + "# UPDATE " + dateFormat.format(System.currentTimeMillis()) + " #\n" + "# ------------------------- #"); + for (KV pair : update) { + if (pair.getValue().length == 1) { + content.add("# " + pair.getKey() + ": " + pair.getValue()[0]); + } else { + content.add("# " + pair.getKey() + ":"); + content.add(String.join("\n# ", pair.getValue())); + } + } + migrated = true; + } + if (migrated) { + if (hash2.isEmpty()) { + content.add(""); + content.add("# --------------------------------------------- #"); + content.add("# HASH " + hash1 + " #"); + content.add("# --------------------------------------------- #"); + } else { + for (int i = 0; i < content.size(); i++) { + String line = content.get(i); + if (line.startsWith("# HASH ")) { + content.set(i, "# HASH " + hash1 + " #"); + break; + } + } + } + } + return migrated ? content : null; + } + + public static List> contrast(Map current, Map source) { + List deleted = Lists.newArrayList(); + List> difference = Lists.newArrayList(); + // change & add + for (Map.Entry entry : current.entrySet()) { + if (entry.getValue() instanceof ConfigurationSection) { + continue; + } + if (entry.getValue() instanceof Map && source.get(entry.getKey()) instanceof Map) { + List> contrast = contrast((Map) entry.getValue(), (Map) source.get(entry.getKey())); + for (KV pair : contrast) { + pair.setKey(entry.getKey() + "." + pair.getKey()); + } + difference.addAll(contrast); + } else if (!Objects.equals(entry.getValue(), source.get(entry.getKey()))) { + difference.add(new KV<>(entry.getKey().toString(), entry.getValue())); + } + } + // delete + for (Map.Entry entry : source.entrySet()) { + if (deleted.stream().anyMatch(delete -> entry.getKey().toString().startsWith(delete) && delete.split("\\.").length < entry.getKey().toString().split("\\.").length)) { + continue; + } + if (entry.getValue() instanceof Map && current.get(entry.getKey()) instanceof Map) { + List> contrast = contrast((Map) entry.getValue(), (Map) source.get(entry.getKey())); + for (KV pair : contrast) { + pair.setKey(entry.getKey() + "." + pair.getKey()); + } + difference.addAll(contrast); + } else if (!current.containsKey(entry.getKey())) { + deleted.add(entry.getKey().toString()); + difference.add(new KV<>(entry.getKey().toString(), null)); + } + } + return difference; + } +} diff --git a/src/main/scala/io/izzel/taboolib/module/config/TConfigWatcher.java b/src/main/scala/io/izzel/taboolib/module/config/TConfigWatcher.java index bcbe6ae..b8963e3 100644 --- a/src/main/scala/io/izzel/taboolib/module/config/TConfigWatcher.java +++ b/src/main/scala/io/izzel/taboolib/module/config/TConfigWatcher.java @@ -19,7 +19,7 @@ import java.util.function.Consumer; */ public class TConfigWatcher { - private static TConfigWatcher configWatcher = new TConfigWatcher(); + private final static TConfigWatcher configWatcher = new TConfigWatcher(); private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("TConfigWatcherService-%d").build()); private final Map>> map = new HashMap<>(); diff --git a/src/main/scala/io/izzel/taboolib/module/db/local/SecuredFile.java b/src/main/scala/io/izzel/taboolib/module/db/local/SecuredFile.java index 64704ba..1fc7cc4 100644 --- a/src/main/scala/io/izzel/taboolib/module/db/local/SecuredFile.java +++ b/src/main/scala/io/izzel/taboolib/module/db/local/SecuredFile.java @@ -1,9 +1,11 @@ package io.izzel.taboolib.module.db.local; +import io.izzel.taboolib.module.lite.SimpleReflection; import io.izzel.taboolib.util.Files; import org.bukkit.Bukkit; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; +import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.IOException; @@ -11,7 +13,7 @@ import java.text.SimpleDateFormat; import java.util.logging.Level; /** - * @Author sky + * @Author sky, yumc * @Since 2020-02-28 11:14 */ public class SecuredFile extends YamlConfiguration { @@ -39,7 +41,7 @@ public class SecuredFile extends YamlConfiguration { public void load(File file) throws InvalidConfigurationException { String content = Files.readFromFile(file); try { - super.loadFromString(Files.readFromFile(file)); + loadFromString(content); } catch (InvalidConfigurationException t) { Files.copy(file, new File(file.getParent(), file.getName() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis()) + ".bak")); throw t; @@ -60,6 +62,21 @@ public class SecuredFile extends YamlConfiguration { } } + @Override + public String saveToString() { + return super.saveToString(); + } + + public static String dump(Object data) { + Yaml yaml = (Yaml) SimpleReflection.getFieldValueChecked(YamlConfiguration.class, new YamlConfiguration(), "yaml", true); + try { + return yaml.dump(data); + } catch (Throwable t) { + t.printStackTrace(); + } + return ""; + } + public static SecuredFile loadConfiguration(String contents) { SecuredFile config = new SecuredFile(); try { diff --git a/src/main/scala/io/izzel/taboolib/module/inject/TInject.java b/src/main/scala/io/izzel/taboolib/module/inject/TInject.java index 86cb8ff..213e500 100644 --- a/src/main/scala/io/izzel/taboolib/module/inject/TInject.java +++ b/src/main/scala/io/izzel/taboolib/module/inject/TInject.java @@ -31,6 +31,8 @@ public @interface TInject { State state() default State.NONE; + boolean autoMigrate() default false; + enum State { LOADING, STARTING, ACTIVATED, NONE diff --git a/src/main/scala/io/izzel/taboolib/module/inject/TInjectLoader.java b/src/main/scala/io/izzel/taboolib/module/inject/TInjectLoader.java index a7665f2..c5c5291 100644 --- a/src/main/scala/io/izzel/taboolib/module/inject/TInjectLoader.java +++ b/src/main/scala/io/izzel/taboolib/module/inject/TInjectLoader.java @@ -28,7 +28,7 @@ import java.util.Map; */ public class TInjectLoader implements TabooLibLoader.Loader { - private static Map, TInjectTask> injectTypes = Maps.newLinkedHashMap(); + private static final Map, TInjectTask> injectTypes = Maps.newLinkedHashMap(); static { // Instance Inject @@ -91,6 +91,9 @@ public class TInjectLoader implements TabooLibLoader.Loader { t.printStackTrace(); } } + if (args.autoMigrate()) { + config.migrate(); + } TabooLibLoader.runTask(config::runListener); } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/scala/io/izzel/taboolib/util/ArrayUtil.java b/src/main/scala/io/izzel/taboolib/util/ArrayUtil.java index 0de8063..c93a395 100644 --- a/src/main/scala/io/izzel/taboolib/util/ArrayUtil.java +++ b/src/main/scala/io/izzel/taboolib/util/ArrayUtil.java @@ -18,7 +18,7 @@ public class ArrayUtil { public static boolean contains(T[] array, T obj) { return indexOf(array, obj) != -1; } - + public static int indexOf(T[] array, T obj) { return array == null || array.length == 0 ? -1 : IntStream.range(0, array.length).filter(i -> array[i] != null && array[i].equals(obj)).findFirst().orElse(-1); } @@ -73,6 +73,22 @@ public class ArrayUtil { return arrayNew; } + public static List setAutoExpand(List list, int index, T element, T def) { + while (list.size() <= index) { + list.add(def); + } + list.set(index, element); + return list; + } + + public static List addAutoExpand(List list, int index, T element, T def) { + while (list.size() <= index) { + list.add(def); + } + list.add(index, element); + return list; + } + @SuppressWarnings("SuspiciousSystemArraycopy") public static T arrayExpand(T oldArray, int expand) { int length = Array.getLength(oldArray); diff --git a/src/main/scala/io/izzel/taboolib/util/Files.java b/src/main/scala/io/izzel/taboolib/util/Files.java index 179338d..b6e338e 100644 --- a/src/main/scala/io/izzel/taboolib/util/Files.java +++ b/src/main/scala/io/izzel/taboolib/util/Files.java @@ -21,6 +21,7 @@ import java.security.MessageDigest; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.jar.JarFile; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -70,6 +71,10 @@ public class Files { return plugin instanceof InternalPlugin ? getTabooLibResource(filename) : plugin.getClass().getClassLoader().getResourceAsStream(filename); } + public static InputStream getResourceChecked(Plugin plugin, String filename) { + return plugin instanceof InternalPlugin ? getResource(plugin, "__resources__/" + filename) : getResource(filename); + } + public static InputStream getTabooLibResource(String filename) { return getCanonicalResource(TabooLib.getPlugin(), filename); } @@ -234,7 +239,7 @@ public class Files { return Optional.ofNullable(readFromURL(url)).orElse(def); } - public static String readFromURL(String url, Charset charset, String def) { + public static String readFromURL(String url, Charset charset, String def) { return Optional.ofNullable(readFromURL(url, charset)).orElse(def); } @@ -273,6 +278,24 @@ public class Files { return null; } + public static List readToList(File file) { + try (FileInputStream fin = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fin, StandardCharsets.UTF_8); BufferedReader bin = new BufferedReader(isr)) { + return bin.lines().collect(Collectors.toList()); + } catch (IOException e) { + e.printStackTrace(); + } + return Collections.emptyList(); + } + + public static List readToList(InputStream inputStream) { + try (InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8); BufferedReader bin = new BufferedReader(isr)) { + return bin.lines().collect(Collectors.toList()); + } catch (IOException e) { + e.printStackTrace(); + } + return Collections.emptyList(); + } + public static String readFromStream(InputStream in) { return readFromStream(in, 1024, StandardCharsets.UTF_8); } @@ -296,7 +319,9 @@ public class Files { } public static void read(File file, ReadHandle readHandle) { - try (FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader)) { + try (FileInputStream fileInputStream = new FileInputStream(file); + InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { readHandle.read(bufferedReader); } catch (Throwable t) { t.printStackTrace(); @@ -304,7 +329,8 @@ public class Files { } public static void read(InputStream in, ReadHandle readHandle) { - try (InputStreamReader inputStreamReader = new InputStreamReader(in); BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + try (InputStreamReader inputStreamReader = new InputStreamReader(in, StandardCharsets.UTF_8); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { readHandle.read(bufferedReader); } catch (Throwable t) { t.printStackTrace(); @@ -312,7 +338,9 @@ public class Files { } public static void write(File file, WriteHandle writeHandle) { - try (FileWriter fileWriter = new FileWriter(file); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + try (FileOutputStream fileOutputStream = new FileOutputStream(file); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8); + BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter)) { writeHandle.write(bufferedWriter); bufferedWriter.flush(); } catch (Throwable t) { @@ -321,7 +349,9 @@ public class Files { } public static void writeAppend(File file, WriteHandle writeHandle) { - try (FileWriter fileWriter = new FileWriter(file, true); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + try (FileOutputStream fileOutputStream = new FileOutputStream(file, true); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8); + BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter)) { writeHandle.write(bufferedWriter); bufferedWriter.flush(); } catch (Throwable t) { @@ -330,7 +360,8 @@ public class Files { } public static void write(OutputStream out, WriteHandle writeHandle) { - try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(out); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter)) { + try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(out, StandardCharsets.UTF_8); + BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter)) { writeHandle.write(bufferedWriter); bufferedWriter.flush(); } catch (Throwable t) { @@ -355,14 +386,14 @@ public class Files { } public static String getFileHash(File file, String algorithm) { - try(FileInputStream fileInputStream = new FileInputStream(file)) { + try (FileInputStream fileInputStream = new FileInputStream(file)) { MessageDigest digest = MessageDigest.getInstance(algorithm); byte[] buffer = new byte[1024]; int length; while ((length = fileInputStream.read(buffer, 0, 1024)) != -1) { digest.update(buffer, 0, length); } - byte[] md5Bytes = digest.digest(); + byte[] md5Bytes = digest.digest(); return new BigInteger(1, md5Bytes).toString(16); } catch (Throwable t) { t.printStackTrace(); diff --git a/src/main/scala/io/izzel/taboolib/util/Strings.java b/src/main/scala/io/izzel/taboolib/util/Strings.java index 6f4ba08..7dd2d3f 100644 --- a/src/main/scala/io/izzel/taboolib/util/Strings.java +++ b/src/main/scala/io/izzel/taboolib/util/Strings.java @@ -60,9 +60,13 @@ public class Strings { } public static String hashKeyForDisk(String key) { + return hashKeyForDisk(key, "MD5"); + } + + public static String hashKeyForDisk(String key, String type) { String cacheKey; try { - final MessageDigest mDigest = MessageDigest.getInstance("MD5"); + final MessageDigest mDigest = MessageDigest.getInstance(type); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) {