gtk-all.h, util-gtk.h: cosmetix (ignore this headers if built without GTK)
[k8lowj.git] / src / checkfriends.c
blob385c0d54f85f57fa711d688412827dcaa291d2fd
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
5 */
7 #include "glib-all.h"
9 #include "liblj/checkfriends.h"
11 #include <stdio.h>
12 #include <stdlib.h> /* atoi */
13 #ifndef G_OS_WIN32
14 #include <unistd.h> /* unlink */
15 #else
16 #include <io.h>
17 #endif
18 #include <errno.h>
20 #include "checkfriends.h"
21 #include "account.h"
22 #include "conf.h"
23 #include "network.h"
24 #include "util.h"
26 enum {
27 ACCOUNT_CHANGED,
28 STATE_CHANGED,
29 LAST_SIGNAL
32 struct _CFMgr {
33 GObject parent;
34 JamAccountLJ *account; /* for whom the bell tolls^W^W^W to check */
36 gint server_interval; /* minimum poll interval dictated by server */
37 gint user_interval; /* poll interval requested by user */
38 guint32 mask; /* mask of friends groups to check for */
39 CFState state; /* activity status of this cf manager */
40 gint newcount; /* number of NEW hits; for thresholding */
41 gchar *lastupdate; /* opaque server-owned lastupdate tag */
42 gint errors; /* number of (network?) errors withstood */
44 #ifdef HAVE_GTK
45 guint timeout; /* handle on glib timeout for next check */
46 #endif
49 struct _CFMgrClass {
50 GObjectClass parent_class;
51 void (*account_changed)(CFMgr *cfm, JamAccount *acc);
52 void (*state_changed) (CFMgr *cfm, CFState state);
55 static guint signals[LAST_SIGNAL] = { 0 };
57 static const gint CF_MAXERRORS = 3;
58 static const gint CF_AUTOSCHED = -1;
59 static const gint CF_MAX_THRESHOLD = 7;
61 static int do_checkfriends(CFMgr *cfm, NetContext *ctx);
62 static void reschedule(CFMgr *cfm, gint interval);
64 static void cfmgr_init(CFMgr *cfm);
65 #if 0
66 void
67 cfmgr_free(CFMgr *cfm) {
68 #ifdef HAVE_GTK
69 /* and destroy it */
70 if (cfm->timeout)
71 gtk_timeout_remove(cfm->timeout); /* is this safe *enough*? */
73 /* FIXME: we chicken out on actually freeing the indicators.
74 * there's no actual cost now because they'd been passed to the new
75 * cfmgr with the inherit hack. They're not GOjects yet, anyway */
76 //g_slist_foreach(cfm->indicators, (GFunc)g_object_unref, NULL);
77 g_slist_free(cfm->indicators);
78 #endif /* HAVE_GTK */
80 g_free(cfm->lastupdate);
82 #endif
84 static void
85 cfmgr_class_init(gpointer klass, gpointer class_data) {
86 signals[ACCOUNT_CHANGED] = g_signal_new("account_changed",
87 LOGJAM_TYPE_CFMGR,
88 G_SIGNAL_RUN_LAST,
89 G_STRUCT_OFFSET(CFMgrClass, account_changed),
90 NULL, NULL,
91 g_cclosure_marshal_VOID__POINTER,
92 G_TYPE_NONE, 1, G_TYPE_POINTER);
94 signals[STATE_CHANGED] = g_signal_new("state_changed",
95 LOGJAM_TYPE_CFMGR,
96 G_SIGNAL_RUN_LAST,
97 G_STRUCT_OFFSET(CFMgrClass, state_changed),
98 NULL, NULL,
99 g_cclosure_marshal_VOID__INT,
100 G_TYPE_NONE, 1, G_TYPE_INT);
103 void
104 cfmgr_set_account(CFMgr *cfm, JamAccount *acc) {
105 cfmgr_set_state(cfm, CF_DISABLED);
106 cfmgr_init(cfm);
108 if (JAM_ACCOUNT_IS_LJ(acc)) {
109 cfm->account = JAM_ACCOUNT_LJ(acc);
110 cfmgr_set_mask(cfm, jam_account_lj_get_cfmask(JAM_ACCOUNT_LJ(acc)));
111 #ifdef HAVE_GTK
112 if (conf.options.cfautostart && jam_account_lj_get_checkfriends(acc))
113 cfmgr_set_state(cfm, CF_ON);
114 #endif /* HAVE_GTK */
115 } else {
116 cfm->account = NULL;
118 g_signal_emit_by_name(cfm, "account_changed", acc);
121 static gboolean
122 cf_timeout_cb(CFMgr *mgr) {
123 if (do_checkfriends(mgr, network_ctx_silent) == 0)
124 reschedule(mgr, CF_AUTOSCHED);
125 return FALSE;
128 /* install a timeout for the next checkfriends call according to the
129 * most recent interval information */
130 static void
131 reschedule(CFMgr *cfm, gint interval) {
132 #ifdef HAVE_GTK
133 if (interval == CF_AUTOSCHED)
134 interval = MAX(cfm->server_interval, cfm->user_interval) * 1000;
136 cfm->timeout = g_timeout_add(
137 interval,
138 (GSourceFunc)cf_timeout_cb,
139 (gpointer)cfm);
140 #endif
143 void
144 cfmgr_set_state(CFMgr *cfm, CFState state) {
145 cfm->state = state;
147 switch (state) {
148 case CF_DISABLED:
149 #ifdef HAVE_GTK
150 if (cfm->timeout)
151 gtk_timeout_remove(cfm->timeout);
152 cfm->timeout = 0;
153 #endif /* HAVE_GTK */
154 break;
156 case CF_ON:
157 /* every startup gets a clean slate */
158 cfm->errors = 0;
159 cfm->newcount = 0;
161 /* Start checking friends right away when we're turned on.
163 * We schedule an almost-immediate checkfriends instead
164 * of calling it directly to improve UI responsiveness */
165 reschedule(cfm, 0);
166 break;
168 case CF_NEW:
169 break;
171 g_signal_emit_by_name(cfm, "state_changed", cfm->state);
174 CFState
175 cfmgr_get_state(CFMgr *cfm) {
176 return cfm->state;
179 JamAccountLJ*
180 cfmgr_get_account(CFMgr *cfm) {
181 if (!cfm->account) return NULL;
182 return JAM_ACCOUNT_LJ(cfm->account);
185 void
186 cfmgr_set_mask(CFMgr *cfm, guint32 mask) {
187 #ifdef HAVE_GTK
188 if (conf.options.cfusemask == FALSE)
189 mask = 0;
190 #endif /* HAVE_GTK */
191 if (cfm->mask != mask) {
192 cfm->mask = mask; /* install new mask */
193 string_replace(&cfm->lastupdate, g_strdup(""));
194 /* invalidate monitor information */
198 static void
199 cfmgr_init(CFMgr *cfm) {
200 cfm->account = NULL;
201 cfm->state = CF_DISABLED;
202 cfm->server_interval = 45; /* just a default quicky overridden */
203 cfm->user_interval = conf.cfuserinterval ? conf.cfuserinterval :
204 cfm->server_interval;
205 cfm->mask = 0; /* set by cfmgr_mask_set */
206 cfm->newcount = 0; /* approximate number of new friend entries */
207 cfm->errors = 0;
209 string_replace(&cfm->lastupdate, g_strdup(""));
211 #ifdef HAVE_GTK
212 cfm->timeout = 0; /* will be installed upon first "CF_ON" */
213 #endif /* HAVE_GTK */
216 CFMgr*
217 cfmgr_new(JamAccount *acc) {
218 CFMgr *cfm = LOGJAM_CFMGR(g_object_new(cfmgr_get_type(), NULL));
219 cfmgr_set_account(cfm, acc);
220 return cfm;
223 GType
224 cfmgr_get_type(void) {
225 static GType cfm_type = 0;
226 if (!cfm_type) {
227 const GTypeInfo cfm_info = {
228 sizeof (CFMgrClass),
229 NULL,
230 NULL,
231 (GClassInitFunc) cfmgr_class_init,
232 NULL,
233 NULL,
234 sizeof (CFMgr),
236 (GInstanceInitFunc) cfmgr_init,
238 cfm_type = g_type_register_static(G_TYPE_OBJECT,
239 "CFMgr", &cfm_info, 0);
241 return cfm_type;
245 void
246 cf_threshold_normalize(gint *threshold) {
247 if (*threshold < 1)
248 *threshold = 1;
249 if (*threshold > CF_MAX_THRESHOLD)
250 *threshold = CF_MAX_THRESHOLD;
254 /* returns -1 on error,
255 * 0 on success, but no new posts,
256 * and 1 on success with new posts.
258 static int
259 do_checkfriends(CFMgr *cfm, NetContext *ctx) {
260 LJCheckFriends *cf;
262 cf = lj_checkfriends_new(jam_account_lj_get_user(cfm->account),
263 cfm->lastupdate);
265 if (cfm->mask)
266 lj_checkfriends_set_mask(cf, cfm->mask);
268 if (!net_run_verb_ctx((LJVerb*)cf, ctx, NULL)) {
269 /* if the request fails, we only stop polling after several attempts */
270 lj_checkfriends_free(cf);
272 cfm->errors++;
273 if (cfm->errors > CF_MAXERRORS) {
274 #if 0
275 /* the transient parent window of this notification is NULL, but
276 * it's worth thinking about a better general user notification
277 * strategy, that finds the current screen, etc. */
278 jam_message(NULL, JAM_MSG_WARNING, TRUE, NULL,
279 _("Too many network errors; checking friends disabled."));
280 #endif
281 cfmgr_set_state(cfm, CF_DISABLED);
282 return -1;
284 return 0;
287 /* we succeeded. reset the error count. */
288 cfm->errors = 0;
290 string_replace(&cfm->lastupdate, g_strdup(cf->lastupdate));
291 cfm->server_interval = cf->interval;
293 lj_checkfriends_free(cf);
295 if (cf->newposts == 0)
296 return 0;
298 cfm->newcount++;
299 if (cfm->newcount >= conf.cfthreshold)
300 cfmgr_set_state(cfm, CF_NEW);
301 return 1;
305 static void
306 cf_cli_make_path(JamAccountLJ *acc, char *path) {
307 gchar file[1024];
308 char *id = jam_account_id_strdup(JAM_ACCOUNT(acc));
309 g_snprintf(file, 1024, "checkfriends-%s", id);
310 g_free(id);
311 conf_make_path(file, path, 1024);
314 void
315 checkfriends_cli_purge(JamAccountLJ *acc) {
316 gchar path[1024];
318 cf_cli_make_path(acc, path);
320 if (g_file_test(path, G_FILE_TEST_EXISTS)) {
321 if (!unlink(path)) return;
323 g_printerr(_("Can't unlink %s: %s."), path, g_strerror(errno));
327 static time_t
328 cf_cli_conf_read(CFMgr *cfm, JamAccountLJ *acc) {
329 time_t lasttry = 0;
330 gchar *cfconfdata;
331 gchar **parseddata;
332 gchar path[1024];
334 cf_cli_make_path(acc, path);
336 if (g_file_get_contents(path, &cfconfdata, NULL, NULL)) {
337 parseddata = g_strsplit(cfconfdata, "|", 5);
338 cfm->lastupdate = parseddata[0];
339 cfm->server_interval = atoi(parseddata[1]); g_free(parseddata[1]);
340 cfm->errors = atoi(parseddata[2]); g_free(parseddata[2]);
341 cfm->state = atoi(parseddata[3]); g_free(parseddata[3]);
342 lasttry = atoi(parseddata[4]); g_free(parseddata[4]);
343 g_free(cfconfdata);
344 } else {
345 cfm->state = CF_ON;
347 return lasttry;
350 static gboolean
351 cf_cli_conf_write(CFMgr *cfm, JamAccountLJ *acc) {
352 FILE *f;
353 gchar path[1024];
355 cf_cli_make_path(acc, path);
357 f = fopen(path, "w");
358 if (!f) {
359 g_printerr(_("Error opening %s for write: %s."),
360 path, g_strerror(errno));
361 return FALSE;
364 fprintf(f, "%s|%d|%d|%d|%ld",
365 cfm->lastupdate ? cfm->lastupdate : "",
366 cfm->server_interval,
367 cfm->errors,
368 cfm->state,
369 time(NULL));
370 fclose(f);
371 return TRUE;
374 /* gui-less version of checkfriends.
376 * returns TRUE when new friends entries have been detected
377 * FALSE when no such entries exist (or when something has prevented
378 * the check)
380 * keeps track of its own persistent information with cf_cli_conf_*(). */
381 gboolean
382 checkfriends_cli(JamAccountLJ *acc) {
383 CFMgr *cfm = cfmgr_new(JAM_ACCOUNT(acc));
384 time_t now = time(NULL);
385 time_t then = cf_cli_conf_read(cfm, acc);
387 /* don't even approach the server in some cases.
388 * report the reason to the user unless we're in quiet mode */
390 if (cfm->errors > CF_MAXERRORS) {
391 if (!app.quiet)
392 g_printerr(_("Maximum error count reached in contacting server.\n"
393 "Run \"%s --checkfriends=purge\" and try again.\n"),
394 app.programname);
395 return FALSE;
397 if (now - then < cfm->server_interval) {
398 if (!app.quiet)
399 g_printerr(_("Request rate exceeded. Slow down.\n"));
400 return FALSE;
402 if (cfm->state == CF_NEW) {
403 if (!app.quiet)
404 g_printerr(_("Read your friends page, then run\n"
405 "\"%s checkfriends purge\".\n"), app.programname);
406 return TRUE;
409 do_checkfriends(cfm, network_ctx_cmdline);
411 cf_cli_conf_write(cfm, acc);
413 return (cfm->state == CF_NEW);