DispatchWatcher.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. //
  2. // GCDFSWatcher.swift
  3. // FileKit
  4. //
  5. // Created by ijump on 5/2/16.
  6. // Copyright © 2017 Nikolai Vazquez. All rights reserved.
  7. //
  8. import Foundation
  9. /// Delegate for `DispatchFileSystemWatcher`
  10. public protocol DispatchFileSystemWatcherDelegate: class {
  11. // MARK: - Protocol
  12. /// Call when the file-system object was deleted from the namespace.
  13. func fsWatcherDidObserveDelete(_ watch: DispatchFileSystemWatcher)
  14. /// Call when the file-system object data changed.
  15. func fsWatcherDidObserveWrite(_ watch: DispatchFileSystemWatcher)
  16. /// Call when the file-system object changed in size.
  17. func fsWatcherDidObserveExtend(_ watch: DispatchFileSystemWatcher)
  18. /// Call when the file-system object metadata changed.
  19. func fsWatcherDidObserveAttrib(_ watch: DispatchFileSystemWatcher)
  20. /// Call when the file-system object link count changed.
  21. func fsWatcherDidObserveLink(_ watch: DispatchFileSystemWatcher)
  22. /// Call when the file-system object was renamed in the namespace.
  23. func fsWatcherDidObserveRename(_ watch: DispatchFileSystemWatcher)
  24. /// Call when the file-system object was revoked.
  25. func fsWatcherDidObserveRevoke(_ watch: DispatchFileSystemWatcher)
  26. /// Call when the file-system object was created.
  27. func fsWatcherDidObserveCreate(_ watch: DispatchFileSystemWatcher)
  28. /// Call when the directory changed (additions, deletions, and renamings).
  29. ///
  30. /// Calls `fsWatcherDidObserveWrite` by default.
  31. func fsWatcherDidObserveDirectoryChange(_ watch: DispatchFileSystemWatcher)
  32. }
  33. // Optional func and default func for `GCDFSWatcherDelegate`
  34. // Empty func treated as Optional func
  35. public extension DispatchFileSystemWatcherDelegate {
  36. // MARK: - Extension
  37. /// Call when the file-system object was deleted from the namespace.
  38. func fsWatcherDidObserveDelete(_ watch: DispatchFileSystemWatcher) {}
  39. /// Call when the file-system object data changed.
  40. func fsWatcherDidObserveWrite(_ watch: DispatchFileSystemWatcher) {}
  41. /// Call when the file-system object changed in size.
  42. func fsWatcherDidObserveExtend(_ watch: DispatchFileSystemWatcher) {}
  43. /// Call when the file-system object metadata changed.
  44. func fsWatcherDidObserveAttrib(_ watch: DispatchFileSystemWatcher) {}
  45. /// Call when the file-system object link count changed.
  46. func fsWatcherDidObserveLink(_ watch: DispatchFileSystemWatcher) {}
  47. /// Call when the file-system object was renamed in the namespace.
  48. func fsWatcherDidObserveRename(_ watch: DispatchFileSystemWatcher) {}
  49. /// Call when the file-system object was revoked.
  50. func fsWatcherDidObserveRevoke(_ watch: DispatchFileSystemWatcher) {}
  51. /// Call when the file-system object was created.
  52. func fsWatcherDidObserveCreate(_ watch: DispatchFileSystemWatcher) {}
  53. /// Call when the directory changed (additions, deletions, and renamings).
  54. ///
  55. /// Calls `fsWatcherDidObserveWrite` by default.
  56. func fsWatcherDidObserveDirectoryChange(_ watch: DispatchFileSystemWatcher) {
  57. fsWatcherDidObserveWrite(watch)
  58. }
  59. }
  60. /// Watcher for Vnode events
  61. open class DispatchFileSystemWatcher {
  62. // MARK: - Properties
  63. /// The paths being watched.
  64. public let path: Path
  65. /// The events used to create the watcher.
  66. public let events: DispatchFileSystemEvents
  67. /// The delegate to call when events happen
  68. weak var delegate: DispatchFileSystemWatcherDelegate?
  69. /// The watcher for watching creation event
  70. weak var createWatcher: DispatchFileSystemWatcher?
  71. /// The callback for file system events.
  72. fileprivate let callback: ((DispatchFileSystemWatcher) -> Void)?
  73. /// The queue for the watcher.
  74. fileprivate let queue: DispatchQueue?
  75. /// A file descriptor for the path.
  76. fileprivate var fileDescriptor: CInt = -1
  77. /// A dispatch source to monitor a file descriptor created from the path.
  78. fileprivate var source: DispatchSourceProtocol?
  79. /// Current events
  80. open var currentEvent: DispatchFileSystemEvents? {
  81. if let source = source {
  82. return DispatchFileSystemEvents(rawValue: source.data)
  83. }
  84. if createWatcher != nil {
  85. return .Create
  86. }
  87. return nil
  88. }
  89. // MARK: - Initialization
  90. /// Creates a watcher for the given paths.
  91. ///
  92. /// - Parameter paths: The paths.
  93. /// - Parameter events: The create events.
  94. /// - Parameter queue: The queue to be run within.
  95. /// - Parameter callback: The callback to be called on changes.
  96. ///
  97. /// This method does follow links.
  98. init(path: Path,
  99. events: DispatchFileSystemEvents,
  100. queue: DispatchQueue,
  101. callback: ((DispatchFileSystemWatcher) -> Void)?
  102. ) {
  103. self.path = path.absolute
  104. self.events = events
  105. self.queue = queue
  106. self.callback = callback
  107. }
  108. // MARK: - Deinitialization
  109. deinit {
  110. // print("\(path): Deinit")
  111. close()
  112. }
  113. // MARK: - Private Methods
  114. /// Dispatch the event.
  115. ///
  116. /// If `callback` is set, call the `callback`. Else if `delegate` is set, call the `delegate`
  117. ///
  118. /// - Parameter eventType: The current event to be watched.
  119. fileprivate func dispatchDelegate(_ eventType: DispatchFileSystemEvents) {
  120. if let callback = self.callback {
  121. callback(self)
  122. } else if let delegate = self.delegate {
  123. if eventType.contains(.Delete) {
  124. delegate.fsWatcherDidObserveDelete(self)
  125. }
  126. if eventType.contains(.Write) {
  127. if path.isDirectoryFile {
  128. delegate.fsWatcherDidObserveDirectoryChange(self)
  129. } else {
  130. delegate.fsWatcherDidObserveWrite(self)
  131. }
  132. }
  133. if eventType.contains(.Extend) {
  134. delegate.fsWatcherDidObserveExtend(self)
  135. }
  136. if eventType.contains(.Attribute) {
  137. delegate.fsWatcherDidObserveAttrib(self)
  138. }
  139. if eventType.contains(.Link) {
  140. delegate.fsWatcherDidObserveLink(self)
  141. }
  142. if eventType.contains(.Rename) {
  143. delegate.fsWatcherDidObserveRename(self)
  144. }
  145. if eventType.contains(.Revoke) {
  146. delegate.fsWatcherDidObserveRevoke(self)
  147. }
  148. if eventType.contains(.Create) {
  149. delegate.fsWatcherDidObserveCreate(self)
  150. }
  151. }
  152. }
  153. // MARK: - Methods
  154. /// Start watching.
  155. ///
  156. /// This method does follow links.
  157. @discardableResult
  158. open func startWatching() -> Bool {
  159. // create a watcher for CREATE event if path not exists and events contains CREATE
  160. if !path.exists {
  161. if events.contains(.Create) {
  162. let parent = path.parent.absolute
  163. var _events = events
  164. _events.remove(.Create)
  165. // only watch a CREATE event if parent exists and is a directory
  166. if parent.isDirectoryFile {
  167. #if os(OSX)
  168. let watch = { parent.watch2($0, callback: $1) }
  169. #else
  170. let watch = { parent.watch($0, callback: $1) }
  171. #endif
  172. createWatcher = watch(.Write) { [weak self] watch in
  173. // stop watching when path created
  174. if self?.path.isRegular == true || self?.path.isDirectoryFile == true {
  175. self?.dispatchDelegate(.Create)
  176. // self.delegate?.fsWatcherDidObserveCreate(self)
  177. self?.createWatcher = nil
  178. self?.startWatching()
  179. watch.stopWatching()
  180. }
  181. }
  182. return true
  183. }
  184. }
  185. }
  186. // Only watching for regular file and directory
  187. else if path.isRegular || path.isDirectoryFile {
  188. if source == nil && fileDescriptor == -1 {
  189. fileDescriptor = open(path._safeRawValue, O_EVTONLY)
  190. if fileDescriptor == -1 { return false }
  191. var _events = events
  192. _events.remove(.Create)
  193. source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor, eventMask: DispatchSource.FileSystemEvent(rawValue: _events.rawValue), queue: queue)
  194. // Recheck if open success and source create success
  195. if source != nil && fileDescriptor != -1 {
  196. guard callback != nil || delegate != nil else {
  197. return false
  198. }
  199. // Define the block to call when a file change is detected.
  200. source!.setEventHandler { // [unowned self] () in
  201. let eventType = DispatchFileSystemEvents(rawValue: self.source!.data)
  202. self.dispatchDelegate(eventType)
  203. }
  204. // Define a cancel handler to ensure the path is closed when the source is cancelled.
  205. source!.setCancelHandler { // [unowned self] () in
  206. _ = Darwin.close(self.fileDescriptor)
  207. self.fileDescriptor = -1
  208. self.source = nil
  209. }
  210. // Start monitoring the path via the source.
  211. source!.resume()
  212. return true
  213. }
  214. }
  215. }
  216. return false
  217. }
  218. /// Stop watching.
  219. ///
  220. /// **Note:** make sure call this func, or `self` will not release
  221. open func stopWatching() {
  222. if source != nil {
  223. source!.cancel()
  224. }
  225. }
  226. /// Closes the watcher.
  227. open func close() {
  228. createWatcher?.stopWatching()
  229. _ = Darwin.close(self.fileDescriptor)
  230. self.fileDescriptor = -1
  231. self.source = nil
  232. }
  233. }
  234. extension Path {
  235. #if os(OSX)
  236. // MARK: - Watching
  237. /// Watches a path for filesystem events and handles them in the callback or delegate.
  238. ///
  239. /// - Parameter events: The create events.
  240. /// - Parameter queue: The queue to be run within.
  241. /// - Parameter delegate: The delegate to call when events happen.
  242. /// - Parameter callback: The callback to be called on changes.
  243. public func watch2(_ events: DispatchFileSystemEvents = .All,
  244. queue: DispatchQueue? = nil,
  245. delegate: DispatchFileSystemWatcherDelegate? = nil,
  246. callback: ((DispatchFileSystemWatcher) -> Void)? = nil
  247. ) -> DispatchFileSystemWatcher {
  248. let dispatchQueue: DispatchQueue
  249. if #available(OSX 10.10, *) {
  250. dispatchQueue = queue ?? DispatchQueue.global(qos: .default)
  251. } else {
  252. dispatchQueue = queue ?? DispatchQueue.global(priority: .default)
  253. }
  254. let watcher = DispatchFileSystemWatcher(path: self, events: events, queue: dispatchQueue, callback: callback)
  255. watcher.delegate = delegate
  256. watcher.startWatching()
  257. return watcher
  258. }
  259. #else
  260. // MARK: - Watching
  261. /// Watches a path for filesystem events and handles them in the callback or delegate.
  262. ///
  263. /// - Parameter events: The create events.
  264. /// - Parameter queue: The queue to be run within.
  265. /// - Parameter delegate: The delegate to call when events happen.
  266. /// - Parameter callback: The callback to be called on changes.
  267. public func watch(_ events: DispatchFileSystemEvents = .All,
  268. queue: DispatchQueue = DispatchQueue.global(qos: .default),
  269. delegate: DispatchFileSystemWatcherDelegate? = nil,
  270. callback: ((DispatchFileSystemWatcher) -> Void)? = nil
  271. ) -> DispatchFileSystemWatcher {
  272. let watcher = DispatchFileSystemWatcher(path: self, events: events, queue: queue, callback: callback)
  273. watcher.delegate = delegate
  274. watcher.startWatching()
  275. return watcher
  276. }
  277. #endif
  278. }