summaryrefslogtreecommitdiff
path: root/indra/linux_updater
diff options
context:
space:
mode:
Diffstat (limited to 'indra/linux_updater')
-rw-r--r--indra/linux_updater/CMakeLists.txt58
-rw-r--r--indra/linux_updater/linux_updater.cpp818
2 files changed, 876 insertions, 0 deletions
diff --git a/indra/linux_updater/CMakeLists.txt b/indra/linux_updater/CMakeLists.txt
new file mode 100644
index 0000000000..9fe32ecb46
--- /dev/null
+++ b/indra/linux_updater/CMakeLists.txt
@@ -0,0 +1,58 @@
+# -*- cmake -*-
+
+project(linux_updater)
+
+include(00-Common)
+include(CURL)
+include(CARes)
+include(OpenSSL)
+include(UI)
+include(LLCommon)
+include(LLVFS)
+include(LLXML)
+include(LLXUIXML)
+include(Linking)
+
+include_directories(
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLVFS_INCLUDE_DIRS}
+ ${LLXML_INCLUDE_DIRS}
+ ${LLXUIXML_INCLUDE_DIRS}
+ ${CURL_INCLUDE_DIRS}
+ ${CARES_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ ${UI_INCLUDE_DIRS}
+ )
+
+set(linux_updater_SOURCE_FILES linux_updater.cpp)
+
+set(linux_updater_HEADER_FILES CMakeLists.txt)
+
+set_source_files_properties(${linux_updater_HEADER_FILES}
+ PROPERTIES HEADER_FILES_ONLY TRUE)
+
+list(APPEND linux_updater_SOURCE_FILES ${linux_updater_HEADER_FILES})
+
+add_executable(linux-updater ${linux_updater_SOURCE_FILES})
+
+target_link_libraries(linux-updater
+ ${CURL_LIBRARIES}
+ ${CARES_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${CRYPTO_LIBRARIES}
+ ${UI_LIBRARIES}
+ ${LLXML_LIBRARIES}
+ ${LLXUIXML_LIBRARIES}
+ ${LLVFS_LIBRARIES}
+ ${LLCOMMON_LIBRARIES}
+ )
+
+add_custom_command(
+ OUTPUT linux-updater-stripped
+ COMMAND strip
+ ARGS --strip-debug -o linux-updater-stripped linux-updater
+ DEPENDS linux-updater
+ )
+
+add_custom_target(linux-updater-strip-target ALL
+ DEPENDS linux-updater-stripped)
diff --git a/indra/linux_updater/linux_updater.cpp b/indra/linux_updater/linux_updater.cpp
new file mode 100644
index 0000000000..acc60d42bf
--- /dev/null
+++ b/indra/linux_updater/linux_updater.cpp
@@ -0,0 +1,818 @@
+/**
+ * @file linux_updater.cpp
+ * @author Kyle Ambroff <ambroff@lindenlab.com>, Tofu Linden
+ * @brief Viewer update program for unix platforms that support GTK+
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ *
+ * Copyright (c) 2008, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "linden_common.h"
+#include "llerrorcontrol.h"
+#include "llfile.h"
+#include "lldir.h"
+#include "llxmlnode.h"
+#include "lltrans.h"
+
+#include <curl/curl.h>
+
+extern "C" {
+#include <gtk/gtk.h>
+}
+
+const guint UPDATE_PROGRESS_TIMEOUT = 100;
+const guint UPDATE_PROGRESS_TEXT_TIMEOUT = 1000;
+const guint ROTATE_IMAGE_TIMEOUT = 8000;
+
+typedef struct _updater_app_state {
+ std::string app_name;
+ std::string url;
+ std::string image_dir;
+ std::string dest_dir;
+ std::string strings_dirs;
+ std::string strings_file;
+
+ GtkWidget *window;
+ GtkWidget *progress_bar;
+ GtkWidget *image;
+
+ double progress_value;
+ bool activity_mode;
+
+ guint image_rotation_timeout_id;
+ guint progress_update_timeout_id;
+ guint update_progress_text_timeout_id;
+
+ bool failure;
+} UpdaterAppState;
+
+// List of entries from strings.xml to always replace
+static std::set<std::string> default_trans_args;
+void init_default_trans_args()
+{
+ default_trans_args.insert("SECOND_LIFE"); // World
+ default_trans_args.insert("SECOND_LIFE_VIEWER");
+ default_trans_args.insert("SECOND_LIFE_GRID");
+ default_trans_args.insert("SECOND_LIFE_SUPPORT");
+}
+
+bool translate_init(std::string comma_delim_path_list,
+ std::string base_xml_name)
+{
+ init_default_trans_args();
+
+ // extract paths string vector from comma-delimited flat string
+ std::vector<std::string> paths;
+ LLStringUtil::getTokens(comma_delim_path_list, paths); // split over ','
+
+ // suck the translation xml files into memory
+ LLXMLNodePtr root;
+ bool success = LLXMLNode::getLayeredXMLNode(base_xml_name, root, paths);
+ if (!success)
+ {
+ // couldn't load string table XML
+ return false;
+ }
+ else
+ {
+ // get those strings out of the XML
+ LLTrans::parseStrings(root, default_trans_args);
+ return true;
+ }
+}
+
+
+void updater_app_ui_init(void);
+void updater_app_quit(UpdaterAppState *app_state);
+void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state);
+std::string next_image_filename(std::string& image_path);
+void display_error(GtkWidget *parent, std::string title, std::string message);
+BOOL install_package(std::string package_file, std::string destination);
+BOOL spawn_viewer(UpdaterAppState *app_state);
+
+extern "C" {
+ void on_window_closed(GtkWidget *sender, gpointer state);
+ gpointer worker_thread_cb(gpointer *data);
+ int download_progress_cb(gpointer data, double t, double d, double utotal, double ulnow);
+ gboolean rotate_image_cb(gpointer data);
+ gboolean progress_update_timeout(gpointer data);
+ gboolean update_progress_text_timeout(gpointer data);
+}
+
+void updater_app_ui_init(UpdaterAppState *app_state)
+{
+ GtkWidget *vbox;
+ GtkWidget *summary_label;
+ GtkWidget *description_label;
+ GtkWidget *frame;
+
+ llassert(app_state != NULL);
+
+ // set up window and main container
+ std::string window_title = LLTrans::getString("UpdaterWindowTitle");
+ app_state->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(app_state->window),
+ window_title.c_str());
+ gtk_window_set_resizable(GTK_WINDOW(app_state->window), FALSE);
+ gtk_window_set_position(GTK_WINDOW(app_state->window),
+ GTK_WIN_POS_CENTER_ALWAYS);
+
+ gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12);
+ g_signal_connect(G_OBJECT(app_state->window), "delete-event",
+ G_CALLBACK(on_window_closed), app_state);
+
+ vbox = gtk_vbox_new(FALSE, 6);
+ gtk_container_add(GTK_CONTAINER(app_state->window), vbox);
+
+ // set top label
+ std::ostringstream label_ostr;
+ label_ostr << "<big><b>"
+ << LLTrans::getString("UpdaterNowUpdating")
+ << "</b></big>";
+
+ summary_label = gtk_label_new(NULL);
+ gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE);
+ gtk_label_set_markup(GTK_LABEL(summary_label),
+ label_ostr.str().c_str());
+ gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5);
+ gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0);
+
+ // create the description label
+ description_label = gtk_label_new(LLTrans::getString("UpdaterUpdatingDescriptive").c_str());
+ gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(description_label), 0, 0.5);
+ gtk_box_pack_start(GTK_BOX(vbox), description_label, FALSE, FALSE, 0);
+
+ // If an image path has been set, load the background images
+ if (!app_state->image_dir.empty()) {
+ frame = gtk_frame_new(NULL);
+ gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
+ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+
+ // load the first image
+ app_state->image = gtk_image_new_from_file
+ (next_image_filename(app_state->image_dir).c_str());
+ gtk_widget_set_size_request(app_state->image, 340, 310);
+ gtk_container_add(GTK_CONTAINER(frame), app_state->image);
+
+ // rotate the images every 5 seconds
+ app_state->image_rotation_timeout_id = g_timeout_add
+ (ROTATE_IMAGE_TIMEOUT, rotate_image_cb, app_state);
+ }
+
+ // set up progress bar, and update it roughly every 1/10 of a second
+ app_state->progress_bar = gtk_progress_bar_new();
+ gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
+ LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str());
+ gtk_box_pack_start(GTK_BOX(vbox),
+ app_state->progress_bar, FALSE, TRUE, 0);
+ app_state->progress_update_timeout_id = g_timeout_add
+ (UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state);
+ app_state->update_progress_text_timeout_id = g_timeout_add
+ (UPDATE_PROGRESS_TEXT_TIMEOUT, update_progress_text_timeout, app_state);
+
+ gtk_widget_show_all(app_state->window);
+}
+
+gboolean rotate_image_cb(gpointer data)
+{
+ UpdaterAppState *app_state;
+ std::string filename;
+
+ llassert(data != NULL);
+ app_state = (UpdaterAppState *) data;
+
+ filename = next_image_filename(app_state->image_dir);
+
+ gdk_threads_enter();
+ gtk_image_set_from_file(GTK_IMAGE(app_state->image), filename.c_str());
+ gdk_threads_leave();
+
+ return TRUE;
+}
+
+std::string next_image_filename(std::string& image_path)
+{
+ std::string image_filename;
+ gDirUtilp->getNextFileInDir(image_path, "/*.jpg", image_filename, true);
+ return image_path + "/" + image_filename;
+}
+
+void on_window_closed(GtkWidget *sender, gpointer data)
+{
+ UpdaterAppState *app_state;
+
+ llassert(data != NULL);
+ app_state = (UpdaterAppState *) data;
+
+ updater_app_quit(app_state);
+}
+
+void updater_app_quit(UpdaterAppState *app_state)
+{
+ if (app_state != NULL)
+ {
+ g_source_remove(app_state->progress_update_timeout_id);
+
+ if (!app_state->image_dir.empty())
+ {
+ g_source_remove(app_state->image_rotation_timeout_id);
+ }
+ }
+
+ gtk_main_quit();
+}
+
+void display_error(GtkWidget *parent, std::string title, std::string message)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ message.c_str());
+ gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+gpointer worker_thread_cb(gpointer data)
+{
+ UpdaterAppState *app_state;
+ CURL *curl;
+ CURLcode result;
+ FILE *package_file;
+ GError *error = NULL;
+ char *tmp_filename = NULL;
+ int fd;
+
+ //g_return_val_if_fail (data != NULL, NULL);
+ app_state = (UpdaterAppState *) data;
+
+ try {
+ // create temporary file to store the package.
+ fd = g_file_open_tmp
+ ("secondlife-update-XXXXXX", &tmp_filename, &error);
+ if (error != NULL)
+ {
+ llerrs << "Unable to create temporary file: "
+ << error->message
+ << llendl;
+
+ g_error_free(error);
+ throw 0;
+ }
+
+ package_file = fdopen(fd, "wb");
+ if (package_file == NULL)
+ {
+ llerrs << "Failed to create temporary file: "
+ << tmp_filename
+ << llendl;
+
+ gdk_threads_enter();
+ display_error(app_state->window,
+ LLTrans::getString("UpdaterFailDownloadTitle"),
+ LLTrans::getString("UpdaterFailUpdateDescriptive"));
+ gdk_threads_leave();
+ throw 0;
+ }
+
+ // initialize curl and start downloading the package
+ llinfos << "Downloading package: " << app_state->url << llendl;
+
+ curl = curl_easy_init();
+ if (curl == NULL)
+ {
+ llerrs << "Failed to initialize libcurl" << llendl;
+
+ gdk_threads_enter();
+ display_error(app_state->window,
+ LLTrans::getString("UpdaterFailDownloadTitle"),
+ LLTrans::getString("UpdaterFailUpdateDescriptive"));
+ gdk_threads_leave();
+ throw 0;
+ }
+
+ curl_easy_setopt(curl, CURLOPT_URL, app_state->url.c_str());
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, TRUE);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file);
+ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
+ curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
+ &download_progress_cb);
+ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state);
+
+ result = curl_easy_perform(curl);
+ fclose(package_file);
+ curl_easy_cleanup(curl);
+
+ if (result)
+ {
+ llerrs << "Failed to download update: "
+ << app_state->url
+ << llendl;
+
+ gdk_threads_enter();
+ display_error(app_state->window,
+ LLTrans::getString("UpdaterFailDownloadTitle"),
+ LLTrans::getString("UpdaterFailUpdateDescriptive"));
+ gdk_threads_leave();
+
+ throw 0;
+ }
+
+ // now pulse the progres bar back and forth while the package is
+ // being unpacked
+ gdk_threads_enter();
+ std::string installing_msg = LLTrans::getString("UpdaterNowInstalling");
+ gtk_progress_bar_set_text(
+ GTK_PROGRESS_BAR(app_state->progress_bar),
+ installing_msg.c_str());
+ app_state->activity_mode = TRUE;
+ gdk_threads_leave();
+
+ // *TODO: if the destination is not writable, terminate this
+ // thread and show file chooser?
+ if (!install_package(tmp_filename, app_state->dest_dir))
+ {
+ llwarns << "Failed to install package to destination: "
+ << app_state->dest_dir
+ << llendl;
+
+ gdk_threads_enter();
+ display_error(app_state->window,
+ LLTrans::getString("UpdaterFailInstallTitle"),
+ LLTrans::getString("UpdaterFailUpdateDescriptive"));
+ //"Failed to update " + app_state->app_name,
+ gdk_threads_leave();
+ throw 0;
+ }
+
+ // try to spawn the new viewer
+ if (!spawn_viewer(app_state))
+ {
+ llwarns << "Viewer was not installed properly in : "
+ << app_state->dest_dir
+ << llendl;
+
+ gdk_threads_enter();
+ display_error(app_state->window,
+ LLTrans::getString("UpdaterFailStartTitle"),
+ LLTrans::getString("UpdaterFailUpdateDescriptive"));
+ gdk_threads_leave();
+ throw 0;
+ }
+ }
+ catch (...)
+ {
+ app_state->failure = TRUE;
+ }
+
+ // FIXME: delete package file also if delete-event is raised on window
+ if (tmp_filename != NULL)
+ {
+ if (gDirUtilp->fileExists(tmp_filename))
+ {
+ LLFile::remove(tmp_filename);
+ }
+ }
+
+ gdk_threads_enter();
+ updater_app_quit(app_state);
+ gdk_threads_leave();
+
+ return NULL;
+}
+
+
+gboolean less_anal_gspawnsync(gchar **argv,
+ gchar **stderr_output,
+ gint *child_exit_status,
+ GError **spawn_error)
+{
+ // store current SIGCHLD handler if there is one, replace with default
+ // handler to make glib happy
+ struct sigaction sigchld_backup;
+ struct sigaction sigchld_appease_glib;
+ sigchld_appease_glib.sa_handler = SIG_DFL;
+ sigemptyset(&sigchld_appease_glib.sa_mask);
+ sigchld_appease_glib.sa_flags = 0;
+ sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup);
+
+ gboolean rtn = g_spawn_sync(NULL,
+ argv,
+ NULL,
+ (GSpawnFlags) (G_SPAWN_STDOUT_TO_DEV_NULL),
+ NULL,
+ NULL,
+ NULL,
+ stderr_output,
+ child_exit_status,
+ spawn_error);
+
+ // restore SIGCHLD handler
+ sigaction(SIGCHLD, &sigchld_backup, NULL);
+
+ return rtn;
+}
+
+
+// perform a rename, or perform a (prompted) root rename if that fails
+int
+rename_with_sudo_fallback(const std::string& filename, const std::string& newname)
+{
+ int rtncode = ::rename(filename.c_str(), newname.c_str());
+ lldebugs << "rename result is: " << rtncode << " / " << errno << llendl;
+ if (rtncode && (EACCES == errno || EPERM == errno || EXDEV == errno))
+ {
+ llinfos << "Permission problem in rename, or moving between different mount points. Retrying as a mv under a sudo." << llendl;
+ // failed due to permissions, try again as a gksudo or kdesu mv wrapper hack
+ char *sudo_cmd = NULL;
+ sudo_cmd = g_find_program_in_path("gksudo");
+ if (!sudo_cmd)
+ {
+ sudo_cmd = g_find_program_in_path("kdesu");
+ }
+ if (sudo_cmd)
+ {
+ char *mv_cmd = NULL;
+ mv_cmd = g_find_program_in_path("mv");
+ if (mv_cmd)
+ {
+ char *src_string_copy = g_strdup(filename.c_str());
+ char *dst_string_copy = g_strdup(newname.c_str());
+ char* argv[] =
+ {
+ sudo_cmd,
+ mv_cmd,
+ src_string_copy,
+ dst_string_copy,
+ NULL
+ };
+
+ gchar *stderr_output = NULL;
+ gint child_exit_status = 0;
+ GError *spawn_error = NULL;
+ if (!less_anal_gspawnsync(argv, &stderr_output,
+ &child_exit_status, &spawn_error))
+ {
+ llwarns << "Failed to spawn child process: "
+ << spawn_error->message
+ << llendl;
+ }
+ else if (child_exit_status)
+ {
+ llwarns << "mv command failed: "
+ << (stderr_output ? stderr_output : "(no reason given)")
+ << llendl;
+ }
+ else
+ {
+ // everything looks good, clear the error code
+ rtncode = 0;
+ }
+
+ g_free(src_string_copy);
+ g_free(dst_string_copy);
+ if (spawn_error) g_error_free(spawn_error);
+ }
+ }
+ }
+ return rtncode;
+}
+
+gboolean install_package(std::string package_file, std::string destination)
+{
+ char *tar_cmd = NULL;
+ std::ostringstream command;
+
+ // Find the absolute path to the 'tar' command.
+ tar_cmd = g_find_program_in_path("tar");
+ if (!tar_cmd)
+ {
+ llerrs << "`tar' was not found in $PATH" << llendl;
+ return FALSE;
+ }
+ llinfos << "Found tar command: " << tar_cmd << llendl;
+
+ // Unpack the tarball in a temporary place first, then move it to
+ // its final destination
+ std::string tmp_dest_dir = gDirUtilp->getTempFilename();
+ if (LLFile::mkdir(tmp_dest_dir, 0744))
+ {
+ llerrs << "Failed to create directory: "
+ << destination
+ << llendl;
+
+ return FALSE;
+ }
+
+ char *package_file_string_copy = g_strdup(package_file.c_str());
+ char *tmp_dest_dir_string_copy = g_strdup(tmp_dest_dir.c_str());
+ char *argv[8] = {
+ tar_cmd,
+ "--strip", "1",
+ "-xjf",
+ package_file_string_copy,
+ "-C", tmp_dest_dir_string_copy,
+ NULL,
+ };
+
+ llinfos << "Untarring package: " << package_file << llendl;
+
+ // store current SIGCHLD handler if there is one, replace with default
+ // handler to make glib happy
+ struct sigaction sigchld_backup;
+ struct sigaction sigchld_appease_glib;
+ sigchld_appease_glib.sa_handler = SIG_DFL;
+ sigemptyset(&sigchld_appease_glib.sa_mask);
+ sigchld_appease_glib.sa_flags = 0;
+ sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup);
+
+ gchar *stderr_output = NULL;
+ gint child_exit_status = 0;
+ GError *untar_error = NULL;
+ if (!less_anal_gspawnsync(argv, &stderr_output,
+ &child_exit_status, &untar_error))
+ {
+ llwarns << "Failed to spawn child process: "
+ << untar_error->message
+ << llendl;
+ return FALSE;
+ }
+
+ if (child_exit_status)
+ {
+ llwarns << "Untar command failed: "
+ << (stderr_output ? stderr_output : "(no reason given)")
+ << llendl;
+ return FALSE;
+ }
+
+ g_free(tar_cmd);
+ g_free(package_file_string_copy);
+ g_free(tmp_dest_dir_string_copy);
+ g_free(stderr_output);
+ if (untar_error) g_error_free(untar_error);
+
+ // move the existing package out of the way if it exists
+ if (gDirUtilp->fileExists(destination))
+ {
+ std::string backup_dir = destination + ".backup";
+ int oldcounter = 1;
+ while (gDirUtilp->fileExists(backup_dir))
+ {
+ // find a foo.backup.N folder name that isn't taken yet
+ backup_dir = destination + ".backup." + llformat("%d", oldcounter);
+ ++oldcounter;
+ }
+
+ if (rename_with_sudo_fallback(destination, backup_dir))
+ {
+ llwarns << "Failed to move directory: '"
+ << destination << "' -> '" << backup_dir
+ << llendl;
+ return FALSE;
+ }
+ }
+
+ // The package has been unpacked in a staging directory, now we just
+ // need to move it to its destination.
+ if (rename_with_sudo_fallback(tmp_dest_dir, destination))
+ {
+ llwarns << "Failed to move installation to the destination: "
+ << destination
+ << llendl;
+ return FALSE;
+ }
+
+ // \0/ Success!
+ return TRUE;
+}
+
+gboolean progress_update_timeout(gpointer data)
+{
+ UpdaterAppState *app_state;
+
+ llassert(data != NULL);
+
+ app_state = (UpdaterAppState *) data;
+
+ gdk_threads_enter();
+ if (app_state->activity_mode)
+ {
+ gtk_progress_bar_pulse
+ (GTK_PROGRESS_BAR(app_state->progress_bar));
+ }
+ else
+ {
+ gtk_progress_set_value(GTK_PROGRESS(app_state->progress_bar),
+ app_state->progress_value);
+ }
+ gdk_threads_leave();
+
+ return TRUE;
+}
+
+gboolean update_progress_text_timeout(gpointer data)
+{
+ UpdaterAppState *app_state;
+
+ llassert(data != NULL);
+ app_state = (UpdaterAppState *) data;
+
+ if (app_state->activity_mode == TRUE)
+ {
+ // We no longer need this timeout, it will be removed.
+ return FALSE;
+ }
+
+ if (!app_state->progress_value)
+ {
+ return TRUE;
+ }
+
+ std::string progress_text = llformat((LLTrans::getString("UpdaterProgressBarText")+" (%.0f%%)").c_str(), app_state->progress_value);
+
+ gdk_threads_enter();
+ gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
+ progress_text.c_str());
+ gdk_threads_leave();
+
+ return TRUE;
+}
+
+int download_progress_cb(gpointer data,
+ double t,
+ double d,
+ double utotal,
+ double ulnow)
+{
+ UpdaterAppState *app_state;
+
+ llassert(data != NULL);
+ app_state = (UpdaterAppState *) data;
+
+ if (t <= 0.0)
+ {
+ app_state->progress_value = 0;
+ }
+ else
+ {
+ app_state->progress_value = d * 100.0 / t;
+ }
+ return 0;
+}
+
+BOOL spawn_viewer(UpdaterAppState *app_state)
+{
+ llassert(app_state != NULL);
+
+ std::string cmd = app_state->dest_dir + "/secondlife";
+ GError *error = NULL;
+
+ // We want to spawn the Viewer on the same display as the updater app
+ gboolean success = gdk_spawn_command_line_on_screen
+ (gtk_widget_get_screen(app_state->window), cmd.c_str(), &error);
+
+ if (!success)
+ {
+ llwarns << "Failed to launch viewer: " << error->message
+ << llendl;
+ }
+
+ if (error) g_error_free(error);
+
+ return success;
+}
+
+void show_usage_and_exit()
+{
+ std::cout << "Usage: linux-updater --url URL --name NAME --dest PATH --stringsdir PATH1,PATH2 --stringsfile FILE"
+ << "[--image-dir PATH]"
+ << std::endl;
+ exit(1);
+}
+
+void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state)
+{
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ if ((!strcmp(argv[i], "--url")) && (++i < argc))
+ {
+ app_state->url = argv[i];
+ }
+ else if ((!strcmp(argv[i], "--name")) && (++i < argc))
+ {
+ app_state->app_name = argv[i];
+ }
+ else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc))
+ {
+ app_state->image_dir = argv[i];
+ }
+ else if ((!strcmp(argv[i], "--dest")) && (++i < argc))
+ {
+ app_state->dest_dir = argv[i];
+ }
+ else if ((!strcmp(argv[i], "--stringsdir")) && (++i < argc))
+ {
+ app_state->strings_dirs = argv[i];
+ }
+ else if ((!strcmp(argv[i], "--stringsfile")) && (++i < argc))
+ {
+ app_state->strings_file = argv[i];
+ }
+ else
+ {
+ // show usage, an invalid option was given.
+ show_usage_and_exit();
+ }
+ }
+
+ if (app_state->app_name.empty()
+ || app_state->url.empty()
+ || app_state->dest_dir.empty())
+ {
+ show_usage_and_exit();
+ }
+
+ app_state->progress_value = 0.0;
+ app_state->activity_mode = FALSE;
+ app_state->failure = FALSE;
+
+ translate_init(app_state->strings_dirs, app_state->strings_file);
+}
+
+int main(int argc, char **argv)
+{
+ UpdaterAppState app_state;
+ GThread *worker_thread;
+
+ parse_args_and_init(argc, argv, &app_state);
+
+ // Initialize logger, and rename old log file
+ gDirUtilp->initAppDirs("SecondLife");
+ LLError::initForApplication
+ (gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
+ std::string old_log_file = gDirUtilp->getExpandedFilename
+ (LL_PATH_LOGS, "updater.log.old");
+ std::string log_file =
+ gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log");
+ LLFile::rename(log_file, old_log_file);
+ LLError::logToFile(log_file);
+
+ // initialize gthreads and gtk+
+ if (!g_thread_supported())
+ {
+ g_thread_init(NULL);
+ gdk_threads_init();
+ }
+
+ gtk_init(&argc, &argv);
+
+ // create UI
+ updater_app_ui_init(&app_state);
+
+ //llinfos << "SAMPLE TRANSLATION IS: " << LLTrans::getString("LoginInProgress") << llendl;
+
+ // create download thread
+ worker_thread = g_thread_create
+ (GThreadFunc(worker_thread_cb), &app_state, FALSE, NULL);
+
+ gdk_threads_enter();
+ gtk_main();
+ gdk_threads_leave();
+
+ return (app_state.failure == FALSE) ? 0 : 1;
+}