From 1c339c622eb2ca85500b34ef56a53d1be9aa84af Mon Sep 17 00:00:00 2001 From: zonble Date: Mon, 31 Jan 2022 04:29:53 +0800 Subject: [PATCH] Loads the user phrases just after the files are changes. The revision uses a FSEventStream to monitor the change of the folder that stores user phrases. --- McBopomofo.xcodeproj/project.pbxproj | 17 +++++ Packages/FSEventStreamHelper/.gitignore | 7 ++ Packages/FSEventStreamHelper/Package.swift | 28 +++++++ Packages/FSEventStreamHelper/README.md | 3 + .../FSEventStreamHelper.swift | 73 +++++++++++++++++++ .../FSEventStreamHelperTests.swift | 5 ++ Source/AppDelegate.swift | 13 ++++ Source/InputMethodController.swift | 2 +- Source/LanguageModelManager.mm | 4 +- 9 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 Packages/FSEventStreamHelper/.gitignore create mode 100644 Packages/FSEventStreamHelper/Package.swift create mode 100644 Packages/FSEventStreamHelper/README.md create mode 100644 Packages/FSEventStreamHelper/Sources/FSEventStreamHelper/FSEventStreamHelper.swift create mode 100644 Packages/FSEventStreamHelper/Tests/FSEventStreamHelperTests/FSEventStreamHelperTests.swift diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 31ea286c..629c3053 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */; }; + D47D73C327A7200500255A50 /* FSEventStreamHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D47D73C227A7200500255A50 /* FSEventStreamHelper */; }; D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; }; @@ -203,6 +204,7 @@ D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = ""; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerBopomofoTests.swift; sourceTree = ""; }; + D47D73C027A71FFA00255A50 /* FSEventStreamHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FSEventStreamHelper; path = Packages/FSEventStreamHelper; sourceTree = ""; }; D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; @@ -232,6 +234,7 @@ D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */, D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */, D427F7A927905E90004A2160 /* TooltipUI in Frameworks */, + D47D73C327A7200500255A50 /* FSEventStreamHelper in Frameworks */, D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */, D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */, ); @@ -262,6 +265,7 @@ 6A0D4EC215FC0D3C00ABF4B3 /* Source */, D485D3B72796A8A000657FF3 /* McBopomofoTests */, 6A0D4EA315FC0D2D00ABF4B3 /* Products */, + D47D73C127A7200500255A50 /* Frameworks */, ); sourceTree = ""; }; @@ -467,10 +471,18 @@ D427F7B2279086B5004A2160 /* InputSourceHelper */, D427F7BF27908EAC004A2160 /* OpenCCBridge */, D44FB7482791B346003C80A6 /* VXHanConvert */, + D47D73C027A71FFA00255A50 /* FSEventStreamHelper */, ); name = Packages; sourceTree = ""; }; + D47D73C127A7200500255A50 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; D485D3B72796A8A000657FF3 /* McBopomofoTests */ = { isa = PBXGroup; children = ( @@ -522,6 +534,7 @@ D427F7B3279086DC004A2160 /* InputSourceHelper */, D427F7C027908EFC004A2160 /* OpenCCBridge */, D44FB7492791B829003C80A6 /* VXHanConvert */, + D47D73C227A7200500255A50 /* FSEventStreamHelper */, ); productName = McBopomofo; productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */; @@ -1387,6 +1400,10 @@ isa = XCSwiftPackageProductDependency; productName = VXHanConvert; }; + D47D73C227A7200500255A50 /* FSEventStreamHelper */ = { + isa = XCSwiftPackageProductDependency; + productName = FSEventStreamHelper; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 6A0D4E9415FC0CFA00ABF4B3 /* Project object */; diff --git a/Packages/FSEventStreamHelper/.gitignore b/Packages/FSEventStreamHelper/.gitignore new file mode 100644 index 00000000..bb460e7b --- /dev/null +++ b/Packages/FSEventStreamHelper/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/Packages/FSEventStreamHelper/Package.swift b/Packages/FSEventStreamHelper/Package.swift new file mode 100644 index 00000000..25df501a --- /dev/null +++ b/Packages/FSEventStreamHelper/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "FSEventStreamHelper", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "FSEventStreamHelper", + targets: ["FSEventStreamHelper"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "FSEventStreamHelper", + dependencies: []), + .testTarget( + name: "FSEventStreamHelperTests", + dependencies: ["FSEventStreamHelper"]), + ] +) diff --git a/Packages/FSEventStreamHelper/README.md b/Packages/FSEventStreamHelper/README.md new file mode 100644 index 00000000..096a3b6f --- /dev/null +++ b/Packages/FSEventStreamHelper/README.md @@ -0,0 +1,3 @@ +# FSEventStreamHelper + +A description of this package. diff --git a/Packages/FSEventStreamHelper/Sources/FSEventStreamHelper/FSEventStreamHelper.swift b/Packages/FSEventStreamHelper/Sources/FSEventStreamHelper/FSEventStreamHelper.swift new file mode 100644 index 00000000..4e84af08 --- /dev/null +++ b/Packages/FSEventStreamHelper/Sources/FSEventStreamHelper/FSEventStreamHelper.swift @@ -0,0 +1,73 @@ +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..