| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- //
- // FileSystemEvent.swift
- // FileKit
- //
- // The MIT License (MIT)
- //
- // Copyright (c) 2015-2017 Nikolai Vazquez
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Foundation
- #if os(OSX)
- /// Watches a given set of paths and runs a callback per event.
- public class FileSystemWatcher {
- // MARK: - Private Static Properties
- /// The event stream callback for when events occur.
- private static let _eventCallback: FSEventStreamCallback = { // swiftlint:disable all
- (stream: ConstFSEventStreamRef,
- contextInfo: UnsafeMutableRawPointer?,
- numEvents: Int,
- eventPaths: UnsafeMutableRawPointer,
- eventFlags: UnsafePointer<FSEventStreamEventFlags>,
- eventIds: UnsafePointer<FSEventStreamEventId>) in // swiftlint:enable all
- FileSystemWatcher.log("Callback Fired")
- let watcher: FileSystemWatcher = unsafeBitCast(contextInfo, to: FileSystemWatcher.self)
- defer {
- watcher.lastEventId = eventIds[numEvents - 1]
- }
- guard let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else {
- return
- }
- for index in 0..<numEvents {
- let id = eventIds[index]
- let path = paths[index]
- let flags = eventFlags[index]
- let event = FileSystemEvent(
- id: id,
- path: Path(path),
- flags: FileSystemEventFlags(rawValue: Int(flags)))
- watcher._processEvent(event)
- }
- }
- // MARK: - Properties
- /// The paths being watched.
- public let paths: [Path]
- /// How often the watcher updates.
- public let latency: CFTimeInterval
- /// The queue for the watcher.
- public let queue: DispatchQueue?
- /// The flags used to create the watcher.
- public let flags: FileSystemEventStreamCreateFlags
- /// The run loop mode for the watcher.
- public var runLoopMode: CFRunLoopMode = CFRunLoopMode.defaultMode
- /// The run loop for the watcher.
- public var runLoop: CFRunLoop = CFRunLoopGetMain()
- /// The callback for filesystem events.
- private let _callback: (FileSystemEvent) -> Void
- /// The last event ID for the watcher.
- public private(set) var lastEventId: FSEventStreamEventId
- /// Whether or not the watcher has started yet.
- private var _started = false
- /// The event stream for the watcher.
- private var _stream: FileSystemEventStream?
- // MARK: - Initialization
- /// Creates a watcher for the given paths.
- ///
- /// - Parameter paths: The paths.
- /// - Parameter sinceWhen: The date to start at.
- /// - Parameter flags: The create flags.
- /// - Parameter latency: The latency.
- /// - Parameter queue: The queue to be run within.
- /// - Parameter callback: The callback to be called on changes.
- public init(paths: [Path],
- sinceWhen: FSEventStreamEventId = FileSystemEvent.nowEventId,
- flags: FileSystemEventStreamCreateFlags = [.UseCFTypes, .FileEvents],
- latency: CFTimeInterval = 0,
- queue: DispatchQueue? = nil,
- callback: @escaping (FileSystemEvent) -> Void
- ) {
- self.lastEventId = sinceWhen
- self.paths = paths
- self.flags = flags
- self.latency = latency
- self.queue = queue
- self._callback = callback
- }
- // MARK: - Deinitialization
- deinit {
- self.close()
- }
- // MARK: - Private Methods
- /// Processes the event by logging it and then running the callback.
- ///
- /// - Parameter event: The file system event to be logged.
- private func _processEvent(_ event: FileSystemEvent) {
- FileSystemWatcher.log("\t\(event.id) - \(event.flags) - \(event.path)")
- self._callback(event)
- }
- /// Prints the message when in debug mode.
- ///
- /// - Parameter message: The message to be logged.
- private static func log(_ message: String) {
- #if DEBUG
- print(message)
- #endif
- }
- // MARK: - Methods
- // Start watching by creating the stream
- /// Starts watching.
- public func watch() {
- guard _started == false else { return }
- var context = FSEventStreamContext(
- version: 0,
- info: nil,
- retain: nil,
- release: nil,
- copyDescription: nil
- )
- // add self into context
- context.info = Unmanaged.passUnretained(self).toOpaque()
- guard let streamRef = FSEventStreamCreate(
- kCFAllocatorDefault,
- FileSystemWatcher._eventCallback,
- &context,
- paths.map {$0.rawValue} as CFArray,
- // since when
- lastEventId,
- // how long to wait after an event occurs before forwarding it
- latency,
- UInt32(flags.rawValue)
- ) else {
- return
- }
- _stream = FileSystemEventStream(rawValue: streamRef)
- _stream?.scheduleWithRunLoop(runLoop, runLoopMode: runLoopMode)
- if let q = queue {
- _stream?.setDispatchQueue(q)
- }
- _stream?.start()
- _started = true
- }
- // Stops, invalidates and releases the stream
- /// Closes the watcher.
- public func close() {
- guard _started == true else { return }
- _stream?.stop()
- _stream?.invalidate()
- _stream?.release()
- _stream = nil
- _started = false
- }
- /// Requests that the fseventsd daemon send any events it has already
- /// buffered (via the latency parameter).
- ///
- /// This occurs asynchronously; clients will not have received all the
- /// callbacks by the time this call returns to them.
- public func flushAsync() {
- _stream?.flushAsync()
- }
- /// Requests that the fseventsd daemon send any events it has already
- /// buffered (via the latency). Then runs the runloop in its private mode
- /// till all events that have occurred have been reported (via the client's
- /// callback).
- ///
- /// This occurs synchronously; clients will have received all the callbacks
- /// by the time this call returns to them.
- public func flushSync() {
- _stream?.flushSync()
- }
- }
- #endif
|