1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
9 #include "liblj/checkfriends.h"
12 #include <stdlib.h> /* atoi */
14 #include <unistd.h> /* unlink */
20 #include "checkfriends.h"
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 */
45 guint timeout
; /* handle on glib timeout for next check */
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
);
67 cfmgr_free(CFMgr
*cfm
) {
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
);
80 g_free(cfm
->lastupdate
);
85 cfmgr_class_init(gpointer klass
, gpointer class_data
) {
86 signals
[ACCOUNT_CHANGED
] = g_signal_new("account_changed",
89 G_STRUCT_OFFSET(CFMgrClass
, account_changed
),
91 g_cclosure_marshal_VOID__POINTER
,
92 G_TYPE_NONE
, 1, G_TYPE_POINTER
);
94 signals
[STATE_CHANGED
] = g_signal_new("state_changed",
97 G_STRUCT_OFFSET(CFMgrClass
, state_changed
),
99 g_cclosure_marshal_VOID__INT
,
100 G_TYPE_NONE
, 1, G_TYPE_INT
);
104 cfmgr_set_account(CFMgr
*cfm
, JamAccount
*acc
) {
105 cfmgr_set_state(cfm
, CF_DISABLED
);
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
)));
112 if (conf
.options
.cfautostart
&& jam_account_lj_get_checkfriends(acc
))
113 cfmgr_set_state(cfm
, CF_ON
);
114 #endif /* HAVE_GTK */
118 g_signal_emit_by_name(cfm
, "account_changed", acc
);
122 cf_timeout_cb(CFMgr
*mgr
) {
123 if (do_checkfriends(mgr
, network_ctx_silent
) == 0)
124 reschedule(mgr
, CF_AUTOSCHED
);
128 /* install a timeout for the next checkfriends call according to the
129 * most recent interval information */
131 reschedule(CFMgr
*cfm
, gint interval
) {
133 if (interval
== CF_AUTOSCHED
)
134 interval
= MAX(cfm
->server_interval
, cfm
->user_interval
) * 1000;
136 cfm
->timeout
= g_timeout_add(
138 (GSourceFunc
)cf_timeout_cb
,
144 cfmgr_set_state(CFMgr
*cfm
, CFState state
) {
151 gtk_timeout_remove(cfm
->timeout
);
153 #endif /* HAVE_GTK */
157 /* every startup gets a clean slate */
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 */
171 g_signal_emit_by_name(cfm
, "state_changed", cfm
->state
);
175 cfmgr_get_state(CFMgr
*cfm
) {
180 cfmgr_get_account(CFMgr
*cfm
) {
181 if (!cfm
->account
) return NULL
;
182 return JAM_ACCOUNT_LJ(cfm
->account
);
186 cfmgr_set_mask(CFMgr
*cfm
, guint32 mask
) {
188 if (conf
.options
.cfusemask
== FALSE
)
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 */
199 cfmgr_init(CFMgr
*cfm
) {
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 */
209 string_replace(&cfm
->lastupdate
, g_strdup(""));
212 cfm
->timeout
= 0; /* will be installed upon first "CF_ON" */
213 #endif /* HAVE_GTK */
217 cfmgr_new(JamAccount
*acc
) {
218 CFMgr
*cfm
= LOGJAM_CFMGR(g_object_new(cfmgr_get_type(), NULL
));
219 cfmgr_set_account(cfm
, acc
);
224 cfmgr_get_type(void) {
225 static GType cfm_type
= 0;
227 const GTypeInfo cfm_info
= {
231 (GClassInitFunc
) cfmgr_class_init
,
236 (GInstanceInitFunc
) cfmgr_init
,
238 cfm_type
= g_type_register_static(G_TYPE_OBJECT
,
239 "CFMgr", &cfm_info
, 0);
246 cf_threshold_normalize(gint
*threshold
) {
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.
259 do_checkfriends(CFMgr
*cfm
, NetContext
*ctx
) {
262 cf
= lj_checkfriends_new(jam_account_lj_get_user(cfm
->account
),
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
);
273 if (cfm
->errors
> CF_MAXERRORS
) {
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."));
281 cfmgr_set_state(cfm
, CF_DISABLED
);
287 /* we succeeded. reset the error count. */
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)
299 if (cfm
->newcount
>= conf
.cfthreshold
)
300 cfmgr_set_state(cfm
, CF_NEW
);
306 cf_cli_make_path(JamAccountLJ
*acc
, char *path
) {
308 char *id
= jam_account_id_strdup(JAM_ACCOUNT(acc
));
309 g_snprintf(file
, 1024, "checkfriends-%s", id
);
311 conf_make_path(file
, path
, 1024);
315 checkfriends_cli_purge(JamAccountLJ
*acc
) {
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
));
328 cf_cli_conf_read(CFMgr
*cfm
, JamAccountLJ
*acc
) {
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]);
351 cf_cli_conf_write(CFMgr
*cfm
, JamAccountLJ
*acc
) {
355 cf_cli_make_path(acc
, path
);
357 f
= fopen(path
, "w");
359 g_printerr(_("Error opening %s for write: %s."),
360 path
, g_strerror(errno
));
364 fprintf(f
, "%s|%d|%d|%d|%ld",
365 cfm
->lastupdate
? cfm
->lastupdate
: "",
366 cfm
->server_interval
,
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
380 * keeps track of its own persistent information with cf_cli_conf_*(). */
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
) {
392 g_printerr(_("Maximum error count reached in contacting server.\n"
393 "Run \"%s --checkfriends=purge\" and try again.\n"),
397 if (now
- then
< cfm
->server_interval
) {
399 g_printerr(_("Request rate exceeded. Slow down.\n"));
402 if (cfm
->state
== CF_NEW
) {
404 g_printerr(_("Read your friends page, then run\n"
405 "\"%s checkfriends purge\".\n"), app
.programname
);
409 do_checkfriends(cfm
, network_ctx_cmdline
);
411 cf_cli_conf_write(cfm
, acc
);
413 return (cfm
->state
== CF_NEW
);