#!/usr/bin/env python # $LicenseInfo:firstyear=2016&license=internal$ # # Copyright (c) 2016, Linden Research, Inc. # # The following source code is PROPRIETARY AND CONFIDENTIAL. Use of # this source code is governed by the Linden Lab Source Code Disclosure # Agreement ("Agreement") previously entered between you and Linden # Lab. By accessing, using, copying, modifying or distributing this # software, you acknowledge that you have been informed of your # obligations under the Agreement 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$ # Copyright (c) 2013, Linden Research, Inc. import os import sys #module globals log_file_handle = None cwd = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, os.path.join(cwd, 'llbase')) import argparse import collections import InstallerUserMessage #NOTA BENE: # For POSIX platforms, llsd.py will be imported from the same directory. # For Windows, llsd.py will be compiled into the executable by pyinstaller try: from llbase import llsd except: #if Windows, this is expected, if not, we're dead if os.name == 'nt': pass import platform import subprocess import update_manager def silent_write(log_file_handle, text): #if we have a log file, write. If not, do nothing. #this is so we don't have to keep trapping for an exception with a None handle #oh and because it is best effort, it is also a holey_write ;) if (log_file_handle): #prepend text for easy grepping timestamp = datetime.utcnow().strftime("%Y-%m-%D %H:%M:%S") log_file_handle.write(timestamp + " LAUNCHER: " + text + "\n") def get_cmd_line(): platform_name = platform.system() #find the parent of the logs and user_settings directories if (platform_name == 'Darwin'): settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'Resources/app_settings/cmd_line.xml') elif (platform_name == 'Linux'): settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml') #using list format of join is important here because the Windows pathsep in a string escapes the next char elif (platform_name == 'Windows'): settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml') else: settings_file = None try: cmd_line = llsd.parse((open(settings_file)).read()) except: silent_write(log_file_handle, "Could not parse settings file %s" % settings_file) cmd_line = None return cmd_line def get_settings(): #return the settings file parsed into a dict try: settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml')) settings = llsd.parse((open(settings_file)).read()) except llsd.LLSDParseError as lpe: silent_write(log_file_handle, "Could not parse settings file %s" % lpe) return None return settings def capture_vmp_args(arg_list = None, cmd_line = None): #expected input format: arg_list = ['--set', 'foo', 'bar', '-X', '-Y', 'qux'] #take a copy of the viewer parameters that are of interest to VMP. #the regex for a parameter is -- {opt1} {opt2} cli_overrides = {} cmd_line = get_cmd_line() vmp_params = {'--channel':'channel', '--settings':'settings', '--update-service':'update-service', '--set':'set'} #the settings set with --set. All such settings have only one argument. vmp_setters = ('UpdaterMaximumBandwidth', 'UpdaterServiceCheckPeriod', 'UpdaterServicePath', 'UpdaterServiceSetting', 'UpdaterServiceURL', 'UpdaterWillingToTest') #Here turn the list into a queue, popping off the left as we go. Note that deque() makes a copy by value, not by reference #Because of the complexity introduced by the uncertainty of how many options a parameter can take, this is far less complicated code than the more #pythonic (x,y) = since we will sometimes have (x), sometimes (x,y) and sometimes (x,y,z) #also, because the pop is destructive, we prevent ourselves from iterating back over list elements that iterator methods would peek ahead at vmp_queue = collections.deque(arg_list) while (len(vmp_queue)): param = vmp_queue.popleft() #if it is not one of ours, pop through args until we get to the next parameter if param in vmp_params.keys(): if param == '--set': setting_name = vmp_queue.popleft() setting_value = vmp_queue.popleft() if setting_name in vmp_setters: cli_overrides[vmp_params[param]] = (setting_name, setting_value) else: #find out how many args this parameter has no_dashes = vmp_params[param] count = cmd_line[no_dashes]['count'] param_args = [] if count > 0: for argh in range(0,count): param_args.append(vmp_queue.popleft()) #the parameter name is the key, the (possibly empty) list of args is the value cli_overrides[vmp_params[param]] = param_args #to prevent KeyErrors on missing keys, set the remainder to None for key in vmp_params: if key != '--set': try: cli_overrides[key] except KeyError: cli_overrides[key] = None else: cli_overrides["--set"] = {} for arg in vmp_setters: try: cli_overrides[key][arg] except KeyError: cli_overrides[key][arg] = None return cli_overrides #main entry point #this and a few other update manager methods really should be refactored into a util lib parent_dir = update_manager.get_parent_path(update_manager.get_platform_key()) log_file_handle = update_manager.get_log_file_handle(parent_dir, 'launcher.log') executable_name = "" if sys.platform.startswith('darwin'): executable_name = "Second Life" elif sys.platform.startswith("win") or sys.platform.startswith("cyg"): if os.path.isfile(os.path.join(cwd,"SecondLifeViewer.exe")): executable_name = "SecondLifeViewer.exe" elif os.path.isfile(os.path.join(cwd,"SecondLifeTest.exe")): executable_name = "SecondLifeTest.exe" else: sys.exit("Can't find Windows viewer binary") elif sys.platform.startswith("linux"): executable_name = "secondlife" else: #SL doesn't run on VMS or punch cards sys.exit("Unsupported platform") #find the viewer to be lauched viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name) parser = argparse.ArgumentParser() args = parser.parse_known_args(sys.argv) #args[1] looks like ['./SL_Launcher', '--set', 'foo', 'bar', '-X', '-Y', 'qux'], dump the progname args_list_to_pass = args[1][1:] vmp_args = capture_vmp_args(args_list_to_pass) #make a copy by value, not by reference command = list(args_list_to_pass) (success, state, condition) = update_manager.update_manager(vmp_args) # From update_manager: # (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing) # (False, 'download', version): we failed to download the new version # (False, 'apply', version): we failed to apply the new version # (True, None, None): No update found # (True, 'in place', True): update applied in place # (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location # (True, 'background', True): background download initiated #These boil down three cases: # Success is False, then pop up a message and launch the current viewer # No update, update succeeded in place in foreground, or background update started: silently launch the current viewer channel # Updated succeed to a different channel, launch that viewer and exit if not success: msg = 'Update failed in the %s process. Please check logs. Viewer will launch starting momentarily.' % state update_manager.after_frame(msg) command.insert(0,viewer_binary) viewer_process = subprocess.Popen(command) #at the moment, we just exit here. Later, the crash monitor will be launched at this point elif (success == True and (state == None or (state == 'background' and condition == True) or (state == 'in_place' and condition == True))): command.insert(0,viewer_binary) viewer_process = subprocess.Popen(command) #at the moment, we just exit here. Later, the crash monitor will be launched at this point else: #'condition' is the path to the new launcher. command.insert(0,condition) viewer_process = subprocess.Popen(command) sys.exit(0)