pull from rev d22beb597e52ecbf1c98f25d4489ea0425eda4b0 of sl-321

coyot@coyot-sager-PC 2017-02-28 22:35:01 +00:00
parent 2699ef356a
commit 069c938eb6
22 changed files with 0 additions and 3520 deletions

View File

@ -1,49 +0,0 @@
#!/usr/bin/env python
"""\
@file InstallerError.py
@author coyot
@date 2016-05-16
@brief custom exception class for VMP
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
"""
usage:
>>> import InstallerError
>>> import os
>>> try:
... os.mkdir('/tmp')
... except OSError, oe:
... ie = InstallerError.InstallerError(oe, "foo")
... raise ie
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
InstallerError.InstallerError: [Errno [Errno 17] File exists: '/tmp'] foo
"""
class InstallerError(OSError):
def __init___(self, message):
Exception.__init__(self, message)

View File

@ -1,316 +0,0 @@
#!/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:firstyear=2013&license=viewerlgpl$
# Copyright (c) 2013, Linden Research, Inc.
# $/LicenseInfo$
"""
@file InstallerUserMessage.py
@author coyot
@date 2016-05-16
"""
"""
This does everything the old updater/scripts/darwin/messageframe.py script did and some more bits.
Pushed up the manager directory to be multiplatform.
"""
import os
import Queue
import threading
import time
import Tkinter as tk
import ttk
class InstallerUserMessage(tk.Tk):
#Goals for this class:
# Provide a uniform look and feel
# Provide an easy to use convenience class for other scripts
# Provide windows that automatically disappear when done (for differing notions of done)
# Provide a progress bar that isn't a glorified spinner, but based on download progress
#Non-goals:
# No claim to threadsafety is made or warranted. Your mileage may vary.
# Please consult a doctor if you experience thread pain.
#Linden standard green color, from Marketing
linden_green = "#487A7B"
def __init__(self, text="", title="", width=500, height=200, wraplength = 400, icon_name = None, icon_path = None):
tk.Tk.__init__(self)
self.grid()
self.title(title)
self.choice = tk.BooleanVar()
self.config(background = 'black')
# background="..." doesn't work on MacOS for radiobuttons or progress bars
# http://tinyurl.com/tkmacbuttons
ttk.Style().configure('Linden.TLabel', foreground=InstallerUserMessage.linden_green, background='black')
ttk.Style().configure('Linden.TButton', foreground=InstallerUserMessage.linden_green, background='black')
ttk.Style().configure("black.Horizontal.TProgressbar", foreground=InstallerUserMessage.linden_green, background='black')
#This bit of configuration centers the window on the screen
# The constants below are to adjust for typical overhead from the
# frame borders.
self.xp = (self.winfo_screenwidth() / 2) - (width / 2) - 8
self.yp = (self.winfo_screenheight() / 2) - (height / 2) - 20
self.geometry('{0}x{1}+{2}+{3}'.format(width, height, self.xp, self.yp))
#find a few things
self.script_dir = os.path.dirname(os.path.realpath(__file__))
self.contents_dir = os.path.dirname(self.script_dir)
self.icon_dir = os.path.abspath(os.path.join(self.contents_dir, 'Resources/vmp_icons'))
#finds the icon and creates the widget
self.find_icon(icon_path, icon_name)
#defines what to do when window is closed
self.protocol("WM_DELETE_WINDOW", self._delete_window)
#callback id
self.id = -1
def _delete_window(self):
#capture and discard all destroy events before the choice is set
if not ((self.choice == None) or (self.choice == "")):
try:
#initialized value. If we have an outstanding callback, kill it before killing ourselves
if self.id != -1:
self.after_cancel(self.id)
self.destroy()
except:
#tk may try to destroy the same object twice
pass
def set_colors(self, widget):
# #487A7B is "Linden Green"
widget.config(foreground = InstallerUserMessage.linden_green)
widget.config(background='black')
def find_icon(self, icon_path = None, icon_name = None):
#we do this in each message, let's do it just once instead.
if not icon_path:
icon_path = self.icon_dir
icon_path = os.path.join(icon_path, icon_name)
print icon_path
if os.path.exists(icon_path):
icon = tk.PhotoImage(file=icon_path)
self.image_label = tk.Label(image = icon)
self.image_label.image = icon
else:
#default to text if image not available
self.image_label = tk.Label(text = "Second Life")
def auto_resize(self, row_count = 0, column_count = 0, heavy_row = None, heavy_column = None):
#auto resize window to fit all rows and columns
#"heavy" gets extra weight
for x in range(column_count):
if x == heavy_column:
self.columnconfigure(x, weight = 2)
else:
self.columnconfigure(x, weight=1)
for y in range(row_count):
if y == heavy_row:
self.rowconfigure(y, weight = 2)
else:
self.rowconfigure(x, weight=1)
def basic_message(self, message):
#message: text to be displayed
#icon_path: directory holding the icon, defaults to icons subdir of script dir
#icon_name: filename of icon to be displayed
self.choice.set(True)
self.text_label = tk.Label(text = message)
self.set_colors(self.text_label)
self.set_colors(self.image_label)
#pad, direction and weight are all experimentally derived by retrying various values
self.image_label.grid(row = 1, column = 1, sticky = 'W')
self.text_label.grid(row = 1, column = 2, sticky = 'W', padx =100)
self.auto_resize(row_count = 1, column_count = 2)
self.mainloop()
def binary_choice_message(self, message, true = 'Yes', false = 'No'):
#true: first option, returns True
#false: second option, returns False
#usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
#usage:
# frame = InstallerUserMessage.InstallerUserMessage( ... )
# frame = frame.binary_choice_message( ... )
# (wait for user to click)
# value = frame.choice.get()
self.text_label = tk.Label(text = message)
#command registers the callback to the method named. We want the frame to go away once clicked.
#button 1 returns True/1, button 2 returns False/0
self.button_one = ttk.Radiobutton(text = true, variable = self.choice, value = True,
command = self._delete_window, style = 'Linden.TButton')
self.button_two = ttk.Radiobutton(text = false, variable = self.choice, value = False,
command = self._delete_window, style = 'Linden.TButton')
self.set_colors(self.text_label)
self.set_colors(self.image_label)
#pad, direction and weight are all experimentally derived by retrying various values
self.image_label.grid(row = 1, column = 1, rowspan = 3, sticky = 'W')
self.text_label.grid(row = 1, column = 2, rowspan = 3)
self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 40)
self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 0)
self.auto_resize(row_count = 2, column_count = 3, heavy_column = 3)
#self.button_two.deselect()
self.update()
self.mainloop()
def trinary_choice_message(self, message, one = 1, two = 2, three = 3):
#one: first option, returns 1
#two: second option, returns 2
#three: third option, returns 3
#usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
#usage:
# frame = InstallerUserMessage.InstallerUserMessage( ... )
# frame = frame.binary_choice_message( ... )
# (wait for user to click)
# value = frame.choice.get()
self.text_label = tk.Label(text = message)
#command registers the callback to the method named. We want the frame to go away once clicked.
self.button_one = ttk.Radiobutton(text = one, variable = self.choice, value = 1,
command = self._delete_window, style = 'Linden.TButton')
self.button_two = ttk.Radiobutton(text = two, variable = self.choice, value = 2,
command = self._delete_window, style = 'Linden.TButton')
self.button_three = ttk.Radiobutton(text = three, variable = self.choice, value = 3,
command = self._delete_window, style = 'Linden.TButton')
self.set_colors(self.text_label)
self.set_colors(self.image_label)
#pad, direction and weight are all experimentally derived by retrying various values
self.image_label.grid(row = 1, column = 1, rowspan = 4, sticky = 'W')
self.text_label.grid(row = 1, column = 2, rowspan = 4, padx = 5)
self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 5)
self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 5)
self.button_three.grid(row = 3, column = 3, sticky = 'W', pady = 5)
self.auto_resize(row_count = 3, column_count = 3, heavy_column = 3)
#self.button_two.deselect()
self.update()
self.mainloop()
def progress_bar(self, message = None, size = 0, interval = 100, pb_queue = None):
#Best effort attempt at a real progress bar
# This is what Tk calls "determinate mode" rather than "indeterminate mode"
#size: denominator of percent complete
#interval: frequency, in ms, of how often to poll the file for progress
#pb_queue: queue object used to send updates to the bar
self.text_label = tk.Label(text = message)
self.set_colors(self.text_label)
self.set_colors(self.image_label)
self.image_label.grid(row = 1, column = 1, sticky = 'NSEW')
self.text_label.grid(row = 2, column = 1, sticky = 'NSEW')
self.progress = ttk.Progressbar(self, style = 'black.Horizontal.TProgressbar', orient="horizontal", length=100, mode="determinate")
self.progress.grid(row = 3, column = 1, sticky = 'NSEW')
self.value = 0
self.progress["maximum"] = size
self.auto_resize(row_count = 1, column_count = 3)
self.queue = pb_queue
self.check_scheduler()
def check_scheduler(self):
try:
if self.value < self.progress["maximum"]:
self.check_queue()
self.id = self.after(100, self.check_scheduler)
else:
#prevent a race condition between polling and the widget destruction
self.after_cancel(self.id)
except tk.TclError:
#we're already dead, just die quietly
pass
def check_queue(self):
while self.queue.qsize():
try:
msg = float(self.queue.get(0))
#custom signal, time to tear down
if msg == -1:
self.choice.set(True)
self.destroy()
else:
self.progress.step(msg)
self.value = msg
except Queue.Empty:
#nothing to do
return
class ThreadedClient(threading.Thread):
#for test only, not part of the functional code
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for x in range(1, 90, 10):
time.sleep(1)
print "run " + str(x)
self.queue.put(10)
#tkk progress bars wrap at exactly 100 percent, look full at 99%
print "leftovers"
self.queue.put(9)
time.sleep(5)
# -1 is a custom signal to the progress_bar to quit
self.queue.put(-1)
if __name__ == "__main__":
#When run as a script, just test the InstallUserMessage.
#To proceed with the test, close the first window, select on the second and fourth. The third will close by itself.
import sys
import tempfile
def set_and_check(frame, value):
print "value: " + str(value)
frame.progress.step(value)
if frame.progress["value"] < frame.progress["maximum"]:
print "In Progress"
else:
print "Over now"
#basic message window test
frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100", icon_name="head-sl-logo.gif")
print frame2.contents_dir
print frame2.icon_dir
frame2.basic_message(message = "...attracts me like no other.")
print "Destroyed!"
sys.stdout.flush()
#binary choice test. User destroys window when they select.
frame3 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
frame3.binary_choice_message(message = "And all I have to do is think of her.",
true = "Don't want to leave her now", false = 'You know I believe and how')
print frame3.choice.get()
sys.stdout.flush()
#progress bar
queue = Queue.Queue()
thread = ThreadedClient(queue)
thread.start()
print "thread started"
frame4 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 300", icon_name="head-sl-logo.gif")
frame4.progress_bar(message = "You're asking me will my love grow", size = 100, pb_queue = queue)
print "frame defined"
frame4.mainloop()
#trinary choice test. User destroys window when they select.
frame3a = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
frame3a.trinary_choice_message(message = "And all I have to do is think of her.",
one = "Don't want to leave her now", two = 'You know I believe and how', three = 'John is Dead')
print frame3a.choice.get()
sys.stdout.flush()

View File

@ -1,199 +0,0 @@
#!/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 --<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, '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)

View File

@ -1,277 +0,0 @@
#!/usr/bin/env python
# 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:firstyear=2016&license=viewerlgpl$
# Copyright (c) 2016, Linden Research, Inc.
# $/LicenseInfo$
"""
@file apply_update.py
@author coyot
@date 2016-06-28
"""
"""
Applies an already downloaded update.
"""
import argparse
import errno
import fnmatch
import InstallerUserMessage as IUM
import os
import os.path
import plistlib
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
#Module level variables
#fnmatch expressions
LNX_REGEX = '*' + '.bz2'
MAC_REGEX = '*' + '.dmg'
MAC_APP_REGEX = '*' + '.app'
WIN_REGEX = '*' + '.exe'
#which install the updater is run from
INSTALL_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
#whether the update is to the INSTALL_DIR or not. Most of the time this is the case.
IN_PLACE = True
BUNDLE_IDENTIFIER = "com.secondlife.indra.viewer"
# Magic OS directory name that causes Cocoa viewer to crash on OS X 10.7.5
# (see MAINT-3331)
STATE_DIR = os.path.join(os.environ["HOME"], "Library", "Saved Application State",
BUNDLE_IDENTIFIER + ".savedState")
def silent_write(log_file_handle, text):
#if we have a log file, write. If not, do nothing.
if (log_file_handle):
#prepend text for easy grepping
log_file_handle.write("APPLY UPDATE: " + text + "\n")
def get_filename(download_dir = None):
#given a directory that supposedly has the download, find the installable
#if you are on platform X and you give the updater a directory with an installable
#for platform Y, you are either trying something fancy or get what you deserve
#or both
for filename in os.listdir(download_dir):
if (fnmatch.fnmatch(filename, LNX_REGEX)
or fnmatch.fnmatch(filename, MAC_REGEX)
or fnmatch.fnmatch(filename, WIN_REGEX)):
return os.path.join(download_dir, filename)
#someone gave us a bad directory
return None
def try_dismount(log_file_handle = None, installable = None, tmpdir = None):
#best effort cleanup try to dismount the dmg file if we have mounted one
#the French judge gave it a 5.8
try:
#use the df command to find the device name
#Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
#/dev/disk1s2 2047936 643280 1404656 32% 80408 175582 31% /private/tmp/mnt/Second Life Installer
command = ["df", os.path.join(tmpdir, "Second Life Installer")]
output = subprocess.check_output(command)
#first word of second line of df output is the device name
mnt_dev = output.split('\n')[1].split()[0]
#do the dismount
command = ["hdiutil", "detach", "-force", mnt_dev]
output = subprocess.check_output(command)
silent_write(log_file_handle, "hdiutil detach succeeded")
silent_write(log_file_handle, output)
except Exception, e:
silent_write(log_file_handle, "Could not detach dmg file %s. Error messages: %s" % (installable, e.message))
def apply_update(download_dir = None, platform_key = None, log_file_handle = None, in_place = True):
#for lnx and mac, returns path to newly installed viewer
#for win, return the name of the executable
#returns None on failure for all three
#throws an exception if it can't find an installable at all
IN_PLACE = in_place
installable = get_filename(download_dir)
if not installable:
#could not find the download
raise ValueError("Could not find installable in " + download_dir)
#apply update using the platform specific tools
if platform_key == 'lnx':
installed = apply_linux_update(installable, log_file_handle)
elif platform_key == 'mac':
installed = apply_mac_update(installable, log_file_handle)
elif platform_key == 'win':
installed = apply_windows_update(installable, log_file_handle)
else:
#wtf?
raise ValueError("Unknown Platform: " + platform_key)
if not installed:
#only mark the download as done when everything is done
done_filename = os.path.join(os.path.dirname(installable), ".done")
open(done_filename, 'w+').close()
return installed
def apply_linux_update(installable = None, log_file_handle = None):
try:
#untar to tmpdir
tmpdir = tempfile.mkdtemp()
tar = tarfile.open(name = installable, mode="r:bz2")
tar.extractall(path = tmpdir)
if IN_PLACE:
#rename current install dir
shutil.move(INSTALL_DIR,install_dir + ".bak")
#mv new to current
shutil.move(tmpdir, INSTALL_DIR)
#delete tarball on success
os.remove(installable)
except Exception, e:
silent_write(log_file_handle, "Update failed due to " + repr(e))
return None
return INSTALL_DIR
def apply_mac_update(installable = None, log_file_handle = None):
#INSTALL_DIR is something like /Applications/Second Life Viewer.app/Contents/MacOS, need to jump up two levels for the install base
install_base = os.path.dirname(INSTALL_DIR)
install_base = os.path.dirname(install_base)
#verify dmg file
try:
output = subprocess.check_output(["hdiutil", "verify", installable], stderr=subprocess.STDOUT)
silent_write(log_file_handle, "dmg verification succeeded")
silent_write(log_file_handle, output)
except Exception, e:
silent_write(log_file_handle, "Could not verify dmg file %s. Error messages: %s" % (installable, e.message))
return None
#make temp dir and mount & attach dmg
tmpdir = tempfile.mkdtemp()
try:
output = subprocess.check_output(["hdiutil", "attach", installable, "-mountroot", tmpdir])
silent_write(log_file_handle, "hdiutil attach succeeded")
silent_write(log_file_handle, output)
except Exception, e:
silent_write(log_file_handle, "Could not attach dmg file %s. Error messages: %s" % (installable, e.message))
return None
#verify plist
mounted_appdir = None
for top_dir in os.listdir(tmpdir):
for appdir in os.listdir(os.path.join(tmpdir, top_dir)):
appdir = os.path.join(os.path.join(tmpdir, top_dir), appdir)
if fnmatch.fnmatch(appdir, MAC_APP_REGEX):
try:
plist = os.path.join(appdir, "Contents", "Info.plist")
CFBundleIdentifier = plistlib.readPlist(plist)["CFBundleIdentifier"]
mounted_appdir = appdir
except:
#there is no except for this try because there are multiple directories that legimately don't have what we are looking for
pass
if not mounted_appdir:
silent_write(log_file_handle, "Could not find app bundle in dmg %s." % (installable,))
return None
if CFBundleIdentifier != BUNDLE_IDENTIFIER:
silent_write(log_file_handle, "Wrong or null bundle identifier for dmg %s. Bundle identifier: %s" % (installable, CFBundleIdentifier))
try_dismount(log_file_handle, installable, tmpdir)
return None
#do the install, finally
if IN_PLACE:
# swap out old install directory
bundlename = os.path.basename(mounted_appdir)
silent_write(log_file_handle, "Updating %s" % bundlename)
swapped_out = os.path.join(tmpdir, INSTALL_DIR.lstrip('/'))
shutil.move(install_base, swapped_out)
else:
silent_write(log_file_handle, "Installing %s" % install_base)
# copy over the new bits
try:
shutil.copytree(mounted_appdir, install_base, symlinks=True)
retcode = 0
except Exception, e:
# try to restore previous viewer
if os.path.exists(swapped_out):
silent_write(log_file_handle, "Install of %s failed, rolling back to previous viewer." % installable)
shutil.move(swapped_out, installed_test)
retcode = 1
finally:
try_dismount(log_file_handle, installable, tmpdir)
if retcode:
return None
#see MAINT-3331
try:
shutil.rmtree(STATE_DIR)
except Exception, e:
#if we fail to delete something that isn't there, that's okay
if e[0] == errno.ENOENT:
pass
else:
raise e
os.remove(installable)
return install_base
def apply_windows_update(installable = None, log_file_handle = None):
#the windows install is just running the NSIS installer executable
#from VMP's perspective, it is a black box
try:
output = subprocess.check_output(installable, stderr=subprocess.STDOUT)
silent_write(log_file_handle, "Install of %s succeeded." % installable)
silent_write(log_file_handle, output)
except subprocess.CalledProcessError, cpe:
silent_write(log_file_handle, "%s failed with return code %s. Error messages: %s." %
(cpe.cmd, cpe.returncode, cpe.message))
return None
#Due to the black box nature of the install, we have to derive the application path from the
#name of the installable. This is essentially reverse-engineering app_name()/app_name_oneword()
#in viewer_manifest.py
#the format of the filename is: Second_Life_{Project Name}_A-B-C-XXXXXX_i686_Setup.exe
#which deploys to C:\Program Files (x86)\SecondLifeProjectName\
#so we want all but the last four phrases and tack on Viewer if there is no project
if re.search('Project', installable):
winstall = os.path.join("C:\\Program Files (x86)\\", "".join(installable.split("_")[:-3]))
else:
winstall = os.path.join("C:\\Program Files (x86)\\", "".join(installable.split("_")[:-3])+"Viewer")
return winstall
def main():
parser = argparse.ArgumentParser("Apply Downloaded Update")
parser.add_argument('--dir', dest = 'download_dir', help = 'directory to find installable', required = True)
parser.add_argument('--pkey', dest = 'platform_key', help =' OS: lnx|mac|win', required = True)
parser.add_argument('--in_place', action = 'store_false', help = 'This upgrade is for a different channel', default = True)
parser.add_argument('--log_file', dest = 'log_file', default = None, help = 'file to write messages to')
args = parser.parse_args()
if args.log_file:
try:
f = open(args.log_file,'w+')
except:
print "%s could not be found or opened" % args.log_file
sys.exit(1)
IN_PLACE = args.in_place
result = apply_update(download_dir = args.download_dir, platform_key = args.platform_key, log_file_handle = f)
if not result:
sys.exit("Update failed")
else:
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -1,105 +0,0 @@
#!/usr/bin/env python
# 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:firstyear=2016&license=viewerlgpl$
# Copyright (c) 2016, Linden Research, Inc.
# $/LicenseInfo$
"""
@file download_update.py
@author coyot
@date 2016-06-23
"""
"""
Performs a download of an update. In a separate script from update_manager so that we can
call it with subprocess.
"""
import argparse
import InstallerUserMessage as IUM
import os
import Queue
import requests
import threading
#module default
CHUNK_SIZE = 1024
def download_update(url = None, download_dir = None, size = None, progressbar = False, chunk_size = CHUNK_SIZE):
#url to download from
#download_dir to download to
#total size (for progressbar) of download
#progressbar: whether to display one (not used for background downloads)
#chunk_size is in bytes, amount to download at once
queue = Queue.Queue()
if not os.path.exists(download_dir):
os.makedirs(download_dir)
#the url split provides the basename of the filename
filename = os.path.join(download_dir, url.split('/')[-1])
req = requests.get(url, stream=True)
down_thread = ThreadedDownload(req, filename, chunk_size, progressbar, queue)
down_thread.start()
if progressbar:
frame = IUM.InstallerUserMessage(title = "Second Life Downloader", icon_name="head-sl-logo.gif")
frame.progress_bar(message = "Download Progress", size = size, pb_queue = queue)
frame.mainloop()
else:
#nothing for the main thread to do
down_thread.join()
class ThreadedDownload(threading.Thread):
def __init__(self, req, filename, chunk_size, progressbar, in_queue):
#req is a python request object
#target filename to download to
#chunk_size is in bytes, amount to download at once
#progressbar: whether to display one (not used for background downloads)
#in_queue mediates communication between this thread and the progressbar
threading.Thread.__init__(self)
self.req = req
self.filename = filename
self.chunk_size = int(chunk_size)
self.progressbar = progressbar
self.in_queue = in_queue
def run(self):
with open(self.filename, 'wb') as fd:
#keep downloading until we run out of chunks, then download the last bit
for chunk in self.req.iter_content(self.chunk_size):
fd.write(chunk)
if self.progressbar:
#this will increment the progress bar by len(chunk)/size units
self.in_queue.put(len(chunk))
#signal value saying to the progress bar that it is done and can destroy itself
#if len(chunk) is ever -1, we get to file a bug against Python
self.in_queue.put(-1)
def main():
#main method is for standalone use such as support and QA
#VMP will import this module and run download_update directly
parser = argparse.ArgumentParser("Download URI to directory")
parser.add_argument('--url', dest='url', help='URL of file to be downloaded', required=True)
parser.add_argument('--dir', dest='download_dir', help='directory to be downloaded to', required=True)
parser.add_argument('--pb', dest='progressbar', help='whether or not to show a progressbar', action="store_true", default = False)
parser.add_argument('--size', dest='size', help='size of download for progressbar')
parser.add_argument('--chunk_size', dest='chunk_size', default=CHUNK_SIZE, help='max portion size of download to be loaded in memory in bytes.')
args = parser.parse_args()
download_update(url = args.url, download_dir = args.download_dir, size = args.size, progressbar = args.progressbar, chunk_size = args.chunk_size)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"}

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
"""\
@file test_InstallerError.py
@author coyot
@date 2016-06-01
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import assert_equal
import InstallerError
import os
def test_InstallerError():
try:
#try to make our own homedir, this will fail on all three platforms
homedir = os.path.abspath(os.path.expanduser('~'))
os.mkdir(homedir)
except OSError, oe:
ie = InstallerError.InstallerError(oe, "Installer failed to create a homedir that already exists.")
assert_equal( str(ie),
"[Errno [Errno 17] File exists: '%s'] Installer failed to create a homedir that already exists." % homedir)

View File

@ -1,59 +0,0 @@
#!/usr/bin/env python
"""
@file test_check_for_completed_download.py
@author coyot
@date 2016-06-03
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
from nose import with_setup
import os
import shutil
import tempfile
import update_manager
import with_setup_args
def check_for_completed_download_setup():
tmpdir1 = tempfile.mkdtemp(prefix = 'test1')
tmpdir2 = tempfile.mkdtemp(prefix = 'test2')
tempfile.mkstemp(suffix = '.done', dir = tmpdir1)
return [tmpdir1,tmpdir2], {}
def check_for_completed_download_teardown(tmpdir1,tmpdir2):
shutil.rmtree(tmpdir1, ignore_errors = True)
shutil.rmtree(tmpdir2, ignore_errors = True)
@with_setup_args.with_setup_args(check_for_completed_download_setup, check_for_completed_download_teardown)
def test_completed_check_for_completed_download(tmpdir1,tmpdir2):
assert_equal(update_manager.check_for_completed_download(tmpdir1), 'done'), "Failed to find completion marker"
@with_setup_args.with_setup_args(check_for_completed_download_setup, check_for_completed_download_teardown)
def test_incomplete_check_for_completed_download(tmpdir1,tmpdir2):
#should return False
incomplete = not update_manager.check_for_completed_download(tmpdir2)
assert incomplete, "False positive, should not mark complete without a marker"

View File

@ -1,61 +0,0 @@
#!/usr/bin/env python
"""\
@file test_convert_version_file_style.py
@author coyot
@date 2016-06-01
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import assert_equal
import update_manager
def test_normal_form():
version = '1.2.3.456789'
golden = '1_2_3_456789'
converted = update_manager.convert_version_file_style(version)
assert_equal(golden, converted)
def test_short_form():
version = '1.23'
golden = '1_23'
converted = update_manager.convert_version_file_style(version)
assert_equal(golden, converted)
def test_idempotent():
version = '123'
golden = '123'
converted = update_manager.convert_version_file_style(version)
assert_equal(golden, converted)
def test_none():
version = None
golden = None
converted = update_manager.convert_version_file_style(version)
assert_equal(golden, converted)

View File

@ -1,66 +0,0 @@
#!/usr/bin/env python
"""
@file test_get_filename.py
@author coyot
@date 2016-06-30
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
from nose import with_setup
import os
import shutil
import tempfile
import apply_update
import with_setup_args
def get_filename_setup():
tmpdir1 = tempfile.mkdtemp(prefix = 'lnx')
tmpdir2 = tempfile.mkdtemp(prefix = 'mac')
tmpdir3 = tempfile.mkdtemp(prefix = 'win')
tmpdir4 = tempfile.mkdtemp(prefix = 'bad')
tempfile.mkstemp(suffix = '.bz2', dir = tmpdir1)
tempfile.mkstemp(suffix = '.dmg', dir = tmpdir2)
tempfile.mkstemp(suffix = '.exe', dir = tmpdir3)
return [tmpdir1,tmpdir2, tmpdir3, tmpdir4], {}
def get_filename_teardown(tmpdir1,tmpdir2, tmpdir3, tmpdir4):
shutil.rmtree(tmpdir1, ignore_errors = True)
shutil.rmtree(tmpdir2, ignore_errors = True)
shutil.rmtree(tmpdir3, ignore_errors = True)
shutil.rmtree(tmpdir4, ignore_errors = True)
@with_setup_args.with_setup_args(get_filename_setup, get_filename_teardown)
def test_get_filename(tmpdir1, tmpdir2, tmpdir3, tmpdir4):
assert_is_not_none(apply_update.get_filename(tmpdir1)), "Failed to find installable"
assert_is_not_none(apply_update.get_filename(tmpdir2)), "Failed to find installable"
assert_is_not_none(apply_update.get_filename(tmpdir3)), "Failed to find installable"
@with_setup_args.with_setup_args(get_filename_setup, get_filename_teardown)
def test_missing_get_filename(tmpdir1, tmpdir2, tmpdir3, tmpdir4):
not_found = not apply_update.get_filename(tmpdir4)
assert not_found, "False positive, should not find an installable in an empty dir"

View File

@ -1,68 +0,0 @@
#!/usr/bin/env python
"""
@file test_get_log_file_handle.py
@author coyot
@date 2016-06-08
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
import os
import shutil
import tempfile
import update_manager
import with_setup_args
def get_log_file_handle_setup():
tmpdir1 = tempfile.mkdtemp(prefix = 'test1')
tmpdir2 = tempfile.mkdtemp(prefix = 'test2')
log_file_path = os.path.abspath(os.path.join(tmpdir1,"update_manager.log"))
#not using tempfile because we want a particular filename
open(log_file_path, 'w+').close
return [tmpdir1,tmpdir2,log_file_path], {}
def get_log_file_handle_teardown(tmpdir1,tmpdir2,log_file_path):
shutil.rmtree(tmpdir1, ignore_errors = True)
shutil.rmtree(tmpdir2, ignore_errors = True)
@with_setup_args.with_setup_args(get_log_file_handle_setup, get_log_file_handle_teardown)
def test_existing_get_log_file_handle(tmpdir1,tmpdir2,log_file_path):
handle = update_manager.get_log_file_handle(tmpdir1)
if not handle:
print "Failed to find existing log file"
assert False
elif not os.path.exists(os.path.abspath(log_file_path+".old")):
print "Failed to rotate update manager log"
assert False
assert True
@with_setup_args.with_setup_args(get_log_file_handle_setup, get_log_file_handle_teardown)
def test_missing_get_log_file_handle(tmpdir1,tmpdir2,log_file_path):
handle = update_manager.get_log_file_handle(tmpdir2)
if not os.path.exists(log_file_path):
print "Failed to touch new log file"
assert False
assert True

View File

@ -1,83 +0,0 @@
#!/usr/bin/env python
"""
@file test_get_parent_path.py
@author coyot
@date 2016-06-02
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
from nose import with_setup
import os
import shutil
import update_manager
import with_setup_args
def get_parent_path_setup():
key = update_manager.get_platform_key()
try:
if key == 'mac':
settings_dir = os.path.join(os.path.expanduser('~'),'Library','Application Support','SecondLife')
elif key == 'lnx':
settings_dir = os.path.join(os.path.expanduser('~'),'.secondlife')
elif key == 'win':
settings_dir = os.path.join(os.path.expanduser('~'),'AppData','Roaming','SecondLife')
else:
raise Exception("Invalid Platform Key")
#preserve existing settings dir if any
if os.path.exists(settings_dir):
old_dir = settings_dir + ".tmp"
if os.path.exists(old_dir):
shutil.rmtree(old_dir, ignore_errors = True)
os.rename(settings_dir, old_dir)
os.makedirs(settings_dir)
except Exception, e:
print "get_parent_path_setup failed due to: %s" % str(e)
assert False
#this is we don't have to rediscover settings_dir for test and teardown
return [settings_dir], {}
def get_parent_path_teardown(settings_dir):
try:
shutil.rmtree(settings_dir, ignore_errors = True)
#restore previous settings dir if any
old_dir = settings_dir + ".tmp"
if os.path.exists(old_dir):
os.rename(old_dir, settings_dir)
except:
#cleanup is best effort
pass
@with_setup_args.with_setup_args(get_parent_path_setup, get_parent_path_teardown)
def test_get_parent_path(settings_dir):
key = update_manager.get_platform_key()
got_settings_dir = update_manager.get_parent_path(key)
assert settings_dir, "test_get_parent_path failed to obtain parent path"
assert_equal(settings_dir, got_settings_dir)

View File

@ -1,44 +0,0 @@
#!/usr/bin/env python
"""
@file test_get_platform_key.py
@author coyot
@date 2016-06-01
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import assert_equal
import platform
import update_manager
def test_get_platform_key():
key = update_manager.get_platform_key()
if key == 'mac':
assert_equal(platform.system(),'Darwin')
elif key == 'lnx':
assert_equal(platform.system(),'Linux')
elif key == 'win':
assert_equal(platform.system(),'Windows')
else:
assert_equal(key, None)

View File

@ -1,87 +0,0 @@
#!/usr/bin/env python
"""
@file test_get_settings.py
@author coyot
@date 2016-06-03
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
from nose import with_setup
import os
import shutil
import update_manager
import with_setup_args
def get_settings_setup():
try:
key = update_manager.get_platform_key()
settings_dir = os.path.join(update_manager.get_parent_path(key), "user_settings")
print settings_dir
#preserve existing settings dir if any
if os.path.exists(settings_dir):
old_dir = settings_dir + ".tmp"
if os.path.exists(old_dir):
shutil.rmtree(old_dir, ignore_errors = True)
os.rename(settings_dir, old_dir)
os.makedirs(settings_dir)
#the data subdir of the tests dir that this script is in
data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data")
#the test settings file
settings_file = os.path.join(data_dir, "settings.xml")
shutil.copyfile(settings_file, os.path.join(settings_dir, "settings.xml"))
except Exception, e:
print "get_settings_setup failed due to: %s" % str(e)
assert False
#this is we don't have to rediscover settings_dir for test and teardown
return [settings_dir], {}
def get_settings_teardown(settings_dir):
try:
shutil.rmtree(settings_dir, ignore_errors = True)
#restore previous settings dir if any
old_dir = settings_dir + ".tmp"
if os.path.exists(old_dir):
os.rename(old_dir, settings_dir)
except:
#cleanup is best effort
pass
@with_setup_args.with_setup_args(get_settings_setup, get_settings_teardown)
def test_get_settings(settings_dir):
key = update_manager.get_platform_key()
parent = update_manager.get_parent_path(key)
log_file = update_manager.get_log_file_handle(parent)
got_settings = update_manager.get_settings(log_file, parent)
assert got_settings, "test_get_settings failed to find a settings.xml file"
#test one key just to make sure it parsed
assert_equal(got_settings['CurrentGrid']['Value'], 'util.agni.lindenlab.com')

View File

@ -1,47 +0,0 @@
#!/usr/bin/env python
"""
@file test_make_VVM_UUID_hash.py
@author coyot
@date 2016-06-03
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
import update_manager
def test_make_VVM_UUID_hash():
#because the method returns different results on different hosts
#it is not easy to unit test it reliably.
#About the best we can do is check for the exception from subprocess
key = update_manager.get_platform_key()
try:
UUID_hash = update_manager.make_VVM_UUID_hash(key)
except Exception, e:
print "Test failed due to: %s" % str(e)
assert False
#make_UUID_hash returned None
assert UUID_hash, "make_UUID_hash failed to make a hash."

View File

@ -1,47 +0,0 @@
#!/usr/bin/env python
"""
@file test_make_download_dir.py
@author coyot
@date 2016-06-03
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
import update_manager
def test_make_download_dir():
key = update_manager.get_platform_key()
path = update_manager.get_parent_path(key)
version = '1.2.3.456789'
try:
download_dir = update_manager.make_download_dir(path, version)
except OSError, e:
print "make_download_dir failed to eat OSError %s" % str(e)
assert False
except Exception, e:
print "make_download_dir raised an unexpected exception %s" % str(e)
assert False
assert download_dir, "make_download_dir returned None for path %s and version %s" % (path, version)

View File

@ -1,73 +0,0 @@
#!/usr/bin/env python
"""
@file test_query_vvm.py
@author coyot
@date 2016-06-08
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
import os
import re
import shutil
import tempfile
import update_manager
import with_setup_args
def query_vvm_setup():
tmpdir1 = tempfile.mkdtemp(prefix = 'test1')
handle = update_manager.get_log_file_handle(tmpdir1)
return [tmpdir1,handle], {}
def query_vvm_teardown(tmpdir1, handle):
shutil.rmtree(tmpdir1, ignore_errors = True)
@with_setup_args.with_setup_args(query_vvm_setup, query_vvm_teardown)
def test_query_vvm(tmpdir1, handle):
key = update_manager.get_platform_key()
parent = update_manager.get_parent_path(key)
settings = update_manager.get_settings(handle, parent)
launcher_path = os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))
summary = update_manager.get_summary(key, launcher_path)
#for unit testing purposes, just testing a value from results. If no update, then None and it falls through
#for formal QA see:
# https://docs.google.com/document/d/1WNjOPdKlq0j_7s7gdNe_3QlyGnQDa3bFNvtyVM6Hx8M/edit
# https://wiki.lindenlab.com/wiki/Login_Test#Test_Viewer_Updater
#for test plans on all cases, as it requires setting up a fake VVM service
try:
results = update_manager.query_vvm(handle, key, settings, summary)
except Exception, e:
print "query_vvm threw unexpected exception %s" % str(e)
assert False
if results:
pattern = re.compile('Second Life')
assert pattern.search(results['channel']), "Bad results returned %s" % str(results)
assert True

View File

@ -1,49 +0,0 @@
#!/usr/bin/env python
"""
@file test_silent_write.py
@author coyot
@date 2016-06-02
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
import tempfile
import update_manager
def test_silent_write_to_file():
test_log = tempfile.TemporaryFile()
try:
update_manager.silent_write(test_log, "This is a test.")
except Exception, e:
print "Test failed due to: %s" % str(e)
assert False
def test_silent_write_to_null():
try:
update_manager.silent_write(None, "This is a test.")
except Exception, e:
print "Test failed due to: %s" % str(e)
assert False

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
"""
@file test_summary.py
@author coyot
@date 2016-06-02
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
from nose.tools import *
import os.path
import tempfile
import update_manager
def test_get_summary():
key = update_manager.get_platform_key()
#launcher is one dir above tests
launcher_path = os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))
summary_json = update_manager.get_summary(key, launcher_path)
#we aren't testing the JSON library, one key pair is enough
#so we will use the one pair that is actually a constant
assert_equal(summary_json['Type'],'viewer')

View File

@ -1,68 +0,0 @@
#!/usr/bin/env python
"""
@file with_setup_args.py
@author garyvdm
@date 2016-06-02
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
def with_setup_args(setup, teardown=None):
"""Decorator to add setup and/or teardown methods to a test function::
@with_setup_args(setup, teardown)
def test_something():
" ... "
The setup function should return (args, kwargs) which will be passed to
test function, and teardown function.
Note that `with_setup_args` is useful *only* for test functions, not for test
methods or inside of TestCase subclasses.
"""
def decorate(func):
args = []
kwargs = {}
def test_wrapped():
func(*args, **kwargs)
test_wrapped.__name__ = func.__name__
def setup_wrapped():
a, k = setup()
args.extend(a)
kwargs.update(k)
if hasattr(func, 'setup'):
func.setup()
test_wrapped.setup = setup_wrapped
if teardown:
def teardown_wrapped():
if hasattr(func, 'teardown'):
func.teardown()
teardown(*args, **kwargs)
test_wrapped.teardown = teardown_wrapped
else:
if hasattr(func, 'teardown'):
test_wrapped.teardown = func.teardown()
return test_wrapped
return decorate

View File

@ -1,547 +0,0 @@
#!/usr/bin/env python
"""\
@file update_manager.py
@author coyot
@date 2016-05-16
@brief executes viewer update checking and manages downloading and applying of updates
$LicenseInfo:firstyear=2016&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2016, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import os
#NOTA BENE:
# For POSIX platforms, llbase will be imported from the same directory.
# For Windows, llbase will be compiled into the executable by pyinstaller
try:
from llbase import llrest
from llbase.llrest import RESTError
from llbase import llsd
except:
#if Windows, this is expected, if not, we're dead
if os.name == 'nt':
pass
from copy import deepcopy
from datetime import datetime
from urlparse import urljoin
import apply_update
import download_update
import errno
import fnmatch
import hashlib
import InstallerUserMessage
import json
import platform
import re
import shutil
import subprocess
import sys
import tempfile
import thread
import urllib
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 + " UPDATE MANAGER: " + text + "\n")
def after_frame(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.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(timeout, lambda: frame._delete_window)
frame.after(timeout, lambda: frame.destroy())
frame.basic_message(message = message)
def convert_version_file_style(version):
#converts a version string a.b.c.d to a_b_c_d as used in downloaded filenames
#re will throw a TypeError if it gets None, just return that.
try:
pattern = re.compile('\.')
return pattern.sub('_', version)
except TypeError, te:
return None
def get_platform_key():
#this is the name that is inserted into the VVM URI
#and carried forward through the rest of the updater to determine
#platform specific actions as appropriate
platform_dict = {'Darwin':'mac', 'Linux':'lnx', 'Windows':'win'}
platform_uname = platform.system()
try:
return platform_dict[platform_uname]
except KeyError:
return None
def get_summary(platform_name, launcher_path):
#get the contents of the summary.json file.
#for linux and windows, this file is in the same directory as the script
#for mac, the script is in ../Contents/MacOS/ and the file is in ../Contents/Resources/
script_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
if (platform_name == 'mac'):
summary_dir = os.path.abspath(os.path.join(script_dir, "../Resources"))
else:
summary_dir = script_dir
summary_file = os.path.join(summary_dir,"summary.json")
with open(summary_file) as summary_handle:
return json.load(summary_handle)
def get_parent_path(platform_name):
#find the parent of the logs and user_settings directories
if (platform_name == 'mac'):
settings_dir = os.path.join(os.path.expanduser('~'),'Library','Application Support','SecondLife')
elif (platform_name == 'lnx'):
settings_dir = os.path.join(os.path.expanduser('~'),'.secondlife')
#using list format of join is important here because the Windows pathsep in a string escapes the next char
elif (platform_name == 'win'):
settings_dir = os.path.join(os.path.expanduser('~'),'AppData','Roaming','SecondLife')
else:
settings_dir = None
return settings_dir
def make_download_dir(parent_dir, new_version):
#make a canonical download dir if it does not already exist
#format: ../user_settings/downloads/1.2.3.456789
#we do this so that multiple viewers on the same host can update separately
#this also functions as a getter
try:
download_dir = os.path.join(parent_dir, "downloads", new_version)
os.makedirs(download_dir)
except OSError, hell:
#Directory already exists, that's okay. Other OSErrors are not okay.
if hell[0] == errno.EEXIST:
pass
else:
raise hell
return download_dir
def check_for_completed_download(download_dir):
#there will be two files on completion, the download and a marker file called "".done""
#for optional upgrades, there may also be a .skip file to skip this particular upgrade
#or .next to install on next run
completed = None
marker_regex = '*' + '.done'
skip_regex = '*' + '.skip'
next_regex = '*' + '.next'
for filename in os.listdir(download_dir):
if fnmatch.fnmatch(filename, marker_regex):
completed = 'done'
elif fnmatch.fnmatch(filename, skip_regex):
completed = 'skip'
elif fnmatch.fnmatch(filename, next_regex):
#so we don't skip infinitely
os.remove(filename)
completed = 'next'
if not completed:
#cleanup
shutil.rmtree(download_dir)
return completed
def get_settings(log_file_handle, parent_dir):
#return the settings file parsed into a dict
try:
settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml'))
#this happens when the path to settings file happens on the command line
#we get a full path and don't need to munge it
if not os.path.exists(settings_file):
settings_file = parent_dir
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 get_log_file_handle(parent_dir, filename = None):
#return a write handle on the log file
#plus log rotation and not dying on failure
if not filename:
return None
log_file = os.path.join(parent_dir, filename)
old_file = log_file + '.old'
#if someone's log files are present but not writable, they've screwed up their install.
if os.access(log_file, os.W_OK):
if os.access(old_file, os.W_OK):
os.unlink(old_file)
os.rename(log_file, old_file)
elif not os.path.exists(log_file):
#reimplement TOUCH(1) in Python
#perms default to 644 which is fine
open(log_file, 'w+').close()
try:
f = open(log_file,'w+')
except Exception as e:
#we don't have a log file to write to, make a best effort and sally onward
print "Could not open update manager log file %s" % log_file
f = None
return f
def make_VVM_UUID_hash(platform_key):
#NOTE: There is no python library support for a persistent machine specific UUID
# AND all three platforms do this a different way, so exec'ing out is really the best we can do
#Lastly, this is a best effort service. If we fail, we should still carry on with the update
uuid = None
if (platform_key == 'lnx'):
uuid = subprocess.check_output(['/usr/bin/hostid']).rstrip()
elif (platform_key == 'mac'):
#this is absurdly baroque
#/usr/sbin/system_profiler SPHardwareDataType | fgrep 'Serial' | awk '{print $NF}'
uuid = subprocess.check_output(["/usr/sbin/system_profiler", "SPHardwareDataType"])
#findall[0] does the grep for the value we are looking for: "Serial Number (system): XXXXXXXX"
#split(:)[1] gets us the XXXXXXX part
#lstrip shaves off the leading space that was after the colon
uuid = re.split(":", re.findall('Serial Number \(system\): \S*', uuid)[0])[1].lstrip()
elif (platform_key == 'win'):
# wmic csproduct get UUID | grep -v UUID
uuid = subprocess.check_output(['wmic','csproduct','get','UUID'])
#outputs in two rows:
#UUID
#XXXXXXX-XXXX...
uuid = re.split('\n',uuid)[1].rstrip()
if uuid is not None:
return hashlib.md5(uuid).hexdigest()
else:
#fake it
return hashlib.md5(str(uuid.uuid1())).hexdigest()
def query_vvm(log_file_handle = None, platform_key = None, settings = None, summary_dict = None, UpdaterServiceURL = None, UpdaterWillingToTest = None):
result_data = None
baseURI = None
#URI template /update/v1.1/channelname/version/platformkey/platformversion/willing-to-test/uniqueid
#https://wiki.lindenlab.com/wiki/Viewer_Version_Manager_REST_API#Viewer_Update_Query
#note that the only two valid options are:
# # version-phx0.damballah.lindenlab.com
# # version-qa.secondlife-staging.com
print "updater service host: " + repr(UpdaterServiceURL)
if UpdaterServiceURL:
#we can't really expect the users to put the protocol or base dir on, they will give us a host
base_URI = urljoin('https://' + UpdaterServiceURL[0], '/update/')
else:
base_URI = 'https://update.secondlife.com/update/'
channelname = summary_dict['Channel']
#this is kind of a mess because the settings value is a) in a map and b) is both the cohort and the version in one string
version = summary_dict['Version']
#we need to use the dotted versions of the platform versions in order to be compatible with VVM rules and arithmetic
if platform_key == 'win':
platform_version = platform.win32_ver()[1]
elif platform_key == 'mac':
platform_version = platform.mac_ver()[0]
else:
platform_version = platform.release()
#this will always return something usable, error handling in method
UUID = str(make_VVM_UUID_hash(platform_key))
#note that this will not normally be in a settings.xml file and is only here for test builds.
#for test builds, add this key to the ../user_settings/settings.xml
"""
<key>test</key>
<map>
<key>Comment</key>
<string>Tell update manager you aren't willing to test.</string>
<key>Type</key>
<string>String</string>
<key>Value</key>
<integer>testno</integer>
</map>
</map>
"""
if UpdaterWillingToTest is not None:
if UpdaterWillingToTest:
test_ok = 'testok'
else:
test_ok = 'testno'
else:
try:
test_ok = settings['test']['Value']
except KeyError:
#normal case, no testing key
test_ok = 'testok'
#because urljoin can't be arsed to take multiple elements
#channelname is a list because although it is only one string, it is a kind of argument and viewer args can take multiple keywords.
query_string = urllib.quote('v1.0/' + channelname[0] + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID)
silent_write(log_file_handle, "About to query VVM: %s" % base_URI + query_string)
VVMService = llrest.SimpleRESTService(name='VVM', baseurl=base_URI)
try:
result_data = VVMService.get(query_string)
except RESTError as re:
silent_write(log_file_handle, "Failed to query VVM using %s failed as %s" % (urljoin(base_URI,query_string), re))
return None
return result_data
def download(url = None, version = None, download_dir = None, size = 0, background = False, chunk_size = None, log_file_handle = None):
download_tries = 0
download_success = False
if not chunk_size:
chunk_size = 1024
#for background execution
path_to_downloader = os.path.join(os.path.dirname(os.path.realpath(__file__)), "download_update.py")
#three strikes and you're out
while download_tries < 3 and not download_success:
#323: Check for a partial update of the required update; in either event, display an alert that a download is required, initiate the download, and then install and launch
if download_tries == 0:
after_frame(message = "Downloading new version " + version + " Please wait.", timeout = 5000)
else:
after_frame(message = "Trying again to download new version " + version + " Please wait.", timeout = 5000)
if not background:
try:
download_update.download_update(url = url, download_dir = download_dir, size = size, progressbar = True, chunk_size = chunk_size)
download_success = True
except Exception, e:
download_tries += 1
silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.")
silent_write(log_file_handle, "Logging download exception: %s" % e.message)
else:
try:
#Python does not have a facility to multithread a method, so we make the method a standalone
#and subprocess that
subprocess.call(path_to_downloader, "--url = %s --dir = %s --pb --size = %s --chunk_size = %s" % (url, download_dir, size, chunk_size))
download_success = True
except:
download_tries += 1
silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.")
if not download_success:
silent_write(log_file_handle, "Failed to download new version " + version)
after_frame(message = "Failed to download new version " + version + " Please check connectivity.")
return False
return True
def install(platform_key = None, download_dir = None, log_file_handle = None, in_place = None, downloaded = None):
#user said no to this one
if downloaded != 'skip':
after_frame(message = "New version downloaded. Installing now, please wait.")
success = apply_update.apply_update(download_dir, platform_key, log_file_handle, in_place)
version = download_dir.split('/')[-1]
if success:
silent_write(log_file_handle, "successfully updated to " + version)
shutil.rmtree(download_dir)
#this is either True for in place or the path to the new install for not in place
return success
else:
after_frame(message = "Failed to apply " + version)
silent_write(log_file_handle, "Failed to update viewer to " + version)
return False
def download_and_install(downloaded = None, url = None, version = None, download_dir = None, size = None,
platform_key = None, log_file_handle = None, in_place = None, chunk_size = 1024):
#extracted to a method because we do it twice in update_manager() and this makes the logic clearer
if not downloaded:
#do the download, exit if we fail
if not download(url = url, version = version, download_dir = download_dir, size = size, chunk_size = chunk_size, log_file_handle = log_file_handle):
return (False, 'download', version)
#do the install
path_to_new_launcher = install(platform_key = platform_key, download_dir = download_dir,
log_file_handle = log_file_handle, in_place = in_place, downloaded = downloaded)
if path_to_new_launcher:
#if we succeed, propagate the success type upwards
if in_place:
return (True, 'in place', True)
else:
return (True, 'in place', path_to_new_launcher)
else:
#propagate failure
return (False, 'apply', version)
def update_manager(cli_overrides = None):
#cli_overrides is a dict where the keys are specific parameters of interest and the values are the arguments to
#comments that begin with '323:' are steps taken from the algorithm in the description of SL-323.
# Note that in the interest of efficiency, such as determining download success once at the top
# The code does follow precisely the same order as the algorithm.
#return values rather than exit codes. All of them are to communicate with launcher
#we print just before we return so that __main__ outputs something - returns are swallowed
# (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
#setup and getting initial parameters
platform_key = get_platform_key()
parent_dir = get_parent_path(platform_key)
log_file_handle = get_log_file_handle(parent_dir, 'update_manager.log')
settings = None
#check to see if user has install rights
#get the owner of the install and the current user
script_owner_id = os.stat(os.path.realpath(__file__)).st_uid
user_id = os.geteuid()
#if we are on lnx or mac, we can pretty print the IDs as names using the pwd module
#win does not provide this support and Python will throw an ImportError there, so just use raw IDs
if script_owner_id != user_id:
if platform_key != 'win':
import pwd
script_owner_name = pwd.getpwuid(script_owner_id)[0]
username = pwd.getpwuid(user_id)[0]
else:
username = user_id
script_owner_name = script_owner_id
silent_write(log_file_handle, "Upgrade notification attempted by userid " + username)
frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
frame.binary_choice_message(message = "Second Life was installed by userid " + script_owner_name
+ ". Do you have privileges to install?", true = "Yes", false = 'No')
if not frame.choice.get():
silent_write(log_file_handle, "Upgrade attempt declined by userid " + username)
after_frame(message = "Please find a system admin to upgrade Second Life")
print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
return (False, 'setup', None)
if cli_overrides is not None:
if 'settings' in cli_overrides.keys():
if cli_overrides['settings'] is not None:
settings = get_settings(log_file_handle, cli_overrides['settings'][0])
else:
settings = get_settings(log_file_handle, parent_dir)
if settings is None:
silent_write(log_file_handle, "Failed to load viewer settings")
print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
return (False, 'setup', None)
#323: If a complete download of that update is found, check the update preference:
#settings['UpdaterServiceSetting'] = 0 is manual install
"""
<key>UpdaterServiceSetting</key>
<map>
<key>Comment</key>
<string>Configure updater service.</string>
<key>Type</key>
<string>U32</string>
<key>Value</key>
<string>0</string>
</map>
"""
if cli_overrides is not None:
if 'set' in cli_overrides.keys():
if 'UpdaterServiceSetting' in cli_overrides['set'].keys():
install_automatically = cli_overrides['set']['UpdaterServiceSetting']
else:
try:
install_automatically = settings['UpdaterServiceSetting']['Value']
#because, for some godforsaken reason, we delete the setting rather than changing the value
except KeyError:
install_automatically = 1
#use default chunk size if none is given
if cli_overrides is not None:
if 'set' in cli_overrides.keys():
if 'UpdaterMaximumBandwidth' in cli_overrides['set'].keys():
chunk_size = cli_overrides['set']['UpdaterMaximumBandwidth']
else:
chunk_size = 1024
else:
chunk_size = 1024
#get channel and version
try:
summary_dict = get_summary(platform_key, os.path.abspath(os.path.realpath(__file__)))
#we send the override to the VVM, but retain the summary.json version for in_place computations
channel_override_summary = deepcopy(summary_dict)
if cli_overrides is not None:
if 'channel' in cli_overrides.keys():
channel_override_summary['Channel'] = cli_overrides['channel']
except Exception, e:
silent_write(log_file_handle, "Could not obtain channel and version, exiting.")
silent_write(log_file_handle, e.message)
print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
return (False, 'setup', None)
#323: On launch, the Viewer Manager should query the Viewer Version Manager update api.
if cli_overrides is not None:
if 'update-service' in cli_overrides.keys():
UpdaterServiceURL = cli_overrides['update-service']
else:
#tells query_vvm to use the default
UpdaterServiceURL = None
else:
UpdaterServiceURL = None
result_data = query_vvm(log_file_handle, platform_key, settings, channel_override_summary, UpdaterServiceURL)
#nothing to do or error
if not result_data:
silent_write(log_file_handle, "No update found.")
print "Update manager exited with (%s, %s, %s)" % (True, None, None)
return (True, None, None)
#get download directory, if there are perm issues or similar problems, give up
try:
download_dir = make_download_dir(parent_dir, result_data['version'])
except Exception, e:
print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
return (False, 'setup', None)
#if the channel name of the response is the same as the channel we are launched from, the update is "in place"
#and launcher will launch the viewer in this install location. Otherwise, it will launch the Launcher from
#the new location and kill itself.
in_place = (summary_dict['Channel'] == result_data['channel'])
print "summary %s, result %s, in_place %s" % (summary_dict['Channel'], result_data['channel'], in_place)
#determine if we've tried this download before
downloaded = check_for_completed_download(download_dir)
#323: If the response indicates that there is a required update:
if result_data['required'] or (not result_data['required'] and install_automatically):
#323: Check for a completed download of the required update; if found, display an alert, install the required update, and launch the newly installed viewer.
#323: If [optional download and] Install Automatically: display an alert, install the update and launch updated viewer.
return download_and_install(downloaded = downloaded, url = result_data['url'], version = result_data['version'], download_dir = download_dir,
size = result_data['size'], platform_key = platform_key, log_file_handle = log_file_handle, in_place = in_place, chunk_size = chunk_size)
else:
#323: If the update response indicates that there is an optional update:
#323: Check to see if the optional update has already been downloaded.
#323: If a complete download of that update is found, check the update preference:
#note: automatic install handled above as the steps are the same as required upgrades
#323: If Install Manually: display a message with the update information and ask the user whether or not to install the update with three choices:
#323: Skip this update: create a marker that subsequent launches should not prompt for this update as long as it is optional,
# but leave the download in place so that if it becomes required it will be there.
#323: Install next time: create a marker that skips the prompt and installs on the next launch
#323: Install and launch now: do it.
if downloaded is not None and downloaded != 'skip':
frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
#The choices are reordered slightly to encourage immediate install and slightly discourage skipping
frame.trinary_message(message = "Please make a selection",
one = "Install new version now.", two = 'Install the next time the viewer is launched.', three = 'Skip this update.')
choice = frame.choice.get()
if choice == 1:
return download_and_install(downloaded = downloaded, url = result_data['url'], version = result_data['version'], download_dir = download_dir,
size = result_data['size'], platform_key = platform_key, log_file_handle = log_file_handle, in_place = in_place, chunk_size = chunk_size)
elif choice == 2:
tempfile.mkstmp(suffix = ".next", dir = download_dir)
return (True, None, None)
else:
tempfile.mkstmp(suffix = ".skip", dir = download_dir)
return (True, None, None)
else:
#multithread a download
download(url = result_data['url'], version = result_data['version'], download_dir = download_dir, size = result_data['size'], background = True, log_file_handle = log_file_handle)
print "Update manager exited with (%s, %s, %s)" % (True, 'background', True)
return (True, 'background', True)
if __name__ == '__main__':
#there is no argument parsing or other main() work to be done
update_manager()