1 /*
2 ** 2002-07-22 - Finally, gentoo has learned to use FAM (File Alteration Monitor) if available. FAM,
3 ** from SGI, provides an application with the ability to be notified when a file or
4 ** directory changes. We use this to trigger pane reloads, so gentoo knows what's going
5 ** on in the filesystem.
6 **
7 ** FAM support is entierly optional: if you don't want it, or you system doesn't have it,
8 ** it will be built as stubs that do nothing. Use --disable-fam to manually turn it off.
9 **
10 ** Btw, this module is named "gfam" because plain "fam.h" collided with <fam.h>. Weird.
11 ** 2009-11-10 - Totally rewritten (and I do mean *totally*), now simply wraps GIO's monitoring. For
12 ** the usual dried up grapes' sake, I left the name of the module alone. Later ...
13 */
14
15 #include "gentoo.h"
16
17 #include "dialog.h"
18 #include "dirpane.h"
19 #include "miscutil.h"
20
21 #include "gfam.h"
22
23 /* ----------------------------------------------------------------------------------------- */
24
25 static const guint RESCAN_INTERVAL = 50; /* Milliseconds. */
26
27 static struct {
28 struct {
29 DirPane *dp;
30 GFileMonitor *monitor;
31 gulong sig_changed;
32 } pane[2];
33 gulong block_count;
34 guint rescan_mask;
35 guint timeout;
36 } monitor_info;
37
38 /* ----------------------------------------------------------------------------------------- */
39
fam_initialize(MainInfo * min)40 gboolean fam_initialize(MainInfo *min)
41 {
42 guint i;
43
44 for(i = 0; i < sizeof monitor_info.pane / sizeof *monitor_info.pane; i++)
45 {
46 monitor_info.pane[i].dp = &min->gui->pane[i];
47 monitor_info.pane[i].monitor = NULL;
48 monitor_info.pane[i].sig_changed = 0;
49 }
50 monitor_info.block_count = 0;
51 monitor_info.rescan_mask = 0;
52 monitor_info.timeout = 0;
53
54 return TRUE;
55 }
56
fam_is_active(void)57 gboolean fam_is_active(void)
58 {
59 return TRUE;
60 }
61
evt_timeout(gpointer data)62 static gboolean evt_timeout(gpointer data)
63 {
64 guint i, mask;
65
66 if(monitor_info.block_count > 0)
67 goto remove_and_exit; /* It's not often, but here's one! */
68 mask = monitor_info.rescan_mask;
69 for(i = 0; i < sizeof monitor_info.pane / sizeof *monitor_info.pane; i++)
70 {
71 if(mask & (1U << i))
72 {
73 GTimer *timer;
74
75 timer = g_timer_new();
76 dp_rescan(monitor_info.pane[i].dp);
77 if(timer != NULL)
78 {
79 gint limit;
80
81 g_timer_stop(timer);
82 limit = g_timer_elapsed(timer, NULL);
83 g_timer_destroy(timer);
84 if(limit < 50)
85 limit = 50;
86 else if(limit > 10000)
87 limit = 10000;
88 g_file_monitor_set_rate_limit(monitor_info.pane[i].monitor, limit);
89 }
90 }
91 }
92 monitor_info.rescan_mask = 0;
93
94 remove_and_exit:
95 /* The timeout is always a one-shot; clear handle and ask glib to delete. */
96 monitor_info.timeout = 0;
97 return FALSE;
98 }
99
queue_rescan(guint pane)100 static void queue_rescan(guint pane)
101 {
102 monitor_info.rescan_mask |= (1U << pane);
103 if(monitor_info.block_count == 0)
104 {
105 if(monitor_info.timeout == 0)
106 monitor_info.timeout = g_timeout_add(RESCAN_INTERVAL, evt_timeout, NULL);
107 }
108 }
109
evt_monitor_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event,gpointer user)110 static void evt_monitor_changed(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event, gpointer user)
111 {
112 DirPane *dp = user;
113
114 if(event == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT || event == G_FILE_MONITOR_EVENT_CREATED || event == G_FILE_MONITOR_EVENT_DELETED)
115 queue_rescan(dp->index);
116 }
117
fam_monitor(const DirPane * dp)118 gboolean fam_monitor(const DirPane *dp)
119 {
120 if(dp == NULL)
121 return FALSE;
122 if(dp->index >= sizeof monitor_info.pane / sizeof *monitor_info.pane)
123 {
124 g_error("Too many panes, can't monitor pane with index %u", dp->index);
125 return FALSE;
126 }
127 if(monitor_info.pane[dp->index].monitor != NULL)
128 {
129 g_object_unref(monitor_info.pane[dp->index].monitor);
130 monitor_info.pane[dp->index].sig_changed = 0;
131 }
132 if(dp->dir.root == NULL)
133 {
134 g_error("No root GFile, failing to monitor pane with index %u\n", dp->index);
135 return FALSE;
136 }
137 if((monitor_info.pane[dp->index].monitor = g_file_monitor(dp->dir.root, G_FILE_MONITOR_WATCH_MOUNTS, NULL, NULL)) != NULL)
138 monitor_info.pane[dp->index].sig_changed = g_signal_connect(G_OBJECT(monitor_info.pane[dp->index].monitor), "changed", G_CALLBACK(evt_monitor_changed), (gpointer) dp);
139 else
140 g_warning("GIO failed to attach monitor to '%s'", dp->dir.path);
141 return monitor_info.pane[dp->index].monitor != NULL;
142 }
143
fam_is_monitored(const DirPane * dp)144 gboolean fam_is_monitored(const DirPane *dp)
145 {
146 if(dp == NULL)
147 return FALSE;
148 if(dp->index >= sizeof monitor_info.pane / sizeof *monitor_info.pane)
149 return FALSE;
150 return monitor_info.pane[dp->index].monitor != NULL;
151 }
152
fam_rescan_block(void)153 void fam_rescan_block(void)
154 {
155 monitor_info.block_count++;
156 }
157
fam_rescan_unblock(void)158 void fam_rescan_unblock(void)
159 {
160 guint i;
161
162 if(--monitor_info.block_count == 0)
163 {
164 for(i = 0; i < sizeof monitor_info.pane / sizeof *monitor_info.pane; i++)
165 {
166 if(monitor_info.rescan_mask & (1U << i))
167 {
168 queue_rescan(i);
169 monitor_info.rescan_mask &= ~(1U << i);
170 }
171 }
172 }
173 }
174
fam_shutdown(MainInfo * min)175 void fam_shutdown(MainInfo *min)
176 {
177 guint i;
178
179 if(monitor_info.timeout != 0)
180 g_source_remove(monitor_info.timeout);
181
182 for(i = 0; i < sizeof monitor_info.pane / sizeof *monitor_info.pane; i++)
183 {
184 if(monitor_info.pane[i].monitor != NULL && monitor_info.pane[i].sig_changed != 0)
185 {
186 g_object_unref(monitor_info.pane[i].monitor);
187 monitor_info.pane[i].sig_changed = 0;
188 }
189 }
190 }
191