1from FSEvents import * 2import objc 3import sys 4import os 5import stat 6import errno 7 8def T_or_F(x): 9 if x: 10 return "TRUE" 11 else: 12 return "FALSE" 13 14class Settings (object): 15 __slots__ = ( 16 'sinceWhen', 17 'latency', 18 'flags', 19 'array_of_paths', 20 'print_settings', 21 'verbose', 22 'flush_seconds', 23 ) 24 def __init__(self): 25 self.sinceWhen = kFSEventStreamEventIdSinceNow 26 self.latency = 60 27 self.flags = 0 28 self.array_of_paths = [] 29 self.print_settings = False 30 self.verbose = False 31 self.flush_seconds = -1 32 33 def mesg(self, fmt, *args, **kwds): 34 if args: 35 fmt = fmt % args 36 37 elif kwds: 38 fmt = fmt % kwds 39 40 if self.verbose: 41 print >>sys.stderr, fmt 42 else: 43 print >>sys.stdout, fmt 44 45 def debug(self, fmt, *args, **kwds): 46 if not self.verbose: 47 return 48 49 if args: 50 fmt = fmt % args 51 52 elif kwds: 53 fmt = fmt % kwds 54 55 print >>sys.stderr, fmt 56 57 def error(self, fmt, *args, **kwds): 58 if args: 59 fmt = fmt % args 60 61 elif kwds: 62 fmt = fmt % kwds 63 64 print >>sys.stderr, fmt 65 66 def dump(self): 67 self.mesg("settings->sinceWhen = %d", self.sinceWhen) 68 self.mesg("settings->latency = %f", self.latency) 69 self.mesg("settings->flags = %#x", self.flags) 70 self.mesg("settings->num_paths = %d", len(self.array_of_paths)) 71 for idx, path in enumerate(self.array_of_paths): 72 self.mesg("settings->array_of_paths[%d] = '%s'", idx, path) 73 self.mesg("settings->verbose = %s", T_or_F(self.verbose)) 74 self.mesg("settings->print_settings = %s", T_or_F(self.print_settings)) 75 self.mesg("settings->flush_seconds = %d", self.flush_seconds) 76 77 def parse_argv(self, argv): 78 self.latency = 1.0 79 self.sinceWhen = -1 # kFSEventStreamEventIdSinceNow 80 self.flush_seconds = -1 81 82 idx = 1 83 while idx < len(argv): 84 if argv[idx] == '-usage': 85 usage(argv[0]) 86 87 elif argv[idx] == '-print_settings': 88 self.print_settings = True 89 90 elif argv[idx] == '-sinceWhen': 91 self.sinceWhen = int(argv[idx+1]) 92 idx += 1 93 94 elif argv[idx] == '-latency': 95 self.latency = float(argv[idx+1]) 96 idx += 1 97 98 elif argv[idx] == '-flags': 99 self.flags = int(argv[idx+1]) 100 idx += 1 101 102 elif argv[idx] == '-flush': 103 self.flush_seconds = float(argv[idx+1]) 104 idx += 1 105 106 elif argv[idx] == '-verbose': 107 self.verbose = True 108 109 else: 110 break 111 112 idx += 1 113 114 self.array_of_paths = argv[idx:] 115 116settings = Settings() 117 118def usage(progname): 119 settings.mesg("") 120 settings.mesg("Usage: %s <flags> <path>", progname) 121 settings.mesg("Flags:") 122 settings.mesg(" -sinceWhen <when> Specify a time from whence to search for applicable events") 123 settings.mesg(" -latency <seconds> Specify latency") 124 settings.mesg(" -flags <flags> Specify flags as a number") 125 settings.mesg(" -flush <seconds> Invoke FSEventStreamFlushAsync() after the specified number of seconds.") 126 settings.mesg("") 127 sys.exit(1) 128 129 130def timer_callback(timer, streamRef): 131 settings.debug("CFAbsoluteTimeGetCurrent() => %.3f", CFAbsoluteTimeGetCurrent()) 132 settings.debug("FSEventStreamFlushAsync(streamRef = %s)", streamRef) 133 FSEventStreamFlushAsync(streamRef) 134 135def fsevents_callback(streamRef, clientInfo, numEvents, eventPaths, eventMasks, eventIDs): 136 settings.debug("fsevents_callback(streamRef = %s, clientInfo = %s, numEvents = %s)", streamRef, clientInfo, numEvents) 137 settings.debug("fsevents_callback: FSEventStreamGetLatestEventId(streamRef) => %s", FSEventStreamGetLatestEventId(streamRef)) 138 full_path = clientInfo 139 140 for i in range(numEvents): 141 path = eventPaths[i] 142 if path[-1] == '/': 143 path = path[:-1] 144 145 if eventMasks[i] & kFSEventStreamEventFlagMustScanSubDirs: 146 recursive = True 147 148 elif eventMasks[i] & kFSEventStreamEventFlagUserDropped: 149 settings.mesg("BAD NEWS! We dropped events.") 150 settings.mesg("Forcing a full rescan.") 151 recursive = 1 152 path = full_path 153 154 elif eventMasks[i] & kFSEventStreamEventFlagKernelDropped: 155 settings.mesg("REALLY BAD NEWS! The kernel dropped events.") 156 settings.mesg("Forcing a full rescan.") 157 recursive = 1 158 path = full_path 159 160 else: 161 recursive = False 162 163 new_size = get_directory_size(path, recursive) 164 if new_size < 0: 165 print "Could not update size on %s"%(path,) 166 167 else: 168 print "New total size: %d (change made to %s) for path: %s"%( 169 get_total_size(), path, full_path) 170 171 172def my_FSEventStreamCreate(path): 173 if settings.verbose: 174 print [path] 175 176 streamRef = FSEventStreamCreate(kCFAllocatorDefault, 177 fsevents_callback, 178 path, 179 [path], 180 settings.sinceWhen, 181 settings.latency, 182 settings.flags) 183 if streamRef is None: 184 settings.error("ERROR: FSEVentStreamCreate() => NULL") 185 return None 186 187 if settings.verbose: 188 FSEventStreamShow(streamRef) 189 190 return streamRef 191 192def main(argv=None): 193 if argv is None: 194 argv = sys.argv 195 196 settings.parse_argv(argv) 197 198 if settings.verbose or settings.print_settings: 199 settings.dump() 200 201 if settings.print_settings: 202 return 0 203 204 if len(settings.array_of_paths) != 1: 205 usage(argv[0]) 206 207 full_path = os.path.abspath(settings.array_of_paths[0]) 208 209 streamRef = my_FSEventStreamCreate(full_path) 210 211 FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode) 212 213 startedOK = FSEventStreamStart(streamRef) 214 if not startedOK: 215 settings.error("failed to start the FSEventStream") 216 return 217 218 # NOTE: we get the initial size *after* we start the 219 # FSEventStream so that there is no window 220 # during which we would miss events. 221 # 222 dir_sz = get_directory_size(full_path, 1) 223 print "Initial total size is: %d for path: %s"%(get_total_size(), full_path) 224 225 if settings.flush_seconds >= 0: 226 settings.debug("CFAbsoluteTimeGetCurrent() => %.3f", CFAbsoluteTimeGetCurrent()) 227 228 timer = CFRunLoopTimerCreate( 229 FSEventStreamGetSinceWhen(streamRef), 230 CFAbsoluteTimeGetCurrent() + settings.flush_seconds, 231 settings.flush_seconds, 232 0, 0, timer_callback, streamRef) 233 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode) 234 235 236 # Run 237 CFRunLoopRun() 238 239 #Stop / Invalidate / Release 240 FSEventStreamStop(streamRef) 241 FSEventStreamInvalidate(streamRef) 242 #FSEventStreamRelease(streamRef) 243 return 244 245 246# 247#-------------------------------------------------------------------------------- 248# Routines to keep track of the size of the directory hierarchy 249# we are watching. 250# 251# This code is not exemplary in any way. It should definitely 252# not be used in production code as it is inefficient. 253# 254 255class dir_item (object): 256 __slots__ = ('dirname', 'size') 257 258dir_items = {} 259 260def get_total_size(): 261 return sum(dir_items.itervalues()) 262 263def iterate_subdirs(dirname, recursive): 264 dir_items[dirname] = 0 265 266 try: 267 names = os.listdir(dirname) 268 except os.error, msg: 269 print msg.errno, errno.EPERM 270 if msg.errno in (errno.ENOENT, errno.EPERM, errno.EACCES): 271 del dir_items[dirname] 272 return 0 273 274 raise 275 276 size = 0 277 for nm in names: 278 full_path = os.path.join(dirname, nm) 279 st = os.lstat(full_path) 280 size += st.st_size 281 282 if stat.S_ISDIR(st.st_mode) and (recursive or (full_path not in dir_items)): 283 result = get_directory_size(full_path, 1) 284 285 dir_items[dirname] = size 286 return size 287 288 289def check_for_deleted_dirs(): 290 for path in dir_items.keys(): 291 try: 292 os.stat(path) 293 except os.error: 294 del dir_items[path] 295 296def get_directory_size(dirname, recursive): 297 check_for_deleted_dirs() 298 return iterate_subdirs(dirname, recursive) 299 300if __name__ == "__main__": 301 main() 302