summaryrefslogtreecommitdiff
path: root/indra/viewer_components/manager/SL_Launcher
blob: a96f2392a744f4d9de76e398d69cb09ed500f9dd (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
#!/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 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
import llsd
import os
import platform
import sys
import subprocess
import update_manager

def after_frame(my_message, timeout = 10000):
   #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds
   #note that this blocks the caller for the duration of timeout
   frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
   #this is done before basic_message so that we aren't blocked by mainloop()
   frame.after(timout, lambda: frame._delete_window)
   frame.basic_message(message = my_message)
   
def get_cmd_line():
   platform_name = platform.system()
   #find the parent of the logs and user_settings directories
   if (platform_name == 'mac'):
      settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'Resources/app_settings/cmd_line.xml')
   elif (platform_name == 'lnx'): 
      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 == 'win'):
      settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml')
   else:
      settings_dir = None
      
   try:
      cmd_line = llsd.parse((open(settings_file)).read())
   except:
      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
            count = cmd_line[param]['count']
            param_args = []
            if count:
               for argh in range(1,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:
         for arg in vmp_setters:
            try:
               cli_overrides[key][arg]
            except KeyError:
               cli_overrides[key][arg] = None
   return cli_overrides
   
cwd = os.path.dirname(os.path.realpath(__file__))

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")
   
#check for an update
#TODO

#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)
print args[1]
sys.exit()
#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(cli_overrides)
# 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.'
   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)