plover-gtk/transactionhelper.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Jul 16 11:07:18 2016 +0100 (2016-07-16)
changeset 61 31fb35727621
parent 50 a4f43ad0e0c8
child 75 6575679d2e8e
permissions -rw-r--r--
Support parallel installations. The idea is that for CAD screener, we want
to be able to install this on the same machine as a standard AVOT setup
(most notably for John's laptop). To allow for the possibility of a second
application that might have the same requirements, we add the concept of
vendor-specific distributions. Thus we can have one distribution for CAD
screener and one for The Next Big Thing. It doesn't seem trivial to have
both CAD screener and AVOT under the same vendor tag so we'll have to have
AVOT under "City Occupational" and CAD screener under "City Occupational Ltd"
or some such kludge.

Most of this is done although we are very short of test cases (in particular
we don't test that it's actually possible to install CAD screener in parallel
with AVOT or to update either of them once installed, which is fundamental).

We also have a lot of baggage left over, including an intercept of razor_set.
The problem that this was introduced to debug has been fixed but it looks
like there are a number of memory leaks which it might be useful to help
track down so it has been left in place for now.

There is still a lot of confusion in plover between path-based and URI-based
API. We should review the API, decide what we want and have a general clear up.

There is also confusion as to the purpose of RAZOR_ROOT (and meaning; path or
URI). This is not used at all in librazor (although it is used in razor.exe).
Ideally we shouldn't use it in plover or plover-gtk either although again, we
might want to support it or an equivalent in (some of) the various executables.

Work that would still to nice to do for CAD screener:

- uninstall (ideally as an installed program that hooks into Add/Remove programs
but even re-running the installer would be acceptable).
- xz support (smaller packages).
- repomd.xml and xml:base (would be needed for an Internet installer).
- graphical installer.
     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 }