FileSystemWatcher.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. //
  2. // FileSystemEvent.swift
  3. // FileKit
  4. //
  5. // The MIT License (MIT)
  6. //
  7. // Copyright (c) 2015-2017 Nikolai Vazquez
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. //
  27. import Foundation
  28. #if os(OSX)
  29. /// Watches a given set of paths and runs a callback per event.
  30. public class FileSystemWatcher {
  31. // MARK: - Private Static Properties
  32. /// The event stream callback for when events occur.
  33. private static let _eventCallback: FSEventStreamCallback = { // swiftlint:disable all
  34. (stream: ConstFSEventStreamRef,
  35. contextInfo: UnsafeMutableRawPointer?,
  36. numEvents: Int,
  37. eventPaths: UnsafeMutableRawPointer,
  38. eventFlags: UnsafePointer<FSEventStreamEventFlags>,
  39. eventIds: UnsafePointer<FSEventStreamEventId>) in // swiftlint:enable all
  40. FileSystemWatcher.log("Callback Fired")
  41. let watcher: FileSystemWatcher = unsafeBitCast(contextInfo, to: FileSystemWatcher.self)
  42. defer {
  43. watcher.lastEventId = eventIds[numEvents - 1]
  44. }
  45. guard let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else {
  46. return
  47. }
  48. for index in 0..<numEvents {
  49. let id = eventIds[index]
  50. let path = paths[index]
  51. let flags = eventFlags[index]
  52. let event = FileSystemEvent(
  53. id: id,
  54. path: Path(path),
  55. flags: FileSystemEventFlags(rawValue: Int(flags)))
  56. watcher._processEvent(event)
  57. }
  58. }
  59. // MARK: - Properties
  60. /// The paths being watched.
  61. public let paths: [Path]
  62. /// How often the watcher updates.
  63. public let latency: CFTimeInterval
  64. /// The queue for the watcher.
  65. public let queue: DispatchQueue?
  66. /// The flags used to create the watcher.
  67. public let flags: FileSystemEventStreamCreateFlags
  68. /// The run loop mode for the watcher.
  69. public var runLoopMode: CFRunLoopMode = CFRunLoopMode.defaultMode
  70. /// The run loop for the watcher.
  71. public var runLoop: CFRunLoop = CFRunLoopGetMain()
  72. /// The callback for filesystem events.
  73. private let _callback: (FileSystemEvent) -> Void
  74. /// The last event ID for the watcher.
  75. public private(set) var lastEventId: FSEventStreamEventId
  76. /// Whether or not the watcher has started yet.
  77. private var _started = false
  78. /// The event stream for the watcher.
  79. private var _stream: FileSystemEventStream?
  80. // MARK: - Initialization
  81. /// Creates a watcher for the given paths.
  82. ///
  83. /// - Parameter paths: The paths.
  84. /// - Parameter sinceWhen: The date to start at.
  85. /// - Parameter flags: The create flags.
  86. /// - Parameter latency: The latency.
  87. /// - Parameter queue: The queue to be run within.
  88. /// - Parameter callback: The callback to be called on changes.
  89. public init(paths: [Path],
  90. sinceWhen: FSEventStreamEventId = FileSystemEvent.nowEventId,
  91. flags: FileSystemEventStreamCreateFlags = [.UseCFTypes, .FileEvents],
  92. latency: CFTimeInterval = 0,
  93. queue: DispatchQueue? = nil,
  94. callback: @escaping (FileSystemEvent) -> Void
  95. ) {
  96. self.lastEventId = sinceWhen
  97. self.paths = paths
  98. self.flags = flags
  99. self.latency = latency
  100. self.queue = queue
  101. self._callback = callback
  102. }
  103. // MARK: - Deinitialization
  104. deinit {
  105. self.close()
  106. }
  107. // MARK: - Private Methods
  108. /// Processes the event by logging it and then running the callback.
  109. ///
  110. /// - Parameter event: The file system event to be logged.
  111. private func _processEvent(_ event: FileSystemEvent) {
  112. FileSystemWatcher.log("\t\(event.id) - \(event.flags) - \(event.path)")
  113. self._callback(event)
  114. }
  115. /// Prints the message when in debug mode.
  116. ///
  117. /// - Parameter message: The message to be logged.
  118. private static func log(_ message: String) {
  119. #if DEBUG
  120. print(message)
  121. #endif
  122. }
  123. // MARK: - Methods
  124. // Start watching by creating the stream
  125. /// Starts watching.
  126. public func watch() {
  127. guard _started == false else { return }
  128. var context = FSEventStreamContext(
  129. version: 0,
  130. info: nil,
  131. retain: nil,
  132. release: nil,
  133. copyDescription: nil
  134. )
  135. // add self into context
  136. context.info = Unmanaged.passUnretained(self).toOpaque()
  137. guard let streamRef = FSEventStreamCreate(
  138. kCFAllocatorDefault,
  139. FileSystemWatcher._eventCallback,
  140. &context,
  141. paths.map {$0.rawValue} as CFArray,
  142. // since when
  143. lastEventId,
  144. // how long to wait after an event occurs before forwarding it
  145. latency,
  146. UInt32(flags.rawValue)
  147. ) else {
  148. return
  149. }
  150. _stream = FileSystemEventStream(rawValue: streamRef)
  151. _stream?.scheduleWithRunLoop(runLoop, runLoopMode: runLoopMode)
  152. if let q = queue {
  153. _stream?.setDispatchQueue(q)
  154. }
  155. _stream?.start()
  156. _started = true
  157. }
  158. // Stops, invalidates and releases the stream
  159. /// Closes the watcher.
  160. public func close() {
  161. guard _started == true else { return }
  162. _stream?.stop()
  163. _stream?.invalidate()
  164. _stream?.release()
  165. _stream = nil
  166. _started = false
  167. }
  168. /// Requests that the fseventsd daemon send any events it has already
  169. /// buffered (via the latency parameter).
  170. ///
  171. /// This occurs asynchronously; clients will not have received all the
  172. /// callbacks by the time this call returns to them.
  173. public func flushAsync() {
  174. _stream?.flushAsync()
  175. }
  176. /// Requests that the fseventsd daemon send any events it has already
  177. /// buffered (via the latency). Then runs the runloop in its private mode
  178. /// till all events that have occurred have been reported (via the client's
  179. /// callback).
  180. ///
  181. /// This occurs synchronously; clients will have received all the callbacks
  182. /// by the time this call returns to them.
  183. public func flushSync() {
  184. _stream?.flushSync()
  185. }
  186. }
  187. #endif