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