1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/watch.c for the Openbox window manager
4 Copyright (c) 2010 Dana Jansens
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 See the COPYING file for a copy of the GNU General Public License.
19 #include "obt/watch.h"
20 #include "obt/watch_interface.h"
28 #ifdef HAVE_SYS_TYPES_H
29 # include <sys/types.h>
31 #ifdef HAVE_SYS_STAT_H
32 # include <sys/stat.h>
37 /* Interface for system-specific stuff (e.g. inotify). the functions are
38 defined in in watch_<system>.c
41 /* General system which uses the watch_sys_* stuff
46 GHashTable *targets_by_path;
50 struct _ObtWatchTarget {
55 gboolean watch_hidden;
57 gpointer watch_sys_data;
60 struct _ObtWatchFile {
64 /* If this is a directory, then the hash table contains pointers to other
65 ObtWatchFile objects that are contained in this one. */
66 GHashTable *children_by_name;
69 gpointer watch_sys_data;
72 static void target_free(ObtWatchTarget *t);
74 static ObtWatchFile* tree_create(ObtWatchTarget *t,
75 const gchar *path, const gchar *last,
76 ObtWatchFile *parent);
77 static void tree_destroy(ObtWatchFile *file);
79 static void notify(ObtWatchTarget *target, ObtWatchFile *file,
80 ObtWatchNotifyType type);
82 ObtWatch* obt_watch_new()
88 source = watch_sys_create_source();
90 w = g_slice_new(ObtWatch);
92 w->targets_by_path = g_hash_table_new_full(
93 g_str_hash, g_str_equal, NULL, (GDestroyNotify)target_free);
96 g_source_attach(source, NULL);
101 void obt_watch_ref(ObtWatch *w)
106 void obt_watch_unref(ObtWatch *w)
109 g_hash_table_destroy(w->targets_by_path);
110 g_source_destroy(w->source);
111 g_slice_free(ObtWatch, w);
115 static void target_free(ObtWatchTarget *t)
117 if (t->file) tree_destroy(t->file);
118 g_free(t->base_path);
119 g_slice_free(ObtWatchTarget, t);
122 /*! Scans through the filesystem under the target and reports each file/dir
123 that it finds to the watch subsystem.
124 @path Absolute path to the file to scan for.
125 @last The last component (filename) of the file to scan for.
126 @parent The parent directory (if it exists) of the file we are scanning for.
128 static ObtWatchFile* tree_create(ObtWatchTarget *target,
129 const gchar *path, const gchar *last,
130 ObtWatchFile *parent)
135 if (stat(path, &buf) < 0)
138 file = g_slice_new(ObtWatchFile);
139 file->name = g_strdup(last);
141 file->parent = parent;
142 file->children_by_name = NULL;
144 if (S_ISDIR(buf.st_mode))
145 file->children_by_name =
146 g_hash_table_new_full(g_str_hash, g_str_equal,
147 NULL, (GDestroyNotify)tree_destroy);
150 g_assert(target->file == NULL);
154 g_hash_table_replace(parent->children_by_name,
158 if (!S_ISDIR(buf.st_mode))
159 notify(target, file, OBT_WATCH_ADDED);
161 file->watch_sys_data =
162 watch_sys_add_file(target->w->source,
163 target, file, S_ISDIR(buf.st_mode));
165 /* recurse on the contents if it's a directory */
166 if (file && watch_main_file_is_dir(file)) {
169 dir = g_dir_open(path, 0, NULL);
173 while ((name = g_dir_read_name(dir))) {
174 if (name[0] != '.' || target->watch_hidden) {
178 subpath = g_build_filename(path, name, NULL);
179 child = tree_create(target, subpath, name, file);
188 file->modified = buf.st_mtime;
193 static void tree_destroy(ObtWatchFile *file)
196 if (file->children_by_name)
197 g_hash_table_unref(file->children_by_name);
198 g_slice_free(ObtWatchFile, file);
201 gboolean obt_watch_add(ObtWatch *w, const gchar *path, gboolean watch_hidden,
202 ObtWatchFunc func, gpointer data)
206 g_return_val_if_fail(w != NULL, FALSE);
207 g_return_val_if_fail(path != NULL, FALSE);
208 g_return_val_if_fail(func != NULL, FALSE);
209 g_return_val_if_fail(path[0] == G_DIR_SEPARATOR, FALSE);
211 t = g_slice_new(ObtWatchTarget);
213 t->base_path = g_strdup(path);
214 t->watch_hidden = watch_hidden;
218 g_hash_table_replace(w->targets_by_path, t->base_path, t);
220 watch_main_notify_add(t, NULL, NULL);
225 void obt_watch_remove(ObtWatch *w, const gchar *path)
227 g_return_if_fail(w != NULL);
228 g_return_if_fail(path != NULL);
229 g_return_if_fail(path[0] == G_DIR_SEPARATOR);
231 /* this also calls target_free which does notifies */
232 g_hash_table_remove(w->targets_by_path, path);
235 static ObtWatchFile* refresh_file(ObtWatchTarget *target, ObtWatchFile *file,
236 ObtWatchFile *parent, const gchar *path)
240 g_assert(file != NULL);
242 if (stat(path, &buf) < 0) {
243 watch_main_notify_remove(target, file);
247 else if (S_ISDIR(buf.st_mode) != watch_main_file_is_dir(file)) {
248 gchar *name = g_strdup(file->name);
250 watch_main_notify_remove(target, file);
251 watch_main_notify_add(target, parent, name);
256 /* recurse on the contents if it's a directory */
257 else if (watch_main_file_is_dir(file)) {
259 GList *children, *it;
261 children = watch_main_file_children(file);
262 for (it = children; it; it = g_list_next(it))
263 ((ObtWatchFile*)it->data)->seen = FALSE;
264 g_list_free(children);
266 dir = g_dir_open(path, 0, NULL);
270 while ((name = g_dir_read_name(dir))) {
271 if (name[0] != '.' || target->watch_hidden) {
272 ObtWatchFile *child = watch_main_file_child(file, name);
275 watch_main_notify_add(target, file, name);
278 ObtWatchFile *newchild;
280 subpath = g_build_filename(path, name, NULL);
281 newchild = refresh_file(target, child, file, subpath);
292 children = watch_main_file_children(file);
293 for (it = children; it; it = g_list_next(it))
294 if (((ObtWatchFile*)it->data)->seen == FALSE)
295 watch_main_notify_remove(target, it->data);
296 g_list_free(children);
299 /* check for modifications if it's a file */
301 if (file->modified >= 0 && buf.st_mtime > file->modified)
302 watch_main_notify_modify(target, file);
306 file->modified = buf.st_mtime;
311 static void foreach_refresh_target(gpointer k, gpointer v, gpointer u)
313 ObtWatchTarget *target = v;
316 /* we don't have any files being watched, try look for
317 the target's root again */
318 watch_main_notify_add(target, NULL, NULL);
320 refresh_file(target, target->file, NULL, target->base_path);
323 void obt_watch_refresh(ObtWatch *w)
325 g_return_if_fail(w != NULL);
327 g_hash_table_foreach(w->targets_by_path, foreach_refresh_target, NULL);
330 static void notify(ObtWatchTarget *target, ObtWatchFile *file,
331 ObtWatchNotifyType type)
333 gchar *sub_path = watch_main_file_sub_path(file);
334 target->func(target->w, target->base_path, sub_path, type,
339 void watch_main_notify_add(ObtWatchTarget *target,
340 ObtWatchFile *parent, const gchar *name)
344 if (parent && name[0] == '.' && !target->watch_hidden)
347 path = g_build_filename(target->base_path,
348 watch_main_file_sub_path(parent),
351 tree_create(target, path, name, parent);
355 static void foreach_child_notify_removed(gpointer k, gpointer v, gpointer u)
357 ObtWatchTarget *target = u;
358 ObtWatchFile *file = v;
360 if (watch_main_file_is_dir(file)) {
361 g_hash_table_foreach(file->children_by_name,
362 foreach_child_notify_removed,
364 g_hash_table_remove_all(file->children_by_name);
366 else if (!file->parent)
367 notify(target, file, OBT_WATCH_SELF_REMOVED);
369 notify(target, file, OBT_WATCH_REMOVED);
371 watch_sys_remove_file(target->w->source,
372 target, file, file->watch_sys_data);
375 void watch_main_notify_remove(ObtWatchTarget *target, ObtWatchFile *file)
377 foreach_child_notify_removed(NULL, file, target);
384 void watch_main_notify_modify(ObtWatchTarget *target, ObtWatchFile *file)
386 if (!watch_main_file_is_dir(file)) {
387 notify(target, file, OBT_WATCH_MODIFIED);
392 void build_sub_path(GString *str, ObtWatchFile *file)
395 build_sub_path(str, file->parent);
396 g_string_append(str, G_DIR_SEPARATOR_S);
397 g_string_append(str, file->name);
401 gboolean watch_main_target_watch_hidden(ObtWatchTarget *target)
403 return target->watch_hidden;
406 ObtWatchFile* watch_main_target_root(ObtWatchTarget *target)
411 gchar* watch_main_file_sub_path(ObtWatchFile *file)
417 str = g_string_new("");
418 build_sub_path(str, file);
420 g_string_free(str, FALSE);
426 gchar* watch_main_target_file_full_path(ObtWatchTarget *target,
433 str = g_string_new(target->base_path);
434 build_sub_path(str, file);
436 g_string_free(str, FALSE);
442 gboolean watch_main_file_is_dir(ObtWatchFile *file)
444 return file->children_by_name != NULL;
447 ObtWatchFile* watch_main_file_child(ObtWatchFile *file,
450 return g_hash_table_lookup(file->children_by_name, name);
453 GList* watch_main_file_children(ObtWatchFile *file)
455 return g_hash_table_get_values(file->children_by_name);