1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
6 #include "liblj/checkfriends.h"
9 #include <stdlib.h> /* atoi */
10 #include <unistd.h> /* unlink */
14 #include "checkfriends.h"
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 */
40 guint timeout
; /* handle on glib timeout for next check */
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
);
65 void cfmgr_free (CFMgr
*cfm
) {
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
);
75 g_free(cfm
->lastupdate
);
80 static void cfmgr_class_init (gpointer klass
, gpointer class_data
) {
81 signals
[ACCOUNT_CHANGED
] = g_signal_new("account_changed",
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",
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
);
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
)));
101 if (conf
.options
.cfautostart
&& jam_account_lj_get_checkfriends(acc
)) cfmgr_set_state(cfm
, CF_ON
);
102 #endif /* HAVE_GTK */
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
);
116 /* install a timeout for the next checkfriends call according to the most recent interval information */
117 static void reschedule (CFMgr
*cfm
, gint interval
) {
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
);
125 void cfmgr_set_state (CFMgr
*cfm
, CFState state
) {
130 if (cfm
->timeout
) gtk_timeout_remove(cfm
->timeout
);
132 #endif /* HAVE_GTK */
135 /* every startup gets a clean slate */
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 */
147 g_signal_emit_by_name(cfm
, "state_changed", cfm
->state
);
151 CFState
cfmgr_get_state (CFMgr
*cfm
) {
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
) {
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
) {
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 */
182 string_replace(&cfm
->lastupdate
, g_strdup(""));
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
);
196 GType
cfmgr_get_type (void) {
197 static GType cfm_type
= 0;
199 const GTypeInfo cfm_info
= {
203 (GClassInitFunc
) cfmgr_class_init
,
208 (GInstanceInitFunc
) cfmgr_init
,
210 cfm_type
= g_type_register_static(G_TYPE_OBJECT
, "CFMgr", &cfm_info
, 0);
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
);
233 if (cfm
->errors
> CF_MAXERRORS
) {
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."));
240 cfmgr_set_state(cfm
, CF_DISABLED
);
245 /* we succeeded. reset the error count. */
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;
252 if (cfm
->newcount
>= conf
.cfthreshold
) cfmgr_set_state(cfm
, CF_NEW
);
257 static void cf_cli_make_path (JamAccountLJ
*acc
, char *path
) {
259 char *id
= jam_account_id_strdup(JAM_ACCOUNT(acc
));
260 g_snprintf(file
, 1024, "checkfriends-%s", id
);
262 conf_make_path(file
, path
, sizeof(file
));
266 void checkfriends_cli_purge (JamAccountLJ
*acc
) {
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
) {
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]);
301 static gboolean
cf_cli_conf_write (CFMgr
*cfm
, JamAccountLJ
*acc
) {
304 cf_cli_make_path(acc
, path
);
305 f
= fopen(path
, "w");
307 g_printerr(_("Error opening %s for write: %s."), path
, g_strerror(errno
));
310 fprintf(f
, "%s|%d|%d|%d|%ld", (cfm
->lastupdate
? cfm
->lastupdate
: ""), cfm
->server_interval
, cfm
->errors
, cfm
->state
, time(NULL
));
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
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
) {
331 g_printerr(_("Maximum error count reached in contacting server.\n"
332 "Run \"%s --checkfriends=purge\" and try again.\n"), app
.programname
);
336 if (now
- then
< cfm
->server_interval
) {
337 if (!app
.quiet
) g_printerr("%s", _("Request rate exceeded. Slow down.\n"));
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
);
344 do_checkfriends(cfm
, network_ctx_cmdline
);
345 cf_cli_conf_write(cfm
, acc
);
346 return (cfm
->state
== CF_NEW
);