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