1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
|
/*
** 2002-07-22 - Finally, gentoo has learned to use FAM (File Alteration Monitor) if available. FAM,
** from SGI, provides an application with the ability to be notified when a file or
** directory changes. We use this to trigger pane reloads, so gentoo knows what's going
** on in the filesystem.
**
** FAM support is entierly optional: if you don't want it, or you system doesn't have it,
** it will be built as stubs that do nothing. Use --disable-fam to manually turn it off.
**
** Btw, this module is named "gfam" because plain "fam.h" collided with <fam.h>. Weird.
*/
#include "gentoo.h"
#if defined HAVE_FAM
#include <fam.h>
#endif
#include "dialog.h"
#include "dirpane.h"
#include "gfam.h"
/* If we don't have FAM, or it has been disabled, simply define the necessary functions
** as do-nothing stubs. Since the interface is (still?) pretty simple, it's easier and
** cleaner to do it in this all-at-once way, rather than scattering #if defined HAVE_FAMs
** all over the place. Perhaps the compiler can even optimize these stubs out?
*/
#if !defined HAVE_FAM
gboolean fam_initialize(MainInfo *min)
{
return TRUE;
}
gboolean fam_is_active(void)
{
return FALSE;
}
void fam_monitor(const DirPane *dp)
{
}
void fam_rescan_block(void)
{
}
void fam_rescan_unblock(void)
{
}
void fam_shutdown(MainInfo *min)
{
}
#else /* If HAVE_FAM is actually defined, provide functional functions. */
/* ----------------------------------------------------------------------------------------- */
#define MAGIC_OFFSET 16
static struct {
gboolean ok;
MainInfo *min;
FAMConnection conn;
FAMRequest req[2];
guint block_cnt; /* If set, don't rescan. */
guint block_rescan; /* Used to track what needs to be reloaded on unblock(). */
guint timeout[2]; /* Used to notify of pending refresh after rate limiting. */
} the_faminfo = { FALSE };
/* ----------------------------------------------------------------------------------------- */
/* 2003-09-30 - Timeout handler to catch rate limited refreshes. Set when a refresh is denied
** by the rate limiter, to do a final refresh since one is known to be needed.
*/
static gboolean evt_fam_timeout(gpointer data)
{
guint index = GPOINTER_TO_UINT(data);
if(the_faminfo.block_cnt)
the_faminfo.block_rescan |= (1 << index);
else
dp_rescan(&the_faminfo.min->gui->pane[index]);
the_faminfo.timeout[index] = 0U;
return FALSE;
}
/* 2003-09-30 - Rate limit pane rescans. Returns TRUE if pane <index> can do a rescan, FALSE
** if it cannot.
*/
static gboolean rescan_rate_limit(guint index)
{
const gfloat LIMIT = 0.300f; /* Lower limit on period between refreshes. */
static GTimeVal stamp[2] = { { 0 }, { 0 } };
static gfloat elapsed[2] = { 0.0f, 0.0f}; /* Time since last rescan allowed. */
GTimeVal now;
gboolean ret = TRUE;
g_get_current_time(&now);
if(stamp[index].tv_sec && stamp[index].tv_usec)
{
gfloat dt = 1E-6f * (now.tv_usec - stamp[index].tv_usec) + (now.tv_sec - stamp[index].tv_sec);
if(dt < LIMIT)
{
elapsed[index] += dt;
if(elapsed[index] < LIMIT) /* When blocking has reached limit, let a request through. */
{
if(the_faminfo.timeout[index])
gtk_timeout_remove(the_faminfo.timeout[index]);
the_faminfo.timeout[index] = gtk_timeout_add(1000.0f * LIMIT, evt_fam_timeout, GUINT_TO_POINTER(index));
ret = FALSE;
}
}
}
stamp[index] = now;
if(ret) /* If we're allowing refresh, cancel any outstanding timer and reset bookkeeping. */
{
elapsed[index] = 0.0f;
if(the_faminfo.timeout[index])
{
gtk_timeout_remove(the_faminfo.timeout[index]);
the_faminfo.timeout[index] = 0U;
}
}
return ret;
}
/* 2002-07-22 - Rescan all panes whose index bit is set in <rescan>. Overkill. */
static void rescan_issue(guint rescan)
{
guint i, f = 0;
for(i = 0; i < sizeof the_faminfo.req / sizeof *the_faminfo.req; i++)
{
if(rescan & (1 << i) && rescan_rate_limit(i))
{
dp_rescan(&the_faminfo.min->gui->pane[i]);
f |= the_faminfo.min->gui->cur_pane == &the_faminfo.min->gui->pane[i];
}
}
if(f)
dp_show_stats(the_faminfo.min->gui->cur_pane);
}
/* 2002-07-22 - GTK+ calls this whenever a FAM event is reported. We filter out the interesting
** ones, and issue rescans on the affected panes, either now or buffered for later.
*/
static void evt_fam_monitor(gpointer data, gint source, GdkInputCondition cond)
{
FAMEvent evt;
guint rescan = 0U;
while(FAMPending(&the_faminfo.conn))
{
if(FAMNextEvent(&the_faminfo.conn, &evt) == 1) /* Returns -1 on error, annoyingly enough. */
{
if(evt.code < FAMAcknowledge) /* Filter out events that wouldn't show up anyway. */
rescan |= 1 << (evt.fr.reqnum - MAGIC_OFFSET);
}
}
if(the_faminfo.block_cnt)
the_faminfo.block_rescan |= rescan; /* Non-destructive, important. */
else if(rescan)
rescan_issue(rescan);
}
/* 2002-07-22 - Initialize FAM subsystem, by opening a connection and hooking up with GTK+'s event loop. */
gboolean fam_initialize(MainInfo *min)
{
the_faminfo.min = min;
if(FAMOpen2(&the_faminfo.conn, PACKAGE) == 0)
{
guint i;
for(i = 0; i < sizeof the_faminfo.req / sizeof *the_faminfo.req; i++)
the_faminfo.req[i].reqnum = 0;
/* Hook us into the main GTK+ event detection system. Simple. */
gtk_input_add_full(FAMCONNECTION_GETFD(&the_faminfo.conn), GDK_INPUT_READ, evt_fam_monitor, NULL, NULL, NULL);
the_faminfo.ok = TRUE;
the_faminfo.block_cnt = 0;
}
else
g_warning(_("FAM open failed, error %d--FAM will not be used"), FAMErrno);
return the_faminfo.ok;
}
/* 2002-07-22 - Just tell a caller if FAM is indeed in use. Says "FALSE" if built without FAM. */
gboolean fam_is_active(void)
{
return the_faminfo.ok;
}
/* 2002-07-22 - Start monitoring the directory of <dp>. Can't take path only, need to key it to pane index for rescan. */
void fam_monitor(const DirPane *dp)
{
if(!the_faminfo.ok)
return;
if(the_faminfo.req[dp->index].reqnum > 0)
FAMCancelMonitor(&the_faminfo.conn, &the_faminfo.req[dp->index]);
the_faminfo.req[dp->index].reqnum = MAGIC_OFFSET + dp->index;
if(FAMMonitorDirectory2(&the_faminfo.conn, dp->dir.path, &the_faminfo.req[dp->index]) != 0)
{
the_faminfo.req[dp->index].reqnum = 0;
g_warning(_("Couldn't add FAM monitor on \"%s\", error %s (restart with --no-fam to go around, perhaps)"), dp->dir.path, FamErrlist[FAMErrno]);
}
}
/* 2002-07-22 - Caller is about to start doing stuff for which no rescans are allowed until unblock() is called.
** This prevents dp_rescan() from reallocating the core buffers, and thus rendering the caller's
** dp_get_selection() list to change meaning, which can be *very* devastating.
*/
void fam_rescan_block(void)
{
if(the_faminfo.block_cnt == 0)
the_faminfo.block_rescan = 0U;
the_faminfo.block_cnt++;
}
/* 2002-07-22 - Unblock rescans. If any FAM events occured during block, issue the rescans now. */
void fam_rescan_unblock(void)
{
if(the_faminfo.block_cnt)
{
the_faminfo.block_cnt--;
if(the_faminfo.block_cnt == 0U)
rescan_issue(the_faminfo.block_rescan);
}
else
g_warning("Non-balanced fam_rescan_unblock() call detected; underflow");
}
/* 2002-07-22 - Shut down FAM system, by closing the connection. */
void fam_shutdown(MainInfo *min)
{
if(the_faminfo.ok)
FAMClose(&the_faminfo.conn);
}
#endif /* HAVE_FAM */
|