app-manager/app-manager.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Jul 16 11:07:18 2016 +0100 (2016-07-16)
changeset 61 31fb35727621
parent 38 a29623b68ca2
child 103 c4b0d5cc34bc
permissions -rw-r--r--
Support parallel installations. The idea is that for CAD screener, we want
to be able to install this on the same machine as a standard AVOT setup
(most notably for John's laptop). To allow for the possibility of a second
application that might have the same requirements, we add the concept of
vendor-specific distributions. Thus we can have one distribution for CAD
screener and one for The Next Big Thing. It doesn't seem trivial to have
both CAD screener and AVOT under the same vendor tag so we'll have to have
AVOT under "City Occupational" and CAD screener under "City Occupational Ltd"
or some such kludge.

Most of this is done although we are very short of test cases (in particular
we don't test that it's actually possible to install CAD screener in parallel
with AVOT or to update either of them once installed, which is fundamental).

We also have a lot of baggage left over, including an intercept of razor_set.
The problem that this was introduced to debug has been fixed but it looks
like there are a number of memory leaks which it might be useful to help
track down so it has been left in place for now.

There is still a lot of confusion in plover between path-based and URI-based
API. We should review the API, decide what we want and have a general clear up.

There is also confusion as to the purpose of RAZOR_ROOT (and meaning; path or
URI). This is not used at all in librazor (although it is used in razor.exe).
Ideally we shouldn't use it in plover or plover-gtk either although again, we
might want to support it or an equivalent in (some of) the various executables.

Work that would still to nice to do for CAD screener:

- uninstall (ideally as an installed program that hooks into Add/Remove programs
but even re-running the installer would be acceptable).
- xz support (smaller packages).
- repomd.xml and xml:base (would be needed for an Internet installer).
- graphical installer.
     1 /*
     2  * Copyright (C) 2010  J. Ali Harlow <ali@juiblex.co.uk>
     3  *
     4  * This program is free software; you can redistribute it and/or modify
     5  * it under the terms of the GNU General Public License as published by
     6  * the Free Software Foundation; either version 2 of the License, or
     7  * (at your option) any later version.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License along
    15  * with this program; if not, write to the Free Software Foundation, Inc.,
    16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    17  */
    18 
    19 #include "config.h"
    20 #include <stdlib.h>
    21 #include <string.h>
    22 #ifdef WIN32
    23 #include <windows.h>
    24 #endif	/* WIN32 */
    25 #include <lua.h>
    26 #include <glib.h>
    27 #include <gio/gio.h>
    28 #include <gtk/gtk.h>
    29 #include <whelk/whelk.h>
    30 #include <plover/plover.h>
    31 #include <plover/packageset.h>
    32 #include <plover-gtk/stockicons.h>
    33 #include "app-manager.h"
    34 #include "localmedia.h"
    35 
    36 LUALIB_API int luaopen_posix(lua_State *L);
    37 
    38 #define LOGO_NAME	"plover-applications"
    39 
    40 GtkBuilder *ui;
    41 GtkTreeModel *installed,*applications,*location,*local_media;
    42 char *prefix=NULL;
    43 struct razor_relocations *relocations=NULL;
    44 
    45 void show_busy_cursor(gboolean busy)
    46 {
    47     GList *list,*link,*remaining;
    48     GdkDisplay *display;
    49     GdkCursor *cursor;
    50     GtkWidget *w;
    51     list=gtk_window_list_toplevels();
    52     while(list)
    53     {
    54 	w=GTK_WIDGET(list->data);
    55 	if (!w->window)
    56 	{
    57 	    link=list;
    58 	    list=g_list_remove_link(list,link);
    59 	    g_list_free_1(link);
    60 	}
    61 	else
    62 	{
    63 	    display=gtk_widget_get_display(w);
    64 	    cursor=busy?gdk_cursor_new_for_display(display,GDK_WATCH):NULL;
    65 	    remaining=NULL;
    66 	    for(link=list;link;link=link->next)
    67 	    {
    68 		w=GTK_WIDGET(link->data);
    69 		if (w->window)
    70 		{
    71 		    if (gtk_widget_get_display(w)==display)
    72 			gdk_window_set_cursor(w->window,cursor);
    73 		    else
    74 			remaining=g_list_prepend(remaining,w);
    75 		}
    76 	    }
    77 	    gdk_display_flush(display);
    78 	    if (cursor)
    79 		gdk_cursor_unref(cursor);
    80 	    g_list_free(list);
    81 	    list=remaining;
    82 	}
    83     }
    84 }
    85 
    86 /*
    87  * In Gtk+ 2.16.6, the default handler generates g_warnings on error.
    88  * It should display an error to the user. Do it ourselves.
    89  */
    90 
    91 static void show_uri(GtkLinkButton *button,const gchar *uri,gpointer data)
    92 {
    93     GdkScreen *screen;
    94     GtkWidget *dialog;
    95     GError *error=NULL;
    96     if (gtk_widget_has_screen(GTK_WIDGET(button)))
    97 	screen=gtk_widget_get_screen(GTK_WIDGET(button));
    98     else
    99 	screen=NULL;
   100     gtk_show_uri(screen,uri,GDK_CURRENT_TIME,&error);
   101     if (error)
   102     {
   103 	dialog=gtk_message_dialog_new(
   104 	  GTK_WINDOW(gtk_builder_get_object(ui,"MainWindow")),
   105 	  GTK_DIALOG_DESTROY_WITH_PARENT,GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
   106 	  "Unable to show '%s'",uri);
   107 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
   108 	  error->message);
   109 	g_error_free(error);
   110 	gtk_dialog_run(GTK_DIALOG(dialog));
   111 	gtk_widget_destroy(dialog);
   112     }
   113 }
   114 
   115 #if 0
   116 /* Checks whether a loader for SVG files has been registered
   117  * with GdkPixbuf.
   118  */
   119 static gboolean pixbuf_supports_svg(void)
   120 {
   121     GSList *formats;
   122     GSList *tmp_list;
   123     static gint found_svg=-1;
   124     gchar **mime_types,**mime_type;
   125     if (found_svg!=-1)
   126 	return found_svg;
   127     formats=gdk_pixbuf_get_formats();
   128     found_svg=FALSE;
   129     for (tmp_list=formats;tmp_list && !found_svg;tmp_list=tmp_list->next)
   130     {
   131 	mime_types=gdk_pixbuf_format_get_mime_types(tmp_list->data);
   132 	for (mime_type=mime_types;*mime_type && !found_svg;mime_type++)
   133 	    if (!strcmp(*mime_type,"image/svg"))
   134 		found_svg=TRUE;
   135 	g_strfreev(mime_types);
   136     }
   137     g_slist_free(formats);
   138     return found_svg;
   139 }
   140 
   141 static void install_icon_at_size(const char *icon_name,GtkIconSet *icon_set,
   142   GtkIconSize size,const char *filename)
   143 {
   144     int w,h;
   145     GdkPixbuf *pixbuf;
   146     GtkIconSource *source;
   147     if (gtk_icon_size_lookup(size,&w,&h))
   148     {
   149 	pixbuf=gdk_pixbuf_new_from_file_at_size(filename,w,h,NULL);
   150 	if (pixbuf)
   151 	{
   152 	    source=gtk_icon_source_new();
   153 	    gtk_icon_source_set_size_wildcarded(source,FALSE);
   154 	    gtk_icon_source_set_size(source,size);
   155 	    gtk_icon_source_set_pixbuf(source,pixbuf);
   156 	    gtk_icon_set_add_source(icon_set,source);
   157 	    gtk_icon_source_free(source);
   158 	    g_object_unref(pixbuf);
   159 	}
   160     }
   161 }
   162 
   163 static void install_icons(void)
   164 {
   165     gchar *s;
   166     GtkIconSource *source;
   167     GtkIconSet *icon_set;
   168     GtkIconFactory *factory;
   169     factory=gtk_icon_factory_new();
   170     icon_set=gtk_icon_set_new();
   171     if (pixbuf_supports_svg())
   172     {
   173 	source=gtk_icon_source_new();
   174 	s=g_build_filename(prefix?prefix:"/usr",
   175 	  "share/icons/hicolor/scalable/apps/plover-applications.svg",NULL);
   176 	gtk_icon_source_set_filename(source,s);
   177 	g_free(s);
   178 	gtk_icon_set_add_source(icon_set,source);
   179 	gtk_icon_source_free(source);
   180     }
   181     else
   182     {
   183 	s=g_build_filename(prefix?prefix:"/usr",
   184 	  "share/icons/hicolor/24x24/apps/plover-applications.png",NULL);
   185 	install_icon_at_size(LOGO_NAME,icon_set,GTK_ICON_SIZE_MENU,s);
   186 	install_icon_at_size(LOGO_NAME,icon_set,GTK_ICON_SIZE_BUTTON,s);
   187 	install_icon_at_size(LOGO_NAME,icon_set,GTK_ICON_SIZE_SMALL_TOOLBAR,s);
   188 	install_icon_at_size(LOGO_NAME,icon_set,GTK_ICON_SIZE_LARGE_TOOLBAR,s);
   189 	g_free(s);
   190 	s=g_build_filename(prefix?prefix:"/usr",
   191 	  "share/icons/hicolor/48x48/apps/plover-applications.png",NULL);
   192 	install_icon_at_size(LOGO_NAME,icon_set,GTK_ICON_SIZE_DND,s);
   193 	install_icon_at_size(LOGO_NAME,icon_set,GTK_ICON_SIZE_DIALOG,s);
   194 	g_free(s);
   195     }
   196     gtk_icon_factory_add(factory,LOGO_NAME,icon_set);
   197     gtk_icon_set_unref(icon_set);
   198     icon_set=gtk_icon_factory_lookup(factory,LOGO_NAME);
   199     gtk_icon_factory_add_default(factory);
   200     g_object_unref(factory);
   201     icon_set=gtk_icon_factory_lookup_default(LOGO_NAME);
   202     gtk_window_set_default_icon_name(LOGO_NAME);
   203 }
   204 #endif
   205 
   206 static void install_icons(void)
   207 {
   208     GtkIconSet *icon_set;
   209     plover_icons_add_to_stock("apps",LOGO_NAME);
   210     icon_set=gtk_icon_factory_lookup_default(LOGO_NAME);
   211     gtk_window_set_default_icon_name(LOGO_NAME);
   212 }
   213 
   214 int main(int argc,char **argv)
   215 {
   216     GError *err=0;
   217     GtkWidget *w;
   218     gchar *s,*database_uri,*contents;
   219     gchar *database=NULL,*setup_base=NULL,*update_base=NULL;
   220     gsize len;
   221     struct comps *comps;
   222     PloverPackageSet *set=NULL;
   223     GSList *objects,*lnk;
   224     gboolean started;
   225     GOptionEntry options[]={
   226 	{"database",0,0,G_OPTION_ARG_STRING,&database,
   227 	  "Operate on a distribution-local database","vendor/distribution"},
   228 	{"setup",0,0,G_OPTION_ARG_STRING,&setup_base,
   229 	  "Setup from installation media","uri"},
   230 	{"update",0,0,G_OPTION_ARG_STRING,&update_base,
   231 	  "Update from upgrade media","uri"},
   232 	{NULL}
   233     };
   234 #ifdef WIN32
   235     /*
   236      * app-manager is normally a GUI application, but rpm scripts may well
   237      * call console applications and it looks ugly if console windows keep
   238      * popping up. Avoid this by allocating our own console and hiding it.
   239      * Note:
   240      *	- If app-manager is a console application (typically for debugging),
   241      *    then skip this step.
   242      *  - Call ShowWindow twice to negate special handling on first call.
   243      */
   244     if (!GetConsoleWindow())
   245     {
   246 	AllocConsole();
   247 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   248 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   249     }
   250 #endif
   251     plover_exception_handler_init();
   252     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   253     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   254     if (!gtk_init_with_args(&argc,&argv,NULL,options,NULL,&err))
   255     {
   256 	g_printerr("%s\n",err->message);
   257 	exit(1);
   258     }
   259     if (setup_base && update_base)
   260     {
   261 	g_printerr("--setup and --update are mutually exclusive\n");
   262 	exit(1);
   263     }
   264 #ifdef WIN32
   265     prefix=g_win32_get_package_installation_directory_of_module(NULL);
   266 #endif
   267     install_icons();
   268     ui=gtk_builder_new();
   269     if (!g_file_get_contents("app-manager.ui",&contents,&len,&err) &&
   270       g_error_matches(err,G_FILE_ERROR,G_FILE_ERROR_NOENT))
   271     {
   272 #ifdef WIN32
   273 	s=g_build_filename(prefix,"share","plover","app-manager.ui",NULL);
   274 #else
   275 	s=g_build_filename(PLOVER_DATADIR,"app-manager.ui",NULL);
   276 #endif
   277 	g_clear_error(&err);
   278 	(void)g_file_get_contents(s,&contents,&len,&err);
   279 	g_free(s);
   280     }
   281     if (!err)
   282     {
   283 	(void)gtk_builder_add_from_string(ui,contents,len,&err);
   284 	g_free(contents);
   285     }
   286     if (err)
   287     {
   288 	g_error("%s",err->message);
   289 	exit(0);
   290     }
   291     gtk_builder_connect_signals(ui,NULL);
   292     gtk_link_button_set_uri_hook(show_uri,NULL,NULL);
   293     if (setup_base)
   294 	started=setup(setup_base);
   295     else if (update_base)
   296 	started=update(update_base);
   297     else
   298     {
   299 	if (database)
   300 	{
   301 	    g_free(prefix);
   302 	    prefix=NULL;
   303 	    s=strchr(database,'/');
   304 	    if (*s)
   305 		*s++='\0';
   306 	    comps=plover_comps_new();
   307 	    plover_comps_set_vendor(comps,database);
   308 	    if (s)
   309 	    {
   310 		plover_comps_set_distribution(comps,s);
   311 		*--s='/';
   312 	    }
   313 	    prefix=plover_comps_get_default_prefix(comps);
   314 	    plover_comps_free(comps);
   315 	    s=g_strconcat(prefix,"/var/lib/razor",NULL);
   316 	    database_uri=razor_path_to_uri(s);
   317 	    g_free(s);
   318 	    razor_set_database_uri(database_uri);
   319 	    g_free(database_uri);
   320 	}
   321 	if (prefix)
   322 	{
   323 	    relocations=razor_relocations_create();
   324 	    razor_relocations_add(relocations,"/usr",prefix);
   325 	}
   326 	installed=GTK_TREE_MODEL(plover_package_store_new());
   327 	set=plover_package_set_new();
   328 	(void)plover_package_set_open(set,"",TRUE,NULL);
   329 	plover_package_store_add_set(PLOVER_PACKAGE_STORE(installed),set);
   330 	if (plover_package_set_get_no_details(set))
   331 	{
   332 	    w=GTK_WIDGET(gtk_builder_get_object(ui,"ViewFiles"));
   333 	    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w),TRUE);
   334 	}
   335 	applications=plover_applications_model_new(installed);
   336 	set_package_model(applications);
   337 	w=GTK_WIDGET(gtk_builder_get_object(ui,"MainWindow"));
   338 	gtk_widget_show(w);
   339 	started=TRUE;
   340     }
   341     if (started)
   342 	gtk_main();
   343     g_clear_object(&set);
   344     objects=gtk_builder_get_objects(ui);
   345     for(lnk=objects;lnk;lnk=lnk->next)
   346 	if (GTK_IS_WIDGET(lnk->data) &&
   347 	  gtk_widget_is_toplevel(GTK_WIDGET(lnk->data)))
   348 	    gtk_widget_destroy(GTK_WIDGET(lnk->data));
   349     g_slist_free(objects);
   350     g_clear_object(&ui);
   351     g_clear_object(&installed);
   352     g_clear_object(&applications);
   353     g_clear_object(&location);
   354     g_clear_object(&local_media);
   355     if (relocations)
   356 	razor_relocations_destroy(relocations);
   357     g_free(prefix);
   358     g_free(setup_base);
   359     g_free(update_base);
   360     g_free(database);
   361     exit(0);
   362 }
   363 
   364 G_MODULE_EXPORT void
   365   on_applications_toggled(GtkToggleToolButton *button,gpointer data)
   366 {
   367     if (gtk_toggle_tool_button_get_active(button))
   368     {
   369 	if (!applications)
   370 	    applications=plover_applications_model_new(installed);
   371 	set_package_model(applications);
   372     }
   373 }
   374 
   375 G_MODULE_EXPORT void
   376   on_all_packages_toggled(GtkToggleToolButton *button,gpointer data)
   377 {
   378     if (gtk_toggle_tool_button_get_active(button))
   379 	set_package_model(installed);
   380 }
   381 
   382 G_MODULE_EXPORT void
   383   on_local_media_toggled(GtkToggleToolButton *button,gpointer data)
   384 {
   385     if (gtk_toggle_tool_button_get_active(button))
   386     {
   387 	if (!local_media)
   388 	{
   389 	    show_busy_cursor(TRUE);
   390 	    local_media=plover_local_media_store_new();
   391 	    show_busy_cursor(FALSE);
   392 	}
   393 	set_package_model(local_media);
   394     }
   395 }
   396 
   397 G_MODULE_EXPORT void
   398   on_location_toggled(GtkToggleToolButton *button,gpointer data)
   399 {
   400     if (gtk_toggle_tool_button_get_active(button))
   401 	set_package_model(location);
   402 }
   403 
   404 G_MODULE_EXPORT void on_open_location(GtkWidget *widget)
   405 {
   406     GtkWidget *w=GTK_WIDGET(gtk_builder_get_object(ui,"MainWindow"));
   407     GtkWidget *dialog;
   408     GFile *file,*parent;
   409     GFileInfo *fi;
   410     GMount *mount;
   411     gchar *path,*name;
   412     PloverPackageSet *set;
   413     GSList *sets;
   414     GError *err=NULL;
   415     dialog=gtk_file_chooser_dialog_new("Open Location",GTK_WINDOW(w),
   416       GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,GTK_STOCK_CANCEL,
   417       GTK_RESPONSE_CANCEL,GTK_STOCK_OPEN,GTK_RESPONSE_ACCEPT,NULL);
   418 #if GTK_CHECK_VERSION(2,18,0)
   419     gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog),FALSE);
   420 #endif
   421     if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
   422     {
   423 	show_busy_cursor(TRUE);
   424 	path=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
   425 	set=plover_package_set_new_from_yum(path,relocations,&err);
   426 	if (set)
   427 	{
   428 	    if (!location)
   429 		location=GTK_TREE_MODEL(plover_package_store_new());
   430 	    while((sets=
   431 	      plover_package_store_get_sets(PLOVER_PACKAGE_STORE(location))))
   432 		plover_package_store_remove_set(PLOVER_PACKAGE_STORE(location),
   433 		  PLOVER_PACKAGE_SET(sets->data));
   434 	    plover_package_store_add_set(PLOVER_PACKAGE_STORE(location),set);
   435 	    g_object_unref(set);
   436 	    w=GTK_WIDGET(gtk_builder_get_object(ui,"LocationButton"));
   437 	    file=g_file_new_for_path(path);
   438 	    parent=g_file_get_parent(file);
   439 	    if (parent)
   440 	    {
   441 		g_object_unref(parent);
   442 		mount=NULL;
   443 	    }
   444 	    else
   445 		mount=g_file_find_enclosing_mount(file,NULL,NULL);
   446 	    if (mount)
   447 	    {
   448 		name=g_mount_get_name(mount);
   449 		g_object_unref(mount);
   450 	    }
   451 	    else
   452 	    {
   453 		fi=g_file_query_info(file,
   454 		  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
   455 		  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,NULL,NULL);
   456 		if (fi)
   457 		{
   458 		    name=g_strdup(g_file_info_get_display_name(fi));
   459 		    g_object_unref(fi);
   460 		}
   461 		else
   462 		    name=g_filename_display_basename(path);
   463 		g_object_unref(file);
   464 	    }
   465 	    gtk_tool_button_set_label(GTK_TOOL_BUTTON(w),name);
   466 	    g_free(name);
   467 	    gtk_widget_show(w);
   468 	    gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(w),TRUE);
   469 	}
   470 	else
   471 	{
   472 	    gtk_widget_destroy(dialog);
   473 	    dialog=gtk_message_dialog_new(GTK_WINDOW(w),
   474 	      GTK_DIALOG_DESTROY_WITH_PARENT,GTK_MESSAGE_ERROR,
   475 	      GTK_BUTTONS_CLOSE,"Error loading repository '%s'",path);
   476 	    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
   477 	      "%s",err->message);
   478 	    gtk_dialog_run(GTK_DIALOG(dialog));
   479 	    g_error_free(err);
   480 	}
   481 	g_free(path);
   482 	show_busy_cursor(FALSE);
   483     }
   484     gtk_widget_destroy(dialog);
   485 }
   486 
   487 G_MODULE_EXPORT void on_scan_local_media(GtkWidget *widget)
   488 {
   489     show_busy_cursor(TRUE);
   490     if (!local_media)
   491 	local_media=plover_local_media_store_new();
   492     plover_local_media_store_scan(PLOVER_LOCAL_MEDIA_STORE(local_media));
   493     show_busy_cursor(FALSE);
   494 }
   495 
   496 G_MODULE_EXPORT void on_help_about(GtkWidget *widget)
   497 {
   498     GtkWidget *w=GTK_WIDGET(gtk_builder_get_object(ui,"MainWindow"));
   499     gtk_show_about_dialog(GTK_WINDOW(w),"name",PACKAGE_NAME,
   500       "version",PACKAGE_VERSION,"comments","Application Manager",
   501       "copyright","Copyright © 2010, 2014 J. Ali Harlow",
   502       "logo-icon-name",LOGO_NAME,
   503       NULL);
   504 }
   505 
   506 G_MODULE_EXPORT void on_find_clicked(GtkButton *button)
   507 {
   508     gchar *text;
   509     GtkWidget *w=GTK_WIDGET(gtk_builder_get_object(ui,"SearchEntry"));
   510     text=g_strdup(gtk_entry_get_text(GTK_ENTRY(w)));
   511     gtk_entry_set_text(GTK_ENTRY(w),"");
   512     gtk_entry_set_text(GTK_ENTRY(w),text);
   513     g_free(text);
   514 }