69
70
event.name.decode("utf8")
70
71
except UnicodeDecodeError:
71
72
dirname = event.path.decode("utf8")
72
self.invnames_log.info("%s in %r: path %r", event.maskname,
73
self.general_processor.invnames_log.info("%s in %r: path %r", event.maskname,
73
74
dirname, event.name)
74
self.monitor.eq.push('FS_INVALID_NAME',
75
self.general_processor.monitor.eq.push('FS_INVALID_NAME',
75
76
dirname=dirname, filename=event.name)
77
78
real_func(self, event)
150
151
FS_(DIR|FILE)_MOVE event when possible.
152
153
def __init__(self, monitor, ignore_config=None):
153
self.log = logging.getLogger('ubuntuone.SyncDaemon.GeneralINotProc')
154
self.invnames_log = logging.getLogger(
155
'ubuntuone.SyncDaemon.InvalidNames')
156
self.monitor = monitor
154
self.general_processor = GeneralINotifyProcessor(monitor,
155
self.handle_dir_delete, NAME_TRANSLATIONS,
156
self.platform_is_ignored, pyinotify.IN_IGNORED,
157
ignore_config=ignore_config)
157
158
self.held_event = None
158
159
self.timer = None
159
self.frozen_path = None
160
self.frozen_evts = False
161
self._to_mute = MuteFilter()
162
self.conflict_RE = re.compile(r"\.u1conflict(?:\.\d+)?$")
164
if ignore_config is not None:
165
self.log.info("Ignoring files: %s", ignore_config)
166
# thanks Chipaca for the following "regex composing"
167
complex = '|'.join('(?:' + r + ')' for r in ignore_config)
168
self.ignore_RE = re.compile(complex)
170
self.ignore_RE = None
172
def _mute_filter(self, action, event, paths):
173
"""Really touches the mute filter."""
174
# all events have one path except the MOVEs
175
if event in ("FS_FILE_MOVE", "FS_DIR_MOVE"):
176
f_path, t_path = paths['path_from'], paths['path_to']
177
is_from_forreal = not self.is_ignored(f_path)
178
is_to_forreal = not self.is_ignored(t_path)
179
if is_from_forreal and is_to_forreal:
180
action(event, **paths)
182
action('FS_FILE_CREATE', path=t_path)
183
action('FS_FILE_CLOSE_WRITE', path=t_path)
186
if not self.is_ignored(path):
187
action(event, **paths)
189
161
def rm_from_mute_filter(self, event, paths):
190
162
"""Remove an event and path(s) from the mute filter."""
191
self._mute_filter(self._to_mute.rm, event, paths)
163
self.general_processor.rm_from_mute_filter(event, paths)
193
165
def add_to_mute_filter(self, event, paths):
194
166
"""Add an event and path(s) to the mute filter."""
195
self._mute_filter(self._to_mute.add, event, paths)
167
self.general_processor.add_to_mute_filter(event, paths)
197
169
def on_timeout(self):
198
170
"""Called on timeout."""
207
179
except error.AlreadyCalled:
208
180
# self.timeout() was *just* called, do nothing here
210
self.push_event(self.held_event)
182
self.general_processor.push_event(self.held_event)
211
183
self.held_event = None
213
185
@validate_filename
214
186
def process_IN_OPEN(self, event):
215
187
"""Filter IN_OPEN to make it happen only in files."""
216
188
if not (event.mask & pyinotify.IN_ISDIR):
217
self.push_event(event)
189
self.general_processor.push_event(event)
219
191
@validate_filename
220
192
def process_IN_CLOSE_NOWRITE(self, event):
221
193
"""Filter IN_CLOSE_NOWRITE to make it happen only in files."""
222
194
if not (event.mask & pyinotify.IN_ISDIR):
223
self.push_event(event)
195
self.general_processor.push_event(event)
225
197
def process_IN_MOVE_SELF(self, event):
226
198
"""Don't do anything here.
239
211
self.held_event = event
240
212
self.timer = reactor.callLater(1, self.on_timeout)
214
def platform_is_ignored(self, path):
215
"""Should we ignore this path in the current platform.?"""
216
# don't support links yet
217
if os.path.islink(path):
242
221
def is_ignored(self, path):
243
"""should we ignore this path?"""
244
# don't support symlinks yet
245
if os.path.islink(path):
248
# check if we are can read
249
if os.path.exists(path) and not os.access(path, os.R_OK):
250
self.log.warning("Ignoring path as we don't have enough "
251
"permissions to track it: %r", path)
254
is_conflict = self.conflict_RE.search
255
dirname, filename = os.path.split(path)
257
if is_conflict(filename):
259
# ignore partial downloads
260
if filename == '.u1partial' or filename.startswith('.u1partial.'):
263
# and ignore paths that are inside conflicts (why are we even
264
# getting the event?)
265
if any(part.endswith('.u1partial') or is_conflict(part)
266
for part in dirname.split(os.path.sep)):
269
if self.ignore_RE is not None and self.ignore_RE.match(filename):
222
"""Should we ignore this path?"""
223
return self.general_processor.is_ignored(path)
274
225
@validate_filename
275
226
def process_IN_MOVED_TO(self, event):
299
252
if f_share_id != t_share_id:
300
253
# if the share_id are != push a delete/create
301
254
m = "Delete because of different shares: %r"
302
self.log.info(m, f_path)
303
self.eq_push(evtname+"DELETE", path=f_path)
304
self.eq_push(evtname+"CREATE", path=t_path)
255
self.general_processor.log.info(m, f_path)
256
self.general_processor.eq_push(evtname+"DELETE", path=f_path)
257
self.general_processor.eq_push(evtname+"CREATE", path=t_path)
305
258
if not event.dir:
306
self.eq_push('FS_FILE_CLOSE_WRITE',
259
self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
309
self.monitor.inotify_watch_fix(f_path, t_path)
310
self.eq_push(evtname+"MOVE",
262
self.general_processor.monitor.inotify_watch_fix(f_path, t_path)
263
self.general_processor.eq_push(evtname+"MOVE",
311
264
path_from=f_path, path_to=t_path)
312
265
elif is_to_forreal:
313
266
# this is the case of a MOVE from something ignored
316
269
evtname = "FS_DIR_"
318
271
evtname = "FS_FILE_"
319
self.eq_push(evtname + "CREATE", path=t_path)
272
self.general_processor.eq_push(evtname + "CREATE", path=t_path)
320
273
if not event.dir:
321
self.eq_push('FS_FILE_CLOSE_WRITE', path=t_path)
274
self.general_processor.eq_push('FS_FILE_CLOSE_WRITE', path=t_path)
323
276
self.held_event = None
326
279
self.release_held_event()
327
self.push_event(event)
280
self.general_processor.push_event(event)
329
282
# we don't have a held_event so this is a move from outside.
330
283
# if it's a file move it's atomic on POSIX, so we aren't going to
331
284
# receive a IN_CLOSE_WRITE, so let's fake it for files
332
self.push_event(event)
285
self.general_processor.push_event(event)
333
286
if not event.dir:
334
287
t_path = os.path.join(event.path, event.name)
335
self.eq_push('FS_FILE_CLOSE_WRITE', path=t_path)
337
def eq_push(self, event_name, **event_data):
338
"""Sends to EQ the event data, maybe filtering it."""
339
if not self._to_mute.pop(event_name, **event_data):
340
self.monitor.eq.push(event_name, **event_data)
288
self.general_processor.eq_push('FS_FILE_CLOSE_WRITE', path=t_path)
342
290
@validate_filename
343
291
def process_default(self, event):
344
292
"""Push the event into the EventQueue."""
345
293
if self.held_event is not None:
346
294
self.release_held_event()
347
self.push_event(event)
349
def push_event(self, event):
350
"""Push the event to the EQ."""
352
if event.mask == pyinotify.IN_IGNORED:
355
# change the pattern IN_CREATE to FS_FILE_CREATE or FS_DIR_CREATE
357
evt_name = NAME_TRANSLATIONS[event.mask]
359
raise KeyError("Unhandled Event in INotify: %s" % event)
362
fullpath = os.path.join(event.path, event.name)
364
# check if the path is not frozen
365
if self.frozen_path is not None:
366
if event.path == self.frozen_path:
367
# this will at least store the last one, for debug
369
self.frozen_evts = (evt_name, fullpath)
372
if not self.is_ignored(fullpath):
373
if evt_name == 'FS_DIR_DELETE':
374
self.handle_dir_delete(fullpath)
375
self.eq_push(evt_name, path=fullpath)
295
self.general_processor.push_event(event)
377
298
def freeze_begin(self, path):
378
299
"""Puts in hold all the events for this path."""
379
self.log.debug("Freeze begin: %r", path)
380
self.frozen_path = path
381
self.frozen_evts = False
300
self.general_processor.freeze_begin(path)
383
302
def freeze_rollback(self):
384
303
"""Unfreezes the frozen path, reseting to idle state."""
385
self.log.debug("Freeze rollback: %r", self.frozen_path)
386
self.frozen_path = None
387
self.frozen_evts = False
304
self.general_processor.freeze_rollback()
389
306
def freeze_commit(self, events):
390
307
"""Unfreezes the frozen path, sending received events if not dirty.
395
312
- push the here received events, return False
397
self.log.debug("Freeze commit: %r (%d events)",
398
self.frozen_path, len(events))
401
self.log.debug("Dirty by %s", self.frozen_evts)
402
self.frozen_evts = False
405
# push the received events
406
for evt_name, path in events:
407
if not self.is_ignored(path):
408
self.eq_push(evt_name, path=path)
410
self.frozen_path = None
411
self.frozen_evts = False
314
return self.general_processor.freeze_commit(events)
414
316
def handle_dir_delete(self, fullpath):
415
317
"""Some special work when a directory is deleted."""
416
318
# remove the watch on that dir from our structures
417
self.monitor.rm_watch(fullpath)
319
self.general_processor.rm_watch(fullpath)
419
321
# handle the case of move a dir to a non-watched directory
420
paths = self.monitor.fs.get_paths_starting_with(fullpath,
322
paths = self.general_processor.get_paths_starting_with(fullpath,
422
325
paths.sort(reverse=True)
423
326
for path, is_dir in paths:
424
327
m = "Pushing deletion because of parent dir move: (is_dir=%s) %r"
425
self.log.info(m, is_dir, path)
328
self.general_processor.log.info(m, is_dir, path)
427
self.monitor.rm_watch(path)
428
self.eq_push('FS_DIR_DELETE', path=path)
330
self.general_processor.rm_watch(path)
331
self.general_processor.eq_push('FS_DIR_DELETE', path=path)
430
self.eq_push('FS_FILE_DELETE', path=path)
333
self.general_processor.eq_push('FS_FILE_DELETE', path=path)
336
def mute_filter(self):
337
"""Return the mute filter used by the processor."""
338
return self.general_processor.filter
341
def frozen_path(self):
342
"""Return the frozen path."""
343
return self.general_processor.frozen_path
347
"""Return the logger of the instance."""
348
return self.general_processor.log
433
351
class FilesystemMonitor(object):