summaryrefslogtreecommitdiff
path: root/indra/viewer_components/manager/SL_Launcher
blob: 1184a4c7940210c8710db87c4774272f5f169b4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/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
      log_file_handle.write("SL 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 --<param> {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) = <some generator> 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)

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)