forked from bmdelacruz/FileSystemWatcher
-
Notifications
You must be signed in to change notification settings - Fork 3
/
fswatcher.swift
167 lines (130 loc) · 5.01 KB
/
fswatcher.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import Dispatch
import Foundation
import inotify
public typealias FileDescriptor = Int
public typealias WatchDescriptor = Int
public struct FileSystemEvent {
public var watchDescriptor: WatchDescriptor
public var name: String
public var mask: UInt32
public var cookie: UInt32
public var length: UInt32
}
public enum FileSystemEventType: UInt32 {
case inAccess = 0x00000001
case inModify = 0x00000002
case inAttrib = 0x00000004
case inCloseWrite = 0x00000008
case inCloseNoWrite = 0x00000010
case inClose = 0x00000018
case inOpen = 0x00000020
case inMovedFrom = 0x00000040
case inMovedTo = 0x00000080
case inMove = 0x000000C0
case inCreate = 0x00000100
case inDelete = 0x00000200
case inDeleteSelf = 0x00000400
case inMoveSelf = 0x00000800
case inUnmount = 0x00002000
case inQueueOverflow = 0x00004000
case inIgnored = 0x00008000
case inOnlyDir = 0x01000000
case inDontFollow = 0x02000000
case inExcludeUnlink = 0x04000000
case inMaskAdd = 0x20000000
case inIsDir = 0x40000000
case inOneShot = 0x80000000
case inAllEvents = 0x00000FFF
@available(*, unavailable)
public static func getTypesFromMask(_ mask: UInt32) -> [FileSystemEventType] {
return [FileSystemEventType]()
}
}
public class FileSystemWatcher {
private let fileDescriptor: FileDescriptor
private let dispatchQueue: DispatchQueue
private var watchDescriptors: [WatchDescriptor]
private var shouldStopWatching: Bool = false
private let deferringDelay : Double
public init(deferringDelay : Double = 2.0) {
dispatchQueue = DispatchQueue(label: "inotify.queue", qos: .background,
attributes: [.initiallyInactive, .concurrent])
fileDescriptor = FileDescriptor(inotify_init())
if fileDescriptor < 0 {
fatalError("Failed to initialize inotify")
}
watchDescriptors = [WatchDescriptor]()
self.deferringDelay = deferringDelay
}
public func start() {
shouldStopWatching = false
dispatchQueue.activate()
}
public func stop() {
shouldStopWatching = true
dispatchQueue.suspend()
for watchDescriptor in watchDescriptors {
inotify_rm_watch(Int32(fileDescriptor), Int32(watchDescriptor))
}
close(Int32(fileDescriptor))
}
public func watch(paths: [String], for events: [FileSystemEventType],
thenInvoke callback: @escaping (FileSystemEvent) -> Void) -> [WatchDescriptor] {
var flags: UInt32 = events.count > 0 ? 0 : FileSystemEventType.inAllEvents.rawValue
for event in events {
flags |= event.rawValue
}
var wds = [WatchDescriptor]() // watch descriptors for the call only
for path in paths {
let watchDescriptor = inotify_add_watch(Int32(fileDescriptor), path, flags)
watchDescriptors.append(WatchDescriptor(watchDescriptor))
wds.append(WatchDescriptor(watchDescriptor))
// For deferred execution
var lastTimeStamp = Date()
dispatchQueue.async {
let bufferLength = 32
let buffer = UnsafeMutablePointer<CChar>.allocate(capacity: bufferLength)
var fileSystemEvent : FileSystemEvent?
while !self.shouldStopWatching {
// IF it's been more than 2 seconds since the last callback,
// run the callback again.
if(lastTimeStamp.timeIntervalSinceNow < -self.deferringDelay) {
lastTimeStamp = Date()
// This checks if there exists an event
// before sending it. It's very important,
// because it makes the first run possible.
if let lastEvent = fileSystemEvent {
self.dispatchQueue.asyncAfter(deadline: .now() + self.deferringDelay) {
callback(lastEvent)
}
}
}
// IF NOT, then we defer the events until enough time passes
// for the callback window to open again
else {
var currentIndex: Int = 0
let readLength = read(Int32(self.fileDescriptor), buffer, bufferLength)
while currentIndex < readLength {
let event = withUnsafePointer(to: &buffer[currentIndex]) {
return $0.withMemoryRebound(to: inotify_event.self, capacity: 1) {
return $0.pointee
}
}
if event.len > 0 {
fileSystemEvent = FileSystemEvent(
watchDescriptor: WatchDescriptor(event.wd),
name: "", // String(cString: event.name), // value of type 'inotify_event' has no member 'name'
mask: event.mask,
cookie: event.cookie,
length: event.len
)
}
currentIndex += MemoryLayout<inotify_event>.stride + Int(event.len)
}
}
}
}
}
return wds
}
}