PageRenderTime 53ms CodeModel.GetById 2ms app.highlight 44ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/linux_updater/linux_updater.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 843 lines | 640 code | 131 blank | 72 comment | 85 complexity | 29dde5f399ab0b2f44d3656044bdb235 MD5 | raw file
  1/** 
  2 * @file linux_updater.cpp
  3 * @author Kyle Ambroff <ambroff@lindenlab.com>, Tofu Linden
  4 * @brief Viewer update program for unix platforms that support GTK+
  5 *
  6 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2010, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27
 28#include <unistd.h>
 29#include <signal.h>
 30#include <errno.h>
 31
 32#include "linden_common.h"
 33#include "llerrorcontrol.h"
 34#include "llfile.h"
 35#include "lldir.h"
 36#include "lldiriterator.h"
 37#include "llxmlnode.h"
 38#include "lltrans.h"
 39
 40#include <curl/curl.h>
 41
 42extern "C" {
 43#include <gtk/gtk.h>
 44}
 45
 46const guint UPDATE_PROGRESS_TIMEOUT = 100;
 47const guint UPDATE_PROGRESS_TEXT_TIMEOUT = 1000;
 48const guint ROTATE_IMAGE_TIMEOUT = 8000;
 49
 50typedef struct _updater_app_state {
 51	std::string app_name;
 52	std::string url;
 53	std::string file;
 54	std::string image_dir;
 55	std::string dest_dir;
 56	std::string strings_dirs;
 57	std::string strings_file;
 58
 59	LLDirIterator *image_dir_iter;
 60
 61	GtkWidget *window;
 62	GtkWidget *progress_bar;
 63	GtkWidget *image;
 64
 65	double progress_value;
 66	bool activity_mode;
 67
 68	guint image_rotation_timeout_id;
 69	guint progress_update_timeout_id;
 70	guint update_progress_text_timeout_id;
 71
 72	bool failure;
 73} UpdaterAppState;
 74
 75// List of entries from strings.xml to always replace
 76static std::set<std::string> default_trans_args;
 77void init_default_trans_args()
 78{
 79        default_trans_args.insert("SECOND_LIFE"); // World
 80        default_trans_args.insert("APP_NAME");
 81        default_trans_args.insert("SECOND_LIFE_GRID");
 82        default_trans_args.insert("SUPPORT_SITE");
 83}
 84
 85bool translate_init(std::string comma_delim_path_list,
 86		    std::string base_xml_name)
 87{
 88	init_default_trans_args();
 89
 90	// extract paths string vector from comma-delimited flat string
 91	std::vector<std::string> paths;
 92	LLStringUtil::getTokens(comma_delim_path_list, paths, ","); // split over ','
 93
 94	for(std::vector<std::string>::iterator it = paths.begin(), end_it = paths.end();
 95		it != end_it;
 96		++it)
 97	{
 98		(*it) = gDirUtilp->findSkinnedFilename(*it, base_xml_name);
 99	}
100
101	// suck the translation xml files into memory
102	LLXMLNodePtr root;
103	bool success = LLXMLNode::getLayeredXMLNode(root, paths);
104	if (!success)
105	{
106		// couldn't load string table XML
107		return false;
108	}
109	else
110	{
111		// get those strings out of the XML
112		LLTrans::parseStrings(root, default_trans_args);
113		return true;
114	}
115}
116
117
118void updater_app_ui_init(void);
119void updater_app_quit(UpdaterAppState *app_state);
120void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state);
121std::string next_image_filename(std::string& image_path, LLDirIterator& iter);
122void display_error(GtkWidget *parent, std::string title, std::string message);
123BOOL install_package(std::string package_file, std::string destination);
124BOOL spawn_viewer(UpdaterAppState *app_state);
125
126extern "C" {
127	void on_window_closed(GtkWidget *sender, GdkEvent *event, gpointer state);
128	gpointer worker_thread_cb(gpointer *data);
129	int download_progress_cb(gpointer data, double t, double d, double utotal, double ulnow);
130	gboolean rotate_image_cb(gpointer data);
131	gboolean progress_update_timeout(gpointer data);
132	gboolean update_progress_text_timeout(gpointer data);
133}
134
135void updater_app_ui_init(UpdaterAppState *app_state)
136{
137	GtkWidget *vbox;
138	GtkWidget *summary_label;
139	GtkWidget *description_label;
140	GtkWidget *frame;
141
142	llassert(app_state != NULL);
143
144	// set up window and main container
145	std::string window_title = LLTrans::getString("UpdaterWindowTitle");
146	app_state->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
147	gtk_window_set_title(GTK_WINDOW(app_state->window),
148			     window_title.c_str());
149	gtk_window_set_resizable(GTK_WINDOW(app_state->window), FALSE);
150	gtk_window_set_position(GTK_WINDOW(app_state->window),
151				GTK_WIN_POS_CENTER_ALWAYS);
152
153	gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12);
154	g_signal_connect(G_OBJECT(app_state->window), "delete-event", 
155			 G_CALLBACK(on_window_closed), app_state);
156
157	vbox = gtk_vbox_new(FALSE, 6);
158	gtk_container_add(GTK_CONTAINER(app_state->window), vbox);
159
160	// set top label
161	std::ostringstream label_ostr;
162	label_ostr << "<big><b>"
163		   << LLTrans::getString("UpdaterNowUpdating")
164		   << "</b></big>";
165
166	summary_label = gtk_label_new(NULL);
167	gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE);
168	gtk_label_set_markup(GTK_LABEL(summary_label), 
169			     label_ostr.str().c_str());
170	gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5);
171	gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0);
172
173	// create the description label
174	description_label = gtk_label_new(LLTrans::getString("UpdaterUpdatingDescriptive").c_str());
175	gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE);
176	gtk_misc_set_alignment(GTK_MISC(description_label), 0, 0.5);
177	gtk_box_pack_start(GTK_BOX(vbox), description_label, FALSE, FALSE, 0);
178
179	// If an image path has been set, load the background images
180	if (!app_state->image_dir.empty()) {
181		frame = gtk_frame_new(NULL);
182		gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
183		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
184
185		// load the first image
186		app_state->image = gtk_image_new_from_file
187			(next_image_filename(app_state->image_dir, *app_state->image_dir_iter).c_str());
188		gtk_widget_set_size_request(app_state->image, 340, 310);
189		gtk_container_add(GTK_CONTAINER(frame), app_state->image);
190
191		// rotate the images every 5 seconds
192		app_state->image_rotation_timeout_id = g_timeout_add
193			(ROTATE_IMAGE_TIMEOUT, rotate_image_cb, app_state);
194	}
195
196	// set up progress bar, and update it roughly every 1/10 of a second
197	app_state->progress_bar = gtk_progress_bar_new();
198	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar), 
199				  LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str());
200	gtk_box_pack_start(GTK_BOX(vbox), 
201			   app_state->progress_bar, FALSE, TRUE, 0);
202	app_state->progress_update_timeout_id = g_timeout_add
203		(UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state);
204	app_state->update_progress_text_timeout_id = g_timeout_add
205		(UPDATE_PROGRESS_TEXT_TIMEOUT, update_progress_text_timeout, app_state);
206
207	gtk_widget_show_all(app_state->window);
208}
209
210gboolean rotate_image_cb(gpointer data)
211{
212	UpdaterAppState *app_state;
213	std::string filename;
214
215	llassert(data != NULL);
216	app_state = (UpdaterAppState *) data;
217
218	filename = next_image_filename(app_state->image_dir, *app_state->image_dir_iter);
219
220	gdk_threads_enter();
221	gtk_image_set_from_file(GTK_IMAGE(app_state->image), filename.c_str());
222	gdk_threads_leave();
223
224	return TRUE;
225}
226
227std::string next_image_filename(std::string& image_path, LLDirIterator& iter)
228{
229	std::string image_filename;
230	iter.next(image_filename);
231	return image_path + "/" + image_filename;
232}
233
234void on_window_closed(GtkWidget *sender, GdkEvent* event, gpointer data)
235{
236	UpdaterAppState *app_state;
237
238	llassert(data != NULL);
239	app_state = (UpdaterAppState *) data;
240
241	updater_app_quit(app_state);
242}
243
244void updater_app_quit(UpdaterAppState *app_state)
245{
246	if (app_state != NULL)
247	{
248		g_source_remove(app_state->progress_update_timeout_id);
249
250		if (!app_state->image_dir.empty())
251		{
252			g_source_remove(app_state->image_rotation_timeout_id);
253		}
254	}
255
256	gtk_main_quit();
257}
258
259void display_error(GtkWidget *parent, std::string title, std::string message)
260{
261	GtkWidget *dialog;
262
263	dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
264					GTK_DIALOG_DESTROY_WITH_PARENT,
265					GTK_MESSAGE_ERROR,
266					GTK_BUTTONS_OK,
267					"%s", message.c_str());
268	gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
269	gtk_dialog_run(GTK_DIALOG(dialog));
270	gtk_widget_destroy(dialog);
271}
272
273gpointer worker_thread_cb(gpointer data)
274{
275	UpdaterAppState *app_state;
276	CURL *curl;
277	CURLcode result;
278	FILE *package_file;
279	GError *error = NULL;
280	int fd;
281
282	//g_return_val_if_fail (data != NULL, NULL);
283	app_state = (UpdaterAppState *) data;
284
285	try {
286
287		if(!app_state->url.empty())
288		{
289			char* tmp_local_filename = NULL;
290			// create temporary file to store the package.
291			fd = g_file_open_tmp
292				("secondlife-update-XXXXXX", &tmp_local_filename, &error);
293			if (error != NULL)
294			{
295				llerrs << "Unable to create temporary file: "
296					   << error->message
297					   << llendl;
298
299				g_error_free(error);
300				throw 0;
301			}
302			
303			if(tmp_local_filename != NULL)
304			{
305				app_state->file = tmp_local_filename;
306				g_free(tmp_local_filename);
307			}
308
309			package_file = fdopen(fd, "wb");
310			if (package_file == NULL)
311			{
312				llerrs << "Failed to create temporary file: "
313					   << app_state->file.c_str()
314					   << llendl;
315
316				gdk_threads_enter();
317				display_error(app_state->window,
318							  LLTrans::getString("UpdaterFailDownloadTitle"),
319							  LLTrans::getString("UpdaterFailUpdateDescriptive"));
320				gdk_threads_leave();
321				throw 0;
322			}
323
324			// initialize curl and start downloading the package
325			llinfos << "Downloading package: " << app_state->url << llendl;
326
327			curl = curl_easy_init();
328			if (curl == NULL)
329			{
330				llerrs << "Failed to initialize libcurl" << llendl;
331
332				gdk_threads_enter();
333				display_error(app_state->window,
334							  LLTrans::getString("UpdaterFailDownloadTitle"),
335							  LLTrans::getString("UpdaterFailUpdateDescriptive"));
336				gdk_threads_leave();
337				throw 0;
338			}
339
340			curl_easy_setopt(curl, CURLOPT_URL, app_state->url.c_str());
341			curl_easy_setopt(curl, CURLOPT_NOSIGNAL, TRUE);
342			curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
343			curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file);
344			curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
345			curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, 
346							 &download_progress_cb);
347			curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state);
348
349			result = curl_easy_perform(curl);
350			fclose(package_file);
351			curl_easy_cleanup(curl);
352
353			if (result)
354			{
355				llerrs << "Failed to download update: " 
356					   << app_state->url 
357					   << llendl;
358
359				gdk_threads_enter();
360				display_error(app_state->window,
361							  LLTrans::getString("UpdaterFailDownloadTitle"),
362							  LLTrans::getString("UpdaterFailUpdateDescriptive"));
363				gdk_threads_leave();
364
365				throw 0;
366			}
367		}
368		
369		// now pulse the progres bar back and forth while the package is
370		// being unpacked
371		gdk_threads_enter();
372		std::string installing_msg = LLTrans::getString("UpdaterNowInstalling");
373		gtk_progress_bar_set_text(
374			GTK_PROGRESS_BAR(app_state->progress_bar),
375			installing_msg.c_str());
376		app_state->activity_mode = TRUE;
377		gdk_threads_leave();
378
379		// *TODO: if the destination is not writable, terminate this
380		// thread and show file chooser?
381		if (!install_package(app_state->file.c_str(), app_state->dest_dir))
382		{
383			llwarns << "Failed to install package to destination: "
384				<< app_state->dest_dir
385				<< llendl;
386
387			gdk_threads_enter();
388			display_error(app_state->window,
389				      LLTrans::getString("UpdaterFailInstallTitle"),
390				      LLTrans::getString("UpdaterFailUpdateDescriptive"));
391			//"Failed to update " + app_state->app_name,
392			gdk_threads_leave();
393			throw 0;
394		}
395
396		// try to spawn the new viewer
397		if (!spawn_viewer(app_state))
398		{
399			llwarns << "Viewer was not installed properly in : "
400				<< app_state->dest_dir
401				<< llendl;
402
403			gdk_threads_enter();
404			display_error(app_state->window,
405				      LLTrans::getString("UpdaterFailStartTitle"),
406				      LLTrans::getString("UpdaterFailUpdateDescriptive"));
407			gdk_threads_leave();
408			throw 0;
409		}
410	}
411	catch (...)
412	{
413		app_state->failure = TRUE;
414	}
415
416	gdk_threads_enter();
417	updater_app_quit(app_state);
418	gdk_threads_leave();
419
420	return NULL;
421}
422
423
424gboolean less_anal_gspawnsync(gchar **argv,
425			      gchar **stderr_output,
426			      gint *child_exit_status,
427			      GError **spawn_error)
428{
429	// store current SIGCHLD handler if there is one, replace with default
430	// handler to make glib happy
431	struct sigaction sigchld_backup;
432	struct sigaction sigchld_appease_glib;
433	sigchld_appease_glib.sa_handler = SIG_DFL;
434	sigemptyset(&sigchld_appease_glib.sa_mask);
435	sigchld_appease_glib.sa_flags = 0;
436	sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup);
437
438	gboolean rtn = g_spawn_sync(NULL,
439				    argv,
440				    NULL,
441				    (GSpawnFlags) (G_SPAWN_STDOUT_TO_DEV_NULL),
442				    NULL,
443				    NULL,
444				    NULL,
445				    stderr_output,
446				    child_exit_status,
447				    spawn_error);
448
449	// restore SIGCHLD handler
450	sigaction(SIGCHLD, &sigchld_backup, NULL);
451	
452	return rtn;
453}
454
455
456// perform a rename, or perform a (prompted) root rename if that fails
457int
458rename_with_sudo_fallback(const std::string& filename, const std::string& newname)
459{
460	int rtncode = ::rename(filename.c_str(), newname.c_str());
461	lldebugs << "rename result is: " << rtncode << " / " << errno << llendl;
462	if (rtncode && (EACCES == errno || EPERM == errno || EXDEV == errno))
463	{
464		llinfos << "Permission problem in rename, or moving between different mount points.  Retrying as a mv under a sudo." << llendl;
465		// failed due to permissions, try again as a gksudo or kdesu mv wrapper hack
466		char *sudo_cmd = NULL;
467		sudo_cmd = g_find_program_in_path("gksudo");
468		if (!sudo_cmd)
469		{
470			sudo_cmd = g_find_program_in_path("kdesu");
471		}
472		if (sudo_cmd)
473		{
474			char *mv_cmd = NULL;
475			mv_cmd = g_find_program_in_path("mv");
476			if (mv_cmd)
477			{
478				char *src_string_copy = g_strdup(filename.c_str());
479				char *dst_string_copy = g_strdup(newname.c_str());
480				char* argv[] = 
481					{
482						sudo_cmd,
483						mv_cmd,
484						src_string_copy,
485						dst_string_copy,
486						NULL
487					};
488
489				gchar *stderr_output = NULL;
490				gint child_exit_status = 0;
491				GError *spawn_error = NULL;
492				if (!less_anal_gspawnsync(argv, &stderr_output,
493							  &child_exit_status, &spawn_error))
494				{
495					llwarns << "Failed to spawn child process: " 
496						<< spawn_error->message 
497						<< llendl;
498				}
499				else if (child_exit_status)
500				{
501					llwarns << "mv command failed: "
502						<< (stderr_output ? stderr_output : "(no reason given)")
503						<< llendl;
504				}
505				else
506				{
507					// everything looks good, clear the error code
508					rtncode = 0;
509				}				
510
511				g_free(src_string_copy);
512				g_free(dst_string_copy);
513				if (spawn_error) g_error_free(spawn_error);
514			}
515		}
516	}
517	return rtncode;
518}
519
520gboolean install_package(std::string package_file, std::string destination)
521{
522	char *tar_cmd = NULL;
523	std::ostringstream command;
524
525	// Find the absolute path to the 'tar' command.
526	tar_cmd = g_find_program_in_path("tar");
527	if (!tar_cmd)
528	{
529		llerrs << "`tar' was not found in $PATH" << llendl;
530		return FALSE;
531	}
532	llinfos << "Found tar command: " << tar_cmd << llendl;
533
534	// Unpack the tarball in a temporary place first, then move it to 
535	// its final destination
536	std::string tmp_dest_dir = gDirUtilp->getTempFilename();
537	if (LLFile::mkdir(tmp_dest_dir, 0744))
538	{
539		llerrs << "Failed to create directory: "
540		       << destination
541		       << llendl;
542
543		return FALSE;
544	}
545
546	char *package_file_string_copy = g_strdup(package_file.c_str());
547	char *tmp_dest_dir_string_copy = g_strdup(tmp_dest_dir.c_str());
548	gchar *argv[8] = {
549		tar_cmd,
550		const_cast<gchar*>("--strip"), const_cast<gchar*>("1"),
551		const_cast<gchar*>("-xjf"),
552		package_file_string_copy,
553		const_cast<gchar*>("-C"), tmp_dest_dir_string_copy,
554		NULL,
555	};
556
557	llinfos << "Untarring package: " << package_file << llendl;
558
559	// store current SIGCHLD handler if there is one, replace with default
560	// handler to make glib happy
561	struct sigaction sigchld_backup;
562	struct sigaction sigchld_appease_glib;
563	sigchld_appease_glib.sa_handler = SIG_DFL;
564	sigemptyset(&sigchld_appease_glib.sa_mask);
565	sigchld_appease_glib.sa_flags = 0;
566	sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup);
567
568	gchar *stderr_output = NULL;
569	gint child_exit_status = 0;
570	GError *untar_error = NULL;
571	if (!less_anal_gspawnsync(argv, &stderr_output,
572				  &child_exit_status, &untar_error))
573	{
574		llwarns << "Failed to spawn child process: " 
575			<< untar_error->message 
576			<< llendl;
577		return FALSE;
578	}
579
580	if (child_exit_status)
581	{
582	 	llwarns << "Untar command failed: "
583			<< (stderr_output ? stderr_output : "(no reason given)")
584			<< llendl;
585		return FALSE;
586	}
587
588	g_free(tar_cmd);
589	g_free(package_file_string_copy);
590	g_free(tmp_dest_dir_string_copy);
591	g_free(stderr_output);
592	if (untar_error) g_error_free(untar_error);
593
594	// move the existing package out of the way if it exists
595	if (gDirUtilp->fileExists(destination))
596	{
597		std::string backup_dir = destination + ".backup";
598		int oldcounter = 1;
599		while (gDirUtilp->fileExists(backup_dir))
600		{
601			// find a foo.backup.N folder name that isn't taken yet
602			backup_dir = destination + ".backup." + llformat("%d", oldcounter);
603			++oldcounter;
604		}
605
606		if (rename_with_sudo_fallback(destination, backup_dir))
607		{
608			llwarns << "Failed to move directory: '" 
609				<< destination << "' -> '" << backup_dir 
610				<< llendl;
611			return FALSE;
612		}
613	}
614
615	// The package has been unpacked in a staging directory, now we just
616	// need to move it to its destination.
617	if (rename_with_sudo_fallback(tmp_dest_dir, destination))
618	{
619		llwarns << "Failed to move installation to the destination: "
620			<< destination 
621			<< llendl;
622		return FALSE;
623	}
624
625	// \0/ Success!
626	return TRUE;
627}
628
629gboolean progress_update_timeout(gpointer data)
630{
631	UpdaterAppState *app_state;
632
633	llassert(data != NULL);
634
635	app_state = (UpdaterAppState *) data;
636
637	gdk_threads_enter();
638	if (app_state->activity_mode)
639	{
640		gtk_progress_bar_pulse
641			(GTK_PROGRESS_BAR(app_state->progress_bar));
642	}
643	else
644	{
645		gtk_progress_set_value(GTK_PROGRESS(app_state->progress_bar),
646				       app_state->progress_value);
647	}
648	gdk_threads_leave();
649
650	return TRUE;
651}
652
653gboolean update_progress_text_timeout(gpointer data)
654{
655	UpdaterAppState *app_state;
656
657	llassert(data != NULL);
658	app_state = (UpdaterAppState *) data;
659
660	if (app_state->activity_mode == TRUE)
661	{
662		// We no longer need this timeout, it will be removed.
663		return FALSE;
664	}
665
666	if (!app_state->progress_value)
667	{
668		return TRUE;
669	}
670
671	std::string progress_text = llformat((LLTrans::getString("UpdaterProgressBarText")+" (%.0f%%)").c_str(), app_state->progress_value);
672
673	gdk_threads_enter();
674	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
675				  progress_text.c_str());
676	gdk_threads_leave();
677
678	return TRUE;
679}
680
681int download_progress_cb(gpointer data,
682			 double t,
683			 double d,
684			 double utotal,
685			 double ulnow)
686{
687	UpdaterAppState *app_state;
688
689	llassert(data != NULL);
690	app_state = (UpdaterAppState *) data;
691
692	if (t <= 0.0)
693	{
694		app_state->progress_value = 0;
695	}
696	else
697	{
698		app_state->progress_value = d * 100.0 / t;
699	}
700	return 0;
701}
702
703BOOL spawn_viewer(UpdaterAppState *app_state)
704{
705	llassert(app_state != NULL);
706
707	std::string cmd = app_state->dest_dir + "/secondlife";
708	GError *error = NULL;
709
710	// We want to spawn the Viewer on the same display as the updater app
711	gboolean success = gdk_spawn_command_line_on_screen
712		(gtk_widget_get_screen(app_state->window), cmd.c_str(), &error);
713
714	if (!success)
715	{
716		llwarns << "Failed to launch viewer: " << error->message 
717			<< llendl;
718	}
719
720	if (error) g_error_free(error);
721
722	return success;
723}
724
725void show_usage_and_exit()
726{
727	std::cout << "Usage: linux-updater <--url URL | --file FILE> --name NAME --dest PATH --stringsdir PATH1,PATH2 --stringsfile FILE"
728		  << "[--image-dir PATH]"
729		  << std::endl;
730	exit(1);
731}
732
733void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state)
734{
735	int i;
736
737	for (i = 1; i < argc; i++)
738	{
739		if ((!strcmp(argv[i], "--url")) && (++i < argc))
740		{
741			app_state->url = argv[i];
742		}
743		else if ((!strcmp(argv[i], "--file")) && (++i < argc))
744		{
745			app_state->file = argv[i];
746		}
747		else if ((!strcmp(argv[i], "--name")) && (++i < argc))
748		{
749			app_state->app_name = argv[i];
750		}
751		else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc))
752		{
753			app_state->image_dir = argv[i];
754			app_state->image_dir_iter = new LLDirIterator(argv[i], "/*.jpg");
755		}
756		else if ((!strcmp(argv[i], "--dest")) && (++i < argc))
757		{
758			app_state->dest_dir = argv[i];
759		}
760		else if ((!strcmp(argv[i], "--stringsdir")) && (++i < argc))
761		{
762			app_state->strings_dirs = argv[i];
763		}
764		else if ((!strcmp(argv[i], "--stringsfile")) && (++i < argc))
765		{
766			app_state->strings_file = argv[i];
767		}
768		else
769		{
770			// show usage, an invalid option was given.
771			show_usage_and_exit();
772		}
773	}
774
775	if (app_state->app_name.empty() 
776	    || (app_state->url.empty() && app_state->file.empty())  
777	    || app_state->dest_dir.empty())
778	{
779		show_usage_and_exit();
780	}
781
782	app_state->progress_value = 0.0;
783	app_state->activity_mode = FALSE;
784	app_state->failure = FALSE;
785
786	translate_init(app_state->strings_dirs, app_state->strings_file);
787}
788
789int main(int argc, char **argv)
790{
791	UpdaterAppState* app_state = new UpdaterAppState;
792	GThread *worker_thread;
793
794	parse_args_and_init(argc, argv, app_state);
795
796	// Initialize logger, and rename old log file
797	gDirUtilp->initAppDirs("SecondLife");
798	LLError::initForApplication
799		(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
800	std::string old_log_file = gDirUtilp->getExpandedFilename
801		(LL_PATH_LOGS, "updater.log.old");
802	std::string log_file = 
803		gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log");
804	LLFile::rename(log_file, old_log_file);
805	LLError::logToFile(log_file);
806
807	// initialize gthreads and gtk+
808	if (!g_thread_supported())
809	{
810		g_thread_init(NULL);
811		gdk_threads_init();
812	}
813
814	gtk_init(&argc, &argv);
815
816	// create UI
817	updater_app_ui_init(app_state);
818
819	//llinfos << "SAMPLE TRANSLATION IS: " << LLTrans::getString("LoginInProgress") << llendl;
820
821	// create download thread
822	worker_thread = g_thread_create
823		(GThreadFunc(worker_thread_cb), app_state, FALSE, NULL);
824
825	gdk_threads_enter();
826	gtk_main();
827	gdk_threads_leave();
828
829	// Delete the file only if created from url download.
830	if(!app_state->url.empty() && !app_state->file.empty())
831	{
832		if (gDirUtilp->fileExists(app_state->file))
833		{
834			LLFile::remove(app_state->file);
835		}
836	}
837
838	bool success = !app_state->failure;
839	delete app_state->image_dir_iter;
840	delete app_state;
841	return success ? 0 : 1;
842}
843