1 /*
2 * xfce4-mailwatch-plugin - a mail notification applet for the xfce4 panel
3 * Copyright (c) 2005 Pasi Orovuo <pasi.ov@gmail.com>
4 * Copyright (c) 2008 Brian Tarricone <bjt23@cornell.edu>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License ONLY.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #ifdef HAVE_STRING_H
25 #include <string.h>
26 #endif
27
28 #ifdef HAVE_SYS_TYPES_H
29 #include <sys/types.h>
30 #endif
31
32 #ifdef HAVE_SYS_STAT_H
33 #include <sys/stat.h>
34 #endif
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39
40 #ifdef HAVE_STDLIB_H
41 #include <stdlib.h>
42 #endif
43
44 #ifdef HAVE_ERRNO_H
45 #include <errno.h>
46 #endif
47
48 #include <gtk/gtk.h>
49
50 #include <libxfce4util/libxfce4util.h>
51 #include <libxfce4ui/libxfce4ui.h>
52
53 #include "mailwatch.h"
54
55 #define XFCE_MAILWATCH_MH_MAILBOX( p ) ( (XfceMailwatchMHMailbox *) p )
56 #define BORDER ( 8 )
57
58 #define MH_PLUGIN_NAME "mh-maildir-plugin"
59 /* For now these ain't user configurable (from gui) - Should they be? */
60 #define MH_PROFILE ( ".mh_profile" )
61 #define MH_SEQUENCES ( ".mh_sequences" )
62 #define MH_INBOX ( "inbox" )
63 #define MH_UNSEEN_SEQ ( "unseen" )
64
65 typedef struct {
66 XfceMailwatchMailbox *xfce_mailwatch_mailbox;
67 XfceMailwatch *mailwatch;
68
69 gchar *mh_profile_fn;
70 time_t mh_profile_ctime;
71 gchar *mh_sequences_fn;
72 time_t mh_sequences_ctime;
73 gchar *unseen_sequence; /* This should be a list as there can be multiple? */
74
75 guint timeout;
76 guint last_update;
77
78 gint running;
79 gpointer thread; /* (GThread *) */
80 guint check_id;
81 } XfceMailwatchMHMailbox;
82
83 typedef struct {
84 gchar *component;
85 gchar *value;
86 } MHProfileEntry;
87
88 static void
mh_profile_free(GList * list)89 mh_profile_free( GList *list )
90 {
91 GList *li;
92
93 DBG( " " );
94
95 for ( li = g_list_first( list ); li != NULL; li = g_list_next( li ) ) {
96 MHProfileEntry *entry = li->data;
97
98 g_free( entry->component );
99 g_free( entry->value );
100 }
101 g_list_free( list );
102 }
103
104 #ifdef DEBUG
105 static void
mh_profile_print(GList * profile)106 mh_profile_print( GList *profile )
107 {
108 GList *li;
109
110 for ( li = g_list_first( profile ); li != NULL; li = g_list_next( li ) ) {
111 MHProfileEntry *e = li->data;
112
113 DBG( "%s: %s", e->component, e->value );
114 }
115 }
116 #endif /* DEBUG */
117
118 static MHProfileEntry *
mh_profile_entry_create_new(const char * line)119 mh_profile_entry_create_new( const char *line )
120 {
121 MHProfileEntry *entry = NULL;
122 gchar **v;
123
124 v = g_strsplit( line, ":", 2 );
125
126 if ( v && v[0] && v[1] ) {
127 entry = g_new0( MHProfileEntry, 1 );
128
129 entry->component = g_strstrip( v[0] );
130 entry->value = g_strstrip( v[1] );
131
132 g_free( v );
133 }
134 else {
135 g_strfreev( v );
136 }
137 return ( entry );
138 }
139
140 static gchar *
mh_profile_readline(XfceMailwatchMHMailbox * mh,const gchar * mh_profile,GIOChannel * ioc)141 mh_profile_readline( XfceMailwatchMHMailbox *mh, const gchar *mh_profile, GIOChannel *ioc )
142 {
143 gchar *line = NULL, *curline;
144 gsize nread, newline;
145 GIOStatus status;
146 GError *error = NULL;
147
148 g_return_val_if_fail( ioc != NULL, NULL );
149
150 status = g_io_channel_read_line( ioc, &curline,
151 &nread, &newline,
152 &error );
153 while ( status == G_IO_STATUS_NORMAL ) {
154 gchar c;
155
156 curline[newline] = 0;
157
158 if ( !*curline ) {
159 /* An mh profile shouldn't contain blank lines. Ignore 'em */
160 g_free( curline );
161 }
162 else {
163 if ( !line ) {
164 if ( g_ascii_isspace( *curline ) ) {
165 /* The profile isn't right, ignore */
166 curline = g_strstrip( curline );
167 }
168
169 line = curline;
170 }
171 else {
172 gchar *p;
173
174 curline = g_strstrip( curline );
175
176 p = g_strconcat( line, curline, NULL );
177
178 g_free( line );
179 g_free( curline );
180
181 line = p;
182 }
183
184 if ( g_io_channel_read_chars( ioc, &c, 1, &nread, NULL ) == G_IO_STATUS_NORMAL ) {
185 if ( !g_ascii_isspace( c ) || g_ascii_iscntrl( c ) ) {
186 /* g_ascii_iscntrl() is supposed to catch newlines */
187 g_io_channel_seek_position( ioc, -1, G_SEEK_CUR, NULL );
188 break;
189 }
190 }
191 }
192
193 status = g_io_channel_read_line( ioc, &curline,
194 &nread, &newline,
195 &error );
196 }
197
198 if ( status == G_IO_STATUS_ERROR ) {
199 xfce_mailwatch_log_message( mh->mailwatch, XFCE_MAILWATCH_MAILBOX( mh ),
200 XFCE_MAILWATCH_LOG_WARNING,
201 "Error reading file %s: %s",
202 mh_profile, error->message );
203
204 g_error_free( error );
205 }
206
207 return ( line );
208 }
209
210 static GList *
mh_profile_read(XfceMailwatchMHMailbox * mh,const gchar * mh_profile)211 mh_profile_read( XfceMailwatchMHMailbox *mh, const gchar *mh_profile )
212 {
213 GIOChannel *ioc;
214 GError *error = NULL;
215 gchar *line = NULL;
216 GList *li = NULL;
217
218 DBG( "-->>" );
219
220 ioc = g_io_channel_new_file( mh_profile, "r", &error );
221 if ( !ioc ) {
222 xfce_mailwatch_log_message( mh->mailwatch, XFCE_MAILWATCH_MAILBOX( mh ),
223 XFCE_MAILWATCH_LOG_ERROR,
224 "Failed to open file %s: %s",
225 mh_profile, error->message );
226 g_error_free( error );
227 return ( NULL );
228 }
229 g_io_channel_set_encoding( ioc, NULL, NULL );
230
231 while ( ( line = mh_profile_readline( mh, mh_profile, ioc ) ) ) {
232 MHProfileEntry *entry;
233
234 entry = mh_profile_entry_create_new( line );
235 if ( entry ) {
236 li = g_list_prepend( li, entry );
237 }
238 else {
239 xfce_mailwatch_log_message( mh->mailwatch, XFCE_MAILWATCH_MAILBOX( mh ),
240 XFCE_MAILWATCH_LOG_WARNING,
241 _( "Malformed line %s in %s ignored." ), line, mh_profile );
242 }
243 g_free( line );
244 }
245
246 g_io_channel_shutdown( ioc, FALSE, NULL );
247 g_io_channel_unref( ioc );
248
249 DBG( "<<--" );
250 return ( li );
251 }
252
253 static gint
mh_profile_entry_compare(gconstpointer a,gconstpointer b)254 mh_profile_entry_compare( gconstpointer a, gconstpointer b )
255 {
256 const MHProfileEntry *e = a;
257
258 DBG( "%s <-> %s", e->component, (const gchar *) b );
259
260 return ( g_ascii_strcasecmp( e->component, b ) );
261 }
262
263 static gchar *
mh_profile_entry_get_value(GList * profile,const gchar * component)264 mh_profile_entry_get_value( GList *profile, const gchar *component )
265 {
266 MHProfileEntry *entry;
267 GList *li;
268
269 DBG( "--> %s", component );
270
271 li = g_list_find_custom( profile, component, mh_profile_entry_compare );
272 if ( !li ) {
273 DBG( "NULL" );
274 return ( NULL );
275 }
276 entry = li->data;
277
278 g_assert( entry != NULL );
279
280 return ( g_strdup( entry->value ) );
281 }
282
283 static gchar *
mh_get_profile_filename(void)284 mh_get_profile_filename( void )
285 {
286 const gchar *env;
287 gchar *mh_profile;
288
289 env = g_getenv( "MH" );
290 if ( env ) {
291 if ( !g_path_is_absolute( env ) ) {
292 gchar *curdir = g_get_current_dir();
293
294 mh_profile = g_build_filename( curdir, env, NULL );
295
296 g_free( curdir );
297 }
298 else {
299 mh_profile = g_strdup( env );
300 }
301 }
302 else {
303 mh_profile = g_build_filename( g_get_home_dir(), MH_PROFILE, NULL );
304 }
305 return ( mh_profile );
306 }
307
308 static void
mh_read_config(XfceMailwatchMHMailbox * mh)309 mh_read_config( XfceMailwatchMHMailbox *mh )
310 {
311 gchar *mh_path = NULL, *mh_inbox = NULL;
312 gchar *mh_sequences = NULL, *tmpptr = NULL;
313 GList *profile;
314
315 DBG( "-->>" );
316
317 if ( mh->mh_sequences_fn ) {
318 g_free( mh->mh_sequences_fn );
319 mh->mh_sequences_fn = NULL;
320 }
321 if ( mh->unseen_sequence ) {
322 g_free( mh->unseen_sequence );
323 mh->unseen_sequence = NULL;
324 }
325
326 if ( !mh->mh_profile_fn ) {
327 mh->mh_profile_fn = mh_get_profile_filename();
328 }
329
330 profile = mh_profile_read( mh, mh->mh_profile_fn );
331 if ( !profile ) {
332 DBG( "Profile == NULL" );
333 return;
334 }
335
336 #ifdef DEBUG
337 mh_profile_print( profile );
338 #endif
339
340 mh_path = mh_profile_entry_get_value( profile, "Path" );
341 if ( !mh_path ) {
342 mh_profile_free( profile );
343 return;
344 }
345
346 if ( !g_path_is_absolute( mh_path ) ) {
347 tmpptr = g_build_filename( g_get_home_dir(), mh_path, NULL );
348 g_free( mh_path );
349 mh_path = tmpptr;
350 }
351
352 mh_inbox = mh_profile_entry_get_value( profile, "Inbox" );
353 mh_sequences = mh_profile_entry_get_value( profile, "mh-sequences" );
354
355 mh->unseen_sequence = mh_profile_entry_get_value( profile, "Unseen-Sequence" );
356
357 mh->mh_sequences_fn = g_build_filename( mh_path,
358 mh_inbox ? mh_inbox : MH_INBOX,
359 mh_sequences ? mh_sequences : MH_SEQUENCES,
360 NULL );
361
362 g_free( mh_path );
363 if ( mh_inbox ) {
364 g_free( mh_inbox );
365 }
366 if ( mh_sequences ) {
367 g_free( mh_sequences );
368 }
369 mh_profile_free( profile );
370 }
371
372 static void
mh_check_mail(XfceMailwatchMHMailbox * mh)373 mh_check_mail( XfceMailwatchMHMailbox *mh )
374 {
375 struct stat st;
376
377 DBG( "-->>" );
378
379 if ( !mh->mh_profile_fn ) {
380 mh->mh_profile_fn = mh_get_profile_filename();
381 }
382
383 if ( stat( mh->mh_profile_fn, &st ) == 0 ) {
384 if ( st.st_ctime != mh->mh_profile_ctime ) {
385 mh_read_config( mh );
386 mh->mh_profile_ctime = st.st_ctime;
387 }
388 }
389 else {
390 xfce_mailwatch_log_message( mh->mailwatch, XFCE_MAILWATCH_MAILBOX( mh ),
391 XFCE_MAILWATCH_LOG_WARNING,
392 _( "Failed to get status of file %s: %s" ),
393 mh->mh_profile_fn, strerror( errno ) );
394 }
395
396 if ( !mh->mh_sequences_fn ) {
397 return;
398 }
399
400 if ( stat( mh->mh_sequences_fn, &st ) < 0 ) {
401 xfce_mailwatch_log_message( mh->mailwatch, XFCE_MAILWATCH_MAILBOX( mh ),
402 XFCE_MAILWATCH_LOG_ERROR,
403 _( "Failed to get status of file %s: %s" ),
404 mh->mh_sequences_fn, strerror( errno ) );
405 }
406 else {
407 if ( st.st_ctime != mh->mh_sequences_ctime ) {
408 GList *seqlist;
409 gchar *unseen;
410 gulong num_new = 0;
411
412 mh->mh_sequences_ctime = st.st_ctime;
413
414 seqlist = mh_profile_read( mh, mh->mh_sequences_fn );
415
416 #ifdef DEBUG
417 mh_profile_print( seqlist );
418 #endif
419
420 unseen = mh_profile_entry_get_value( seqlist,
421 mh->unseen_sequence ? mh->unseen_sequence : MH_UNSEEN_SEQ );
422 mh_profile_free( seqlist );
423 if ( unseen ) {
424 gchar **v;
425 guint i;
426
427 v = g_strsplit_set( unseen, " \t\r\n", 0 );
428 g_free( unseen );
429
430 for ( i = 0; v[i] != NULL; i++ ) {
431 gchar *q = NULL;
432 gulong l1, l2;
433
434 l1 = strtoul( v[i], &q, 10 );
435 if ( q && *q ) {
436 q++;
437
438 l2 = strtoul( q, NULL, 10 );
439 if ( l2 ) {
440 l1 = 1 + l2 - l1;
441 }
442 else {
443 /* In this case some sort of error occured */
444 l1 = 1;
445 }
446 }
447 else {
448 l1 = 1;
449 }
450
451 num_new += l1;
452 }
453 g_strfreev( v );
454 }
455 xfce_mailwatch_signal_new_messages( mh->mailwatch, XFCE_MAILWATCH_MAILBOX( mh ), num_new );
456 }
457 }
458
459 DBG( "<<--" );
460 }
461
462 static gpointer
mh_main_thread(gpointer data)463 mh_main_thread( gpointer data )
464 {
465 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( data );
466
467 while( !g_atomic_pointer_get( &mh->thread ) && g_atomic_int_get( &mh->running ) )
468 g_thread_yield();
469
470 if( g_atomic_int_get( &mh->running ) )
471 mh_check_mail( mh );
472
473 g_atomic_pointer_set( &mh->thread, NULL );
474 return ( NULL );
475 }
476
477 static gboolean
mh_check_mail_timeout(gpointer data)478 mh_check_mail_timeout(gpointer data)
479 {
480 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( data );
481 GThread *th;
482
483 if( g_atomic_pointer_get( &mh->thread ) ) {
484 xfce_mailwatch_log_message( mh->mailwatch,
485 XFCE_MAILWATCH_MAILBOX( mh ),
486 XFCE_MAILWATCH_LOG_WARNING,
487 _( "Previous thread hasn't exited yet, not checking mail this time." ) );
488 return TRUE;
489 }
490
491 th = g_thread_try_new( NULL, mh_main_thread, mh, NULL );
492 g_atomic_pointer_set( &mh->thread, th );
493
494 return TRUE;
495 }
496
497 static XfceMailwatchMailbox *
mh_new(XfceMailwatch * mailwatch,XfceMailwatchMailboxType * type)498 mh_new( XfceMailwatch *mailwatch, XfceMailwatchMailboxType *type )
499 {
500 XfceMailwatchMHMailbox *mh;
501
502 DBG( "entering" );
503
504 mh = g_new0( XfceMailwatchMHMailbox, 1 );
505
506 mh->mailwatch = mailwatch;
507 mh->timeout = XFCE_MAILWATCH_DEFAULT_TIMEOUT;
508
509 return ( XFCE_MAILWATCH_MAILBOX( mh ) );
510 }
511
512 static void
mh_restore_param_list(XfceMailwatchMailbox * mailbox,GList * params)513 mh_restore_param_list( XfceMailwatchMailbox *mailbox, GList *params )
514 {
515 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( mailbox );
516 GList *li;
517
518 DBG( "-->>" );
519
520 for ( li = g_list_first( params ); li != NULL; li = g_list_next( li ) ) {
521 XfceMailwatchParam *param = li->data;
522
523 if ( !strcmp( param->key, "timeout" ) ) {
524 mh->timeout = (guint) atol( param->value );
525 }
526 }
527 }
528
529 static GList *
mh_save_param_list(XfceMailwatchMailbox * mailbox)530 mh_save_param_list( XfceMailwatchMailbox *mailbox )
531 {
532 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( mailbox );
533 XfceMailwatchParam *param;
534 GList *params = NULL;
535
536 DBG( "-->>" );
537
538 param = g_new( XfceMailwatchParam, 1 );
539 param->key = g_strdup( "timeout" );
540 param->value = g_strdup_printf( "%u", mh->timeout );
541 params = g_list_prepend( params, param );
542
543 return ( params );
544 }
545
546 static void
mh_timeout_changed_cb(GtkWidget * spinner,XfceMailwatchMHMailbox * mh)547 mh_timeout_changed_cb( GtkWidget *spinner, XfceMailwatchMHMailbox *mh )
548 {
549 guint value = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spinner ) ) * 60;
550
551 DBG( "-->>" );
552
553 if( value == mh->timeout )
554 return;
555
556 mh->timeout = value;
557
558 if( g_atomic_int_get( &mh->running ) ) {
559 if( mh->check_id )
560 g_source_remove( mh->check_id );
561 mh->check_id = g_timeout_add( mh->timeout * 1000,
562 mh_check_mail_timeout,
563 mh );
564 }
565 }
566
567 static GtkContainer *
mh_get_setup_page(XfceMailwatchMailbox * mailbox)568 mh_get_setup_page( XfceMailwatchMailbox *mailbox )
569 {
570 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( mailbox );
571 GtkWidget *vbox, *hbox;
572 GtkWidget *label, *spinner;
573
574 vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, BORDER );
575 gtk_widget_show( vbox );
576
577 hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, BORDER );
578 gtk_widget_show( hbox );
579 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
580
581 label = gtk_label_new( _( "The configuration of this plugin is read from\n"
582 "the default mh maildir profile file ~/.mh_profile" ) );
583 gtk_widget_show( label );
584 gtk_box_pack_start( GTK_BOX( hbox ), label, FALSE, FALSE, 0 );
585
586 hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, BORDER );
587 gtk_widget_show( hbox );
588 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
589
590 label = gtk_label_new_with_mnemonic( _( "_Interval:" ) );
591 gtk_widget_show( label );
592 gtk_label_set_xalign( GTK_LABEL( label ), 1.0 );
593 gtk_box_pack_start( GTK_BOX( hbox ), label, FALSE, FALSE, 0 );
594
595 spinner = gtk_spin_button_new_with_range( 1.0, 1440.0, 1.0 );
596 gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spinner ), TRUE );
597 gtk_spin_button_set_wrap( GTK_SPIN_BUTTON( spinner ), FALSE );
598 gtk_spin_button_set_value( GTK_SPIN_BUTTON( spinner ), mh->timeout / 60 );
599 gtk_widget_show( spinner );
600 gtk_box_pack_start( GTK_BOX( hbox ), spinner, FALSE, FALSE, 0 );
601 g_signal_connect( G_OBJECT( spinner ), "value-changed",
602 G_CALLBACK( mh_timeout_changed_cb ), mh );
603 gtk_label_set_mnemonic_widget( GTK_LABEL( label ), spinner );
604
605 label = gtk_label_new( _( "minute(s)." ) );
606 gtk_widget_show( label );
607 gtk_box_pack_start( GTK_BOX( hbox ), label, FALSE, FALSE, 0 );
608
609 return ( GTK_CONTAINER( vbox ) );
610 }
611
612 static void
mh_force_update_cb(XfceMailwatchMailbox * mailbox)613 mh_force_update_cb( XfceMailwatchMailbox *mailbox )
614 {
615 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( mailbox );
616
617 DBG( " " );
618
619 if( !g_atomic_pointer_get( &mh->thread ) ) {
620 gboolean restart = FALSE;
621
622 if( mh->check_id ) {
623 g_source_remove( mh->check_id );
624 restart = TRUE;
625 }
626
627 mh_check_mail_timeout( mh );
628
629 if( restart ) {
630 mh->check_id = g_timeout_add( mh->timeout * 1000,
631 mh_check_mail_timeout,
632 mh );
633 }
634 }
635 }
636
637 static void
mh_set_activated_cb(XfceMailwatchMailbox * mailbox,gboolean activate)638 mh_set_activated_cb( XfceMailwatchMailbox *mailbox, gboolean activate )
639 {
640 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( mailbox );
641
642 DBG( " " );
643
644 if( activate == g_atomic_int_get( &mh->running ) )
645 return;
646
647 if( activate ) {
648 g_atomic_int_set( &mh->running, TRUE );
649 mh->check_id = g_timeout_add( mh->timeout * 1000, mh_check_mail_timeout, mh );
650 } else {
651 g_atomic_int_set( &mh->running, FALSE );
652 g_source_remove( mh->check_id );
653 mh->check_id = 0;
654 }
655 }
656
657 static void
mh_free(XfceMailwatchMailbox * mailbox)658 mh_free( XfceMailwatchMailbox *mailbox )
659 {
660 XfceMailwatchMHMailbox *mh = XFCE_MAILWATCH_MH_MAILBOX( mailbox );
661
662 DBG( "-->>" );
663
664 mh_set_activated_cb( mailbox, FALSE );
665 while( g_atomic_pointer_get( &mh->thread ) )
666 g_thread_yield();
667
668 if ( mh->mh_profile_fn ) {
669 g_free( mh->mh_profile_fn );
670 }
671 if ( mh->mh_sequences_fn ) {
672 g_free( mh->mh_sequences_fn );
673 }
674 if ( mh->unseen_sequence ) {
675 g_free( mh->unseen_sequence );
676 }
677
678 g_free( mh );
679 }
680
681 XfceMailwatchMailboxType builtin_mailbox_type_mh = {
682 "mh-maildir",
683 N_( "Local MH mail folder" ),
684 N_( "MH plugin watches local MH folders for new mail" ),
685 mh_new,
686 mh_set_activated_cb,
687 mh_force_update_cb,
688 mh_get_setup_page,
689 mh_restore_param_list,
690 mh_save_param_list,
691 mh_free
692 };
693
694