diff options
author | Adam Moss <moss@lindenlab.com> | 2009-08-21 21:52:21 +0000 |
---|---|---|
committer | Adam Moss <moss@lindenlab.com> | 2009-08-21 21:52:21 +0000 |
commit | c3cbd049859c058526ae9a07a5cbfa7e51085943 (patch) | |
tree | 80f74a033a2f56f465cd4a67fa3e11a00bc18248 /indra/linux_updater | |
parent | 87be6648d50efc55785e2298a9ed6ec0546da564 (diff) |
svn merge -r130238:130240 svn+ssh://svn.lindenlab.com/svn/linden/branches/linux-updater-6
QAR-1771 Linux Viewer Autoupdater + XUI-parse refactoring
Diffstat (limited to 'indra/linux_updater')
-rw-r--r-- | indra/linux_updater/CMakeLists.txt | 58 | ||||
-rw-r--r-- | indra/linux_updater/linux_updater.cpp | 818 |
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; +} |