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