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