From 5a4b3e728ed2fd524b5879d6d4a301d949785297 Mon Sep 17 00:00:00 2001 From: Miwa <63481257+ensan-hcl@users.noreply.github.com> Date: Sun, 25 May 2025 19:36:11 +0900 Subject: [PATCH] test: verify pause file recovery --- Docs/conversion_algorithms.md | 2 + README.md | 4 +- .../DicdataStore/LearningMemory.swift | 13 +++++ .../LearningMemoryTests.swift | 55 +++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 Tests/KanaKanjiConverterModuleTests/LearningMemoryTests.swift diff --git a/Docs/conversion_algorithms.md b/Docs/conversion_algorithms.md index 48c809e..9ee44b7 100644 --- a/Docs/conversion_algorithms.md +++ b/Docs/conversion_algorithms.md @@ -171,6 +171,8 @@ ComposingText( 3のステップの実行中にエラーが生じた場合、`.pause`があるため、次回キーボードを開いた際は学習を停止状態にします。ついで適切なタイミングで再度ステップ3を実行することで、安全に全てのファイルを更新することができます。 +azooKeyKanaKanjiConverter では、変換器を開いた際に `.pause` ファイルが残っている場合、自動的に空の一時記憶とマージを試みて `.pause` を削除し、学習機能を復旧します。 + ## 変換候補の並び順 変換候補の並び順の決定はとても難しい問題です。azooKeyではおおよそ以下のようになっています。`Converter.swift`が並び順を決めていますが、とても複雑な実装になっているため、改善したいと思っています。 diff --git a/README.md b/README.md index b3c5b0a..7455959 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,12 @@ let options = ConvertRequestOptions.withDefaultDictionary( // ユーザ辞書データのあるディレクトリのURL(書類フォルダを指定) sharedContainerURL: .documentsDirectory, // メタデータ - metadata: .init(versionString: "You App Version X") + metadata: .init(versionString: "You App Version X") ) ``` +開く際に保存処理が中断された `.pause` ファイルが残っている場合は、変換器が自動的に復旧を試みてファイルを削除します。 + ### `ComposingText` `ComposingText`は入力管理を行いつつ変換をリクエストするためのAPIです。ローマ字入力などを適切にハンドルするために利用できます。詳しくは[ドキュメント](./Docs/composing_text.md)を参照してください。 diff --git a/Sources/KanaKanjiConverterModule/DicdataStore/LearningMemory.swift b/Sources/KanaKanjiConverterModule/DicdataStore/LearningMemory.swift index 27f9d43..4867c09 100644 --- a/Sources/KanaKanjiConverterModule/DicdataStore/LearningMemory.swift +++ b/Sources/KanaKanjiConverterModule/DicdataStore/LearningMemory.swift @@ -655,6 +655,19 @@ final class LearningManager { init() { self.memoryCollapsed = LongTermLearningMemory.memoryCollapsed(directoryURL: self.options.memoryDirectoryURL) + if self.memoryCollapsed && options.learningType.needUsingMemory { + do { + try LongTermLearningMemory.merge( + tempTrie: TemporalLearningMemoryTrie(), + directoryURL: self.options.memoryDirectoryURL, + maxMemoryCount: options.maxMemoryCount, + char2UInt8: char2UInt8 + ) + } catch { + debug("LearningManager init: automatic merge failed", error) + } + self.memoryCollapsed = LongTermLearningMemory.memoryCollapsed(directoryURL: self.options.memoryDirectoryURL) + } if memoryCollapsed { // 学習データが壊れている状態であることを警告する debug("LearningManager init: Memory Collapsed") diff --git a/Tests/KanaKanjiConverterModuleTests/LearningMemoryTests.swift b/Tests/KanaKanjiConverterModuleTests/LearningMemoryTests.swift new file mode 100644 index 0000000..455672e --- /dev/null +++ b/Tests/KanaKanjiConverterModuleTests/LearningMemoryTests.swift @@ -0,0 +1,55 @@ +// +// LearningMemoryTests.swift +// KanaKanjiConverterModuleTests +// +// Created by Codex on 2025/05/25. +// + +@testable import KanaKanjiConverterModule +import XCTest + +final class LearningMemoryTests: XCTestCase { + static let resourceURL = Bundle.module.resourceURL!.appendingPathComponent("DictionaryMock", isDirectory: true) + + func testPauseFileIsClearedOnInit() throws { + let dir = ConvertRequestOptions.default.memoryDirectoryURL + try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + let pauseURL = dir.appendingPathComponent(".pause", isDirectory: false) + FileManager.default.createFile(atPath: pauseURL.path, contents: Data()) + XCTAssertTrue(LongTermLearningMemory.memoryCollapsed(directoryURL: dir)) + + _ = LearningManager() + + XCTAssertFalse(LongTermLearningMemory.memoryCollapsed(directoryURL: dir)) + try? FileManager.default.removeItem(at: pauseURL) + } + + func testMemoryFilesCreateAndRemove() throws { + let dir = FileManager.default.temporaryDirectory.appendingPathComponent("LearningMemoryTest-\(UUID().uuidString)", isDirectory: true) + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: dir) } + + var manager = LearningManager() + var options = ConvertRequestOptions.default + options.dictionaryResourceURL = Self.resourceURL + options.memoryDirectoryURL = dir + options.learningType = .onlyOutput + options.maxMemoryCount = 32 + manager.setRequestOptions(options: options) + + let element = DicdataElement(word: "テスト", ruby: "テスト", cid: CIDData.一般名詞.cid, mid: MIDData.一般.mid, value: -10) + manager.update(data: [element]) + manager.save() + + let files = try FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil) + XCTAssertTrue(files.contains { $0.lastPathComponent == "memory.louds" }) + XCTAssertTrue(files.contains { $0.lastPathComponent == "memory.loudschars2" }) + XCTAssertTrue(files.contains { $0.lastPathComponent == "memory.memorymetadata" }) + XCTAssertTrue(files.contains { $0.lastPathComponent.hasSuffix(".loudstxt3") }) + + manager.reset() + let filesAfter = try FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil) + XCTAssertTrue(filesAfter.isEmpty) + } +} +