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