plover-gtk/transactionhelper.c
author J. Ali Harlow <ali@juiblex.co.uk>
Fri Mar 08 13:51:08 2019 +0000 (2019-03-08)
changeset 89 e87a7db919c6
parent 76 7a98a5020c44
child 98 71127797ca73
permissions -rw-r--r--
Added tag 0.5.6 for changeset 52ac1fcbdbbe
     1 /*
     2  * Copyright (C) 2014, 2016, 2018  J. Ali Harlow <ali@juiblex.co.uk>
     3  *
     4  * This program is free software; you can redistribute it and/or modify
     5  * it under the terms of the GNU General Public License as published by
     6  * the Free Software Foundation; either version 2 of the License, or
     7  * (at your option) any later version.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License along
    15  * with this program; if not, write to the Free Software Foundation, Inc.,
    16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    17  */
    18 
    19 #include "config.h"
    20 #include <stdlib.h>
    21 #include <string.h>
    22 #include <errno.h>
    23 #include <gtk/gtk.h>
    24 #include <plover/plover.h>
    25 #include <plover/transaction.h>
    26 #include <plover-gtk/transactionhelper.h>
    27 #include "plover/uri-handler.h"
    28 
    29 /*
    30  * A PloverTransactionHelper uses a GtkAssistant to help a user run a
    31  * transaction.
    32  */
    33 
    34 G_DEFINE_TYPE(PloverTransactionHelper,plover_transaction_helper,G_TYPE_OBJECT)
    35 
    36 enum plover_transaction_type {
    37     TRANSACTION_TYPE_NULL=0,
    38     TRANSACTION_TYPE_INSTALL=1UL<<0,
    39     TRANSACTION_TYPE_REMOVE=1UL<<1,
    40     TRANSACTION_TYPE_UPDATE=TRANSACTION_TYPE_INSTALL|TRANSACTION_TYPE_REMOVE
    41 };
    42 
    43 typedef struct _PloverTransactionHelperPrivate {
    44     enum plover_transaction_type transaction_type;
    45     gchar *default_prefix;
    46 } PloverTransactionHelperPrivate;
    47 
    48 #define PLOVER_TRANSACTION_HELPER_GET_PRIVATE(obj)\
    49                                 G_TYPE_INSTANCE_GET_PRIVATE(obj,\
    50 				  PLOVER_TYPE_TRANSACTION_HELPER,\
    51 				  PloverTransactionHelperPrivate)
    52 
    53 enum {
    54     CLOSE=0,
    55     N_SIGNALS
    56 };
    57 
    58 static guint signals[N_SIGNALS];
    59 
    60 static PloverTransaction *
    61   plover_transaction_helper_new_transaction(PloverTransactionHelper *helper,
    62   GError **error);
    63 
    64 static void plover_transaction_helper_finalize(PloverTransactionHelper *helper)
    65 {
    66     PloverTransactionHelperPrivate *priv;
    67     priv=PLOVER_TRANSACTION_HELPER_GET_PRIVATE(helper);
    68     g_free(priv->default_prefix);
    69     g_free(helper->error_primary_text);
    70     g_free(helper->base);
    71     g_free(helper->unsatisfied);
    72     if (helper->comps)
    73 	plover_comps_free(helper->comps);
    74     plover_vector_free(helper->report_adding);
    75     plover_vector_free(helper->report_removing);
    76 }
    77 
    78 static void plover_transaction_helper_dispose(PloverTransactionHelper *helper)
    79 {
    80     g_clear_error(&helper->error);
    81     if (helper->error_dialog)
    82     {
    83 	g_signal_handlers_disconnect_by_data(helper->error_dialog,helper);
    84 	gtk_widget_destroy(helper->error_dialog);
    85 	helper->error_dialog=NULL;
    86     }
    87     if (helper->assistant)
    88     {
    89 	g_signal_handlers_disconnect_by_data(helper->assistant,helper);
    90 	g_clear_object(&helper->assistant);
    91     }
    92     g_clear_object(&helper->ui);
    93     g_slist_foreach(helper->transactions,(GFunc)g_object_unref,NULL);
    94     g_slist_free(helper->transactions);
    95     helper->transactions=NULL;
    96     g_clear_object(&helper->alternate_installed);
    97     g_clear_object(&helper->installed);
    98     g_clear_object(&helper->upstream);
    99     g_clear_object(&helper->relocated_upstream);
   100 }
   101 
   102 static void
   103   plover_transaction_helper_class_init(PloverTransactionHelperClass *klass)
   104 {
   105     GObjectClass *gobject_class=G_OBJECT_CLASS(klass);
   106     gobject_class->finalize=
   107       (void (*)(GObject *))plover_transaction_helper_finalize;
   108     gobject_class->dispose=
   109       (void (*)(GObject *))plover_transaction_helper_dispose;
   110     g_type_class_add_private(klass,sizeof(PloverTransactionHelperPrivate));
   111     signals[CLOSE]=g_signal_newv("close",
   112       G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,NULL,NULL,NULL,
   113       g_cclosure_marshal_VOID__VOID,G_TYPE_NONE,0,NULL);
   114 }
   115 
   116 static void plover_transaction_helper_init(PloverTransactionHelper *helper)
   117 {
   118     helper->report_adding=plover_vector_new();
   119     helper->report_removing=plover_vector_new();
   120 }
   121 
   122 static void plover_transaction_helper_assistant_cancel(GtkAssistant *assistant,
   123   PloverTransactionHelper *helper)
   124 {
   125     gtk_widget_hide(GTK_WIDGET(helper->assistant));
   126     gtk_assistant_set_current_page(helper->assistant,0);
   127     g_signal_emit(helper,signals[CLOSE],0);
   128 }
   129 
   130 static void plover_transaction_helper_assistant_close(GtkAssistant *assistant,
   131   PloverTransactionHelper *helper)
   132 {
   133     gtk_widget_hide(GTK_WIDGET(helper->assistant));
   134     gtk_assistant_set_current_page(helper->assistant,0);
   135     g_signal_emit(helper,signals[CLOSE],0);
   136 }
   137 
   138 static void
   139   plover_transaction_helper_prepare_confirm(PloverTransactionHelper *helper)
   140 {
   141     gchar *package_list,*add,*remove,*s;
   142     GtkLabel *label;
   143     struct plover_vector *report;
   144     if (helper->report_adding->len)
   145     {
   146 	plover_vector_sort(helper->report_adding);
   147 	if (helper->report_adding_dependencies)
   148 	{
   149 	    report=plover_vector_dup(helper->report_adding);
   150 	    if (helper->report_adding->len==1)
   151 		plover_vector_append(report,"its dependencies");
   152 	    else
   153 		plover_vector_append(report,"their dependencies");
   154 	    package_list=plover_vector_format_for_display(report);
   155 	    plover_vector_free(report);
   156 	}
   157 	else
   158 	    package_list=
   159 	      plover_vector_format_for_display(helper->report_adding);
   160 	add=g_strconcat("Packages to be installed or updated: ",package_list,
   161 	  ".",NULL);
   162 	g_free(package_list);
   163     }
   164     else
   165 	add=NULL;
   166     if (helper->report_removing->len)
   167     {
   168 	plover_vector_sort(helper->report_removing);
   169 	if (helper->report_removing_dependants)
   170 	{
   171 	    report=plover_vector_dup(helper->report_removing);
   172 	    if (helper->report_adding->len==1)
   173 		plover_vector_append(report,"its dependants");
   174 	    else
   175 		plover_vector_append(report,"their dependants");
   176 	    package_list=plover_vector_format_for_display(report);
   177 	    plover_vector_free(report);
   178 	}
   179 	else
   180 	    package_list=
   181 	      plover_vector_format_for_display(helper->report_removing);
   182 	remove=g_strconcat("Packages to be removed: ",package_list,".",NULL);
   183 	g_free(package_list);
   184     }
   185     else
   186 	remove=NULL;
   187     label=GTK_LABEL(gtk_builder_get_object(helper->ui,"SISummaryOfWork"));
   188     if (add && remove)
   189 	s=g_strconcat("<b>Installation Summary</b>\n\n",remove,"\n\n",add,NULL);
   190     else if (add || remove)
   191 	s=g_strconcat("<b>Installation Summary</b>\n\n",add?add:remove,NULL);
   192     else
   193 	s=g_strdup("<b>Installation Summary</b>\n\nNo changes scheduled");
   194     gtk_label_set_markup(label,s);
   195     g_free(s);
   196     g_free(add);
   197     g_free(remove);
   198 }
   199 
   200 static void plover_transaction_helper_run(PloverTransactionHelper *helper);
   201 
   202 static void plover_transaction_helper_callback(GObject *source,
   203   GAsyncResult *result,gpointer user_data)
   204 {
   205     GError *error=NULL;
   206     PloverTransactionHelper *helper=user_data;
   207     PloverTransaction *transaction=PLOVER_TRANSACTION(source);
   208     if (!plover_transaction_commit_finish(transaction,result,&error))
   209     {
   210 	plover_transaction_helper_set_error(helper,error,
   211 	  "Software installation failed");
   212 	g_error_free(error);
   213     }
   214     else
   215 	plover_transaction_helper_run(helper);
   216     /*
   217      * There may be status updates queued by transaction as idle events.
   218      * Process them now before we disconnect so that we don't lose them.
   219      */
   220     while(g_main_context_pending(NULL))
   221 	g_main_context_iteration(NULL,FALSE);
   222     g_signal_handlers_disconnect_by_data(transaction,helper);
   223     g_object_unref(transaction);
   224 }
   225 
   226 static void plover_transaction_helper_transaction_status_changed(
   227   PloverTransaction *transaction,const char *status,
   228   PloverTransactionHelper *helper)
   229 {
   230     GtkProgressBar *bar;
   231     bar=GTK_PROGRESS_BAR(gtk_builder_get_object(helper->ui,"SIProgressBar"));
   232     gtk_progress_bar_set_text(bar,status);
   233 }
   234 
   235 static void plover_transaction_helper_run(PloverTransactionHelper *helper)
   236 {
   237     PloverTransaction *transaction;
   238     GtkWidget *page;
   239     page=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgress"));
   240     if (helper->transactions)
   241     {
   242 	if (helper->assistant)
   243 	    gtk_assistant_set_page_complete(helper->assistant,page,FALSE);
   244 	transaction=helper->transactions->data;
   245 	helper->transactions=g_slist_delete_link(helper->transactions,
   246 	  helper->transactions);
   247 	g_signal_connect(transaction,"status-changed",
   248 	  G_CALLBACK(plover_transaction_helper_transaction_status_changed),
   249 	  helper);
   250 	plover_transaction_commit_async(transaction,NULL,
   251 	  plover_transaction_helper_callback,helper);
   252     }
   253     else if (helper->assistant)
   254 	gtk_assistant_set_page_complete(helper->assistant,page,TRUE);
   255 }
   256 
   257 static gboolean plover_transaction_helper_pulse(gpointer user_data)
   258 {
   259     PloverTransactionHelper *helper=user_data;
   260     GtkWidget *w;
   261     GtkProgressBar *bar;
   262     if (!helper->assistant)
   263 	return FALSE;
   264     w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgress"));
   265     bar=GTK_PROGRESS_BAR(gtk_builder_get_object(helper->ui,"SIProgressBar"));
   266     if (gtk_assistant_get_page_complete(helper->assistant,w))
   267     {
   268 	gtk_progress_bar_set_fraction(bar,1.0);
   269 	helper->pulse_handler=0;
   270 	return FALSE;
   271     }
   272     else
   273     {
   274 	gtk_progress_bar_pulse(bar);
   275 	return TRUE;
   276     }
   277 }
   278 
   279 static void
   280   plover_transaction_helper_prepare_progress(PloverTransactionHelper *helper)
   281 {
   282     GError *error=NULL;
   283     GtkToggleButton *button;
   284     PloverTransaction *transaction;
   285     GSList *save_transactions;
   286     PloverTransactionHelperPrivate *priv;
   287     enum plover_transaction_type save_transaction_type;
   288     priv=PLOVER_TRANSACTION_HELPER_GET_PRIVATE(helper);
   289     button=GTK_TOGGLE_BUTTON(gtk_builder_get_object(helper->ui,
   290       "SIRemoveExisting"));
   291     if (gtk_toggle_button_get_active(button))
   292     {
   293 	transaction=plover_transaction_helper_new_transaction(helper,&error);
   294 	/*
   295 	 * I think we want switch to the alternate installed set in the case of
   296 	 * alternate_database_clashes, but not in the case of
   297 	 * active_database_is_incompatible (see
   298 	 * plover_transaction_helper_update_summary_page).
   299 	 * Whether testing for helper->alternate_installed is sufficient I'm
   300 	 * far from clear.
   301 	 */
   302 	if (helper->alternate_installed)
   303 	    plover_transaction_set_installed(transaction,
   304 	      helper->alternate_installed);
   305 	if (transaction && !plover_transaction_remove(transaction,NULL,&error))
   306 	{
   307 	    g_object_unref(transaction);
   308 	    transaction=NULL;
   309 	}
   310 	if (transaction)
   311 	{
   312 	    save_transactions=helper->transactions;
   313 	    helper->transactions=NULL;
   314 	    save_transaction_type=priv->transaction_type;
   315 	    priv->transaction_type=0;
   316 	    if (!plover_transaction_helper_add_transaction(helper,transaction,
   317 	      NULL,PLOVER_TRANSACTION_HELPER_REPORT_REMOVE,&error))
   318 	    {
   319 		g_object_unref(transaction);
   320 		transaction=NULL;
   321 		helper->transactions=save_transactions;
   322 		priv->transaction_type=save_transaction_type;
   323 	    }
   324 	    else
   325 	    {
   326 		g_slist_foreach(save_transactions,(GFunc)g_object_unref,NULL);
   327 		g_slist_free(save_transactions);
   328 	    }
   329 	}
   330 	if (!transaction)
   331 	{
   332 	    if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT))
   333 		g_clear_error(&error);
   334 	    if (error)
   335 	    {
   336 		plover_transaction_helper_set_error(helper,error,
   337 		  "Failed to remove existing packages");
   338 		g_error_free(error);
   339 		return;
   340 	    }
   341 	}
   342     }
   343     /*
   344      * Note that PloverTransaction does support cancelling a transaction, but
   345      * there are a number of challenges with using it:
   346      *	- cancellation is only supported during the file phase if razor
   347      *	  has atomic rollback,
   348      *  - cancellation is not supported during post-transaction scripts at all
   349      *    (since by the time the first script is started the atomic has already
   350      *    been committed) and these can take quite some time,
   351      *  - where a transaction has an embedded COMMIT, any rollback won't
   352      *    go back beyond this point.
   353      * To support user-cancel, then, we would need some mechanism to:
   354      *  - Comunicate that the operation is being cancelled and this may take
   355      *    some time,
   356      *  - Not allow cancellation at all after the last post-transaction script
   357      *    phase is started,
   358      *  - Report the partially completed transaction where cancellation
   359      *    occurred after a COMMIT point.
   360      * At present, this doesn't appear worth the effort.
   361      */
   362     if (helper->assistant)
   363 	gtk_assistant_commit(helper->assistant);
   364     plover_transaction_helper_run(helper);
   365     helper->pulse_handler=g_timeout_add(100,plover_transaction_helper_pulse,
   366       helper);
   367 }
   368 
   369 static void plover_transaction_helper_assistant_prepare(GtkAssistant *assistant,
   370   GtkWidget *page,PloverTransactionHelper *helper)
   371 {
   372     if (page==GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIConfirm")))
   373 	plover_transaction_helper_prepare_confirm(helper);
   374     else if (page==GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgress")))
   375 	plover_transaction_helper_prepare_progress(helper);
   376 }
   377 
   378 static void
   379   plover_transaction_helper_remove_existing_toggled(GtkToggleButton *button,
   380   PloverTransactionHelper *helper)
   381 {
   382     GtkWidget *w;
   383     if (helper->assistant)
   384     {
   385 	w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIConfirm"));
   386 	gtk_assistant_set_page_complete(helper->assistant,w,
   387 	  gtk_toggle_button_get_active(button));
   388     }
   389 }
   390 
   391 PloverTransactionHelper *plover_transaction_helper_new(GtkBuilder *ui)
   392 {
   393     gsize len;
   394     gchar *s,*directory,*contents;
   395     GError *error=NULL;
   396     GtkWidget *w;
   397     PloverTransactionHelper *helper;
   398     g_return_val_if_fail(ui == NULL || GTK_IS_BUILDER(ui),NULL);
   399     helper=PLOVER_TRANSACTION_HELPER(
   400       g_object_new(PLOVER_TYPE_TRANSACTION_HELPER,NULL));
   401     if (ui)
   402 	helper->ui=g_object_ref(ui);
   403     else
   404 	helper->ui=gtk_builder_new();
   405     helper->assistant=
   406       GTK_ASSISTANT(gtk_builder_get_object(helper->ui,"SoftwareInstallation"));
   407     if (!helper->assistant)
   408     {
   409 	directory=g_strdup(g_getenv("PLOVER_DATADIR"));
   410 	if (!directory)
   411 	{
   412 #ifdef WIN32
   413 	    s=g_win32_get_package_installation_directory_of_module(NULL);
   414 	    directory=g_build_filename(s,"share","plover",NULL);
   415 	    g_free(s);
   416 #else
   417 	    directory=g_strdup(PLOVER_DATADIR);
   418 #endif
   419 	}
   420 	s=g_build_filename(directory,"software-installation.ui",NULL);
   421 	g_free(directory);
   422 	(void)g_file_get_contents(s,&contents,&len,&error);
   423 	g_free(s);
   424 	if (!error)
   425 	{
   426 	    (void)gtk_builder_add_from_string(helper->ui,contents,len,&error);
   427 	    g_free(contents);
   428 	}
   429 	if (error)
   430 	{
   431 	    g_critical("software-installation.ui: %s",error->message);
   432 	    g_clear_error(&error);
   433 	    g_set_error(&error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
   434 	      "Internal error (no user interface)");
   435 	    plover_transaction_helper_set_error(helper,error,
   436 	      "Can't start installer");
   437 	    return helper;
   438 	}
   439 	helper->assistant=GTK_ASSISTANT(gtk_builder_get_object(helper->ui,
   440 	  "SoftwareInstallation"));
   441     }
   442     if (!helper->assistant)
   443     {
   444 	g_critical("\"SoftwareInstallation\" object not found");
   445 	g_set_error(&error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
   446 	  "Internal error (missing wizard)");
   447 	plover_transaction_helper_set_error(helper,error,
   448 	  "Can't start installer");
   449 	g_error_free(error);
   450 	return helper;
   451     }
   452     else
   453 	g_object_ref(helper->assistant);
   454     if (!GTK_IS_ASSISTANT(helper->assistant))
   455     {
   456 	g_critical("\"SoftwareInstallation\" is not a GtkAssistant");
   457 	g_set_error(&error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
   458 	  "Internal error (unexpected wizard type)");
   459 	plover_transaction_helper_set_error(helper,error,
   460 	  "Can't start installer");
   461 	g_error_free(error);
   462 	return helper;
   463     }
   464     g_signal_connect(helper->assistant,"cancel",
   465       G_CALLBACK(plover_transaction_helper_assistant_cancel),helper);
   466     g_signal_connect(helper->assistant,"close",
   467       G_CALLBACK(plover_transaction_helper_assistant_close),helper);
   468     g_signal_connect(helper->assistant,"prepare",
   469       G_CALLBACK(plover_transaction_helper_assistant_prepare),helper);
   470     w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIRemoveExisting"));
   471     if (w)
   472 	g_signal_connect(w,"toggled",
   473 	  G_CALLBACK(plover_transaction_helper_remove_existing_toggled),helper);
   474     w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIIntroduction"));
   475     if (w)
   476 	gtk_assistant_set_page_complete(helper->assistant,w,TRUE);
   477     return helper;
   478 }
   479 
   480 PloverPackageSet *
   481   plover_transaction_helper_get_installed(PloverTransactionHelper *helper)
   482 {
   483     gchar *s,*saved_database_uri;
   484     char *install_root,*local_database,*active_database,*alternate_database;
   485     const char *prefix;
   486     struct comps *comps;
   487     PloverPackageSet *alternate_installed,*installed;
   488     GError *error=NULL;
   489     struct razor_error *razor_error=NULL;
   490     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   491     if (!helper->installed)
   492     {
   493 	comps=plover_transaction_helper_get_comps(helper,NULL);
   494 	if (!comps)
   495 	{
   496 	    g_warning("plover_transaction_helper_get_installed: No comps");
   497 	    return NULL;
   498 	}
   499 	install_root=getenv("RAZOR_ROOT");
   500 	if (!install_root)
   501 	    install_root="file:/";
   502 	prefix=plover_transaction_helper_get_prefix(helper,NULL);
   503 	if (prefix)
   504 	{
   505 	    s=g_strconcat(prefix,"/var/lib/razor",NULL);
   506 	    local_database=razor_path_relative_to_uri(install_root,*s=='/'?s+1:s,
   507 	      &razor_error);
   508 	    g_free(s);
   509 	    if (!local_database)
   510 	    {
   511 		g_warning("plover_transaction_helper_get_installed: %s",
   512 		  razor_error_get_msg(razor_error));
   513 		razor_error_free(razor_error);
   514 		return NULL;
   515 	    }
   516 	}
   517 	else
   518 	    local_database=NULL;
   519 	switch(comps->database)
   520 	{
   521 	    case COMPS_DATABASE_DISTRIBUTION_LOCAL:
   522 		active_database=local_database;
   523 		alternate_database=NULL;
   524 		break;
   525 	    case COMPS_DATABASE_GLOBAL:
   526 		active_database=NULL;
   527 		alternate_database=local_database;
   528 		break;
   529 	}
   530 	saved_database_uri=g_strdup(razor_get_database_uri());
   531 	if (prefix)
   532 	{
   533 	    razor_set_database_uri(alternate_database);
   534 	    alternate_installed=plover_package_set_new();
   535 	    if (!plover_package_set_open(alternate_installed,install_root,TRUE,
   536 	      &error))
   537 	    {
   538 		g_object_unref(alternate_installed);
   539 		g_warning("plover_transaction_helper_get_installed: %s",
   540 		  error->message);
   541 		g_error_free(error);
   542 		free(local_database);
   543 		razor_set_database_uri(saved_database_uri);
   544 		g_free(saved_database_uri);
   545 		return NULL;
   546 	    }
   547 	}
   548 	else
   549 	    alternate_installed=NULL;
   550 	razor_set_database_uri(active_database);
   551 	free(local_database);
   552 	installed=plover_package_set_new();
   553 	if (plover_package_set_open(installed,install_root,TRUE,&error))
   554 	{
   555 	    helper->alternate_installed=alternate_installed;
   556 	    helper->installed=installed;
   557 	}
   558 	else
   559 	{
   560 	    g_object_unref(installed);
   561 	    if (alternate_installed)
   562 		g_object_unref(alternate_installed);
   563 	    g_warning("plover_transaction_helper_get_installed: %s",error->message);
   564 	    g_error_free(error);
   565 	}
   566 	razor_set_database_uri(saved_database_uri);
   567 	g_free(saved_database_uri);
   568     }
   569     return helper->installed;
   570 }
   571 
   572 void plover_transaction_helper_set_installed(PloverTransactionHelper *helper,
   573   PloverPackageSet *installed)
   574 {
   575     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
   576     g_return_if_fail(PLOVER_IS_PACKAGE_SET(installed));
   577     g_return_if_fail(helper->installed == NULL);
   578     g_clear_object(&helper->alternate_installed);
   579     helper->installed=g_object_ref(installed);
   580 }
   581 
   582 PloverRepository *
   583   plover_transaction_helper_get_upstream(PloverTransactionHelper *helper,
   584   GError **error)
   585 {
   586     const char *base;
   587     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   588     if (!helper->upstream)
   589     {
   590 	base=plover_transaction_helper_get_base(helper);
   591 	helper->upstream=plover_repository_new_from_yum(base,error);
   592     }
   593     return helper->upstream;
   594 }
   595 
   596 void plover_transaction_helper_set_upstream(PloverTransactionHelper *helper,
   597   PloverRepository *upstream)
   598 {
   599     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
   600     g_return_if_fail(PLOVER_IS_REPOSITORY(upstream));
   601     g_return_if_fail(helper->upstream == NULL);
   602     helper->upstream=g_object_ref(upstream);
   603 }
   604 
   605 const char *plover_transaction_helper_get_base(PloverTransactionHelper *helper)
   606 {
   607     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   608     return helper->base;
   609 }
   610 
   611 void plover_transaction_helper_set_base(PloverTransactionHelper *helper,
   612   const char *base)
   613 {
   614     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
   615     g_return_if_fail(helper->transactions == NULL);
   616     g_free(helper->base);
   617     helper->base=g_strdup(base);
   618 }
   619 
   620 struct comps *
   621   plover_transaction_helper_get_comps(PloverTransactionHelper *helper,
   622   GError **error)
   623 {
   624     gchar *s;
   625     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   626     if (!helper->comps && helper->base)
   627     {
   628 	s=g_strconcat(helper->base,"/repodata/comps.xml",NULL);
   629 	helper->comps=plover_comps_new_from_file(s);
   630 	if (!helper->comps)
   631 	    g_set_error(error,PLOVER_GENERAL_ERROR,
   632 	      PLOVER_GENERAL_ERROR_FAILED,"%s: %s",s,g_strerror(errno));
   633 	g_free(s);
   634     }
   635     return helper->comps;
   636 }
   637 
   638 const char *
   639   plover_transaction_helper_get_prefix(PloverTransactionHelper *helper,
   640   GError **error)
   641 {
   642     const char *prefix;
   643     struct comps *comps;
   644     PloverTransactionHelperPrivate *priv;
   645     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   646     g_return_val_if_fail(helper->base != NULL || plover_transaction_helper_get_installed(helper) != NULL,NULL);
   647     priv=PLOVER_TRANSACTION_HELPER_GET_PRIVATE(helper);
   648     if (helper->base)
   649     {
   650 	comps=plover_transaction_helper_get_comps(helper,error);
   651 	if (!comps)
   652 	    return NULL;
   653 	g_free(priv->default_prefix);
   654 	priv->default_prefix=plover_comps_get_default_prefix(comps);
   655 	return priv->default_prefix;
   656     }
   657     prefix=plover_package_set_guess_prefix(helper->installed,error);
   658     return prefix;
   659 }
   660 
   661 #if 0
   662 static int plover_transaction_helper_package_count(void)
   663 {
   664     int count=0;
   665     char *install_root;
   666     struct razor_set *set;
   667     struct razor_package *package;
   668     struct razor_package_iterator *pi;
   669     install_root=getenv("RAZOR_ROOT");
   670     if (!install_root)
   671 	install_root="";
   672     set=razor_root_open_read_only(install_root,NULL);
   673     if (set)
   674     {
   675 	pi=razor_package_iterator_create(set);
   676 	while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_LAST))
   677 	    count++;
   678 	razor_package_iterator_destroy(pi);
   679 	razor_set_unref(set);
   680     }
   681     return count;
   682 }
   683 #endif
   684 
   685 static gboolean prefix_clashes(const char *prefix,const char *alt)
   686 {
   687     return g_str_has_prefix(prefix,alt) &&
   688       (prefix[strlen(alt)]=='\0' || prefix[strlen(alt)]=='/');
   689 }
   690 
   691 static void
   692   plover_transaction_helper_update_summary_page(PloverTransactionHelper *helper,
   693   GError **error)
   694 {
   695     int i,remove_count=0;
   696     gboolean alternate_database_clashes=FALSE;
   697     gboolean active_database_is_incompatible=FALSE;
   698     char *local_database,*active_database,*alternate_database;
   699     const char *alternate_prefix;
   700     gchar *prefix=NULL,*s;
   701     struct comps *comps=NULL;
   702     GtkWidget *container,*summary,*page;
   703     GtkButton *button;
   704     GtkLabel *label;
   705     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
   706     comps=plover_transaction_helper_get_comps(helper,error);
   707     if (comps)
   708 	prefix=plover_comps_get_default_prefix(comps);
   709     else
   710 	prefix=NULL;
   711     button=GTK_BUTTON(gtk_builder_get_object(helper->ui,"SIRemoveExisting"));
   712     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),FALSE);
   713     container=GTK_WIDGET(gtk_builder_get_object(helper->ui,
   714       "SIIncompatibleInstallation"));
   715     summary=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SISummaryOfWork"));
   716     page=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIConfirm"));
   717     if (helper->check_vendor && prefix && helper->alternate_installed)
   718     {
   719 	alternate_prefix=
   720 	  plover_package_set_guess_prefix(helper->alternate_installed,NULL);
   721 	if (alternate_prefix && prefix_clashes(prefix,alternate_prefix))
   722 	{
   723 	    alternate_database_clashes=TRUE;
   724 	    remove_count=g_slist_length(
   725 	      plover_package_set_get_packages(helper->alternate_installed));
   726 	}
   727     }
   728     /*
   729      * Rather than try to be too clever, we only deal with one thing
   730      * at a time. That means that if the alternate database clashes
   731      * there's no point checking if the active database is compatible.
   732      */
   733     if (!alternate_database_clashes)
   734     {
   735 	if (helper->check_vendor && prefix &&
   736 	  !plover_package_set_files_match_prefix(helper->installed,prefix))
   737 	{
   738 	    active_database_is_incompatible=TRUE;
   739 	    remove_count=
   740 	      g_slist_length(plover_package_set_get_packages(helper->installed));
   741 	}
   742     }
   743     if (alternate_database_clashes || active_database_is_incompatible)
   744     {
   745 	g_assert(comps!=NULL);
   746 	label=GTK_LABEL(gtk_builder_get_object(helper->ui,
   747 	  "SIIncompatibleInstallationLabel"));
   748 	if (alternate_database_clashes)
   749 	    s=g_strdup_printf("<b>Incompatible Installation</b>\n\n"
   750 	      "There is an existing installation under %s\n"
   751 	      "which is not compatible with this distribution. In order\n"
   752 	      "to continue, the existing installation must be uninstalled.",
   753 	      comps->vendor);
   754 	else /* active_database_is_incompatible */
   755 	    s=g_strdup_printf("<b>Incompatible Installation</b>\n\n"
   756 	      "The existing installation is not from %s.\n In order "
   757 	      "to continue, all the existing packages must be removed.",
   758 	      comps->vendor);
   759 	gtk_label_set_markup(label,s);
   760 	g_free(s);
   761 	s=g_strdup_printf("Remove %d existing package%s",remove_count,
   762 	  remove_count==1?"":"s");
   763 	gtk_button_set_label(button,s);
   764 	g_free(s);
   765 	gtk_widget_show(container);
   766 	gtk_widget_hide(summary);
   767 	if (helper->assistant)
   768 	    gtk_assistant_set_page_complete(helper->assistant,page,FALSE);
   769     }
   770     else
   771     {
   772 	gtk_widget_hide(container);
   773 	gtk_widget_show(summary);
   774 	if (helper->assistant)
   775 	    gtk_assistant_set_page_complete(helper->assistant,page,TRUE);
   776     }
   777     g_free(prefix);
   778 }
   779 
   780 void plover_transaction_helper_set_check_vendor(PloverTransactionHelper *helper,
   781   gboolean check_vendor)
   782 {
   783     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
   784     if (helper->check_vendor!=check_vendor)
   785     {
   786 	helper->check_vendor=check_vendor;
   787 	if (helper->transactions)
   788 	    plover_transaction_helper_update_summary_page(helper,NULL);
   789     }
   790 }
   791 
   792 /*
   793  * If plover_transaction_helper_add_transaction() fails with an error
   794  * of PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET
   795  * then plover_transaction_helper_get_unsatisfied() can be used to
   796  * retrieve a textual description of the problem.
   797  */
   798 
   799 const char *
   800   plover_transaction_helper_get_unsatisfied(PloverTransactionHelper *helper)
   801 {
   802     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   803     return helper->unsatisfied;
   804 }
   805 
   806 #define PLOVER_TRANSACTION_HELPER_IS_VALID_REPORT_ACTION(action) \
   807   ((action)==PLOVER_TRANSACTION_HELPER_REPORT_INSTALL || \
   808   (action)==PLOVER_TRANSACTION_HELPER_REPORT_REMOVE || \
   809   (action)==PLOVER_TRANSACTION_HELPER_REPORT_UPDATE)
   810 
   811 gboolean
   812   plover_transaction_helper_add_transaction(PloverTransactionHelper *helper,
   813   PloverTransaction *transaction,struct plover_vector *report_packages,
   814   PloverTransactionHelperReportAction report_action,GError **error)
   815 {
   816     int i,count;
   817     gboolean other_packages;
   818     const char *s,*name;
   819     enum razor_install_action action;
   820     struct razor_install_iterator *ii;
   821     struct razor_set *report_set;
   822     struct razor_package *package;
   823     struct plover_vector *tasked_packages;
   824     PloverTransactionHelperPrivate *priv;
   825     GtkWidget *w;
   826     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
   827     g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
   828     g_return_val_if_fail(PLOVER_TRANSACTION_HELPER_IS_VALID_REPORT_ACTION(report_action),FALSE);
   829     g_return_val_if_fail(plover_transaction_get_system_set(transaction)!=NULL,FALSE);
   830     priv=PLOVER_TRANSACTION_HELPER_GET_PRIVATE(helper);
   831     g_free(helper->unsatisfied);
   832     helper->unsatisfied=NULL;
   833     if (!plover_transaction_resolve(transaction,error))
   834     {
   835 	s=plover_transaction_get_unsatisfied(transaction);
   836 	helper->unsatisfied=g_strdup(s);
   837 	return FALSE;
   838     }
   839     ii=plover_transaction_get_install_iterator(transaction,error);
   840     if (!ii)
   841 	return FALSE;
   842     if (report_action==PLOVER_TRANSACTION_HELPER_REPORT_REMOVE)
   843 	report_set=plover_transaction_get_system_set(transaction);
   844     else
   845 	report_set=plover_transaction_get_next_set(transaction,error);
   846     if (!report_set)
   847 	return FALSE;
   848     tasked_packages=plover_vector_new();
   849     other_packages=FALSE;
   850     while (razor_install_iterator_next(ii,&package,&action,&count))
   851     {
   852 	if (action==report_action || action==RAZOR_INSTALL_ACTION_ADD &&
   853 	  report_action==PLOVER_TRANSACTION_HELPER_REPORT_UPDATE)
   854 	{
   855 	    razor_package_get_details(report_set,package,RAZOR_DETAIL_NAME,
   856 	      &name,RAZOR_DETAIL_LAST);
   857 	    if (!report_packages ||
   858 	      plover_vector_contains(report_packages,name))
   859 		plover_vector_append(tasked_packages,name);
   860 	    else
   861 		other_packages=TRUE;
   862 	}
   863     }
   864     if (!tasked_packages->len)
   865     {
   866 	/*
   867 	 * If there are no reportable packages tasked for action there
   868 	 * shouldn't by any packages at all, but let's be paranoid.
   869 	 */
   870 	other_packages=FALSE;
   871 	razor_install_iterator_rewind(ii);
   872 	while (razor_install_iterator_next(ii,&package,&action,&count))
   873 	{
   874 	    if (action==report_action)
   875 	    {
   876 		razor_package_get_details(report_set,package,RAZOR_DETAIL_NAME,
   877 		  &name,RAZOR_DETAIL_LAST);
   878 		plover_vector_append(tasked_packages,name);
   879 	    }
   880 	}
   881     }
   882     if (!tasked_packages->len)
   883     {
   884 	g_set_error(error,PLOVER_GENERAL_ERROR,
   885 	  PLOVER_GENERAL_ERROR_NO_WORK,"Transaction includes no %s actions",
   886 	  report_action==PLOVER_TRANSACTION_HELPER_REPORT_REMOVE?
   887 	  "remove":"add");
   888 	plover_vector_free(tasked_packages);
   889 	return FALSE;
   890     }
   891     if (!helper->transactions)
   892 	plover_transaction_helper_update_summary_page(helper,error);
   893     g_object_ref(transaction);
   894     helper->transactions=g_slist_append(helper->transactions,transaction);
   895     if (report_action==PLOVER_TRANSACTION_HELPER_REPORT_REMOVE)
   896     {
   897 	priv->transaction_type|=TRANSACTION_TYPE_REMOVE;
   898 	for(i=0;i<tasked_packages->len;i++)
   899 	{
   900 	    s=tasked_packages->strings[i];
   901 	    if (!plover_vector_contains(helper->report_removing,s))
   902 		plover_vector_append(helper->report_removing,s);
   903 	}
   904 	helper->report_removing_dependants|=other_packages;
   905     }
   906     else
   907     {
   908 	if (report_action==PLOVER_TRANSACTION_HELPER_REPORT_UPDATE)
   909 	    priv->transaction_type|=TRANSACTION_TYPE_REMOVE;
   910 	priv->transaction_type|=TRANSACTION_TYPE_INSTALL;
   911 	for(i=0;i<tasked_packages->len;i++)
   912 	{
   913 	    s=tasked_packages->strings[i];
   914 	    if (!plover_vector_contains(helper->report_adding,s))
   915 		plover_vector_append(helper->report_adding,s);
   916 	}
   917 	helper->report_adding_dependencies|=other_packages;
   918     }
   919     w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgressLabel"));
   920     switch(priv->transaction_type)
   921     {
   922 	case TRANSACTION_TYPE_INSTALL:
   923 	    gtk_label_set_markup(GTK_LABEL(w),
   924 	      "<b>Installing the Software</b>\n\n"
   925 	      "Please wait while the Installation Assistant "
   926 	      "installs the software.\n"
   927 	      "This may take several minutes.");
   928 	    break;
   929 	case TRANSACTION_TYPE_REMOVE:
   930 	    gtk_label_set_markup(GTK_LABEL(w),
   931 	      "<b>Removing Packages</b>\n\n"
   932 	      "Please wait while the Installation Assistant "
   933 	      "removes packages.\n"
   934 	      "This may take several minutes.");
   935 	    break;
   936 	default:
   937 	case TRANSACTION_TYPE_UPDATE:
   938 	    gtk_label_set_markup(GTK_LABEL(w),
   939 	      "<b>Updating the Software</b>\n\n"
   940 	      "Please wait while the Installation Assistant "
   941 	      "updates the software.\n"
   942 	      "This may take several minutes.");
   943 	    break;
   944     }
   945     plover_vector_free(tasked_packages);
   946     return TRUE;
   947 }
   948 
   949 static PloverTransaction *
   950   plover_transaction_helper_new_transaction(PloverTransactionHelper *helper,
   951   GError **error)
   952 {
   953     gboolean ok;
   954     const char *base,*prefix;
   955     GError *tmp_error=NULL;
   956     PloverTransaction *transaction;
   957     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
   958     g_return_val_if_fail(plover_transaction_helper_get_installed(helper) != NULL,NULL);
   959     prefix=plover_transaction_helper_get_prefix(helper,&tmp_error);
   960     if (tmp_error)
   961     {
   962 	g_propagate_error(error,tmp_error);
   963 	return NULL;
   964     }
   965     transaction=plover_transaction_new();
   966     plover_transaction_set_prefix(transaction,prefix);
   967     plover_transaction_set_installed(transaction,helper->installed);
   968     if (helper->upstream)
   969 	ok=plover_transaction_set_upstream(transaction,helper->upstream,error);
   970     else
   971     {
   972 	base=plover_transaction_helper_get_base(helper);
   973 	ok=plover_transaction_set_upstream_from_yum(transaction,base,error);
   974     }
   975     if (!ok)
   976     {
   977 	g_object_unref(transaction);
   978 	transaction=NULL;
   979     }
   980     return transaction;
   981 }
   982 
   983 struct plover_vector *plover_transaction_helper_group_get_default_packages(
   984   PloverTransactionHelper *helper,const char *group,GError **error)
   985 {
   986     gboolean changed;
   987     struct comps *comps;
   988     struct comps_group *grp;
   989     struct comps_requirement *pkg;
   990     struct plover_vector *default_packages;
   991     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
   992     comps=plover_transaction_helper_get_comps(helper,error);
   993     if (!comps)
   994 	return NULL;
   995     grp=plover_comps_lookup_group(comps,group);
   996     if (!grp)
   997     {
   998 	g_set_error(error,PLOVER_GENERAL_ERROR,
   999 	  PLOVER_GENERAL_ERROR_FAILED,"%s: group not found",group);
  1000 	return NULL;
  1001     }
  1002     default_packages=plover_vector_new();
  1003     do
  1004     {
  1005 	changed=FALSE;
  1006 	for(pkg=grp->packages;pkg;pkg=pkg->next)
  1007 	{
  1008 	    if (plover_vector_contains(default_packages,pkg->name))
  1009 		continue;
  1010 	    if (pkg->type==COMPS_REQUIREMENT_DEFAULT ||
  1011 	      pkg->type==COMPS_REQUIREMENT_MANDATORY ||
  1012 	      pkg->type==COMPS_REQUIREMENT_CONDITIONAL && pkg->requires &&
  1013 	      plover_vector_contains(default_packages,pkg->requires))
  1014 	    {
  1015 		changed=TRUE;
  1016 		plover_vector_append(default_packages,pkg->name);
  1017 	    }
  1018 	}
  1019     } while(changed);
  1020     return default_packages;
  1021 }
  1022 
  1023 /*
  1024  * Returns TRUE if there is work to be done or FALSE if the packages are
  1025  * already installed or on error.
  1026  */
  1027 gboolean
  1028   plover_transaction_helper_install_packages(PloverTransactionHelper *helper,
  1029   struct plover_vector *packages,GError **error)
  1030 {
  1031     gboolean retval;
  1032     PloverTransaction *transaction;
  1033     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
  1034     g_return_val_if_fail(packages != NULL,FALSE);
  1035     if (!packages->len)
  1036     {
  1037 	g_set_error(error,PLOVER_GENERAL_ERROR,
  1038 	  PLOVER_GENERAL_ERROR_NO_WORK,"No packages listed to be installed");
  1039 	return FALSE;
  1040     }
  1041     transaction=plover_transaction_helper_new_transaction(helper,error);
  1042     if (!transaction)
  1043 	return FALSE;
  1044     if (!plover_transaction_install(transaction,packages->strings,error))
  1045     {
  1046 	g_object_unref(transaction);
  1047 	return FALSE;
  1048     }
  1049     retval=plover_transaction_helper_add_transaction(helper,transaction,
  1050       packages,PLOVER_TRANSACTION_HELPER_REPORT_INSTALL,error);
  1051     g_object_unref(transaction);
  1052     return retval;
  1053 }
  1054 
  1055 /*
  1056  * Returns TRUE if there is work to be done or FALSE if the group is
  1057  * already installed or on error.
  1058  */
  1059 gboolean
  1060   plover_transaction_helper_install_group(PloverTransactionHelper *helper,
  1061   const char *group,GError **error)
  1062 {
  1063     gboolean retval;
  1064     struct plover_vector *selected_packages;
  1065     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
  1066     selected_packages=plover_transaction_helper_group_get_default_packages(
  1067       helper,group,error);
  1068     if (!selected_packages)
  1069 	return FALSE;
  1070     if (!selected_packages->len)
  1071     {
  1072 	g_set_error(error,PLOVER_GENERAL_ERROR,
  1073 	  PLOVER_GENERAL_ERROR_FAILED,"%s: no default packages",group);
  1074 	plover_vector_free(selected_packages);
  1075 	return FALSE;
  1076     }
  1077     retval=plover_transaction_helper_install_packages(helper,selected_packages,
  1078       error);
  1079     plover_vector_free(selected_packages);
  1080     return retval;
  1081 }
  1082 
  1083 /*
  1084  * Returns TRUE if there is work to be done or FALSE if the group is
  1085  * not installed or on error.
  1086  */
  1087 gboolean plover_transaction_helper_remove_group(PloverTransactionHelper *helper,
  1088   const char *group,GError **error)
  1089 {
  1090     gboolean retval;
  1091     struct plover_vector *selected_packages;
  1092     PloverTransaction *transaction;
  1093     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
  1094     g_return_val_if_fail(plover_transaction_helper_get_installed(helper) != NULL,FALSE);
  1095     selected_packages=plover_transaction_helper_group_get_default_packages(
  1096       helper,group,error);
  1097     if (!selected_packages)
  1098 	return FALSE;
  1099     if (!selected_packages->len)
  1100     {
  1101 	g_set_error(error,PLOVER_GENERAL_ERROR,
  1102 	  PLOVER_GENERAL_ERROR_FAILED,"%s: no default packages",group);
  1103 	plover_vector_free(selected_packages);
  1104 	return FALSE;
  1105     }
  1106     transaction=plover_transaction_new();
  1107     plover_transaction_set_installed(transaction,helper->installed);
  1108     if (!plover_transaction_remove(transaction,selected_packages->strings,
  1109       error))
  1110     {
  1111 	plover_vector_free(selected_packages);
  1112 	g_object_unref(transaction);
  1113 	return FALSE;
  1114     }
  1115     retval=plover_transaction_helper_add_transaction(helper,transaction,
  1116       NULL,PLOVER_TRANSACTION_HELPER_REPORT_REMOVE,error);
  1117     g_object_unref(transaction);
  1118     plover_vector_free(selected_packages);
  1119     return retval;
  1120 }
  1121 
  1122 /*
  1123  * Returns TRUE if there is work to be done or FALSE if all updates have
  1124  * already been applied or on error.
  1125  */
  1126 gboolean plover_transaction_helper_update(PloverTransactionHelper *helper,
  1127   GError **error)
  1128 {
  1129     gboolean retval;
  1130     PloverTransaction *transaction;
  1131     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
  1132     transaction=plover_transaction_helper_new_transaction(helper,error);
  1133     if (!transaction)
  1134 	return FALSE;
  1135     if (!plover_transaction_update(transaction,NULL,error))
  1136     {
  1137 	g_object_unref(transaction);
  1138 	return FALSE;
  1139     }
  1140     retval=plover_transaction_helper_add_transaction(helper,transaction,
  1141       NULL,PLOVER_TRANSACTION_HELPER_REPORT_UPDATE,error);
  1142     g_object_unref(transaction);
  1143     return retval;
  1144 }
  1145 
  1146 gboolean plover_transaction_helper_get_visible(PloverTransactionHelper *helper)
  1147 {
  1148     g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
  1149     if (helper->error_dialog)
  1150 	return TRUE;
  1151     else if (!helper->assistant)
  1152 	return FALSE;
  1153     else
  1154 	return gtk_widget_get_visible(GTK_WIDGET(helper->assistant));
  1155 }
  1156 
  1157 void plover_transaction_helper_present(PloverTransactionHelper *helper)
  1158 {
  1159     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
  1160     if (helper->error_dialog)
  1161 	gtk_window_present(GTK_WINDOW(helper->error_dialog));
  1162     else if (helper->assistant)
  1163 	gtk_window_present(GTK_WINDOW(helper->assistant));
  1164 }
  1165 
  1166 static void
  1167   plover_transaction_helper_error_dialog_response(GtkDialog *error_dialog,
  1168   int response_id,PloverTransactionHelper *helper)
  1169 {
  1170     g_signal_handlers_disconnect_by_data(error_dialog,helper);
  1171     if ((GtkWidget *)error_dialog==helper->error_dialog)
  1172     {
  1173 	gtk_widget_destroy(helper->error_dialog);
  1174 	helper->error_dialog=NULL;
  1175 	if (helper->assistant)
  1176 	{
  1177 	    gtk_widget_hide(GTK_WIDGET(helper->assistant));
  1178 	    gtk_assistant_set_current_page(helper->assistant,0);
  1179 	}
  1180 	g_signal_emit(helper,signals[CLOSE],0);
  1181     }
  1182 }
  1183 
  1184 const char *plover_transaction_helper_get_error(PloverTransactionHelper *helper,
  1185   const GError **error)
  1186 {
  1187     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
  1188     if (!helper->error_dialog)
  1189 	return NULL;
  1190     if (error)
  1191 	*error=helper->error;
  1192     return helper->error_primary_text;
  1193 }
  1194 
  1195 void plover_transaction_helper_set_error(PloverTransactionHelper *helper,
  1196   const GError *error,const char *primary_text)
  1197 {
  1198     GtkMessageType type;
  1199     GtkWindow *window;
  1200     g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
  1201     g_return_if_fail(error != NULL);
  1202     g_return_if_fail(primary_text != NULL);
  1203     if (helper->pulse_handler)
  1204     {
  1205 	g_source_remove(helper->pulse_handler);
  1206 	helper->pulse_handler=0;
  1207     }
  1208     if (helper->error_dialog)
  1209     {
  1210 	gtk_widget_destroy(helper->error_dialog);
  1211 	helper->error_dialog=NULL;
  1212     }
  1213     g_free(helper->error_primary_text);
  1214     helper->error_primary_text=g_strdup(primary_text);
  1215     g_clear_error(&helper->error);
  1216     helper->error=g_error_copy(error);
  1217     if (g_error_matches(error,PLOVER_GENERAL_ERROR,
  1218       PLOVER_GENERAL_ERROR_NO_WORK))
  1219 	type=GTK_MESSAGE_INFO;
  1220     else
  1221 	type=GTK_MESSAGE_ERROR;
  1222     if (helper->assistant)
  1223 	window=GTK_WINDOW(helper->assistant);
  1224     else
  1225     	window=NULL;
  1226     helper->error_dialog=gtk_message_dialog_new(window,
  1227       GTK_DIALOG_DESTROY_WITH_PARENT,type,GTK_BUTTONS_CLOSE,primary_text);
  1228     gtk_message_dialog_format_secondary_text(
  1229       GTK_MESSAGE_DIALOG(helper->error_dialog),error->message);
  1230     gtk_widget_show(helper->error_dialog);
  1231     g_signal_connect(helper->error_dialog,"response",
  1232       G_CALLBACK(plover_transaction_helper_error_dialog_response),helper);
  1233 }