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.
This commit is contained in:
zonble 2022-01-31 04:29:53 +08:00
parent 3099798195
commit 1c339c622e
9 changed files with 150 additions and 2 deletions

View File

@ -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 = "<group>"; };
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerBopomofoTests.swift; sourceTree = "<group>"; };
D47D73C027A71FFA00255A50 /* FSEventStreamHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FSEventStreamHelper; path = Packages/FSEventStreamHelper; sourceTree = "<group>"; };
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
@ -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 = "<group>";
};
@ -467,10 +471,18 @@
D427F7B2279086B5004A2160 /* InputSourceHelper */,
D427F7BF27908EAC004A2160 /* OpenCCBridge */,
D44FB7482791B346003C80A6 /* VXHanConvert */,
D47D73C027A71FFA00255A50 /* FSEventStreamHelper */,
);
name = Packages;
sourceTree = "<group>";
};
D47D73C127A7200500255A50 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
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 */;

View File

@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -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"]),
]
)

View File

@ -0,0 +1,3 @@
# FSEventStreamHelper
A description of this package.

View File

@ -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<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!).takeUnretainedValue()
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.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..<eventCount).map {
FSEventStreamHelper.Event(path: String(cString: pathsPtr[$0]),
flags: flagsPtr[$0],
id: eventIDsPtr[$0] )
}
helper.delegate?.helper(helper, didReceive: events)
},
&context,
[path] as CFArray,
UInt64(kFSEventStreamEventIdSinceNow),
1.0,
FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone)
) else {
return false
}
FSEventStreamSetDispatchQueue(stream, dispatchQueue)
if !FSEventStreamStart(stream) {
FSEventStreamInvalidate(stream)
return false
}
self.stream = stream
return true
}
func stop() {
guard let stream = stream else {
return
}
FSEventStreamStop(stream)
FSEventStreamInvalidate(stream)
self.stream = nil
}
}

View File

@ -0,0 +1,5 @@
import XCTest
@testable import FSEventStreamHelper
final class FSEventStreamHelperTests: XCTestCase {
}

View File

@ -23,6 +23,7 @@
import Cocoa
import InputMethodKit
import FSEventStreamHelper
private let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
private let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
@ -150,11 +151,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
private var preferencesWindowController: PreferencesWindowController?
private var checkTask: URLSessionTask?
private var updateNextStepURL: URL?
private var fsStreamHelper = FSEventStreamHelper(path: LanguageModelManager.dataFolderPath, queue: DispatchQueue(label: "User Phrases"))
func applicationDidFinishLaunching(_ notification: Notification) {
LanguageModelManager.setupDataModelValueConverter()
LanguageModelManager.loadUserPhrases()
LanguageModelManager.loadUserPhraseReplacement()
fsStreamHelper.delegate = self
_ = fsStreamHelper.start()
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)
@ -243,3 +247,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
updateNextStepURL = nil
}
}
extension AppDelegate : FSEventStreamHelperDelegate {
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) {
DispatchQueue.main.async {
LanguageModelManager.loadUserPhrases()
LanguageModelManager.loadUserPhraseReplacement()
}
}
}

View File

@ -393,7 +393,7 @@ extension McBopomofoInputMethodController {
if state.tooltip.isEmpty {
hideTooltip()
} else {
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, client: client)
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.markerIndex, client: client)
}
}

View File

@ -222,7 +222,9 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
[writeFile writeData:data];
[writeFile closeFile];
[self loadUserPhrases];
// We use FSEventStream to monitor the change of the user phrase folder,
// so we don't have to load data here.
// [self loadUserPhrases];
return YES;
}