pull from rev d22beb597e52ecbf1c98f25d4489ea0425eda4b0 of sl-321
parent
2699ef356a
commit
069c938eb6
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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')
|
||||
|
|
@ -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."
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue