diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 0959b114..65552c77 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -128,13 +128,23 @@ struct VersionUpdateApi { } @objc(AppDelegate) -class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate { +class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate, FSEventStreamHelperDelegate { + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) { + DispatchQueue.main.async { + if Preferences.shouldAutoReloadUserDataFiles { + LanguageModelManager.loadUserPhrases() + LanguageModelManager.loadUserPhraseReplacement() + } + } + } + @IBOutlet weak var window: NSWindow? private var preferencesWindowController: PreferencesWindowController? private var aboutWindowController: frmAboutWindow? // New About Window private var checkTask: URLSessionTask? private var updateNextStepURL: URL? + private var fsStreamHelper = FSEventStreamHelper(path: LanguageModelManager.dataFolderPath, queue: DispatchQueue(label: "User Phrases")) // 補上 dealloc deinit { @@ -149,6 +159,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle LanguageModelManager.loadCNSData() LanguageModelManager.loadUserPhrases() LanguageModelManager.loadUserPhraseReplacement() + fsStreamHelper.delegate = self + _ = fsStreamHelper.start() Preferences.setMissingDefaults() diff --git a/Source/Engine/LanguageModel/FSEventStreamHelper.swift b/Source/Engine/LanguageModel/FSEventStreamHelper.swift new file mode 100644 index 00000000..8db85e3b --- /dev/null +++ b/Source/Engine/LanguageModel/FSEventStreamHelper.swift @@ -0,0 +1,80 @@ +/* + * FSEventStreamHelper.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +import Cocoa + +public protocol FSEventStreamHelperDelegate: AnyObject { + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) +} + +public class FSEventStreamHelper : NSObject { + + public struct Event { + var path: String + var flags: FSEventStreamEventFlags + var id: FSEventStreamEventId + } + + public let path: String + public let dispatchQueue: DispatchQueue + public weak var delegate: FSEventStreamHelperDelegate? + + @objc public init(path: String, queue: DispatchQueue) { + self.path = path + self.dispatchQueue = queue + } + + private var stream: FSEventStreamRef? = nil + + public func start() -> Bool { + if stream != nil { + return false + } + var context = FSEventStreamContext() + context.info = Unmanaged.passUnretained(self).toOpaque() + guard let stream = FSEventStreamCreate(nil, { + (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in + let helper = Unmanaged.fromOpaque(clientCallBackInfo!).takeUnretainedValue() + let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) + let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) + let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) + let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) + let events = (0..& nodes, double epsilon) NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - // Load UserPhrases // 這裡今後需要改造成「驗證檔案指紋、根據驗證結果判定是否需要重新讀入」的形式。 - if (Preferences.shouldAutoReloadUserDataFiles) { - [self reloadUserPhrases:(id)nil]; - } - // reset the state _currentDeferredClient = nil; _currentCandidateClient = nil; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 7ab0ff8a..c70b20d9 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -181,7 +181,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [writeFile writeData:data]; [writeFile closeFile]; - [self loadUserPhrases]; + // [self loadUserPhrases]; // Not Needed since AppDelegate is handling this. return YES; } diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 2d5893b2..19b78ffd 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -17,4 +17,5 @@ + (void)loadCNSData; + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; +@property (class, readonly, nonatomic) NSString *dataFolderPath; @end diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 79091b13..abe9cde9 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */; }; 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; 5B810D9F27A3A5E50032C1A9 /* LMConsolidator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */; }; + 5B9DD0A927A7950D00ED335A /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; }; @@ -124,6 +125,7 @@ 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEventStreamHelper.swift; sourceTree = ""; }; 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; }; @@ -264,6 +266,7 @@ 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */, 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */, 5B810D9E27A3A5E50032C1A9 /* LMConsolidator.h */, + 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */, ); path = LanguageModel; sourceTree = ""; @@ -714,6 +717,7 @@ 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */, 5BDD25F9279D6D1200AA18F8 /* ioapi.m in Sources */, 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */, + 5B9DD0A927A7950D00ED335A /* FSEventStreamHelper.swift in Sources */, 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.swift in Sources */,