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