From 42d0cc5ca71d52130cea48c481344ce081b158a1 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Wed, 21 Jun 2000 18:14:44 +0000 Subject: Add the async mail API (still not hooked up though) svn path=/trunk/; revision=3676 --- mail/.cvsignore | 1 + mail/ChangeLog | 12 +- mail/Makefile.am | 15 +- mail/mail-threads.c | 566 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mail/mail-threads.h | 45 +++++ mail/test-thread.c | 113 +++++++++++ 6 files changed, 750 insertions(+), 2 deletions(-) create mode 100644 mail/mail-threads.c create mode 100644 mail/mail-threads.h create mode 100644 mail/test-thread.c (limited to 'mail') diff --git a/mail/.cvsignore b/mail/.cvsignore index 898d2ab6fc..63d45eccce 100644 --- a/mail/.cvsignore +++ b/mail/.cvsignore @@ -11,3 +11,4 @@ evolution-mail evolution-mail.pure test-mail test-sources +test-thread \ No newline at end of file diff --git a/mail/ChangeLog b/mail/ChangeLog index cc1f6f1679..69e4ffd4bc 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,4 +1,14 @@ -2000-06-21 Peter Williams +2000-06-21 Peter Williams + * mail-thread.{c,h}: New files -- a simple API for executing + the major mail ops (fetch_mail etc) asynchronously, allowing + the operations to send messages and update a progress bar. + + * test-thread.{c,h}: Tests the mail-thread API. + + * Makefile.am: add mail-thread.[ch] to evolution_mail_SOURCES + and declare the test_thread noinst_PROGRAM. + +2000-06-21 Peter Williams * mail-format.c (mail_generate_reply): Include "e-setup.h" to get the prototype for evolution_dir. diff --git a/mail/Makefile.am b/mail/Makefile.am index 51c7aa3150..e940eeed45 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -1,4 +1,4 @@ -bin_PROGRAMS = evolution-mail test-mail +bin_PROGRAMS = evolution-mail test-mail test-thread providerdir = $(libdir)/evolution/camel-providers/$(VERSION) @@ -45,6 +45,8 @@ evolution_mail_SOURCES = \ mail-format.c \ mail-identify.c \ mail-ops.c \ + mail-threads.c \ + mail-threads.h \ mail-types.h \ main.c \ message-list.c \ @@ -67,6 +69,7 @@ evolution_mail_LDADD = \ $(top_builddir)/libibex/libibex.la \ $(top_builddir)/filter/libfilter.la \ $(BONOBO_HTML_GNOME_LIBS) \ + $(PTHREAD_LIB) \ $(UNICODE_LIBS) test_mail_SOURCES = \ @@ -75,6 +78,16 @@ test_mail_SOURCES = \ test_mail_LDADD = \ $(BONOBO_HTML_GNOME_LIBS) +test_thread_SOURCES = \ + mail-threads.c \ + mail-threads.h \ + test-thread.c + +test_thread_LDADD = \ + $(BONOBO_VFS_GNOME_LIBS) \ + $(PTHREAD_LIB) + +test_thread_CFLAGS = -g GOAD_FILES = evolution-mail.gnorba OAF_FILES = evolution-mail.oafinfo diff --git a/mail/mail-threads.c b/mail/mail-threads.c new file mode 100644 index 0000000000..ebd2570570 --- /dev/null +++ b/mail/mail-threads.c @@ -0,0 +1,566 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Peter Williams (peterw@helixcode.com) + * + * Copyright 2000, Helix Code, Inc. (http://www.helixcode.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + +#include + +#include +#include +#include "mail.h" +#include "mail-threads.h" + +/* FIXME: support other thread types!!!! */ +#define USE_PTHREADS + +/** + * A function and its userdata + **/ + +typedef struct closure_s { + void (*func)( gpointer ); + gpointer data; +} closure_t; + +/** + * A command issued through the compipe + **/ + +typedef struct com_msg_s { + enum com_msg_type_e { STARTING, PERCENTAGE, HIDE_PBAR, SHOW_PBAR, MESSAGE, ERROR, FINISHED } type; + gfloat percentage; + gchar *message; +} com_msg_t; + +/** + * @mail_operation_in_progress: When true, there's + * another thread executing a major ev-mail operation: + * fetch_mail, etc. + * + * Because camel is not thread-safe we work + * with the restriction that more than one mailbox + * cannot be accessed at once. Thus we cannot + * concurrently check mail and move messages, etc. + **/ + +static gboolean mail_operation_in_progress; + +/** + * @queue_window: The little window on the screen that + * shows the progress of the current operation and the + * operations that are queued to proceed after it. + * + * @queue_window_pending: The vbox that contains the + * list of pending operations. + * + * @queue_window_message: The label that contains the + * operation's message to the user + **/ + +static GtkWidget *queue_window = NULL; +static GtkWidget *queue_window_pending = NULL; +static GtkWidget *queue_window_message = NULL; +static GtkWidget *queue_window_progress = NULL; + +/** + * @op_queue: The list of operations the are scheduled + * to proceed after the currently executing one. + **/ + +static GSList *op_queue = NULL; + +/** + * @compipe: The pipe through which the dispatcher communicates + * with the main thread for GTK+ calls + * + * @chan_reader: the GIOChannel that reads our pipe + */ + +#define READER compipe[0] +#define WRITER compipe[1] + +static int compipe[2] = { -1, -1 }; + +GIOChannel *chan_reader = NULL; + +/** + * Static prototypes + **/ + +static void create_queue_window( void ); +static void dispatch( closure_t *clur ); +static void *dispatch_func( void *data ); +static void check_compipe( void ); +static gboolean read_msg( GIOChannel *source, GIOCondition condition, gpointer userdata ); +static void remove_next_pending( void ); + +/* Pthread code */ +#ifdef USE_PTHREADS + +#include + +/** + * @dispatch_thread: the pthread_t (when using pthreads, of + * course) representing our dispatcher routine. + **/ +static pthread_t dispatch_thread; + +/* FIXME: do we need to set any attributes for our thread? */ + +#else /* defined USE_PTHREADS */ +choke on this: no thread type defined +#endif + +/** + * mail_operation_try: + * @description: A user-friendly string describing the operation. + * @callback: the function to call in another thread to start the operation + * @user_data: extra data passed to the callback + * + * Waits for the currently executing operation to finished, then + * executes the callback function in another thread. Returns TRUE + * on success, FALSE on some sort of queueing error. + **/ + +gboolean +mail_operation_try( const gchar *description, void (*callback)( gpointer ), gpointer user_data ) +{ + closure_t *clur; + g_assert( callback ); + + clur = g_new( closure_t, 1 ); + clur->func = callback; + clur->data = user_data; + + if( mail_operation_in_progress == FALSE ) { + /* We got the lock. Yippeee! This means that no operations + * are pending, either, so we'll create the queue window and + * show only the message and progress bar. + */ + + mail_operation_in_progress = TRUE; + + check_compipe(); + create_queue_window(); + gtk_widget_show_all( queue_window ); + gnome_win_hints_set_layer( queue_window, + WIN_LAYER_ONTOP ); + gnome_win_hints_set_state( queue_window, + WIN_STATE_ARRANGE_IGNORE ); + gnome_win_hints_set_hints( queue_window, + WIN_HINTS_SKIP_FOCUS | + WIN_HINTS_SKIP_WINLIST | + WIN_HINTS_SKIP_TASKBAR ); + gtk_widget_hide( queue_window_pending ); + + dispatch( clur ); + } else { + GtkWidget *label; + + /* Zut. We already have an operation running. Well, + * queue ourselves up. */ + + /* Yes, prepend is faster. But we pop operations + * off the beginning later and that's a lot easier. + */ + + op_queue = g_slist_append( op_queue, clur ); + + /* Show us in the pending window. */ + label = gtk_label_new( description ); + gtk_misc_set_alignment( GTK_MISC( label ), 1.0, 0.5 ); + gtk_box_pack_start( GTK_BOX( queue_window_pending ), label, + TRUE, TRUE, 2 ); + + /* If we want the next op to be on the bottom, uncomment this */ + /* 1 = first on list always (0-based) */ + /* gtk_box_reorder_child( GTK_BOX( queue_window_pending ), label, 1 ); */ + gtk_widget_show_all( queue_window_pending ); + } + + return TRUE; +} + +/** + * mail_op_set_percentage: + * @percentage: the percentage that will be displayed in the progress bar + * + * Set the percentage of the progress bar for the currently executing operation. + * Threadsafe for, nay, intended to be called by, the dispatching thread. + **/ + +void mail_op_set_percentage( gfloat percentage ) +{ + com_msg_t msg; + + msg.type = PERCENTAGE; + msg.percentage = percentage; + write( WRITER, &msg, sizeof( msg ) ); +} + +/** + * mail_op_hide_progressbar: + * + * Hide the progress bar in the status box + * Threadsafe for, nay, intended to be called by, the dispatching thread. + **/ + +/* FIXME: I'd much rather have one of those Netscape-style progress + * bars that just zips back and forth, but gtkprogressbar can't do + * that, right? + */ + +void mail_op_hide_progressbar( void ) +{ + com_msg_t msg; + + msg.type = HIDE_PBAR; + write( WRITER, &msg, sizeof( msg ) ); +} + +/** + * mail_op_show_progressbar: + * + * Show the progress bar in the status box + * Threadsafe for, nay, intended to be called by, the dispatching thread. + **/ + +void mail_op_show_progressbar( void ) +{ + com_msg_t msg; + + msg.type = SHOW_PBAR; + write( WRITER, &msg, sizeof( msg ) ); +} + +/** + * mail_op_set_message: + * @fmt: printf-style format string for the message + * @...: arguments to the format string + * + * Set the message displayed above the progress bar for the currently + * executing operation. + * Threadsafe for, nay, intended to be called by, the dispatching thread. + **/ + +void mail_op_set_message( gchar *fmt, ... ) +{ + com_msg_t msg; + va_list val; + + va_start( val, fmt ); + msg.type = MESSAGE; + msg.message = g_strdup_vprintf( fmt, val ); + va_end( val ); + + write( WRITER, &msg, sizeof( msg ) ); +} + +/** + * mail_op_error: + * @fmt: printf-style format string for the error + * @...: arguments to the format string + * + * Opens an error dialog for the currently executing operation. + * Threadsafe for, nay, intended to be called by, the dispatching thread. + **/ + +void mail_op_error( gchar *fmt, ... ) +{ + com_msg_t msg; + va_list val; + + va_start( val, fmt ); + msg.type = ERROR; + msg.message = g_strdup_vprintf( fmt, val ); + va_end( val ); + + write( WRITER, &msg, sizeof( msg ) ); +} + +/** + * mail_operation_wait_for_finish: + * + * Waits for the currently executing async operations + * to finish executing + */ + +void mail_operation_wait_for_finish( void ) +{ + while( mail_operation_in_progress ) { + while( gtk_events_pending() ) + gtk_main_iteration(); + } +} + +/* ** Static functions **************************************************** */ + +/** + * create_queue_window: + * + * Creates the queue_window widget that displays the progress of the + * current operation. + */ + +static void +create_queue_window( void ) +{ + GtkWidget *vbox; + GtkWidget *pending_vb, *pending_lb; + GtkWidget *progress_lb, *progress_bar; + + /* Check to see if we've only hidden it */ + if( queue_window != NULL ) + return; + + queue_window = gtk_window_new( GTK_WINDOW_DIALOG ); + gtk_container_set_border_width( GTK_CONTAINER( queue_window ), 8 ); + + vbox = gtk_vbox_new( FALSE, 4 ); + + pending_vb = gtk_vbox_new( TRUE, 2 ); + queue_window_pending = pending_vb; + + pending_lb = gtk_label_new( _("Currently pending operations:") ); + gtk_misc_set_alignment( GTK_MISC( pending_lb ), 0.0, 0.0 ); + gtk_box_pack_start( GTK_BOX( pending_vb ), pending_lb, + TRUE, TRUE, 0 ); + + gtk_box_pack_start( GTK_BOX( vbox ), pending_vb, + TRUE, TRUE, 4 ); + + /* FIXME: 'operation' is not the warmest cuddliest word. */ + progress_lb = gtk_label_new( _("Starting operation...") ); + queue_window_message = progress_lb; + gtk_box_pack_start( GTK_BOX( vbox ), progress_lb, + TRUE, TRUE, 4 ); + + progress_bar = gtk_progress_bar_new(); + queue_window_progress = progress_bar; + /* FIXME: is this fit for l10n? */ + gtk_progress_bar_set_orientation( GTK_PROGRESS_BAR( progress_bar ), + GTK_PROGRESS_LEFT_TO_RIGHT ); + gtk_progress_bar_set_bar_style( GTK_PROGRESS_BAR( progress_bar ), + GTK_PROGRESS_CONTINUOUS ); + gtk_box_pack_start( GTK_BOX( vbox ), progress_bar, + TRUE, TRUE, 4 ); + + gtk_container_add( GTK_CONTAINER( queue_window ), vbox ); +} + +/** + * check_compipe: + * + * Check and see if our pipe has been opened and open + * it if necessary. + **/ + +static void check_compipe( void ) +{ + if( READER > 0 ) + return; + + if( pipe( compipe ) < 0 ) { + g_warning( "Call to pipe(2) failed!" ); + + /* FIXME: better error handling. How do we react? */ + return; + } + + chan_reader = g_io_channel_unix_new( READER ); + g_io_add_watch( chan_reader, G_IO_IN, read_msg, NULL ); +} + +/** + * dispatch: + * @clur: The function to execute and its userdata + * + * Start a thread that executes the closure and exit + * it when done. + */ + +static void dispatch( closure_t *clur ) +{ + int res; + + res = pthread_create( &dispatch_thread, NULL, (void *) &dispatch_func, clur ); + + if( res != 0 ) { + g_warning( "Error launching dispatch thread!" ); + /* FIXME: more error handling */ + } +} + +/** + * dispatch_func: + * @data: the closure to run + * + * Runs the closure and exits the thread. + */ + +static void *dispatch_func( void *data ) +{ + com_msg_t msg; + closure_t *clur = (closure_t *) data; + + msg.type = STARTING; + write( WRITER, &msg, sizeof( msg ) ); + + (clur->func)( clur->data ); + + msg.type = FINISHED; + write( WRITER, &msg, sizeof( msg ) ); + + g_free( data ); + + pthread_exit( 0 ); + return NULL; /*NOTREACHED*/ +} + +/** + * read_msg: + * @userdata: unused + * + * A message has been recieved on our pipe; perform the appropriate + * action. + **/ + +static gboolean read_msg( GIOChannel *source, GIOCondition condition, gpointer userdata ) +{ + com_msg_t msg; + closure_t *clur; + GSList *temp; + guint size; + #if 0 + GtkWidget *err_dialog; + #else + gchar *errmsg; + #endif + + g_io_channel_read( source, (gchar *) &msg, + sizeof( msg ) / sizeof( gchar ), + &size ); + + if( size != sizeof( msg ) ) { + g_warning( _("Incomplete message written on pipe!") ); + msg.type = ERROR; + msg.message = g_strdup( _("Error reading commands from dispatching thread.") ); + } + + switch( msg.type ) { + case STARTING: + gtk_label_set_text( GTK_LABEL( queue_window_message ), + _("Starting operation...") ); + gtk_progress_bar_update( GTK_PROGRESS_BAR( queue_window_progress ), 0.0 ); + break; + case PERCENTAGE: + gtk_progress_bar_update( GTK_PROGRESS_BAR( queue_window_progress ), msg.percentage ); + break; + case HIDE_PBAR: + gtk_widget_hide( GTK_WIDGET( queue_window_progress ) ); + break; + case SHOW_PBAR: + gtk_widget_show( GTK_WIDGET( queue_window_progress ) ); + break; + case MESSAGE: + gtk_label_set_text( GTK_LABEL( queue_window_message ), + msg.message ); + g_free( msg.message ); + break; + case ERROR: + #if 0 + /* FIXME FIXME: the gnome_dialog_ functions are causing coredumps + * on my machine! Every time, threads or not... "IMLIB ERROR: Cannot + * allocate XImage buffer". Until then, work around + */ + err_dialog = gnome_error_dialog( msg.message ); + gnome_dialog_run_and_close( GNOME_DIALOG( err_dialog ) ); + #else + errmsg = g_strdup_printf( "ERROR: %s", msg.message ); + gtk_label_set_text( GTK_LABEL( queue_window_message ), + errmsg ); + g_free( errmsg ); + #endif + g_free( msg.message ); + break; + /* Don't fall through; dispatch_func does the FINISHED + * call for us + */ + case FINISHED: + if( op_queue == NULL ) { + /* All done! */ + gtk_widget_hide( queue_window ); + mail_operation_in_progress = FALSE; + } else { + /* There's another operation left */ + + /* Pop it off the front */ + clur = op_queue->data; + temp = g_slist_next( op_queue ); + g_slist_free_1( op_queue ); + op_queue = temp; + + /* Clear it out of the 'pending' vbox */ + remove_next_pending(); + + /* Run run run little process */ + dispatch( clur ); + } + break; + default: + g_warning( _("Corrupted message from dispatching thread?") ); + break; + } + + return TRUE; +} + +/** + * remove_next_pending: + * + * Remove an item from the list of pending items. If + * that's the last one, additionally hide the little + * 'pending' message. + **/ + +static void remove_next_pending( void ) +{ + GList *children; + + children = gtk_container_children( GTK_CONTAINER( queue_window_pending ) ); + + /* Skip past the header label */ + children = g_list_first( children ); + children = g_list_next( children ); + + /* Nuke the one on top */ + gtk_container_remove( GTK_CONTAINER( queue_window_pending ), + GTK_WIDGET( children->data ) ); + + /* Hide it? */ + if( g_list_next( children ) == NULL ) + gtk_widget_hide( queue_window_pending ); + + /* FIXME: The window gets really messed up here */ + gtk_container_resize_children( GTK_CONTAINER( queue_window_pending ) ); + gtk_container_resize_children( GTK_CONTAINER( queue_window ) ); +} diff --git a/mail/mail-threads.h b/mail/mail-threads.h new file mode 100644 index 0000000000..89fa439fa9 --- /dev/null +++ b/mail/mail-threads.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Peter Williams (peterw@helixcode.com) + * + * Copyright 2000, Helix Code, Inc. (http://www.helixcode.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _MAIL_THREADS_H_ +#define _MAIL_THREADS_H_ + +/* Schedule to operation to happen eventually */ + +gboolean mail_operation_try( const gchar *description, + void (*callback)( gpointer ), + gpointer user_data ); + +/* User interface hooks for the other thread */ + +void mail_op_set_percentage( gfloat percentage ); +void mail_op_hide_progressbar( void ); +void mail_op_show_progressbar( void ); +void mail_op_set_message( gchar *fmt, ... ) G_GNUC_PRINTF( 1, 2 ); +void mail_op_error( gchar *fmt, ... ) G_GNUC_PRINTF( 1, 2 ); + +/* Wait for the async operations to finish */ +void mail_operation_wait_for_finish( void ); + +#endif /* defined _MAIL_THREADS_H_ */ diff --git a/mail/test-thread.c b/mail/test-thread.c new file mode 100644 index 0000000000..f2b46be763 --- /dev/null +++ b/mail/test-thread.c @@ -0,0 +1,113 @@ +/* Tests the multithreaded UI code */ + +#include "config.h" +#include +#include +#include +#include +#include "mail-threads.h" + +static void op_1( gpointer userdata ); +static void op_2( gpointer userdata ); +static void op_3( gpointer userdata ); +static void op_4( gpointer userdata ); +static gboolean queue_ops( void ); + +static gboolean queue_ops( void ) +{ + int i; + gchar buf[32]; + + g_message( "Top of queue_ops" ); + + mail_operation_try( "The Crawling Progress Bar of Doom", op_1, NULL ); + mail_operation_try( "The Mysterious Message Setter", op_2, NULL ); + mail_operation_try( "The Error Dialog of No Return", op_3, NULL ); + + for( i = 0; i < 7; i++ ) { + sprintf( buf, "Queue Filler %d", i ); + mail_operation_try( buf, op_4, GINT_TO_POINTER( i ) ); + } + + g_message( "Waiting for finish..." ); + mail_operation_wait_for_finish(); + + g_message( "Ops done -- queue some more!" ); + + mail_operation_try( "Progress Bar Redux", op_1, NULL ); + + g_message( "Waiting for finish again..." ); + mail_operation_wait_for_finish(); + + g_message( "Ops done -- more, more!" ); + + for( i = 0; i < 3; i++ ) { + sprintf( buf, "Queue Filler %d", i ); + mail_operation_try( buf, op_4, GINT_TO_POINTER( i ) ); + } + + g_message( "Waiting for finish AGAIN..." ); + mail_operation_wait_for_finish(); + g_message( "Ops done again. Exiting 0" ); + gtk_exit( 0 ); + return FALSE; +} + +static void op_1( gpointer userdata ) +{ + gfloat pct; + + mail_op_show_progressbar(); + mail_op_set_message( "Watch the progress bar!" ); + + for( pct = 0.0; pct < 1.0; pct += 0.1 ) { + sleep( 1 ); + mail_op_set_percentage( pct ); + } +} + +static void op_2( gpointer userdata ) +{ + int i; + + mail_op_hide_progressbar(); + for( i = 10; i > 0; i-- ) { + mail_op_set_message( "%d", i ); + sleep( 1 ); + } + + mail_op_set_message( "BOOOM!" ); + sleep( 1 ); +} + +static void op_3( gpointer userdata ) +{ + gfloat pct; + + mail_op_show_progressbar(); + mail_op_set_message( "Frobulating the foosamatic" ); + + for( pct = 0.0; pct < 0.3; pct += 0.1 ) { + mail_op_set_percentage( pct ); + sleep( 1 ); + } + + mail_op_error( "Oh no! The foosamatic was booby-trapped!" ); + sleep( 1 ); +} + +static void op_4( gpointer userdata ) +{ + mail_op_hide_progressbar(); + mail_op_set_message( "Filler # %d", GPOINTER_TO_INT( userdata ) ); + sleep( 1 ); +} + +int main( int argc, char **argv ) +{ + g_thread_init( NULL ); + gtk_init( &argc, &argv ); + gtk_idle_add( (GtkFunction) queue_ops, NULL ); + gtk_main(); + return 0; +} -- cgit v1.2.3