diff --git a/AUTHORS b/AUTHORS index 8a513d36..ad116f65 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,7 +18,6 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in - McBopomofo for macOS 2.x architect. - Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen). - Notifier window and Tooltip UI. - - FSEventStreamHelper. - App-style installer (only preserved for developer purposes). - mgrPrefs (userdefaults manager). - apiUpdate. diff --git a/README-CHT.md b/README-CHT.md index 519af832..a1da4aed 100644 --- a/README-CHT.md +++ b/README-CHT.md @@ -82,10 +82,8 @@ ## 應用授權 -威注音專案僅用到小麥注音的下述程式組件(MIT License): +威注音專案目前僅用到小麥注音的下述程式組件(MIT License): -- 狀態管理引擎 & NSStringUtils & FSEventStreamHelper (by Zonble Yang),基於狀態設計模式: - - ctlInputMethod 輸入法主控制器內則採用策略設計模式來處理各種狀態。 - 半衰記憶模組的 C++ 原版作者是 Mengjuei Hsieh,且由 Shiki Suen 用 Swift 與 C# 分別重寫、繼續開發。 - 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。 - Voltaire MK2 選字窗、飄雲通知視窗 (by Zonble Yang),有大幅度修改。 diff --git a/README.md b/README.md index fe6a9f04..586d6053 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,8 @@ ## 应用授权 -威注音专案仅用到小麦注音的下述程式组件(MIT License): +威注音专案目前仅用到小麦注音的下述程式组件(MIT License): -- 状态管理引擎 & NSStringUtils & FSEventStreamHelper (by Zonble Yang),基于状态设计模式: - - ctlInputMethod 输入法主控制器内则采用策略设计模式来处理各种状态。 - 半衰记忆模组的 C++ 原版作者是 Mengjuei Hsieh,且由 Shiki Suen 用 Swift 与 C# 分别重写、继续开发。 - 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。 - Voltaire MK2 选字窗、飘云通知视窗 (by Zonble Yang),有大幅度修改。 diff --git a/Source/3rdParty/FolderMonitor/FolderMonitor.swift b/Source/3rdParty/FolderMonitor/FolderMonitor.swift new file mode 100644 index 00000000..4a98c56c --- /dev/null +++ b/Source/3rdParty/FolderMonitor/FolderMonitor.swift @@ -0,0 +1,58 @@ +// (c) 2018 Daniel Galasko +// Ref: https://medium.com/over-engineering/monitoring-a-folder-for-changes-in-ios-dc3f8614f902 + +import Foundation + +class FolderMonitor { + // MARK: Properties + + /// A file descriptor for the monitored directory. + private var monitoredFolderFileDescriptor: CInt = -1 + /// A dispatch queue used for sending file changes in the directory. + private let folderMonitorQueue = DispatchQueue(label: "FolderMonitorQueue", attributes: .concurrent) + /// A dispatch source to monitor a file descriptor created from the directory. + private var folderMonitorSource: DispatchSourceFileSystemObject? + /// URL for the directory being monitored. + let url: URL + + var folderDidChange: (() -> Void)? + + // MARK: Initializers + + init(url: URL) { + self.url = url + } + + // MARK: Monitoring + + /// Listen for changes to the directory (if we are not already). + func startMonitoring() { + guard folderMonitorSource == nil, monitoredFolderFileDescriptor == -1 else { + return + } + // Open the directory referenced by URL for monitoring only. + monitoredFolderFileDescriptor = open(url.path, O_EVTONLY) + // Define a dispatch source monitoring the directory for additions, deletions, and renamings. + folderMonitorSource = DispatchSource.makeFileSystemObjectSource( + fileDescriptor: monitoredFolderFileDescriptor, eventMask: .write, queue: folderMonitorQueue + ) + // Define the block to call when a file change is detected. + folderMonitorSource?.setEventHandler { [weak self] in + self?.folderDidChange?() + } + // Define a cancel handler to ensure the directory is closed when the source is cancelled. + folderMonitorSource?.setCancelHandler { [weak self] in + guard let strongSelf = self else { return } + close(strongSelf.monitoredFolderFileDescriptor) + strongSelf.monitoredFolderFileDescriptor = -1 + strongSelf.folderMonitorSource = nil + } + // Start monitoring the directory via the source. + folderMonitorSource?.resume() + } + + /// Stop listening for changes to the directory, if the source has been created. + func stopMonitoring() { + folderMonitorSource?.cancel() + } +} diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index a2a67c7b..97cb725f 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -12,10 +12,8 @@ import Cocoa import InputMethodKit @objc(AppDelegate) -class AppDelegate: NSObject, NSApplicationDelegate, - FSEventStreamHelperDelegate, NSUserNotificationCenterDelegate -{ - func helper(_: FSEventStreamHelper, didReceive _: [FSEventStreamHelper.Event]) { +class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate { + private func reloadOnFolderChangeHappens() { // 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { if mgrPrefs.shouldAutoReloadUserDataFiles { @@ -30,9 +28,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, private var ctlPrefWindowInstance: ctlPrefWindow? private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window private var checkTask: URLSessionTask? - public var fsStreamHelper = FSEventStreamHelper( - path: mgrLangModel.dataFolderPath(isDefaultFolder: false), - queue: DispatchQueue(label: "vChewing User Phrases") + public lazy var folderMonitor = FolderMonitor( + url: URL(fileURLWithPath: mgrLangModel.dataFolderPath(isDefaultFolder: false)) ) private var currentAlertType: String = "" @@ -59,8 +56,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, IME.initLangModels(userOnly: false) } - fsStreamHelper.delegate = self - _ = fsStreamHelper.start() + folderMonitor.folderDidChange = { [weak self] in + self?.reloadOnFolderChangeHappens() + } + folderMonitor.startMonitoring() mgrPrefs.fixOddPreferences() mgrPrefs.setMissingDefaults() @@ -71,8 +70,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, } } - func updateStreamHelperPath() { - fsStreamHelper.path = mgrPrefs.userDataFolderSpecified + func updateDirectoryMonitorPath() { + folderMonitor.stopMonitoring() + folderMonitor = FolderMonitor( + url: URL(fileURLWithPath: mgrLangModel.dataFolderPath(isDefaultFolder: false)) + ) + folderMonitor.folderDidChange = { [weak self] in + self?.reloadOnFolderChangeHappens() + } + folderMonitor.startMonitoring() } func showPreferences() { diff --git a/Source/Modules/FileHandlers/FSEventStreamHelper.swift b/Source/Modules/FileHandlers/FSEventStreamHelper.swift deleted file mode 100644 index 30e2c294..00000000 --- a/Source/Modules/FileHandlers/FSEventStreamHelper.swift +++ /dev/null @@ -1,88 +0,0 @@ -// (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// No trademark license is granted to use the trade names, trademarks, service -// marks, or product names of Contributor, except as required to fulfill notice -// requirements defined in MIT License. - -import Cocoa - -public protocol FSEventStreamHelperDelegate: AnyObject { - func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) -} - -public class FSEventStreamHelper { - public struct Event { - var path: String - var flags: FSEventStreamEventFlags - var id: FSEventStreamEventId - } - - public var path: String - public let dispatchQueue: DispatchQueue - public weak var delegate: FSEventStreamHelperDelegate? - - public init(path: String, queue: DispatchQueue) { - self.path = path - dispatchQueue = queue - } - - private var stream: FSEventStreamRef? - - public func start() -> Bool { - if stream != nil { - return false - } - var context = FSEventStreamContext() - context.info = Unmanaged.passUnretained(self).toOpaque() - guard - let stream = FSEventStreamCreate( - nil, - { - _, 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..