static HANDLE add_watch (SeafWTMonitorPriv *priv, const char *repo_id, const char *worktree) { HANDLE dir_handle = NULL; wchar_t *path = NULL; RepoWatchInfo *info; /* worktree is in utf8, need to convert to wchar in win32 */ path = wchar_from_utf8 (worktree); dir_handle = get_handle_of_path (path); if (!dir_handle) { seaf_warning ("failed to open handle for worktree " "of repo %s\n", repo_id); g_free (path); return NULL; } g_free (path); pthread_mutex_lock (&priv->hash_lock); g_hash_table_insert (priv->handle_hash, g_strdup(repo_id), (gpointer)(long)dir_handle); info = create_repo_watch_info (repo_id, worktree); g_hash_table_insert (priv->info_hash, (gpointer)(long)dir_handle, info); pthread_mutex_unlock (&priv->hash_lock); add_event_to_queue (info->status, WT_EVENT_CREATE_OR_UPDATE, "", NULL); return dir_handle; }
/* * On Windows, RENAMED_OLD_NAME and RENAMED_NEW_NAME always comes in pairs. * If a file or dir is moved in/out of the worktree, ADDED or REMOVED event * will be emitted by the kernel. * * This is a two-state state machine. The states are 'not processing rename' and * 'processing rename'. */ static void handle_rename (RepoWatchInfo *info, PFILE_NOTIFY_INFORMATION event, const char *worktree, const char *filename, gboolean last_event) { WTStatus *status = info->status; RenameInfo *rename_info = info->rename_info; if (event->Action == FILE_ACTION_RENAMED_OLD_NAME) seaf_debug ("Move %s ->\n", filename); else if (event->Action == FILE_ACTION_RENAMED_NEW_NAME) seaf_debug ("Move -> %s.\n", filename); if (!rename_info->processing) { if (event->Action == FILE_ACTION_RENAMED_OLD_NAME) { if (!last_event) { set_rename_processing_state (rename_info, filename); } else { /* RENAMED_OLD_NAME should not be the last event, just ignore it. */ } } } else { if (event->Action == FILE_ACTION_RENAMED_NEW_NAME) { /* Rename pair detected. */ add_event_to_queue (status, WT_EVENT_RENAME, rename_info->old_path, filename); unset_rename_processing_state (rename_info); } } }
static void process_one_event (RepoWatchInfo *info, const char *worktree, PFILE_NOTIFY_INFORMATION event, gboolean last_event) { WTStatus *status = info->status; char *filename; gboolean add_to_queue = TRUE; #if 0 if (handle_consecutive_duplicate_event (info, event)) add_to_queue = FALSE; #endif filename = convert_to_unix_path (event->FileName, event->FileNameLength); handle_rename (info, event, worktree, filename, last_event); if (event->Action == FILE_ACTION_MODIFIED) { seaf_debug ("Modified %s.\n", filename); /* Ignore modified event for directories. */ char *full_path = g_build_filename (worktree, filename, NULL); if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) { g_free (full_path); goto out; } g_free (full_path); if (add_to_queue) add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } else if (event->Action == FILE_ACTION_ADDED) { seaf_debug ("Created %s.\n", filename); add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } else if (event->Action == FILE_ACTION_REMOVED) { seaf_debug ("Deleted %s.\n", filename); add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL); } out: g_free (filename); g_atomic_int_set (&info->status->last_changed, (gint)time(NULL)); }
static FSEventStreamRef add_watch (SeafWTMonitor *monitor, const char* repo_id, const char* worktree) { SeafWTMonitorPriv *priv = monitor->priv; RepoWatchInfo *info; double latency = 0.25; /* unit: second */ char *worktree_nfd = g_utf8_normalize (worktree, -1, G_NORMALIZE_NFD); CFStringRef mypaths[1]; mypaths[0] = CFStringCreateWithCString (kCFAllocatorDefault, worktree_nfd, kCFStringEncodingUTF8); g_free (worktree_nfd); CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)mypaths, 1, NULL); FSEventStreamRef stream; /* Create the stream, passing in a callback */ seaf_debug("Use kFSEventStreamCreateFlagWatchRoot\n"); // kFSEventStreamCreateFlagFileEvents does not work for libraries with name // containing accent characters. struct FSEventStreamContext ctx = {0, monitor, NULL, NULL, NULL}; stream = FSEventStreamCreate(kCFAllocatorDefault, stream_callback, &ctx, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagWatchRoot ); CFRelease (mypaths[0]); CFRelease (pathsToWatch); if (!stream) { seaf_warning ("[wt] Failed to create event stream.\n"); return stream; } FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); FSEventStreamStart (stream); /* FSEventStreamShow (stream); */ seaf_debug ("[wt mon] Add repo %s watch success: %s.\n", repo_id, worktree); pthread_mutex_lock (&priv->hash_lock); g_hash_table_insert (priv->handle_hash, g_strdup(repo_id), (gpointer)(long)stream); info = create_repo_watch_info (repo_id, worktree); g_hash_table_insert (priv->info_hash, (gpointer)(long)stream, info); pthread_mutex_unlock (&priv->hash_lock); /* An empty path indicates repo-mgr to scan the whole worktree. */ add_event_to_queue (info->status, WT_EVENT_CREATE_OR_UPDATE, "", NULL); return stream; }
static FSEventStreamRef add_watch (SeafWTMonitor *monitor, const char* repo_id, const char* worktree) { SeafWTMonitorPriv *priv = monitor->priv; const char *path = worktree; RepoWatchInfo *info; double latency = 0.25; /* unit: second */ CFStringRef mypath = CFStringCreateWithCString (kCFAllocatorDefault, path, kCFStringEncodingUTF8); CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL); FSEventStreamRef stream; /* Create the stream, passing in a callback */ struct FSEventStreamContext ctx = {0, monitor, NULL, NULL, NULL}; stream = FSEventStreamCreate(kCFAllocatorDefault, stream_callback, &ctx, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagFileEvents /* deprecated OSX 10.6 support*/ ); CFRelease (mypath); CFRelease (pathsToWatch); if (!stream) { seaf_warning ("[wt] Failed to create event stream \n"); return stream; } FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); FSEventStreamStart (stream); #ifdef FSEVENT_DEBUG FSEventStreamShow (stream); seaf_debug ("[wt mon] Add repo %s watch success :%s.\n", repo_id, repo->worktree); #endif pthread_mutex_lock (&priv->hash_lock); g_hash_table_insert (priv->handle_hash, g_strdup(repo_id), (gpointer)(long)stream); info = create_repo_watch_info (repo_id, worktree); g_hash_table_insert (priv->info_hash, (gpointer)(long)stream, info); pthread_mutex_unlock (&priv->hash_lock); /* An empty path indicates repo-mgr to scan the whole worktree. */ add_event_to_queue (info->status, WT_EVENT_CREATE_OR_UPDATE, "", NULL); return stream; }
static void process_one_event (const char* eventPath, RepoWatchInfo *info, const char *worktree, const FSEventStreamEventId eventId, const FSEventStreamEventFlags eventFlags) { WTStatus *status = info->status; char *filename; const char *tmp; tmp = eventPath + strlen(worktree); if (*tmp == '/') tmp++; filename = g_strdup(tmp); /* Reinterpreted RENAMED as combine of CREATED or DELETED event */ if (eventFlags & kFSEventStreamEventFlagItemRenamed) { seaf_debug ("Rename Event Affected: %s \n", filename); struct stat buf; if (stat (eventPath, &buf)) { /* ret = -1, file is gone */ add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL); } else { /* ret = 0, file is here, but rename behaviour is unknown to us */ add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } } else if (eventFlags & kFSEventStreamEventFlagItemModified) { seaf_debug ("Modified %s.\n", filename); add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagItemCreated) { seaf_debug ("Created %s.\n", filename); /** * no need to rechecking recursively in FSEventStream * * these flags are useful if necessary: * kFSEventStreamEventFlagItemIsFile * kFSEventStreamEventFlagItemIsDir * kFSEventStreamEventFlagItemIsSymlink */ add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagItemRemoved) { seaf_debug ("Deleted %s.\n", filename); add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagItemXattrMod) { seaf_debug ("XattrMod %s.\n", filename); add_event_to_queue (status, WT_EVENT_ATTRIB, filename, NULL); } //TODO: kFSEventStreamEventFlagRootChanged and //kFSEventStreamCreateFlagWatchRoot g_free (filename); g_atomic_int_set (&info->status->last_changed, (gint)time(NULL)); }
/* Every time after a read event is processed, we should call * ReadDirectoryChangesW() on the dir handle asynchronously for the IOCP to * detect the change of the workthree. */ static BOOL start_watch_dir_change(SeafWTMonitorPriv *priv, HANDLE dir_handle) { if (!dir_handle) return FALSE; BOOL first_alloc = FALSE; DirWatchAux *aux = g_hash_table_lookup (priv->buf_hash, dir_handle); /* allocate aux buffer at the first watch, it would be freed if the repo is removed */ if (!aux) { first_alloc = TRUE; aux = g_new0(DirWatchAux, 1); init_overlapped(&aux->ol); } /* The ending W of this function indicates that the info recevied about the change would be in Unicode(specifically, the name of the file that is changed would be encoded in wide char). */ BOOL ret; DWORD code; RepoWatchInfo *info; retry: ret = ReadDirectoryChangesW (dir_handle, /* dir handle */ &aux->buf, /* buf to hold change info */ DIR_WATCH_BUFSIZE, /* buf size */ TRUE, /* watch subtree */ DIR_WATCH_MASK, /* notify filter */ NULL, /* bytes returned */ &aux->ol, /* pointer to overlapped */ NULL); /* completion routine */ if (!ret) { code = GetLastError(); seaf_warning("Failed to ReadDirectoryChangesW, " "error code %lu", code); if (first_alloc) /* if failed at the first watch, free the aux buffer */ g_free(aux); else if (code == ERROR_NOTIFY_ENUM_DIR) { /* If buffer overflowed after the last call, * add an overflow event and retry watch. */ info = g_hash_table_lookup (priv->info_hash, dir_handle); add_event_to_queue (info->status, WT_EVENT_OVERFLOW, NULL, NULL); goto retry; } } else { if (first_alloc) /* insert the aux buffer into hash table at the first watch */ g_hash_table_insert (priv->buf_hash, (gpointer)dir_handle, (gpointer)aux); } return ret; }
static void process_one_event (const char* eventPath, RepoWatchInfo *info, const char *worktree, const FSEventStreamEventId eventId, const FSEventStreamEventFlags eventFlags) { WTStatus *status = info->status; char *filename; char *event_path_nfc; const char *tmp; event_path_nfc = g_utf8_normalize (eventPath, -1, G_NORMALIZE_NFC); tmp = event_path_nfc + strlen(worktree); if (*tmp == '/') tmp++; filename = g_strdup(tmp); g_free (event_path_nfc); /* Reinterpreted RENAMED as combine of CREATED or DELETED event */ if (eventFlags & kFSEventStreamEventFlagItemRenamed) { seaf_debug ("Rename Event Affected: %s \n", filename); struct stat buf; if (stat (eventPath, &buf)) { /* ret = -1, file is gone */ add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL); } else { /* ret = 0, file is here, but rename behaviour is unknown to us */ add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } } else if (eventFlags & kFSEventStreamEventFlagItemModified) { seaf_debug ("Modified %s.\n", filename); add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagItemCreated) { seaf_debug ("Created %s.\n", filename); /** * no need to rechecking recursively in FSEventStream * * these flags are useful if necessary: * kFSEventStreamEventFlagItemIsFile * kFSEventStreamEventFlagItemIsDir * kFSEventStreamEventFlagItemIsSymlink */ add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagItemRemoved) { seaf_debug ("Deleted %s.\n", filename); add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagItemXattrMod) { seaf_debug ("XattrMod %s.\n", filename); add_event_to_queue (status, WT_EVENT_ATTRIB, filename, NULL); } else if (eventFlags & kFSEventStreamEventFlagRootChanged) { /* An empty path indicates repo-mgr to scan the whole worktree. */ seaf_debug ("RootChange event.\n"); add_event_to_queue (info->status, WT_EVENT_CREATE_OR_UPDATE, "", NULL); } else { seaf_debug ("Unhandled event with flags %x.\n", eventFlags); } g_free (filename); g_atomic_int_set (&info->status->last_changed, (gint)time(NULL)); }