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