Add GUI front-end to setup and update
authorJ. Ali Harlow <ali@juiblex.co.uk>
Sat, 15 Nov 2014 19:04:45 +0000 (19:04 +0000)
committerJ. Ali Harlow <ali@juiblex.co.uk>
Sat, 15 Nov 2014 19:04:45 +0000 (19:04 +0000)
61 files changed:
.gitignore
Makefile.am
app-manager/Makefile.am
app-manager/app-manager.c
app-manager/app-manager.h
app-manager/app-manager.ui
app-manager/applications.c
app-manager/fetch.c
app-manager/localmedia.c
app-manager/packagelist.c
app-manager/resources.rc.in
app-manager/setup.c [new file with mode: 0644]
app-manager/update.c [new file with mode: 0644]
configure.ac
plover-gtk/Makefile.am
plover-gtk/error.c [deleted file]
plover-gtk/error.h [deleted file]
plover-gtk/package.c [deleted file]
plover-gtk/packagefilestore.c
plover-gtk/packagefilestore.h
plover-gtk/packageset.c [deleted file]
plover-gtk/packageset.h [deleted file]
plover-gtk/packagestore.c
plover-gtk/packagestore.h
plover-gtk/software-installation.ui [new file with mode: 0644]
plover-gtk/stockicons.c [new file with mode: 0644]
plover-gtk/stockicons.h [new file with mode: 0644]
plover-gtk/transactionhelper.c [new file with mode: 0644]
plover-gtk/transactionhelper.h [new file with mode: 0644]
plover/Makefile.am
plover/import-yum.c
plover/log.c [new file with mode: 0644]
plover/package.c [new file with mode: 0644]
plover/package.h [moved from plover-gtk/package.h with 73% similarity]
plover/packageset.c [new file with mode: 0644]
plover/packageset.h [new file with mode: 0644]
plover/plover.h
plover/plover.pc.in
plover/razor.c
plover/repository.c [new file with mode: 0644]
plover/repository.h [new file with mode: 0644]
plover/transaction.c [new file with mode: 0644]
plover/transaction.h [new file with mode: 0644]
plover/util.c
plover/vector.c [new file with mode: 0644]
pre-inst/Makefile.am [new file with mode: 0644]
pre-inst/icon16.png [new file with mode: 0644]
pre-inst/icon22.png [new file with mode: 0644]
pre-inst/icon32.png [new file with mode: 0644]
pre-inst/manifest.xml.in [new file with mode: 0644]
pre-inst/pre-inst.c [new file with mode: 0644]
pre-inst/resource.h [new file with mode: 0644]
pre-inst/resources.rc.in [new file with mode: 0644]
setup/Makefile.am
setup/resources.rc.in
setup/setup.c
setup/setup.js.in [new file with mode: 0644]
update/Makefile.am
update/resources.rc.in
update/update.c
update/update.js.in [new file with mode: 0644]

index ecfd2e0..e1dbd73 100644 (file)
@@ -13,10 +13,26 @@ stamp-h1
 *.o
 *.lo
 *.la
+*.ico
+*.exe
+*.exe.manifest
 plover/plover.pc
 plover-gtk/plover-gtk.pc
 setup/resources.rc
 setup/setup
+setup/setup.js
+setup/icon*.pnm
 update/resources.rc
 update/update
+update/update.js
+update/icon*.pnm
+pre-inst/resources.rc
+pre-inst/pre-inst
+pre-inst/icon*.pnm
 app-manager/resources.rc
+app-manager/fetch
+app-manager/app-manager
+app-manager/plover-applications*.pgm
+app-manager/plover-applications*.pnm
+app-manager/24x24
+app-manager/48x48
index c3475ed..e82944d 100644 (file)
@@ -1 +1 @@
-SUBDIRS=plover setup update plover-gtk app-manager
+SUBDIRS=plover setup update pre-inst plover-gtk app-manager
index 4007415..591dd9b 100644 (file)
@@ -3,12 +3,15 @@ LDADD=../plover/libplover.la ../plover-gtk/libplover-gtk.la $(GUI_LIBS)
 
 bin_PROGRAMS=app-manager fetch
 app_manager_SOURCES=app-manager.c app-manager.h packagelist.c applications.c \
-       localmedia.c localmedia.h
+       localmedia.c localmedia.h setup.c update.c
 fetch_SOURCES=fetch.c
 fetch_LDADD=$(LDADD) $(FETCH_LIBS)
 if HAVE_WINDRES
 app_manager_SOURCES+=resources.rc app-manager.exe.manifest
 endif
+if PLOVER_MINGW
+app_manager_LDFLAGS=-mwindows
+endif
 uidir=$(pkgdatadir)
 ui_DATA=app-manager.ui
 desktopdir=$(datadir)/applications
@@ -20,34 +23,39 @@ smallicon_DATA=24x24/plover-applications.png
 bigicondir=$(datadir)/icons/hicolor/48x48/apps
 bigicon_DATA=48x48/plover-applications.png
 
+# PLOVER_V_SKIP: Don't echo anything for this command if V=0
+PLOVER_V_SKIP = $(PLOVER_V_SKIP_$(V))
+PLOVER_V_SKIP_ = $(PLOVER_V_SKIP_$(AM_DEFAULT_VERBOSITY))
+PLOVER_V_SKIP_0 = @
+
 .rc.$(OBJEXT):
-       $(WINDRES) $< $@
+       $(AM_V_GEN)$(WINDRES) $< $@
 
 resources.$(OBJEXT):   app-manager.ico app-manager.exe.manifest
 
 plover-applications%.pnm:      plover-applications.svg
-       rsvg -w $* -h $* -f png $< temp.png
-       pngtopnm temp.png | pnmquant 256 > $@
-       $(RM) temp.png
+       $(PLOVER_V_SKIP)rsvg -w $* -h $* -f png $< temp.png
+       $(AM_V_GEN)pngtopnm temp.png | pnmquant -quiet 256 > $@
+       $(PLOVER_V_SKIP)$(RM) temp.png
 
 plover-applications%.pgm:      plover-applications.svg
-       rsvg -w $* -h $* -f png $< temp.png
-       pngtopnm -alpha temp.png > $@
-       $(RM) temp.png
+       $(PLOVER_V_SKIP)rsvg -w $* -h $* -f png $< temp.png
+       $(AM_V_GEN)pngtopnm -alpha temp.png > $@
+       $(PLOVER_V_SKIP)$(RM) temp.png
 
 24x24/plover-applications.png: plover-applications.svg
-       mkdir -p 24x24
-       rsvg -w 24 -h 24 -f png $< $@
+       $(PLOVER_V_SKIP)mkdir -p 24x24
+       $(AM_V_GEN)rsvg -w 24 -h 24 -f png $< $@
 
 48x48/plover-applications.png: plover-applications.svg
-       mkdir -p 48x48
-       rsvg -w 48 -h 48 -f png $< $@
+       $(PLOVER_V_SKIP)mkdir -p 48x48
+       $(AM_V_GEN)rsvg -w 48 -h 48 -f png $< $@
 
 app-manager.ico:       plover-applications16.pnm plover-applications16.pgm \
                plover-applications22.pnm plover-applications22.pgm \
                plover-applications32.pnm plover-applications32.pgm \
                plover-applications46.pnm plover-applications46.pgm
-       ppmtowinicon -andpgms -output=$@ $^
+       $(AM_V_GEN)ppmtowinicon -andpgms -output=$@ $^
 
 clean-local:
        -rm -rf 24x24 48x48
index 652e71e..479bd7c 100644 (file)
 #include "config.h"
 #include <stdlib.h>
 #include <string.h>
+#ifdef WIN32
+#include <windows.h>
+#endif /* WIN32 */
+#include <lua.h>
 #include <glib.h>
 #include <gio/gio.h>
 #include <gtk/gtk.h>
-#include <plover-gtk/packageset.h>
+#include <whelk/whelk.h>
+#include <plover/packageset.h>
+#include <plover-gtk/stockicons.h>
 #include "app-manager.h"
 #include "localmedia.h"
 
+LUALIB_API int luaopen_posix(lua_State *L);
+
 #define LOGO_NAME      "plover-applications"
 
 GtkBuilder *ui;
@@ -103,6 +111,7 @@ static void show_uri(GtkLinkButton *button,const gchar *uri,gpointer data)
     }
 }
 
+#if 0
 /* Checks whether a loader for SVG files has been registered
  * with GdkPixbuf.
  */
@@ -191,21 +200,61 @@ static void install_icons(void)
     icon_set=gtk_icon_factory_lookup_default(LOGO_NAME);
     gtk_window_set_default_icon_name(LOGO_NAME);
 }
+#endif
+
+static void install_icons(void)
+{
+    GtkIconSet *icon_set;
+    plover_icons_add_to_stock("apps",LOGO_NAME);
+    icon_set=gtk_icon_factory_lookup_default(LOGO_NAME);
+    gtk_window_set_default_icon_name(LOGO_NAME);
+}
 
 int main(int argc,char **argv)
 {
     GError *err=0;
     GtkWidget *w;
     gchar *s,*contents;
+    gchar *setup_base=NULL,*update_base=NULL;
     gsize len;
     PloverPackageSet *set;
+    GSList *objects,*lnk;
+    gboolean started;
     GOptionEntry options[]={
+       {"setup",0,0,G_OPTION_ARG_FILENAME,&setup_base,
+         "Setup from installation media","path"},
+       {"update",0,0,G_OPTION_ARG_FILENAME,&update_base,
+         "Update from upgrade media","path"},
        {NULL}
     };
+#ifdef WIN32
+    /*
+     * app-manager is normally a GUI application, but rpm scripts may well
+     * call console applications and it looks ugly if console windows keep
+     * popping up. Avoid this by allocating our own console and hiding it.
+     * Note:
+     * - If app-manager is a console application (typically for debugging),
+     *    then skip this step.
+     *  - Call ShowWindow twice to negate special handling on first call.
+     */
+    if (!GetConsoleWindow())
+    {
+       AllocConsole();
+       ShowWindow(GetConsoleWindow(),SW_HIDE);
+       ShowWindow(GetConsoleWindow(),SW_HIDE);
+    }
+#endif
+    razor_set_lua_loader("posix",luaopen_posix);
+    razor_set_lua_loader("whelk",luaopen_whelk);
     if (!gtk_init_with_args(&argc,&argv,NULL,options,NULL,&err))
     {
-       g_printerr("%s",err->message);
-       exit(0);
+       g_printerr("%s\n",err->message);
+       exit(1);
+    }
+    if (setup_base && update_base)
+    {
+       g_printerr("--setup and --update are mutually exclusive\n");
+       exit(1);
     }
 #ifdef WIN32
     prefix=g_win32_get_package_installation_directory_of_module(NULL);
@@ -242,23 +291,45 @@ int main(int argc,char **argv)
     gtk_builder_connect_signals(ui,NULL);
     gtk_link_button_set_uri_hook(show_uri,NULL,NULL);
     installed=GTK_TREE_MODEL(plover_package_store_new());
-    set=plover_package_set_new_from_installed("",NULL);
-    if (set)
+    set=plover_package_set_new();
+    (void)plover_package_set_open(set,"",TRUE,NULL);
+    plover_package_store_add_set(PLOVER_PACKAGE_STORE(installed),set);
+    if (plover_package_set_get_no_details(set))
     {
-       plover_package_store_add_set(PLOVER_PACKAGE_STORE(installed),set);
-       if (plover_package_set_get_no_details(set))
-       {
-           w=GTK_WIDGET(gtk_builder_get_object(ui,"ViewFiles"));
-           gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w),TRUE);
-       }
+       w=GTK_WIDGET(gtk_builder_get_object(ui,"ViewFiles"));
+       gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w),TRUE);
     }
     applications=plover_applications_model_new(installed);
     set_package_model(applications);
-    gtk_main();
-    g_object_unref(ui);
+    if (setup_base)
+       started=setup(set,setup_base);
+    else if (update_base)
+       started=update(set,update_base);
+    else
+    {
+       w=GTK_WIDGET(gtk_builder_get_object(ui,"MainWindow"));
+       gtk_widget_show(w);
+       started=TRUE;
+    }
+    if (started)
+       gtk_main();
+    g_clear_object(&set);
+    objects=gtk_builder_get_objects(ui);
+    for(lnk=objects;lnk;lnk=lnk->next)
+       if (GTK_IS_WIDGET(lnk->data) &&
+         gtk_widget_is_toplevel(GTK_WIDGET(lnk->data)))
+           gtk_widget_destroy(GTK_WIDGET(lnk->data));
+    g_slist_free(objects);
+    g_clear_object(&ui);
+    g_clear_object(&installed);
+    g_clear_object(&applications);
+    g_clear_object(&location);
+    g_clear_object(&local_media);
     if (relocations)
        razor_relocations_destroy(relocations);
     g_free(prefix);
+    g_free(setup_base);
+    g_free(update_base);
     exit(0);
 }
 
@@ -323,7 +394,7 @@ G_MODULE_EXPORT void on_open_location(GtkWidget *widget)
     {
        show_busy_cursor(TRUE);
        path=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
-       set=plover_package_set_new_from_repository(path,relocations,&err);
+       set=plover_package_set_new_from_yum(path,relocations,&err);
        if (set)
        {
            if (!location)
@@ -399,7 +470,8 @@ G_MODULE_EXPORT void on_help_about(GtkWidget *widget)
     GtkWidget *w=GTK_WIDGET(gtk_builder_get_object(ui,"MainWindow"));
     gtk_show_about_dialog(GTK_WINDOW(w),"name",PACKAGE_NAME,
       "version",PACKAGE_VERSION,"comments","Application Manager",
-      "copyright","Copyright © 2010 J. Ali Harlow","logo-icon-name",LOGO_NAME,
+      "copyright","Copyright © 2010, 2014 J. Ali Harlow",
+      "logo-icon-name",LOGO_NAME,
       NULL);
 }
 
index 63f077e..9e61cce 100644 (file)
@@ -1,9 +1,11 @@
 #include <razor.h>
 #include <gtk/gtk.h>
-#include <plover-gtk/package.h>
+#include <plover/package.h>
 
 extern GtkBuilder *ui;
 extern struct razor_relocations *relocations;
 GtkTreeModel *plover_applications_model_new(GtkTreeModel *installed);
 void set_package_model(GtkTreeModel *model);
 PloverPackage *get_active_package(void);
+gboolean setup(PloverPackageSet *current,const char *base);
+gboolean update(PloverPackageSet *current,const char *base);
index be45944..f5b7b74 100644 (file)
@@ -1,9 +1,8 @@
 <?xml version="1.0"?>
 <interface>
   <requires lib="gtk+" version="2.16"/>
-  <!-- interface-naming-policy toplevel-contextual -->
+  <!-- interface-naming-policy project-wide -->
   <object class="GtkWindow" id="MainWindow">
-    <property name="visible">True</property>
     <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
     <property name="title" translatable="yes">Application Manager</property>
     <property name="default_width">600</property>
               </packing>
             </child>
             <child>
-              <object class="GtkAlignment" id="alignment2">
+              <object class="GtkAlignment" id="alignment3">
                 <property name="visible">True</property>
                 <property name="left_padding">6</property>
                 <property name="right_padding">6</property>
index ceabefc..1a25680 100644 (file)
@@ -25,6 +25,7 @@
 #include <glib.h>
 #include <gtk/gtk.h>
 #include <plover-gtk/packagestore.h>
+#include <plover-gtk/packagefilestore.h>
 #include "app-manager.h"
 
 #ifdef WIN32
@@ -63,7 +64,8 @@ static gboolean plover_applications_visible_func(GtkTreeModel *model,
     gtk_tree_model_get(model,iter,PLOVER_PACKAGE_STORE_OBJ_COLUMN,&package,-1);
     if (package)
     {
-       file_store=GTK_TREE_MODEL(plover_package_get_file_store(package));
+       file_store=
+         GTK_TREE_MODEL(plover_package_file_store_new_from_package(package));
        if (gtk_tree_model_get_iter_first(file_store,&fi))
        {
            do
@@ -94,6 +96,7 @@ static gboolean plover_applications_visible_func(GtkTreeModel *model,
                g_free(name);
            } while(!visible && gtk_tree_model_iter_next(file_store,&fi));
        }
+       g_object_unref(file_store);
     }
     g_object_unref(package);
     return visible;
index 8980899..41ddcf3 100644 (file)
@@ -158,7 +158,7 @@ gchar *find_prefixed_file(const char *file)
     const char *name;
     char *install_root;
     struct razor_set *set;
-    struct razor_atomic *atomic;
+    struct razor_error *error=NULL;
     struct razor_package *package;
     struct razor_package_iterator *pi;
     struct razor_file_iterator *fi;
@@ -167,8 +167,7 @@ gchar *find_prefixed_file(const char *file)
     install_root=getenv("RAZOR_ROOT");
     if (!install_root)
        install_root="";
-    atomic=razor_atomic_open("Query packages");
-    set=razor_root_open_read_only(install_root,atomic);
+    set=razor_root_open_read_only(install_root,&error);
     if (set)
     {
        pi=razor_package_iterator_create(set);
@@ -192,7 +191,8 @@ gchar *find_prefixed_file(const char *file)
        razor_package_iterator_destroy(pi);
        razor_set_unref(set);
     }
-    razor_atomic_destroy(atomic);
+    if (error)
+       razor_error_free(error);
     return retval;
 }
 
index b4f745e..8a52b59 100644 (file)
@@ -90,7 +90,7 @@ static void local_media_scan_mount(PloverLocalMediaStore *store,GMount *mount)
 #endif
     if (path)
     {
-       set=plover_package_set_new_from_repository(path,relocations,NULL);
+       set=plover_package_set_new_from_yum(path,relocations,NULL);
        if (set)
        {
            g_object_set_data(G_OBJECT(mount),"plover-local-media-set",set);
index 988c7e3..d4041cd 100644 (file)
@@ -20,8 +20,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <gtk/gtk.h>
-#include <plover-gtk/package.h>
-#include <plover-gtk/packageset.h>
+#include <plover/package.h>
+#include <plover/packageset.h>
 #include <plover-gtk/packagestore.h>
 #include <plover-gtk/packagefilestore.h>
 #include "app-manager.h"
@@ -36,6 +36,7 @@ void package_present(PloverPackage *package)
     const char *text,*t;
     GtkWidget *w;
     GtkTextBuffer *buf;
+    PloverPackageFileStore *store;
     buf=GTK_TEXT_BUFFER(gtk_builder_get_object(ui,"description"));
     if (package)
     {
@@ -97,8 +98,9 @@ void package_present(PloverPackage *package)
        {
            gtk_widget_hide(w);
            w=GTK_WIDGET(gtk_builder_get_object(ui,"Files"));
-           gtk_tree_view_set_model(GTK_TREE_VIEW(w),
-             GTK_TREE_MODEL(plover_package_get_file_store(package)));
+           store=plover_package_file_store_new_from_package(package);
+           gtk_tree_view_set_model(GTK_TREE_VIEW(w),GTK_TREE_MODEL(store));
+           g_object_unref(store);
        }
        else
        {
index 8169b6e..3781823 100644 (file)
@@ -1,6 +1,8 @@
 #include <winver.h>
 #include <winuser.h>
 
+#pragma code_page(65001)
+
 MAINICON ICON "app-manager.ico"
 
 VS_VERSION_INFO VERSIONINFO
@@ -31,3 +33,5 @@ VS_VERSION_INFO VERSIONINFO
            VALUE "Translation",0x809,0x4B0
        }
     }
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "app-manager.exe.manifest"
diff --git a/app-manager/setup.c b/app-manager/setup.c
new file mode 100644 (file)
index 0000000..047a2ea
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <plover/plover.h>
+#include <plover/transaction.h>
+#include <plover/packageset.h>
+#include <plover-gtk/transactionhelper.h>
+#include "app-manager.h"
+
+gboolean setup(PloverPackageSet *installed,const char *base)
+{
+    gchar *s;
+    const char *prefix;
+    GError *error=NULL;
+    static PloverTransactionHelper *helper=NULL;
+    if (!helper)
+    {
+       helper=plover_transaction_helper_new(ui);
+       plover_transaction_helper_set_installed(helper,installed);
+       plover_transaction_helper_set_base(helper,base);
+       prefix=plover_transaction_helper_get_prefix(helper,&error);
+       if (error)
+           g_clear_error(&error);
+       else
+       {
+           s=g_strconcat(prefix?prefix:"","/var/log/setup",NULL);
+           plover_log_open(s);
+           g_free(s);
+       }
+       plover_transaction_helper_set_check_vendor(helper,TRUE);
+       g_signal_connect(helper,"close",G_CALLBACK(gtk_main_quit),NULL);
+    }
+    if (!plover_transaction_helper_get_visible(helper))
+    {
+       if (!plover_transaction_helper_install_group(helper,"base",&error))
+       {
+           if (g_error_matches(error,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_NO_WORK))
+           {
+               g_error_free(error);
+               error=g_error_new_literal(PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_NO_WORK,
+                 "All packages already installed");
+               plover_transaction_helper_set_error(helper,error,
+                 "Software installation");
+           }
+           else if (g_error_matches(error,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET))
+           {
+               g_error_free(error);
+               error=g_error_new_literal(PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET,
+                 "Software cannot be installed because of missing updates. "
+                 "Installing all updates first should resolve this problem");
+               plover_transaction_helper_set_error(helper,error,
+                 "Software installation failed");
+           }
+           else
+               plover_transaction_helper_set_error(helper,error,
+                 "Software installation failed");
+           g_error_free(error);
+       }
+    }
+    plover_transaction_helper_present(helper);
+    return TRUE;
+}
diff --git a/app-manager/update.c b/app-manager/update.c
new file mode 100644 (file)
index 0000000..0a647c8
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <plover/plover.h>
+#include <plover/transaction.h>
+#include <plover/packageset.h>
+#include <plover-gtk/transactionhelper.h>
+#include "app-manager.h"
+
+gboolean update(PloverPackageSet *installed,const char *base)
+{
+    gchar *s;
+    const char *prefix;
+    GError *error=NULL;
+    static PloverTransactionHelper *helper=NULL;
+    if (!helper)
+    {
+       helper=plover_transaction_helper_new(ui);
+       plover_transaction_helper_set_installed(helper,installed);
+       plover_transaction_helper_set_base(helper,base);
+       prefix=plover_transaction_helper_get_prefix(helper,&error);
+       if (error)
+           g_clear_error(&error);
+       else
+       {
+           s=g_strconcat(prefix?prefix:"","/var/log/update",NULL);
+           plover_log_open(s);
+           g_free(s);
+       }
+       plover_transaction_helper_set_check_vendor(helper,TRUE);
+       g_signal_connect(helper,"close",G_CALLBACK(gtk_main_quit),NULL);
+    }
+    if (!plover_transaction_helper_get_visible(helper))
+    {
+       if (!plover_transaction_helper_update(helper,&error))
+       {
+           if (g_error_matches(error,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_NO_WORK))
+           {
+               g_error_free(error);
+               error=g_error_new_literal(PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_NO_WORK,
+                 "All relevant updates already applied");
+               plover_transaction_helper_set_error(helper,error,
+                 "Software update");
+           }
+           else if (g_error_matches(error,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET))
+           {
+               g_error_free(error);
+               error=g_error_new_literal(PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET,
+                 "Updates cannot be applied because some earlier updates are "
+                 "missing. Installing updates in sequence should resolve this "
+                 "problem");
+               plover_transaction_helper_set_error(helper,error,
+                 "Software update failed");
+           }
+           else
+               plover_transaction_helper_set_error(helper,error,
+                 "Software update failed");
+           g_error_free(error);
+       }
+    }
+    plover_transaction_helper_present(helper);
+    return TRUE;
+}
index 0a76556..71c61a8 100644 (file)
@@ -1,7 +1,7 @@
 #                                               -*- Autoconf -*-
 # Process this file with autoconf to produce a configure script.
 
-AC_INIT([plover],[0.4.3],[ali@juiblex.co.uk])
+AC_INIT([plover],[0.4.50],[ali@juiblex.co.uk])
 AC_PREREQ(2.59)
 AC_CONFIG_AUX_DIR([config])
 AC_CONFIG_SRCDIR([plover/plover.h])
@@ -15,14 +15,18 @@ setup/Makefile
 setup/resources.rc
 update/Makefile
 update/resources.rc
+pre-inst/Makefile
+pre-inst/resources.rc
 app-manager/Makefile
 app-manager/resources.rc
 ])
 PLOVER_MSWIN_MANIFEST([setup/setup.exe.manifest:setup/manifest.xml.in
 update/update.exe.manifest:update/manifest.xml.in
+pre-inst/pre-inst.exe.manifest:pre-inst/manifest.xml.in
 app-manager/app-manager.exe.manifest:app-manager/manifest.xml.in
 ])
 AM_INIT_AUTOMAKE(no-define)
+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
 case $VERSION in
   *.*.*)
     AC_SUBST(PLOVER_MAJOR_VERSION,[[`echo $VERSION | sed 's/\..*//'`]])
@@ -44,6 +48,15 @@ esac
 AC_CANONICAL_HOST
 AC_SUBST(HOST_OS,$host_os)
 AC_SUBST(HOST_CPU,$host_cpu)
+case $host_os in
+    mingw*)
+       host_mingw="yes"
+       ;;
+    *)
+       host_mingw=""
+       ;;
+esac
+AM_CONDITIONAL(PLOVER_MINGW,[test -n "$host_mingw"])
 
 # libtool versioning for libplover. For a release one of the following
 # must apply:
@@ -54,7 +67,7 @@ AC_SUBST(HOST_CPU,$host_cpu)
 #   increment CURRENT and set AGE and REVISION to 0.
 # - If the interface is the same as the previous version, increment REVISION.
 #
-lt_current=2
+lt_current=3
 lt_revision=0
 lt_age=0
 LIBPLOVER_LT_VERSION_INFO="$lt_current:$lt_revision:$lt_age"
@@ -93,32 +106,44 @@ AC_CHECK_HEADERS([winhttp.h],[],[],
 ##################################################
 # Checks for libraries.
 ##################################################
+PKG_CHECK_MODULES(WHELK,[whelk])
 PKG_CHECK_MODULES(RAZOR,[razor >= 0.5.4],[:],[RAZOR_LIBS=-lrazor])
 PKG_CHECK_MODULES(EXPAT,[expat],[:],[EXPAT_LIBS=-lexpat])
 PKG_CHECK_MODULES(ZLIB,[zlib],[:],[ZLIB_LIBS=-lz])
 PKG_CHECK_MODULES(GIO,[gio-2.0])
 PKG_CHECK_MODULES(GTK,[gtk+-2.0])
 PKG_CHECK_MODULES(GMODULE_EXPORT,[gmodule-export-2.0])
-LIBPLOVER_CFLAGS="$RAZOR_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS"
-LIBPLOVER_LIBS="$RAZOR_LIBS $EXPAT_LIBS $ZLIB_LIBS"
+LIBPLOVER_CFLAGS="$RAZOR_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $GIO_CFLAGS"
+LIBPLOVER_LIBS="$RAZOR_LIBS $EXPAT_LIBS $ZLIB_LIBS $GIO_LIBS"
 AC_SUBST(LIBPLOVER_CFLAGS)
 AC_SUBST(LIBPLOVER_LIBS)
 PLOVER_GTK_CFLAGS="$GTK_CFLAGS $RAZOR_CFLAGS"
 PLOVER_GTK_LIBS="$GTK_LIBS $RAZOR_LIBS"
 AC_SUBST(PLOVER_GTK_CFLAGS)
 AC_SUBST(PLOVER_GTK_LIBS)
-GUI_CFLAGS="$GMODULE_EXPORT_CFLAGS $GIO_CFLAGS $PLOVER_GTK_CFLAGS $LIBPLOVER_CFLAGS"
-GUI_LIBS="$GMODULE_EXPORT_LIBS $GIO_LIBS $PLOVER_GTK_LIBS $LIBPLOVER_LIBS"
+save_LIBS="$LIBS"
+AC_SEARCH_LIBS([crypt],[crypt])
+GUI_CFLAGS="$GMODULE_EXPORT_CFLAGS $WHELK_CFLAGS $PLOVER_GTK_CFLAGS \
+  $LIBPLOVER_CFLAGS"
+GUI_LIBS="-llua-posix $GMODULE_EXPORT_LIBS $WHELK_LIBS $PLOVER_GTK_LIBS \
+  $LIBPLOVER_LIBS $LIBS"
+LIBS="$save_LIBS"
 AC_SUBST(GUI_CFLAGS)
 AC_SUBST(GUI_LIBS)
 save_PKG_CONFIG="$PKG_CONFIG"
 PKG_CONFIG="$PKG_CONFIG --static"
-PKG_CHECK_MODULES(SETUP,[whelk])
+PKG_CHECK_MODULES(SETUP,[whelk razor >= 0.5.4 expat zlib gio-2.0])
+if test -n "$host_mingw"; then
+    # Hack: -liconv is required for mingw. This probably stems from our use of
+    # libiconv rather than win-iconv that Fedora uses, but should be addressed
+    # somewhere in the stack below us.
+    SETUP_LIBS="$SETUP_LIBS -liconv"
+fi
 PKG_CONFIG="$save_PKG_CONFIG"
 save_LIBS="$LIBS"
 AC_SEARCH_LIBS([crypt],[crypt])
-SETUP_LIBS="-llua-posix $SETUP_LIBS $RAZOR_LIBS $LIBS"
-SETUP_CFLAGS="$SETUP_CFLAGS $RAZOR_CFLAGS"
+SETUP_LIBS="-llua-posix $SETUP_LIBS $LIBS"
+SETUP_CFLAGS="$SETUP_CFLAGS"
 AC_SUBST(SETUP_LIBS)
 AC_SUBST(SETUP_CFLAGS)
 LIBS="$save_LIBS"
@@ -139,7 +164,7 @@ LIBS="$save_LIBS"
 ##################################################
 # Checks for library functions.
 ##################################################
-AC_CHECK_FUNCS_ONCE([fchdir])
+AC_CHECK_FUNCS_ONCE([fchdir fpathconf dirfd])
 
 ##################################################
 # Checks for processor independent files.
index f5c8f35..50025e1 100644 (file)
@@ -1,15 +1,19 @@
-AM_CFLAGS=-g $(PLOVER_GTK_CFLAGS)
+AM_CFLAGS=-g $(PLOVER_GTK_CFLAGS) -DPLOVER_DATADIR=\""$(pkgdatadir)"\"
 LIBS=../plover/libplover.la $(PLOVER_GTK_LIBS)
 INCLUDES=-I$(top_srcdir)
 AM_LDFLAGS=-no-undefined -version-info $(PLOVER_GTK_LT_VERSION_INFO)
 
-pkginclude_HEADERS=error.h package.h packageset.h packagestore.h \
-               packagefilestore.h
+uidir=$(pkgdatadir)
+ui_DATA=software-installation.ui
+
+pkginclude_HEADERS=\
+       transactionhelper.h packagestore.h packagefilestore.h stockicons.h
 
 lib_LTLIBRARIES=libplover-gtk.la
 libplover_gtk_la_SOURCES=$(pkginclude_HEADERS) \
-               error.c package.c packageset.c packagestore.c \
-               packagefilestore.c
+       transactionhelper.c packagestore.c packagefilestore.c stockicons.c
 
 pkgconfigdir=$(libdir)/pkgconfig
 pkgconfig_DATA=plover-gtk.pc
+
+EXTRA_DIST=software-installation.ui
diff --git a/plover-gtk/error.c b/plover-gtk/error.c
deleted file mode 100644 (file)
index 237d39a..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2010  J. Ali Harlow <ali@juiblex.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include <stdlib.h>
-#include <glib.h>
-
-GQuark plover_razor_error_quark(void)
-{
-    static GQuark quark=0;
-    if (!quark)
-       quark=g_quark_from_static_string("plover-razor-error-quark");
-    return quark;
-}
-
diff --git a/plover-gtk/error.h b/plover-gtk/error.h
deleted file mode 100644 (file)
index 640dd36..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef __PLOVER_ERROR_H__
-#define __PLOVER_ERROR_H__
-
-#define PLOVER_RAZOR_ERROR     plover_razor_error_quark()
-
-typedef enum {
-    PLOVER_RAZOR_ERROR_FAILED
-} PloverRazorError;
-
-GQuark plover_razor_error_quark(void);
-
-#endif /* __PLOVER_ERROR_H__ */
diff --git a/plover-gtk/package.c b/plover-gtk/package.c
deleted file mode 100644 (file)
index 96c8a73..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2010  J. Ali Harlow <ali@juiblex.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include <stdlib.h>
-#include <string.h>
-#include <glib-object.h>
-#include <gtk/gtk.h>
-#include <razor.h>
-#include "plover-gtk/package.h"
-
-G_DEFINE_TYPE(PloverPackage,plover_package,G_TYPE_OBJECT);
-
-typedef struct _PloverPackagePrivate {
-    struct razor_set *set;
-    struct razor_package *pkg;
-    PloverPackageFileStore *file_store;
-} PloverPackagePrivate;
-
-#define PLOVER_PACKAGE_GET_PRIVATE(obj)\
-                               G_TYPE_INSTANCE_GET_PRIVATE(obj,\
-                                 PLOVER_TYPE_PACKAGE,PloverPackagePrivate)
-
-enum {
-    CHANGED=0,
-    N_SIGNALS
-};
-
-static guint signals[N_SIGNALS];
-
-static void plover_package_dispose(GObject *obj)
-{
-    PloverPackagePrivate *priv=PLOVER_PACKAGE_GET_PRIVATE(obj);
-    if (priv->file_store)
-    {
-       g_object_unref(priv->file_store);
-       priv->file_store=NULL;
-    }
-    if (G_OBJECT_CLASS(plover_package_parent_class)->dispose)
-       G_OBJECT_CLASS(plover_package_parent_class)->dispose(obj);
-}
-
-static void plover_package_class_init(PloverPackageClass *klass)
-{
-    GObjectClass *oclass=G_OBJECT_CLASS(klass);
-    oclass->dispose=plover_package_dispose;
-    g_type_class_add_private(klass,sizeof(PloverPackagePrivate));
-    signals[CHANGED]=g_signal_newv("changed",
-      G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,NULL,NULL,NULL,
-      g_cclosure_marshal_VOID__VOID,G_TYPE_NONE,0,NULL);
-}
-
-static void plover_package_init(PloverPackage *package)
-{
-}
-
-PloverPackage *plover_package_new(struct razor_set *set,
-  struct razor_package *pkg)
-{
-    PloverPackage *package;
-    PloverPackagePrivate *priv;
-    package=g_object_new(PLOVER_TYPE_PACKAGE,NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    priv->set=set;
-    priv->pkg=pkg;
-    return package;
-}
-
-const char *plover_package_get_name(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *name=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_NAME,&name,
-      RAZOR_DETAIL_LAST);
-    return name;
-}
-
-const char *plover_package_get_summary(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *summary=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_SUMMARY,&summary,
-      RAZOR_DETAIL_LAST);
-    return summary;
-}
-
-const char *plover_package_get_version(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *version=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_VERSION,&version,
-      RAZOR_DETAIL_LAST);
-    return version;
-}
-
-const char *plover_package_get_license(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *license=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_LICENSE,&license,
-      RAZOR_DETAIL_LAST);
-    return license;
-}
-
-const char *plover_package_get_arch(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *arch=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_ARCH,&arch,
-      RAZOR_DETAIL_LAST);
-    return arch;
-}
-
-const char *plover_package_get_description(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *description=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_DESCRIPTION,
-      &description,RAZOR_DETAIL_LAST);
-    return description;
-}
-
-const char *plover_package_get_URL(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    const char *URL=NULL;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_URL,&URL,
-      RAZOR_DETAIL_LAST);
-    return URL;
-}
-
-GdkPixbuf *plover_package_get_icon(PloverPackage *package)
-{
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    return NULL;
-}
-
-PloverPackageFileStore *plover_package_get_file_store(PloverPackage *package)
-{
-    PloverPackagePrivate *priv;
-    struct razor_file_iterator *iter;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
-    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
-    if (!priv->file_store)
-    {
-       iter=razor_file_iterator_create(priv->set,priv->pkg,0);
-       priv->file_store=plover_package_file_store_new(iter);
-       razor_file_iterator_destroy(iter);
-    }
-    return priv->file_store;
-}
index 2b1c4e4..f890e0f 100644 (file)
@@ -257,3 +257,15 @@ PloverPackageFileStore *
     gtk_tree_path_free(path);
     return store;
 }
+
+PloverPackageFileStore *
+  plover_package_file_store_new_from_package(PloverPackage *package)
+{
+    PloverPackageFileStore *store;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    struct razor_file_iterator *iter;
+    iter=plover_package_file_iterator_create(package,FALSE);
+    store=GTK_TREE_MODEL(plover_package_file_store_new(iter));
+    razor_file_iterator_destroy(iter);
+    return store;
+}
index 65c27c6..e9778f3 100644 (file)
@@ -2,6 +2,7 @@
 #define __PLOVER_PACKAGE_FILE_STORE_H__
 
 #include <glib-object.h>
+#include <plover/package.h>
 #include <razor.h>
 
 G_BEGIN_DECLS
@@ -44,6 +45,8 @@ typedef struct _PloverPackageFileStoreClass {
 GType plover_package_file_store_get_type(void) G_GNUC_CONST;
 PloverPackageFileStore *
   plover_package_file_store_new(struct razor_file_iterator *files);
+PloverPackageFileStore *
+  plover_package_file_store_new_from_package(PloverPackage *package);
 
 G_END_DECLS
 
diff --git a/plover-gtk/packageset.c b/plover-gtk/packageset.c
deleted file mode 100644 (file)
index e310641..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2010-2012  J. Ali Harlow <ali@juiblex.co.uk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include <stdlib.h>
-#include <string.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <glib-object.h>
-#include <razor.h>
-#include "plover/plover.h"
-#include "plover-gtk/error.h"
-#include "plover-gtk/packageset.h"
-#include "plover-gtk/package.h"
-
-G_DEFINE_TYPE(PloverPackageSet,plover_package_set,G_TYPE_OBJECT);
-
-typedef struct _PloverPackageSetPrivate {
-    struct razor_root *root;
-    struct razor_set *set;
-    GSList *packages;
-    int no_details;
-} PloverPackageSetPrivate;
-
-#define PLOVER_PACKAGE_SET_GET_PRIVATE(obj)\
-                               G_TYPE_INSTANCE_GET_PRIVATE(obj,\
-                                 PLOVER_TYPE_PACKAGE_SET,\
-                                 PloverPackageSetPrivate)
-
-enum {
-    CHANGED=0,
-    N_SIGNALS
-};
-
-static guint signals[N_SIGNALS];
-
-static void plover_package_set_finalize(GObject *obj)
-{
-    PloverPackageSetPrivate *priv=PLOVER_PACKAGE_SET_GET_PRIVATE(obj);
-    if (priv->root)
-       razor_root_close(priv->root);
-    if (G_OBJECT_CLASS(plover_package_set_parent_class)->finalize)
-       G_OBJECT_CLASS(plover_package_set_parent_class)->finalize(obj);
-}
-
-static void plover_package_set_dispose(GObject *obj)
-{
-    PloverPackageSetPrivate *priv=PLOVER_PACKAGE_SET_GET_PRIVATE(obj);
-    if (priv->set)
-    {
-       razor_set_unref(priv->set);
-       priv->set=NULL;
-    }
-    if (G_OBJECT_CLASS(plover_package_set_parent_class)->dispose)
-       G_OBJECT_CLASS(plover_package_set_parent_class)->dispose(obj);
-}
-
-static void plover_package_set_class_init(PloverPackageSetClass *klass)
-{
-    GObjectClass *oclass=G_OBJECT_CLASS(klass);
-    oclass->finalize=plover_package_set_finalize;
-    oclass->dispose=plover_package_set_dispose;
-    g_type_class_add_private(klass,sizeof(PloverPackageSetPrivate));
-    signals[CHANGED]=g_signal_newv("changed",
-      G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,NULL,NULL,NULL,
-      g_cclosure_marshal_VOID__VOID,G_TYPE_NONE,0,NULL);
-}
-
-static void plover_package_set_init(PloverPackageSet *set)
-{
-    PloverPackageSetPrivate *priv;
-    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
-    priv->no_details=-1;
-}
-
-PloverPackageSet *plover_package_set_new(void)
-{
-    return g_object_new(PLOVER_TYPE_PACKAGE_SET,NULL);
-}
-
-PloverPackageSet *plover_package_set_new_from_installed(const char *root,
-  GError **err)
-{
-    PloverPackageSet *set;
-    PloverPackageSetPrivate *priv;
-    struct razor_error *error=NULL;
-    set=plover_package_set_new();
-    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
-    priv->root=razor_root_open(root,&error);
-    if (!priv->root)
-    {
-       g_set_error_literal(err,PLOVER_RAZOR_ERROR,PLOVER_RAZOR_ERROR_FAILED,
-         razor_error_get_msg(error));
-       razor_error_free(error);
-       g_object_unref(set);
-       return NULL;
-    }
-    priv->set=razor_set_ref(razor_root_get_system_set(priv->root));
-    if (!priv->set)
-    {
-       g_set_error(err,PLOVER_RAZOR_ERROR,PLOVER_RAZOR_ERROR_FAILED,
-         "Failed to get system set from %s",root);
-       g_object_unref(set);
-       return NULL;
-    }
-    return set;
-}
-
-PloverPackageSet *plover_package_set_new_from_repository(const char *base,
-  struct razor_relocations *relocations,GError **err)
-{
-#if HAVE_FCHDIR
-    int fd;
-#else
-    size_t wd_len;
-    char *wd;
-#endif
-    gchar *s;
-    struct razor_set *reloc;
-    struct razor_error *error=NULL;
-    PloverPackageSet *set;
-    PloverPackageSetPrivate *priv;
-    set=plover_package_set_new();
-    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
-#if HAVE_FCHDIR
-    fd=open(".",O_RDONLY);
-#else
-    wd_len=32;
-    wd=malloc(wd_len);
-    while (!getcwd(wd,wd_len) && errno==ERANGE)
-    {
-       free(wd);
-       wd_len*=2;
-       wd=malloc(wd_len);
-    }
-#endif
-    s=g_build_filename(base,"repodata",NULL);
-    if (chdir(s)<0)
-    {
-       g_set_error(err,G_FILE_ERROR,g_file_error_from_errno(errno),
-         "%s: %s",s,g_strerror(errno));
-       g_object_unref(set);
-#if HAVE_FCHDIR
-       close(fd);
-#else
-       free(wd);
-#endif
-       return NULL;
-    }
-    g_free(s);
-    priv->set=plover_razor_set_create_from_yum("..");
-#if HAVE_FCHDIR
-    (void)fchdir(fd);
-    close(fd);
-#else
-    chdir(wd);
-    free(wd);
-#endif
-    if (priv->set && relocations)
-    {
-       reloc=plover_relocate_packages(priv->set,base,relocations,&error);
-       if (!reloc)
-       {
-           g_set_error_literal(err,PLOVER_RAZOR_ERROR,
-             PLOVER_RAZOR_ERROR_FAILED,razor_error_get_msg(error));
-           razor_error_free(error);
-           g_object_unref(set);
-           return NULL;
-       }
-       razor_set_unref(priv->set);
-       priv->set=reloc;
-    }
-    if (!priv->set)
-    {
-       g_set_error(err,PLOVER_RAZOR_ERROR,PLOVER_RAZOR_ERROR_FAILED,
-         "Failed to create package set from repository %s",base);
-       g_object_unref(set);
-       return NULL;
-    }
-    return set;
-}
-
-GSList *plover_package_set_get_packages(PloverPackageSet *set)
-{
-    struct razor_package_iterator *iter;
-    struct razor_package *pkg;
-    PloverPackageSetPrivate *priv;
-    PloverPackage *package;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
-    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
-    if (priv->set && !priv->packages)
-    {
-       iter=razor_package_iterator_create(priv->set);
-       while(razor_package_iterator_next(iter,&pkg,RAZOR_DETAIL_LAST))
-       {
-           package=plover_package_new(priv->set,pkg);
-           priv->packages=g_slist_prepend(priv->packages,package);
-       }
-       razor_package_iterator_destroy(iter);
-    }
-    return priv->packages;
-}
-
-/*
- * Some versions of razor have a bug which causes all detail strings
- * to be discarded. If such a version of razor is used to install or
- * update a package, then all the detail strings for the installed
- * set will be lost. This function tests for this condition and can
- * be used to present something more useful than blank details.
- */
-
-gboolean plover_package_set_get_no_details(PloverPackageSet *set)
-{
-    PloverPackageSetPrivate *priv;
-    PloverPackage *package;
-    GSList *packages,*link;
-    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),FALSE);
-    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
-    if (priv->no_details<0)
-    {
-       packages=plover_package_set_get_packages(set);
-       if (packages)
-       {
-           priv->no_details=0;
-           for(link=packages;link;link=link->next)
-           {
-               package=link->data;
-               priv->no_details+=2;
-               if (*plover_package_get_summary(package))
-                   priv->no_details--;
-               if (*plover_package_get_license(package))
-                   priv->no_details--;
-               if (*plover_package_get_description(package))
-                   priv->no_details--;
-               if (*plover_package_get_URL(package))
-                   priv->no_details--;
-           }
-           if (priv->no_details<0)     /* More than 50% of strings present */
-               priv->no_details=0;
-       }
-    }
-    return priv->no_details>0;
-}
diff --git a/plover-gtk/packageset.h b/plover-gtk/packageset.h
deleted file mode 100644 (file)
index 66c0529..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#ifndef __PLOVER_PACKAGE_SET_H__
-#define __PLOVER_PACKAGE_SET_H__
-
-#include <razor.h>
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define PLOVER_TYPE_PACKAGE_SET        plover_package_set_get_type()
-#define PLOVER_PACKAGE_SET(obj)        G_TYPE_CHECK_INSTANCE_CAST(obj,\
-                                 PLOVER_TYPE_PACKAGE_SET,PloverPackageSet)
-#define PLOVER_PACKAGE_SET_CLASS(klass)\
-                               G_TYPE_CHECK_CLASS_CAST(klass,\
-                                 PLOVER_TYPE_PACKAGE_SET,\
-                                 PloverPackageSetClass)
-#define PLOVER_IS_PACKAGE_SET(obj)\
-                               G_TYPE_CHECK_INSTANCE_TYPE(obj,\
-                                 PLOVER_TYPE_PACKAGE_SET)
-#define PLOVER_IS_PACKAGE_SET_CLASS(klass)\
-                               G_TYPE_CHECK_CLASS_TYPE(obj,\
-                                 PLOVER_TYPE_PACKAGE_SET)
-#define PLOVER_PACKAGE_SET_GET_CLASS(obj)\
-                               G_TYPE_INSTANCE_GET_CLASS(obj,\
-                                 PLOVER_TYPE_PACKAGE_SET,\
-                                 PloverPackageSetClass)
-
-typedef struct _PloverPackageSet {
-    GObject parent_instance;
-} PloverPackageSet;
-
-typedef struct _PloverPackageSetClass {
-    GObjectClass parent_class;
-} PloverPackageSetClass;
-
-GType plover_package_set_get_type(void) G_GNUC_CONST;
-PloverPackageSet *plover_package_set_new(void);
-PloverPackageSet *plover_package_set_new_from_installed(const char *root,
-  GError **err);
-PloverPackageSet *plover_package_set_new_from_repository(const char *base,
-  struct razor_relocations *relocations,GError **err);
-GSList *plover_package_set_get_packages(PloverPackageSet *set);
-gboolean plover_package_set_get_no_details(PloverPackageSet *set);
-
-G_END_DECLS
-
-#endif /* __PLOVER_PACKAGE_SET_H__ */
index e5cb99a..3611372 100644 (file)
@@ -24,7 +24,7 @@
 #include <gtk/gtk.h>
 #include <razor.h>
 #include "plover/plover.h"
-#include "plover-gtk/package.h"
+#include "plover/package.h"
 #include "plover-gtk/packagestore.h"
 
 #define VALID_ITER(iter,store) ((iter) && (iter)->user_data && \
@@ -139,6 +139,8 @@ static void plover_package_store_get_value(GtkTreeModel *tree_model,
     char *s;
     PloverPackageStore *store=(PloverPackageStore *)tree_model;
     PloverPackage *package;
+    GInputStream *stream;
+    GdkPixbuf *icon;
     g_return_if_fail(column>=0 && column<PLOVER_PACKAGE_STORE_NO_COLUMNS);
     g_return_if_fail(VALID_ITER(iter,store));
     package=PLOVER_PACKAGE(g_sequence_get(iter->user_data));
@@ -151,7 +153,15 @@ static void plover_package_store_get_value(GtkTreeModel *tree_model,
        case PLOVER_PACKAGE_STORE_INSTALLED_COLUMN:
            break;
        case PLOVER_PACKAGE_STORE_ICON_COLUMN:
-           g_value_set_object(value,plover_package_get_icon(package));
+           stream=plover_package_read_icon(package,NULL);
+           if (stream)
+           {
+               icon=gdk_pixbuf_new_from_stream(stream,NULL,NULL);
+               g_object_unref(stream);
+           }
+           else
+               icon=NULL;
+           g_value_set_object(value,icon);
            break;
        case PLOVER_PACKAGE_STORE_NAME_COLUMN:
            g_value_set_string(value,plover_package_get_name(package));
index 78691a9..bd1b1ba 100644 (file)
@@ -2,7 +2,7 @@
 #define __PLOVER_PACKAGE_STORE_H__
 
 #include <glib-object.h>
-#include <plover-gtk/packageset.h>
+#include <plover/packageset.h>
 
 G_BEGIN_DECLS
 
diff --git a/plover-gtk/software-installation.ui b/plover-gtk/software-installation.ui
new file mode 100644 (file)
index 0000000..091e665
--- /dev/null
@@ -0,0 +1,175 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAssistant" id="SoftwareInstallation">
+    <property name="border_width">12</property>
+    <property name="title" translatable="yes">Software Installation</property>
+    <signal name="delete_event" handler="gtk_main_quit"/>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <object class="GtkLabel" id="SIIntroduction">
+        <property name="visible">True</property>
+        <property name="xalign">0</property>
+        <property name="yalign">0</property>
+        <property name="xpad">16</property>
+        <property name="ypad">16</property>
+        <property name="label" translatable="yes">&lt;b&gt;Welcome to the Installation Assistant&lt;/b&gt;
+
+The Installation Assistant will install the software.
+To continue, click Forward.</property>
+        <property name="use_markup">True</property>
+      </object>
+      <packing>
+        <property name="page_type">intro</property>
+        <property name="title" translatable="yes">Software Installation</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkVBox" id="SIConfirm">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkVBox" id="SIIncompatibleInstallation">
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="SIIncompatibleInstallationLabel">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+                <property name="xpad">16</property>
+                <property name="ypad">16</property>
+                <property name="label" translatable="yes">&lt;b&gt;Incompatible Installation&lt;/b&gt;
+
+The existing installation is not from %s
+In order to continue, all the existing packages must be removed.</property>
+                <property name="use_markup">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkAlignment" id="alignment4">
+                <property name="visible">True</property>
+                <property name="left_padding">16</property>
+                <property name="right_padding">16</property>
+                <child>
+                  <object class="GtkCheckButton" id="SIRemoveExisting">
+                    <property name="label" translatable="yes">Remove all existing packages</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="SISummaryOfWork">
+            <property name="width_request">450</property>
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="xpad">16</property>
+            <property name="ypad">16</property>
+            <property name="label" translatable="yes">&lt;b&gt;Installation Summary&lt;/b&gt;
+
+Packages to be installed or updated: ... plus dependencies.</property>
+            <property name="use_markup">True</property>
+            <property name="wrap">True</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="page_type">confirm</property>
+        <property name="title" translatable="yes">Software Installation (2 of 4)</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkVBox" id="SIProgress">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="xpad">16</property>
+            <property name="ypad">16</property>
+            <property name="label" translatable="yes">&lt;b&gt;Installing the Software&lt;/b&gt;
+
+Please wait while the Installation Assistant installs the software.
+This may take several minutes.</property>
+            <property name="use_markup">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkAlignment" id="alignment2">
+            <property name="visible">True</property>
+            <property name="bottom_padding">16</property>
+            <property name="left_padding">16</property>
+            <property name="right_padding">16</property>
+            <child>
+              <object class="GtkProgressBar" id="SIProgressBar">
+                <property name="visible">True</property>
+                <property name="activity_mode">True</property>
+                <property name="show_text">True</property>
+                <property name="text" translatable="yes">Unpacking files</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="page_type">progress</property>
+        <property name="title" translatable="yes">Software Installation (3 of 4)</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="SISummary">
+        <property name="visible">True</property>
+        <property name="xalign">0</property>
+        <property name="yalign">0</property>
+        <property name="xpad">16</property>
+        <property name="ypad">16</property>
+        <property name="label" translatable="yes">&lt;b&gt;Installation Assistant Completed&lt;/b&gt;
+
+The Installation Assistant has successfully completed.
+Click Close to exit the Assistant.</property>
+        <property name="use_markup">True</property>
+      </object>
+      <packing>
+        <property name="page_type">summary</property>
+        <property name="title" translatable="yes">Software Installation (4 of 4)</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/plover-gtk/stockicons.c b/plover-gtk/stockicons.c
new file mode 100644 (file)
index 0000000..f5c4011
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#ifdef WIN32
+#include <windows.h>
+#endif /* WIN32 */
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <plover-gtk/stockicons.h>
+
+/* Checks whether a loader for SVG files has been registered
+ * with GdkPixbuf.
+ */
+static gboolean plover_pixbuf_supports_svg(void)
+{
+    GSList *formats;
+    GSList *tmp_list;
+    static gint found_svg=-1;
+    gchar **mime_types,**mime_type;
+    if (found_svg!=-1)
+       return found_svg;
+    formats=gdk_pixbuf_get_formats();
+    found_svg=FALSE;
+    for (tmp_list=formats;tmp_list && !found_svg;tmp_list=tmp_list->next)
+    {
+       mime_types=gdk_pixbuf_format_get_mime_types(tmp_list->data);
+       for (mime_type=mime_types;*mime_type && !found_svg;mime_type++)
+           if (!strcmp(*mime_type,"image/svg"))
+               found_svg=TRUE;
+       g_strfreev(mime_types);
+    }
+    g_slist_free(formats);
+    return found_svg;
+}
+
+static void plover_install_icon_at_size(const char *icon_name,
+  GtkIconSet *icon_set,GtkIconSize size,const char *filename)
+{
+    int w,h;
+    GdkPixbuf *pixbuf;
+    GtkIconSource *source;
+    if (gtk_icon_size_lookup(size,&w,&h))
+    {
+       pixbuf=gdk_pixbuf_new_from_file_at_size(filename,w,h,NULL);
+       if (pixbuf)
+       {
+           source=gtk_icon_source_new();
+           gtk_icon_source_set_size_wildcarded(source,FALSE);
+           gtk_icon_source_set_size(source,size);
+           gtk_icon_source_set_pixbuf(source,pixbuf);
+           gtk_icon_set_add_source(icon_set,source);
+           gtk_icon_source_free(source);
+           g_object_unref(pixbuf);
+       }
+    }
+}
+
+/**
+ * plover_icons_add_to_stock:
+ * @type: The icon type, typically "apps" or "mimetype"
+ * @name: The icon name (the basename of files containing the icons)
+ *
+ * Find icons in <datadir>/icons/hicolor and add them to the stock images
+ * so that, for example, gtk_image_new_from_stock() will be able find them.
+ *
+ * If there is an SVG loader registered with GdkPixbuf, then:
+ * <datadir>/icons/hicolor/scalable/@type/@name.svg will be used. Otherwise,
+ * <datadir>/icons/hicolor/24x24/@type/@name.png and
+ * <datadir>/icons/hicolor/48x48/@type/@name.png will be used.
+ */
+void plover_icons_add_to_stock(const char *type,const char *name)
+{
+    gchar *prefix,*s,*filename;
+    GtkIconSource *source;
+    GtkIconSet *icon_set;
+    GtkIconFactory *factory;
+    factory=gtk_icon_factory_new();
+    icon_set=gtk_icon_set_new();
+#ifdef WIN32
+    prefix=g_win32_get_package_installation_directory_of_module(NULL);
+#else
+    prefix=NULL;
+#endif
+    if (plover_pixbuf_supports_svg())
+    {
+       source=gtk_icon_source_new();
+       s=g_strconcat(name,".svg");
+       filename=g_build_filename(prefix?prefix:"/usr",
+         "share/icons/hicolor/scalable",type,s,NULL);
+       g_free(s);
+       gtk_icon_source_set_filename(source,filename);
+       g_free(filename);
+       gtk_icon_set_add_source(icon_set,source);
+       gtk_icon_source_free(source);
+    }
+    else
+    {
+       s=g_strconcat(name,".png");
+       filename=g_build_filename(prefix?prefix:"/usr",
+         "share/icons/hicolor/24x24",type,s,NULL);
+       plover_install_icon_at_size(name,icon_set,GTK_ICON_SIZE_MENU,
+         filename);
+       plover_install_icon_at_size(name,icon_set,GTK_ICON_SIZE_BUTTON,
+         filename);
+       plover_install_icon_at_size(name,icon_set,GTK_ICON_SIZE_SMALL_TOOLBAR,
+         filename);
+       plover_install_icon_at_size(name,icon_set,GTK_ICON_SIZE_LARGE_TOOLBAR,
+         filename);
+       g_free(filename);
+       filename=g_build_filename(prefix?prefix:"/usr",
+         "share/icons/hicolor/48x48",type,s,NULL);
+       plover_install_icon_at_size(name,icon_set,GTK_ICON_SIZE_DND,
+         filename);
+       plover_install_icon_at_size(name,icon_set,GTK_ICON_SIZE_DIALOG,
+         filename);
+       g_free(filename);
+       g_free(s);
+    }
+    gtk_icon_factory_add(factory,name,icon_set);
+    gtk_icon_set_unref(icon_set);
+    //icon_set=gtk_icon_factory_lookup(factory,name);
+    gtk_icon_factory_add_default(factory);
+    g_object_unref(factory);
+}
diff --git a/plover-gtk/stockicons.h b/plover-gtk/stockicons.h
new file mode 100644 (file)
index 0000000..e2ac76f
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef __PLOVER_STOCK_ICONS_H__
+#define __PLOVER_STOCK_ICONS_H__
+
+void plover_icons_add_to_stock(const char *type,const char *name);
+
+#endif /* __PLOVER_STOCK_ICONS_H__ */
diff --git a/plover-gtk/transactionhelper.c b/plover-gtk/transactionhelper.c
new file mode 100644 (file)
index 0000000..686f6d7
--- /dev/null
@@ -0,0 +1,1037 @@
+/*
+ * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <plover/plover.h>
+#include <plover/transaction.h>
+#include <plover-gtk/transactionhelper.h>
+
+/*
+ * A PloverTransactionHelper uses a GtkAssistant to help a user run a
+ * transaction.
+ */
+
+G_DEFINE_TYPE(PloverTransactionHelper,plover_transaction_helper,G_TYPE_OBJECT)
+
+enum {
+    CLOSE=0,
+    N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void plover_transaction_helper_finalize(PloverTransactionHelper *helper)
+{
+    g_free(helper->error_primary_text);
+    g_free(helper->base);
+    g_free(helper->unsatisfied);
+    if (helper->comps)
+       plover_comps_free(helper->comps);
+    plover_vector_free(helper->report_adding);
+    plover_vector_free(helper->report_removing);
+}
+
+static void plover_transaction_helper_dispose(PloverTransactionHelper *helper)
+{
+    g_clear_error(&helper->error);
+    if (helper->error_dialog)
+    {
+       g_signal_handlers_disconnect_by_data(helper->error_dialog,helper);
+       gtk_widget_destroy(helper->error_dialog);
+       helper->error_dialog=NULL;
+    }
+    if (helper->assistant)
+    {
+       g_signal_handlers_disconnect_by_data(helper->assistant,helper);
+       g_clear_object(&helper->assistant);
+    }
+    g_clear_object(&helper->ui);
+    g_slist_foreach(helper->transactions,(GFunc)g_object_unref,NULL);
+    g_slist_free(helper->transactions);
+    helper->transactions=NULL;
+    g_clear_object(&helper->installed);
+    g_clear_object(&helper->upstream);
+    g_clear_object(&helper->relocated_upstream);
+}
+
+static void
+  plover_transaction_helper_class_init(PloverTransactionHelperClass *klass)
+{
+    GObjectClass *gobject_class=G_OBJECT_CLASS(klass);
+    gobject_class->finalize=
+      (void (*)(GObject *))plover_transaction_helper_finalize;
+    gobject_class->dispose=
+      (void (*)(GObject *))plover_transaction_helper_dispose;
+    signals[CLOSE]=g_signal_newv("close",
+      G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,NULL,NULL,NULL,
+      g_cclosure_marshal_VOID__VOID,G_TYPE_NONE,0,NULL);
+}
+
+static void plover_transaction_helper_init(PloverTransactionHelper *helper)
+{
+    helper->report_adding=plover_vector_new();
+    helper->report_removing=plover_vector_new();
+}
+
+static void plover_transaction_helper_assistant_cancel(GtkAssistant *assistant,
+  PloverTransactionHelper *helper)
+{
+    gtk_widget_hide(GTK_WIDGET(helper->assistant));
+    gtk_assistant_set_current_page(helper->assistant,0);
+    g_signal_emit(helper,signals[CLOSE],0);
+}
+
+static void plover_transaction_helper_assistant_close(GtkAssistant *assistant,
+  PloverTransactionHelper *helper)
+{
+    gtk_widget_hide(GTK_WIDGET(helper->assistant));
+    gtk_assistant_set_current_page(helper->assistant,0);
+    g_signal_emit(helper,signals[CLOSE],0);
+}
+
+static void
+  plover_transaction_helper_prepare_confirm(PloverTransactionHelper *helper)
+{
+    gchar *package_list,*add,*remove,*s;
+    GtkLabel *label;
+    struct plover_vector *report;
+    if (helper->report_adding->len)
+    {
+       plover_vector_sort(helper->report_adding);
+       if (helper->report_adding_dependencies)
+       {
+           report=plover_vector_dup(helper->report_adding);
+           if (helper->report_adding->len==1)
+               plover_vector_append(report,"its dependencies");
+           else
+               plover_vector_append(report,"their dependencies");
+           package_list=plover_vector_format_for_display(report);
+           plover_vector_free(report);
+       }
+       else
+           package_list=
+             plover_vector_format_for_display(helper->report_adding);
+       add=g_strconcat("Packages to be installed or updated: ",package_list,
+         ".",NULL);
+       g_free(package_list);
+    }
+    else
+       add=NULL;
+    if (helper->report_removing->len)
+    {
+       plover_vector_sort(helper->report_removing);
+       if (helper->report_removing_dependants)
+       {
+           report=plover_vector_dup(helper->report_removing);
+           if (helper->report_adding->len==1)
+               plover_vector_append(report,"its dependants");
+           else
+               plover_vector_append(report,"their dependants");
+           package_list=plover_vector_format_for_display(report);
+           plover_vector_free(report);
+       }
+       else
+           package_list=
+             plover_vector_format_for_display(helper->report_removing);
+       remove=g_strconcat("Packages to be removed: ",package_list,".",NULL);
+       g_free(package_list);
+    }
+    else
+       remove=NULL;
+    label=GTK_LABEL(gtk_builder_get_object(helper->ui,"SISummaryOfWork"));
+    if (add && remove)
+       s=g_strconcat("<b>Installation Summary</b>\n\n",remove,"\n\n",add,NULL);
+    else if (add || remove)
+       s=g_strconcat("<b>Installation Summary</b>\n\n",add?add:remove,NULL);
+    else
+       s=g_strdup("<b>Installation Summary</b>\n\nNo changes scheduled");
+    gtk_label_set_markup(label,s);
+    g_free(s);
+    g_free(add);
+    g_free(remove);
+}
+
+static void plover_transaction_helper_run(PloverTransactionHelper *helper);
+
+static void plover_transaction_helper_callback(GObject *source,
+  GAsyncResult *result,gpointer user_data)
+{
+    GError *error=NULL;
+    PloverTransactionHelper *helper=user_data;
+    PloverTransaction *transaction=PLOVER_TRANSACTION(source);
+    if (!plover_transaction_commit_finish(transaction,result,&error))
+    {
+       plover_transaction_helper_set_error(helper,error,
+         "Software installation failed");
+       g_error_free(error);
+    }
+    else
+       plover_transaction_helper_run(helper);
+    g_signal_handlers_disconnect_by_data(transaction,helper);
+    g_object_unref(transaction);
+}
+
+static void plover_transaction_helper_transaction_status_changed(
+  PloverTransaction *transaction,const char *status,
+  PloverTransactionHelper *helper)
+{
+    GtkProgressBar *bar;
+    bar=GTK_PROGRESS_BAR(gtk_builder_get_object(helper->ui,"SIProgressBar"));
+    gtk_progress_bar_set_text(bar,status);
+}
+
+static void plover_transaction_helper_run(PloverTransactionHelper *helper)
+{
+    PloverTransaction *transaction;
+    GtkWidget *page;
+    page=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgress"));
+    if (helper->transactions)
+    {
+       if (helper->assistant)
+           gtk_assistant_set_page_complete(helper->assistant,page,FALSE);
+       transaction=helper->transactions->data;
+       helper->transactions=g_slist_delete_link(helper->transactions,
+         helper->transactions);
+       g_signal_connect(transaction,"status-changed",
+         G_CALLBACK(plover_transaction_helper_transaction_status_changed),
+         helper);
+       plover_transaction_commit_async(transaction,NULL,
+         plover_transaction_helper_callback,helper);
+    }
+    else if (helper->assistant)
+       gtk_assistant_set_page_complete(helper->assistant,page,TRUE);
+}
+
+static gboolean plover_transaction_helper_pulse(gpointer user_data)
+{
+    PloverTransactionHelper *helper=user_data;
+    GtkWidget *w;
+    GtkProgressBar *bar;
+    if (!helper->assistant)
+       return FALSE;
+    w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgress"));
+    bar=GTK_PROGRESS_BAR(gtk_builder_get_object(helper->ui,"SIProgressBar"));
+    if (gtk_assistant_get_page_complete(helper->assistant,w))
+    {
+       gtk_progress_bar_set_fraction(bar,1.0);
+       helper->pulse_handler=0;
+       return FALSE;
+    }
+    else
+    {
+       gtk_progress_bar_pulse(bar);
+       return TRUE;
+    }
+}
+
+static void
+  plover_transaction_helper_prepare_progress(PloverTransactionHelper *helper)
+{
+    GError *error=NULL;
+    GtkToggleButton *button;
+    PloverTransaction *transaction;
+    button=GTK_TOGGLE_BUTTON(gtk_builder_get_object(helper->ui,
+      "SIRemoveExisting"));
+    if (gtk_toggle_button_get_active(button))
+    {
+       transaction=plover_transaction_new_remove(NULL,&error);
+       if (!transaction)
+       {
+           if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT))
+               g_clear_error(&error);
+           if (error)
+           {
+               plover_transaction_helper_set_error(helper,error,
+                 "Software installation failed");
+               g_error_free(error);
+               return;
+           }
+       }
+       else
+           helper->transactions=
+             g_slist_prepend(helper->transactions,transaction);
+    }
+    /*
+     * Note that PloverTransaction does support cancelling a transaction, but
+     * there are a number of challenges with using it:
+     * - cancellation is only supported during the file phase if razor
+     *   has atomic rollback,
+     *  - cancellation is not supported during post-transaction scripts at all
+     *    (since by the time the first script is started the atomic has already
+     *    been committed) and these can take quite some time,
+     *  - where a transaction has an embedded COMMIT, any rollback won't
+     *    go back beyond this point.
+     * To support user-cancel, then, we would need some mechanism to:
+     *  - Comunicate that the operation is being cancelled and this may take
+     *    some time,
+     *  - Not allow cancellation at all after the last post-transaction script
+     *    phase is started,
+     *  - Report the partially completed transaction where cancellation
+     *    occurred after a COMMIT point.
+     * At present, this doesn't appear worth the effort.
+     */
+    if (helper->assistant)
+       gtk_assistant_commit(helper->assistant);
+    plover_transaction_helper_run(helper);
+    helper->pulse_handler=g_timeout_add(100,plover_transaction_helper_pulse,
+      helper);
+}
+
+static void plover_transaction_helper_assistant_prepare(GtkAssistant *assistant,
+  GtkWidget *page,PloverTransactionHelper *helper)
+{
+    if (page==GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIConfirm")))
+       plover_transaction_helper_prepare_confirm(helper);
+    else if (page==GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIProgress")))
+       plover_transaction_helper_prepare_progress(helper);
+}
+
+static void
+  plover_transaction_helper_remove_existing_toggled(GtkToggleButton *button,
+  PloverTransactionHelper *helper)
+{
+    GtkWidget *w;
+    if (helper->assistant)
+    {
+       w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIConfirm"));
+       gtk_assistant_set_page_complete(helper->assistant,w,
+         gtk_toggle_button_get_active(button));
+    }
+}
+
+PloverTransactionHelper *plover_transaction_helper_new(GtkBuilder *ui)
+{
+    gsize len;
+    gchar *s,*directory,*contents;
+    GError *error=NULL;
+    GtkWidget *w;
+    PloverTransactionHelper *helper;
+    g_return_val_if_fail(ui == NULL || GTK_IS_BUILDER(ui),NULL);
+    helper=PLOVER_TRANSACTION_HELPER(
+      g_object_new(PLOVER_TYPE_TRANSACTION_HELPER,NULL));
+    if (ui)
+       helper->ui=g_object_ref(ui);
+    else
+       helper->ui=gtk_builder_new();
+    helper->assistant=
+      GTK_ASSISTANT(gtk_builder_get_object(helper->ui,"SoftwareInstallation"));
+    if (!helper->assistant)
+    {
+       directory=g_strdup(g_getenv("PLOVER_DATADIR"));
+       if (!directory)
+       {
+#ifdef WIN32
+           s=g_win32_get_package_installation_directory_of_module(NULL);
+           directory=g_build_filename(s,"share","plover",NULL);
+           g_free(s);
+#else
+           directory=g_strdup(PLOVER_DATADIR);
+#endif
+       }
+       s=g_build_filename(directory,"software-installation.ui",NULL);
+       g_free(directory);
+       (void)g_file_get_contents(s,&contents,&len,&error);
+       g_free(s);
+       if (!error)
+       {
+           (void)gtk_builder_add_from_string(helper->ui,contents,len,&error);
+           g_free(contents);
+       }
+       if (error)
+       {
+           g_critical("software-installation.ui: %s",error->message);
+           g_clear_error(&error);
+           g_set_error(&error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
+             "Internal error (no user interface)");
+           plover_transaction_helper_set_error(helper,error,
+             "Can't start installer");
+           return helper;
+       }
+       helper->assistant=GTK_ASSISTANT(gtk_builder_get_object(helper->ui,
+         "SoftwareInstallation"));
+    }
+    if (!helper->assistant)
+    {
+       g_critical("\"SoftwareInstallation\" object not found");
+       g_set_error(&error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
+         "Internal error (missing wizard)");
+       plover_transaction_helper_set_error(helper,error,
+         "Can't start installer");
+       g_error_free(error);
+       return helper;
+    }
+    else
+       g_object_ref(helper->assistant);
+    if (!GTK_IS_ASSISTANT(helper->assistant))
+    {
+       g_critical("\"SoftwareInstallation\" is not a GtkAssistant");
+       g_set_error(&error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
+         "Internal error (unexpected wizard type)");
+       plover_transaction_helper_set_error(helper,error,
+         "Can't start installer");
+       g_error_free(error);
+       return helper;
+    }
+    g_signal_connect(helper->assistant,"cancel",
+      G_CALLBACK(plover_transaction_helper_assistant_cancel),helper);
+    g_signal_connect(helper->assistant,"close",
+      G_CALLBACK(plover_transaction_helper_assistant_close),helper);
+    g_signal_connect(helper->assistant,"prepare",
+      G_CALLBACK(plover_transaction_helper_assistant_prepare),helper);
+    w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIRemoveExisting"));
+    if (w)
+       g_signal_connect(w,"toggled",
+         G_CALLBACK(plover_transaction_helper_remove_existing_toggled),helper);
+    w=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIIntroduction"));
+    if (w)
+       gtk_assistant_set_page_complete(helper->assistant,w,TRUE);
+    return helper;
+}
+
+PloverPackageSet *
+  plover_transaction_helper_get_installed(PloverTransactionHelper *helper)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    return helper->installed;
+}
+
+void plover_transaction_helper_set_installed(PloverTransactionHelper *helper,
+  PloverPackageSet *installed)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    g_return_if_fail(PLOVER_IS_PACKAGE_SET(installed));
+    g_return_if_fail(helper->installed == NULL);
+    helper->installed=g_object_ref(installed);
+}
+
+PloverRepository *
+  plover_transaction_helper_get_upstream(PloverTransactionHelper *helper,
+  GError **error)
+{
+    const char *base;
+#if 0
+    const char *prefix;
+    struct razor_relocations *relocations=NULL;
+#endif
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    if (!helper->upstream)
+    {
+#if 0
+       prefix=plover_transaction_helper_get_prefix(helper,error);
+       if (!prefix)
+           return NULL;
+#endif
+       base=plover_transaction_helper_get_base(helper);
+#if 0
+       if (prefix)
+       {
+           relocations=razor_relocations_create();
+           razor_relocations_add(relocations,"/usr",prefix);
+       }
+#endif
+       helper->upstream=plover_repository_new_from_yum(base,error);
+#if 0
+       if (relocations)
+           razor_relocations_destroy(relocations);
+#endif
+    }
+    return helper->upstream;
+}
+
+static PloverPackageSet *plover_transaction_helper_get_relocated_upstream(
+  PloverTransactionHelper *helper,GError **error)
+{
+    const char *prefix;
+    struct razor_relocations *relocations=NULL;
+    GError *tmp_error=NULL;
+    PloverRepository *upstream;
+    PloverPackageSet *set;
+    if (!helper->relocated_upstream)
+    {
+       upstream=plover_transaction_helper_get_upstream(helper,error);
+       if (!upstream)
+           return NULL;
+       prefix=plover_transaction_helper_get_prefix(helper,&tmp_error);
+       if (tmp_error)
+       {
+           g_propagate_error(error,tmp_error);
+           return NULL;
+       }
+       set=plover_repository_get_package_set(upstream);
+       if (prefix)
+       {
+           relocations=razor_relocations_create();
+           razor_relocations_add(relocations,"/usr",prefix);
+           helper->relocated_upstream=
+             plover_package_set_new_from_repository(upstream,relocations,
+             error);
+           if (relocations)
+               razor_relocations_destroy(relocations);
+       }
+       else
+           helper->relocated_upstream=g_object_ref(set);
+    }
+    return helper->relocated_upstream;
+}
+
+void plover_transaction_helper_set_upstream(PloverTransactionHelper *helper,
+  PloverRepository *upstream)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    g_return_if_fail(PLOVER_IS_REPOSITORY(upstream));
+    g_return_if_fail(helper->upstream == NULL);
+    helper->upstream=g_object_ref(upstream);
+}
+
+const char *plover_transaction_helper_get_base(PloverTransactionHelper *helper)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    return helper->base;
+}
+
+void plover_transaction_helper_set_base(PloverTransactionHelper *helper,
+  const char *base)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    g_return_if_fail(helper->transactions == NULL);
+    g_free(helper->base);
+    helper->base=g_strdup(base);
+}
+
+struct comps *
+  plover_transaction_helper_get_comps(PloverTransactionHelper *helper,
+  GError **error)
+{
+    gchar *s;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    g_return_val_if_fail(helper->base != NULL,NULL);
+    if (!helper->comps)
+    {
+       s=g_strconcat(helper->base,"/repodata/comps.xml",NULL);
+       helper->comps=plover_comps_new_from_file(s);
+       if (!helper->comps)
+           g_set_error(error,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_FAILED,"%s: %s",s,g_strerror(errno));
+       g_free(s);
+    }
+    return helper->comps;
+}
+
+const char *
+  plover_transaction_helper_get_prefix(PloverTransactionHelper *helper,
+  GError **error)
+{
+    const char *prefix;
+    struct comps *comps;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    g_return_val_if_fail(helper->base != NULL || helper->installed != NULL,NULL);
+    if (helper->base)
+    {
+       comps=plover_transaction_helper_get_comps(helper,error);
+       if (!comps)
+           return NULL;
+       return plover_default_prefix_for_vendor(comps->vendor);
+    }
+    prefix=plover_package_set_guess_prefix(helper->installed,error);
+    return prefix;
+}
+
+static int plover_transaction_helper_package_count(void)
+{
+    int count=0;
+    char *install_root;
+    struct razor_set *set;
+    struct razor_package *package;
+    struct razor_package_iterator *pi;
+    install_root=getenv("RAZOR_ROOT");
+    if (!install_root)
+       install_root="";
+    set=razor_root_open_read_only(install_root,NULL);
+    if (set)
+    {
+       pi=razor_package_iterator_create(set);
+       while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_LAST))
+           count++;
+       razor_package_iterator_destroy(pi);
+       razor_set_unref(set);
+    }
+    return count;
+}
+
+static gboolean
+  plover_transaction_helper_check_vendor(PloverTransactionHelper *helper,
+  GError **error)
+{
+    int i;
+    gchar *prefix=NULL,*s;
+    struct comps *comps=NULL;
+    GtkWidget *container,*page;
+    GtkButton *button;
+    GtkLabel *label;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    if (helper->check_vendor)
+    {
+       comps=plover_transaction_helper_get_comps(helper,error);
+       if (!comps)
+           return FALSE;
+       prefix=plover_default_prefix_for_vendor(comps->vendor);
+    }
+    button=GTK_BUTTON(gtk_builder_get_object(helper->ui,"SIRemoveExisting"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),FALSE);
+    container=GTK_WIDGET(gtk_builder_get_object(helper->ui,
+      "SIIncompatibleInstallation"));
+    page=GTK_WIDGET(gtk_builder_get_object(helper->ui,"SIConfirm"));
+    if (helper->check_vendor && prefix &&
+      !plover_installed_files_match_prefix(prefix))
+    {
+       label=GTK_LABEL(gtk_builder_get_object(helper->ui,
+         "SIIncompatibleInstallationLabel"));
+       s=g_strdup_printf("<b>Incompatible Installation</b>\n\n"
+         "The existing installation is not from %s.\n"
+         "In order to continue, all the existing packages must be removed.",
+         comps->vendor);
+       gtk_label_set_markup(label,s);
+       g_free(s);
+       i=plover_transaction_helper_package_count();
+       s=g_strdup_printf("Remove %d existing package%s",i,i==1?"":"s");
+       gtk_button_set_label(button,s);
+       g_free(s);
+       gtk_widget_show(container);
+       if (helper->assistant)
+           gtk_assistant_set_page_complete(helper->assistant,page,FALSE);
+    }
+    else
+    {
+       gtk_widget_hide(container);
+       if (helper->assistant)
+           gtk_assistant_set_page_complete(helper->assistant,page,TRUE);
+    }
+    return TRUE;
+}
+
+void plover_transaction_helper_set_check_vendor(PloverTransactionHelper *helper,
+  gboolean check_vendor)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    if (helper->check_vendor!=check_vendor)
+    {
+       helper->check_vendor=check_vendor;
+       if (helper->transactions)
+           plover_transaction_helper_check_vendor(helper,NULL);
+    }
+}
+
+/*
+ * If plover_transaction_helper_add_transaction() failes with an error
+ * of PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET
+ * then plover_transaction_helper_get_unsatisfied() can be used to
+ * retrieve a textual description of the problem.
+ */
+
+const char *
+  plover_transaction_helper_get_unsatisfied(PloverTransactionHelper *helper)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    return helper->unsatisfied;
+}
+
+gboolean
+  plover_transaction_helper_add_transaction(PloverTransactionHelper *helper,
+  PloverTransaction *transaction,struct plover_vector *report_packages,
+  enum razor_install_action report_action,GError **error)
+{
+    int i,count;
+    gboolean other_packages;
+    const char *s,*name;
+    enum razor_install_action action;
+    struct razor_install_iterator *ii;
+    struct razor_set *next;
+    struct razor_package *package;
+    struct plover_vector *tasked_packages;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(report_action==RAZOR_INSTALL_ACTION_ADD || report_action==RAZOR_INSTALL_ACTION_REMOVE,FALSE);
+    g_free(helper->unsatisfied);
+    helper->unsatisfied=NULL;
+    if (!plover_transaction_resolve(transaction,error))
+    {
+       s=plover_transaction_get_unsatisfied(transaction);
+       helper->unsatisfied=g_strdup(s);
+       return FALSE;
+    }
+    ii=plover_transaction_get_install_iterator(transaction,error);
+    if (!ii)
+       return FALSE;
+    next=plover_transaction_get_next_set(transaction,error);
+    if (!next)
+       return FALSE;
+    tasked_packages=plover_vector_new();
+    other_packages=FALSE;
+    while (razor_install_iterator_next(ii,&package,&action,&count))
+    {
+       if (action==report_action)
+       {
+           razor_package_get_details(next,package,RAZOR_DETAIL_NAME,&name,
+             RAZOR_DETAIL_LAST);
+           if (!report_packages ||
+             plover_vector_contains(report_packages,name))
+               plover_vector_append(tasked_packages,name);
+           else
+               other_packages=TRUE;
+       }
+    }
+    if (!tasked_packages->len)
+    {
+       /*
+        * If there are no reportable packages tasked for action there
+        * shouldn't by any packages at all, but let's be paranoid.
+        */
+       other_packages=FALSE;
+       razor_install_iterator_rewind(ii);
+       while (razor_install_iterator_next(ii,&package,&action,&count))
+       {
+           if (action==report_action)
+           {
+               razor_package_get_details(next,package,RAZOR_DETAIL_NAME,&name,
+                 RAZOR_DETAIL_LAST);
+               plover_vector_append(tasked_packages,name);
+           }
+       }
+    }
+    if (!tasked_packages->len)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_NO_WORK,"Transaction includes no %s actions",
+         report_action==RAZOR_INSTALL_ACTION_ADD?"add":"remove");
+       plover_vector_free(tasked_packages);
+       return FALSE;
+    }
+    if (!helper->transactions)
+       plover_transaction_helper_check_vendor(helper,error);
+    g_object_ref(transaction);
+    helper->transactions=g_slist_append(helper->transactions,transaction);
+    if (report_action==RAZOR_INSTALL_ACTION_ADD)
+    {
+       for(i=0;i<tasked_packages->len;i++)
+       {
+           s=tasked_packages->strings[i];
+           if (!plover_vector_contains(helper->report_adding,s))
+               plover_vector_append(helper->report_adding,s);
+       }
+       helper->report_adding_dependencies|=other_packages;
+    }
+    else
+    {
+       for(i=0;i<tasked_packages->len;i++)
+       {
+           s=tasked_packages->strings[i];
+           if (!plover_vector_contains(helper->report_removing,s))
+               plover_vector_append(helper->report_removing,s);
+       }
+       helper->report_removing_dependants|=other_packages;
+    }
+    plover_vector_free(tasked_packages);
+    return TRUE;
+}
+
+static PloverTransaction *
+  plover_transaction_helper_new_transaction(PloverTransactionHelper *helper,
+  GError **error)
+{
+    gboolean ok;
+    const char *base,*prefix;
+    GError *tmp_error=NULL;
+    PloverTransaction *transaction;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),NULL);
+    prefix=plover_transaction_helper_get_prefix(helper,&tmp_error);
+    if (tmp_error)
+    {
+       g_propagate_error(error,tmp_error);
+       return NULL;
+    }
+    transaction=plover_transaction_new();
+    plover_transaction_set_prefix(transaction,prefix);
+    plover_transaction_set_installed(transaction,helper->installed);
+    if (helper->upstream)
+       ok=plover_transaction_set_upstream(transaction,helper->upstream,error);
+    else
+    {
+       base=plover_transaction_helper_get_base(helper);
+       ok=plover_transaction_set_upstream_from_yum(transaction,base,error);
+    }
+    if (!ok)
+    {
+       g_object_unref(transaction);
+       transaction=NULL;
+    }
+    return transaction;
+}
+
+struct plover_vector *plover_transaction_helper_group_get_default_packages(
+  PloverTransactionHelper *helper,const char *group,GError **error)
+{
+    gboolean changed;
+    struct comps *comps;
+    struct comps_group *grp;
+    struct comps_requirement *pkg;
+    struct plover_vector *default_packages;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    comps=plover_transaction_helper_get_comps(helper,error);
+    if (!comps)
+       return NULL;
+    grp=plover_comps_lookup_group(comps,group);
+    if (!grp)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_FAILED,"%s: group not found",group);
+       return NULL;
+    }
+    default_packages=plover_vector_new();
+    do
+    {
+       changed=FALSE;
+       for(pkg=grp->packages;pkg;pkg=pkg->next)
+       {
+           if (plover_vector_contains(default_packages,pkg->name))
+               continue;
+           if (pkg->type==COMPS_REQUIREMENT_DEFAULT ||
+             pkg->type==COMPS_REQUIREMENT_MANDATORY ||
+             pkg->type==COMPS_REQUIREMENT_CONDITIONAL &&
+             plover_vector_contains(default_packages,pkg->requires))
+           {
+               changed=TRUE;
+               plover_vector_append(default_packages,pkg->name);
+           }
+       }
+    } while(changed);
+    return default_packages;
+}
+
+/*
+ * Returns TRUE if there is work to be done or FALSE if the packages are
+ * already installed or on error.
+ */
+gboolean
+  plover_transaction_helper_install_packages(PloverTransactionHelper *helper,
+  struct plover_vector *packages,GError **error)
+{
+    gboolean retval;
+    PloverTransaction *transaction;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    g_return_val_if_fail(packages != NULL,FALSE);
+    if (!packages->len)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_NO_WORK,"No packages listed to be installed");
+       return FALSE;
+    }
+    transaction=plover_transaction_helper_new_transaction(helper,error);
+    if (!transaction)
+       return FALSE;
+    if (!plover_transaction_install(transaction,packages->strings,error))
+    {
+       g_object_unref(transaction);
+       return FALSE;
+    }
+    retval=plover_transaction_helper_add_transaction(helper,transaction,
+      packages,RAZOR_INSTALL_ACTION_ADD,error);
+    g_object_unref(transaction);
+    return retval;
+}
+
+/*
+ * Returns TRUE if there is work to be done or FALSE if the group is
+ * already installed or on error.
+ */
+gboolean
+  plover_transaction_helper_install_group(PloverTransactionHelper *helper,
+  const char *group,GError **error)
+{
+    gboolean retval;
+    struct plover_vector *selected_packages;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    selected_packages=plover_transaction_helper_group_get_default_packages(
+      helper,group,error);
+    if (!selected_packages)
+       return FALSE;
+    if (!selected_packages->len)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_FAILED,"%s: no default packages",group);
+       plover_vector_free(selected_packages);
+       return FALSE;
+    }
+    retval=plover_transaction_helper_install_packages(helper,selected_packages,
+      error);
+    plover_vector_free(selected_packages);
+    return retval;
+}
+
+/*
+ * Returns TRUE if there is work to be done or FALSE if the group is
+ * not installed or on error.
+ */
+gboolean plover_transaction_helper_remove_group(PloverTransactionHelper *helper,
+  const char *group,GError **error)
+{
+    gboolean retval;
+    struct plover_vector *selected_packages;
+    PloverTransaction *transaction;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    selected_packages=plover_transaction_helper_group_get_default_packages(
+      helper,group,error);
+    if (!selected_packages)
+       return FALSE;
+    if (!selected_packages->len)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_FAILED,"%s: no default packages",group);
+       plover_vector_free(selected_packages);
+       return FALSE;
+    }
+    transaction=plover_transaction_new();
+    plover_transaction_set_installed(transaction,helper->installed);
+    if (!plover_transaction_remove(transaction,selected_packages->strings,
+      error))
+    {
+       plover_vector_free(selected_packages);
+       g_object_unref(transaction);
+       return FALSE;
+    }
+    retval=plover_transaction_helper_add_transaction(helper,transaction,
+      NULL,RAZOR_INSTALL_ACTION_REMOVE,error);
+    g_object_unref(transaction);
+    plover_vector_free(selected_packages);
+    return retval;
+}
+
+/*
+ * Returns TRUE if there is work to be done or FALSE if all updates have
+ * already been applied or on error.
+ */
+gboolean plover_transaction_helper_update(PloverTransactionHelper *helper,
+  GError **error)
+{
+    gboolean retval;
+    PloverTransaction *transaction;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    transaction=plover_transaction_helper_new_transaction(helper,error);
+    if (!transaction)
+       return FALSE;
+    if (!plover_transaction_update(transaction,NULL,error))
+    {
+       g_object_unref(transaction);
+       return FALSE;
+    }
+    retval=plover_transaction_helper_add_transaction(helper,transaction,
+      NULL,RAZOR_INSTALL_ACTION_ADD,error);
+    g_object_unref(transaction);
+    return retval;
+}
+
+gboolean plover_transaction_helper_get_visible(PloverTransactionHelper *helper)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper),FALSE);
+    if (helper->error_dialog)
+       return TRUE;
+    else if (!helper->assistant)
+       return FALSE;
+    else
+       return gtk_widget_get_visible(GTK_WIDGET(helper->assistant));
+}
+
+void plover_transaction_helper_present(PloverTransactionHelper *helper)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    if (helper->error_dialog)
+       gtk_window_present(GTK_WINDOW(helper->error_dialog));
+    else if (helper->assistant)
+       gtk_window_present(GTK_WINDOW(helper->assistant));
+}
+
+static void
+  plover_transaction_helper_error_dialog_response(GtkDialog *error_dialog,
+  int response_id,PloverTransactionHelper *helper)
+{
+    g_signal_handlers_disconnect_by_data(error_dialog,helper);
+    if ((GtkWidget *)error_dialog==helper->error_dialog)
+    {
+       gtk_widget_destroy(helper->error_dialog);
+       helper->error_dialog=NULL;
+       if (helper->assistant)
+       {
+           gtk_widget_hide(GTK_WIDGET(helper->assistant));
+           gtk_assistant_set_current_page(helper->assistant,0);
+       }
+       g_signal_emit(helper,signals[CLOSE],0);
+    }
+}
+
+const char *plover_transaction_helper_get_error(PloverTransactionHelper *helper,
+  const GError **error)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    if (!helper->error_dialog)
+       return NULL;
+    if (error)
+       *error=helper->error;
+    return helper->error_primary_text;
+}
+
+void plover_transaction_helper_set_error(PloverTransactionHelper *helper,
+  const GError *error,const char *primary_text)
+{
+    GtkMessageType type;
+    GtkWindow *window;
+    g_return_if_fail(PLOVER_IS_TRANSACTION_HELPER(helper));
+    g_return_if_fail(error != NULL);
+    g_return_if_fail(primary_text != NULL);
+    if (helper->pulse_handler)
+    {
+       g_source_remove(helper->pulse_handler);
+       helper->pulse_handler=0;
+    }
+    if (helper->error_dialog)
+    {
+       gtk_widget_destroy(helper->error_dialog);
+       helper->error_dialog=NULL;
+    }
+    g_free(helper->error_primary_text);
+    helper->error_primary_text=g_strdup(primary_text);
+    g_clear_error(&helper->error);
+    helper->error=g_error_copy(error);
+    if (g_error_matches(error,PLOVER_GENERAL_ERROR,
+      PLOVER_GENERAL_ERROR_NO_WORK))
+       type=GTK_MESSAGE_INFO;
+    else
+       type=GTK_MESSAGE_ERROR;
+    if (helper->assistant)
+       window=GTK_WINDOW(helper->assistant);
+    else
+       window=NULL;
+    helper->error_dialog=gtk_message_dialog_new(window,
+      GTK_DIALOG_DESTROY_WITH_PARENT,type,GTK_BUTTONS_CLOSE,primary_text);
+    gtk_message_dialog_format_secondary_text(
+      GTK_MESSAGE_DIALOG(helper->error_dialog),error->message);
+    gtk_widget_show(helper->error_dialog);
+    g_signal_connect(helper->error_dialog,"response",
+      G_CALLBACK(plover_transaction_helper_error_dialog_response),helper);
+}
diff --git a/plover-gtk/transactionhelper.h b/plover-gtk/transactionhelper.h
new file mode 100644 (file)
index 0000000..6452c91
--- /dev/null
@@ -0,0 +1,102 @@
+#ifndef __PLOVER_TRANSACTION_HELPER_H__
+#define __PLOVER_TRANSACTION_HELPER_H__
+
+#include <gtk/gtk.h>
+#include <plover/packageset.h>
+#include <plover/repository.h>
+#include <plover/transaction.h>
+
+#define PLOVER_TYPE_TRANSACTION_HELPER plover_transaction_helper_get_type()
+#define PLOVER_TRANSACTION_HELPER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj,\
+                                         PLOVER_TYPE_TRANSACTION_HELPER,\
+                                         PloverTransactionHelper)
+#define PLOVER_TRANSACTION_HELPER_CLASS(klass) \
+                                       G_TYPE_CHECK_CLASS_CAST(klass,\
+                                         PLOVER_TYPE_TRANSACTION_HELPER,\
+                                         PloverTransactionHelperClass)
+#define PLOVER_IS_TRANSACTION_HELPER(obj) \
+                                       G_TYPE_CHECK_INSTANCE_TYPE(obj,\
+                                         PLOVER_TYPE_TRANSACTION_HELPER)
+#define PLOVER_IS_TRANSACTION_HELPER_CLASS(klass) \
+                                       G_TYPE_CHECK_CLASS_TYPE(obj,\
+                                         PLOVER_TYPE_TRANSACTION_HELPER)
+#define PLOVER_TRANSACTION_HELPER_GET_CLASS(obj) \
+                                       G_TYPE_INSTANCE_GET_CLASS(obj,\
+                                         PLOVER_TYPE_TRANSACTION_HELPER,\
+                                         PloverTransactionHelperClass)
+
+typedef struct _PloverTransactionHelper {
+    GObject parent_instance;
+    PloverPackageSet *installed;
+    PloverRepository *upstream;
+    PloverPackageSet *relocated_upstream;
+    gchar *base;
+    gchar *unsatisfied;
+    struct comps *comps;
+    gboolean check_vendor;
+    gboolean report_adding_dependencies;
+    gboolean report_removing_dependants;
+    struct plover_vector *report_adding,*report_removing;
+    GSList *transactions;
+    GtkBuilder *ui;
+    GtkAssistant *assistant;
+    guint pulse_handler;
+    GError *error;
+    gchar *error_primary_text;
+    GtkWidget *error_dialog;
+} PloverTransactionHelper;
+
+typedef struct _PloverTransactionHelperClass {
+    GObjectClass parent_class;
+} PloverTransactionHelperClass;
+
+GType plover_transaction_helper_get_type(void);
+PloverTransactionHelper *plover_transaction_helper_new(GtkBuilder *ui);
+PloverPackageSet *
+  plover_transaction_helper_get_installed(PloverTransactionHelper *helper);
+void plover_transaction_helper_set_installed(PloverTransactionHelper *helper,
+  PloverPackageSet *installed);
+PloverRepository *
+  plover_transaction_helper_get_upstream(PloverTransactionHelper *helper,
+  GError **error);
+void plover_transaction_helper_set_upstream(PloverTransactionHelper *helper,
+  PloverRepository *upstream);
+const char *plover_transaction_helper_get_base(PloverTransactionHelper *helper);
+void plover_transaction_helper_set_base(PloverTransactionHelper *helper,
+  const char *base);
+struct comps *
+  plover_transaction_helper_get_comps(PloverTransactionHelper *helper,
+  GError **error);
+const char *
+  plover_transaction_helper_get_prefix(PloverTransactionHelper *helper,
+  GError **error);
+void plover_transaction_helper_set_check_vendor(PloverTransactionHelper *helper,
+  gboolean check_vendor);
+const char *
+  plover_transaction_helper_get_unsatisfied(PloverTransactionHelper *helper);
+gboolean
+  plover_transaction_helper_add_transaction(PloverTransactionHelper *helper,
+  PloverTransaction *transaction,struct plover_vector *report_packages,
+  enum razor_install_action report_action,GError **error);
+struct plover_vector *plover_transaction_helper_group_get_default_packages(
+  PloverTransactionHelper *helper,const char *group,GError **error);
+gboolean
+  plover_transaction_helper_install_packages(PloverTransactionHelper *helper,
+  struct plover_vector *packages,GError **error);
+gboolean
+  plover_transaction_helper_install_group(PloverTransactionHelper *helper,
+  const char *group,GError **error);
+gboolean
+  plover_transaction_helper_remove_group(PloverTransactionHelper *helper,
+  const char *group,GError **error);
+gboolean
+  plover_transaction_helper_update(PloverTransactionHelper *helper,
+  GError **error);
+gboolean plover_transaction_helper_get_visible(PloverTransactionHelper *helper);
+void plover_transaction_helper_present(PloverTransactionHelper *helper);
+const char *plover_transaction_helper_get_error(PloverTransactionHelper *helper,
+  const GError **error);
+void plover_transaction_helper_set_error(PloverTransactionHelper *helper,
+  const GError *error,const char *primary_text);
+
+#endif /* __PLOVER_TRANSACTION_HELPER_H__ */
index 03187f0..1ae5c2e 100644 (file)
@@ -3,10 +3,11 @@ LIBS=$(LIBPLOVER_LIBS)
 INCLUDES=-I$(top_srcdir)
 AM_LDFLAGS=-no-undefined -version-info $(LIBPLOVER_LT_VERSION_INFO)
 
-pkginclude_HEADERS=plover.h
+pkginclude_HEADERS=plover.h transaction.h package.h packageset.h repository.h
 
 lib_LTLIBRARIES=libplover.la
-libplover_la_SOURCES=$(pkginclude_HEADERS) util.c import-yum.c razor.c comps.c
+libplover_la_SOURCES=$(pkginclude_HEADERS) util.c import-yum.c razor.c comps.c \
+       log.c vector.c transaction.c package.c packageset.c repository.c
 
 pkgconfigdir=$(libdir)/pkgconfig
 pkgconfig_DATA=plover.pc
index 7ef59fc..ce2ec1a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2008  Kristian Høgsberg <krh@redhat.com>
  * Copyright (C) 2008  Red Hat, Inc
- * Copyright (C) 2009, 2011  J. Ali Harlow <ali@juiblex.co.uk>
+ * Copyright (C) 2009, 2011, 2014  J. Ali Harlow <ali@juiblex.co.uk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@
 #include <fcntl.h>
 #include <errno.h>
 
+#include <glib.h>
 #include <expat.h>
 #include <zlib.h>
 #include <razor.h>
@@ -306,94 +307,110 @@ static int plover_system_arch_is_x86(void)
 
 #define XML_BUFFER_SIZE 4096
 
-struct razor_set *
-plover_razor_set_create_from_yum(const char *base)
+struct razor_set *plover_razor_set_create_from_yum(const char *base,
+  GError **error)
 {
-       struct yum_context ctx;
-       void *buf;
-       int len;
-       gzFile primary, filelists;
-       XML_ParsingStatus status;
-       struct razor_set *set;
-
-       ctx.importer = razor_importer_create();
-       ctx.state = YUM_STATE_BEGIN;
-
-       ctx.primary_parser = XML_ParserCreate(NULL);
-       XML_SetUserData(ctx.primary_parser, &ctx);
-       XML_SetElementHandler(ctx.primary_parser,
-                             yum_primary_start_element,
-                             yum_primary_end_element);
-       XML_SetCharacterDataHandler(ctx.primary_parser,
-                                   yum_character_data);
-
-       ctx.filelists_parser = XML_ParserCreate(NULL);
-       XML_SetUserData(ctx.filelists_parser, &ctx);
-       XML_SetElementHandler(ctx.filelists_parser,
-                             yum_filelists_start_element,
-                             yum_filelists_end_element);
-       XML_SetCharacterDataHandler(ctx.filelists_parser,
-                                   yum_character_data);
-
-       primary = gzopen("primary.xml.gz", "rb");
-       if (primary == NULL)
-               return NULL;
-       filelists = gzopen("filelists.xml.gz", "rb");
-       if (filelists == NULL)
-               return NULL;
-
-       ctx.current_parser = ctx.primary_parser;
-
-       ctx.current = 0;
-
-       do {
-               XML_GetParsingStatus(ctx.current_parser, &status);
-               switch (status.parsing) {
-               case XML_SUSPENDED:
-                       XML_ResumeParser(ctx.current_parser);
-                       break;
-               case XML_PARSING:
-               case XML_INITIALIZED:
-                       buf = XML_GetBuffer(ctx.current_parser,
-                                           XML_BUFFER_SIZE);
-                       if (ctx.current_parser == ctx.primary_parser)
-                               len = gzread(primary, buf, XML_BUFFER_SIZE);
-                       else
-                               len = gzread(filelists, buf, XML_BUFFER_SIZE);
-                       if (len < 0) {
-                               fprintf(stderr,
-                                       "couldn't read input: %s\n",
-                                       strerror(errno));
-                               return NULL;
-                       }
-
-                       XML_ParseBuffer(ctx.current_parser, len, len == 0);
-                       break;
-               case XML_FINISHED:
-                       break;
+    struct yum_context ctx;
+    gchar *s;
+    void *buf;
+    int len;
+    gzFile primary, filelists;
+    XML_ParsingStatus status;
+    struct razor_set *set;
+    int errnum;
+    const char *errstr,*errobj;
+    ctx.importer=razor_importer_create();
+    ctx.state=YUM_STATE_BEGIN;
+    ctx.primary_parser=XML_ParserCreate(NULL);
+    XML_SetUserData(ctx.primary_parser,&ctx);
+    XML_SetElementHandler(ctx.primary_parser,yum_primary_start_element,
+      yum_primary_end_element);
+    XML_SetCharacterDataHandler(ctx.primary_parser,yum_character_data);
+    ctx.filelists_parser=XML_ParserCreate(NULL);
+    XML_SetUserData(ctx.filelists_parser,&ctx);
+    XML_SetElementHandler(ctx.filelists_parser,yum_filelists_start_element,
+      yum_filelists_end_element);
+    XML_SetCharacterDataHandler(ctx.filelists_parser,yum_character_data);
+    errno=0;
+    s=g_build_filename(base,"repodata","primary.xml.gz",NULL);
+    primary=gzopen(s,"rb");
+    if (!primary)
+    {
+       g_set_error(error,RAZOR_POSIX_ERROR,errno?errno:ENOMEM,
+         "%s: %s",s,strerror(errno));
+       g_free(s);
+       return NULL;
+    }
+    g_free(s);
+    s=g_build_filename(base,"repodata","filelists.xml.gz",NULL);
+    filelists=gzopen(s,"rb");
+    if (!filelists)
+    {
+       g_set_error(error,RAZOR_POSIX_ERROR,errno?errno:ENOMEM,
+         "%s: %s",s,strerror(errno));
+       g_free(s);
+       return NULL;
+    }
+    g_free(s);
+    ctx.current_parser=ctx.primary_parser;
+    ctx.current=0;
+    do
+    {
+       XML_GetParsingStatus(ctx.current_parser,&status);
+       switch (status.parsing)
+       {
+           case XML_SUSPENDED:
+               XML_ResumeParser(ctx.current_parser);
+               break;
+           case XML_PARSING:
+           case XML_INITIALIZED:
+               buf=XML_GetBuffer(ctx.current_parser,XML_BUFFER_SIZE);
+               if (ctx.current_parser==ctx.primary_parser)
+                   len=gzread(primary,buf,XML_BUFFER_SIZE);
+               else
+                   len=gzread(filelists,buf,XML_BUFFER_SIZE);
+               if (len<0)
+               {
+                   if (ctx.current_parser==ctx.primary_parser)
+                   {
+                       errstr=gzerror(primary,&errnum);
+                       errobj="primary.xml.gz";
+                   }
+                   else
+                   {
+                       errstr=gzerror(filelists,&errnum);
+                       errobj="filelists.xml.gz";
+                   }
+                   if (errnum==Z_ERRNO)
+                       g_set_error(error,PLOVER_POSIX_ERROR,errno,"%s: %s",
+                         errobj,strerror(errno));
+                   else
+                       g_set_error(error,RAZOR_ZLIB_ERROR,errnum,"%s: %s",
+                         errobj,errstr);
+                   return NULL;
                }
-       } while (status.parsing != XML_FINISHED);
-
-
-       XML_ParserFree(ctx.primary_parser);
-       XML_ParserFree(ctx.filelists_parser);
-
-       gzclose(primary);
-       gzclose(filelists);
-
-       set = razor_importer_finish(ctx.importer);
-#if RAZOR_HEADER_VERSION_MIN <= 1
-       /*
-        * Header version 1 is supported by plover v0.3 and is used on
-        * 32-bit intel machines which allows the setup and update
-        * applications from v0.3 to work. On other machines, we don't
-        * want these old applications to work (since they would do
-        * the wrong thing) and so we use the current header version
-        * which they don't support.
-        */
-       if (plover_system_arch_is_x86())
-               razor_set_set_header_version(set, 1);
+               XML_ParseBuffer(ctx.current_parser,len,!len);
+               break;
+           case XML_FINISHED:
+               break;
+       }
+    } while (status.parsing!=XML_FINISHED);
+    XML_ParserFree(ctx.primary_parser);
+    XML_ParserFree(ctx.filelists_parser);
+    gzclose(primary);
+    gzclose(filelists);
+    set=razor_importer_finish(ctx.importer);
+#if RAZOR_HEADER_VERSION_MIN<=1
+    /*
+     * Header version 1 is supported by plover v0.3 and is used on
+     * 32-bit intel machines which allows the setup and update
+     * applications from v0.3 to work. On other machines, we don't
+     * want these old applications to work (since they would do
+     * the wrong thing) and so we use the current header version
+     * which they don't support.
+     */
+    if (plover_system_arch_is_x86())
+       razor_set_set_header_version(set,1);
 #endif
-
-       return set;
+    return set;
 }
diff --git a/plover/log.c b/plover/log.c
new file mode 100644 (file)
index 0000000..519676e
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef WIN32
+#include <windows.h>
+#include <io.h>
+#else
+#include <dirent.h>
+#endif
+#include "config.h"
+#include "plover.h"
+
+#ifndef FALSE
+#define FALSE  0
+#endif
+
+#ifndef TRUE
+#define TRUE   (!FALSE)
+#endif
+
+#define MAX_OLD_LOGFILES       4       /* (5 including the current) */
+
+#ifdef WIN32
+
+struct find_suffixed_data {
+    HANDLE handle;
+    WIN32_FIND_DATA wfd;
+    int base_len;
+    char *suffix;
+};
+
+static int find_suffixed_first(const char *path,struct find_suffixed_data *data)
+{
+    const char *t1,*t2;
+    gchar *s;
+    s=g_strconcat(path,"-*",NULL);
+    data->handle=FindFirstFile(s,&data->wfd);
+    g_free(s);
+    if (data->handle==INVALID_HANDLE_VALUE)
+       return FALSE;
+    t1=strrchr(path,'/');
+    t2=strrchr(t1?t1:path,'\\');
+    if (t2)
+       data->base_len=strlen(t2+1);
+    else if (t1)
+       data->base_len=strlen(t1+1);
+    else
+       data->base_len=strlen(path);
+    data->suffix=strdup(data->wfd.cFileName+data->base_len);
+    return TRUE;
+}
+
+static int find_suffixed_next(struct find_suffixed_data *data)
+{
+    if (!FindNextFile(data->handle,&data->wfd))
+       return FALSE;
+    free(data->suffix);
+    data->suffix=strdup(data->wfd.cFileName+data->base_len);
+    return TRUE;
+}
+
+static void find_suffixed_close(struct find_suffixed_data *data)
+{
+    free(data->suffix);
+    FindClose(data->handle);
+}
+
+#else  /* WIN32 */
+
+struct find_suffixed_data {
+    DIR *dir;
+    struct dirent *entry;
+    char *base;
+    int base_len;
+    char *suffix;
+};
+
+static int find_suffixed_next(struct find_suffixed_data *data)
+{
+    struct dirent *entry_result;
+    while (!readdir_r(data->dir,data->entry,&entry_result) && entry_result)
+    {
+       if (strncmp(data->entry->d_name,data->base,data->base_len) ||
+         data->entry->d_name[data->base_len]!='-')
+           continue;
+       free(data->suffix);
+       data->suffix=strdup(data->entry->d_name+data->base_len);
+       return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * From http://womble.decadent.org.uk/readdir_r-advisory.html
+ *
+ * Calculate the required buffer size (in bytes) for directory
+ * entries read from the given directory handle. Return -1 if this
+ * this cannot be done.
+ *
+ * This code does not trust values of NAME_MAX that are less than
+ * 255, since some systems (including at least HP-UX) incorrectly
+ * define it to be a smaller value.
+ *
+ * If you use autoconf, include fpathconf and dirfd in your
+ * AC_CHECK_FUNCS list. Otherwise use some other method to detect
+ * and use them where available.
+ */
+
+static size_t dirent_buf_size(DIR * dirp)
+{
+    long name_max;
+    size_t name_end;
+#if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) && defined(_PC_NAME_MAX)
+    name_max=fpathconf(dirfd(dirp),_PC_NAME_MAX);
+    if (name_max==-1)
+#if defined(NAME_MAX)
+       name_max=(NAME_MAX>255)?NAME_MAX:255;
+#else
+       return (size_t)-1;
+#endif /* NAME_MAX */
+#else
+#if defined(NAME_MAX)
+    name_max=(NAME_MAX>255)?NAME_MAX:255;
+#else
+#error "buffer size for readdir_r cannot be determined"
+#endif /* NAME_MAX */
+#endif /* HAVE_FPATHCONF && HAVE_DIRFD && _PC_NAME_MAX */
+    name_end=(size_t)offsetof(struct dirent,d_name)+name_max+1;
+    return (name_end>sizeof(struct dirent)?name_end:sizeof(struct dirent));
+}
+
+static int find_suffixed_first(const char *path,struct find_suffixed_data *data)
+{
+    int len;
+    char *s,*base;
+    base=strrchr(path,'/');
+    if (base)
+    {
+       if (base==path)
+           data->dir=opendir("/");
+       else
+       {
+           s=strndup(path,base-path);
+           data->dir=opendir(s);
+           free(s);
+       }
+       data->base=strdup(path+1);
+    }
+    else
+    {
+       data->dir=opendir(".");
+       data->base=strdup(path);
+    }
+    if (!data->dir)
+    {
+       free(data->base);
+       return FALSE;
+    }
+    data->base_len=strlen(data->base);
+    len=dirent_buf_size(data->dir);
+    if (len<0)
+    {
+       closedir(data->dir);
+       return FALSE;
+    }
+    data->entry=malloc(len);
+    if (!data->entry)
+    {
+       closedir(data->dir);
+       return FALSE;
+    }
+    if (find_suffixed_next(data))
+       return TRUE;
+    free(data->entry);
+    closedir(data->dir);
+    return FALSE;
+}
+
+static void find_suffixed_close(struct find_suffixed_data *data)
+{
+    free(data->suffix);
+    free(data->entry);
+    closedir(data->dir);
+}
+
+#endif /* WIN32 */
+
+static int prune_old_logfiles(const char *path)
+{
+    int i,n_suffixes;
+    gchar *s;
+    char *suffix,*suffixes[MAX_OLD_LOGFILES];
+    struct find_suffixed_data fsd;
+    if (find_suffixed_first(path,&fsd))
+    {
+       n_suffixes=0;
+       do
+       {
+           suffix=strdup(fsd.suffix);
+           if (n_suffixes<MAX_OLD_LOGFILES)
+               suffixes[n_suffixes++]=suffix;
+           else
+           {
+               for(i=0;i<MAX_OLD_LOGFILES;i++)
+                   if (strcmp(suffix,suffixes[i])>0)
+                   {
+                       s=suffixes[i];
+                       suffixes[i]=suffix;
+                       suffix=s;
+                   }
+               s=g_strconcat(path,suffix,NULL);
+               (void)remove(s);
+               g_free(s);
+               free(suffix);
+           }
+       } while(find_suffixed_next(&fsd));
+       find_suffixed_close(&fsd);
+       for(i=0;i<n_suffixes;i++)
+           free(suffixes[i]);
+    }
+    return 0;
+}
+
+static int rotate_logfile(const char *path,struct tm *modified)
+{
+    gchar *s;
+    char serial;
+    char suffix[11];                   /* -yyyymmdd or -yyyymmdds */
+    sprintf(suffix,"-%04d%02d%02d",modified->tm_year+1900,modified->tm_mon+1,
+      modified->tm_mday);
+    s=g_strconcat(path,suffix,NULL);
+    if (rename(path,s))
+    {
+       suffix[10]='\0';
+       for(serial='a';serial<='z';serial++)
+       {
+           if (errno!=EACCES || serial=='z')
+           {
+               perror(s);
+               free(s);
+               return -1;
+           }
+           free(s);
+           suffix[9]=serial;
+           s=g_strconcat(path,suffix,NULL);
+           if (!rename(path,s))
+               break;
+       }
+    }
+    g_free(s);
+    return prune_old_logfiles(path);
+}
+
+int plover_log_open(const char *path)
+{
+    int retval;
+    char *root;
+    gchar *filename;
+    struct stat sb;
+    time_t t;
+    struct tm today,modified;
+    struct razor_atomic *atomic;
+    FILE *fp;
+    root=getenv("RAZOR_ROOT");
+    if (root)
+       filename=g_strconcat(root,path,NULL);
+    else
+       filename=g_strdup(path);
+    atomic=razor_atomic_open("Open log");
+    razor_atomic_make_dirs(atomic,"",filename);
+    retval=razor_atomic_commit(atomic);
+    if (retval)
+       fprintf(stderr,"Can't open log: %s\n",
+         razor_atomic_get_error_msg(atomic));
+    razor_atomic_destroy(atomic);
+    if (retval)
+       return retval;
+    if (stat(filename,&sb)<0)
+    {
+       if (errno!=ENOENT)
+       {
+           fprintf(stderr,"Can't open log: ");
+           perror(filename);
+           g_free(filename);
+           return -1;
+       }
+    }
+    else if (!S_ISREG(sb.st_mode))
+    {
+       fprintf(stderr,"Can't open log: %s: Not a regular file\n",filename);
+       g_free(filename);
+       return -1;
+    }
+    else
+    {
+       time(&t);
+#ifdef WIN32
+       localtime_s(&today,&t);
+       localtime_s(&modified,&sb.st_mtime);
+#else
+       localtime_r(&t,&today);
+       localtime_r(&sb.st_mtime,&modified);
+#endif
+       if (modified.tm_yday!=today.tm_yday || modified.tm_year!=today.tm_year)
+           rotate_logfile(filename,&modified);
+    }
+    fp=fopen(filename,"a");
+    if (!fp)
+    {
+       fprintf(stderr,"Can't open log: ");
+       perror(filename);
+       g_free(filename);
+       return -1;
+    }
+    g_free(filename);
+#ifdef WIN32
+    /*
+     * The situation under MS-Windows is a little complicated. If standard
+     * output and standard error are valid then the normal code will work.
+     * This applies in console applications and even in GUI applications
+     * if standard output and standard error are redirected by the parent
+     * process (eg., by cmd.exe). However GUI applications started in
+     * typical fashion will have invalid standard output and standard error.
+     * See http://support.microsoft.com/kb/105305 for some more detail.
+     * NB: This solution assumes that fd 1 and 2 are either used for
+     * standard output/error or are unused.
+     */
+    fclose(stdout);
+    fclose(stderr);
+#else
+    fflush(stdout);
+    fflush(stderr);
+#endif
+    if (dup2(fileno(fp),1)<0 || dup2(fileno(fp),2)<0)
+    {
+       perror("Failed to redirect standard error/output");
+       fclose(fp);
+       return -1;
+    }
+    fclose(fp);
+#ifdef WIN32
+    *stdout=*fdopen(1,"a");
+    setvbuf(stdout,NULL,_IONBF,0);
+    *stderr=*fdopen(2,"a");
+    setvbuf(stderr,NULL,_IONBF,0);
+    SetStdHandle(STD_OUTPUT_HANDLE,(HANDLE)_get_osfhandle(1));
+    SetStdHandle(STD_ERROR_HANDLE,(HANDLE)_get_osfhandle(2));
+#endif
+    time(&t);
+    printf("Run started on %s",ctime(&t));
+    fflush(stdout);
+    return 0;
+}
diff --git a/plover/package.c b/plover/package.c
new file mode 100644 (file)
index 0000000..507b27f
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2010  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <glib-object.h>
+#include <razor.h>
+#include "plover/package.h"
+
+G_DEFINE_TYPE(PloverPackage,plover_package,G_TYPE_OBJECT);
+
+typedef struct _PloverPackagePrivate {
+    struct razor_set *set;
+    struct razor_package *pkg;
+    GObject *file_store;
+    const char **prefixes;
+} PloverPackagePrivate;
+
+#define PLOVER_PACKAGE_GET_PRIVATE(obj)\
+                               G_TYPE_INSTANCE_GET_PRIVATE(obj,\
+                                 PLOVER_TYPE_PACKAGE,PloverPackagePrivate)
+
+enum {
+    CHANGED=0,
+    N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void plover_package_finalize(GObject *obj)
+{
+    PloverPackagePrivate *priv=PLOVER_PACKAGE_GET_PRIVATE(obj);
+    g_free(priv->prefixes);
+    G_OBJECT_CLASS(plover_package_parent_class)->finalize(obj);
+}
+
+static void plover_package_dispose(GObject *obj)
+{
+    PloverPackagePrivate *priv=PLOVER_PACKAGE_GET_PRIVATE(obj);
+    if (priv->file_store)
+    {
+       g_object_unref(priv->file_store);
+       priv->file_store=NULL;
+    }
+    G_OBJECT_CLASS(plover_package_parent_class)->dispose(obj);
+}
+
+static void plover_package_class_init(PloverPackageClass *klass)
+{
+    GObjectClass *oclass=G_OBJECT_CLASS(klass);
+    oclass->finalize=plover_package_finalize;
+    oclass->dispose=plover_package_dispose;
+    g_type_class_add_private(klass,sizeof(PloverPackagePrivate));
+    signals[CHANGED]=g_signal_newv("changed",
+      G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,NULL,NULL,NULL,
+      g_cclosure_marshal_VOID__VOID,G_TYPE_NONE,0,NULL);
+}
+
+static void plover_package_init(PloverPackage *package)
+{
+}
+
+PloverPackage *plover_package_new(struct razor_set *set,
+  struct razor_package *pkg)
+{
+    PloverPackage *package;
+    PloverPackagePrivate *priv;
+    package=g_object_new(PLOVER_TYPE_PACKAGE,NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    priv->set=set;
+    priv->pkg=pkg;
+    return package;
+}
+
+struct razor_set *plover_package_get_razor_set(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    return priv->set;
+}
+
+struct razor_package *plover_package_get_razor_package(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    return priv->pkg;
+}
+
+const char *plover_package_get_name(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *name=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_NAME,&name,
+      RAZOR_DETAIL_LAST);
+    return name;
+}
+
+const char *plover_package_get_summary(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *summary=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_SUMMARY,&summary,
+      RAZOR_DETAIL_LAST);
+    return summary;
+}
+
+const char *plover_package_get_version(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *version=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_VERSION,&version,
+      RAZOR_DETAIL_LAST);
+    return version;
+}
+
+const char *plover_package_get_license(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *license=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_LICENSE,&license,
+      RAZOR_DETAIL_LAST);
+    return license;
+}
+
+const char *plover_package_get_arch(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *arch=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_ARCH,&arch,
+      RAZOR_DETAIL_LAST);
+    return arch;
+}
+
+const char *plover_package_get_description(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *description=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_DESCRIPTION,
+      &description,RAZOR_DETAIL_LAST);
+    return description;
+}
+
+const char *plover_package_get_URL(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    const char *URL=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    razor_package_get_details(priv->set,priv->pkg,RAZOR_DETAIL_URL,&URL,
+      RAZOR_DETAIL_LAST);
+    return URL;
+}
+
+/**
+ * plover_package_read_icon:
+ * @package: #PloverPackage to read icon from
+ * @error: a #GError, or %NULL
+ *
+ * Opens an icon for reading. The result is a #GInputStream that
+ * can be used to read the contents of the file.
+ *
+ * If the icon does not exist, the %G_IO_ERROR_NOT_FOUND error will be
+ * returned. Other errors are possible too.
+ *
+ * Returns: (transfer full): #GInputStream or %NULL on error.
+ *     Free the returned object with g_object_unref().
+ */
+GInputStream *plover_package_read_icon(PloverPackage *package,GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    g_set_error_literal(error,G_IO_ERROR,G_IO_ERROR_NOT_SUPPORTED,
+      "Operation not supported");
+    return NULL;
+}
+
+/**
+ * plover_package_property_iterator_create:
+ * @package: #PloverPackage to create property iterator for
+ *
+ * Creates a property iterator for package. The result is a
+ * #struct razor_property_iterator that can be used to iterate over the
+ * properties of a package.
+ *
+ * Returns: (transfer full): #struct razor_property_iterator or %NULL on error.
+ *     Free the returned iterator with razor_property_iterator_destroy().
+ */
+struct razor_property_iterator *
+  plover_package_property_iterator_create(PloverPackage *package)
+{
+    PloverPackagePrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    return razor_property_iterator_create(priv->set,priv->pkg);
+}
+
+/**
+ * plover_package_file_iterator_create:
+ * @package: #PloverPackage to create file iterator for
+ * @reverse: %TRUE if the files should be iterated in reverse order
+ *
+ * Creates a file iterator for package. The result is a
+ * #struct razor_file_iterator that can be used to iterate over the files
+ * in a package.
+ *
+ * Returns: (transfer full): #struct razor_file_iterator or %NULL on error.
+ *     Free the returned iterator with razor_file_iterator_destroy().
+ */
+struct razor_file_iterator *
+  plover_package_file_iterator_create(PloverPackage *package,gboolean reverse)
+{
+    PloverPackagePrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    return razor_file_iterator_create(priv->set,priv->pkg,reverse);
+}
+
+/**
+ * plover_package_get_prefixes:
+ * @package: #PloverPackage to get prefixes for
+ *
+ * Retrieve the prefixes for a relocatable package.
+ *
+ * Returns: (transfer none): NULL-terminated array of prefixes.
+ */
+const char *const *plover_package_get_prefixes(PloverPackage *package)
+{
+    const char *prefix;
+    struct razor_string_iterator *si;
+    GPtrArray *prefixes;
+    PloverPackagePrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_PACKAGE_GET_PRIVATE(package);
+    if (!priv->prefixes)
+    {
+       prefixes=g_ptr_array_new();
+       si=razor_install_prefix_iterator_create(priv->set,priv->pkg);
+       while (razor_string_iterator_next(si,&prefix))
+           g_ptr_array_add(prefixes,prefix);
+       razor_string_iterator_destroy(si);
+       g_ptr_array_add(prefixes,NULL);
+       priv->prefixes=g_ptr_array_free(prefixes,FALSE);
+    }
+    return priv->prefixes;
+}
similarity index 73%
rename from plover-gtk/package.h
rename to plover/package.h
index 23bbff5..f164f44 100644 (file)
@@ -3,9 +3,7 @@
 
 #include <razor.h>
 #include <glib-object.h>
-#include <gdk-pixbuf/gdk-pixbuf.h>
-#include <plover-gtk/packageset.h>
-#include <plover-gtk/packagefilestore.h>
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
@@ -32,9 +30,13 @@ typedef struct _PloverPackageClass {
     GObjectClass parent_class;
 } PloverPackageClass;
 
+#include <plover/packageset.h>
+
 GType plover_package_get_type(void) G_GNUC_CONST;
 PloverPackage *plover_package_new(struct razor_set *set,
   struct razor_package *pkg);
+struct razor_set *plover_package_get_razor_set(PloverPackage *package);
+struct razor_package *plover_package_get_razor_package(PloverPackage *package);
 const char *plover_package_get_name(PloverPackage *package);
 const char *plover_package_get_summary(PloverPackage *package);
 const char *plover_package_get_version(PloverPackage *package);
@@ -42,8 +44,12 @@ const char *plover_package_get_license(PloverPackage *package);
 const char *plover_package_get_arch(PloverPackage *package);
 const char *plover_package_get_description(PloverPackage *package);
 const char *plover_package_get_URL(PloverPackage *package);
-GdkPixbuf *plover_package_get_icon(PloverPackage *package);
-PloverPackageFileStore *plover_package_get_file_store(PloverPackage *package);
+GInputStream *plover_package_get_icon_stream(PloverPackage *package);
+struct razor_property_iterator *
+  plover_package_property_iterator_create(PloverPackage *package);
+struct razor_file_iterator *
+  plover_package_file_iterator_create(PloverPackage *package,gboolean reverse);
+const char *const *plover_package_get_prefixes(PloverPackage *package);
 
 G_END_DECLS
 
diff --git a/plover/packageset.c b/plover/packageset.c
new file mode 100644 (file)
index 0000000..22c6559
--- /dev/null
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2010-2012, 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <glib-object.h>
+#include <razor.h>
+#include "plover/plover.h"
+#include "plover/package.h"
+#include "plover/packageset.h"
+
+G_DEFINE_TYPE(PloverPackageSet,plover_package_set,G_TYPE_OBJECT);
+
+typedef struct _PloverPackageSetPrivate {
+    gchar *install_root;
+    struct razor_root *root;
+    struct razor_set *set;
+    GSList *packages;
+    int no_details;
+    gchar *guessed_prefix;
+} PloverPackageSetPrivate;
+
+#define PLOVER_PACKAGE_SET_GET_PRIVATE(obj)\
+                               G_TYPE_INSTANCE_GET_PRIVATE(obj,\
+                                 PLOVER_TYPE_PACKAGE_SET,\
+                                 PloverPackageSetPrivate)
+
+enum {
+    CHANGED=0,
+    N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void plover_package_set_finalize(GObject *obj)
+{
+    PloverPackageSetPrivate *priv=PLOVER_PACKAGE_SET_GET_PRIVATE(obj);
+    g_free(priv->guessed_prefix);
+    G_OBJECT_CLASS(plover_package_set_parent_class)->finalize(obj);
+}
+
+static void plover_package_set_dispose(GObject *obj)
+{
+    PloverPackageSetPrivate *priv=PLOVER_PACKAGE_SET_GET_PRIVATE(obj);
+    if (priv->set)
+    {
+       razor_set_unref(priv->set);
+       priv->set=NULL;
+    }
+    if (priv->root)
+    {
+       g_free(priv->install_root);
+       priv->install_root=NULL;
+       razor_root_close(priv->root);
+       priv->root=NULL;
+    }
+    G_OBJECT_CLASS(plover_package_set_parent_class)->dispose(obj);
+}
+
+static void plover_package_set_class_init(PloverPackageSetClass *klass)
+{
+    GObjectClass *oclass=G_OBJECT_CLASS(klass);
+    oclass->finalize=plover_package_set_finalize;
+    oclass->dispose=plover_package_set_dispose;
+    g_type_class_add_private(klass,sizeof(PloverPackageSetPrivate));
+    signals[CHANGED]=g_signal_newv("changed",
+      G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,NULL,NULL,NULL,
+      g_cclosure_marshal_VOID__VOID,G_TYPE_NONE,0,NULL);
+}
+
+static void plover_package_set_init(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    priv->no_details=-1;
+}
+
+PloverPackageSet *plover_package_set_new(void)
+{
+    return g_object_new(PLOVER_TYPE_PACKAGE_SET,NULL);
+}
+
+void plover_package_set_close(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    g_return_if_fail(PLOVER_IS_PACKAGE_SET(set));
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (priv->root)
+    {
+       razor_root_close(priv->root);
+       priv->root=NULL;
+    }
+}
+
+gboolean plover_package_set_open(PloverPackageSet *set,const char *install_root,
+  gboolean exclusive,GError **err)
+{
+    struct razor_root *root=NULL;
+    struct razor_set *system=NULL;
+    PloverPackageSetPrivate *priv;
+    struct razor_error *error=NULL;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),FALSE);
+    if (exclusive)
+    {
+       root=razor_root_open(install_root,NULL);
+       if (!root)
+       {
+           if (razor_root_create(install_root,&error))
+           {
+               if (razor_error_get_domain(error)==RAZOR_GENERAL_ERROR &&
+                 razor_error_get_code(error)==
+                 RAZOR_GENERAL_ERROR_DATABASE_EXISTS)
+               {
+                   razor_error_free(error);
+                   error=NULL;
+                   root=razor_root_open(install_root,&error);
+               }                             
+           }
+           else
+               root=razor_root_open(install_root,&error);
+           if (!root)                        
+           {
+               plover_propagate_razor_error(err,error);
+               return FALSE;
+           }
+       }
+       system=razor_root_get_system_set(root);
+       if (system)
+           razor_set_ref(system);
+    }
+    else
+       system=razor_root_open_read_only(install_root,&error);
+    if (error)
+    {
+       g_set_error_literal(err,PLOVER_RAZOR_ERROR,RAZOR_GENERAL_ERROR_FAILED,
+         razor_error_get_msg(error));
+       razor_error_free(error);
+       return FALSE;
+    }
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (priv->set)
+    {
+       razor_set_unref(priv->set);
+       priv->set=NULL;
+    }
+    if (priv->root)
+    {
+       razor_root_close(priv->root);
+       priv->root=NULL;
+    }
+    g_free(priv->install_root);
+    priv->install_root=g_strdup(install_root);
+    priv->root=root;
+    priv->set=system;
+    return TRUE;
+}
+
+const char *plover_package_set_get_install_root(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    return priv->install_root;
+}
+
+gboolean plover_package_set_get_exclusive(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),FALSE);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    return !!priv->root;
+}
+
+gboolean plover_package_set_update(PloverPackageSet *set,struct razor_set *next,
+  struct razor_atomic *atomic)
+{
+    gboolean retval;
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),FALSE);
+    g_return_val_if_fail(plover_package_set_get_exclusive(set) != FALSE,FALSE);
+    g_return_val_if_fail(next != NULL,FALSE);
+    g_return_val_if_fail(atomic != NULL,FALSE);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    retval=!razor_root_update(priv->root,next,atomic);
+    if (retval)
+    {
+       razor_set_unref(priv->set);
+       priv->set=razor_root_get_system_set(priv->root);
+       if (priv->set)
+           razor_set_ref(priv->set);
+    }
+    return retval;
+}
+
+PloverPackageSet *plover_package_set_new_from_installed(const char *root,
+  GError **err)
+{
+    PloverPackageSet *set;
+    set=plover_package_set_new();
+    if (!plover_package_set_open(set,root,FALSE,err))
+    {
+       g_object_unref(set);
+       return NULL;
+    }
+    return set;
+}
+
+PloverPackageSet *plover_package_set_new_from_razor(struct razor_set *razor)
+{
+    PloverPackageSet *set;
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(razor != NULL,NULL);
+    set=plover_package_set_new();
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    priv->set=razor;
+    razor_set_ref(priv->set);
+    return set;
+}
+
+static gboolean
+  plover_package_set_import_package(PloverRepository *repository,
+  struct razor_relocations *relocations,struct razor_importer *importer,
+  PloverPackage *package,GError **error)
+{
+    struct razor_property_iterator *prop_iter;
+    struct razor_file_iterator *file_iter;
+    struct razor_rpm *rpm;
+    struct razor_property *prop;
+    const char *name,*version,*arch,*summary,*desc,*url,*license;
+    uint32_t flags;
+    rpm=plover_repository_open_rpm(repository,package,error);
+    if (!rpm)
+       return FALSE;
+    razor_relocations_set_rpm(relocations,rpm);
+    razor_rpm_close(rpm);
+    name=plover_package_get_name(package);
+    version=plover_package_get_version(package);
+    arch=plover_package_get_arch(package);
+    razor_importer_begin_package(importer,name,version,arch);
+    summary=plover_package_get_summary(package);
+    desc=plover_package_get_description(package);
+    url=plover_package_get_URL(package);
+    license=plover_package_get_license(package);
+    razor_importer_add_details(importer,summary,desc,url,license);
+    prop_iter=plover_package_property_iterator_create(package);
+    while (razor_property_iterator_next(prop_iter,&prop,&name,&flags,&version))
+       razor_importer_add_property(importer,name,flags,version);
+    razor_property_iterator_destroy(prop_iter);
+    file_iter=plover_package_file_iterator_create(package,FALSE);
+    while (razor_file_iterator_next(file_iter,&name))
+    {
+       name=razor_relocations_apply(relocations,name);
+       razor_importer_add_file(importer,name);
+    }
+    razor_file_iterator_destroy(file_iter);
+    razor_importer_finish_package(importer);
+    return TRUE;
+}
+
+PloverPackageSet *
+  plover_package_set_new_from_repository(PloverRepository *repository,
+  struct razor_relocations *relocations,GError **error)
+{
+    struct razor_importer *importer;
+    uint32_t header_version;
+    GSList *packages,*lnk;
+    PloverPackageSet *unrelocated,*set;
+    PloverPackageSetPrivate *priv;
+    PloverPackage *package;
+    g_return_val_if_fail(PLOVER_IS_REPOSITORY(repository),NULL);
+    unrelocated=plover_repository_get_package_set(repository);
+    set=plover_package_set_new();
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (relocations)
+    {
+       importer=razor_importer_create();
+       packages=plover_package_set_get_packages(unrelocated);
+       for(lnk=packages;lnk;lnk=lnk->next)
+       {
+           package=lnk->data;
+           if (!plover_package_set_import_package(repository,relocations,
+             importer,package,error))
+           {
+               razor_importer_destroy(importer);
+               g_object_unref(set);
+               return NULL;
+           }
+       }
+       priv->set=razor_importer_finish(importer);
+       if (!priv->set)
+       {
+           g_object_unref(set);
+           return NULL;
+       }
+       header_version=plover_package_set_get_header_version(unrelocated);
+       if (header_version)
+           plover_package_set_set_header_version(set,header_version);
+    }
+    else
+    {
+       priv->set=plover_package_set_get_razor(unrelocated);
+       razor_set_ref(priv->set);
+    }
+    return set;
+}
+
+PloverPackageSet *plover_package_set_new_from_yum(const char *base,
+  struct razor_relocations *relocations,GError **error)
+{
+    PloverPackageSet *set;
+    PloverRepository *repository;
+    repository=plover_repository_new_from_yum(base,error);
+    if (!repository)
+       return NULL;
+    set=plover_package_set_new_from_repository(repository,relocations,error);
+    g_object_unref(repository);
+    return set;
+}
+
+PloverPackageSet *plover_package_set_new_from_rpms(const char **filenames,
+  GError **error)
+{
+    int i;
+    PloverPackageSet *set;
+    PloverPackageSetPrivate *priv;
+    struct razor_importer *importer;
+    struct razor_rpm *rpm;
+    struct razor_error *tmp_error=NULL;
+    importer=razor_importer_create();
+    for(i=0;filenames[i];i++)
+    {
+       rpm=razor_rpm_open(filenames[i],&tmp_error);
+       if (!rpm)
+       {
+           razor_importer_destroy(importer);
+           plover_propagate_razor_error(error,tmp_error);
+           return NULL;
+       }
+       if (razor_importer_add_rpm(importer,rpm))
+       {
+           g_set_error(error,PLOVER_RAZOR_ERROR,RAZOR_GENERAL_ERROR_FAILED,
+             "%s: failed to import",filenames[i]);
+           razor_importer_destroy(importer);
+           return NULL;
+       }
+       razor_rpm_close(rpm);
+    }
+    set=plover_package_set_new();
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    priv->set=razor_importer_finish(importer);
+    return set;
+}
+
+uint32_t plover_package_set_get_header_version(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),0);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (priv->set)
+       return razor_set_get_header_version(priv->set);
+    else
+       return 0;
+}
+
+gboolean plover_package_set_set_header_version(PloverPackageSet *set,
+  uint32_t header_version)
+{
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),FALSE);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (priv->set)
+       return !razor_set_set_header_version(priv->set,header_version);
+    else
+       return FALSE;
+}
+
+struct razor_set *plover_package_set_get_razor(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    return priv->set;
+}
+
+GSList *plover_package_set_get_packages(PloverPackageSet *set)
+{
+    struct razor_package_iterator *iter;
+    struct razor_package *pkg;
+    PloverPackageSetPrivate *priv;
+    PloverPackage *package;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (priv->set && !priv->packages)
+    {
+       iter=razor_package_iterator_create(priv->set);
+       while(razor_package_iterator_next(iter,&pkg,RAZOR_DETAIL_LAST))
+       {
+           package=plover_package_new(priv->set,pkg);
+           priv->packages=g_slist_prepend(priv->packages,package);
+       }
+       razor_package_iterator_destroy(iter);
+       priv->packages=g_slist_reverse(priv->packages);
+    }
+    return priv->packages;
+}
+
+PloverPackage *plover_package_set_lookup(PloverPackageSet *set,
+  struct razor_package *razor_package)
+{
+    GSList *packages,*lnk;
+    PloverPackage *package;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    packages=plover_package_set_get_packages(set);
+    for(lnk=packages;lnk;lnk=lnk->next)
+    {
+       package=lnk->data;
+       if (plover_package_get_razor_package(package)==razor_package)
+           return package;
+    }
+    return NULL;
+}
+
+PloverPackage *plover_package_set_find_custom(PloverPackageSet *set,
+  gconstpointer data,GCompareFunc func)
+{
+    GSList *packages,*lnk;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    packages=plover_package_set_get_packages(set);
+    lnk=g_slist_find_custom(packages,data,func);
+    return lnk?lnk->data:NULL;
+}
+
+static gint plover_package_set_compare(gconstpointer a,gconstpointer b)
+{
+    PloverPackage *pa=(void *)a,*pb=(void *)b;
+    const char *sa,*sb;
+    sa=plover_package_get_name(pa);
+    sb=plover_package_get_name(pb);
+    if (g_strcmp0(sa,sb))
+       return 1;
+    sa=plover_package_get_arch(pa);
+    sb=plover_package_get_arch(pb);
+    if (g_strcmp0(sa,sb))
+       return 1;
+    sa=plover_package_get_version(pa);
+    sb=plover_package_get_version(pb);
+    return !!g_strcmp0(sa,sb);
+}
+
+PloverPackage *plover_package_set_find_matching(PloverPackageSet *set,
+  PloverPackage *template)
+{
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    return plover_package_set_find_custom(set,template,
+      plover_package_set_compare);
+}
+
+/*
+ * Some versions of razor have a bug which causes all detail strings
+ * to be discarded. If such a version of razor is used to install or
+ * update a package, then all the detail strings for the installed
+ * set will be lost. This function tests for this condition and can
+ * be used to present something more useful than blank details.
+ */
+
+gboolean plover_package_set_get_no_details(PloverPackageSet *set)
+{
+    PloverPackageSetPrivate *priv;
+    PloverPackage *package;
+    GSList *packages,*link;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),FALSE);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (priv->no_details<0)
+    {
+       packages=plover_package_set_get_packages(set);
+       if (packages)
+       {
+           priv->no_details=0;
+           for(link=packages;link;link=link->next)
+           {
+               package=link->data;
+               priv->no_details+=2;
+               if (*plover_package_get_summary(package))
+                   priv->no_details--;
+               if (*plover_package_get_license(package))
+                   priv->no_details--;
+               if (*plover_package_get_description(package))
+                   priv->no_details--;
+               if (*plover_package_get_URL(package))
+                   priv->no_details--;
+           }
+           if (priv->no_details<0)     /* More than 50% of strings present */
+               priv->no_details=0;
+       }
+    }
+    return priv->no_details>0;
+}
+
+struct plover_package_set_prefix {
+    gchar *path;
+    guint count;
+};
+
+static GArray *plover_package_set_popchart_new(void)
+{
+    GArray *prefixes;
+    prefixes=g_array_new(FALSE,FALSE,sizeof(struct plover_package_set_prefix));
+    return prefixes;
+}
+
+static void plover_package_set_popchart_free(GArray *popchart)
+{
+    int i;
+    struct plover_package_set_prefix *prefix;
+    for(i=0;i<popchart->len;i++)
+    {
+       prefix=&g_array_index(popchart,struct plover_package_set_prefix,i);
+       g_free(prefix->path);
+    }
+    g_array_free(popchart,TRUE);
+}
+
+static void plover_package_set_popchart_add(GArray *popchart,const char *path)
+{
+    int i;
+    struct plover_package_set_prefix *prefix;
+    for(i=popchart->len-1;i>=0;i--)
+    {
+       prefix=&g_array_index(popchart,struct plover_package_set_prefix,i);
+       if (!strcmp(prefix->path,path))
+       {
+           prefix->count++;
+           return;
+       }
+    }
+    g_array_set_size(popchart,popchart->len+1);
+    prefix=&g_array_index(popchart,struct plover_package_set_prefix,
+      popchart->len-1);
+    prefix->path=g_strdup(path);
+    prefix->count=1;
+}
+
+static const char *plover_package_set_popchart_get_popular(GArray *popchart)
+{
+    int i;
+    struct plover_package_set_prefix *prefix,*popular;
+    if (!popchart->len)
+       return NULL;
+    popular=&g_array_index(popchart,struct plover_package_set_prefix,0);
+    for(i=1;i<popchart->len;i++)
+    {
+       prefix=&g_array_index(popchart,struct plover_package_set_prefix,i);
+       if (prefix->count>popular->count)
+           popular=prefix;
+    }
+    return popular->path;
+}
+
+static void plover_package_set_popchart_add_prefixes(PloverPackageSet *set,
+  GArray *popchart)
+{
+    int i;
+    PloverPackage *package;
+    GSList *packages,*lnk;
+    const char *const *prefixes;
+    packages=plover_package_set_get_packages(set);
+    for(lnk=packages;lnk;lnk=lnk->next)
+    {
+       package=lnk->data;
+       prefixes=plover_package_get_prefixes(package);
+       for(i=0;prefixes[i];i++)
+           plover_package_set_popchart_add(popchart,prefixes[i]);
+    }
+}
+
+static void plover_package_set_popchart_add_from_files(PloverPackageSet *set,
+  GArray *popchart)
+{
+    int len;
+    const char *name,*s;
+    gchar *default_prefix,*path;
+    struct razor_file_iterator *fi;
+    GSList *packages,*lnk;
+    PloverPackage *package;
+    default_prefix=plover_default_prefix_for_vendor("");
+    if (!default_prefix)
+       return;
+    len=strlen(default_prefix);
+    packages=plover_package_set_get_packages(set);
+    for(lnk=packages;lnk;lnk=lnk->next)
+    {
+       package=lnk->data;
+       fi=plover_package_file_iterator_create(package,FALSE);
+       while (razor_file_iterator_next(fi,&name))
+       {
+           if (g_str_has_prefix(name,default_prefix))
+           {
+               for(s=name+len;*s;s++)
+                   if (G_IS_DIR_SEPARATOR(*s))
+                       break;
+               path=g_strndup(name,s-name);
+               plover_package_set_popchart_add(popchart,path);
+               g_free(path);
+           }
+       }
+       razor_file_iterator_destroy(fi);
+    }
+    g_free(default_prefix);
+}
+
+const char *plover_package_set_guess_prefix(PloverPackageSet *set,
+  GError **error)
+{
+    GArray *popchart;
+    const char *prefix;
+    PloverPackageSetPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_PACKAGE_SET(set),NULL);
+    priv=PLOVER_PACKAGE_SET_GET_PRIVATE(set);
+    if (!priv->guessed_prefix)
+    {
+       popchart=plover_package_set_popchart_new();
+       plover_package_set_popchart_add_prefixes(set,popchart);
+       prefix=plover_package_set_popchart_get_popular(popchart);
+       if (prefix)
+           priv->guessed_prefix=g_strdup(prefix);
+       else
+       {
+           plover_package_set_popchart_add_from_files(set,popchart);
+           prefix=plover_package_set_popchart_get_popular(popchart);
+           if (prefix)
+               priv->guessed_prefix=g_strdup(prefix);
+       }
+       plover_package_set_popchart_free(popchart);
+    }
+    if (!priv->guessed_prefix)
+       priv->guessed_prefix=
+         plover_default_prefix_for_vendor("Acme Corporation");
+    return priv->guessed_prefix;
+}
diff --git a/plover/packageset.h b/plover/packageset.h
new file mode 100644 (file)
index 0000000..3de950a
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef __PLOVER_PACKAGE_SET_H__
+#define __PLOVER_PACKAGE_SET_H__
+
+#include <razor.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PLOVER_TYPE_PACKAGE_SET        plover_package_set_get_type()
+#define PLOVER_PACKAGE_SET(obj)        G_TYPE_CHECK_INSTANCE_CAST(obj,\
+                                 PLOVER_TYPE_PACKAGE_SET,PloverPackageSet)
+#define PLOVER_PACKAGE_SET_CLASS(klass)\
+                               G_TYPE_CHECK_CLASS_CAST(klass,\
+                                 PLOVER_TYPE_PACKAGE_SET,\
+                                 PloverPackageSetClass)
+#define PLOVER_IS_PACKAGE_SET(obj)\
+                               G_TYPE_CHECK_INSTANCE_TYPE(obj,\
+                                 PLOVER_TYPE_PACKAGE_SET)
+#define PLOVER_IS_PACKAGE_SET_CLASS(klass)\
+                               G_TYPE_CHECK_CLASS_TYPE(obj,\
+                                 PLOVER_TYPE_PACKAGE_SET)
+#define PLOVER_PACKAGE_SET_GET_CLASS(obj)\
+                               G_TYPE_INSTANCE_GET_CLASS(obj,\
+                                 PLOVER_TYPE_PACKAGE_SET,\
+                                 PloverPackageSetClass)
+
+typedef struct _PloverPackageSet {
+    GObject parent_instance;
+} PloverPackageSet;
+
+typedef struct _PloverPackageSetClass {
+    GObjectClass parent_class;
+} PloverPackageSetClass;
+
+#include <plover/repository.h>
+
+GType plover_package_set_get_type(void) G_GNUC_CONST;
+PloverPackageSet *plover_package_set_new(void);
+void plover_package_set_close(PloverPackageSet *set);
+gboolean plover_package_set_open(PloverPackageSet *set,const char *install_root,
+  gboolean exclusive,GError **err);
+gboolean plover_package_set_update(PloverPackageSet *set,struct razor_set *next,
+  struct razor_atomic *atomic);
+const char *plover_package_set_get_install_root(PloverPackageSet *set);
+gboolean plover_package_set_get_exclusive(PloverPackageSet *set);
+PloverPackageSet *plover_package_set_new_from_installed(const char *root,
+  GError **err);
+PloverPackageSet *plover_package_set_new_from_razor(struct razor_set *razor);
+PloverPackageSet *
+  plover_package_set_new_from_repository(PloverRepository *repository,
+  struct razor_relocations *relocations,GError **err);
+PloverPackageSet *plover_package_set_new_from_yum(const char *base,
+  struct razor_relocations *relocations,GError **err);
+PloverPackageSet *plover_package_set_new_from_rpms(const char **filenames,
+  GError **error);
+uint32_t plover_package_set_get_header_version(PloverPackageSet *set);
+gboolean plover_package_set_set_header_version(PloverPackageSet *set,
+  uint32_t header_version);
+struct razor_set *plover_package_set_get_razor(PloverPackageSet *set);
+GSList *plover_package_set_get_packages(PloverPackageSet *set);
+PloverPackage *plover_package_set_lookup(PloverPackageSet *set,
+  struct razor_package *razor_package);
+PloverPackage *plover_package_set_find_custom(PloverPackageSet *set,
+  gconstpointer data,GCompareFunc func);
+PloverPackage *plover_package_set_find_matching(PloverPackageSet *set,
+  PloverPackage *template);
+gboolean plover_package_set_get_no_details(PloverPackageSet *set);
+const char *plover_package_set_guess_prefix(PloverPackageSet *set,
+  GError **error);
+
+G_END_DECLS
+
+#endif /* __PLOVER_PACKAGE_SET_H__ */
index f2ae8db..9e3d54d 100644 (file)
@@ -2,6 +2,26 @@
 #define __PLOVER_H__
 
 #include <razor.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <plover/packageset.h>
+#include <plover/repository.h>
+
+#define PLOVER_GENERAL_ERROR           RAZOR_ERROR_DOMAIN('P','l','v',0)
+#define PLOVER_SCRIPTLET_ERROR         RAZOR_ERROR_DOMAIN('P','l','v',1)
+#define PLOVER_RAZOR_ERROR             plover_razor_error_quark()
+#define PLOVER_POSIX_ERROR             plover_posix_error_quark()
+#define PLOVER_MSWIN_ERROR             plover_mswin_error_quark()
+#define PLOVER_ZLIB_ERROR              plover_zlib_error_quark()
+
+enum plover_general_error
+{
+    PLOVER_GENERAL_ERROR_FAILED,
+    PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,
+    PLOVER_GENERAL_ERROR_NO_WORK,
+    PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET,
+    PLOVER_GENERAL_ERROR_CANCELLED,
+};
 
 enum comps_requirement_type
 {
@@ -33,25 +53,36 @@ struct comps
     struct comps_group *groups;
 };
 
-char *plover_strconcat(const char *string,...);
-char *plover_default_prefix_for_vendor(const char *vendor);
+struct plover_vector
+{
+    int len,alloc;
+    char **strings;
+};
+
+gchar *plover_default_prefix_for_vendor(const char *vendor);
+gchar *plover_pre_install_prefix(void);
 char *plover_get_program_directory(const char *argv0);
+GQuark plover_razor_error_quark(void);
+GQuark plover_posix_error_quark(void);
+GQuark plover_mswin_error_quark(void);
+GQuark plover_zlib_error_quark(void);
+void plover_propagate_razor_error_dup(GError **dest,struct razor_error *src);
+void plover_propagate_razor_error(GError **dest,struct razor_error *src);
+void plover_propagate_g_error(struct razor_error **dest,GError *src);
 
-struct razor_set *plover_razor_set_create_from_yum(const char *base);
+struct razor_set *plover_razor_set_create_from_yum(const char *base,
+  GError **error);
 
-struct razor_set *plover_relocate_packages(struct razor_set *set,
-  const char *base,struct razor_relocations *relocations,
-  struct razor_error **error);
 int plover_run_transaction(struct razor_transaction *trans,
-  struct razor_install_iterator *ii,const char *base,const char *install_root,
-  struct razor_set *system,struct razor_set *next,struct razor_atomic *atomic,
-  struct razor_relocations *relocations,enum razor_stage_type stage);
-int plover_commit_transaction(struct razor_transaction *trans,const char *base,
-  const char *install_root,struct razor_root *root,
-  struct razor_relocations *relocations);
-int plover_install(const char *base,const char *prefix,char **pkgs);
-int plover_update(const char *base,const char *prefix,char **pkgs);
-int plover_remove(char **pkgs);
+  struct razor_install_iterator *ii,const char *install_root,
+  struct razor_set *system,PloverPackageSet *next,PloverRepository *upstream,
+  struct razor_atomic *atomic,struct razor_relocations *relocations,
+  enum razor_stage_type stage,GCancellable *cancellable);
+gboolean plover_install(const char *base,const char *prefix,char **pkgs,
+  GError **error);
+gboolean plover_update(const char *base,const char *prefix,char **pkgs,
+  GError **error);
+gboolean plover_remove(char **pkgs,GError **error);
 int plover_installed_files_match_prefix(const char *prefix);
 
 struct comps *plover_comps_new(void);
@@ -60,4 +91,15 @@ void plover_comps_free(struct comps *comps);
 struct comps_group *plover_comps_lookup_group(struct comps *comps,
   const char *id);
 
+int plover_log_open(const char *path);
+
+struct plover_vector *plover_vector_new(void);
+struct plover_vector *plover_vector_dup(struct plover_vector *old);
+void plover_vector_append(struct plover_vector *vector,const char *str);
+int plover_vector_contains(struct plover_vector *vector,const char *str);
+int plover_vector_remove(struct plover_vector *vector,const char *str);
+void plover_vector_sort(struct plover_vector *vector);
+char *plover_vector_format_for_display(struct plover_vector *vector);
+void plover_vector_free(struct plover_vector *vector);
+
 #endif /* __PLOVER_H__ */
index bab71c3..7590b4a 100644 (file)
@@ -6,6 +6,6 @@ includedir=@includedir@
 Name: plover
 Description: Plover packaging system
 Version: @VERSION@
-Requires: razor expat zlib
+Requires: razor expat zlib glib2
 Libs: -L${libdir} -lplover
 Cflags: -I${includedir}
index 3379711..73b8204 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2008  Kristian Høgsberg <krh@redhat.com>
  * Copyright (C) 2008  Red Hat, Inc
- * Copyright (C) 2009, 2011, 2012  J. Ali Harlow <ali@juiblex.co.uk>
+ * Copyright (C) 2009, 2011, 2012, 2014  J. Ali Harlow <ali@juiblex.co.uk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
+#include <errno.h>
 #include <unistd.h>
 #include <razor.h>
+#include <glib.h>
+#include <gio/gio.h>
 #include "config.h"
 #include "plover/plover.h"
+#include "plover/transaction.h"
 
 static char *rpm_filename(const char *name,const char *version,const char *arch)
 {
@@ -35,65 +39,7 @@ static char *rpm_filename(const char *name,const char *version,const char *arch)
        v++;
     else
        v=version;
-    return plover_strconcat(name,"-",v,".",arch,".rpm",NULL);
-}
-
-struct razor_set *plover_relocate_packages(struct razor_set *set,
-  const char *base,struct razor_relocations *relocations,
-  struct razor_error **error)
-{
-    struct razor_importer *importer;
-    struct razor_property_iterator *prop_iter;
-    struct razor_package_iterator *pkg_iter;
-    struct razor_file_iterator *file_iter;
-    struct razor_package *package;
-    struct razor_property *property;
-    struct razor_rpm *rpm;
-    struct razor_set *new;
-    const char *name,*version,*arch,*summary,*desc,*url,*license;
-    char *s,*file;
-    uint32_t flags;
-    importer=razor_importer_create();
-    pkg_iter=razor_package_iterator_create(set);
-    while (razor_package_iterator_next(pkg_iter,&package,RAZOR_DETAIL_NAME,
-      &name,RAZOR_DETAIL_VERSION,&version,RAZOR_DETAIL_ARCH,&arch,
-      RAZOR_DETAIL_SUMMARY,&summary,RAZOR_DETAIL_DESCRIPTION,&desc,
-      RAZOR_DETAIL_URL,&url,RAZOR_DETAIL_LICENSE,&license,RAZOR_DETAIL_LAST))
-    {
-       s=rpm_filename(name,version,arch);
-       file=plover_strconcat(base,"/rpms/",s,NULL);
-       free(s);
-       rpm=razor_rpm_open(file,error);
-       free(file);
-       if (!rpm)
-       {
-           razor_package_iterator_destroy(pkg_iter);
-           razor_importer_destroy(importer);
-           return NULL;
-       }
-       razor_relocations_set_rpm(relocations,rpm);
-       razor_rpm_close(rpm);
-       razor_importer_begin_package(importer,name,version,arch);
-       razor_importer_add_details(importer,summary,desc,url,license);
-       prop_iter=razor_property_iterator_create(set,package);
-       while (razor_property_iterator_next(prop_iter,&property,&name,&flags,
-         &version))
-           razor_importer_add_property(importer,name,flags,version);
-       razor_property_iterator_destroy(prop_iter);
-       file_iter=razor_file_iterator_create(set,package,0);
-       while (razor_file_iterator_next(file_iter,&name))
-       {
-           name=razor_relocations_apply(relocations,name);
-           razor_importer_add_file(importer,name);
-       }
-       razor_file_iterator_destroy(file_iter);
-       razor_importer_finish_package(importer);
-    }
-    razor_package_iterator_destroy(pkg_iter);
-    new=razor_importer_finish(importer);
-    if (new)
-       razor_set_set_header_version(new,razor_set_get_header_version(set));
-    return new;
+    return g_strconcat(name,"-",v,".",arch,".rpm",NULL);
 }
 
 /*
@@ -101,17 +47,20 @@ struct razor_set *plover_relocate_packages(struct razor_set *set,
  * is met (in which case the action is consumed).
  */
 int plover_run_transaction(struct razor_transaction *trans,
-  struct razor_install_iterator *ii,const char *base,const char *install_root,
-  struct razor_set *system,struct razor_set *next,struct razor_atomic *atomic,
-  struct razor_relocations *relocations,enum razor_stage_type stage)
+  struct razor_install_iterator *ii,const char *install_root,
+  struct razor_set *system,PloverPackageSet *next,PloverRepository *upstream,
+  struct razor_atomic *atomic,struct razor_relocations *relocations,
+  enum razor_stage_type stage,GCancellable *cancellable)
 {
-    struct razor_package *package;
+    struct razor_package *pkg;
     enum razor_install_action action;
     struct razor_rpm *rpm;
     struct razor_error *error=NULL;
     const char *name,*version,*arch;
-    char *s,*file;
+    gchar *t;
     int r,count;
+    GError *tmp_error=NULL;
+    PloverPackage *package;
     switch(stage)
     {
        case RAZOR_STAGE_SCRIPTS_PRE:
@@ -127,40 +76,41 @@ int plover_run_transaction(struct razor_transaction *trans,
            /* Keep the compiler happy */
            break;
     }
-    while (razor_install_iterator_next(ii,&package,&action,&count))
+    while (razor_install_iterator_next(ii,&pkg,&action,&count))
     {
+       if (g_cancellable_is_cancelled(cancellable))
+       {
+           razor_atomic_abort(atomic,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_CANCELLED,"Operation was cancelled");
+           return -1;
+       }
        if (action==RAZOR_INSTALL_ACTION_REMOVE)
        {
-           razor_package_get_details(system,package,RAZOR_DETAIL_NAME,&name,
+           razor_package_get_details(system,pkg,RAZOR_DETAIL_NAME,&name,
              RAZOR_DETAIL_LAST);
            if (stage==RAZOR_STAGE_FILES)
                printf("  Removing : %s ",name);
-           r=razor_package_remove(system,next,atomic,package,install_root,
-             count,stage);
+           r=razor_package_remove(system,plover_package_set_get_razor(next),
+             atomic,pkg,install_root,count,stage);
            if (stage==RAZOR_STAGE_FILES)
                printf("\n");
        }
        else if (action==RAZOR_INSTALL_ACTION_ADD)
        {
-           razor_package_get_details(next,package,RAZOR_DETAIL_NAME,&name,
-             RAZOR_DETAIL_VERSION,&version,RAZOR_DETAIL_ARCH,&arch,
-             RAZOR_DETAIL_LAST);
-           s=rpm_filename(name,version,arch);
-           file=plover_strconcat(base,"/rpms/",s,NULL);
-           free(s);
-           rpm=razor_rpm_open(file,&error);
-           free(file);
+           package=plover_package_set_lookup(next,pkg);
+           rpm=plover_repository_open_rpm(upstream,package,&tmp_error);
            if (!rpm)
            {
-               razor_atomic_abort(atomic,razor_error_get_msg(error));
+               plover_propagate_g_error(&error,tmp_error);
+               razor_atomic_propagate_error(atomic,error,NULL);
                razor_error_free(error);
                return -1;
            }
            if (stage==RAZOR_STAGE_FILES)
-               printf("  Installing : %s ",name);
+               printf("  Installing : %s ",plover_package_get_name(package));
            if (relocations)
                razor_rpm_set_relocations(rpm,relocations);
-           razor_transaction_fixup_package(trans,package,rpm);
+           razor_transaction_fixup_package(trans,pkg,rpm);
            r=razor_rpm_install(rpm,atomic,install_root,1,stage);
            razor_rpm_close(rpm);
            if (stage==RAZOR_STAGE_FILES)
@@ -174,9 +124,10 @@ int plover_run_transaction(struct razor_transaction *trans,
            return -1;
        else if (r)
        {
-           razor_package_get_details(next,package,RAZOR_DETAIL_NAME,&name,
-             RAZOR_DETAIL_VERSION,&version,RAZOR_DETAIL_ARCH,&arch,
-             RAZOR_DETAIL_LAST);
+           package=plover_package_set_lookup(next,pkg);
+           name=plover_package_get_name(package);
+           version=plover_package_get_version(package);
+           arch=plover_package_get_arch(package);
            /*
             * If a pre or preun script fails, then we should
             * treat that as a fatal error. post and postun
@@ -185,10 +136,12 @@ int plover_run_transaction(struct razor_transaction *trans,
             */
            if (stage==RAZOR_STAGE_SCRIPTS_PRE)
            {
-               fprintf(stderr,
-                 "error: %s(%s-%s.%s) scriptlet failed, exit status %d\n",
-                 action==RAZOR_INSTALL_ACTION_ADD?"%pre":"%preun",
-                 name,version,arch,r);
+               t=g_strconcat(action==RAZOR_INSTALL_ACTION_ADD?
+                 "%pre":"%preun","(",name,"-",version,".",arch,
+                 ") scriptlet failed",NULL);
+               fprintf(stderr,"error: %s, exit status %d\n",t,r);
+               razor_atomic_abort(atomic,PLOVER_SCRIPTLET_ERROR,r,t);
+               g_free(t);
                return -1;
            }
            else
@@ -201,339 +154,70 @@ int plover_run_transaction(struct razor_transaction *trans,
     return 0;
 }
 
-int plover_commit_transaction(struct razor_transaction *trans,const char *base,
-  const char *install_root,struct razor_root *root,
-  struct razor_relocations *relocations)
+gboolean plover_install(const char *base,const char *prefix,char **pkgs,
+  GError **error)
 {
-    int r,retval;
-    size_t pos;
-    struct razor_set *system,*next,*set;
-    struct razor_install_iterator *ii;
-    struct razor_atomic *atomic;
-    razor_transaction_resolve(trans);
-    if (razor_transaction_describe(trans)>0)
-       return -1;
-    next=razor_transaction_commit(trans);
-    system=razor_set_ref(razor_root_get_system_set(root));
-    ii=razor_set_create_install_iterator(system,next);
-    do
-    {
-       pos=razor_install_iterator_tell(ii);
-       atomic=razor_atomic_open("package transaction");
-       r=plover_run_transaction(trans,ii,base,install_root,system,next,atomic,
-         relocations,RAZOR_STAGE_SCRIPTS_PRE);
-       if (r<0)
-           fprintf(stderr,"Transaction aborted\n");
-       else
-       {
-           razor_install_iterator_seek(ii,pos);
-           r=plover_run_transaction(trans,ii,base,install_root,system,next,
-             atomic,relocations,RAZOR_STAGE_FILES);
-           if (r==1)
-           {
-               set=razor_install_iterator_commit_set(ii);
-               razor_root_update(root,set,atomic);
-               razor_set_unref(set);
-           }
-           else if (!r)
-               razor_root_update(root,next,atomic);
-           retval=razor_atomic_commit(atomic);
-           if (retval)
-               fprintf(stderr,"%s\n",razor_atomic_get_error_msg(atomic));
-           else
-           {
-               razor_install_iterator_seek(ii,pos);
-               plover_run_transaction(trans,ii,base,install_root,system,next,
-                 atomic,relocations,RAZOR_STAGE_SCRIPTS_POST);
-           }
-       }
-       razor_atomic_destroy(atomic);
-    } while(!retval && r==1);
-    razor_set_unref(system);
-    razor_set_unref(next);
-    razor_install_iterator_destroy(ii);
+    gboolean retval;
+    PloverTransaction *transaction;
+    transaction=plover_transaction_new_install(base,prefix,pkgs,error);
+    if (!transaction)
+       return FALSE;
+    retval=plover_transaction_commit(transaction,NULL,error);
+    g_object_unref(transaction);
     return retval;
 }
 
-static int plover_mark_package_for_update(struct razor_transaction *trans,
-  struct razor_set *set,const char *pkg)
+gboolean plover_update(const char *base,const char *prefix,char **pkgs,
+  GError **error)
 {
-    struct razor_package_iterator *pi;
-    struct razor_package *package;
-    const char *name;
-    int retval=-1;
-    pi=razor_package_iterator_create(set);
-    while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_NAME,&name,
-      RAZOR_DETAIL_LAST))
+    gboolean retval;
+    GError *tmp_error=NULL;
+    PloverTransaction *transaction;
+    transaction=plover_transaction_new_update(base,prefix,pkgs,&tmp_error);
+    if (!transaction)
     {
-       if (!strcmp(name,pkg))
-       {
-           razor_transaction_update_package(trans,package);
-           retval=0;
-           break;
-       }
-    }
-    razor_package_iterator_destroy(pi);
-    return retval;
-}
-
-int plover_install(const char *base,const char *prefix,char **pkgs)
-{
-    int i,retval;
-    char *s;
-    char *install_root;
-    struct razor_set *system,*set,*upstream;
-    struct razor_transaction *trans;
-    struct razor_relocations *relocations;
-    struct razor_root *root;
-    struct razor_error *error=NULL;
-    install_root=getenv("RAZOR_ROOT");
-    if (!install_root)
-       install_root="";
-    if (prefix)
-    {
-       relocations=razor_relocations_create();
-       razor_relocations_add(relocations,"/usr",prefix);
-    }
-    else
-       relocations=NULL;
-    root=razor_root_open(install_root,NULL);
-    if (!root)
-    {
-       if (razor_root_create(install_root,&error))
-           root=NULL;
+       retval=g_error_matches(tmp_error,PLOVER_POSIX_ERROR,ENOENT);
+       if (retval)
+           g_error_free(tmp_error);
        else
-           root=razor_root_open(install_root,&error);
-       if (!root)
-       {
-           fprintf(stderr,"%s\n",razor_error_get_msg(error));
-           razor_error_free(error);
-           if (relocations)
-               razor_relocations_destroy(relocations);
-           return -1;
-       }
-    }
-    system=razor_root_get_system_set(root);
-    if (!system)
-    {
-       fprintf(stderr,"Internal error: No system set\n");
-       razor_root_close(root);
-       if (relocations)
-           razor_relocations_destroy(relocations);
-       return -1;
-    }
-    s=plover_strconcat(base,"/repodata",NULL);
-    if (s)
-    {
-       retval=chdir(s);
-       if (retval<0)
-           perror(s);
-    }
-    else
-    {
-       fprintf(stderr,"Not enough memory\n");
-       retval=-1;
-    }
-    free(s);
-    if (retval<0)
-    {
-       razor_root_close(root);
-       if (relocations)
-           razor_relocations_destroy(relocations);
-       return -1;
-    }
-    set=plover_razor_set_create_from_yum(base);
-    if (set)
-    {
-       upstream=plover_relocate_packages(set,base,relocations,&error);
-       if (!upstream)
-       {
-           fprintf(stderr,"%s\n",razor_error_get_msg(error));
-           razor_error_free(error);
-       }
-       razor_set_unref(set);
+           g_propagate_error(error,tmp_error);
     }
     else
-       upstream=NULL;
-    if (!upstream)
     {
-       razor_root_close(root);
-       if (relocations)
-           razor_relocations_destroy(relocations);
-       return -1;
+       retval=plover_transaction_commit(transaction,NULL,error);
+       g_object_unref(transaction);
     }
-    trans=razor_transaction_create(system,upstream);
-    razor_set_unref(upstream);
-    for(i=0;pkgs[i];i++)
-       if (plover_mark_package_for_update(trans,system,pkgs[i]) &&
-         plover_mark_package_for_update(trans,upstream,pkgs[i]))
-       {
-           fprintf(stderr,"%s: Package not found\n",pkgs[i]);
-           retval=-1;
-           break;
-       }
-    if (!retval)
-       retval=plover_commit_transaction(trans,base,install_root,root,
-         relocations);
-    razor_transaction_destroy(trans);
-    razor_root_close(root);
-    if (relocations)
-       razor_relocations_destroy(relocations);
     return retval;
 }
 
-int plover_update(const char *base,const char *prefix,char **pkgs)
+gboolean plover_remove(char **pkgs,GError **error)
 {
-    int i,retval;
-    char *install_root,*s;
-    struct razor_set *system,*set,*upstream;
-    struct razor_transaction *trans;
-    struct razor_relocations *relocations;
-    struct razor_root *root;
-    struct razor_error *error=NULL;
-    install_root=getenv("RAZOR_ROOT");
-    if (!install_root)
-       install_root="";
-    if (prefix)
+    gboolean retval;
+    GError *tmp_error=NULL;
+    PloverTransaction *transaction;
+    transaction=plover_transaction_new_remove(pkgs,&tmp_error);
+    if (!transaction)
     {
-       relocations=razor_relocations_create();
-       razor_relocations_add(relocations,"/usr",prefix);
-    }
-    else
-       relocations=NULL;
-    root=razor_root_open(install_root,&error);
-    if (!root)
-    {
-       fprintf(stderr,"%s\n",razor_error_get_msg(error));
-       razor_error_free(error);
-       if (relocations)
-           razor_relocations_destroy(relocations);
-       return 0;
-    }
-    system=razor_root_get_system_set(root);
-    s=plover_strconcat(base,"/repodata",NULL);
-    if (s)
-    {
-       retval=chdir(s);
+       retval=g_error_matches(tmp_error,PLOVER_POSIX_ERROR,ENOENT);
        if (retval)
-           perror(s);
-    }
-    else
-    {
-       fprintf(stderr,"Not enough memory");
-       retval=-1;
-    }
-    free(s);
-    if (retval)
-    {
-       razor_root_close(root);
-       if (relocations)
-           razor_relocations_destroy(relocations);
-       return -1;
-    }
-    set=plover_razor_set_create_from_yum(base);
-    if (set)
-    {
-       upstream=plover_relocate_packages(set,base,relocations,&error);
-       if (!upstream)
        {
-           fprintf(stderr,"%s\n",razor_error_get_msg(error));
-           razor_error_free(error);
-       }
-       razor_set_unref(set);
-    }
-    else
-       upstream=NULL;
-    if (!upstream)
-    {
-       razor_root_close(root);
-       if (relocations)
-           razor_relocations_destroy(relocations);
-       return -1;
-    }
-    trans=razor_transaction_create(system,upstream);
-    razor_set_unref(upstream);
-    if (pkgs)
-       for(i=0;pkgs[i];i++)
-       {
-           if (plover_mark_package_for_update(trans,system,pkgs[i]))
+           g_error_free(tmp_error);
+           if (pkgs)
            {
-               fprintf(stderr,"%s: Package not found\n",pkgs[i]);
-               retval=-1;
-               break;
+               g_set_error(error,PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,"%s: %s",pkgs[0],
+                 "Package not found");
+               retval=FALSE;
            }
        }
-    else
-       razor_transaction_update_all(trans);
-    if (!retval)
-       retval=plover_commit_transaction(trans,base,install_root,root,
-         relocations);
-    razor_transaction_destroy(trans);
-    razor_root_close(root);
-    if (relocations)
-       razor_relocations_destroy(relocations);
-    return retval;
-}
-
-static int plover_mark_packages_for_removal(struct razor_transaction *trans,
-  struct razor_set *set,const char *pkg)
-{
-    struct razor_package_iterator *pi;
-    struct razor_package *package;
-    const char *name;
-    int retval=pkg?-1:0;
-    pi=razor_package_iterator_create(set);
-    while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_NAME,&name,
-      RAZOR_DETAIL_LAST))
-    {
-       if (!pkg || !strcmp(name,pkg))
-       {
-           razor_transaction_remove_package(trans,package);
-           retval=0;
-       }
+       else
+           g_propagate_error(error,tmp_error);
     }
-    razor_package_iterator_destroy(pi);
-    return retval;
-}
-
-int plover_remove(char **pkgs)
-{
-    int i,retval=0;
-    char *install_root;
-    struct razor_set *system,*upstream;
-    struct razor_transaction *trans;
-    struct razor_root *root;
-    struct razor_error *error=NULL;
-    install_root=getenv("RAZOR_ROOT");
-    if (!install_root)
-       install_root="";
-    root=razor_root_open(install_root,&error);
-    if (!root)
+    else
     {
-       fprintf(stderr,"%s\n",razor_error_get_msg(error));
-       razor_error_free(error);
-       return 0;
+       retval=plover_transaction_commit(transaction,NULL,error);
+       g_object_unref(transaction);
     }
-    system=razor_root_get_system_set(root);
-    upstream=razor_set_create_without_root();
-    trans=razor_transaction_create(system,upstream);
-    razor_set_unref(upstream);
-    if (pkgs)
-       for(i=0;pkgs[i];i++)
-       {
-           if (plover_mark_packages_for_removal(trans,system,pkgs[i]))
-           {
-               fprintf(stderr,"%s: Package not found\n",pkgs[i]);
-               retval=-1;
-               break;
-           }
-       }
-    else
-       plover_mark_packages_for_removal(trans,system,NULL);
-    if (!retval)
-       retval=plover_commit_transaction(trans,NULL,install_root,root,NULL);
-    razor_transaction_destroy(trans);
-    razor_root_close(root);
     return retval;
 }
 
diff --git a/plover/repository.c b/plover/repository.c
new file mode 100644 (file)
index 0000000..e9b001c
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <glib-object.h>
+#include <razor.h>
+#include <plover/plover.h>
+#include <plover/repository.h>
+
+G_DEFINE_TYPE(PloverRepository,plover_repository,G_TYPE_OBJECT);
+
+typedef struct _PloverRepositoryPrivate {
+    PloverPackageSet *set;
+    gchar **filenames;
+} PloverRepositoryPrivate;
+
+#define PLOVER_REPOSITORY_GET_PRIVATE(obj)\
+                               G_TYPE_INSTANCE_GET_PRIVATE(obj,\
+                                 PLOVER_TYPE_REPOSITORY,\
+                                 PloverRepositoryPrivate)
+
+static void plover_repository_finalize(GObject *obj)
+{
+    PloverRepositoryPrivate *priv=PLOVER_REPOSITORY_GET_PRIVATE(obj);
+    g_strfreev(priv->filenames);
+    if (G_OBJECT_CLASS(plover_repository_parent_class)->finalize)
+       G_OBJECT_CLASS(plover_repository_parent_class)->finalize(obj);
+}
+
+static void plover_repository_dispose(GObject *obj)
+{
+    PloverRepositoryPrivate *priv=PLOVER_REPOSITORY_GET_PRIVATE(obj);
+    g_clear_object(&priv->set);
+    if (G_OBJECT_CLASS(plover_repository_parent_class)->dispose)
+       G_OBJECT_CLASS(plover_repository_parent_class)->dispose(obj);
+}
+
+static void plover_repository_class_init(PloverRepositoryClass *klass)
+{
+    GObjectClass *oclass=G_OBJECT_CLASS(klass);
+    oclass->finalize=plover_repository_finalize;
+    oclass->dispose=plover_repository_dispose;
+    g_type_class_add_private(klass,sizeof(PloverRepositoryPrivate));
+}
+
+static void plover_repository_init(PloverRepository *repository)
+{
+}
+
+PloverRepository *plover_repository_new_from_files(const char **filenames,
+  GError **error)
+{
+    PloverPackageSet *set;
+    PloverRepository *repository;
+    PloverRepositoryPrivate *priv;
+    set=plover_package_set_new_from_rpms(filenames,error);
+    if (!set)
+       return NULL;
+    repository=g_object_new(PLOVER_TYPE_REPOSITORY,NULL);
+    priv=PLOVER_REPOSITORY_GET_PRIVATE(repository);
+    priv->filenames=g_strdupv((gchar **)filenames);
+    priv->set=set;
+    return repository;
+}
+
+static char *rpm_filename(const char *name,const char *version,const char *arch)
+{
+    const char *v;
+    v=strchr(version,':');            /* Skip epoch */
+    if (v)
+       v++;
+    else
+       v=version;
+    return g_strconcat(name,"-",v,".",arch,".rpm",NULL);
+}
+
+PloverRepository *plover_repository_new_from_yum(const char *base,
+  GError **error)
+{
+    char *s;
+    const char *name,*version,*arch;
+    GPtrArray *filenames;
+    struct razor_set *imported;
+    struct razor_package *package;
+    struct razor_package_iterator *pi;
+    PloverPackageSet *set;
+    PloverRepository *repository;
+    PloverRepositoryPrivate *priv;
+    imported=plover_razor_set_create_from_yum(base,error);
+    if (!imported)
+       return NULL;
+    set=plover_package_set_new_from_razor(imported);
+    razor_set_unref(imported);
+    repository=g_object_new(PLOVER_TYPE_REPOSITORY,NULL);
+    priv=PLOVER_REPOSITORY_GET_PRIVATE(repository);
+    filenames=g_ptr_array_new();
+    pi=razor_package_iterator_create(plover_package_set_get_razor(set));
+    while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_NAME,&name,
+      RAZOR_DETAIL_VERSION,&version,RAZOR_DETAIL_ARCH,&arch,RAZOR_DETAIL_LAST))
+    {
+       s=rpm_filename(name,version,arch);
+       g_ptr_array_add(filenames,g_build_filename(base,"rpms",s,NULL));
+       free(s);
+    }
+    razor_package_iterator_destroy(pi);
+    g_ptr_array_add(filenames,NULL);
+    priv->filenames=(gchar **)g_ptr_array_free(filenames,FALSE);
+    priv->set=set;
+    return repository;
+}
+
+PloverPackageSet *
+  plover_repository_get_package_set(PloverRepository *repository)
+{
+    PloverRepositoryPrivate *priv;
+    g_return_val_if_fail(PLOVER_IS_REPOSITORY(repository),NULL);
+    priv=PLOVER_REPOSITORY_GET_PRIVATE(repository);
+    return priv->set;
+}
+
+struct razor_rpm *plover_repository_open_rpm(PloverRepository *repository,
+  PloverPackage *package,GError **error)
+{
+    int nth;
+    struct razor_rpm *rpm;
+    struct razor_error *tmp_error=NULL;
+    PloverRepositoryPrivate *priv;
+    PloverPackage *internal;
+    g_return_val_if_fail(PLOVER_IS_REPOSITORY(repository),NULL);
+    g_return_val_if_fail(PLOVER_IS_PACKAGE(package),NULL);
+    priv=PLOVER_REPOSITORY_GET_PRIVATE(repository);
+    nth=g_slist_index(plover_package_set_get_packages(priv->set),package);
+    if (nth<0)
+    {
+       internal=plover_package_set_find_matching(priv->set,package);
+       nth=g_slist_index(plover_package_set_get_packages(priv->set),internal);
+    }
+    if (nth<0)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,
+         "%s-%s.%s: Package not in repository",
+         plover_package_get_name(package),plover_package_get_version(package),
+         plover_package_get_arch(package));
+       return NULL;
+    }
+    rpm=razor_rpm_open(priv->filenames[nth],&tmp_error);
+    if (!rpm)
+       plover_propagate_razor_error(error,tmp_error);
+    return rpm;
+}
diff --git a/plover/repository.h b/plover/repository.h
new file mode 100644 (file)
index 0000000..8409756
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef __PLOVER_REPOSITORY_H__
+#define __PLOVER_REPOSITORY_H__
+
+#include <razor.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define PLOVER_TYPE_REPOSITORY plover_repository_get_type()
+#define PLOVER_REPOSITORY(obj) G_TYPE_CHECK_INSTANCE_CAST(obj,\
+                                 PLOVER_TYPE_REPOSITORY,PloverRepository)
+#define PLOVER_REPOSITORY_CLASS(klass)\
+                               G_TYPE_CHECK_CLASS_CAST(klass,\
+                                 PLOVER_TYPE_REPOSITORY,PloverRepositoryClass)
+#define PLOVER_IS_REPOSITORY(obj)\
+                               G_TYPE_CHECK_INSTANCE_TYPE(obj,\
+                                 PLOVER_TYPE_REPOSITORY)
+#define PLOVER_IS_REPOSITORY_CLASS(klass)\
+                               G_TYPE_CHECK_CLASS_TYPE(obj,\
+                                 PLOVER_TYPE_REPOSITORY)
+#define PLOVER_REPOSITORY_GET_CLASS(obj)\
+                               G_TYPE_INSTANCE_GET_CLASS(obj,\
+                                 PLOVER_TYPE_REPOSITORY,PloverRepositoryClass)
+
+typedef struct _PloverRepository {
+    GObject parent_instance;
+} PloverRepository;
+
+typedef struct _PloverRepositoryClass {
+    GObjectClass parent_class;
+} PloverRepositoryClass;
+
+#include <plover/package.h>
+#include <plover/packageset.h>
+
+GType plover_repository_get_type(void) G_GNUC_CONST;
+PloverRepository *plover_repository_new_from_files(const char **filenames,
+  GError **error);
+PloverRepository *plover_repository_new_from_yum(const char *base,
+  GError **error);
+PloverPackageSet *
+  plover_repository_get_package_set(PloverRepository *repository);
+struct razor_rpm *plover_repository_open_rpm(PloverRepository *repository,
+  PloverPackage *package,GError **error);
+
+G_END_DECLS
+
+#endif /* __PLOVER_REPOSITORY_H__ */
diff --git a/plover/transaction.c b/plover/transaction.c
new file mode 100644 (file)
index 0000000..d22715b
--- /dev/null
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2009, 2011, 2012, 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <glib-object.h>
+#include "plover/transaction.h"
+#include "plover/plover.h"
+
+G_DEFINE_TYPE(PloverTransaction,plover_transaction,G_TYPE_OBJECT);
+
+enum {
+    STATUS_CHANGED=0,
+    N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void plover_transaction_finalize(PloverTransaction *transaction)
+{
+    g_free(transaction->base);
+    g_free(transaction->prefix);
+    g_free(transaction->unsatisfied);
+    if (transaction->trans)
+       razor_transaction_destroy(transaction->trans);
+    if (transaction->relocations)
+       razor_relocations_destroy(transaction->relocations);
+    if (transaction->install_iterator)
+       razor_install_iterator_destroy(transaction->install_iterator);
+    if (transaction->next)
+       razor_set_unref(transaction->next);
+    if (transaction->system)
+       razor_set_unref(transaction->system);
+}
+
+static void plover_transaction_dispose(PloverTransaction *transaction)
+{
+    g_clear_object(&transaction->installed);
+    g_clear_object(&transaction->relocated);
+    g_clear_object(&transaction->upstream);
+}
+
+static void plover_transaction_class_init(PloverTransactionClass *klass)
+{
+    GObjectClass *gobject_class=G_OBJECT_CLASS(klass);
+    gobject_class->finalize=(void (*)(GObject *))plover_transaction_finalize;
+    gobject_class->dispose=(void (*)(GObject *))plover_transaction_dispose;
+    signals[STATUS_CHANGED]=g_signal_new("status-changed",
+      G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,0,NULL,NULL,
+      g_cclosure_marshal_VOID__STRING,G_TYPE_NONE,1,G_TYPE_STRING);
+}
+
+static void plover_transaction_init(PloverTransaction *transaction)
+{
+}
+
+gboolean plover_transaction_resolve(PloverTransaction *transaction,
+  GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    if (!(transaction->flags&PLOVER_TRANSACTION_RESOLVED))
+    {
+       razor_transaction_resolve(transaction->trans);
+       transaction->flags|=PLOVER_TRANSACTION_RESOLVED;
+       g_free(transaction->unsatisfied);
+       transaction->unsatisfied=NULL;
+       if (razor_transaction_describe(transaction->trans)>0)
+           transaction->flags|=PLOVER_TRANSACTION_UNSATISFIED;
+    }
+    if (transaction->flags&PLOVER_TRANSACTION_UNSATISFIED)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_REQUIREMENTS_NOT_MET,
+         "Package requirements not met");
+       return FALSE;
+    }
+    return TRUE;
+}
+
+static void plover_transaction_unsatisfied_callback(const char *requirement,
+  struct razor_package *package,const char *name,const char *version,
+  const char *arch,void *data)
+{
+    GString *string=data;
+    g_string_append_printf(string,"%s is needed by %s-%s.%s\n",requirement,name,
+      version,arch);
+}
+
+const char *plover_transaction_get_unsatisfied(PloverTransaction *transaction)
+{
+    GString *unsatisfied;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),NULL);
+    if (plover_transaction_resolve(transaction,NULL))
+       return NULL;
+    else if (!transaction->unsatisfied)
+    {
+       unsatisfied=g_string_new(NULL);
+       if (!razor_transaction_unsatisfied(transaction->trans,
+         plover_transaction_unsatisfied_callback,unsatisfied))
+           /* Impossible */
+           g_string_assign(unsatisfied,
+             "Unknown package requirements unsatisfied");
+       transaction->unsatisfied=g_string_free(unsatisfied,FALSE);
+    }
+    return transaction->unsatisfied;
+}
+
+struct razor_set *plover_transaction_get_system_set(
+  PloverTransaction *transaction)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),NULL);
+    if (!transaction->system && transaction->installed)
+    {
+       transaction->system=
+         plover_package_set_get_razor(transaction->installed);
+       if (transaction->system)
+           razor_set_ref(transaction->system);
+    }
+    return transaction->system;
+}
+
+struct razor_set *plover_transaction_get_next_set(
+  PloverTransaction *transaction,GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),NULL);
+    if (!transaction->next)
+    {
+       if (!plover_transaction_resolve(transaction,error))
+           return NULL;
+       transaction->next=razor_transaction_commit(transaction->trans);
+    }
+    return transaction->next;
+}
+
+struct razor_install_iterator *plover_transaction_get_install_iterator(
+  PloverTransaction *transaction,GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),NULL);
+    if (!transaction->install_iterator)
+    {
+       if (!plover_transaction_get_next_set(transaction,error))
+           return NULL;
+       (void)plover_transaction_get_system_set(transaction);
+       transaction->install_iterator=
+         razor_set_create_install_iterator(transaction->system,
+         transaction->next);
+    }
+    razor_install_iterator_rewind(transaction->install_iterator);
+    return transaction->install_iterator;
+}
+
+gboolean plover_transaction_commit(PloverTransaction *transaction,
+  GCancellable *cancellable,GError **error)
+{
+    int r;
+    gboolean retval;
+    size_t pos;
+    struct razor_set *set;
+    struct razor_install_iterator *ii;
+    struct razor_atomic *atomic;
+    PloverPackageSet *next;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    if (g_cancellable_set_error_if_cancelled(cancellable,error))
+       return FALSE;
+    ii=plover_transaction_get_install_iterator(transaction,error);
+    if (!ii)
+       return FALSE;
+    do
+    {
+       if (g_cancellable_set_error_if_cancelled(cancellable,error))
+           return FALSE;
+       pos=razor_install_iterator_tell(ii);
+       g_signal_emit(transaction,signals[STATUS_CHANGED],0,
+         "Running pre-transaction scripts");
+       atomic=razor_atomic_open("package transaction");
+       next=plover_package_set_new_from_razor(transaction->next);
+       r=plover_run_transaction(transaction->trans,ii,
+         plover_package_set_get_install_root(transaction->installed),
+         transaction->system,next,transaction->upstream,atomic,
+         transaction->relocations,RAZOR_STAGE_SCRIPTS_PRE,cancellable);
+       if (r<0)
+       {
+           g_signal_emit(transaction,signals[STATUS_CHANGED],0,
+             "Failed in pre-transaction scripts");
+           plover_propagate_razor_error_dup(error,
+             razor_atomic_get_error(atomic));
+           razor_atomic_destroy(atomic);
+           g_object_unref(next);
+           return FALSE;
+       }
+       else
+       {
+           g_signal_emit(transaction,signals[STATUS_CHANGED],0,
+             "Unpacking files");
+           razor_install_iterator_seek(ii,pos);
+           r=plover_run_transaction(transaction->trans,ii,
+             plover_package_set_get_install_root(transaction->installed),
+             transaction->system,next,transaction->upstream,
+             atomic,transaction->relocations,RAZOR_STAGE_FILES,
+#if RAZOR_HAVE_ATOMIC_ROLLBACK
+             cancellable);
+#else
+             NULL);
+#endif
+           if (r==1)
+           {
+               set=razor_install_iterator_commit_set(ii);
+               plover_package_set_update(transaction->installed,set,atomic);
+               razor_set_unref(set);
+           }
+           else if (!r)
+               plover_package_set_update(transaction->installed,
+                 transaction->next,atomic);
+           retval=!razor_atomic_commit(atomic);
+           if (!retval)
+           {
+               g_signal_emit(transaction,signals[STATUS_CHANGED],0,
+                 "Failed to unpack all files correctly");
+               plover_propagate_razor_error_dup(error,
+                 razor_atomic_get_error(atomic));
+           }
+           else
+           {
+               g_signal_emit(transaction,signals[STATUS_CHANGED],0,
+                 "Running post-transaction scripts");
+               razor_install_iterator_seek(ii,pos);
+               plover_run_transaction(transaction->trans,ii,
+                 plover_package_set_get_install_root(transaction->installed),
+                 transaction->system,next,transaction->upstream,
+                 atomic,transaction->relocations,RAZOR_STAGE_SCRIPTS_POST,
+                 NULL);
+           }
+       }
+       razor_atomic_destroy(atomic);
+       g_object_unref(next);
+    } while(retval && r==1);
+    g_signal_emit(transaction,signals[STATUS_CHANGED],0,"Completed");
+    return retval;
+}
+
+static void plover_transaction_commit_async_thread(GTask *task,
+  gpointer source_object,gpointer task_data,GCancellable *cancellable)
+{
+    PloverTransaction *transaction=source_object;
+    GError *error=NULL;
+    if (!plover_transaction_commit(transaction,cancellable,&error))
+       g_task_return_error(task,error);
+    else
+       g_task_return_boolean(task,TRUE);
+    g_object_unref(task);
+}
+
+void plover_transaction_commit_async(PloverTransaction *transaction,
+  GCancellable *cancellable,GAsyncReadyCallback callback,gpointer user_data)
+{
+    GTask *task;
+    g_return_if_fail(PLOVER_IS_TRANSACTION(transaction));
+    task=g_task_new(transaction,cancellable,callback,user_data);
+    g_task_run_in_thread(task,plover_transaction_commit_async_thread);
+}
+
+gboolean plover_transaction_commit_finish(PloverTransaction *transaction,
+  GAsyncResult *result,GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(g_task_is_valid(result,transaction),FALSE);
+    return g_task_propagate_boolean(G_TASK(result),error);
+}
+
+static int plover_mark_package_for_update(struct razor_transaction *trans,
+  struct razor_set *set,const char *pkg)
+{
+    struct razor_package_iterator *pi;
+    struct razor_package *package;
+    const char *name;
+    int retval=-1;
+    pi=razor_package_iterator_create(set);
+    while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_NAME,&name,
+      RAZOR_DETAIL_LAST))
+    {
+       if (!strcmp(name,pkg))
+       {
+           razor_transaction_update_package(trans,package);
+           retval=0;
+           break;
+       }
+    }
+    razor_package_iterator_destroy(pi);
+    return retval;
+}
+
+PloverTransaction *plover_transaction_new(void)
+{
+    return PLOVER_TRANSACTION(g_object_new(PLOVER_TYPE_TRANSACTION,NULL));
+}
+
+void plover_transaction_set_prefix(PloverTransaction *transaction,
+  const char *prefix)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION(transaction));
+    g_return_if_fail(transaction->upstream == NULL);
+    if (!g_strcmp0(prefix,transaction->prefix))
+       return;
+    if (transaction->relocations)
+       razor_relocations_destroy(transaction->relocations);
+    g_free(transaction->prefix);
+    if (prefix)
+    {
+       transaction->relocations=razor_relocations_create();
+       razor_relocations_add(transaction->relocations,"/usr",prefix);
+    }
+    else
+       transaction->relocations=NULL;
+    transaction->prefix=g_strdup(prefix);
+}
+
+void plover_transaction_set_installed(PloverTransaction *transaction,
+  PloverPackageSet *installed)
+{
+    g_return_if_fail(PLOVER_IS_TRANSACTION(transaction));
+    g_return_if_fail(PLOVER_IS_PACKAGE_SET(installed));
+    if (transaction->installed)
+       g_object_unref(transaction->installed);
+    transaction->installed=g_object_ref(installed);
+}
+
+gboolean plover_transaction_root_open(PloverTransaction *transaction,
+  const char *install_root,GError **error)
+{
+    PloverPackageSet *installed;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    if (!install_root)
+       install_root=g_getenv("RAZOR_ROOT");
+    if (!install_root)
+       install_root="";
+    if (transaction->installed && !g_strcmp0(install_root,
+      plover_package_set_get_install_root(transaction->installed)))
+       return TRUE;
+    installed=plover_package_set_new();
+    if (!plover_package_set_open(installed,install_root,TRUE,error))
+    {
+       g_object_unref(installed);
+       return FALSE;
+    }
+    if (transaction->installed)
+       g_object_unref(transaction->installed);
+    transaction->installed=installed;
+    return TRUE;
+}
+
+struct razor_set *plover_transaction_import_yum(PloverTransaction *transaction,
+  const char *base,GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),NULL);
+    g_return_val_if_fail(transaction->base == NULL,NULL);
+    transaction->base=g_strdup(base);
+    return plover_razor_set_create_from_yum(base,error);
+}
+
+gboolean plover_transaction_set_upstream(PloverTransaction *transaction,
+  PloverRepository *upstream,GError **error)
+{
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(PLOVER_IS_REPOSITORY(upstream),FALSE);
+    g_return_val_if_fail(transaction->upstream == NULL,FALSE);
+    transaction->relocated=plover_package_set_new_from_repository(upstream,
+      transaction->relocations,error);
+    if (transaction->relocated)
+       transaction->upstream=g_object_ref(upstream);
+    return !!transaction->relocated;
+}
+
+gboolean
+  plover_transaction_set_upstream_from_yum(PloverTransaction *transaction,
+  const char *base,GError **error)
+{
+    gboolean retval;
+    PloverRepository *upstream;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(transaction->upstream == NULL,FALSE);
+    upstream=plover_repository_new_from_yum(base,error);
+    if (!upstream)
+       return FALSE;
+    retval=plover_transaction_set_upstream(transaction,upstream,error);
+    g_object_unref(upstream);
+    return retval;
+}
+
+gboolean plover_transaction_install(PloverTransaction *transaction,
+  char **pkgs,GError **error)
+{
+    int i;
+    struct razor_set *system,*upstream;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(transaction->upstream != NULL,FALSE);
+    g_return_val_if_fail(transaction->trans == NULL,FALSE);
+    if (!plover_transaction_root_open(transaction,NULL,error))
+       return FALSE;
+    system=plover_transaction_get_system_set(transaction);
+    if (!system)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
+         "Internal error: No system set");
+       return FALSE;
+    }
+    upstream=plover_package_set_get_razor(transaction->relocated);
+    transaction->trans=razor_transaction_create(system,upstream);
+    for(i=0;pkgs[i];i++)
+       if (plover_mark_package_for_update(transaction->trans,system,pkgs[i]) &&
+         plover_mark_package_for_update(transaction->trans,upstream,pkgs[i]))
+       {
+           g_set_error(error,PLOVER_GENERAL_ERROR,
+             PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,"%s: %s",pkgs[i],
+             "Package not found");
+           razor_transaction_destroy(transaction->trans);
+           transaction->trans=NULL;
+           return FALSE;
+           break;
+       }
+    return TRUE;
+}
+
+gboolean plover_transaction_update(PloverTransaction *transaction,
+  char **pkgs,GError **error)
+{
+    int i;
+    struct razor_set *system,*upstream;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(transaction->upstream != NULL,FALSE);
+    g_return_val_if_fail(transaction->trans == NULL,FALSE);
+    if (!plover_transaction_root_open(transaction,NULL,error))
+       return FALSE;
+    system=plover_transaction_get_system_set(transaction);
+    if (!system)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
+         "Internal error: No system set");
+       return FALSE;
+    }
+    upstream=plover_package_set_get_razor(transaction->relocated);
+    transaction->trans=razor_transaction_create(system,upstream);
+    if (!pkgs)
+       razor_transaction_update_all(transaction->trans);
+    else
+       for(i=0;pkgs[i];i++)
+           if (plover_mark_package_for_update(transaction->trans,system,
+             pkgs[i]))
+           {
+               g_set_error(error,PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,"%s: %s",pkgs[i],
+                 "Package not found");
+               razor_transaction_destroy(transaction->trans);
+               transaction->trans=NULL;
+               return FALSE;
+               break;
+           }
+    return TRUE;
+}
+
+PloverTransaction *plover_transaction_new_install(const char *base,
+  const char *prefix,char **pkgs,GError **error)
+{
+    PloverTransaction *transaction;
+    transaction=plover_transaction_new();
+    plover_transaction_set_prefix(transaction,prefix);
+    if (!plover_transaction_set_upstream_from_yum(transaction,base,error))
+    {
+       g_object_unref(transaction);
+       return NULL;
+    }
+    if (!plover_transaction_install(transaction,pkgs,error))
+    {
+       g_object_unref(transaction);
+       return NULL;
+    }
+    return transaction;
+}
+
+PloverTransaction *plover_transaction_new_update(const char *base,
+  const char *prefix,char **pkgs,GError **error)
+{
+    PloverTransaction *transaction;
+    transaction=plover_transaction_new();
+    plover_transaction_set_prefix(transaction,prefix);
+    if (!plover_transaction_set_upstream_from_yum(transaction,base,error))
+    {
+       g_object_unref(transaction);
+       return NULL;
+    }
+    if (!plover_transaction_update(transaction,pkgs,error))
+    {
+       g_object_unref(transaction);
+       return NULL;
+    }
+    return transaction;
+}
+
+static int plover_mark_packages_for_removal(struct razor_transaction *trans,
+  struct razor_set *set,const char *pkg)
+{
+    struct razor_package_iterator *pi;
+    struct razor_package *package;
+    const char *name;
+    int retval=pkg?-1:0;
+    pi=razor_package_iterator_create(set);
+    while (razor_package_iterator_next(pi,&package,RAZOR_DETAIL_NAME,&name,
+      RAZOR_DETAIL_LAST))
+    {
+       if (!pkg || !strcmp(name,pkg))
+       {
+           razor_transaction_remove_package(trans,package);
+           retval=0;
+       }
+    }
+    razor_package_iterator_destroy(pi);
+    return retval;
+}
+
+gboolean plover_transaction_remove(PloverTransaction *transaction,
+  char **pkgs,GError **error)
+{
+    int i;
+    struct razor_set *system,*empty;
+    g_return_val_if_fail(PLOVER_IS_TRANSACTION(transaction),FALSE);
+    g_return_val_if_fail(transaction->trans == NULL,FALSE);
+    if (!plover_transaction_root_open(transaction,NULL,error))
+       return FALSE;
+    system=plover_transaction_get_system_set(transaction);
+    if (!system)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,PLOVER_GENERAL_ERROR_FAILED,
+         "Internal error: No system set");
+       return FALSE;
+    }
+    empty=razor_set_create_without_root();
+    transaction->trans=razor_transaction_create(system,empty);
+    razor_set_unref(empty);
+    if (!pkgs)
+       plover_mark_packages_for_removal(transaction->trans,system,NULL);
+    else
+       for(i=0;pkgs[i];i++)
+           if (plover_mark_packages_for_removal(transaction->trans,system,
+             pkgs[i]))
+           {
+               g_set_error(error,PLOVER_GENERAL_ERROR,
+                 PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,"%s: %s",pkgs[i],
+                 "Package not found");
+               razor_transaction_destroy(transaction->trans);
+               transaction->trans=NULL;
+               return FALSE;
+               break;
+           }
+    return TRUE;
+}
+
+PloverTransaction *plover_transaction_new_remove(char **pkgs,GError **error)
+{
+    PloverTransaction *transaction;
+    transaction=plover_transaction_new();
+    if (!plover_transaction_remove(transaction,pkgs,error))
+    {
+       g_object_unref(transaction);
+       return NULL;
+    }
+    return transaction;
+}
+
+static GList *plover_what_requires(struct razor_set *set,const char *ref_name)
+{
+    const char *name,*version;
+    uint32_t flags;
+    GList *list=NULL;
+    struct razor_property *property;
+    struct razor_package *package;
+    struct razor_package_iterator *what_requires;
+    struct razor_property_iterator *all_props;
+    all_props=razor_property_iterator_create(set,NULL);
+    while (razor_property_iterator_next(all_props,&property,&name,&flags,
+      &version))
+    {
+       if ((flags&RAZOR_PROPERTY_TYPE_MASK)!=RAZOR_PROPERTY_REQUIRES)
+           continue;
+       if (strcmp(name,ref_name))
+           continue;
+       what_requires=razor_package_iterator_create_for_property(set,property);
+       while(razor_package_iterator_next(what_requires,&package,
+         RAZOR_DETAIL_LAST))
+           list=g_list_prepend(list,package);
+       razor_package_iterator_destroy(what_requires);
+    }
+    razor_property_iterator_destroy(all_props);
+    return list;
+}
+
+/* 
+ * Warning: This code is untested and probably wrong.
+ */
+PloverTransaction *plover_transaction_new_remove_with_leaves(char **pkgs,
+  GError **error)
+{
+    int i,changed,is_leaf;
+    uint32_t flags;
+    const char *install_root;
+    const char *name,*version,*maybe_unused_name;
+    struct razor_set *system,*upstream;
+    struct razor_transaction *trans;
+    PloverPackageSet *installed;
+    PloverTransaction *transaction;
+    if (!pkgs)
+       return plover_transaction_new_remove(NULL,error);
+    installed=plover_package_set_new();
+    install_root=g_getenv("RAZOR_ROOT");
+    if (!install_root)
+       install_root="";
+    if (!plover_package_set_open(installed,install_root,TRUE,error))
+    {
+       g_object_unref(installed);
+       return NULL;
+    }
+    system=plover_package_set_get_razor(installed);
+    struct plover_vector *package_names;
+    GList *to_remove,*lnk,*what_requires;
+    struct razor_package *package,*maybe_unused_package;
+    struct razor_property *property;
+    struct razor_package_query *query;
+    struct razor_package_iterator *all_packages,*removed,*maybe_unused;
+    struct razor_property_iterator *removed_props;
+    package_names=plover_vector_new();
+    for(i=0;pkgs[i];i++)
+       plover_vector_append(package_names,pkgs[i]);
+    to_remove=NULL;
+    all_packages=razor_package_iterator_create(system);
+    while (razor_package_iterator_next(all_packages,&package,
+      RAZOR_DETAIL_NAME,&name,RAZOR_DETAIL_LAST))
+       if (plover_vector_remove(package_names,name))
+           to_remove=g_list_prepend(to_remove,package);
+    razor_package_iterator_destroy(all_packages);
+    if (package_names->len)
+    {
+       g_set_error(error,PLOVER_GENERAL_ERROR,
+         PLOVER_GENERAL_ERROR_NO_SUCH_PACKAGE,"%s: %s",
+         package_names->strings[0],"Package not found");
+       plover_vector_free(package_names);
+       g_list_free(to_remove);
+       g_object_unref(installed);
+       return NULL;
+    }
+    plover_vector_free(package_names);
+    do
+    {
+       changed=FALSE;
+       query=razor_package_query_create(system);
+       for(lnk=to_remove;lnk;lnk=lnk->next)
+           razor_package_query_add_package(query,lnk->data);
+       removed=razor_package_query_finish(query);
+       while(razor_package_iterator_next(removed,&package,RAZOR_DETAIL_LAST))
+       {
+           removed_props=razor_property_iterator_create(system,package);
+           while (razor_property_iterator_next(removed_props,&property,&name,
+             &flags,&version))
+           {
+               if ((flags&RAZOR_PROPERTY_TYPE_MASK)!=RAZOR_PROPERTY_REQUIRES)
+                   continue;
+               maybe_unused=razor_package_iterator_create_for_property(system,
+                 property);
+               while(razor_package_iterator_next(maybe_unused,
+                 &maybe_unused_package,RAZOR_DETAIL_NAME,&maybe_unused_name,
+                 RAZOR_DETAIL_LAST))
+               {
+                   if (g_list_find(to_remove,maybe_unused_package))
+                       continue;
+                   is_leaf=TRUE;
+                   what_requires=plover_what_requires(system,
+                     maybe_unused_name);
+                   for(lnk=what_requires;lnk;lnk=lnk->next)
+                       if (!g_list_find(to_remove,lnk->data))
+                       {
+                           is_leaf=FALSE;
+                           break;
+                       }
+                   g_list_free(what_requires);
+                   if (is_leaf)
+                   {
+                       to_remove=g_list_prepend(to_remove,
+                         maybe_unused_package);
+                       changed=TRUE;
+                   }
+               }
+               razor_package_iterator_destroy(maybe_unused);
+           }
+           razor_property_iterator_destroy(removed_props);
+       }
+       razor_package_iterator_destroy(removed);
+    } while(changed);
+    upstream=razor_set_create_without_root();
+    trans=razor_transaction_create(system,upstream);
+    razor_set_unref(upstream);
+    for(lnk=to_remove;lnk;lnk=lnk->next)
+       razor_transaction_remove_package(trans,lnk->data);
+    transaction=plover_transaction_new();
+    transaction->trans=trans;
+    transaction->installed=installed;
+    return transaction;
+}
diff --git a/plover/transaction.h b/plover/transaction.h
new file mode 100644 (file)
index 0000000..8fa61f0
--- /dev/null
@@ -0,0 +1,87 @@
+#ifndef __PLOVER_TRANSACTION_H__
+#define __PLOVER_TRANSACTION_H__
+
+#include <razor.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <plover/packageset.h>
+
+G_BEGIN_DECLS
+
+#define PLOVER_TYPE_TRANSACTION                plover_transaction_get_type()
+#define PLOVER_TRANSACTION(obj)                G_TYPE_CHECK_INSTANCE_CAST(obj,\
+                                         PLOVER_TYPE_TRANSACTION,\
+                                         PloverTransaction)
+#define PLOVER_IS_TRANSACTION(obj)     G_TYPE_CHECK_INSTANCE_TYPE(obj,\
+                                         PLOVER_TYPE_TRANSACTION)
+
+typedef enum {
+    PLOVER_TRANSACTION_RESOLVED=1<<0,
+    PLOVER_TRANSACTION_UNSATISFIED=1<<1,
+} PloverTransactionFlags;
+
+typedef struct _PloverTransaction {
+    GObject parent_instance;
+    PloverTransactionFlags flags;
+    struct razor_transaction *trans;
+    char *prefix;
+    char *base;
+    char *install_root;
+    char *unsatisfied;
+    PloverPackageSet *installed,*relocated;
+    PloverRepository *upstream;
+    struct razor_relocations *relocations;
+    struct razor_set *next,*system;
+    struct razor_install_iterator *install_iterator;
+} PloverTransaction;
+
+typedef struct _PloverTransactionClass {
+    GObjectClass parent_class;
+} PloverTransactionClass;
+
+GType plover_transaction_get_type(void) G_GNUC_CONST;
+void plover_transaction_commit_async(PloverTransaction *transaction,
+  GCancellable *cancellable,GAsyncReadyCallback callback,gpointer user_data);
+gboolean plover_transaction_commit_finish(PloverTransaction *transaction,
+  GAsyncResult *result,GError **error);
+PloverTransaction *plover_transaction_new();
+void plover_transaction_set_prefix(PloverTransaction *transaction,
+  const char *prefix);
+void plover_transaction_set_installed(PloverTransaction *transaction,
+  PloverPackageSet *installed);
+gboolean plover_transaction_root_open(PloverTransaction *transaction,
+  const char *install_root,GError **error);
+struct razor_set *plover_transaction_import_yum(PloverTransaction *transaction,
+  const char *base,GError **error);
+gboolean plover_transaction_set_upstream(PloverTransaction *transaction,
+  PloverRepository *upstream,GError **error);
+gboolean
+  plover_transaction_set_upstream_from_yum(PloverTransaction *transaction,
+  const char *base,GError **error);
+gboolean plover_transaction_install(PloverTransaction *transaction,
+  char **pkgs,GError **error);
+PloverTransaction *plover_transaction_new_install(const char *base,
+  const char *prefix,char **pkgs,GError **error);
+gboolean plover_transaction_update(PloverTransaction *transaction,
+  char **pkgs,GError **error);
+PloverTransaction *plover_transaction_new_update(const char *base,
+  const char *prefix,char **pkgs,GError **error);
+gboolean plover_transaction_remove(PloverTransaction *transaction,
+  char **pkgs,GError **error);
+PloverTransaction *plover_transaction_new_remove(char **pkgs,
+  GError **error);
+gboolean plover_transaction_resolve(PloverTransaction *transaction,
+  GError **error);
+const char *plover_transaction_get_unsatisfied(PloverTransaction *transaction);
+struct razor_set *plover_transaction_get_system_set(
+  PloverTransaction *transaction);
+struct razor_set *plover_transaction_get_next_set(
+  PloverTransaction *transaction,GError **error);
+struct razor_install_iterator *plover_transaction_get_install_iterator(
+  PloverTransaction *transaction,GError **error);
+gboolean plover_transaction_commit(PloverTransaction *transaction,
+  GCancellable *cancellable,GError **error);
+void plover_transaction_commit_async(PloverTransaction *transaction,
+  GCancellable *cancellable,GAsyncReadyCallback callback,gpointer user_data);
+
+#endif /* __PLOVER_TRANSACTION_H__ */
index 6a65e55..6678d27 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009, 2011  J. Ali Harlow <ali@juiblex.co.uk>
+ * Copyright (C) 2009, 2011, 2014  J. Ali Harlow <ali@juiblex.co.uk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include "config.h"
 #include "plover.h"
 
-char *plover_strconcat(const char *string,...)
-{
-    va_list ap,aq;
-    size_t n;
-    char *result=NULL,*t;
-    const char *s;
-    if (string)
-    {
-       va_start(ap,string);
-       va_copy(aq,ap);
-       n=strlen(string);
-       while((s=va_arg(aq,const char *)))
-           n+=strlen(s);
-       va_end(aq);
-       result=malloc(n+1);
-       if (result)
-       {
-           strcpy(result,string);
-           t=result+strlen(result);
-           while((s=va_arg(ap,const char *)))
-           {
-               strcpy(t,s);
-               t+=strlen(t);
-           }
-       }
-       va_end(ap);
-    }
-    return result;
-}
-
-char *plover_default_prefix_for_vendor(const char *vendor)
+gchar *plover_default_prefix_for_vendor(const char *vendor)
 {
 #ifdef WIN32
     /*
@@ -83,12 +53,59 @@ char *plover_default_prefix_for_vendor(const char *vendor)
          buf);
        program_files=buf;
     }
-    return plover_strconcat(program_files,"\\",vendor?vendor:"Plover",NULL);
+    return g_strconcat(program_files,"\\",vendor?vendor:"Plover",NULL);
 #else
     return NULL;
 #endif
 }
 
+gchar *plover_pre_install_prefix(void)
+{
+#ifdef WIN32
+    {
+       HRESULT result;
+       HKEY key;
+       DWORD type,nb;
+       int len;
+       gunichar2 *str2;
+       gchar *root=NULL;
+       result=RegOpenKeyW(HKEY_LOCAL_MACHINE,L"Software\\Plover",&key);
+       if (result==ERROR_SUCCESS)
+       {
+           result=RegQueryValueExW(key,L"Root",0,&type,0,&nb);
+           if (result==ERROR_SUCCESS && type==REG_SZ)
+           {
+               str2=malloc(nb);
+               result=RegQueryValueExW(key,L"Root",0,NULL,(void *)str2,&nb);
+               len=nb/2;
+               if (!str2[len-1])       /* Cope with unterminated strings */
+                   len--;
+               root=g_utf16_to_utf8(str2,len,NULL,NULL,NULL);
+               free(str2);
+           }
+           RegCloseKey(key);
+       }
+       if (!root)
+       {
+           root=plover_default_prefix_for_vendor("Plover Root");
+           result=RegCreateKeyExW(HKEY_LOCAL_MACHINE,L"Software\\Plover",0,
+             NULL,REG_OPTION_NON_VOLATILE,KEY_READ|KEY_WRITE,NULL,&key,NULL);
+           if (result==ERROR_SUCCESS)
+           {
+               str2=g_utf8_to_utf16(root,-1,NULL,NULL,NULL);
+               RegSetValueExW(key,L"Root",0,REG_SZ,(void *)str2,
+                 (strlen(root)+1)*sizeof(gunichar2));
+               g_free(str2);
+               RegCloseKey(key);
+           }
+       }
+       return root;
+    }
+#else
+    return g_strdup("/var/lib/plover/root");
+#endif
+}
+
 /*
  * Get the directory containing the program executable.
  */
@@ -119,3 +136,80 @@ char *plover_get_program_directory(const char *argv0)
        return strdup(".");
 #endif
 }
+
+G_DEFINE_QUARK(plover-razor-error-quark,plover_razor_error)
+G_DEFINE_QUARK(plover-mswin-error-quark,plover_mswin_error)
+G_DEFINE_QUARK(plover-posix-error-quark,plover_posix_error)
+G_DEFINE_QUARK(plover-zlib-error-quark,plover_zlib_error)
+
+void plover_propagate_razor_error_dup(GError **dest,struct razor_error *src)
+{
+    GQuark domain;
+    int code;
+    if (dest)
+    {
+       code=razor_error_get_code(src);
+       switch(razor_error_get_domain(src))
+       {
+           case RAZOR_GENERAL_ERROR:
+               domain=PLOVER_RAZOR_ERROR;
+               break;
+           case RAZOR_POSIX_ERROR:
+               domain=PLOVER_POSIX_ERROR;
+               break;
+           case RAZOR_MSWIN_ERROR:
+               domain=PLOVER_MSWIN_ERROR;
+               break;
+           case RAZOR_ZLIB_ERROR:
+               domain=PLOVER_ZLIB_ERROR;
+               break;
+           case PLOVER_GENERAL_ERROR:
+               if (code==PLOVER_GENERAL_ERROR_CANCELLED)
+               {
+                   domain=G_IO_ERROR;
+                   code=G_IO_ERROR_CANCELLED;
+                   break;
+               }
+               /* else fall though */
+           default:
+               domain=PLOVER_RAZOR_ERROR;
+               code=RAZOR_GENERAL_ERROR_FAILED;
+       }
+       *dest=g_error_new_literal(domain,code,razor_error_get_msg(src));
+    }
+}
+
+void plover_propagate_razor_error(GError **dest,struct razor_error *src)
+{
+    plover_propagate_razor_error_dup(dest,src);
+    razor_error_free(src);
+}
+
+void plover_propagate_g_error(struct razor_error **dest,GError *src)
+{
+    int domain,code;
+    if (dest)
+    {
+       code=src->code;
+       if (src->domain==PLOVER_RAZOR_ERROR)
+           domain=RAZOR_GENERAL_ERROR;
+       else if (src->domain==PLOVER_POSIX_ERROR)
+           domain=RAZOR_POSIX_ERROR;
+       else if (src->domain==PLOVER_MSWIN_ERROR)
+           domain=RAZOR_MSWIN_ERROR;
+       else if (src->domain==PLOVER_ZLIB_ERROR)
+           domain=RAZOR_ZLIB_ERROR;
+       else if (src->domain==G_IO_ERROR && code==G_IO_ERROR_CANCELLED)
+       {
+           domain=PLOVER_GENERAL_ERROR;
+           code=PLOVER_GENERAL_ERROR_CANCELLED;
+       }
+       else
+       {
+           domain=RAZOR_GENERAL_ERROR;
+           code=RAZOR_GENERAL_ERROR_FAILED;
+       }
+       *dest=razor_error_new_str(domain,code,NULL,src->message);
+    }
+    g_error_free(src);
+}
diff --git a/plover/vector.c b/plover/vector.c
new file mode 100644 (file)
index 0000000..cefced6
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2009, 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "config.h"
+#include "plover.h"
+
+struct plover_vector *plover_vector_new(void)
+{
+    struct plover_vector *vector;
+    vector=malloc(sizeof(*vector));
+    vector->len=0;
+    vector->alloc=16;
+    vector->strings=calloc(vector->alloc,sizeof(char *));
+    return vector;
+}
+
+struct plover_vector *plover_vector_dup(struct plover_vector *old)
+{
+    int i;
+    struct plover_vector *vector;
+    vector=malloc(sizeof(*vector));
+    vector->len=old->len;
+    vector->alloc=old->alloc;
+    vector->strings=calloc(vector->alloc,sizeof(char *));
+    for(i=0;i<old->len;i++)
+       vector->strings[i]=strdup(old->strings[i]);
+    vector->strings[i]=NULL;
+    return vector;
+}
+
+void plover_vector_append(struct plover_vector *vector,const char *str)
+{
+    if (++(vector->len)>=vector->alloc)
+    {
+       vector->alloc*=2;
+       vector->strings=realloc(vector->strings,vector->alloc*sizeof(char *));
+    }
+    vector->strings[vector->len-1]=strdup(str);
+    vector->strings[vector->len]=NULL;
+}
+
+int plover_vector_contains(struct plover_vector *vector,const char *str)
+{
+    int i;
+    for(i=0;i<vector->len;i++)
+       if (!strcmp(vector->strings[i],str))
+           return 1;
+    return 0;
+}
+
+int plover_vector_remove(struct plover_vector *vector,const char *str)
+{
+    int i;
+    for(i=0;i<vector->len;i++)
+       if (!strcmp(vector->strings[i],str))
+       {
+           free(vector->strings[i]);
+           vector->len--;
+           vector->strings[i]=vector->strings[vector->len];
+           vector->strings[vector->len]=NULL;
+           return 1;
+       }
+    return 0;
+}
+
+static int string_compar(const void *p1,const void *p2)
+{
+    return strcmp(*(char * const *)p1,*(char * const *)p2);
+}
+
+void plover_vector_sort(struct plover_vector *vector)
+{
+    qsort(vector->strings,vector->len,sizeof(char *),string_compar);
+}
+
+char *plover_vector_format_for_display(struct plover_vector *vector)
+{
+    int i,len;
+    char *s,*p;
+    if (!vector->len)
+       return strdup("none");
+    else if (vector->len==1)
+       return strdup(vector->strings[0]);
+    else
+    {
+       len=(vector->len-1)*2+5+1;
+       for(i=0;i<vector->len;i++)
+           len+=strlen(vector->strings[i]);
+       s=malloc(len);
+       strcpy(s,vector->strings[0]);
+       p=s+strlen(s);
+       for(i=1;i<vector->len-1;i++)
+       {
+           *p++=',';
+           *p++=' ';
+           strcpy(p,vector->strings[i]);
+           p+=strlen(p);
+       }
+       strcpy(p," and ");
+       p+=5;
+       strcpy(p,vector->strings[i]);
+       return s;
+    }
+}
+
+void plover_vector_free(struct plover_vector *vector)
+{
+    int i;
+    for(i=0;i<vector->len;i++)
+       free(vector->strings[i]);
+    free(vector->strings);
+    free(vector);
+}
diff --git a/pre-inst/Makefile.am b/pre-inst/Makefile.am
new file mode 100644 (file)
index 0000000..658ea74
--- /dev/null
@@ -0,0 +1,32 @@
+AM_CFLAGS=-g $(SETUP_CFLAGS)
+LDADD=../plover/libplover.la $(SETUP_LIBS)
+if PLOVER_MINGW
+LDADD+=-lcomctl32
+# pre-inst is not intended to produce any output (and any that is produced
+# can be safely junked). Thus -mwindows is appropriate since if called with
+# a console window attached no output should be visible anyway and if called
+# when no console window is attached we don't want one to be created.
+AM_LDFLAGS=-mwindows
+endif
+INCLUDES=-I$(top_srcdir) -I$(srcdir)
+
+bin_PROGRAMS=pre-inst
+
+pre_inst_SOURCES=pre-inst.c
+pre_inst_LDFLAGS=$(AM_LDFLAGS) -all-static
+if HAVE_WINDRES
+pre_inst_SOURCES+=resources.rc pre-inst.exe.manifest resource.h
+endif
+
+.rc.$(OBJEXT):
+       $(AM_V_GEN)$(WINDRES) $(INCLUDES) $< $@
+
+resources.$(OBJEXT): resources.rc resource.h pre-inst.exe.manifest pre-inst.ico
+
+.png.pnm:
+       $(AM_V_GEN)pngtopnm $< | pnmquant -quiet 256 > $@
+
+pre-inst.ico:     icon16.pnm icon22.pnm icon32.pnm
+       $(AM_V_GEN)ppmtowinicon -output=$@ $^
+
+EXTRA_DIST=icon16.png icon22.png icon32.png
diff --git a/pre-inst/icon16.png b/pre-inst/icon16.png
new file mode 100644 (file)
index 0000000..0ca1590
Binary files /dev/null and b/pre-inst/icon16.png differ
diff --git a/pre-inst/icon22.png b/pre-inst/icon22.png
new file mode 100644 (file)
index 0000000..4b7f82c
Binary files /dev/null and b/pre-inst/icon22.png differ
diff --git a/pre-inst/icon32.png b/pre-inst/icon32.png
new file mode 100644 (file)
index 0000000..361cd51
Binary files /dev/null and b/pre-inst/icon32.png differ
diff --git a/pre-inst/manifest.xml.in b/pre-inst/manifest.xml.in
new file mode 100644 (file)
index 0000000..fe6a9ec
--- /dev/null
@@ -0,0 +1,29 @@
+changequote([,])dnl
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <assemblyIdentity
+    version="@PLOVER_MAJOR_VERSION@.@PLOVER_MINOR_VERSION@.@PLOVER_MICRO_VERSION@.0"
+    name="The plover development team.plover.pre-inst" type="win32"
+    processorArchitecture="ifelse([@HOST_CPU@],[x86_64],[ia64],[x86])" />
+  <description>Plover pre-inst program</description>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 7 functionality -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls"
+        version="6.0.0.0" processorArchitecture="*"
+       publicKeyToken="6595b64144ccf1df" language="*"/>
+    </dependentAssembly>
+  </dependency>
+</assembly>
diff --git a/pre-inst/pre-inst.c b/pre-inst/pre-inst.c
new file mode 100644 (file)
index 0000000..52a4ded
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * References:
+ *     http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/
+ */
+
+#include "config.h"
+#ifndef WIN32
+#define _XOPEN_SOURCE 500
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <lua.h>
+#include <razor.h>
+#include <plover/plover.h>
+#include <whelk/whelk.h>
+#ifdef WIN32
+#include <windows.h>
+#include <process.h>
+#include <commctrl.h>
+#include "resource.h"
+
+#ifndef FOF_NO_UI
+#define FOF_NO_UI      (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
+                       FOF_NOCONFIRMMKDIR)
+#endif
+
+#else  /* WIN32 */
+#include <ftw.h>
+#endif /* WIN32 */
+
+#ifdef WIN32
+/* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
+#undef USE_G_SPAWN
+#else
+#define USE_G_SPAWN
+#endif
+
+LUALIB_API int luaopen_posix(lua_State *L);
+
+#ifdef WIN32
+DWORD main_thread_id;
+#endif
+
+gchar *prefix;
+
+int verify_and_fix(const char *root)
+{
+    return 0;
+}
+
+#ifdef WIN32
+INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
+  LPARAM l_param)
+{
+    HWND progress;
+    DWORD style;
+    switch (msg)
+    {
+       case WM_INITDIALOG:
+           progress=GetDlgItem(dialog,IDC_PROGRESS); 
+           style=GetWindowLong(progress,GWL_STYLE);
+           SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
+           SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
+           return (INT_PTR)TRUE;
+    }
+    return (INT_PTR)FALSE;
+}
+#endif
+
+#ifdef WIN32
+__stdcall
+#endif
+unsigned pre_install_thread(void *data)
+{
+    int retval;
+    char *path=data;
+    gchar *s;
+    char *install[]={"plover-gtkui",NULL};
+    GError *error=NULL;
+    prefix=plover_pre_install_prefix();
+    s=g_strconcat(prefix,"/var/log/pre-install",NULL);
+    plover_log_open(s);
+    g_free(s);
+    s=g_strconcat(prefix,"/var/lib/razor",NULL);
+    razor_set_database_path(s);
+    g_free(s);
+    if (verify_and_fix(prefix))
+    {
+       free(path);
+       g_free(prefix);
+       return -1;
+    }
+    retval=!plover_install(path,prefix,install,&error);
+    if (!retval)
+       retval=!plover_update(path,prefix,NULL,&error);
+    if (error)
+    {
+       fprintf(stderr,"%s\n",error->message);
+       g_error_free(error);
+    }
+    free(path);
+#ifdef WIN32
+    PostQuitMessage(retval);
+    PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
+    _endthreadex(retval);
+#endif
+    return retval;
+}
+
+/*
+ * The idea is that if pre_install() fails, update/setup should fall back
+ * to console interfaces.
+ */
+#ifdef WIN32
+HANDLE
+#else
+void *
+#endif
+pre_install(const char *argv0)
+{
+#ifdef WIN32
+    HANDLE retval;
+#else
+    void *retval;
+#endif
+    char *path;
+    razor_set_lua_loader("posix",luaopen_posix);
+    razor_set_lua_loader("whelk",luaopen_whelk);
+    path=plover_get_program_directory(argv0);
+#ifdef WIN32
+    retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,path,0,NULL);
+#else
+    if (pre_install_thread(path))
+       retval=NULL;
+    else
+       retval=(void *)1;               /* Non-NULL to indicate success */
+#endif
+    return retval;
+}
+
+#ifndef WIN32
+int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
+  struct FTW *ftwbuf)
+{
+    (void)remove(fpath);
+    return 0;
+}
+#endif
+
+gboolean deltree(const char *path)
+{
+#ifdef WIN32
+    /* Based on g_local_file_trash() */
+    SHFILEOPSTRUCTW op={0};
+    gboolean success;
+    wchar_t *wfilename;
+    long len;
+    wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
+    /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
+    wfilename=g_renew(wchar_t,wfilename,len+2);
+    wfilename[len+1]=0;
+    op.wFunc=FO_DELETE;
+    op.pFrom=wfilename;
+    op.fFlags=FOF_NO_UI;
+    success=!SHFileOperationW(&op);
+    if (success && op.fAnyOperationsAborted)
+       success=FALSE;
+    g_free(wfilename);
+    return success;
+#else
+    return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
+#endif
+}
+
+gboolean pre_uninstall(void)
+{
+    gboolean success;
+    gchar *s;
+    GError *error=NULL;
+    razor_set_lua_loader("posix",luaopen_posix);
+    razor_set_lua_loader("whelk",luaopen_whelk);
+    prefix=plover_pre_install_prefix();
+    s=g_strconcat(prefix,"/var/lib/razor",NULL);
+    razor_set_database_path(s);
+    g_free(s);
+    success=plover_remove(NULL,&error);
+    if (error)
+    {
+       fprintf(stderr,"%s\n",error->message);
+       g_error_free(error);
+    }
+    deltree(prefix);
+    return success;
+}
+
+#if defined(WIN32) && !defined(USE_G_SPAWN)
+/* Based on glib's g_spawn_win32.c */
+
+static gchar *
+win32_cmdline_quote(const char *string)
+{
+    const gchar *p=string;
+    gchar *retval,*q;
+    gint len=0;
+    gboolean need_dblquotes=FALSE;
+    while (*p)
+    {
+       if (*p==' ' || *p=='\t')
+           need_dblquotes=TRUE;
+       else if (*p=='"')
+           len++;
+       else if (*p=='\\')
+       {
+           const gchar *pp=p;
+           while (*pp && *pp=='\\')
+               pp++;
+           if (*pp=='"')
+               len++;
+       }
+       len++;
+       p++;
+    }
+    q=retval=g_malloc(len+need_dblquotes*2+1);
+    p=string;
+    if (need_dblquotes)
+       *q++='"';
+    while (*p)
+    {
+       if (*p=='"')
+           *q++='\\';
+       else if (*p=='\\')
+       {
+           const gchar *pp=p;
+           while (*pp && *pp=='\\')
+               pp++;
+           if (*pp=='"')
+               *q++='\\';
+       }
+       *q++=*p;
+       p++;
+    }
+    if (need_dblquotes)
+       *q++='"';
+    *q++='\0';
+    return retval;
+}
+
+/* Create a win32-style wide-character argv with suitable quoting */
+wchar_t **win32_argv_import(char **argv,GError **error)
+{
+    int i,n;
+    gchar *s;
+    wchar_t **wargv;
+    GError *tmp_error=NULL;
+    n=g_strv_length(argv);
+    wargv=g_new(wchar_t *,n+1);
+    for(i=0;i<n;i++)
+    {
+       s=win32_cmdline_quote(argv[i]);
+       wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
+       g_free(s);
+       if (!wargv[i])
+       {
+           g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
+             "Invalid argument #%d: %s",i,tmp_error->message);
+           g_error_free(tmp_error);
+           for(i--;i>=0;i--)
+               g_free(wargv[i]);
+           g_free(wargv);
+           return FALSE;
+       }
+    }
+    wargv[i]=NULL;
+    return wargv;
+}
+
+gboolean spawn_sync(char **argv,GError **error)
+{
+    wchar_t *wargv0,**wargv;
+    gintptr rc;
+    GError *tmp_error=NULL;
+    wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
+    if (!wargv0)
+    {
+       fprintf(stderr,"Conversion error in post\n");
+       g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
+         "Invalid program name: %s",tmp_error->message);
+       g_error_free(tmp_error);
+       return FALSE;
+    }
+    wargv=win32_argv_import(argv,error);
+    if (!wargv)
+    {
+       fprintf(stderr,"Conversion error in post\n");
+       g_free(wargv0);
+       return FALSE;
+    }
+    errno=0;
+    rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
+    g_free(wargv0);
+    g_strfreev((gchar **)wargv);
+    if (rc==-1 && errno!=0)
+    {
+       fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
+       g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
+         "Failed to execute post command: %s",g_strerror(errno));
+       return FALSE;
+    }
+    if (rc!=EXIT_SUCCESS)
+    {
+       fprintf(stderr,"post command failed (%ld)\n",(long)rc);
+       g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
+         "Post command exited with code %ld",(long)rc);
+       return FALSE;
+    }
+    return TRUE;
+}
+
+#endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
+
+/*
+ * Run a command after completing request.
+ *
+ * Command may refer to %INSTALL_PREFIX% which will be replaced by the
+ * (first) install prefix used and/or %TEST_RESULT% which will be replaced
+ * bu either "pass" or "fail" depending as to whether the request succeeded
+ * or not. Command may also include double quotes which will be used to
+ * affect how the command is split into arguments much like a shell does.
+ */
+gboolean run_post(int argc,char **argv,gboolean test_result,GError **error)
+{
+    int i,post_argc;
+    char *s;
+    gchar *expanded;
+    gchar **post_argv;
+#ifdef USE_G_SPAWN
+    gchar *standard_output,*standard_error;
+    int exit_status;
+#endif
+    GError *tmp_error=NULL;
+    if (argc<1)
+    {
+       g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
+         "--post: No command given");
+       return FALSE;
+    }
+    printf("Running post command: %s\n",argv[1]);
+    if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
+    {
+       g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
+       return FALSE;
+    }
+    for(i=0;i<post_argc;i++)
+    {
+       s=strstr(post_argv[i],"%INSTALL_PREFIX%");
+       if (s)
+       {
+           *s='\0';
+           s+=strlen("%INSTALL_PREFIX%");
+           expanded=g_strconcat(post_argv[i],prefix,s,NULL);
+           g_free(post_argv[i]);
+           post_argv[i]=expanded;
+       }
+       s=strstr(post_argv[i],"%TEST_RESULT%");
+       if (s)
+       {
+           *s='\0';
+           s+=strlen("%TEST_RESULT%");
+           expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
+           g_free(post_argv[i]);
+           post_argv[i]=expanded;
+       }
+    }
+#ifdef USE_G_SPAWN
+    if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
+      &standard_output,&standard_error,&exit_status,&tmp_error))
+    {
+       fprintf(stderr,"Failed to start post command\n");
+       g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
+       return FALSE;
+    }
+    if (standard_output && *standard_output)
+    {
+       printf("Output from post command %s:\n",post_argv[0]);
+       fputs(standard_output,stdout);
+    }
+    g_free(standard_output);
+    if (standard_error && *standard_error)
+    {
+       printf("Error output from post command %s:\n",post_argv[0]);
+       fputs(standard_error,stdout);
+    }
+    g_free(standard_error);
+    if (!g_spawn_check_exit_status(exit_status,&tmp_error))
+    {
+       fprintf(stderr,"post command failed\n");
+       g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
+       return FALSE;
+    }
+#else
+    if (!spawn_sync(post_argv,&tmp_error))
+    {
+       g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
+       return FALSE;
+    }
+#endif
+    return TRUE;
+}
+
+#ifdef WIN32
+DWORD win32_pre_install_gui(char *argv0)
+{
+    HANDLE thread;
+    INITCOMMONCONTROLSEX icc={0,};
+    MSG msg;
+    DWORD retval;
+    main_thread_id=GetCurrentThreadId();
+    thread=(HANDLE)pre_install(argv0);
+    if (!thread)
+       return EXIT_FAILURE;
+    icc.dwSize=sizeof(icc);
+    icc.dwICC=ICC_WIN95_CLASSES;
+    InitCommonControlsEx(&icc);
+    DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
+      &ProgressDialogProc);
+    while(GetMessage(&msg,NULL,0,0)>0)
+    {
+       TranslateMessage(&msg);
+       DispatchMessage(&msg);
+    }
+    WaitForSingleObject(thread,INFINITE);
+    GetExitCodeThread(thread,&retval);
+    CloseHandle(thread);
+    return retval;
+}
+#endif /* WIN32 */
+
+int main(int argc,char **argv)
+{
+    gboolean success;
+    GError *error=NULL;
+#ifdef WIN32
+    /*
+     * pre-inst is normally a GUI application, but rpm scripts may well
+     * call console applications and it looks ugly if console windows keep
+     * popping up. Avoid this by allocating our own console and hiding it.
+     * Note:
+     * - If pre-inst is a console application (typically for debugging),
+     *    then skip this step.
+     *  - Call ShowWindow twice to negate special handling on first call.
+     */
+    if (!GetConsoleWindow())
+    {
+       AllocConsole();
+       ShowWindow(GetConsoleWindow(),SW_HIDE);
+       ShowWindow(GetConsoleWindow(),SW_HIDE);
+    }
+#endif
+    if (argc>1 && !strcmp(argv[1],"-u"))
+    {
+       success=pre_uninstall();
+       argc--;
+       argv++;
+    }
+    else
+#ifdef WIN32
+       success=win32_pre_install_gui(argv[0])==EXIT_SUCCESS;
+#else
+       success=!!pre_install(argv[0]);
+#endif
+    if (argc>1 && !strcmp(argv[1],"--post") &&
+      !run_post(argc-1,argv+1,success,&error))
+    {
+#ifndef WIN32
+       fprintf(stderr,"Error in post: %s\n",error->message);
+#else
+       MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
+#endif
+       g_error_free(error);
+       success=FALSE;
+    }
+#ifdef WIN32
+    return success?EXIT_SUCCESS:EXIT_FAILURE;
+#else
+    return success?0:1;
+#endif
+}
diff --git a/pre-inst/resource.h b/pre-inst/resource.h
new file mode 100644 (file)
index 0000000..e72598d
--- /dev/null
@@ -0,0 +1,6 @@
+#define IDD_PROGRESSDIALOG             100
+#define IDC_PROGRESS                   1000
+
+#ifndef IDC_STATIC
+#define IDC_STATIC                     -1
+#endif
diff --git a/pre-inst/resources.rc.in b/pre-inst/resources.rc.in
new file mode 100644 (file)
index 0000000..d4d967c
--- /dev/null
@@ -0,0 +1,47 @@
+#include <windows.h>
+#include "resource.h"
+
+#pragma code_page(65001)
+
+MAINICON ICON "pre-inst.ico"
+
+VS_VERSION_INFO VERSIONINFO
+    FILEVERSION @PLOVER_MAJOR_VERSION@,@PLOVER_MINOR_VERSION@,
+      @PLOVER_MICRO_VERSION@,0
+    PRODUCTVERSION @PLOVER_MAJOR_VERSION@,@PLOVER_MINOR_VERSION@,
+      @PLOVER_MICRO_VERSION@,0
+    FILEOS VOS__WINDOWS32
+    FILETYPE VFT_APP
+    {
+       BLOCK "StringFileInfo"
+       {
+           BLOCK "080904B0"
+           {
+               VALUE "CompanyName","The plover development team"
+               VALUE "FileDescription","Plover pre-inst program"
+               VALUE "FileVersion","@PACKAGE_VERSION@"
+               VALUE "InternalName","pre-inst"
+               VALUE "LegalCopyright",
+                 "Copyright © 2014 J. Ali Harlow et al"
+               VALUE "OriginalFilename","pre-inst.exe"
+               VALUE "ProductName","plover"
+               VALUE "ProductVersion","@PACKAGE_VERSION@"
+           }
+       }
+       BLOCK "VarFileInfo"
+       {
+           VALUE "Translation",0x809,0x4B0
+       }
+    }
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "pre-inst.exe.manifest"
+
+IDD_PROGRESSDIALOG DIALOGEX 0,0,167,67
+    STYLE DS_SETFONT|DS_MODALFRAME|DS_FIXEDSYS|WS_POPUP|WS_CAPTION
+    CAPTION "Preparing to install..."
+    FONT 8,"MS Shell Dlg",400,0,0x1
+    {
+       ICON MAINICON,IDC_STATIC,10,17,20,20
+       LTEXT "Unpacking installation program.",IDC_STATIC,45,16,106,8
+       CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,45,28,106,8
+    }
index a1e148b..14e1737 100644 (file)
@@ -3,6 +3,7 @@ LDADD=../plover/libplover.la $(SETUP_LIBS)
 INCLUDES=-I$(top_srcdir)
 
 bin_PROGRAMS=setup
+bin_SCRIPTS=setup.js
 
 setup_SOURCES=setup.c
 setup_LDFLAGS=-all-static
@@ -10,13 +11,19 @@ if HAVE_WINDRES
 setup_SOURCES+=resources.rc setup.exe.manifest
 endif
 
-.png.pnm:
-       pngtopnm $< | pnmquant 256 > $@
+.rc.$(OBJEXT):
+       $(AM_V_GEN)$(WINDRES) resources.rc $@
 
 resources.$(OBJEXT): resources.rc setup.exe.manifest setup.ico
-       $(WINDRES) resources.rc $@
+
+%.js: $(srcdir)/%.js.in
+       $(AM_V_GEN)sed -e 's/$$/\r/' $(srcdir)/$@.in > $@
+
+.png.pnm:
+       $(AM_V_GEN)pngtopnm $< | pnmquant -quiet 256 > $@
 
 setup.ico:     icon16.pnm icon22.pnm icon32.pnm
-       ppmtowinicon -output=$@ $^
+       $(AM_V_GEN)ppmtowinicon -output=$@ $^
 
-EXTRA_DIST=icon16.png icon22.png icon32.png
+EXTRA_DIST=icon16.png icon22.png icon32.png setup.js.in
+CLEANFILES=setup.js
index ce69c18..87666a0 100644 (file)
@@ -1,6 +1,8 @@
 #include <winver.h>
 #include <winuser.h>
 
+#pragma code_page(65001)
+
 MAINICON ICON "setup.ico"
 
 VS_VERSION_INFO VERSIONINFO
@@ -20,7 +22,7 @@ VS_VERSION_INFO VERSIONINFO
                VALUE "FileVersion","@PACKAGE_VERSION@"
                VALUE "InternalName","setup"
                VALUE "LegalCopyright",
-                 "Copyright (c) 2009,2011,2012 J. Ali Harlow et al"
+                 "Copyright © 2009,2011,2012 J. Ali Harlow et al"
                VALUE "OriginalFilename","setup.exe"
                VALUE "ProductName","plover"
                VALUE "ProductVersion","@PACKAGE_VERSION@"
index c6cf79e..4804d74 100644 (file)
 
 LUALIB_API int luaopen_posix(lua_State *L);
 
-struct vector {
-    int len,alloc;
-    char **strings;
-};
-
-struct vector *vector_new(void)
-{
-    struct vector *vector;
-    vector=malloc(sizeof(*vector));
-    vector->len=0;
-    vector->alloc=16;
-    vector->strings=calloc(vector->alloc,sizeof(char *));
-    return vector;
-}
-
-void vector_append(struct vector *vector,const char *str)
-{
-    if (++(vector->len)>=vector->alloc)
-    {
-       vector->alloc*=2;
-       vector->strings=realloc(vector->strings,vector->alloc*sizeof(char *));
-    }
-    vector->strings[vector->len-1]=strdup(str);
-    vector->strings[vector->len]=NULL;
-}
-
-int vector_contains(struct vector *vector,const char *str)
-{
-    int i;
-    for(i=0;i<vector->len;i++)
-       if (!strcmp(vector->strings[i],str))
-           return 1;
-    return 0;
-}
-
-void vector_free(struct vector *vector)
-{
-    int i;
-    for(i=0;i<vector->len;i++)
-       free(vector->strings[i]);
-    free(vector->strings);
-    free(vector);
-}
-
 void setup(const char *argv0)
 {
-    char *path,*s,*prefix;
+    char *path;
+    gchar *s,*prefix;
     int ch,changed;
     struct comps *comps;
     struct comps_group *group;
     struct comps_requirement *pkg;
-    struct vector *packages=NULL;
+    struct plover_vector *packages=NULL;
+    GError *error=NULL;
     path=plover_get_program_directory(argv0);
-    s=plover_strconcat(path,"/repodata/comps.xml",NULL);
+    s=g_strconcat(path,"/repodata/comps.xml",NULL);
     comps=plover_comps_new_from_file(s);
     if (!comps)
     {
        perror(s);
        exit(1);
     }
-    free(s);
+    g_free(s);
     prefix=plover_default_prefix_for_vendor(comps->vendor);
     if (!plover_installed_files_match_prefix(prefix))
     {
@@ -103,7 +61,12 @@ void setup(const char *argv0)
            exit(1);
        while(ch!='\n' && ch!=EOF)
            ch=getchar();
-       plover_remove(NULL);
+       if (plover_remove(NULL,&error))
+       {
+           fprintf(stderr,"%s\n",error->message);
+           g_error_free(error);
+           exit(1);
+       }
     }
     group=plover_comps_lookup_group(comps,"base");
     if (!group)
@@ -111,21 +74,21 @@ void setup(const char *argv0)
        fprintf(stderr,"No base group found in comps.xml\n");
        exit(1);
     }
-    packages=vector_new();
+    packages=plover_vector_new();
     do
     {
        changed=0;
        for(pkg=group->packages;pkg;pkg=pkg->next)
        {
-           if (vector_contains(packages,pkg->name))
+           if (plover_vector_contains(packages,pkg->name))
                continue;
            if (pkg->type==COMPS_REQUIREMENT_DEFAULT ||
              pkg->type==COMPS_REQUIREMENT_MANDATORY ||
              pkg->type==COMPS_REQUIREMENT_CONDITIONAL &&
-             vector_contains(packages,pkg->requires))
+             plover_vector_contains(packages,pkg->requires))
            {
                changed++;
-               vector_append(packages,pkg->name);
+               plover_vector_append(packages,pkg->name);
            }
        }
     } while(changed);
@@ -135,18 +98,31 @@ void setup(const char *argv0)
        fprintf(stderr,"No packages to install\n");
        exit(1);
     }
-    plover_install(path,prefix,packages->strings);
-    vector_free(packages);
-    free(prefix);
+    if (!plover_install(path,prefix,packages->strings,&error))
+    {
+       fprintf(stderr,"%s\n",error->message);
+       g_error_free(error);
+       exit(1);
+    }
+    plover_vector_free(packages);
+    g_free(prefix);
     free(path);
 }
 
 int main(int argc,char **argv)
 {
+    GError *error=NULL;
     razor_set_lua_loader("posix",luaopen_posix);
     razor_set_lua_loader("whelk",luaopen_whelk);
     if (argc>1 && !strcmp(argv[1],"-u"))
-       plover_remove(NULL);
+    {
+       if (!plover_remove(NULL,&error))
+       {
+           fprintf(stderr,"%s\n",error->message);
+           g_error_free(error);
+           exit(1);
+       }
+    }
     else
        setup(argv[0]);
     exit(0);
diff --git a/setup/setup.js.in b/setup/setup.js.in
new file mode 100644 (file)
index 0000000..2b73f07
--- /dev/null
@@ -0,0 +1,92 @@
+var WshShell = WScript.CreateObject("WScript.Shell");
+var ScriptPath = WScript.ScriptFullName.replace(/\\[^\\]*$/, "");
+var FileSystemObject = new ActiveXObject("Scripting.FileSystemObject");
+
+function RunSetup(args)
+{
+    var OKButton = 0, ErrorIcon = 16, ForReading = 1;
+    var path, command, i, ExecObject, text;
+    path = FileSystemObject.BuildPath(ScriptPath, "setup.exe");
+    command = "\"" + path + "\"";
+    for(i = 0; i < args.Length; i++)
+       command += " \"" + args.Item(i) + "\"";
+    ExecObject = WshShell.Exec(command);
+    if (!ExecObject.StdErr.AtEndOfStream)
+    {
+       text = "Setup failed for an unexpected reason. Please report "
+         + "the following error to support@city-occupational.co.uk:\n\n"
+         + ExecObject.StdErr.ReadAll();
+       WshShell.Popup(text, 0, "Setup failed", OKButton + ErrorIcon);
+       return false;
+    }
+    else if (ExecObject.ExitCode)
+    {
+       text = "Setup failed without giving a reason. Please report "
+         + "this to support@city-occupational.co.uk";
+       WshShell.Popup(text, 0, "Setup failed", OKButton + ErrorIcon);
+       return false;
+    }
+    return true;
+}
+
+function Install()
+{
+    var path;
+    path = FileSystemObject.BuildPath(ScriptPath, "pre-inst.exe");
+    WshShell.Run("\"" + path + "\"" + " --post \"wscript.exe"
+      + " \\\"" + WScript.ScriptFullName + "\\\""
+      + " --post %INSTALL_PREFIX% %TEST_RESULT%\"",
+      7, true);
+}
+
+function Uninstall()
+{
+    var path;
+    path = FileSystemObject.BuildPath(ScriptPath, "pre-inst.exe");
+    WshShell.Run("\"" + path + "\"" + " -u --post \"wscript.exe"
+      + " \\\"" + WScript.ScriptFullName + "\\\"" + " --postun\"", 7, true);
+}
+
+function PostInstall()
+{
+    var OKButton = 0, InfoIcon = 64, text;
+    var test_result, install_prefix, path, args;
+    install_prefix = WScript.Arguments.Item(1);
+    test_result = WScript.Arguments.Item(2);
+    if (test_result == "pass")
+    {
+       path = FileSystemObject.BuildPath(install_prefix,
+         "bin\\app-manager.exe");
+       WshShell.Run("\"" + path + "\" --setup \"" + ScriptPath + "\"");
+    }
+    else
+    {
+       args = { Length:0, Item:function(){return nil} };
+       if (RunSetup(args))
+       {
+           text = "Software installation completed successfully.";
+           WshShell.Popup(text, 0, "Software Installation",
+             OKButton + InfoIcon);
+       }
+    }
+}
+
+function PostUninstall()
+{
+    var args = { Length:1, Item:function(){return "-u"} };
+    var OKButton = 0, InfoIcon = 64, text;
+    if (RunSetup(args))
+    {
+       text = "Uninstall completed successfully.";
+       WshShell.Popup(text, 0, "Uninstall", OKButton + InfoIcon);
+    }
+}
+
+if (WScript.Arguments.Length < 1)
+    Install()
+else if (WScript.Arguments.Item(0) == "-u")
+    Uninstall()
+else if (WScript.Arguments.Length >= 3 && WScript.Arguments.Item(0) == "--post")
+    PostInstall()
+else if (WScript.Arguments.Item(0) == "--postun")
+    PostUninstall()
index bd8b841..4b67d18 100644 (file)
@@ -3,6 +3,7 @@ LDADD=../plover/libplover.la $(SETUP_LIBS)
 INCLUDES=-I$(top_srcdir)
 
 bin_PROGRAMS=update
+bin_SCRIPTS=update.js
 
 update_SOURCES=update.c
 update_LDFLAGS=-all-static
@@ -10,13 +11,19 @@ if HAVE_WINDRES
 update_SOURCES+=resources.rc update.exe.manifest
 endif
 
-.png.pnm:
-       pngtopnm $< | pnmquant 256 > $@
+.rc.$(OBJEXT):
+       $(AM_V_GEN)$(WINDRES) resources.rc $@
 
 resources.$(OBJEXT): resources.rc update.exe.manifest update.ico
-       $(WINDRES) resources.rc $@
+
+%.js: $(srcdir)/%.js.in
+       $(AM_V_GEN)sed -e 's/$$/\r/' $(srcdir)/$@.in > $@
+
+.png.pnm:
+       $(AM_V_GEN)pngtopnm $< | pnmquant -quiet 256 > $@
 
 update.ico:     icon16.pnm icon22.pnm icon32.pnm
-       ppmtowinicon -output=$@ $^
+       $(AM_V_GEN)ppmtowinicon -output=$@ $^
 
-EXTRA_DIST=icon16.png icon22.png icon32.png
+EXTRA_DIST=icon16.png icon22.png icon32.png update.js.in
+CLEANFILES=update.js
index aa0e5a8..dc417d2 100644 (file)
@@ -1,6 +1,8 @@
 #include <winver.h>
 #include <winuser.h>
 
+#pragma code_page(65001)
+
 MAINICON ICON "update.ico"
 
 VS_VERSION_INFO VERSIONINFO
@@ -20,7 +22,7 @@ VS_VERSION_INFO VERSIONINFO
                VALUE "FileVersion","@PACKAGE_VERSION@"
                VALUE "InternalName","update"
                VALUE "LegalCopyright",
-                 "Copyright (c) 2009,2011,2012 J. Ali Harlow et al"
+                 "Copyright © 2009,2011,2012 J. Ali Harlow et al"
                VALUE "OriginalFilename","update.exe"
                VALUE "ProductName","plover"
                VALUE "ProductVersion","@PACKAGE_VERSION@"
index 4127239..d1b8ee4 100644 (file)
@@ -27,18 +27,20 @@ LUALIB_API int luaopen_posix(lua_State *L);
 
 void update(const char *argv0)
 {
-    char *path,*s,*prefix;
+    char *path;
+    gchar *s,*prefix;
     int ch;
     struct comps *comps;
+    GError *error=NULL;
     path=plover_get_program_directory(argv0);
-    s=plover_strconcat(path,"/repodata/comps.xml",NULL);
+    s=g_strconcat(path,"/repodata/comps.xml",NULL);
     comps=plover_comps_new_from_file(s);
     if (!comps)
     {
        perror(s);
        exit(1);
     }
-    free(s);
+    g_free(s);
     prefix=plover_default_prefix_for_vendor(comps->vendor);
     if (!plover_installed_files_match_prefix(prefix))
     {
@@ -50,11 +52,21 @@ void update(const char *argv0)
            exit(1);
        while(ch!='\n' && ch!=EOF)
            ch=getchar();
-       plover_remove(NULL);
+       if (plover_remove(NULL,&error))
+       {
+           fprintf(stderr,"%s\n",error->message);
+           g_error_free(error);
+           exit(1);
+       }
     }
     plover_comps_free(comps);
-    plover_update(path,prefix,NULL);
-    free(prefix);
+    if (!plover_update(path,prefix,NULL,&error))
+    {
+       fprintf(stderr,"%s\n",error->message);
+       g_error_free(error);
+       exit(1);
+    }
+    g_free(prefix);
     free(path);
 }
 
diff --git a/update/update.js.in b/update/update.js.in
new file mode 100644 (file)
index 0000000..d01c4a0
--- /dev/null
@@ -0,0 +1,68 @@
+var WshShell = WScript.CreateObject("WScript.Shell");
+var ScriptPath = WScript.ScriptFullName.replace(/\\[^\\]*$/, "");
+var FileSystemObject = new ActiveXObject("Scripting.FileSystemObject");
+
+function RunUpdate(args)
+{
+    var OKButton = 0, ErrorIcon = 16, ForReading = 1;
+    var path, command, i, ExecObject, text;
+    path = FileSystemObject.BuildPath(ScriptPath, "update.exe");
+    command = "\"" + path + "\"";
+    for(i = 0; i < args.Length; i++)
+       command += " \"" + args.Item(i) + "\"";
+    ExecObject = WshShell.Exec(command);
+    if (!ExecObject.StdErr.AtEndOfStream)
+    {
+       text = "Update failed for an unexpected reason. Please report "
+         + "the following error to support@city-occupational.co.uk:\n\n"
+         + ExecObject.StdErr.ReadAll();
+       WshShell.Popup(text, 0, "Update failed", OKButton + ErrorIcon);
+       return false;
+    }
+    else if (ExecObject.ExitCode)
+    {
+       text = "Update failed without giving a reason. Please report "
+         + "this to support@city-occupational.co.uk";
+       WshShell.Popup(text, 0, "Update failed", OKButton + ErrorIcon);
+       return false;
+    }
+    return true;
+}
+
+function Update()
+{
+    var path;
+    path = FileSystemObject.BuildPath(ScriptPath, "pre-inst.exe");
+    WshShell.Run("\"" + path + "\"" + " --post \"wscript.exe"
+      + " \\\"" + WScript.ScriptFullName + "\\\""
+      + " --post %INSTALL_PREFIX% %TEST_RESULT%\"",
+      7, true);
+}
+
+function PostUpdate()
+{
+    var OKButton = 0, InfoIcon = 64, text;
+    var test_result, install_prefix, path, args;
+    install_prefix = WScript.Arguments.Item(1);
+    test_result = WScript.Arguments.Item(2);
+    if (test_result == "pass")
+    {
+       path = FileSystemObject.BuildPath(install_prefix,
+         "bin\\app-manager.exe");
+       WshShell.Run("\"" + path + "\" --update \"" + ScriptPath + "\"");
+    }
+    else
+    {
+       args = { Length:0, Item:function(){return nil} };
+       if (RunUpdate(args))
+       {
+           text = "Software update completed successfully.";
+           WshShell.Popup(text, 0, "Software Update", OKButton + InfoIcon);
+       }
+    }
+}
+
+if (WScript.Arguments.Length < 1)
+    Update()
+else if (WScript.Arguments.Length >= 3 && WScript.Arguments.Item(0) == "--post")
+    PostUpdate()