poll.h: cosmetix
[k8lowj.git] / src / checkfriends.c
blobd07e9f1ff7c7b025d89a80dee6ad541a93ada12d
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
3 */
4 #include "glib-all.h"
6 #include "liblj/checkfriends.h"
8 #include <stdio.h>
9 #include <stdlib.h> /* atoi */
10 #include <unistd.h> /* unlink */
11 #include <errno.h>
13 #include "account.h"
14 #include "checkfriends.h"
15 #include "conf.h"
16 #include "network.h"
17 #include "util.h"
20 enum {
21 ACCOUNT_CHANGED,
22 STATE_CHANGED,
23 LAST_SIGNAL
27 struct _CFMgr {
28 GObject parent;
29 JamAccountLJ *account; /* for whom the bell tolls^W^W^W to check */
31 gint server_interval; /* minimum poll interval dictated by server */
32 gint user_interval; /* poll interval requested by user */
33 guint32 mask; /* mask of friends groups to check for */
34 CFState state; /* activity status of this cf manager */
35 gint newcount; /* number of NEW hits; for thresholding */
36 gchar *lastupdate; /* opaque server-owned lastupdate tag */
37 gint errors; /* number of (network?) errors withstood */
39 #ifdef HAVE_GTK
40 guint timeout; /* handle on glib timeout for next check */
41 #endif
45 struct _CFMgrClass {
46 GObjectClass parent_class;
47 void (*account_changed) (CFMgr *cfm, JamAccount *acc);
48 void (*state_changed) (CFMgr *cfm, CFState state);
52 static guint signals[LAST_SIGNAL] = { 0 };
54 static const gint CF_MAXERRORS = 3;
55 static const gint CF_AUTOSCHED = -1;
56 static const gint CF_MAX_THRESHOLD = 7;
58 static int do_checkfriends(CFMgr *cfm, NetContext *ctx);
59 static void reschedule(CFMgr *cfm, gint interval);
61 static void cfmgr_init(CFMgr *cfm);
64 #if 0
65 void cfmgr_free (CFMgr *cfm) {
66 #ifdef HAVE_GTK
67 /* and destroy it */
68 if (cfm->timeout) gtk_timeout_remove(cfm->timeout); /* is this safe *enough*? */
69 /* FIXME: we chicken out on actually freeing the indicators.
70 * there's no actual cost now because they'd been passed to the new
71 * cfmgr with the inherit hack. They're not GOjects yet, anyway */
72 //g_slist_foreach(cfm->indicators, (GFunc)g_object_unref, NULL);
73 g_slist_free(cfm->indicators);
74 #endif /* HAVE_GTK */
75 g_free(cfm->lastupdate);
77 #endif
80 static void cfmgr_class_init (gpointer klass, gpointer class_data) {
81 signals[ACCOUNT_CHANGED] = g_signal_new("account_changed",
82 LOGJAM_TYPE_CFMGR,
83 G_SIGNAL_RUN_LAST,
84 G_STRUCT_OFFSET(CFMgrClass, account_changed),
85 NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
86 signals[STATE_CHANGED] = g_signal_new("state_changed",
87 LOGJAM_TYPE_CFMGR,
88 G_SIGNAL_RUN_LAST,
89 G_STRUCT_OFFSET(CFMgrClass, state_changed),
90 NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
94 void cfmgr_set_account (CFMgr *cfm, JamAccount *acc) {
95 cfmgr_set_state(cfm, CF_DISABLED);
96 cfmgr_init(cfm);
97 if (JAM_ACCOUNT_IS_LJ(acc)) {
98 cfm->account = JAM_ACCOUNT_LJ(acc);
99 cfmgr_set_mask(cfm, jam_account_lj_get_cfmask(JAM_ACCOUNT_LJ(acc)));
100 #ifdef HAVE_GTK
101 if (conf.options.cfautostart && jam_account_lj_get_checkfriends(acc)) cfmgr_set_state(cfm, CF_ON);
102 #endif /* HAVE_GTK */
103 } else {
104 cfm->account = NULL;
106 g_signal_emit_by_name(cfm, "account_changed", acc);
110 static gboolean cf_timeout_cb (CFMgr *mgr) {
111 if (do_checkfriends(mgr, network_ctx_silent) == 0) reschedule(mgr, CF_AUTOSCHED);
112 return FALSE;
116 /* install a timeout for the next checkfriends call according to the most recent interval information */
117 static void reschedule (CFMgr *cfm, gint interval) {
118 #ifdef HAVE_GTK
119 if (interval == CF_AUTOSCHED) interval = MAX(cfm->server_interval, cfm->user_interval)*1000;
120 cfm->timeout = g_timeout_add(interval, (GSourceFunc) cf_timeout_cb, (gpointer) cfm);
121 #endif
125 void cfmgr_set_state (CFMgr *cfm, CFState state) {
126 cfm->state = state;
127 switch (state) {
128 case CF_DISABLED:
129 #ifdef HAVE_GTK
130 if (cfm->timeout) gtk_timeout_remove(cfm->timeout);
131 cfm->timeout = 0;
132 #endif /* HAVE_GTK */
133 break;
134 case CF_ON:
135 /* every startup gets a clean slate */
136 cfm->errors = 0;
137 cfm->newcount = 0;
138 /* Start checking friends right away when we're turned on.
140 * We schedule an almost-immediate checkfriends instead
141 * of calling it directly to improve UI responsiveness */
142 reschedule(cfm, 0);
143 break;
144 case CF_NEW:
145 break;
147 g_signal_emit_by_name(cfm, "state_changed", cfm->state);
151 CFState cfmgr_get_state (CFMgr *cfm) {
152 return cfm->state;
156 JamAccountLJ *cfmgr_get_account (CFMgr *cfm) {
157 if (!cfm->account) return NULL;
158 return JAM_ACCOUNT_LJ(cfm->account);
162 void cfmgr_set_mask (CFMgr *cfm, guint32 mask) {
163 #ifdef HAVE_GTK
164 if (conf.options.cfusemask == FALSE) mask = 0;
165 #endif /* HAVE_GTK */
166 if (cfm->mask != mask) {
167 cfm->mask = mask; /* install new mask */
168 string_replace(&cfm->lastupdate, g_strdup(""));
169 /* invalidate monitor information */
174 static void cfmgr_init (CFMgr *cfm) {
175 cfm->account = NULL;
176 cfm->state = CF_DISABLED;
177 cfm->server_interval = 45; /* just a default quicky overridden */
178 cfm->user_interval = (conf.cfuserinterval ? conf.cfuserinterval : cfm->server_interval);
179 cfm->mask = 0; /* set by cfmgr_mask_set */
180 cfm->newcount = 0; /* approximate number of new friend entries */
181 cfm->errors = 0;
182 string_replace(&cfm->lastupdate, g_strdup(""));
183 #ifdef HAVE_GTK
184 cfm->timeout = 0; /* will be installed upon first "CF_ON" */
185 #endif /* HAVE_GTK */
189 CFMgr *cfmgr_new (JamAccount *acc) {
190 CFMgr *cfm = LOGJAM_CFMGR(g_object_new(cfmgr_get_type(), NULL));
191 cfmgr_set_account(cfm, acc);
192 return cfm;
196 GType cfmgr_get_type (void) {
197 static GType cfm_type = 0;
198 if (!cfm_type) {
199 const GTypeInfo cfm_info = {
200 sizeof(CFMgrClass),
201 NULL,
202 NULL,
203 (GClassInitFunc) cfmgr_class_init,
204 NULL,
205 NULL,
206 sizeof(CFMgr),
208 (GInstanceInitFunc) cfmgr_init,
210 cfm_type = g_type_register_static(G_TYPE_OBJECT, "CFMgr", &cfm_info, 0);
212 return cfm_type;
216 void cf_threshold_normalize (gint *threshold) {
217 if (*threshold < 1) *threshold = 1;
218 if (*threshold > CF_MAX_THRESHOLD) *threshold = CF_MAX_THRESHOLD;
222 /* returns -1 on error,
223 * 0 on success, but no new posts,
224 * and 1 on success with new posts.
226 static int do_checkfriends (CFMgr *cfm, NetContext *ctx) {
227 LJCheckFriends *cf = lj_checkfriends_new(jam_account_lj_get_user(cfm->account), cfm->lastupdate);
228 if (cfm->mask) lj_checkfriends_set_mask(cf, cfm->mask);
229 if (!net_run_verb_ctx((LJVerb *)cf, ctx, NULL)) {
230 /* if the request fails, we only stop polling after several attempts */
231 lj_checkfriends_free(cf);
232 ++cfm->errors;
233 if (cfm->errors > CF_MAXERRORS) {
234 #if 0
235 /* the transient parent window of this notification is NULL, but
236 * it's worth thinking about a better general user notification
237 * strategy, that finds the current screen, etc. */
238 jam_message(NULL, JAM_MSG_WARNING, TRUE, NULL, _("Too many network errors; checking friends disabled."));
239 #endif
240 cfmgr_set_state(cfm, CF_DISABLED);
241 return -1;
243 return 0;
245 /* we succeeded. reset the error count. */
246 cfm->errors = 0;
247 string_replace(&cfm->lastupdate, g_strdup(cf->lastupdate));
248 cfm->server_interval = cf->interval;
249 lj_checkfriends_free(cf);
250 if (cf->newposts == 0) return 0;
251 ++cfm->newcount;
252 if (cfm->newcount >= conf.cfthreshold) cfmgr_set_state(cfm, CF_NEW);
253 return 1;
257 static void cf_cli_make_path (JamAccountLJ *acc, char *path) {
258 gchar file[1024];
259 char *id = jam_account_id_strdup(JAM_ACCOUNT(acc));
260 g_snprintf(file, 1024, "checkfriends-%s", id);
261 g_free(id);
262 conf_make_path(file, path, sizeof(file));
266 void checkfriends_cli_purge (JamAccountLJ *acc) {
267 gchar path[1024];
268 cf_cli_make_path(acc, path);
269 if (g_file_test(path, G_FILE_TEST_EXISTS)) {
270 if (!unlink(path)) return;
271 g_printerr(_("Can't unlink %s: %s."), path, g_strerror(errno));
276 static time_t cf_cli_conf_read (CFMgr *cfm, JamAccountLJ *acc) {
277 time_t lasttry = 0;
278 gchar *cfconfdata;
279 gchar **parseddata;
280 gchar path[1024];
281 cf_cli_make_path(acc, path);
282 if (g_file_get_contents(path, &cfconfdata, NULL, NULL)) {
283 parseddata = g_strsplit(cfconfdata, "|", 5);
284 cfm->lastupdate = parseddata[0];
285 cfm->server_interval = atoi(parseddata[1]);
286 g_free(parseddata[1]);
287 cfm->errors = atoi(parseddata[2]);
288 g_free(parseddata[2]);
289 cfm->state = atoi(parseddata[3]);
290 g_free(parseddata[3]);
291 lasttry = atoi(parseddata[4]);
292 g_free(parseddata[4]);
293 g_free(cfconfdata);
294 } else {
295 cfm->state = CF_ON;
297 return lasttry;
301 static gboolean cf_cli_conf_write (CFMgr *cfm, JamAccountLJ *acc) {
302 FILE *f;
303 gchar path[1024];
304 cf_cli_make_path(acc, path);
305 f = fopen(path, "w");
306 if (!f) {
307 g_printerr(_("Error opening %s for write: %s."), path, g_strerror(errno));
308 return FALSE;
310 fprintf(f, "%s|%d|%d|%d|%ld", (cfm->lastupdate ? cfm->lastupdate : ""), cfm->server_interval, cfm->errors, cfm->state, time(NULL));
311 fclose(f);
312 return TRUE;
316 /* gui-less version of checkfriends.
318 * returns TRUE when new friends entries have been detected
319 * FALSE when no such entries exist (or when something has prevented
320 * the check)
322 * keeps track of its own persistent information with cf_cli_conf_*(). */
323 gboolean checkfriends_cli (JamAccountLJ *acc) {
324 CFMgr *cfm = cfmgr_new(JAM_ACCOUNT(acc));
325 time_t now = time(NULL);
326 time_t then = cf_cli_conf_read(cfm, acc);
327 /* don't even approach the server in some cases.
328 * report the reason to the user unless we're in quiet mode */
329 if (cfm->errors > CF_MAXERRORS) {
330 if (!app.quiet) {
331 g_printerr(_("Maximum error count reached in contacting server.\n"
332 "Run \"%s --checkfriends=purge\" and try again.\n"), app.programname);
334 return FALSE;
336 if (now - then < cfm->server_interval) {
337 if (!app.quiet) g_printerr("%s", _("Request rate exceeded. Slow down.\n"));
338 return FALSE;
340 if (cfm->state == CF_NEW) {
341 if (!app.quiet) g_printerr(_("Read your friends page, then run\n" "\"%s checkfriends purge\".\n"), app.programname);
342 return TRUE;
344 do_checkfriends(cfm, network_ctx_cmdline);
345 cf_cli_conf_write(cfm, acc);
346 return (cfm->state == CF_NEW);