diff --git a/core/src/main/java/dev/felnull/itts/core/savedata/legacy/LegacyMigrator.java b/core/src/main/java/dev/felnull/itts/core/savedata/legacy/LegacyMigrator.java index b98b39c..275bdc9 100644 --- a/core/src/main/java/dev/felnull/itts/core/savedata/legacy/LegacyMigrator.java +++ b/core/src/main/java/dev/felnull/itts/core/savedata/legacy/LegacyMigrator.java @@ -3,6 +3,7 @@ import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; import dev.felnull.fnjl.util.FNDataUtil; import dev.felnull.fnjl.util.FNStringUtil; import dev.felnull.itts.core.dict.CustomDictionaryEntry; @@ -47,10 +48,39 @@ public class LegacyMigrator { */ private final Gson gson = new Gson(); + + /** + * Jsonの保存先ディレクトリ + */ + private final File jsonSaveDir; + + /** + * グローバル辞書ファイル + */ + private final File globalDictFile; + private LegacyMigrator() { + this(JSON_SAVE_DIR, GLOBAL_DICT_DIR); } - private void execute(DataRepository repo) { + LegacyMigrator(File jsonSaveDir, File globalDictFile) { + this.jsonSaveDir = jsonSaveDir; + this.globalDictFile = globalDictFile; + } + + void moveOldData(File moveDir) throws IOException { + FNDataUtil.wishMkdir(moveDir); + + if (jsonSaveDir.exists()) { + Files.move(jsonSaveDir, new File(moveDir, jsonSaveDir.getName())); + } + + if (globalDictFile.exists()) { + Files.move(globalDictFile, new File(moveDir, globalDictFile.getName())); + } + } + + void execute(DataRepository repo) { migrateServerData(repo); migrateServerUserData(repo); migrateServerDictUseData(repo); @@ -59,7 +89,7 @@ private void execute(DataRepository repo) { } private void migrateServerData(DataRepository repo) { - File dir = new File(JSON_SAVE_DIR, "server"); + File dir = new File(jsonSaveDir, "server"); if (!dir.exists()) { return; } @@ -104,7 +134,7 @@ private void migrateServerData(DataRepository repo) { } private void migrateServerUserData(DataRepository repo) { - File dir = new File(JSON_SAVE_DIR, "server_users"); + File dir = new File(jsonSaveDir, "server_users"); if (!dir.exists()) { return; } @@ -139,9 +169,9 @@ private void migrateServerUserData(DataRepository repo) { .forEach((entry) -> { long userId; try { - userId = Long.parseLong(name); + userId = Long.parseLong(entry.getKey()); } catch (NumberFormatException e) { - LOGGER.error("Invalid data name {}", file.getName()); + LOGGER.error("Invalid data name {}", entry.getKey()); return; } JsonObject entryJo = entry.getValue().getAsJsonObject(); @@ -159,7 +189,7 @@ private void migrateServerUserData(DataRepository repo) { } private void migrateServerDictUseData(DataRepository repo) { - File dir = new File(JSON_SAVE_DIR, "dict_use"); + File dir = new File(jsonSaveDir, "dict_use"); if (!dir.exists()) { return; } @@ -210,7 +240,7 @@ private void migrateServerDictUseData(DataRepository repo) { } private void migrateServerDictData(DataRepository repo) { - File dir = new File(JSON_SAVE_DIR, "server_dict"); + File dir = new File(jsonSaveDir, "server_dict"); if (!dir.exists()) { return; } @@ -238,24 +268,30 @@ private void migrateServerDictData(DataRepository repo) { Map dictEntry = loadDict(jo); CustomDictionaryData dictionaryData = repo.getServerCustomDictionaryData(serverId); dictEntry.forEach((target, read) -> { - dictionaryData.add(new CustomDictionaryEntry(target, read, ReplaceType.WORD)); + if (dictionaryData.getByTarget(target).isEmpty()) { + dictionaryData.add(new CustomDictionaryEntry(target, read, ReplaceType.WORD)); + } }); }); } private void migrateGlobalDictData(DataRepository repo) { - if (!GLOBAL_DICT_DIR.exists()) { + if (!globalDictFile.exists()) { return; } - JsonObject jo = loadJson(GLOBAL_DICT_DIR); + JsonObject jo = loadJson(globalDictFile); if (jo == null) { return; } Map dictEntry = loadDict(jo); CustomDictionaryData dictionaryData = repo.getGlobalCustomDictionaryData(); - dictEntry.forEach((target, read) -> dictionaryData.add(new CustomDictionaryEntry(target, read, ReplaceType.WORD))); + dictEntry.forEach((target, read) -> { + if (dictionaryData.getByTarget(target).isEmpty()) { + dictionaryData.add(new CustomDictionaryEntry(target, read, ReplaceType.WORD)); + } + }); } private Map loadDict(JsonObject jo) { @@ -280,11 +316,16 @@ private JsonObject loadJson(File file) { try (Reader reader = new FileReader(file); Reader bufReader = new BufferedReader(reader)) { jo = gson.fromJson(bufReader, JsonObject.class); - } catch (IOException e) { + } catch (IOException | JsonSyntaxException e) { LOGGER.error("Loading failed {}", file.getName(), e); return null; } + if (jo == null) { + LOGGER.error("Empty json file {}", file.getName()); + return null; + } + int version = JsonUtils.getInt(jo, "version", -1); if (version != 0) { LOGGER.error("Unsupported config version {}", file.getName()); @@ -324,14 +365,7 @@ public static void checkAndExecution(Supplier daoProvider) { File moveDir = new File("old_save_data-" + timeText); try { - if (JSON_SAVE_DIR.exists()) { - Files.move(JSON_SAVE_DIR, moveDir); - } - - if (GLOBAL_DICT_DIR.exists()) { - FNDataUtil.wishMkdir(moveDir); - Files.move(GLOBAL_DICT_DIR, new File(moveDir, "global_dict.json")); - } + migrator.moveOldData(moveDir); } catch (IOException e) { throw new IllegalStateException("Failed to move Old SaveData", e); } diff --git a/core/src/test/java/dev/felnull/itts/core/savedata/legacy/LegacyMigratorTest.java b/core/src/test/java/dev/felnull/itts/core/savedata/legacy/LegacyMigratorTest.java new file mode 100644 index 0000000..9f357dc --- /dev/null +++ b/core/src/test/java/dev/felnull/itts/core/savedata/legacy/LegacyMigratorTest.java @@ -0,0 +1,467 @@ +package dev.felnull.itts.core.savedata.legacy; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import dev.felnull.itts.core.dict.ReplaceType; +import dev.felnull.itts.core.savedata.dao.DAOFactory; +import dev.felnull.itts.core.savedata.repository.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.*; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +class LegacyMigratorTest { + + private static final long SERVER_ID = 1219628077353668688L; + private static final long USER_ID_1 = 1346500316383805532L; + private static final long USER_ID_2 = 328520268274204673L; + + private final Gson gson = new Gson(); + + private DataRepository createRepository(Path tempDir) { + File dbFile = new File(tempDir.toFile(), "test.db"); + DataRepository repo = DataRepository.create( + DAOFactory.getInstance().createSQLiteDAO(dbFile)); + repo.init(); + return repo; + } + + private void writeJson(File file, JsonObject jo) throws IOException { + file.getParentFile().mkdirs(); + try (Writer writer = new FileWriter(file)) { + gson.toJson(jo, writer); + } + } + + private void writeRawFile(File file, String content) throws IOException { + file.getParentFile().mkdirs(); + try (Writer writer = new FileWriter(file)) { + writer.write(content); + } + } + + private JsonObject createVersionedJson() { + JsonObject jo = new JsonObject(); + jo.addProperty("version", 0); + return jo; + } + + @Test + void testMigrateServerData(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject serverJson = createVersionedJson(); + serverJson.addProperty("ignore_regex", "(!|/|\\\\$|`).*"); + serverJson.addProperty("need_join", true); + serverJson.addProperty("overwrite_aloud", true); + serverJson.addProperty("notify_move", false); + serverJson.addProperty("read_limit", 100); + serverJson.addProperty("name_read_limit", 10); + + writeJson(new File(saveDir, "server/" + SERVER_ID + ".json"), serverJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.execute(repo); + + ServerData serverData = repo.getServerData(SERVER_ID); + assertNull(serverData.getDefaultVoiceType()); + assertEquals("(!|/|\\\\$|`).*", serverData.getIgnoreRegex()); + assertTrue(serverData.isNeedJoin()); + assertTrue(serverData.isOverwriteAloud()); + assertFalse(serverData.isNotifyMove()); + assertEquals(100, serverData.getReadLimit()); + assertEquals(10, serverData.getNameReadLimit()); + + repo.dispose(); + } + + @Test + void testMigrateServerUserData_userIdNotServerId(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject user1 = new JsonObject(); + user1.addProperty("deny", true); + user1.addProperty("nick_name", "テストユーザー1"); + + JsonObject user2 = new JsonObject(); + user2.addProperty("deny", false); + user2.addProperty("voice_type", "katyou"); + + JsonObject data = new JsonObject(); + data.add(String.valueOf(USER_ID_1), user1); + data.add(String.valueOf(USER_ID_2), user2); + + JsonObject usersJson = createVersionedJson(); + usersJson.add("data", data); + + writeJson(new File(saveDir, "server_users/" + SERVER_ID + ".json"), usersJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.execute(repo); + + ServerUserData userData1 = repo.getServerUserData(SERVER_ID, USER_ID_1); + assertTrue(userData1.isDeny()); + assertEquals("テストユーザー1", userData1.getNickName()); + assertNull(userData1.getVoiceType()); + + ServerUserData userData2 = repo.getServerUserData(SERVER_ID, USER_ID_2); + assertFalse(userData2.isDeny()); + assertEquals("katyou", userData2.getVoiceType()); + assertNull(userData2.getNickName()); + + repo.dispose(); + } + + @Test + void testMigrateServerDictData(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject data = new JsonObject(); + data.addProperty("test", "テスト"); + data.addProperty("hello", "こんにちは"); + + JsonObject dictJson = createVersionedJson(); + dictJson.add("data", data); + + writeJson(new File(saveDir, "server_dict/" + SERVER_ID + ".json"), dictJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.execute(repo); + + CustomDictionaryData dictData = repo.getServerCustomDictionaryData(SERVER_ID); + List entries = dictData.getAll(); + assertEquals(2, entries.size()); + + Set targets = entries.stream() + .map(e -> e.entry().target()) + .collect(Collectors.toSet()); + assertTrue(targets.contains("test")); + assertTrue(targets.contains("hello")); + + repo.dispose(); + } + + @Test + void testMigrateGlobalDictData(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject data = new JsonObject(); + data.addProperty("world", "せかい"); + data.addProperty("abc", "えーびーしー"); + + JsonObject dictJson = createVersionedJson(); + dictJson.add("data", data); + + writeJson(globalDict, dictJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.execute(repo); + + CustomDictionaryData dictData = repo.getGlobalCustomDictionaryData(); + List entries = dictData.getAll(); + assertEquals(2, entries.size()); + + Set targets = entries.stream() + .map(e -> e.entry().target()) + .collect(Collectors.toSet()); + assertTrue(targets.contains("world")); + assertTrue(targets.contains("abc")); + + entries.forEach(e -> assertEquals(ReplaceType.WORD, e.entry().replaceType())); + + repo.dispose(); + } + + @Test + void testMigrateServerDictUseData(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject data = new JsonObject(); + data.addProperty("server", 2); + data.addProperty("global", 3); + data.addProperty("unit", -1); + + JsonObject dictUseJson = createVersionedJson(); + dictUseJson.add("data", data); + + writeJson(new File(saveDir, "dict_use/" + SERVER_ID + ".json"), dictUseJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.execute(repo); + + DictionaryUseData serverDict = repo.getDictionaryUseData(SERVER_ID, "server"); + assertEquals(true, serverDict.isEnable()); + assertEquals(2, serverDict.getPriority()); + + DictionaryUseData globalDictUse = repo.getDictionaryUseData(SERVER_ID, "global"); + assertEquals(true, globalDictUse.isEnable()); + assertEquals(3, globalDictUse.getPriority()); + + DictionaryUseData unitDict = repo.getDictionaryUseData(SERVER_ID, "unit"); + assertEquals(false, unitDict.isEnable()); + assertNull(unitDict.getPriority()); + + repo.dispose(); + } + + @Test + void testMalformedJsonSkipped(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + writeRawFile(new File(saveDir, "server/" + SERVER_ID + ".json"), "{invalid json!!!"); + + JsonObject validUsersJson = createVersionedJson(); + JsonObject userData = new JsonObject(); + JsonObject user = new JsonObject(); + user.addProperty("deny", true); + userData.add(String.valueOf(USER_ID_1), user); + validUsersJson.add("data", userData); + writeJson(new File(saveDir, "server_users/" + SERVER_ID + ".json"), validUsersJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + ServerUserData serverUserData = repo.getServerUserData(SERVER_ID, USER_ID_1); + assertTrue(serverUserData.isDeny()); + + repo.dispose(); + } + + @Test + void testEmptyJsonFileSkipped(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + writeRawFile(new File(saveDir, "server/" + SERVER_ID + ".json"), ""); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + repo.dispose(); + } + + @Test + void testDictMigrationIdempotent(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject serverDictData = new JsonObject(); + serverDictData.addProperty("aaa", "えーえーえー"); + serverDictData.addProperty("bbb", "びーびーびー"); + + JsonObject serverDictJson = createVersionedJson(); + serverDictJson.add("data", serverDictData); + writeJson(new File(saveDir, "server_dict/" + SERVER_ID + ".json"), serverDictJson); + + JsonObject globalDictData = new JsonObject(); + globalDictData.addProperty("xxx", "えっくすえっくすえっくす"); + + JsonObject globalDictJson = createVersionedJson(); + globalDictJson.add("data", globalDictData); + writeJson(globalDict, globalDictJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + migrator.execute(repo); + migrator.execute(repo); + + CustomDictionaryData serverDict = repo.getServerCustomDictionaryData(SERVER_ID); + assertEquals(2, serverDict.getAll().size()); + + CustomDictionaryData globalDictionary = repo.getGlobalCustomDictionaryData(); + assertEquals(1, globalDictionary.getAll().size()); + + repo.dispose(); + } + + @Test + void testNonExistentDirectoriesSkipped(@TempDir Path tempDir) { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + repo.dispose(); + } + + @Test + void testInvalidFileNameSkipped(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject validJson = createVersionedJson(); + validJson.add("data", new JsonObject()); + + writeJson(new File(saveDir, "server/not_a_number.json"), validJson); + writeJson(new File(saveDir, "server_users/invalid.json"), validJson); + writeJson(new File(saveDir, "dict_use/abc.json"), validJson); + writeJson(new File(saveDir, "server_dict/xyz.json"), validJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + repo.dispose(); + } + + @Test + void testInvalidUserIdSkipped(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject invalidUser = new JsonObject(); + invalidUser.addProperty("deny", true); + + JsonObject validUser = new JsonObject(); + validUser.addProperty("deny", true); + + JsonObject data = new JsonObject(); + data.add("not_a_number", invalidUser); + data.add(String.valueOf(USER_ID_1), validUser); + + JsonObject usersJson = createVersionedJson(); + usersJson.add("data", data); + + writeJson(new File(saveDir, "server_users/" + SERVER_ID + ".json"), usersJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + ServerUserData userData = repo.getServerUserData(SERVER_ID, USER_ID_1); + assertTrue(userData.isDeny()); + + repo.dispose(); + } + + @Test + void testUnsupportedVersionSkipped(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject serverJson = new JsonObject(); + serverJson.addProperty("version", 1); + serverJson.addProperty("need_join", true); + + writeJson(new File(saveDir, "server/" + SERVER_ID + ".json"), serverJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + ServerData serverData = repo.getServerData(SERVER_ID); + assertFalse(serverData.isNeedJoin()); + + repo.dispose(); + } + + @Test + void testMissingDataKeySkipped(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + + JsonObject noDataJson = createVersionedJson(); + + writeJson(new File(saveDir, "server_users/" + SERVER_ID + ".json"), noDataJson); + writeJson(new File(saveDir, "dict_use/" + SERVER_ID + ".json"), noDataJson); + writeJson(new File(saveDir, "server_dict/" + SERVER_ID + ".json"), noDataJson); + writeJson(globalDict, noDataJson); + + DataRepository repo = createRepository(tempDir); + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + + assertDoesNotThrow(() -> migrator.execute(repo)); + + CustomDictionaryData serverDict = repo.getServerCustomDictionaryData(SERVER_ID); + assertTrue(serverDict.getAll().isEmpty()); + + CustomDictionaryData globalDictData = repo.getGlobalCustomDictionaryData(); + assertTrue(globalDictData.getAll().isEmpty()); + + repo.dispose(); + } + + @Test + void testMoveOldData_bothExist(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + File moveDir = new File(tempDir.toFile(), "old_save_data"); + + new File(saveDir, "server").mkdirs(); + writeRawFile(new File(saveDir, "server/123.json"), "{}"); + writeRawFile(globalDict, "{}"); + + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.moveOldData(moveDir); + + assertTrue(moveDir.exists()); + assertTrue(new File(moveDir, "save_data").exists()); + assertTrue(new File(moveDir, "save_data/server/123.json").exists()); + assertTrue(new File(moveDir, "global_dict.json").exists()); + + assertFalse(saveDir.exists()); + assertFalse(globalDict.exists()); + } + + @Test + void testMoveOldData_onlySaveDir(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + File moveDir = new File(tempDir.toFile(), "old_save_data"); + + saveDir.mkdirs(); + writeRawFile(new File(saveDir, "server/123.json"), "{}"); + + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.moveOldData(moveDir); + + assertTrue(new File(moveDir, "save_data").exists()); + assertFalse(new File(moveDir, "global_dict.json").exists()); + assertFalse(saveDir.exists()); + } + + @Test + void testMoveOldData_onlyGlobalDict(@TempDir Path tempDir) throws IOException { + File saveDir = new File(tempDir.toFile(), "save_data"); + File globalDict = new File(tempDir.toFile(), "global_dict.json"); + File moveDir = new File(tempDir.toFile(), "old_save_data"); + + writeRawFile(globalDict, "{}"); + + LegacyMigrator migrator = new LegacyMigrator(saveDir, globalDict); + migrator.moveOldData(moveDir); + + assertTrue(moveDir.exists()); + assertFalse(new File(moveDir, "save_data").exists()); + assertTrue(new File(moveDir, "global_dict.json").exists()); + assertFalse(globalDict.exists()); + } +}