From e48c70392cffb047c7471221b7c6fcbf32d41d06 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 30 Mar 2016 11:41:29 -0700 Subject: SL-321: prototype python launcher --- indra/viewer_components/manager/SL_Launcher | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 indra/viewer_components/manager/SL_Launcher (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher new file mode 100755 index 0000000000..3c5d45d6e5 --- /dev/null +++ b/indra/viewer_components/manager/SL_Launcher @@ -0,0 +1,41 @@ +#!/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$ + +import argparse +import os +import sys +import subprocess +import Tkinter as tk + +viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),"Second Life") + +#to prove we are launching from the script, launch a Tkinter window first +root = tk.Tk() +w = tk.Label(root, text=viewer_binary) +w.pack() +root.after(10000, lambda: root.destroy()) # Destroy the widget after 10 seconds +root.mainloop() + +parser = argparse.ArgumentParser() +#parser.add_argument('--f', action='store_const', const=42) +args = parser.parse_known_args(sys.argv) +args_list_to_pass = args[1][1:] +args_list_to_pass.insert(0,viewer_binary) +#print args_list_to_pass + +viewer_process = subprocess.Popen(args_list_to_pass) \ No newline at end of file -- cgit v1.2.3 From c1951b49a6e6f625a4b5894313f98f57443b94c8 Mon Sep 17 00:00:00 2001 From: "coyot@coyot-sager-PC" Date: Thu, 7 Apr 2016 16:04:24 +0100 Subject: SL-321: Changes for VMP Windows Prototype --- indra/viewer_components/manager/SL_Launcher | 40 ++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 3c5d45d6e5..fb07d3b991 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -22,7 +22,36 @@ import sys import subprocess import Tkinter as tk -viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),"Second Life") +parser = argparse.ArgumentParser() +#parser.add_argument('--f', action='store_const', const=42) +args = parser.parse_known_args(sys.argv) +args_list_to_pass = args[1][1:] +args_list_to_pass.insert(0,viewer_binary) +#print "COYOT: arrrrrghs to pass", args_list_to_pass + +cwd = os.path.dirname(os.path.realpath(__file__)) + +executable_name = "" +if sys.platform.startswith('darwin'): + executable_name = "Second Life" +elif sys.platform.startswith("win") or sys.platform.startswith("cyg"): + if os.path.isfile(os.path.join(cwd,"SecondLifeViewer.exe")): + executable_name = "SecondLifeViewer.exe" + elif os.path.isfile(os.path.join(cwd,"SecondLifeTest.exe")): + executable_name = "SecondLifeTest.exe" + else: + #unsupported by prototypeS + 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") + +#print "COYOT: executable name ", executable_name +#print "COYOT: path ", os.path.dirname(os.path.abspath(sys.argv[0])) + +viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name) #to prove we are launching from the script, launch a Tkinter window first root = tk.Tk() @@ -31,11 +60,4 @@ w.pack() root.after(10000, lambda: root.destroy()) # Destroy the widget after 10 seconds root.mainloop() -parser = argparse.ArgumentParser() -#parser.add_argument('--f', action='store_const', const=42) -args = parser.parse_known_args(sys.argv) -args_list_to_pass = args[1][1:] -args_list_to_pass.insert(0,viewer_binary) -#print args_list_to_pass - -viewer_process = subprocess.Popen(args_list_to_pass) \ No newline at end of file +viewer_process = subprocess.Popen(args_list_to_pass) -- cgit v1.2.3 From 23863db4144fa72868f3712b8e798fea882b0cb6 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Fri, 8 Apr 2016 10:26:33 -0700 Subject: SL-321: move arg parsing past platform discovery --- indra/viewer_components/manager/SL_Launcher | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index fb07d3b991..e715546821 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -22,13 +22,6 @@ import sys import subprocess import Tkinter as tk -parser = argparse.ArgumentParser() -#parser.add_argument('--f', action='store_const', const=42) -args = parser.parse_known_args(sys.argv) -args_list_to_pass = args[1][1:] -args_list_to_pass.insert(0,viewer_binary) -#print "COYOT: arrrrrghs to pass", args_list_to_pass - cwd = os.path.dirname(os.path.realpath(__file__)) executable_name = "" @@ -53,6 +46,13 @@ else: viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name) +parser = argparse.ArgumentParser() +#parser.add_argument('--f', action='store_const', const=42) +args = parser.parse_known_args(sys.argv) +args_list_to_pass = args[1][1:] +args_list_to_pass.insert(0,viewer_binary) +#print "COYOT: arrrrrghs to pass", args_list_to_pass + #to prove we are launching from the script, launch a Tkinter window first root = tk.Tk() w = tk.Label(root, text=viewer_binary) -- cgit v1.2.3 From 8bee0a61a5bd70183ace8ae6207f626d17875d51 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 16 Jun 2016 09:04:19 -0700 Subject: SL-407: create Tkinter UI --- .../manager/InstallerUserMessage.py | 258 +++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 indra/viewer_components/manager/InstallerUserMessage.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py new file mode 100644 index 0000000000..bef2bf28a6 --- /dev/null +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -0,0 +1,258 @@ +#!/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. + def __init__(self, text="", title="", width=500, height=200, fillcolor='#487A7B', *args, **kwargs): + 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='#487A7B', background='black') + ttk.Style().configure('Linden.TButton', foreground='#487A7B', background='black') + ttk.Style().configure("black.Horizontal.TProgressbar", foreground='#487A7B', background='black') + + # 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)) + self.script_dir = os.path.dirname(os.path.realpath(__file__)) + self.icon_dir = os.path.abspath(os.path.join(self.script_dir, 'icons')) + #defines what to do when window is closed + self.protocol("WM_DELETE_WINDOW", self._delete_window) + + def _delete_window(self): + #capture and discard all destroy events before the choice is set + if not ((self.choice == None) or (self.choice == "")): + try: + 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 = '#487A7B') + 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) + 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, icon_path = None, icon_name = None): + #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.find_icon(icon_path, icon_name) + 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(1, 2) + self.mainloop() + + def binary_choice_message(self, message, icon_path = None, icon_name = None, one = 'Yes', two = 'No'): + #one: first option, returns True + #two: 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.find_icon(icon_path, icon_name) + 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 = one, variable = self.choice, value = True, + command = self._delete_window, style = 'Linden.TButton') + self.button_two = ttk.Radiobutton(text = two, 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 progress_bar(self, message = None, icon_path = None, icon_name = 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.find_icon(icon_path, icon_name) + 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(1, 3) + self.queue = pb_queue + self.check_scheduler() + + def check_scheduler(self): + if self.value < self.progress["maximum"]: + self.check_queue() + self.after(100, self.check_scheduler) + + 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: + pass + +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. The third will close by itself. + from contextlib import closing + from multiprocessing import Process + + 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") + frame2.basic_message(message = "...attracts me like no other.", icon_name="head-sl-logo.gif") + 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") + frame3.binary_choice_message(message = "And all I have to do is think of her.", icon_name="head-sl-logo.gif", + one = "Don't want to leave her now", two = '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") + frame4.progress_bar(message = "You're asking me will my love grow", icon_name="head-sl-logo.gif", size = 100, pb_queue = queue) + print "frame defined" + + frame4.mainloop() + + + + -- cgit v1.2.3 From dd18fb215f76b36c96da00a6516ada096b11262c Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Fri, 17 Jun 2016 08:49:26 -0700 Subject: SL-407: post review --- .../manager/InstallerUserMessage.py | 51 ++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index bef2bf28a6..cf0a9da2a1 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -46,7 +46,11 @@ class InstallerUserMessage(tk.Tk): #Non-goals: # No claim to threadsafety is made or warranted. Your mileage may vary. # Please consult a doctor if you experience thread pain. - def __init__(self, text="", title="", width=500, height=200, fillcolor='#487A7B', *args, **kwargs): + + #Linden standard green color, from Marketing + linden_green = "#487A7B" + + def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None, **kwargs): tk.Tk.__init__(self) self.grid() self.title(title) @@ -56,15 +60,22 @@ class InstallerUserMessage(tk.Tk): # http://tinyurl.com/tkmacbuttons ttk.Style().configure('Linden.TLabel', foreground='#487A7B', background='black') ttk.Style().configure('Linden.TButton', foreground='#487A7B', background='black') - ttk.Style().configure("black.Horizontal.TProgressbar", foreground='#487A7B', 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.icon_dir = os.path.abspath(os.path.join(self.script_dir, '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) @@ -79,7 +90,7 @@ class InstallerUserMessage(tk.Tk): def set_colors(self, widget): # #487A7B is "Linden Green" - widget.config(foreground = '#487A7B') + widget.config(foreground = InstallerUserMessage.linden_green) widget.config(background='black') def find_icon(self, icon_path = None, icon_name = None): @@ -110,22 +121,21 @@ class InstallerUserMessage(tk.Tk): else: self.rowconfigure(x, weight=1) - def basic_message(self, message, icon_path = None, icon_name = None): + def basic_message(self, message, icon_name = None): #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.find_icon(icon_path, icon_name) 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(1, 2) + self.auto_resize(row_count = 1, column_count = 2) self.mainloop() - def binary_choice_message(self, message, icon_path = None, icon_name = None, one = 'Yes', two = 'No'): + def binary_choice_message(self, message, true = 'Yes', false = 'No'): #one: first option, returns True #two: second option, returns False #usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice @@ -134,7 +144,7 @@ class InstallerUserMessage(tk.Tk): # frame = frame.binary_choice_message( ... ) # (wait for user to click) # value = frame.choice.get() - self.find_icon(icon_path, icon_name) + 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 @@ -154,13 +164,12 @@ class InstallerUserMessage(tk.Tk): self.update() self.mainloop() - def progress_bar(self, message = None, icon_path = None, icon_name = None, size = 0, interval = 100, pb_queue = None): + 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.find_icon(icon_path, icon_name) self.text_label = tk.Label(text = message) self.set_colors(self.text_label) self.set_colors(self.image_label) @@ -170,7 +179,7 @@ class InstallerUserMessage(tk.Tk): self.progress.grid(row = 3, column = 1, sticky = 'NSEW') self.value = 0 self.progress["maximum"] = size - self.auto_resize(1, 3) + self.auto_resize(row_count = 1, column_count = 3) self.queue = pb_queue self.check_scheduler() @@ -191,7 +200,8 @@ class InstallerUserMessage(tk.Tk): self.progress.step(msg) self.value = msg except Queue.Empty: - pass + #nothing to do + return class ThreadedClient(threading.Thread): #for test only, not part of the functional code @@ -214,9 +224,6 @@ class ThreadedClient(threading.Thread): 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. The third will close by itself. - from contextlib import closing - from multiprocessing import Process - import sys import tempfile @@ -229,15 +236,15 @@ if __name__ == "__main__": print "Over now" #basic message window test - frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100") - frame2.basic_message(message = "...attracts me like no other.", icon_name="head-sl-logo.gif") + frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100", icon_name="head-sl-logo.gif") + 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") - frame3.binary_choice_message(message = "And all I have to do is think of her.", icon_name="head-sl-logo.gif", - one = "Don't want to leave her now", two = 'You know I believe and how') + 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() @@ -247,8 +254,8 @@ if __name__ == "__main__": thread.start() print "thread started" - frame4 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 300") - frame4.progress_bar(message = "You're asking me will my love grow", icon_name="head-sl-logo.gif", size = 100, pb_queue = queue) + 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() -- cgit v1.2.3 From f38439f4761abb73f6c7c93d52697cd9fa9f8368 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Fri, 17 Jun 2016 08:53:46 -0700 Subject: SL-407: post review change testing --- indra/viewer_components/manager/InstallerUserMessage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index cf0a9da2a1..e6dba78169 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -121,7 +121,7 @@ class InstallerUserMessage(tk.Tk): else: self.rowconfigure(x, weight=1) - def basic_message(self, message, icon_name = None): + 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 @@ -148,9 +148,9 @@ class InstallerUserMessage(tk.Tk): 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 = one, variable = self.choice, value = True, + 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 = two, variable = self.choice, value = False, + 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) -- cgit v1.2.3 From 53fe427e6fd2bf4701f98fb9896d69cfef9042b6 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Fri, 17 Jun 2016 09:58:28 -0700 Subject: SL-407: remove kwargs --- indra/viewer_components/manager/InstallerUserMessage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index e6dba78169..0ab7eafc78 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -50,7 +50,7 @@ class InstallerUserMessage(tk.Tk): #Linden standard green color, from Marketing linden_green = "#487A7B" - def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None, **kwargs): + def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None): tk.Tk.__init__(self) self.grid() self.title(title) -- cgit v1.2.3 From eb9cc511de84f2dd54478ffd517eb34bcc6e3d1d Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Fri, 17 Jun 2016 11:01:36 -0700 Subject: SL-321: retrofit launcher to use InstallerUserMessage --- indra/viewer_components/manager/SL_Launcher | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index e715546821..fd7d00be0f 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -15,12 +15,13 @@ # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, # COMPLETENESS OR PERFORMANCE. # $/LicenseInfo$ +# Copyright (c) 2013, Linden Research, Inc. import argparse import os import sys import subprocess -import Tkinter as tk +import InstallerUserMessage cwd = os.path.dirname(os.path.realpath(__file__)) @@ -51,13 +52,10 @@ parser = argparse.ArgumentParser() args = parser.parse_known_args(sys.argv) args_list_to_pass = args[1][1:] args_list_to_pass.insert(0,viewer_binary) -#print "COYOT: arrrrrghs to pass", args_list_to_pass +print "COYOT: arrrrrghs to pass", args_list_to_pass #to prove we are launching from the script, launch a Tkinter window first -root = tk.Tk() -w = tk.Label(root, text=viewer_binary) -w.pack() -root.after(10000, lambda: root.destroy()) # Destroy the widget after 10 seconds -root.mainloop() +frame2 = InstallerUserMessage(title = "Second Life") +frame2.basic_message(message = viewer_binary, icon_name="head-sl-logo.gif") -viewer_process = subprocess.Popen(args_list_to_pass) +#viewer_process = subprocess.Popen(args_list_to_pass) -- cgit v1.2.3 From 5d900a4d8d01ffa2113cc8c763075bcd0968560f Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 23 Jun 2016 08:05:31 -0700 Subject: SL-321 add trinary widget to InstallerUserMessage --- .../manager/InstallerUserMessage.py | 52 ++++++++++++++++++---- indra/viewer_components/manager/SL_Launcher | 1 - 2 files changed, 43 insertions(+), 10 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index 0ab7eafc78..2ec71df030 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -58,8 +58,8 @@ class InstallerUserMessage(tk.Tk): 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='#487A7B', background='black') - ttk.Style().configure('Linden.TButton', foreground='#487A7B', background='black') + 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 @@ -136,8 +136,8 @@ class InstallerUserMessage(tk.Tk): self.mainloop() def binary_choice_message(self, message, true = 'Yes', false = 'No'): - #one: first option, returns True - #two: second option, returns False + #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( ... ) @@ -164,6 +164,38 @@ class InstallerUserMessage(tk.Tk): 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" @@ -248,6 +280,13 @@ if __name__ == "__main__": print frame3.choice.get() sys.stdout.flush() + #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() + #progress bar queue = Queue.Queue() thread = ThreadedClient(queue) @@ -257,9 +296,4 @@ if __name__ == "__main__": 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() - - - - diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index fd7d00be0f..6eaccc8b13 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -34,7 +34,6 @@ elif sys.platform.startswith("win") or sys.platform.startswith("cyg"): elif os.path.isfile(os.path.join(cwd,"SecondLifeTest.exe")): executable_name = "SecondLifeTest.exe" else: - #unsupported by prototypeS sys.exit("Can't find Windows viewer binary") elif sys.platform.startswith("linux"): executable_name = "secondlife" -- cgit v1.2.3 From e01f6168993af377b2a625176efff95b498b5d21 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Mon, 27 Jun 2016 15:30:57 -0700 Subject: SL-323: multithreaded downloader with progress bar --- indra/viewer_components/manager/download_update.py | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100755 indra/viewer_components/manager/download_update.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py new file mode 100755 index 0000000000..aa8555cd21 --- /dev/null +++ b/indra/viewer_components/manager/download_update.py @@ -0,0 +1,96 @@ +#!/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 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 + + +def download_update(url = None, download_dir = None, size = None, progressbar = False, chunk_size = 1024): + #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 + #in_queue is to communicate between download_update and the thread + #out_queue is to communicate between the thread and the progressbar + #if progressbar: + #out_queue = Queue.Queue() + #buffer_thread = ThreadedBuffer(size, in_queue, out_queue) + #buffer_thread.start() + #pb_thread = ThreadedBar(size, out_queue) + #pb_thread.start() + + queue = Queue.Queue() + 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() + +class ThreadedDownload(threading.Thread): + def __init__(self, req, filename, chunk_size, progressbar, in_queue): + 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: + for chunk in self.req.iter_content(self.chunk_size): + fd.write(chunk) + if self.progressbar: + self.in_queue.put(len(chunk)) + self.in_queue.put(-1) + +def main(): + 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', 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() -- cgit v1.2.3 From cb90597b4e6fa64db706d6341039ade3c6d09a5b Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Mon, 27 Jun 2016 16:20:10 -0700 Subject: remove extraneous comments --- indra/viewer_components/manager/download_update.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py index aa8555cd21..71da5c97c2 100755 --- a/indra/viewer_components/manager/download_update.py +++ b/indra/viewer_components/manager/download_update.py @@ -43,14 +43,6 @@ def download_update(url = None, download_dir = None, size = None, progressbar = #total size (for progressbar) of download #progressbar: whether to display one (not used for background downloads) #chunk_size is in bytes - #in_queue is to communicate between download_update and the thread - #out_queue is to communicate between the thread and the progressbar - #if progressbar: - #out_queue = Queue.Queue() - #buffer_thread = ThreadedBuffer(size, in_queue, out_queue) - #buffer_thread.start() - #pb_thread = ThreadedBar(size, out_queue) - #pb_thread.start() queue = Queue.Queue() filename = os.path.join(download_dir, url.split('/')[-1]) -- cgit v1.2.3 From 9bc49fb4bd94814482846106954e198e504d802a Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Tue, 28 Jun 2016 11:34:05 -0700 Subject: SL-323: post review comments on downloader --- indra/viewer_components/manager/download_update.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py index 71da5c97c2..cd4e6680b0 100755 --- a/indra/viewer_components/manager/download_update.py +++ b/indra/viewer_components/manager/download_update.py @@ -1,7 +1,5 @@ #!/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 @@ -14,8 +12,8 @@ # 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:firstyear=2016&license=viewerlgpl$ +# Copyright (c) 2016, Linden Research, Inc. # $/LicenseInfo$ """ @@ -36,8 +34,10 @@ 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 = 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 @@ -54,6 +54,9 @@ def download_update(url = None, download_dir = None, size = None, 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): @@ -78,7 +81,7 @@ def main(): 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', help='max portion size of download to be loaded in memory in bytes.') + 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) -- cgit v1.2.3 From 924e80142fd3ef1454dab1e9a002e5be9e66db94 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Tue, 5 Jul 2016 08:06:03 -0700 Subject: SL-323: apply update code --- indra/viewer_components/manager/apply_update.py | 232 ++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100755 indra/viewer_components/manager/apply_update.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py new file mode 100755 index 0000000000..1cf394e008 --- /dev/null +++ b/indra/viewer_components/manager/apply_update.py @@ -0,0 +1,232 @@ +#!/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 fnmatch +import InstallerUserMessage as IUM +import os +import os.path +import plistlib +import shutil +import subprocess +import sys +import tarfile +import tempfile + +LNX_REGEX = '*' + '.bz2' +MAC_REGEX = '*' + '.dmg' +MAC_APP_REGEX = '*' + '.app' +WIN_REGEX = '*' + '.exe' + +INSTALL_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + +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 + 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) + else: + 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: + command = ["df", os.path.join(tmpdir, "Second Life Installer")] + output = subprocess.check_output(command) + mnt_dev = output.split('\n')[1].split()[0] + 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): + #for lnx and mac, returns path to newly installed viewer and "True" for Windows + #returns None on failure for all three + installable = get_filename(download_dir) + if not installable: + #could not find download + raise ValueError("Could not find installable in " + download_dir) + + 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: + 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) + #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): + #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 + 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"] + 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 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 + # swap out old install directory + bundlename = os.path.basename(appdir) + #INSTALL_DIR is something like /Applications/Second Life Viewer.app/Contents/MacOS, need to jump up two levels + installed_test = os.path.dirname(INSTALL_DIR) + installed_test = os.path.dirname(installed_test) + if os.path.exists(installed_test): + silent_write(log_file_handle, "Updating %s" % installed_test) + swapped_out = os.path.join(tmpdir, INSTALL_DIR.lstrip('/')) + shutil.move(installed_test, swapped_out) + else: + silent_write(log_file_handle, "Installing %s" % installed_test) + + # copy over the new bits + try: + shutil.copytree(appdir, installed_test, 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 + with allow_errno(errno.ENOENT): + shutil.rmtree(STATE_DIR) + + os.remove(installable) + return INSTALL_DIR + +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 + return True + +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('--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) + + 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() -- cgit v1.2.3 From 7e120a61d1ce670b2bab8ab6b1a814ed4af0dc12 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Tue, 5 Jul 2016 13:43:50 -0700 Subject: remove first version of apply_update --- indra/viewer_components/manager/apply_update.py | 232 ------------------------ 1 file changed, 232 deletions(-) delete mode 100755 indra/viewer_components/manager/apply_update.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py deleted file mode 100755 index 1cf394e008..0000000000 --- a/indra/viewer_components/manager/apply_update.py +++ /dev/null @@ -1,232 +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 fnmatch -import InstallerUserMessage as IUM -import os -import os.path -import plistlib -import shutil -import subprocess -import sys -import tarfile -import tempfile - -LNX_REGEX = '*' + '.bz2' -MAC_REGEX = '*' + '.dmg' -MAC_APP_REGEX = '*' + '.app' -WIN_REGEX = '*' + '.exe' - -INSTALL_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) - -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 - 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) - else: - 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: - command = ["df", os.path.join(tmpdir, "Second Life Installer")] - output = subprocess.check_output(command) - mnt_dev = output.split('\n')[1].split()[0] - 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): - #for lnx and mac, returns path to newly installed viewer and "True" for Windows - #returns None on failure for all three - installable = get_filename(download_dir) - if not installable: - #could not find download - raise ValueError("Could not find installable in " + download_dir) - - 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: - 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) - #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): - #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 - 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"] - 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 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 - # swap out old install directory - bundlename = os.path.basename(appdir) - #INSTALL_DIR is something like /Applications/Second Life Viewer.app/Contents/MacOS, need to jump up two levels - installed_test = os.path.dirname(INSTALL_DIR) - installed_test = os.path.dirname(installed_test) - if os.path.exists(installed_test): - silent_write(log_file_handle, "Updating %s" % installed_test) - swapped_out = os.path.join(tmpdir, INSTALL_DIR.lstrip('/')) - shutil.move(installed_test, swapped_out) - else: - silent_write(log_file_handle, "Installing %s" % installed_test) - - # copy over the new bits - try: - shutil.copytree(appdir, installed_test, 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 - with allow_errno(errno.ENOENT): - shutil.rmtree(STATE_DIR) - - os.remove(installable) - return INSTALL_DIR - -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 - return True - -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('--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) - - 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() -- cgit v1.2.3 From bb19a1e9cc2d11f1db89a92eb17f98641736cd1e Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Tue, 5 Jul 2016 13:44:43 -0700 Subject: SL-323: apply update code, v2 --- indra/viewer_components/manager/apply_update.py | 254 ++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100755 indra/viewer_components/manager/apply_update.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py new file mode 100755 index 0000000000..362d57c94e --- /dev/null +++ b/indra/viewer_components/manager/apply_update.py @@ -0,0 +1,254 @@ +#!/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 shutil +import subprocess +import sys +import tarfile +import tempfile + +#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 + 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: + 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] + 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): + #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 + + installable = get_filename(download_dir) + if not installable: + #could not find download + raise ValueError("Could not find installable in " + download_dir) + + 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 + return installable + +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() -- cgit v1.2.3 From 03bcad61115b7128b3b1a5ede3cd7f2bf309fd39 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Mon, 11 Jul 2016 11:24:45 -0700 Subject: SLS-323: integrate update manager with lanucher, various fixes, CMake changes --- .../manager/InstallerUserMessage.py | 14 +- indra/viewer_components/manager/SL_Launcher | 59 ++- indra/viewer_components/manager/apply_update.py | 29 +- indra/viewer_components/manager/download_update.py | 16 +- indra/viewer_components/manager/update_manager.py | 455 +++++++++++++++++++++ 5 files changed, 549 insertions(+), 24 deletions(-) create mode 100755 indra/viewer_components/manager/update_manager.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index 2ec71df030..f66af81d06 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -280,13 +280,6 @@ if __name__ == "__main__": print frame3.choice.get() sys.stdout.flush() - #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() - #progress bar queue = Queue.Queue() thread = ThreadedClient(queue) @@ -297,3 +290,10 @@ if __name__ == "__main__": 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() diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 6eaccc8b13..ecf88a1105 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -18,10 +18,19 @@ # Copyright (c) 2013, Linden Research, Inc. import argparse +import InstallerUserMessage import os import sys import subprocess -import InstallerUserMessage +import update_manager + +def after_frame(my_message, timeout = 10000): + #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds + #note that this blocks the caller for the duration of timeout + frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif") + #this is done before basic_message so that we aren't blocked by mainloop() + frame.after(timout, lambda: frame._delete_window) + frame.basic_message(message = my_message) cwd = os.path.dirname(os.path.realpath(__file__)) @@ -40,21 +49,47 @@ elif sys.platform.startswith("linux"): else: #SL doesn't run on VMS or punch cards sys.exit("Unsupported platform") + +#check for an update +#TODO -#print "COYOT: executable name ", executable_name -#print "COYOT: path ", os.path.dirname(os.path.abspath(sys.argv[0])) - +#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() -#parser.add_argument('--f', action='store_const', const=42) args = parser.parse_known_args(sys.argv) args_list_to_pass = args[1][1:] -args_list_to_pass.insert(0,viewer_binary) -print "COYOT: arrrrrghs to pass", args_list_to_pass - -#to prove we are launching from the script, launch a Tkinter window first -frame2 = InstallerUserMessage(title = "Second Life") -frame2.basic_message(message = viewer_binary, icon_name="head-sl-logo.gif") +#make a copy by value, not by reference +command = list(args_list_to_pass) -#viewer_process = subprocess.Popen(args_list_to_pass) +(success, state, condition) = update_manager.update_manager() +# From update_manager: +# (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing) +# (False, 'download', version): we failed to download the new version +# (False, 'apply', version): we failed to apply the new version +# (True, None, None): No update found +# (True, 'in place', True): update applied in place +# (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location +# (True, 'background', True): background download initiated +#These boil down three cases: +# Success is False, then pop up a message and launch the current viewer +# No update, update succeeded in place in foreground, or background update started: silently launch the current viewer channel +# Updated succeed to a different channel, launch that viewer and exit +if not success: + msg = 'Update failed in the %s process. Please check logs. Viewer will launch starting momentarily.' + after_frame(msg) + command.insert(0,viewer_binary) + viewer_process = subprocess.Popen(command) + #at the moment, we just exit here. Later, the crash monitor will be launched at this point +elif (success == True and + (state == None + or (state == 'background' and condition == True) + or (state == 'in_place' and condition == True))): + command.insert(0,viewer_binary) + viewer_process = subprocess.Popen(command) + #at the moment, we just exit here. Later, the crash monitor will be launched at this point +else: + #'condition' is the path to the new launcher. + command.insert(0,condition) + viewer_process = subprocess.Popen(command) + sys.exit(0) diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py index 362d57c94e..643e4ad2bc 100755 --- a/indra/viewer_components/manager/apply_update.py +++ b/indra/viewer_components/manager/apply_update.py @@ -33,12 +33,15 @@ 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' @@ -65,6 +68,9 @@ def silent_write(log_file_handle, text): 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) @@ -77,10 +83,14 @@ 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") @@ -88,17 +98,20 @@ def try_dismount(log_file_handle = None, installable = None, tmpdir = None): 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): +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 download + #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': @@ -225,7 +238,17 @@ def apply_windows_update(installable = None, log_file_handle = None): silent_write(log_file_handle, "%s failed with return code %s. Error messages: %s." % (cpe.cmd, cpe.returncode, cpe.message)) return None - return installable + #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") diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py index cd4e6680b0..23f784c6c1 100755 --- a/indra/viewer_components/manager/download_update.py +++ b/indra/viewer_components/manager/download_update.py @@ -42,9 +42,10 @@ def download_update(url = None, download_dir = None, size = None, progressbar = #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 + #chunk_size is in bytes, amount to download at once queue = Queue.Queue() + #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) @@ -60,6 +61,11 @@ def download_update(url = None, download_dir = None, size = None, progressbar = 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 @@ -69,13 +75,19 @@ class ThreadedDownload(threading.Thread): 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: - self.in_queue.put(len(chunk)) + #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) diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py new file mode 100755 index 0000000000..ec6df17a6c --- /dev/null +++ b/indra/viewer_components/manager/update_manager.py @@ -0,0 +1,455 @@ +#!/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. + +""" +@file update_manager.py +@author coyot +@date 2016-05-16 +""" + +from llbase import llrest +from llbase import llsd +from urlparse import urljoin + +import apply_update +import download_update +import errno +import fnmatch +import hashlib +import InstallerUserMessage +import json +import os +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 + log_file_handle.write("UPDATE MANAGER: " + text + "\n") + +def after_frame(my_message, timeout = 10000): + #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds + #note that this blocks the caller for the duration of timeout + frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif") + #this is done before basic_message so that we aren't blocked by mainloop() + frame.after(timout, lambda: frame._delete_window) + frame.basic_message(message = my_message) + +def 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')) + 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): + #return a write handle on the log file + #plus log rotation and not dying on failure + log_file = os.path.join(parent_dir, 'update_manager.log') + 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, platform_key, settings, summary_dict): + result_data = 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 + base_URI = 'https://update.secondlife.com/update/' + channelname = summary_dict['Channel'] + #this is kind of a mess because the settings value a) in a map and b) is both the cohort and the version + version = summary_dict['Version'] + platform_version = platform.release() + #this will always return something usable, error handling in method + hashed_UUID = 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 + """ + test + + Comment + Tell update manager you aren't willing to test. + Type + String + Value + testno + + + """ + try: + test_ok = settings['test']['Value'] + except KeyError as ke: + #normal case, no testing key + test_ok = 'testok' + UUID = make_VVM_UUID_hash(platform_key) + #because urljoin can't be arsed to take multiple elements + query_string = '/v1.0/' + channelname + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID + VVMService = llrest.SimpleRESTService(name='VVM', baseurl=base_URI) + try: + result_data = VVMService.get(query_string) + except RESTError as re: + silent_write.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): + download_tries = 0 + download_success = False + #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.") + else: + after_frame(message = "Trying again to download new version " + version + " Please wait.") + if not background: + try: + download_update.download_update(url = url, download_dir = download_dir, size = size, progressbar = True) + download_success = True + except: + download_tries += 1 + silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.") + 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" % (url, download_dir, 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) + 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): + #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): + 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(): + #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) + + #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) + + 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 + """ + UpdaterServiceSetting + + Comment + Configure updater service. + Type + U32 + Value + 0 + + """ + 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 + + #get channel and version + try: + summary_dict = get_summary(platform_key, os.path.abspath(os.path.realpath(__file__))) + except: + silent_write(log_file_handle, "Could not obtain channel and version, exiting.") + 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. + result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict) + #nothing to do or error + if not result_data: + silent_write.write(og_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']) + + #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) + 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) + 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) + 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() -- cgit v1.2.3 From 9c2633cba85f8dae95ff2b748a027dcfb7729848 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 13 Jul 2016 07:36:12 -0700 Subject: SL-323: adding in unit tests --- indra/viewer_components/manager/SL_Launcher | 90 +- .../manager/tests/data/settings.xml | 1184 ++++++++++++++++++++ indra/viewer_components/manager/tests/summary.json | 1 + .../manager/tests/test_InstallerError.py | 39 + .../tests/test_check_for_completed_download.py | 53 + .../tests/test_convert_version_file_style.py | 55 + .../manager/tests/test_get_filename.py | 60 + .../manager/tests/test_get_log_file_handle.py | 63 ++ .../manager/tests/test_get_parent_path.py | 79 ++ .../manager/tests/test_get_platform_key.py | 40 + .../manager/tests/test_get_settings.py | 82 ++ .../manager/tests/test_make_VVM_UUID_hash.py | 42 + .../manager/tests/test_make_download_dir.py | 42 + .../manager/tests/test_query_vvm.py | 67 ++ .../manager/tests/test_silent_write.py | 43 + .../manager/tests/test_summary.py | 39 + .../manager/tests/with_setup_args.py | 66 ++ indra/viewer_components/manager/update_manager.py | 71 +- 18 files changed, 2093 insertions(+), 23 deletions(-) create mode 100644 indra/viewer_components/manager/tests/data/settings.xml create mode 100644 indra/viewer_components/manager/tests/summary.json create mode 100644 indra/viewer_components/manager/tests/test_InstallerError.py create mode 100644 indra/viewer_components/manager/tests/test_check_for_completed_download.py create mode 100644 indra/viewer_components/manager/tests/test_convert_version_file_style.py create mode 100644 indra/viewer_components/manager/tests/test_get_filename.py create mode 100644 indra/viewer_components/manager/tests/test_get_log_file_handle.py create mode 100644 indra/viewer_components/manager/tests/test_get_parent_path.py create mode 100644 indra/viewer_components/manager/tests/test_get_platform_key.py create mode 100644 indra/viewer_components/manager/tests/test_get_settings.py create mode 100644 indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py create mode 100644 indra/viewer_components/manager/tests/test_make_download_dir.py create mode 100644 indra/viewer_components/manager/tests/test_query_vvm.py create mode 100644 indra/viewer_components/manager/tests/test_silent_write.py create mode 100644 indra/viewer_components/manager/tests/test_summary.py create mode 100644 indra/viewer_components/manager/tests/with_setup_args.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index ecf88a1105..1d4c19fa86 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -18,8 +18,11 @@ # Copyright (c) 2013, Linden Research, Inc. import argparse +import collections import InstallerUserMessage +import llsd import os +import platform import sys import subprocess import update_manager @@ -31,7 +34,88 @@ def after_frame(my_message, timeout = 10000): #this is done before basic_message so that we aren't blocked by mainloop() frame.after(timout, lambda: frame._delete_window) frame.basic_message(message = my_message) + +def get_cmd_line(): + platform_name = platform.system() + #find the parent of the logs and user_settings directories + if (platform_name == 'mac'): + settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'Resources/app_settings/cmd_line.xml') + elif (platform_name == 'lnx'): + settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml') + #using list format of join is important here because the Windows pathsep in a string escapes the next char + elif (platform_name == 'win'): + settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml') + else: + settings_dir = None + + try: + cmd_line = llsd.parse((open(settings_file)).read()) + except: + cmd_line = None + + return cmd_line + +def get_settings(): + #return the settings file parsed into a dict + try: + settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml')) + settings = llsd.parse((open(settings_file)).read()) + except llsd.LLSDParseError as lpe: + silent_write(log_file_handle, "Could not parse settings file %s" % lpe) + return None + return settings + +def capture_vmp_args(arg_list = None, cmd_line = None): + #expected input format: arg_list = ['--set', 'foo', 'bar', '-X', '-Y', 'qux'] + #take a copy of the viewer parameters that are of interest to VMP. + #the regex for a parameter is -- {opt1} {opt2} + cli_overrides = {} + cmd_line = get_cmd_line() + + vmp_params = {'--channel':'channel', '--settings':'settings', '--update-service':'update-service', '--set':'set'} + #the settings set with --set. All such settings have only one argument. + vmp_setters = ('UpdaterMaximumBandwidth', 'UpdaterServiceCheckPeriod', 'UpdaterServicePath', 'UpdaterServiceSetting', 'UpdaterServiceURL', 'UpdaterWillingToTest') + + #Here turn the list into a queue, popping off the left as we go. Note that deque() makes a copy by value, not by reference + #Because of the complexity introduced by the uncertainty of how many options a parameter can take, this is far less complicated code than the more + #pythonic (x,y) = since we will sometimes have (x), sometimes (x,y) and sometimes (x,y,z) + #also, because the pop is destructive, we prevent ourselves from iterating back over list elements that iterator methods would peek ahead at + vmp_queue = collections.deque(arg_list) + while (len(vmp_queue)): + param = vmp_queue.popleft() + #if it is not one of ours, pop through args until we get to the next parameter + if param in vmp_params.keys(): + if param == '--set': + setting_name = vmp_queue.popleft() + setting_value = vmp_queue.popleft() + if setting_name in vmp_setters: + cli_overrides[vmp_params[param]] = (setting_name, setting_value) + else: + #find out how many args this parameter has + count = cmd_line[param]['count'] + param_args = [] + if count: + for argh in range(1,count): + param_args.append(vmp_queue.popleft()) + #the parameter name is the key, the (possibly empty) list of args is the value + cli_overrides[vmp_params[param]] = param_args + + #to prevent KeyErrors on missing keys, set the remainder to None + for key in vmp_params: + if key != '--set': + try: + cli_overrides[key] + except KeyError: + cli_overrides[key] = None + else: + for arg in vmp_setters: + try: + cli_overrides[key][arg] + except KeyError: + cli_overrides[key][arg] = None + return cli_overrides + cwd = os.path.dirname(os.path.realpath(__file__)) executable_name = "" @@ -58,11 +142,15 @@ viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),execu parser = argparse.ArgumentParser() args = parser.parse_known_args(sys.argv) +print args[1] +sys.exit() +#args[1] looks like ['./SL_Launcher', '--set', 'foo', 'bar', '-X', '-Y', 'qux'], dump the progname args_list_to_pass = args[1][1:] +vmp_args = capture_vmp_args(args_list_to_pass) #make a copy by value, not by reference command = list(args_list_to_pass) -(success, state, condition) = update_manager.update_manager() +(success, state, condition) = update_manager.update_manager(cli_overrides) # From update_manager: # (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing) # (False, 'download', version): we failed to download the new version diff --git a/indra/viewer_components/manager/tests/data/settings.xml b/indra/viewer_components/manager/tests/data/settings.xml new file mode 100644 index 0000000000..07e420dcb3 --- /dev/null +++ b/indra/viewer_components/manager/tests/data/settings.xml @@ -0,0 +1,1184 @@ + + + AllowMultipleViewers + + Comment + Allow multiple viewers. + Type + Boolean + Value + 1 + + AllowTapTapHoldRun + + Comment + Tapping a direction key twice and holding it down makes avatar run + Type + Boolean + Value + 0 + + AppearanceCameraMovement + + Comment + When entering appearance editing mode, camera zooms in on currently selected portion of avatar + Type + Boolean + Value + 0 + + AudioLevelMedia + + Comment + Audio level of Quicktime movies + Type + F32 + Value + 0.699999988079071044921875 + + AudioLevelMic + + Comment + Audio level of microphone input + Type + F32 + Value + 0.1749999970197677612304688 + + AudioLevelMusic + + Comment + Audio level of streaming music + Type + F32 + Value + 0 + + AudioLevelSFX + + Comment + Audio level of in-world sound effects + Type + F32 + Value + 0.699999988079071044921875 + + AudioLevelVoice + + Comment + Audio level of voice chat + Type + F32 + Value + 1 + + AudioStreamingMedia + + Comment + Enable streaming + Type + Boolean + Value + 0 + + AvatarAxisDeadZone0 + + Comment + Avatar axis 0 dead zone. + Type + F32 + Value + 0.1000000014901161193847656 + + AvatarAxisDeadZone1 + + Comment + Avatar axis 1 dead zone. + Type + F32 + Value + 0.1000000014901161193847656 + + AvatarAxisDeadZone2 + + Comment + Avatar axis 2 dead zone. + Type + F32 + Value + 0.1000000014901161193847656 + + AvatarAxisDeadZone3 + + Comment + Avatar axis 3 dead zone. + Type + F32 + Value + 1 + + AvatarAxisDeadZone4 + + Comment + Avatar axis 4 dead zone. + Type + F32 + Value + 0.01999999955296516418457031 + + AvatarAxisDeadZone5 + + Comment + Avatar axis 5 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + AvatarAxisScale3 + + Comment + Avatar axis 3 scaler. + Type + F32 + Value + 0 + + AvatarAxisScale4 + + Comment + Avatar axis 4 scaler. + Type + F32 + Value + 2 + + AvatarAxisScale5 + + Comment + Avatar axis 5 scaler. + Type + F32 + Value + 2 + + AvatarFeathering + + Comment + Avatar feathering (less is softer) + Type + F32 + Value + 6 + + AvatarFileName + + Comment + Alternative avatar file name + Type + String + Value + avatar_lad_tentacles.xml + + BuildAxisDeadZone0 + + Comment + Build axis 0 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + BuildAxisDeadZone1 + + Comment + Build axis 1 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + BuildAxisDeadZone2 + + Comment + Build axis 2 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + BuildAxisDeadZone3 + + Comment + Build axis 3 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + BuildAxisDeadZone4 + + Comment + Build axis 4 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + BuildAxisDeadZone5 + + Comment + Build axis 5 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + BuildAxisScale0 + + Comment + Build axis 0 scaler. + Type + F32 + Value + 6 + + BuildAxisScale1 + + Comment + Build axis 1 scaler. + Type + F32 + Value + 6 + + BuildAxisScale2 + + Comment + Build axis 2 scaler. + Type + F32 + Value + 6 + + BuildAxisScale3 + + Comment + Build axis 3 scaler. + Type + F32 + Value + 6 + + BuildAxisScale4 + + Comment + Build axis 4 scaler. + Type + F32 + Value + 6 + + BuildAxisScale5 + + Comment + Build axis 5 scaler. + Type + F32 + Value + 6 + + BuildFeathering + + Comment + Build feathering (less is softer) + Type + F32 + Value + 12 + + BulkChangeEveryoneCopy + + Comment + Bulk changed objects can be copied by everyone + Type + Boolean + Value + 1 + + BulkChangeNextOwnerCopy + + Comment + Bulk changed objects can be copied by next owner + Type + Boolean + Value + 1 + + BulkChangeNextOwnerModify + + Comment + Bulk changed objects can be modified by next owner + Type + Boolean + Value + 1 + + BulkChangeShareWithGroup + + Comment + Bulk changed objects are shared with the currently active group + Type + Boolean + Value + 1 + + CacheValidateCounter + + Comment + Used to distribute cache validation + Type + U32 + Value + 122 + + CameraPosOnLogout + + Comment + Camera position when last logged out (global coordinates) + Type + Vector3D + Value + + 288290.4477181434631347656 + 275988.5277819633483886719 + 49.10921102762222290039062 + + + ClickToWalk + + Comment + Click in world to walk to location + Type + Boolean + Value + 0 + + ConversationSortOrder + + Comment + Specifies sort key for conversations + Type + U32 + Value + 0 + + CurrentGrid + + Comment + Currently Selected Grid + Type + String + Value + util.agni.lindenlab.com + + Cursor3D + + Comment + Treat Joystick values as absolute positions (not deltas). + Type + Boolean + Value + 0 + + FirstLoginThisInstall + + Comment + Specifies that you have not logged in with the viewer since you performed a clean install + Type + Boolean + Value + 0 + + FirstRunThisInstall + + Comment + Specifies that you have not run the viewer since you performed a clean install + Type + Boolean + Value + 0 + + FlycamAxisDeadZone0 + + Comment + Flycam axis 0 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + FlycamAxisDeadZone1 + + Comment + Flycam axis 1 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + FlycamAxisDeadZone2 + + Comment + Flycam axis 2 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + FlycamAxisDeadZone3 + + Comment + Flycam axis 3 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + FlycamAxisDeadZone4 + + Comment + Flycam axis 4 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + FlycamAxisDeadZone5 + + Comment + Flycam axis 5 dead zone. + Type + F32 + Value + 0.009999999776482582092285156 + + FlycamAxisDeadZone6 + + Comment + Flycam axis 6 dead zone. + Type + F32 + Value + 1 + + FlycamAxisScale0 + + Comment + Flycam axis 0 scaler. + Type + F32 + Value + 42 + + FlycamAxisScale1 + + Comment + Flycam axis 1 scaler. + Type + F32 + Value + 40 + + FlycamAxisScale2 + + Comment + Flycam axis 2 scaler. + Type + F32 + Value + 40 + + FlycamAxisScale3 + + Comment + Flycam axis 3 scaler. + Type + F32 + Value + 0 + + FlycamAxisScale4 + + Comment + Flycam axis 4 scaler. + Type + F32 + Value + 2 + + FlycamAxisScale5 + + Comment + Flycam axis 5 scaler. + Type + F32 + Value + 3 + + FlycamAxisScale6 + + Comment + Flycam axis 6 scaler. + Type + F32 + Value + 0 + + FlycamFeathering + + Comment + Flycam feathering (less is softer) + Type + F32 + Value + 5 + + FocusPosOnLogout + + Comment + Camera focus point when last logged out (global coordinates) + Type + Vector3D + Value + + 288287.8830481640761718154 + 275991.5973855691263452172 + 47.96361158013021963597566 + + + ForceShowGrid + + Comment + Always show grid dropdown on login screen + Type + Boolean + Value + 1 + + HttpProxyType + + Comment + Proxy type to use for HTTP operations + Type + String + Value + None + + JoystickInitialized + + Comment + Whether or not a joystick has been detected and initiailized. + Type + String + Value + UnknownDevice + + LSLFindCaseInsensitivity + + Comment + Use case insensitivity when searching in LSL editor + Type + Boolean + Value + 1 + + LastFeatureVersion + + Comment + [DO NOT MODIFY] Feature Table Version number for tracking rendering system changes + Type + S32 + Value + 37 + + LastGPUString + + Comment + [DO NOT MODIFY] previous GPU id string for tracking hardware changes + Type + String + Value + NVIDIA Corporation NVIDIA GeForce GT 750M OpenGL Engine + + LastPrefTab + + Comment + Last selected tab in preferences window + Type + S32 + Value + 1 + + LastRunVersion + + Comment + Version number of last instance of the viewer that you ran + Type + String + Value + Second Life Project Bento 5.0.0.315657 + + LocalCacheVersion + + Comment + Version number of cache + Type + S32 + Value + 7 + + LoginLocation + + Comment + Default Login location ('last', 'home') preference + Type + String + Value + home + + MapScale + + Comment + World map zoom level (pixels per region) + Type + F32 + Value + 256 + + MaxJointsPerMeshObject + + Comment + Maximum joints per rigged mesh object + Type + U32 + Value + 51 + + MediaEnablePopups + + Comment + If true, enable targeted links and javascript in media to open new media browser windows without a prompt. + Type + Boolean + Value + 1 + + MediaShowOnOthers + + Comment + Whether or not to show media on other avatars + Type + Boolean + Value + 1 + + MigrateCacheDirectory + + Comment + Check for old version of disk cache to migrate to current location + Type + Boolean + Value + 0 + + NavBarShowParcelProperties + + Comment + Show parcel property icons in navigation bar + Type + Boolean + Value + 0 + + NextLoginLocation + + Comment + Location to log into for this session - set from command line or the login panel, cleared following a successfull login. + Type + String + Value + home + + NotificationConferenceIMOptions + + Comment + + Specifies how the UI responds to Conference IM Notifications. + Allowed values: [openconversations,toast,flash,noaction] + + Type + String + Value + none + + NotificationFriendIMOptions + + Comment + + Specifies how the UI responds to Friend IM Notifications. + Allowed values: [openconversations,toast,flash,noaction] + + Type + String + Value + openconversations + + NotificationGroupChatOptions + + Comment + + Specifies how the UI responds to Group Chat Notifications. + Allowed values: [openconversations,toast,flash,noaction] + + Type + String + Value + none + + NotificationNearbyChatOptions + + Comment + + Specifies how the UI responds to Nearby Chat Notifications. + Allowed values: [openconversations,toast,flash,noaction] + + Type + String + Value + none + + NotificationNonFriendIMOptions + + Comment + + Specifies how the UI responds to Non Friend IM Notifications. + Allowed values: [openconversations,toast,flash,noaction] + + Type + String + Value + openconversations + + NumSessions + + Comment + Number of successful logins to Second Life + Type + S32 + Value + 1674 + + PlayTypingAnim + + Comment + Your avatar plays the typing animation whenever you type in the chat bar + Type + Boolean + Value + 0 + + PoolSizeExpCache + + Comment + Coroutine Pool size for ExpCache + Type + U32 + Value + 5 + + PreferredBrowserBehavior + + Comment + Use system browser for any links (0), use builtin browser for SL links and system one for others (1) or use builtin browser only (2). + Type + U32 + Value + 0 + + PreferredMaturity + + Comment + Setting for the user's preferred maturity level (consts in indra_constants.h) + Type + U32 + Value + 42 + + PresetGraphicActive + + Comment + Name of currently selected preference + Type + String + Value + Default + + ProbeHardwareOnStartup + + Comment + Query current hardware configuration on application startup + Type + Boolean + Value + 0 + + QAMode + + Comment + Enable Testing Features. + Type + Boolean + Value + 1 + + RenderAnisotropic + + Comment + Render textures using anisotropic filtering + Type + Boolean + Value + 1 + + RenderAvatarCloth + + Comment + Controls if avatars use wavy cloth + Type + Boolean + Value + 0 + + RenderAvatarLODFactor + + Comment + Controls level of detail of avatars (multiplier for current screen area when calculated level of detail) + Type + F32 + Value + 1 + + RenderFSAASamples + + Comment + Number of samples to use for FSAA (0 = no AA). + Type + U32 + Value + 2 + + RenderFarClip + + Comment + Distance of far clip plane from camera (meters) + Type + F32 + Value + 128 + + RenderMaxPartCount + + Comment + Maximum number of particles to display on screen + Type + S32 + Value + 2048 + + RenderQualityPerformance + + Comment + Which graphics settings you've chosen + Type + U32 + Value + 4 + + RenderReflectionDetail + + Comment + Detail of reflection render pass. + Type + S32 + Value + 0 + + RenderTerrainLODFactor + + Comment + Controls level of detail of terrain (multiplier for current screen area when calculated level of detail) + Type + F32 + Value + 2 + + RenderVBOEnable + + Comment + Use GL Vertex Buffer Objects + Type + Boolean + Value + 0 + + RenderVolumeLODFactor + + Comment + Controls level of detail of primitives (multiplier for current screen area when calculated level of detail) + Type + F32 + Value + 1.125 + + ShowAdvancedGraphicsSettings + + Comment + Show advanced graphics settings + Type + Boolean + Value + 1 + + ShowBanLines + + Comment + Show in-world ban/access borders + Type + Boolean + Value + 0 + + ShowStartLocation + + Comment + Display starting location menu on login screen + Type + Boolean + Value + 1 + + SkeletonFileName + + Comment + Alternative skeleton file name + Type + String + Value + avatar_skeleton_tentacles.xml + + SkyPresetName + + Comment + Sky preset to use. May be superseded by region settings or by a day cycle (see DayCycleName). + Type + String + Value + Sunset + + SnapshotConfigURL + + Comment + URL to fetch Snapshot Sharing configuration data from. + Type + String + Value + http://photos.apps.avatarsunited.com/viewer_config + + SnapshotFormat + + Comment + Save snapshots in this format (0 = PNG, 1 = JPEG, 2 = BMP) + Type + S32 + Value + 1 + + SnapshotQuality + + Comment + Quality setting of postcard JPEGs (0 = worst, 100 = best) + Type + S32 + Value + 100 + + SpellCheck + + Comment + Enable spellchecking on line and text editors + Type + Boolean + Value + 0 + + SpellCheckDictionary + + Comment + Current primary and secondary dictionaries used for spell checking + Type + String + Value + English (United States) + + TextureMemory + + Comment + Amount of memory to use for textures in MB (0 = autodetect) + Type + S32 + Value + 256 + + UseDayCycle + + Comment + Whether to use use a day cycle or a fixed sky. + Type + Boolean + Value + 0 + + UseDebugMenus + + Comment + Turns on "Debug" menu + Type + Boolean + Value + 1 + + UseEnvironmentFromRegion + + Comment + Choose whether to use the region's environment settings, or override them with the local settings. + Type + Boolean + Value + 0 + + VFSOldSize + + Comment + [DO NOT MODIFY] Controls resizing of local file cache + Type + U32 + Value + 102 + + VFSSalt + + Comment + [DO NOT MODIFY] Controls local file caching behavior + Type + U32 + Value + 260093998 + + VersionChannelName + + Comment + Version information generated by running the viewer + Type + String + Value + Second Life Release + + VertexShaderEnable + + Comment + Enable/disable all GLSL shaders (debug) + Type + Boolean + Value + 1 + + VoiceInputAudioDevice + + Comment + Audio input device to use for voice + Type + String + Value + C-Media USB Audio Device + + VoiceOutputAudioDevice + + Comment + Audio output device to use for voice + Type + String + Value + C-Media USB Audio Device + + WLSkyDetail + + Comment + Controls vertex detail on the WindLight sky. Lower numbers will give better performance and uglier skies. + Type + U32 + Value + 48 + + WebProfileFloaterRect + + Comment + Web profile floater dimensions + Type + Rect + Value + + 1189 + 957 + 1674 + 277 + + + WindowHeight + + Comment + SL viewer window height + Type + U32 + Value + 1000 + + WindowWidth + + Comment + SL viewer window width + Type + U32 + Value + 1626 + + WindowX + + Comment + X coordinate of upper left corner of SL viewer window, relative to upper left corner of primary display (pixels) + Type + S32 + Value + 50 + + WindowY + + Comment + Y coordinate of upper left corner of SL viewer window, relative to upper left corner of primary display (pixels) + Type + S32 + Value + 50 + + + diff --git a/indra/viewer_components/manager/tests/summary.json b/indra/viewer_components/manager/tests/summary.json new file mode 100644 index 0000000000..b78859d427 --- /dev/null +++ b/indra/viewer_components/manager/tests/summary.json @@ -0,0 +1 @@ +{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"} diff --git a/indra/viewer_components/manager/tests/test_InstallerError.py b/indra/viewer_components/manager/tests/test_InstallerError.py new file mode 100644 index 0000000000..d722208b7f --- /dev/null +++ b/indra/viewer_components/manager/tests/test_InstallerError.py @@ -0,0 +1,39 @@ +#!/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$ + +""" +@file test_InstallerError.py +@author coyot +@date 2016-06-01 +""" + +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) diff --git a/indra/viewer_components/manager/tests/test_check_for_completed_download.py b/indra/viewer_components/manager/tests/test_check_for_completed_download.py new file mode 100644 index 0000000000..388bc900e9 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_check_for_completed_download.py @@ -0,0 +1,53 @@ +#!/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$ + +""" +@file test_check_for_completed_download.py +@author coyot +@date 2016-06-03 +""" + +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" \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_convert_version_file_style.py b/indra/viewer_components/manager/tests/test_convert_version_file_style.py new file mode 100644 index 0000000000..d700f91b84 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_convert_version_file_style.py @@ -0,0 +1,55 @@ +#!/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$ + +""" +@file test_convert_version_file_style.py +@author coyot +@date 2016-06-01 +""" + +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) \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_filename.py b/indra/viewer_components/manager/tests/test_get_filename.py new file mode 100644 index 0000000000..95771d75dc --- /dev/null +++ b/indra/viewer_components/manager/tests/test_get_filename.py @@ -0,0 +1,60 @@ +#!/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$ + +""" +@file test_get_filename.py +@author coyot +@date 2016-06-30 +""" + +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" \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_log_file_handle.py b/indra/viewer_components/manager/tests/test_get_log_file_handle.py new file mode 100644 index 0000000000..c5b3c89550 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_get_log_file_handle.py @@ -0,0 +1,63 @@ +#!/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$ + +""" +@file test_get_log_file_handle.py +@author coyot +@date 2016-06-08 +""" + +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 \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_parent_path.py b/indra/viewer_components/manager/tests/test_get_parent_path.py new file mode 100644 index 0000000000..3cfd72310e --- /dev/null +++ b/indra/viewer_components/manager/tests/test_get_parent_path.py @@ -0,0 +1,79 @@ +#!/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$ + +""" +@file test_get_parent_path.py +@author coyot +@date 2016-06-02 +""" + +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) + + \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_platform_key.py b/indra/viewer_components/manager/tests/test_get_platform_key.py new file mode 100644 index 0000000000..37c570532c --- /dev/null +++ b/indra/viewer_components/manager/tests/test_get_platform_key.py @@ -0,0 +1,40 @@ +#!/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$ + +""" +@file test_get_platform_key.py +@author coyot +@date 2016-06-01 +""" + +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) + \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_settings.py b/indra/viewer_components/manager/tests/test_get_settings.py new file mode 100644 index 0000000000..46b779cbd5 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_get_settings.py @@ -0,0 +1,82 @@ +#!/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$ + +""" +@file test_get_settings.py +@author coyot +@date 2016-06-03 +""" + +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') + diff --git a/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py new file mode 100644 index 0000000000..513502a6ca --- /dev/null +++ b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py @@ -0,0 +1,42 @@ +#!/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$ + +""" +@file test_make_VVM_UUID_hash.py +@author coyot +@date 2016-06-03 +""" + +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." + diff --git a/indra/viewer_components/manager/tests/test_make_download_dir.py b/indra/viewer_components/manager/tests/test_make_download_dir.py new file mode 100644 index 0000000000..5198a6de05 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_make_download_dir.py @@ -0,0 +1,42 @@ +#!/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$ + +""" +@file test_make_download_dir.py +@author coyot +@date 2016-06-03 +""" + +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) \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_query_vvm.py b/indra/viewer_components/manager/tests/test_query_vvm.py new file mode 100644 index 0000000000..40a0f7b215 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_query_vvm.py @@ -0,0 +1,67 @@ +#!/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$ + +""" +@file test_query_vvm.py +@author coyot +@date 2016-06-08 +""" + +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 diff --git a/indra/viewer_components/manager/tests/test_silent_write.py b/indra/viewer_components/manager/tests/test_silent_write.py new file mode 100644 index 0000000000..a55665799f --- /dev/null +++ b/indra/viewer_components/manager/tests/test_silent_write.py @@ -0,0 +1,43 @@ +#!/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$ + +""" +@file test_silent_write.py +@author coyot +@date 2016-06-02 +""" + +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 \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_summary.py b/indra/viewer_components/manager/tests/test_summary.py new file mode 100644 index 0000000000..b318012b54 --- /dev/null +++ b/indra/viewer_components/manager/tests/test_summary.py @@ -0,0 +1,39 @@ +#!/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$ + +""" +@file test_summary.py +@author coyot +@date 2016-06-02 +""" + +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') \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/with_setup_args.py b/indra/viewer_components/manager/tests/with_setup_args.py new file mode 100644 index 0000000000..38350ab8c4 --- /dev/null +++ b/indra/viewer_components/manager/tests/with_setup_args.py @@ -0,0 +1,66 @@ +#!/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$ +# +# Taken from: https://gist.github.com/garyvdm/392ae20c673c7ee58d76 + +""" +@file with_setup_args.py +@author garyvdm +@date 2016-06-02 +""" + + +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 \ No newline at end of file diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index ec6df17a6c..a7e0a19aef 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -204,11 +204,14 @@ def make_VVM_UUID_hash(platform_key): #fake it return hashlib.md5(str(uuid.uuid1())).hexdigest() -def query_vvm(log_file_handle, platform_key, settings, summary_dict): +def query_vvm(log_file_handle = None, platform_key = None, settings = None, summary_dict = None, UpdaterServiceURL = None, UpdaterWillingToTest = None): result_data = 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 - base_URI = 'https://update.secondlife.com/update/' + if UpdaterServiceURL: + baseURI = UpdaterServiceURL + else: + base_URI = 'https://update.secondlife.com/update/' channelname = summary_dict['Channel'] #this is kind of a mess because the settings value a) in a map and b) is both the cohort and the version version = summary_dict['Version'] @@ -229,11 +232,17 @@ def query_vvm(log_file_handle, platform_key, settings, summary_dict): """ - try: - test_ok = settings['test']['Value'] - except KeyError as ke: - #normal case, no testing key - test_ok = 'testok' + 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' UUID = make_VVM_UUID_hash(platform_key) #because urljoin can't be arsed to take multiple elements query_string = '/v1.0/' + channelname + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID @@ -245,7 +254,7 @@ def query_vvm(log_file_handle, platform_key, settings, summary_dict): return None return result_data -def download(url = None, version = None, download_dir = None, size = 0, background = False): +def download(url = None, version = None, download_dir = None, size = 0, background = False, chunk_size = 1024): download_tries = 0 download_success = False #for background execution @@ -259,7 +268,7 @@ def download(url = None, version = None, download_dir = None, size = 0, backgrou after_frame(message = "Trying again to download new version " + version + " Please wait.") if not background: try: - download_update.download_update(url = url, download_dir = download_dir, size = size, progressbar = True) + download_update.download_update(url = url, download_dir = download_dir, size = size, progressbar = True, chunk_size = chunk_size) download_success = True except: download_tries += 1 @@ -268,7 +277,7 @@ def download(url = None, version = None, download_dir = None, size = 0, backgrou 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" % (url, download_dir, size)) + 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 @@ -294,11 +303,12 @@ def install(platform_key = None, download_dir = None, log_file_handle = None, in 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): +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): + if not download(url = url, version = version, download_dir = download_dir, size = size, chunk_size = chunk_size): return (False, 'download', version) #do the install path_to_new_launcher = install(platform_key = platform_key, download_dir = download_dir, @@ -313,7 +323,8 @@ def download_and_install(downloaded = None, url = None, version = None, download #propagate failure return (False, 'apply', version) -def update_manager(): +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. @@ -356,7 +367,11 @@ def update_manager(): print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None) return (False, 'setup', None) - settings = get_settings(log_file_handle, parent_dir) + if cli_overrides['settings'] is not None: + settings = get_settings(log_file_handle, cli_overrides['settings']) + 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) @@ -375,22 +390,34 @@ def update_manager(): 0 """ - 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 + if cli_overrides['set']['UpdaterServiceSetting'] is not None: + 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['set']['UpdaterMaximumBandwidth ']: + chunk_size = cli_overrides['set']['UpdaterMaximumBandwidth '] + else: + chunk_size = 1024 #get channel and version try: summary_dict = get_summary(platform_key, os.path.abspath(os.path.realpath(__file__))) + if cli_overrides['channel']: + summary_dict['Channel'] = cli_overrides['channel'] except: silent_write(log_file_handle, "Could not obtain channel and version, exiting.") 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. - result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict) + UpdaterServiceURL = cli_overrides['update-service'] + result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict, UpdaterServiceURL) #nothing to do or error if not result_data: silent_write.write(og_file_handle, "No update found.") @@ -417,7 +444,7 @@ def update_manager(): #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) + 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. @@ -436,7 +463,7 @@ def update_manager(): 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) + 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) -- cgit v1.2.3 From 49b8a1c82d1b12138fa1177db52efa9698c923f5 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 13 Jul 2016 08:19:49 -0700 Subject: include manager directory in CMake list --- indra/viewer_components/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt index 74c9b4568d..2030afacec 100644 --- a/indra/viewer_components/CMakeLists.txt +++ b/indra/viewer_components/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(login) add_subdirectory(updater) +add_subdirectory(manager) -- cgit v1.2.3 From adc67912d097aa032b7e7e76f6e87e8e6c54525e Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 13 Jul 2016 09:37:53 -0700 Subject: include manager directory in viewer manifest, not CMake --- indra/viewer_components/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt index 2030afacec..74c9b4568d 100644 --- a/indra/viewer_components/CMakeLists.txt +++ b/indra/viewer_components/CMakeLists.txt @@ -2,4 +2,3 @@ add_subdirectory(login) add_subdirectory(updater) -add_subdirectory(manager) -- cgit v1.2.3 From 58d8b3a11edc9ad0edeaf11737e693f3dc5ad318 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 13 Jul 2016 17:31:14 -0700 Subject: SL-323: add llsd python module --- indra/viewer_components/manager/SL_Launcher | 3 + indra/viewer_components/manager/llsd.py | 1052 +++++++++++++++++++++++++++ 2 files changed, 1055 insertions(+) create mode 100755 indra/viewer_components/manager/llsd.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 1d4c19fa86..a96f2392a7 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -20,6 +20,9 @@ import argparse import collections import InstallerUserMessage +#NOTA BENE: +# For POSIX platforms, llsd.py will be imported from the same directory. +# For Windows, llsd.py will be compiled into the executable by pyinstaller import llsd import os import platform diff --git a/indra/viewer_components/manager/llsd.py b/indra/viewer_components/manager/llsd.py new file mode 100755 index 0000000000..4527b115f9 --- /dev/null +++ b/indra/viewer_components/manager/llsd.py @@ -0,0 +1,1052 @@ +"""\ +@file llsd.py +@brief Types as well as parsing and formatting functions for handling LLSD. + +$LicenseInfo:firstyear=2006&license=mit$ + +Copyright (c) 2006-2009, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +""" + +import datetime +import base64 +import string +import struct +import time +import types +import re + +from indra.util.fastest_elementtree import ElementTreeError, fromstring +from indra.base import lluuid + +# cllsd.c in server/server-1.25 has memory leaks, +# so disabling cllsd for now +#try: +# import cllsd +#except ImportError: +# cllsd = None +cllsd = None + +int_regex = re.compile(r"[-+]?\d+") +real_regex = re.compile(r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") +alpha_regex = re.compile(r"[a-zA-Z]+") +date_regex = re.compile(r"(?P\d{4})-(?P\d{2})-(?P\d{2})T" + r"(?P\d{2}):(?P\d{2}):(?P\d{2})" + r"(?P(\.\d+)?)Z") +#date: d"YYYY-MM-DDTHH:MM:SS.FFFFFFZ" + +class LLSDParseError(Exception): + pass + +class LLSDSerializationError(TypeError): + pass + + +class binary(str): + pass + +class uri(str): + pass + + +BOOL_TRUE = ('1', '1.0', 'true') +BOOL_FALSE = ('0', '0.0', 'false', '') + + +def format_datestr(v): + """ Formats a datetime or date object into the string format shared by xml and notation serializations.""" + if hasattr(v, 'microsecond'): + return v.isoformat() + 'Z' + else: + return v.strftime('%Y-%m-%dT%H:%M:%SZ') + +def parse_datestr(datestr): + """Parses a datetime object from the string format shared by xml and notation serializations.""" + if datestr == "": + return datetime.datetime(1970, 1, 1) + + match = re.match(date_regex, datestr) + if not match: + raise LLSDParseError("invalid date string '%s'." % datestr) + + year = int(match.group('year')) + month = int(match.group('month')) + day = int(match.group('day')) + hour = int(match.group('hour')) + minute = int(match.group('minute')) + second = int(match.group('second')) + seconds_float = match.group('second_float') + microsecond = 0 + if seconds_float: + microsecond = int(float('0' + seconds_float) * 1e6) + return datetime.datetime(year, month, day, hour, minute, second, microsecond) + + +def bool_to_python(node): + val = node.text or '' + if val in BOOL_TRUE: + return True + else: + return False + +def int_to_python(node): + val = node.text or '' + if not val.strip(): + return 0 + return int(val) + +def real_to_python(node): + val = node.text or '' + if not val.strip(): + return 0.0 + return float(val) + +def uuid_to_python(node): + return lluuid.UUID(node.text) + +def str_to_python(node): + return node.text or '' + +def bin_to_python(node): + return binary(base64.decodestring(node.text or '')) + +def date_to_python(node): + val = node.text or '' + if not val: + val = "1970-01-01T00:00:00Z" + return parse_datestr(val) + + +def uri_to_python(node): + val = node.text or '' + if not val: + return None + return uri(val) + +def map_to_python(node): + result = {} + for index in range(len(node))[::2]: + result[node[index].text] = to_python(node[index+1]) + return result + +def array_to_python(node): + return [to_python(child) for child in node] + + +NODE_HANDLERS = dict( + undef=lambda x: None, + boolean=bool_to_python, + integer=int_to_python, + real=real_to_python, + uuid=uuid_to_python, + string=str_to_python, + binary=bin_to_python, + date=date_to_python, + uri=uri_to_python, + map=map_to_python, + array=array_to_python, + ) + +def to_python(node): + return NODE_HANDLERS[node.tag](node) + +class Nothing(object): + pass + + +class LLSDXMLFormatter(object): + def __init__(self): + self.type_map = { + type(None) : self.UNDEF, + bool : self.BOOLEAN, + int : self.INTEGER, + long : self.INTEGER, + float : self.REAL, + lluuid.UUID : self.UUID, + binary : self.BINARY, + str : self.STRING, + unicode : self.STRING, + uri : self.URI, + datetime.datetime : self.DATE, + datetime.date : self.DATE, + list : self.ARRAY, + tuple : self.ARRAY, + types.GeneratorType : self.ARRAY, + dict : self.MAP, + LLSD : self.LLSD + } + + def elt(self, name, contents=None): + if(contents is None or contents is ''): + return "<%s />" % (name,) + else: + if type(contents) is unicode: + contents = contents.encode('utf-8') + return "<%s>%s" % (name, contents, name) + + def xml_esc(self, v): + if type(v) is unicode: + v = v.encode('utf-8') + return v.replace('&', '&').replace('<', '<').replace('>', '>') + + def LLSD(self, v): + return self.generate(v.thing) + def UNDEF(self, v): + return self.elt('undef') + def BOOLEAN(self, v): + if v: + return self.elt('boolean', 'true') + else: + return self.elt('boolean', 'false') + def INTEGER(self, v): + return self.elt('integer', v) + def REAL(self, v): + return self.elt('real', v) + def UUID(self, v): + if(v.isNull()): + return self.elt('uuid') + else: + return self.elt('uuid', v) + def BINARY(self, v): + return self.elt('binary', base64.encodestring(v)) + def STRING(self, v): + return self.elt('string', self.xml_esc(v)) + def URI(self, v): + return self.elt('uri', self.xml_esc(str(v))) + def DATE(self, v): + return self.elt('date', format_datestr(v)) + def ARRAY(self, v): + return self.elt('array', ''.join([self.generate(item) for item in v])) + def MAP(self, v): + return self.elt( + 'map', + ''.join(["%s%s" % (self.elt('key', self.xml_esc(str(key))), self.generate(value)) + for key, value in v.items()])) + + typeof = type + def generate(self, something): + t = self.typeof(something) + if self.type_map.has_key(t): + return self.type_map[t](something) + else: + raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( + t, something)) + + def _format(self, something): + return '' + self.elt("llsd", self.generate(something)) + + def format(self, something): + if cllsd: + return cllsd.llsd_to_xml(something) + return self._format(something) + +_g_xml_formatter = None +def format_xml(something): + global _g_xml_formatter + if _g_xml_formatter is None: + _g_xml_formatter = LLSDXMLFormatter() + return _g_xml_formatter.format(something) + +class LLSDXMLPrettyFormatter(LLSDXMLFormatter): + def __init__(self, indent_atom = None): + # Call the super class constructor so that we have the type map + super(LLSDXMLPrettyFormatter, self).__init__() + + # Override the type map to use our specialized formatters to + # emit the pretty output. + self.type_map[list] = self.PRETTY_ARRAY + self.type_map[tuple] = self.PRETTY_ARRAY + self.type_map[types.GeneratorType] = self.PRETTY_ARRAY, + self.type_map[dict] = self.PRETTY_MAP + + # Private data used for indentation. + self._indent_level = 1 + if indent_atom is None: + self._indent_atom = ' ' + else: + self._indent_atom = indent_atom + + def _indent(self): + "Return an indentation based on the atom and indentation level." + return self._indent_atom * self._indent_level + + def PRETTY_ARRAY(self, v): + rv = [] + rv.append('\n') + self._indent_level = self._indent_level + 1 + rv.extend(["%s%s\n" % + (self._indent(), + self.generate(item)) + for item in v]) + self._indent_level = self._indent_level - 1 + rv.append(self._indent()) + rv.append('') + return ''.join(rv) + + def PRETTY_MAP(self, v): + rv = [] + rv.append('\n') + self._indent_level = self._indent_level + 1 + keys = v.keys() + keys.sort() + rv.extend(["%s%s\n%s%s\n" % + (self._indent(), + self.elt('key', key), + self._indent(), + self.generate(v[key])) + for key in keys]) + self._indent_level = self._indent_level - 1 + rv.append(self._indent()) + rv.append('') + return ''.join(rv) + + def format(self, something): + data = [] + data.append('\n') + data.append(self.generate(something)) + data.append('\n') + return '\n'.join(data) + +def format_pretty_xml(something): + """@brief Serialize a python object as 'pretty' llsd xml. + + The output conforms to the LLSD DTD, unlike the output from the + standard python xml.dom DOM::toprettyxml() method which does not + preserve significant whitespace. + This function is not necessarily suited for serializing very large + objects. It is not optimized by the cllsd module, and sorts on + dict (llsd map) keys alphabetically to ease human reading. + """ + return LLSDXMLPrettyFormatter().format(something) + +class LLSDNotationFormatter(object): + def __init__(self): + self.type_map = { + type(None) : self.UNDEF, + bool : self.BOOLEAN, + int : self.INTEGER, + long : self.INTEGER, + float : self.REAL, + lluuid.UUID : self.UUID, + binary : self.BINARY, + str : self.STRING, + unicode : self.STRING, + uri : self.URI, + datetime.datetime : self.DATE, + datetime.date : self.DATE, + list : self.ARRAY, + tuple : self.ARRAY, + types.GeneratorType : self.ARRAY, + dict : self.MAP, + LLSD : self.LLSD + } + + def LLSD(self, v): + return self.generate(v.thing) + def UNDEF(self, v): + return '!' + def BOOLEAN(self, v): + if v: + return 'true' + else: + return 'false' + def INTEGER(self, v): + return "i%s" % v + def REAL(self, v): + return "r%s" % v + def UUID(self, v): + return "u%s" % v + def BINARY(self, v): + return 'b64"' + base64.encodestring(v) + '"' + def STRING(self, v): + if isinstance(v, unicode): + v = v.encode('utf-8') + return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") + def URI(self, v): + return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') + def DATE(self, v): + return 'd"%s"' % format_datestr(v) + def ARRAY(self, v): + return "[%s]" % ','.join([self.generate(item) for item in v]) + def MAP(self, v): + def fix(key): + if isinstance(key, unicode): + return key.encode('utf-8') + return key + return "{%s}" % ','.join(["'%s':%s" % (fix(key).replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) + for key, value in v.items()]) + + def generate(self, something): + t = type(something) + handler = self.type_map.get(t) + if handler: + return handler(something) + else: + try: + return self.ARRAY(iter(something)) + except TypeError: + raise LLSDSerializationError( + "Cannot serialize unknown type: %s (%s)" % (t, something)) + + def format(self, something): + return self.generate(something) + +def format_notation(something): + return LLSDNotationFormatter().format(something) + +def _hex_as_nybble(hex): + if (hex >= '0') and (hex <= '9'): + return ord(hex) - ord('0') + elif (hex >= 'a') and (hex <='f'): + return 10 + ord(hex) - ord('a') + elif (hex >= 'A') and (hex <='F'): + return 10 + ord(hex) - ord('A'); + +class LLSDBinaryParser(object): + def __init__(self): + pass + + def parse(self, buffer, ignore_binary = False): + """ + This is the basic public interface for parsing. + + @param buffer the binary data to parse in an indexable sequence. + @param ignore_binary parser throws away data in llsd binary nodes. + @return returns a python object. + """ + self._buffer = buffer + self._index = 0 + self._keep_binary = not ignore_binary + return self._parse() + + def _parse(self): + cc = self._buffer[self._index] + self._index += 1 + if cc == '{': + return self._parse_map() + elif cc == '[': + return self._parse_array() + elif cc == '!': + return None + elif cc == '0': + return False + elif cc == '1': + return True + elif cc == 'i': + # 'i' = integer + idx = self._index + self._index += 4 + return struct.unpack("!i", self._buffer[idx:idx+4])[0] + elif cc == ('r'): + # 'r' = real number + idx = self._index + self._index += 8 + return struct.unpack("!d", self._buffer[idx:idx+8])[0] + elif cc == 'u': + # 'u' = uuid + idx = self._index + self._index += 16 + return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) + elif cc == 's': + # 's' = string + return self._parse_string() + elif cc in ("'", '"'): + # delimited/escaped string + return self._parse_string_delim(cc) + elif cc == 'l': + # 'l' = uri + return uri(self._parse_string()) + elif cc == ('d'): + # 'd' = date in seconds since epoch + idx = self._index + self._index += 8 + seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] + return datetime.datetime.fromtimestamp(seconds) + elif cc == 'b': + binary = self._parse_string() + if self._keep_binary: + return binary + # *NOTE: maybe have a binary placeholder which has the + # length. + return None + else: + raise LLSDParseError("invalid binary token at byte %d: %d" % ( + self._index - 1, ord(cc))) + + def _parse_map(self): + rv = {} + size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] + self._index += 4 + count = 0 + cc = self._buffer[self._index] + self._index += 1 + key = '' + while (cc != '}') and (count < size): + if cc == 'k': + key = self._parse_string() + elif cc in ("'", '"'): + key = self._parse_string_delim(cc) + else: + raise LLSDParseError("invalid map key at byte %d." % ( + self._index - 1,)) + value = self._parse() + rv[key] = value + count += 1 + cc = self._buffer[self._index] + self._index += 1 + if cc != '}': + raise LLSDParseError("invalid map close token at byte %d." % ( + self._index,)) + return rv + + def _parse_array(self): + rv = [] + size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] + self._index += 4 + count = 0 + cc = self._buffer[self._index] + while (cc != ']') and (count < size): + rv.append(self._parse()) + count += 1 + cc = self._buffer[self._index] + if cc != ']': + raise LLSDParseError("invalid array close token at byte %d." % ( + self._index,)) + self._index += 1 + return rv + + def _parse_string(self): + size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] + self._index += 4 + rv = self._buffer[self._index:self._index+size] + self._index += size + return rv + + def _parse_string_delim(self, delim): + list = [] + found_escape = False + found_hex = False + found_digit = False + byte = 0 + while True: + cc = self._buffer[self._index] + self._index += 1 + if found_escape: + if found_hex: + if found_digit: + found_escape = False + found_hex = False + found_digit = False + byte <<= 4 + byte |= _hex_as_nybble(cc) + list.append(chr(byte)) + byte = 0 + else: + found_digit = True + byte = _hex_as_nybble(cc) + elif cc == 'x': + found_hex = True + else: + if cc == 'a': + list.append('\a') + elif cc == 'b': + list.append('\b') + elif cc == 'f': + list.append('\f') + elif cc == 'n': + list.append('\n') + elif cc == 'r': + list.append('\r') + elif cc == 't': + list.append('\t') + elif cc == 'v': + list.append('\v') + else: + list.append(cc) + found_escape = False + elif cc == '\\': + found_escape = True + elif cc == delim: + break + else: + list.append(cc) + return ''.join(list) + +class LLSDNotationParser(object): + """ Parse LLSD notation: + map: { string:object, string:object } + array: [ object, object, object ] + undef: ! + boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE + integer: i#### + real: r#### + uuid: u#### + string: "g\'day" | 'have a "nice" day' | s(size)"raw data" + uri: l"escaped" + date: d"YYYY-MM-DDTHH:MM:SS.FFZ" + binary: b##"ff3120ab1" | b(size)"raw data" + """ + def __init__(self): + pass + + def parse(self, buffer, ignore_binary = False): + """ + This is the basic public interface for parsing. + + @param buffer the notation string to parse. + @param ignore_binary parser throws away data in llsd binary nodes. + @return returns a python object. + """ + if buffer == "": + return False + + self._buffer = buffer + self._index = 0 + return self._parse() + + def _parse(self): + cc = self._buffer[self._index] + self._index += 1 + if cc == '{': + return self._parse_map() + elif cc == '[': + return self._parse_array() + elif cc == '!': + return None + elif cc == '0': + return False + elif cc == '1': + return True + elif cc in ('F', 'f'): + self._skip_alpha() + return False + elif cc in ('T', 't'): + self._skip_alpha() + return True + elif cc == 'i': + # 'i' = integer + return self._parse_integer() + elif cc == ('r'): + # 'r' = real number + return self._parse_real() + elif cc == 'u': + # 'u' = uuid + return self._parse_uuid() + elif cc in ("'", '"', 's'): + return self._parse_string(cc) + elif cc == 'l': + # 'l' = uri + delim = self._buffer[self._index] + self._index += 1 + val = uri(self._parse_string(delim)) + if len(val) == 0: + return None + return val + elif cc == ('d'): + # 'd' = date in seconds since epoch + return self._parse_date() + elif cc == 'b': + return self._parse_binary() + else: + raise LLSDParseError("invalid token at index %d: %d" % ( + self._index - 1, ord(cc))) + + def _parse_binary(self): + i = self._index + if self._buffer[i:i+2] == '64': + q = self._buffer[i+2] + e = self._buffer.find(q, i+3) + try: + return base64.decodestring(self._buffer[i+3:e]) + finally: + self._index = e + 1 + else: + raise LLSDParseError('random horrible binary format not supported') + + def _parse_map(self): + """ map: { string:object, string:object } """ + rv = {} + cc = self._buffer[self._index] + self._index += 1 + key = '' + found_key = False + while (cc != '}'): + if not found_key: + if cc in ("'", '"', 's'): + key = self._parse_string(cc) + found_key = True + elif cc.isspace() or cc == ',': + cc = self._buffer[self._index] + self._index += 1 + else: + raise LLSDParseError("invalid map key at byte %d." % ( + self._index - 1,)) + elif cc.isspace() or cc == ':': + cc = self._buffer[self._index] + self._index += 1 + continue + else: + self._index += 1 + value = self._parse() + rv[key] = value + found_key = False + cc = self._buffer[self._index] + self._index += 1 + + return rv + + def _parse_array(self): + """ array: [ object, object, object ] """ + rv = [] + cc = self._buffer[self._index] + while (cc != ']'): + if cc.isspace() or cc == ',': + self._index += 1 + cc = self._buffer[self._index] + continue + rv.append(self._parse()) + cc = self._buffer[self._index] + + if cc != ']': + raise LLSDParseError("invalid array close token at index %d." % ( + self._index,)) + self._index += 1 + return rv + + def _parse_uuid(self): + match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) + if not match: + raise LLSDParseError("invalid uuid token at index %d." % self._index) + + (start, end) = match.span() + start += self._index + end += self._index + self._index = end + return lluuid.UUID(self._buffer[start:end]) + + def _skip_alpha(self): + match = re.match(alpha_regex, self._buffer[self._index:]) + if match: + self._index += match.end() + + def _parse_date(self): + delim = self._buffer[self._index] + self._index += 1 + datestr = self._parse_string(delim) + return parse_datestr(datestr) + + def _parse_real(self): + match = re.match(real_regex, self._buffer[self._index:]) + if not match: + raise LLSDParseError("invalid real token at index %d." % self._index) + + (start, end) = match.span() + start += self._index + end += self._index + self._index = end + return float( self._buffer[start:end] ) + + def _parse_integer(self): + match = re.match(int_regex, self._buffer[self._index:]) + if not match: + raise LLSDParseError("invalid integer token at index %d." % self._index) + + (start, end) = match.span() + start += self._index + end += self._index + self._index = end + return int( self._buffer[start:end] ) + + def _parse_string(self, delim): + """ string: "g\'day" | 'have a "nice" day' | s(size)"raw data" """ + rv = "" + + if delim in ("'", '"'): + rv = self._parse_string_delim(delim) + elif delim == 's': + rv = self._parse_string_raw() + else: + raise LLSDParseError("invalid string token at index %d." % self._index) + + return rv + + + def _parse_string_delim(self, delim): + """ string: "g'day 'un" | 'have a "nice" day' """ + list = [] + found_escape = False + found_hex = False + found_digit = False + byte = 0 + while True: + cc = self._buffer[self._index] + self._index += 1 + if found_escape: + if found_hex: + if found_digit: + found_escape = False + found_hex = False + found_digit = False + byte <<= 4 + byte |= _hex_as_nybble(cc) + list.append(chr(byte)) + byte = 0 + else: + found_digit = True + byte = _hex_as_nybble(cc) + elif cc == 'x': + found_hex = True + else: + if cc == 'a': + list.append('\a') + elif cc == 'b': + list.append('\b') + elif cc == 'f': + list.append('\f') + elif cc == 'n': + list.append('\n') + elif cc == 'r': + list.append('\r') + elif cc == 't': + list.append('\t') + elif cc == 'v': + list.append('\v') + else: + list.append(cc) + found_escape = False + elif cc == '\\': + found_escape = True + elif cc == delim: + break + else: + list.append(cc) + return ''.join(list) + + def _parse_string_raw(self): + """ string: s(size)"raw data" """ + # Read the (size) portion. + cc = self._buffer[self._index] + self._index += 1 + if cc != '(': + raise LLSDParseError("invalid string token at index %d." % self._index) + + rparen = self._buffer.find(')', self._index) + if rparen == -1: + raise LLSDParseError("invalid string token at index %d." % self._index) + + size = int(self._buffer[self._index:rparen]) + + self._index = rparen + 1 + delim = self._buffer[self._index] + self._index += 1 + if delim not in ("'", '"'): + raise LLSDParseError("invalid string token at index %d." % self._index) + + rv = self._buffer[self._index:(self._index + size)] + self._index += size + cc = self._buffer[self._index] + self._index += 1 + if cc != delim: + raise LLSDParseError("invalid string token at index %d." % self._index) + + return rv + +def format_binary(something): + return '\n' + _format_binary_recurse(something) + +def _format_binary_recurse(something): + def _format_list(something): + array_builder = [] + array_builder.append('[' + struct.pack('!i', len(something))) + for item in something: + array_builder.append(_format_binary_recurse(item)) + array_builder.append(']') + return ''.join(array_builder) + + if something is None: + return '!' + elif isinstance(something, LLSD): + return _format_binary_recurse(something.thing) + elif isinstance(something, bool): + if something: + return '1' + else: + return '0' + elif isinstance(something, (int, long)): + return 'i' + struct.pack('!i', something) + elif isinstance(something, float): + return 'r' + struct.pack('!d', something) + elif isinstance(something, lluuid.UUID): + return 'u' + something._bits + elif isinstance(something, binary): + return 'b' + struct.pack('!i', len(something)) + something + elif isinstance(something, str): + return 's' + struct.pack('!i', len(something)) + something + elif isinstance(something, unicode): + something = something.encode('utf-8') + return 's' + struct.pack('!i', len(something)) + something + elif isinstance(something, uri): + return 'l' + struct.pack('!i', len(something)) + something + elif isinstance(something, datetime.datetime): + seconds_since_epoch = time.mktime(something.timetuple()) + return 'd' + struct.pack('!d', seconds_since_epoch) + elif isinstance(something, (list, tuple)): + return _format_list(something) + elif isinstance(something, dict): + map_builder = [] + map_builder.append('{' + struct.pack('!i', len(something))) + for key, value in something.items(): + if isinstance(key, unicode): + key = key.encode('utf-8') + map_builder.append('k' + struct.pack('!i', len(key)) + key) + map_builder.append(_format_binary_recurse(value)) + map_builder.append('}') + return ''.join(map_builder) + else: + try: + return _format_list(list(something)) + except TypeError: + raise LLSDSerializationError( + "Cannot serialize unknown type: %s (%s)" % + (type(something), something)) + + +def parse_binary(binary): + if binary.startswith(''): + just_binary = binary.split('\n', 1)[1] + else: + just_binary = binary + return LLSDBinaryParser().parse(just_binary) + +def parse_xml(something): + try: + return to_python(fromstring(something)[0]) + except ElementTreeError, err: + raise LLSDParseError(*err.args) + +def parse_notation(something): + return LLSDNotationParser().parse(something) + +def parse(something): + try: + something = string.lstrip(something) #remove any pre-trailing whitespace + if something.startswith(''): + return parse_binary(something) + # This should be better. + elif something.startswith('<'): + return parse_xml(something) + else: + return parse_notation(something) + except KeyError, e: + raise Exception('LLSD could not be parsed: %s' % (e,)) + +class LLSD(object): + def __init__(self, thing=None): + self.thing = thing + + def __str__(self): + return self.toXML(self.thing) + + parse = staticmethod(parse) + toXML = staticmethod(format_xml) + toPrettyXML = staticmethod(format_pretty_xml) + toBinary = staticmethod(format_binary) + toNotation = staticmethod(format_notation) + + +undef = LLSD(None) + +XML_MIME_TYPE = 'application/llsd+xml' +BINARY_MIME_TYPE = 'application/llsd+binary' + +# register converters for llsd in mulib, if it is available +try: + from mulib import stacked, mu + stacked.NoProducer() # just to exercise stacked + mu.safe_load(None) # just to exercise mu +except: + # mulib not available, don't print an error message since this is normal + pass +else: + mu.add_parser(parse, XML_MIME_TYPE) + mu.add_parser(parse, 'application/llsd+binary') + + def llsd_convert_xml(llsd_stuff, request): + request.write(format_xml(llsd_stuff)) + + def llsd_convert_binary(llsd_stuff, request): + request.write(format_binary(llsd_stuff)) + + for typ in [LLSD, dict, list, tuple, str, int, long, float, bool, unicode, type(None)]: + stacked.add_producer(typ, llsd_convert_xml, XML_MIME_TYPE) + stacked.add_producer(typ, llsd_convert_xml, 'application/xml') + stacked.add_producer(typ, llsd_convert_xml, 'text/xml') + + stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') + + stacked.add_producer(LLSD, llsd_convert_xml, '*/*') + + # in case someone is using the legacy mu.xml wrapper, we need to + # tell mu to produce application/xml or application/llsd+xml + # (based on the accept header) from raw xml. Phoenix 2008-07-21 + stacked.add_producer(mu.xml, mu.produce_raw, XML_MIME_TYPE) + stacked.add_producer(mu.xml, mu.produce_raw, 'application/xml') + + + +# mulib wsgi stuff +# try: +# from mulib import mu, adapters +# +# # try some known attributes from mulib to be ultra-sure we've imported it +# mu.get_current +# adapters.handlers +# except: +# # mulib not available, don't print an error message since this is normal +# pass +# else: +# def llsd_xml_handler(content_type): +# def handle_llsd_xml(env, start_response): +# llsd_stuff, _ = mu.get_current(env) +# result = format_xml(llsd_stuff) +# start_response("200 OK", [('Content-Type', content_type)]) +# env['mu.negotiated_type'] = content_type +# yield result +# return handle_llsd_xml +# +# def llsd_binary_handler(content_type): +# def handle_llsd_binary(env, start_response): +# llsd_stuff, _ = mu.get_current(env) +# result = format_binary(llsd_stuff) +# start_response("200 OK", [('Content-Type', content_type)]) +# env['mu.negotiated_type'] = content_type +# yield result +# return handle_llsd_binary +# +# adapters.DEFAULT_PARSERS[XML_MIME_TYPE] = parse + +# for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: +# for content_type in (XML_MIME_TYPE, 'application/xml'): +# adapters.handlers.set_handler(typ, llsd_xml_handler(content_type), content_type) +# +# adapters.handlers.set_handler(typ, llsd_binary_handler(BINARY_MIME_TYPE), BINARY_MIME_TYPE) +# +# adapters.handlers.set_handler(LLSD, llsd_xml_handler(XML_MIME_TYPE), '*/*') -- cgit v1.2.3 From 68832f56ba09541f01a246574b141e3ab5fb62b8 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 14 Jul 2016 08:37:23 -0700 Subject: add dependent modules --- indra/viewer_components/manager/base/llsd.py | 1052 ++++++++++++++++++++ indra/viewer_components/manager/base/lluuid.py | 319 ++++++ indra/viewer_components/manager/llsd.py | 1052 -------------------- .../manager/util/fastest_elementtree.py | 64 ++ 4 files changed, 1435 insertions(+), 1052 deletions(-) create mode 100755 indra/viewer_components/manager/base/llsd.py create mode 100755 indra/viewer_components/manager/base/lluuid.py delete mode 100755 indra/viewer_components/manager/llsd.py create mode 100755 indra/viewer_components/manager/util/fastest_elementtree.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/base/llsd.py b/indra/viewer_components/manager/base/llsd.py new file mode 100755 index 0000000000..4527b115f9 --- /dev/null +++ b/indra/viewer_components/manager/base/llsd.py @@ -0,0 +1,1052 @@ +"""\ +@file llsd.py +@brief Types as well as parsing and formatting functions for handling LLSD. + +$LicenseInfo:firstyear=2006&license=mit$ + +Copyright (c) 2006-2009, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +""" + +import datetime +import base64 +import string +import struct +import time +import types +import re + +from indra.util.fastest_elementtree import ElementTreeError, fromstring +from indra.base import lluuid + +# cllsd.c in server/server-1.25 has memory leaks, +# so disabling cllsd for now +#try: +# import cllsd +#except ImportError: +# cllsd = None +cllsd = None + +int_regex = re.compile(r"[-+]?\d+") +real_regex = re.compile(r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") +alpha_regex = re.compile(r"[a-zA-Z]+") +date_regex = re.compile(r"(?P\d{4})-(?P\d{2})-(?P\d{2})T" + r"(?P\d{2}):(?P\d{2}):(?P\d{2})" + r"(?P(\.\d+)?)Z") +#date: d"YYYY-MM-DDTHH:MM:SS.FFFFFFZ" + +class LLSDParseError(Exception): + pass + +class LLSDSerializationError(TypeError): + pass + + +class binary(str): + pass + +class uri(str): + pass + + +BOOL_TRUE = ('1', '1.0', 'true') +BOOL_FALSE = ('0', '0.0', 'false', '') + + +def format_datestr(v): + """ Formats a datetime or date object into the string format shared by xml and notation serializations.""" + if hasattr(v, 'microsecond'): + return v.isoformat() + 'Z' + else: + return v.strftime('%Y-%m-%dT%H:%M:%SZ') + +def parse_datestr(datestr): + """Parses a datetime object from the string format shared by xml and notation serializations.""" + if datestr == "": + return datetime.datetime(1970, 1, 1) + + match = re.match(date_regex, datestr) + if not match: + raise LLSDParseError("invalid date string '%s'." % datestr) + + year = int(match.group('year')) + month = int(match.group('month')) + day = int(match.group('day')) + hour = int(match.group('hour')) + minute = int(match.group('minute')) + second = int(match.group('second')) + seconds_float = match.group('second_float') + microsecond = 0 + if seconds_float: + microsecond = int(float('0' + seconds_float) * 1e6) + return datetime.datetime(year, month, day, hour, minute, second, microsecond) + + +def bool_to_python(node): + val = node.text or '' + if val in BOOL_TRUE: + return True + else: + return False + +def int_to_python(node): + val = node.text or '' + if not val.strip(): + return 0 + return int(val) + +def real_to_python(node): + val = node.text or '' + if not val.strip(): + return 0.0 + return float(val) + +def uuid_to_python(node): + return lluuid.UUID(node.text) + +def str_to_python(node): + return node.text or '' + +def bin_to_python(node): + return binary(base64.decodestring(node.text or '')) + +def date_to_python(node): + val = node.text or '' + if not val: + val = "1970-01-01T00:00:00Z" + return parse_datestr(val) + + +def uri_to_python(node): + val = node.text or '' + if not val: + return None + return uri(val) + +def map_to_python(node): + result = {} + for index in range(len(node))[::2]: + result[node[index].text] = to_python(node[index+1]) + return result + +def array_to_python(node): + return [to_python(child) for child in node] + + +NODE_HANDLERS = dict( + undef=lambda x: None, + boolean=bool_to_python, + integer=int_to_python, + real=real_to_python, + uuid=uuid_to_python, + string=str_to_python, + binary=bin_to_python, + date=date_to_python, + uri=uri_to_python, + map=map_to_python, + array=array_to_python, + ) + +def to_python(node): + return NODE_HANDLERS[node.tag](node) + +class Nothing(object): + pass + + +class LLSDXMLFormatter(object): + def __init__(self): + self.type_map = { + type(None) : self.UNDEF, + bool : self.BOOLEAN, + int : self.INTEGER, + long : self.INTEGER, + float : self.REAL, + lluuid.UUID : self.UUID, + binary : self.BINARY, + str : self.STRING, + unicode : self.STRING, + uri : self.URI, + datetime.datetime : self.DATE, + datetime.date : self.DATE, + list : self.ARRAY, + tuple : self.ARRAY, + types.GeneratorType : self.ARRAY, + dict : self.MAP, + LLSD : self.LLSD + } + + def elt(self, name, contents=None): + if(contents is None or contents is ''): + return "<%s />" % (name,) + else: + if type(contents) is unicode: + contents = contents.encode('utf-8') + return "<%s>%s" % (name, contents, name) + + def xml_esc(self, v): + if type(v) is unicode: + v = v.encode('utf-8') + return v.replace('&', '&').replace('<', '<').replace('>', '>') + + def LLSD(self, v): + return self.generate(v.thing) + def UNDEF(self, v): + return self.elt('undef') + def BOOLEAN(self, v): + if v: + return self.elt('boolean', 'true') + else: + return self.elt('boolean', 'false') + def INTEGER(self, v): + return self.elt('integer', v) + def REAL(self, v): + return self.elt('real', v) + def UUID(self, v): + if(v.isNull()): + return self.elt('uuid') + else: + return self.elt('uuid', v) + def BINARY(self, v): + return self.elt('binary', base64.encodestring(v)) + def STRING(self, v): + return self.elt('string', self.xml_esc(v)) + def URI(self, v): + return self.elt('uri', self.xml_esc(str(v))) + def DATE(self, v): + return self.elt('date', format_datestr(v)) + def ARRAY(self, v): + return self.elt('array', ''.join([self.generate(item) for item in v])) + def MAP(self, v): + return self.elt( + 'map', + ''.join(["%s%s" % (self.elt('key', self.xml_esc(str(key))), self.generate(value)) + for key, value in v.items()])) + + typeof = type + def generate(self, something): + t = self.typeof(something) + if self.type_map.has_key(t): + return self.type_map[t](something) + else: + raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( + t, something)) + + def _format(self, something): + return '' + self.elt("llsd", self.generate(something)) + + def format(self, something): + if cllsd: + return cllsd.llsd_to_xml(something) + return self._format(something) + +_g_xml_formatter = None +def format_xml(something): + global _g_xml_formatter + if _g_xml_formatter is None: + _g_xml_formatter = LLSDXMLFormatter() + return _g_xml_formatter.format(something) + +class LLSDXMLPrettyFormatter(LLSDXMLFormatter): + def __init__(self, indent_atom = None): + # Call the super class constructor so that we have the type map + super(LLSDXMLPrettyFormatter, self).__init__() + + # Override the type map to use our specialized formatters to + # emit the pretty output. + self.type_map[list] = self.PRETTY_ARRAY + self.type_map[tuple] = self.PRETTY_ARRAY + self.type_map[types.GeneratorType] = self.PRETTY_ARRAY, + self.type_map[dict] = self.PRETTY_MAP + + # Private data used for indentation. + self._indent_level = 1 + if indent_atom is None: + self._indent_atom = ' ' + else: + self._indent_atom = indent_atom + + def _indent(self): + "Return an indentation based on the atom and indentation level." + return self._indent_atom * self._indent_level + + def PRETTY_ARRAY(self, v): + rv = [] + rv.append('\n') + self._indent_level = self._indent_level + 1 + rv.extend(["%s%s\n" % + (self._indent(), + self.generate(item)) + for item in v]) + self._indent_level = self._indent_level - 1 + rv.append(self._indent()) + rv.append('') + return ''.join(rv) + + def PRETTY_MAP(self, v): + rv = [] + rv.append('\n') + self._indent_level = self._indent_level + 1 + keys = v.keys() + keys.sort() + rv.extend(["%s%s\n%s%s\n" % + (self._indent(), + self.elt('key', key), + self._indent(), + self.generate(v[key])) + for key in keys]) + self._indent_level = self._indent_level - 1 + rv.append(self._indent()) + rv.append('') + return ''.join(rv) + + def format(self, something): + data = [] + data.append('\n') + data.append(self.generate(something)) + data.append('\n') + return '\n'.join(data) + +def format_pretty_xml(something): + """@brief Serialize a python object as 'pretty' llsd xml. + + The output conforms to the LLSD DTD, unlike the output from the + standard python xml.dom DOM::toprettyxml() method which does not + preserve significant whitespace. + This function is not necessarily suited for serializing very large + objects. It is not optimized by the cllsd module, and sorts on + dict (llsd map) keys alphabetically to ease human reading. + """ + return LLSDXMLPrettyFormatter().format(something) + +class LLSDNotationFormatter(object): + def __init__(self): + self.type_map = { + type(None) : self.UNDEF, + bool : self.BOOLEAN, + int : self.INTEGER, + long : self.INTEGER, + float : self.REAL, + lluuid.UUID : self.UUID, + binary : self.BINARY, + str : self.STRING, + unicode : self.STRING, + uri : self.URI, + datetime.datetime : self.DATE, + datetime.date : self.DATE, + list : self.ARRAY, + tuple : self.ARRAY, + types.GeneratorType : self.ARRAY, + dict : self.MAP, + LLSD : self.LLSD + } + + def LLSD(self, v): + return self.generate(v.thing) + def UNDEF(self, v): + return '!' + def BOOLEAN(self, v): + if v: + return 'true' + else: + return 'false' + def INTEGER(self, v): + return "i%s" % v + def REAL(self, v): + return "r%s" % v + def UUID(self, v): + return "u%s" % v + def BINARY(self, v): + return 'b64"' + base64.encodestring(v) + '"' + def STRING(self, v): + if isinstance(v, unicode): + v = v.encode('utf-8') + return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") + def URI(self, v): + return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') + def DATE(self, v): + return 'd"%s"' % format_datestr(v) + def ARRAY(self, v): + return "[%s]" % ','.join([self.generate(item) for item in v]) + def MAP(self, v): + def fix(key): + if isinstance(key, unicode): + return key.encode('utf-8') + return key + return "{%s}" % ','.join(["'%s':%s" % (fix(key).replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) + for key, value in v.items()]) + + def generate(self, something): + t = type(something) + handler = self.type_map.get(t) + if handler: + return handler(something) + else: + try: + return self.ARRAY(iter(something)) + except TypeError: + raise LLSDSerializationError( + "Cannot serialize unknown type: %s (%s)" % (t, something)) + + def format(self, something): + return self.generate(something) + +def format_notation(something): + return LLSDNotationFormatter().format(something) + +def _hex_as_nybble(hex): + if (hex >= '0') and (hex <= '9'): + return ord(hex) - ord('0') + elif (hex >= 'a') and (hex <='f'): + return 10 + ord(hex) - ord('a') + elif (hex >= 'A') and (hex <='F'): + return 10 + ord(hex) - ord('A'); + +class LLSDBinaryParser(object): + def __init__(self): + pass + + def parse(self, buffer, ignore_binary = False): + """ + This is the basic public interface for parsing. + + @param buffer the binary data to parse in an indexable sequence. + @param ignore_binary parser throws away data in llsd binary nodes. + @return returns a python object. + """ + self._buffer = buffer + self._index = 0 + self._keep_binary = not ignore_binary + return self._parse() + + def _parse(self): + cc = self._buffer[self._index] + self._index += 1 + if cc == '{': + return self._parse_map() + elif cc == '[': + return self._parse_array() + elif cc == '!': + return None + elif cc == '0': + return False + elif cc == '1': + return True + elif cc == 'i': + # 'i' = integer + idx = self._index + self._index += 4 + return struct.unpack("!i", self._buffer[idx:idx+4])[0] + elif cc == ('r'): + # 'r' = real number + idx = self._index + self._index += 8 + return struct.unpack("!d", self._buffer[idx:idx+8])[0] + elif cc == 'u': + # 'u' = uuid + idx = self._index + self._index += 16 + return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) + elif cc == 's': + # 's' = string + return self._parse_string() + elif cc in ("'", '"'): + # delimited/escaped string + return self._parse_string_delim(cc) + elif cc == 'l': + # 'l' = uri + return uri(self._parse_string()) + elif cc == ('d'): + # 'd' = date in seconds since epoch + idx = self._index + self._index += 8 + seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] + return datetime.datetime.fromtimestamp(seconds) + elif cc == 'b': + binary = self._parse_string() + if self._keep_binary: + return binary + # *NOTE: maybe have a binary placeholder which has the + # length. + return None + else: + raise LLSDParseError("invalid binary token at byte %d: %d" % ( + self._index - 1, ord(cc))) + + def _parse_map(self): + rv = {} + size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] + self._index += 4 + count = 0 + cc = self._buffer[self._index] + self._index += 1 + key = '' + while (cc != '}') and (count < size): + if cc == 'k': + key = self._parse_string() + elif cc in ("'", '"'): + key = self._parse_string_delim(cc) + else: + raise LLSDParseError("invalid map key at byte %d." % ( + self._index - 1,)) + value = self._parse() + rv[key] = value + count += 1 + cc = self._buffer[self._index] + self._index += 1 + if cc != '}': + raise LLSDParseError("invalid map close token at byte %d." % ( + self._index,)) + return rv + + def _parse_array(self): + rv = [] + size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] + self._index += 4 + count = 0 + cc = self._buffer[self._index] + while (cc != ']') and (count < size): + rv.append(self._parse()) + count += 1 + cc = self._buffer[self._index] + if cc != ']': + raise LLSDParseError("invalid array close token at byte %d." % ( + self._index,)) + self._index += 1 + return rv + + def _parse_string(self): + size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] + self._index += 4 + rv = self._buffer[self._index:self._index+size] + self._index += size + return rv + + def _parse_string_delim(self, delim): + list = [] + found_escape = False + found_hex = False + found_digit = False + byte = 0 + while True: + cc = self._buffer[self._index] + self._index += 1 + if found_escape: + if found_hex: + if found_digit: + found_escape = False + found_hex = False + found_digit = False + byte <<= 4 + byte |= _hex_as_nybble(cc) + list.append(chr(byte)) + byte = 0 + else: + found_digit = True + byte = _hex_as_nybble(cc) + elif cc == 'x': + found_hex = True + else: + if cc == 'a': + list.append('\a') + elif cc == 'b': + list.append('\b') + elif cc == 'f': + list.append('\f') + elif cc == 'n': + list.append('\n') + elif cc == 'r': + list.append('\r') + elif cc == 't': + list.append('\t') + elif cc == 'v': + list.append('\v') + else: + list.append(cc) + found_escape = False + elif cc == '\\': + found_escape = True + elif cc == delim: + break + else: + list.append(cc) + return ''.join(list) + +class LLSDNotationParser(object): + """ Parse LLSD notation: + map: { string:object, string:object } + array: [ object, object, object ] + undef: ! + boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE + integer: i#### + real: r#### + uuid: u#### + string: "g\'day" | 'have a "nice" day' | s(size)"raw data" + uri: l"escaped" + date: d"YYYY-MM-DDTHH:MM:SS.FFZ" + binary: b##"ff3120ab1" | b(size)"raw data" + """ + def __init__(self): + pass + + def parse(self, buffer, ignore_binary = False): + """ + This is the basic public interface for parsing. + + @param buffer the notation string to parse. + @param ignore_binary parser throws away data in llsd binary nodes. + @return returns a python object. + """ + if buffer == "": + return False + + self._buffer = buffer + self._index = 0 + return self._parse() + + def _parse(self): + cc = self._buffer[self._index] + self._index += 1 + if cc == '{': + return self._parse_map() + elif cc == '[': + return self._parse_array() + elif cc == '!': + return None + elif cc == '0': + return False + elif cc == '1': + return True + elif cc in ('F', 'f'): + self._skip_alpha() + return False + elif cc in ('T', 't'): + self._skip_alpha() + return True + elif cc == 'i': + # 'i' = integer + return self._parse_integer() + elif cc == ('r'): + # 'r' = real number + return self._parse_real() + elif cc == 'u': + # 'u' = uuid + return self._parse_uuid() + elif cc in ("'", '"', 's'): + return self._parse_string(cc) + elif cc == 'l': + # 'l' = uri + delim = self._buffer[self._index] + self._index += 1 + val = uri(self._parse_string(delim)) + if len(val) == 0: + return None + return val + elif cc == ('d'): + # 'd' = date in seconds since epoch + return self._parse_date() + elif cc == 'b': + return self._parse_binary() + else: + raise LLSDParseError("invalid token at index %d: %d" % ( + self._index - 1, ord(cc))) + + def _parse_binary(self): + i = self._index + if self._buffer[i:i+2] == '64': + q = self._buffer[i+2] + e = self._buffer.find(q, i+3) + try: + return base64.decodestring(self._buffer[i+3:e]) + finally: + self._index = e + 1 + else: + raise LLSDParseError('random horrible binary format not supported') + + def _parse_map(self): + """ map: { string:object, string:object } """ + rv = {} + cc = self._buffer[self._index] + self._index += 1 + key = '' + found_key = False + while (cc != '}'): + if not found_key: + if cc in ("'", '"', 's'): + key = self._parse_string(cc) + found_key = True + elif cc.isspace() or cc == ',': + cc = self._buffer[self._index] + self._index += 1 + else: + raise LLSDParseError("invalid map key at byte %d." % ( + self._index - 1,)) + elif cc.isspace() or cc == ':': + cc = self._buffer[self._index] + self._index += 1 + continue + else: + self._index += 1 + value = self._parse() + rv[key] = value + found_key = False + cc = self._buffer[self._index] + self._index += 1 + + return rv + + def _parse_array(self): + """ array: [ object, object, object ] """ + rv = [] + cc = self._buffer[self._index] + while (cc != ']'): + if cc.isspace() or cc == ',': + self._index += 1 + cc = self._buffer[self._index] + continue + rv.append(self._parse()) + cc = self._buffer[self._index] + + if cc != ']': + raise LLSDParseError("invalid array close token at index %d." % ( + self._index,)) + self._index += 1 + return rv + + def _parse_uuid(self): + match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) + if not match: + raise LLSDParseError("invalid uuid token at index %d." % self._index) + + (start, end) = match.span() + start += self._index + end += self._index + self._index = end + return lluuid.UUID(self._buffer[start:end]) + + def _skip_alpha(self): + match = re.match(alpha_regex, self._buffer[self._index:]) + if match: + self._index += match.end() + + def _parse_date(self): + delim = self._buffer[self._index] + self._index += 1 + datestr = self._parse_string(delim) + return parse_datestr(datestr) + + def _parse_real(self): + match = re.match(real_regex, self._buffer[self._index:]) + if not match: + raise LLSDParseError("invalid real token at index %d." % self._index) + + (start, end) = match.span() + start += self._index + end += self._index + self._index = end + return float( self._buffer[start:end] ) + + def _parse_integer(self): + match = re.match(int_regex, self._buffer[self._index:]) + if not match: + raise LLSDParseError("invalid integer token at index %d." % self._index) + + (start, end) = match.span() + start += self._index + end += self._index + self._index = end + return int( self._buffer[start:end] ) + + def _parse_string(self, delim): + """ string: "g\'day" | 'have a "nice" day' | s(size)"raw data" """ + rv = "" + + if delim in ("'", '"'): + rv = self._parse_string_delim(delim) + elif delim == 's': + rv = self._parse_string_raw() + else: + raise LLSDParseError("invalid string token at index %d." % self._index) + + return rv + + + def _parse_string_delim(self, delim): + """ string: "g'day 'un" | 'have a "nice" day' """ + list = [] + found_escape = False + found_hex = False + found_digit = False + byte = 0 + while True: + cc = self._buffer[self._index] + self._index += 1 + if found_escape: + if found_hex: + if found_digit: + found_escape = False + found_hex = False + found_digit = False + byte <<= 4 + byte |= _hex_as_nybble(cc) + list.append(chr(byte)) + byte = 0 + else: + found_digit = True + byte = _hex_as_nybble(cc) + elif cc == 'x': + found_hex = True + else: + if cc == 'a': + list.append('\a') + elif cc == 'b': + list.append('\b') + elif cc == 'f': + list.append('\f') + elif cc == 'n': + list.append('\n') + elif cc == 'r': + list.append('\r') + elif cc == 't': + list.append('\t') + elif cc == 'v': + list.append('\v') + else: + list.append(cc) + found_escape = False + elif cc == '\\': + found_escape = True + elif cc == delim: + break + else: + list.append(cc) + return ''.join(list) + + def _parse_string_raw(self): + """ string: s(size)"raw data" """ + # Read the (size) portion. + cc = self._buffer[self._index] + self._index += 1 + if cc != '(': + raise LLSDParseError("invalid string token at index %d." % self._index) + + rparen = self._buffer.find(')', self._index) + if rparen == -1: + raise LLSDParseError("invalid string token at index %d." % self._index) + + size = int(self._buffer[self._index:rparen]) + + self._index = rparen + 1 + delim = self._buffer[self._index] + self._index += 1 + if delim not in ("'", '"'): + raise LLSDParseError("invalid string token at index %d." % self._index) + + rv = self._buffer[self._index:(self._index + size)] + self._index += size + cc = self._buffer[self._index] + self._index += 1 + if cc != delim: + raise LLSDParseError("invalid string token at index %d." % self._index) + + return rv + +def format_binary(something): + return '\n' + _format_binary_recurse(something) + +def _format_binary_recurse(something): + def _format_list(something): + array_builder = [] + array_builder.append('[' + struct.pack('!i', len(something))) + for item in something: + array_builder.append(_format_binary_recurse(item)) + array_builder.append(']') + return ''.join(array_builder) + + if something is None: + return '!' + elif isinstance(something, LLSD): + return _format_binary_recurse(something.thing) + elif isinstance(something, bool): + if something: + return '1' + else: + return '0' + elif isinstance(something, (int, long)): + return 'i' + struct.pack('!i', something) + elif isinstance(something, float): + return 'r' + struct.pack('!d', something) + elif isinstance(something, lluuid.UUID): + return 'u' + something._bits + elif isinstance(something, binary): + return 'b' + struct.pack('!i', len(something)) + something + elif isinstance(something, str): + return 's' + struct.pack('!i', len(something)) + something + elif isinstance(something, unicode): + something = something.encode('utf-8') + return 's' + struct.pack('!i', len(something)) + something + elif isinstance(something, uri): + return 'l' + struct.pack('!i', len(something)) + something + elif isinstance(something, datetime.datetime): + seconds_since_epoch = time.mktime(something.timetuple()) + return 'd' + struct.pack('!d', seconds_since_epoch) + elif isinstance(something, (list, tuple)): + return _format_list(something) + elif isinstance(something, dict): + map_builder = [] + map_builder.append('{' + struct.pack('!i', len(something))) + for key, value in something.items(): + if isinstance(key, unicode): + key = key.encode('utf-8') + map_builder.append('k' + struct.pack('!i', len(key)) + key) + map_builder.append(_format_binary_recurse(value)) + map_builder.append('}') + return ''.join(map_builder) + else: + try: + return _format_list(list(something)) + except TypeError: + raise LLSDSerializationError( + "Cannot serialize unknown type: %s (%s)" % + (type(something), something)) + + +def parse_binary(binary): + if binary.startswith(''): + just_binary = binary.split('\n', 1)[1] + else: + just_binary = binary + return LLSDBinaryParser().parse(just_binary) + +def parse_xml(something): + try: + return to_python(fromstring(something)[0]) + except ElementTreeError, err: + raise LLSDParseError(*err.args) + +def parse_notation(something): + return LLSDNotationParser().parse(something) + +def parse(something): + try: + something = string.lstrip(something) #remove any pre-trailing whitespace + if something.startswith(''): + return parse_binary(something) + # This should be better. + elif something.startswith('<'): + return parse_xml(something) + else: + return parse_notation(something) + except KeyError, e: + raise Exception('LLSD could not be parsed: %s' % (e,)) + +class LLSD(object): + def __init__(self, thing=None): + self.thing = thing + + def __str__(self): + return self.toXML(self.thing) + + parse = staticmethod(parse) + toXML = staticmethod(format_xml) + toPrettyXML = staticmethod(format_pretty_xml) + toBinary = staticmethod(format_binary) + toNotation = staticmethod(format_notation) + + +undef = LLSD(None) + +XML_MIME_TYPE = 'application/llsd+xml' +BINARY_MIME_TYPE = 'application/llsd+binary' + +# register converters for llsd in mulib, if it is available +try: + from mulib import stacked, mu + stacked.NoProducer() # just to exercise stacked + mu.safe_load(None) # just to exercise mu +except: + # mulib not available, don't print an error message since this is normal + pass +else: + mu.add_parser(parse, XML_MIME_TYPE) + mu.add_parser(parse, 'application/llsd+binary') + + def llsd_convert_xml(llsd_stuff, request): + request.write(format_xml(llsd_stuff)) + + def llsd_convert_binary(llsd_stuff, request): + request.write(format_binary(llsd_stuff)) + + for typ in [LLSD, dict, list, tuple, str, int, long, float, bool, unicode, type(None)]: + stacked.add_producer(typ, llsd_convert_xml, XML_MIME_TYPE) + stacked.add_producer(typ, llsd_convert_xml, 'application/xml') + stacked.add_producer(typ, llsd_convert_xml, 'text/xml') + + stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') + + stacked.add_producer(LLSD, llsd_convert_xml, '*/*') + + # in case someone is using the legacy mu.xml wrapper, we need to + # tell mu to produce application/xml or application/llsd+xml + # (based on the accept header) from raw xml. Phoenix 2008-07-21 + stacked.add_producer(mu.xml, mu.produce_raw, XML_MIME_TYPE) + stacked.add_producer(mu.xml, mu.produce_raw, 'application/xml') + + + +# mulib wsgi stuff +# try: +# from mulib import mu, adapters +# +# # try some known attributes from mulib to be ultra-sure we've imported it +# mu.get_current +# adapters.handlers +# except: +# # mulib not available, don't print an error message since this is normal +# pass +# else: +# def llsd_xml_handler(content_type): +# def handle_llsd_xml(env, start_response): +# llsd_stuff, _ = mu.get_current(env) +# result = format_xml(llsd_stuff) +# start_response("200 OK", [('Content-Type', content_type)]) +# env['mu.negotiated_type'] = content_type +# yield result +# return handle_llsd_xml +# +# def llsd_binary_handler(content_type): +# def handle_llsd_binary(env, start_response): +# llsd_stuff, _ = mu.get_current(env) +# result = format_binary(llsd_stuff) +# start_response("200 OK", [('Content-Type', content_type)]) +# env['mu.negotiated_type'] = content_type +# yield result +# return handle_llsd_binary +# +# adapters.DEFAULT_PARSERS[XML_MIME_TYPE] = parse + +# for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: +# for content_type in (XML_MIME_TYPE, 'application/xml'): +# adapters.handlers.set_handler(typ, llsd_xml_handler(content_type), content_type) +# +# adapters.handlers.set_handler(typ, llsd_binary_handler(BINARY_MIME_TYPE), BINARY_MIME_TYPE) +# +# adapters.handlers.set_handler(LLSD, llsd_xml_handler(XML_MIME_TYPE), '*/*') diff --git a/indra/viewer_components/manager/base/lluuid.py b/indra/viewer_components/manager/base/lluuid.py new file mode 100755 index 0000000000..7413ffe10d --- /dev/null +++ b/indra/viewer_components/manager/base/lluuid.py @@ -0,0 +1,319 @@ +"""\ +@file lluuid.py +@brief UUID parser/generator. + +$LicenseInfo:firstyear=2004&license=mit$ + +Copyright (c) 2004-2009, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +""" + +import random, socket, string, time, re +import uuid +try: + # Python 2.6 + from hashlib import md5 +except ImportError: + # Python 2.5 and earlier + from md5 import new as md5 + +def _int2binstr(i,l): + s='' + for a in range(l): + s=chr(i&0xFF)+s + i>>=8 + return s + +def _binstr2int(s): + i = long(0) + for c in s: + i = (i<<8) + ord(c) + return i + +class UUID(object): + """ + A class which represents a 16 byte integer. Stored as a 16 byte 8 + bit character string. + + The string version is to be of the form: + AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex) + where A=network address, B=timestamp, C=random. + """ + + NULL_STR = "00000000-0000-0000-0000-000000000000" + + # the UUIDREGEX_STRING is helpful for parsing UUID's in text + hex_wildcard = r"[0-9a-fA-F]" + word = hex_wildcard + r"{4,4}-" + long_word = hex_wildcard + r"{8,8}-" + very_long_word = hex_wildcard + r"{12,12}" + UUID_REGEX_STRING = long_word + word + word + word + very_long_word + uuid_regex = re.compile(UUID_REGEX_STRING) + + rand = random.Random() + ip = '' + try: + ip = socket.gethostbyname(socket.gethostname()) + except(socket.gaierror, socket.error): + # no ip address, so just default to somewhere in 10.x.x.x + ip = '10' + for i in range(3): + ip += '.' + str(rand.randrange(1,254)) + hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) + lastid = '' + + def __init__(self, possible_uuid=None): + """ + Initialize to first valid UUID in argument (if a string), + or to null UUID if none found or argument is not supplied. + + If the argument is a UUID, the constructed object will be a copy of it. + """ + self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + if possible_uuid is None: + return + + if isinstance(possible_uuid, type(self)): + self.set(possible_uuid) + return + + uuid_match = UUID.uuid_regex.search(possible_uuid) + if uuid_match: + uuid_string = uuid_match.group() + s = string.replace(uuid_string, '-', '') + self._bits = _int2binstr(string.atol(s[:8],16),4) + \ + _int2binstr(string.atol(s[8:16],16),4) + \ + _int2binstr(string.atol(s[16:24],16),4) + \ + _int2binstr(string.atol(s[24:],16),4) + + def __len__(self): + """ + Used by the len() builtin. + """ + return 36 + + def __nonzero__(self): + return self._bits != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + + def __str__(self): + uuid_string = self.toString() + return uuid_string + + __repr__ = __str__ + + def __getitem__(self, index): + return str(self)[index] + + def __eq__(self, other): + if isinstance(other, (str, unicode)): + return other == str(self) + return self._bits == getattr(other, '_bits', '') + + def __ne__(self, other): + return not self.__eq__(other) + + def __le__(self, other): + return self._bits <= other._bits + + def __ge__(self, other): + return self._bits >= other._bits + + def __lt__(self, other): + return self._bits < other._bits + + def __gt__(self, other): + return self._bits > other._bits + + def __hash__(self): + return hash(self._bits) + + def set(self, uuid): + self._bits = uuid._bits + + def setFromString(self, uuid_string): + """ + Given a string version of a uuid, set self bits + appropriately. Returns self. + """ + s = string.replace(uuid_string, '-', '') + self._bits = _int2binstr(string.atol(s[:8],16),4) + \ + _int2binstr(string.atol(s[8:16],16),4) + \ + _int2binstr(string.atol(s[16:24],16),4) + \ + _int2binstr(string.atol(s[24:],16),4) + return self + + def setFromMemoryDump(self, gdb_string): + """ + We expect to get gdb_string as four hex units. eg: + 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2 + Which will be translated to: + db547d14-1b3f4bc3-9b984f71-d22f890a + Returns self. + """ + s = string.replace(gdb_string, '0x', '') + s = string.replace(s, ' ', '') + t = '' + for i in range(8,40,8): + for j in range(0,8,2): + t = t + s[i-j-2:i-j] + self.setFromString(t) + + def toString(self): + """ + Return as a string matching the LL standard + AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex) + where A=network address, B=timestamp, C=random. + """ + return uuid_bits_to_string(self._bits) + + def getAsString(self): + """ + Return a different string representation of the form + AAAAAAAA-AAAABBBB-BBBBBBBB-BBCCCCCC (a 128-bit number in hex) + where A=network address, B=timestamp, C=random. + """ + i1 = _binstr2int(self._bits[0:4]) + i2 = _binstr2int(self._bits[4:8]) + i3 = _binstr2int(self._bits[8:12]) + i4 = _binstr2int(self._bits[12:16]) + return '%08lx-%08lx-%08lx-%08lx' % (i1,i2,i3,i4) + + def generate(self): + """ + Generate a new uuid. This algorithm is slightly different + from c++ implementation for portability reasons. + Returns self. + """ + m = md5() + m.update(uuid.uuid1().bytes) + self._bits = m.digest() + return self + + def isNull(self): + """ + Returns 1 if the uuid is null - ie, equal to default uuid. + """ + return (self._bits == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") + + def xor(self, rhs): + """ + xors self with rhs. + """ + v1 = _binstr2int(self._bits[0:4]) ^ _binstr2int(rhs._bits[0:4]) + v2 = _binstr2int(self._bits[4:8]) ^ _binstr2int(rhs._bits[4:8]) + v3 = _binstr2int(self._bits[8:12]) ^ _binstr2int(rhs._bits[8:12]) + v4 = _binstr2int(self._bits[12:16]) ^ _binstr2int(rhs._bits[12:16]) + self._bits = _int2binstr(v1,4) + \ + _int2binstr(v2,4) + \ + _int2binstr(v3,4) + \ + _int2binstr(v4,4) + + +# module-level null constant +NULL = UUID() + +def printTranslatedMemory(four_hex_uints): + """ + We expect to get the string as four hex units. eg: + 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2 + Which will be translated to: + db547d14-1b3f4bc3-9b984f71-d22f890a + """ + uuid = UUID() + uuid.setFromMemoryDump(four_hex_uints) + print uuid.toString() + +def isUUID(id_str): + """ + This function returns: + - 1 if the string passed is a UUID + - 0 is the string passed is not a UUID + - None if it neither of the if's below is satisfied + """ + if not id_str or len(id_str) < 5 or len(id_str) > 36: + return 0 + + if isinstance(id_str, UUID) or UUID.uuid_regex.match(id_str): + return 1 + + return None + +def isPossiblyID(id_str): + """ + This function returns 1 if the string passed has some uuid-like + characteristics. Otherwise returns 0. + """ + + is_uuid = isUUID(id_str) + if is_uuid is not None: + return is_uuid + + # build a string which matches every character. + hex_wildcard = r"[0-9a-fA-F]" + chars = len(id_str) + next = min(chars, 8) + matcher = hex_wildcard+"{"+str(next)+","+str(next)+"}" + chars = chars - next + if chars > 0: + matcher = matcher + "-" + chars = chars - 1 + for block in range(3): + next = max(min(chars, 4), 0) + if next: + matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" + chars = chars - next + if chars > 0: + matcher = matcher + "-" + chars = chars - 1 + if chars > 0: + next = min(chars, 12) + matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" + #print matcher + uuid_matcher = re.compile(matcher) + if uuid_matcher.match(id_str): + return 1 + return 0 + +def uuid_bits_to_string(bits): + i1 = _binstr2int(bits[0:4]) + i2 = _binstr2int(bits[4:6]) + i3 = _binstr2int(bits[6:8]) + i4 = _binstr2int(bits[8:10]) + i5 = _binstr2int(bits[10:12]) + i6 = _binstr2int(bits[12:16]) + return '%08lx-%04lx-%04lx-%04lx-%04lx%08lx' % (i1,i2,i3,i4,i5,i6) + +def uuid_bits_to_uuid(bits): + return UUID(uuid_bits_to_string(bits)) + + +try: + from mulib import stacked + stacked.NoProducer() # just to exercise stacked +except: + #print "Couldn't import mulib.stacked, not registering UUID converter" + pass +else: + def convertUUID(uuid, req): + req.write(str(uuid)) + + stacked.add_producer(UUID, convertUUID, "*/*") + stacked.add_producer(UUID, convertUUID, "text/html") diff --git a/indra/viewer_components/manager/llsd.py b/indra/viewer_components/manager/llsd.py deleted file mode 100755 index 4527b115f9..0000000000 --- a/indra/viewer_components/manager/llsd.py +++ /dev/null @@ -1,1052 +0,0 @@ -"""\ -@file llsd.py -@brief Types as well as parsing and formatting functions for handling LLSD. - -$LicenseInfo:firstyear=2006&license=mit$ - -Copyright (c) 2006-2009, Linden Research, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -$/LicenseInfo$ -""" - -import datetime -import base64 -import string -import struct -import time -import types -import re - -from indra.util.fastest_elementtree import ElementTreeError, fromstring -from indra.base import lluuid - -# cllsd.c in server/server-1.25 has memory leaks, -# so disabling cllsd for now -#try: -# import cllsd -#except ImportError: -# cllsd = None -cllsd = None - -int_regex = re.compile(r"[-+]?\d+") -real_regex = re.compile(r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") -alpha_regex = re.compile(r"[a-zA-Z]+") -date_regex = re.compile(r"(?P\d{4})-(?P\d{2})-(?P\d{2})T" - r"(?P\d{2}):(?P\d{2}):(?P\d{2})" - r"(?P(\.\d+)?)Z") -#date: d"YYYY-MM-DDTHH:MM:SS.FFFFFFZ" - -class LLSDParseError(Exception): - pass - -class LLSDSerializationError(TypeError): - pass - - -class binary(str): - pass - -class uri(str): - pass - - -BOOL_TRUE = ('1', '1.0', 'true') -BOOL_FALSE = ('0', '0.0', 'false', '') - - -def format_datestr(v): - """ Formats a datetime or date object into the string format shared by xml and notation serializations.""" - if hasattr(v, 'microsecond'): - return v.isoformat() + 'Z' - else: - return v.strftime('%Y-%m-%dT%H:%M:%SZ') - -def parse_datestr(datestr): - """Parses a datetime object from the string format shared by xml and notation serializations.""" - if datestr == "": - return datetime.datetime(1970, 1, 1) - - match = re.match(date_regex, datestr) - if not match: - raise LLSDParseError("invalid date string '%s'." % datestr) - - year = int(match.group('year')) - month = int(match.group('month')) - day = int(match.group('day')) - hour = int(match.group('hour')) - minute = int(match.group('minute')) - second = int(match.group('second')) - seconds_float = match.group('second_float') - microsecond = 0 - if seconds_float: - microsecond = int(float('0' + seconds_float) * 1e6) - return datetime.datetime(year, month, day, hour, minute, second, microsecond) - - -def bool_to_python(node): - val = node.text or '' - if val in BOOL_TRUE: - return True - else: - return False - -def int_to_python(node): - val = node.text or '' - if not val.strip(): - return 0 - return int(val) - -def real_to_python(node): - val = node.text or '' - if not val.strip(): - return 0.0 - return float(val) - -def uuid_to_python(node): - return lluuid.UUID(node.text) - -def str_to_python(node): - return node.text or '' - -def bin_to_python(node): - return binary(base64.decodestring(node.text or '')) - -def date_to_python(node): - val = node.text or '' - if not val: - val = "1970-01-01T00:00:00Z" - return parse_datestr(val) - - -def uri_to_python(node): - val = node.text or '' - if not val: - return None - return uri(val) - -def map_to_python(node): - result = {} - for index in range(len(node))[::2]: - result[node[index].text] = to_python(node[index+1]) - return result - -def array_to_python(node): - return [to_python(child) for child in node] - - -NODE_HANDLERS = dict( - undef=lambda x: None, - boolean=bool_to_python, - integer=int_to_python, - real=real_to_python, - uuid=uuid_to_python, - string=str_to_python, - binary=bin_to_python, - date=date_to_python, - uri=uri_to_python, - map=map_to_python, - array=array_to_python, - ) - -def to_python(node): - return NODE_HANDLERS[node.tag](node) - -class Nothing(object): - pass - - -class LLSDXMLFormatter(object): - def __init__(self): - self.type_map = { - type(None) : self.UNDEF, - bool : self.BOOLEAN, - int : self.INTEGER, - long : self.INTEGER, - float : self.REAL, - lluuid.UUID : self.UUID, - binary : self.BINARY, - str : self.STRING, - unicode : self.STRING, - uri : self.URI, - datetime.datetime : self.DATE, - datetime.date : self.DATE, - list : self.ARRAY, - tuple : self.ARRAY, - types.GeneratorType : self.ARRAY, - dict : self.MAP, - LLSD : self.LLSD - } - - def elt(self, name, contents=None): - if(contents is None or contents is ''): - return "<%s />" % (name,) - else: - if type(contents) is unicode: - contents = contents.encode('utf-8') - return "<%s>%s" % (name, contents, name) - - def xml_esc(self, v): - if type(v) is unicode: - v = v.encode('utf-8') - return v.replace('&', '&').replace('<', '<').replace('>', '>') - - def LLSD(self, v): - return self.generate(v.thing) - def UNDEF(self, v): - return self.elt('undef') - def BOOLEAN(self, v): - if v: - return self.elt('boolean', 'true') - else: - return self.elt('boolean', 'false') - def INTEGER(self, v): - return self.elt('integer', v) - def REAL(self, v): - return self.elt('real', v) - def UUID(self, v): - if(v.isNull()): - return self.elt('uuid') - else: - return self.elt('uuid', v) - def BINARY(self, v): - return self.elt('binary', base64.encodestring(v)) - def STRING(self, v): - return self.elt('string', self.xml_esc(v)) - def URI(self, v): - return self.elt('uri', self.xml_esc(str(v))) - def DATE(self, v): - return self.elt('date', format_datestr(v)) - def ARRAY(self, v): - return self.elt('array', ''.join([self.generate(item) for item in v])) - def MAP(self, v): - return self.elt( - 'map', - ''.join(["%s%s" % (self.elt('key', self.xml_esc(str(key))), self.generate(value)) - for key, value in v.items()])) - - typeof = type - def generate(self, something): - t = self.typeof(something) - if self.type_map.has_key(t): - return self.type_map[t](something) - else: - raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( - t, something)) - - def _format(self, something): - return '' + self.elt("llsd", self.generate(something)) - - def format(self, something): - if cllsd: - return cllsd.llsd_to_xml(something) - return self._format(something) - -_g_xml_formatter = None -def format_xml(something): - global _g_xml_formatter - if _g_xml_formatter is None: - _g_xml_formatter = LLSDXMLFormatter() - return _g_xml_formatter.format(something) - -class LLSDXMLPrettyFormatter(LLSDXMLFormatter): - def __init__(self, indent_atom = None): - # Call the super class constructor so that we have the type map - super(LLSDXMLPrettyFormatter, self).__init__() - - # Override the type map to use our specialized formatters to - # emit the pretty output. - self.type_map[list] = self.PRETTY_ARRAY - self.type_map[tuple] = self.PRETTY_ARRAY - self.type_map[types.GeneratorType] = self.PRETTY_ARRAY, - self.type_map[dict] = self.PRETTY_MAP - - # Private data used for indentation. - self._indent_level = 1 - if indent_atom is None: - self._indent_atom = ' ' - else: - self._indent_atom = indent_atom - - def _indent(self): - "Return an indentation based on the atom and indentation level." - return self._indent_atom * self._indent_level - - def PRETTY_ARRAY(self, v): - rv = [] - rv.append('\n') - self._indent_level = self._indent_level + 1 - rv.extend(["%s%s\n" % - (self._indent(), - self.generate(item)) - for item in v]) - self._indent_level = self._indent_level - 1 - rv.append(self._indent()) - rv.append('') - return ''.join(rv) - - def PRETTY_MAP(self, v): - rv = [] - rv.append('\n') - self._indent_level = self._indent_level + 1 - keys = v.keys() - keys.sort() - rv.extend(["%s%s\n%s%s\n" % - (self._indent(), - self.elt('key', key), - self._indent(), - self.generate(v[key])) - for key in keys]) - self._indent_level = self._indent_level - 1 - rv.append(self._indent()) - rv.append('') - return ''.join(rv) - - def format(self, something): - data = [] - data.append('\n') - data.append(self.generate(something)) - data.append('\n') - return '\n'.join(data) - -def format_pretty_xml(something): - """@brief Serialize a python object as 'pretty' llsd xml. - - The output conforms to the LLSD DTD, unlike the output from the - standard python xml.dom DOM::toprettyxml() method which does not - preserve significant whitespace. - This function is not necessarily suited for serializing very large - objects. It is not optimized by the cllsd module, and sorts on - dict (llsd map) keys alphabetically to ease human reading. - """ - return LLSDXMLPrettyFormatter().format(something) - -class LLSDNotationFormatter(object): - def __init__(self): - self.type_map = { - type(None) : self.UNDEF, - bool : self.BOOLEAN, - int : self.INTEGER, - long : self.INTEGER, - float : self.REAL, - lluuid.UUID : self.UUID, - binary : self.BINARY, - str : self.STRING, - unicode : self.STRING, - uri : self.URI, - datetime.datetime : self.DATE, - datetime.date : self.DATE, - list : self.ARRAY, - tuple : self.ARRAY, - types.GeneratorType : self.ARRAY, - dict : self.MAP, - LLSD : self.LLSD - } - - def LLSD(self, v): - return self.generate(v.thing) - def UNDEF(self, v): - return '!' - def BOOLEAN(self, v): - if v: - return 'true' - else: - return 'false' - def INTEGER(self, v): - return "i%s" % v - def REAL(self, v): - return "r%s" % v - def UUID(self, v): - return "u%s" % v - def BINARY(self, v): - return 'b64"' + base64.encodestring(v) + '"' - def STRING(self, v): - if isinstance(v, unicode): - v = v.encode('utf-8') - return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") - def URI(self, v): - return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') - def DATE(self, v): - return 'd"%s"' % format_datestr(v) - def ARRAY(self, v): - return "[%s]" % ','.join([self.generate(item) for item in v]) - def MAP(self, v): - def fix(key): - if isinstance(key, unicode): - return key.encode('utf-8') - return key - return "{%s}" % ','.join(["'%s':%s" % (fix(key).replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) - for key, value in v.items()]) - - def generate(self, something): - t = type(something) - handler = self.type_map.get(t) - if handler: - return handler(something) - else: - try: - return self.ARRAY(iter(something)) - except TypeError: - raise LLSDSerializationError( - "Cannot serialize unknown type: %s (%s)" % (t, something)) - - def format(self, something): - return self.generate(something) - -def format_notation(something): - return LLSDNotationFormatter().format(something) - -def _hex_as_nybble(hex): - if (hex >= '0') and (hex <= '9'): - return ord(hex) - ord('0') - elif (hex >= 'a') and (hex <='f'): - return 10 + ord(hex) - ord('a') - elif (hex >= 'A') and (hex <='F'): - return 10 + ord(hex) - ord('A'); - -class LLSDBinaryParser(object): - def __init__(self): - pass - - def parse(self, buffer, ignore_binary = False): - """ - This is the basic public interface for parsing. - - @param buffer the binary data to parse in an indexable sequence. - @param ignore_binary parser throws away data in llsd binary nodes. - @return returns a python object. - """ - self._buffer = buffer - self._index = 0 - self._keep_binary = not ignore_binary - return self._parse() - - def _parse(self): - cc = self._buffer[self._index] - self._index += 1 - if cc == '{': - return self._parse_map() - elif cc == '[': - return self._parse_array() - elif cc == '!': - return None - elif cc == '0': - return False - elif cc == '1': - return True - elif cc == 'i': - # 'i' = integer - idx = self._index - self._index += 4 - return struct.unpack("!i", self._buffer[idx:idx+4])[0] - elif cc == ('r'): - # 'r' = real number - idx = self._index - self._index += 8 - return struct.unpack("!d", self._buffer[idx:idx+8])[0] - elif cc == 'u': - # 'u' = uuid - idx = self._index - self._index += 16 - return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) - elif cc == 's': - # 's' = string - return self._parse_string() - elif cc in ("'", '"'): - # delimited/escaped string - return self._parse_string_delim(cc) - elif cc == 'l': - # 'l' = uri - return uri(self._parse_string()) - elif cc == ('d'): - # 'd' = date in seconds since epoch - idx = self._index - self._index += 8 - seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] - return datetime.datetime.fromtimestamp(seconds) - elif cc == 'b': - binary = self._parse_string() - if self._keep_binary: - return binary - # *NOTE: maybe have a binary placeholder which has the - # length. - return None - else: - raise LLSDParseError("invalid binary token at byte %d: %d" % ( - self._index - 1, ord(cc))) - - def _parse_map(self): - rv = {} - size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] - self._index += 4 - count = 0 - cc = self._buffer[self._index] - self._index += 1 - key = '' - while (cc != '}') and (count < size): - if cc == 'k': - key = self._parse_string() - elif cc in ("'", '"'): - key = self._parse_string_delim(cc) - else: - raise LLSDParseError("invalid map key at byte %d." % ( - self._index - 1,)) - value = self._parse() - rv[key] = value - count += 1 - cc = self._buffer[self._index] - self._index += 1 - if cc != '}': - raise LLSDParseError("invalid map close token at byte %d." % ( - self._index,)) - return rv - - def _parse_array(self): - rv = [] - size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] - self._index += 4 - count = 0 - cc = self._buffer[self._index] - while (cc != ']') and (count < size): - rv.append(self._parse()) - count += 1 - cc = self._buffer[self._index] - if cc != ']': - raise LLSDParseError("invalid array close token at byte %d." % ( - self._index,)) - self._index += 1 - return rv - - def _parse_string(self): - size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] - self._index += 4 - rv = self._buffer[self._index:self._index+size] - self._index += size - return rv - - def _parse_string_delim(self, delim): - list = [] - found_escape = False - found_hex = False - found_digit = False - byte = 0 - while True: - cc = self._buffer[self._index] - self._index += 1 - if found_escape: - if found_hex: - if found_digit: - found_escape = False - found_hex = False - found_digit = False - byte <<= 4 - byte |= _hex_as_nybble(cc) - list.append(chr(byte)) - byte = 0 - else: - found_digit = True - byte = _hex_as_nybble(cc) - elif cc == 'x': - found_hex = True - else: - if cc == 'a': - list.append('\a') - elif cc == 'b': - list.append('\b') - elif cc == 'f': - list.append('\f') - elif cc == 'n': - list.append('\n') - elif cc == 'r': - list.append('\r') - elif cc == 't': - list.append('\t') - elif cc == 'v': - list.append('\v') - else: - list.append(cc) - found_escape = False - elif cc == '\\': - found_escape = True - elif cc == delim: - break - else: - list.append(cc) - return ''.join(list) - -class LLSDNotationParser(object): - """ Parse LLSD notation: - map: { string:object, string:object } - array: [ object, object, object ] - undef: ! - boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE - integer: i#### - real: r#### - uuid: u#### - string: "g\'day" | 'have a "nice" day' | s(size)"raw data" - uri: l"escaped" - date: d"YYYY-MM-DDTHH:MM:SS.FFZ" - binary: b##"ff3120ab1" | b(size)"raw data" - """ - def __init__(self): - pass - - def parse(self, buffer, ignore_binary = False): - """ - This is the basic public interface for parsing. - - @param buffer the notation string to parse. - @param ignore_binary parser throws away data in llsd binary nodes. - @return returns a python object. - """ - if buffer == "": - return False - - self._buffer = buffer - self._index = 0 - return self._parse() - - def _parse(self): - cc = self._buffer[self._index] - self._index += 1 - if cc == '{': - return self._parse_map() - elif cc == '[': - return self._parse_array() - elif cc == '!': - return None - elif cc == '0': - return False - elif cc == '1': - return True - elif cc in ('F', 'f'): - self._skip_alpha() - return False - elif cc in ('T', 't'): - self._skip_alpha() - return True - elif cc == 'i': - # 'i' = integer - return self._parse_integer() - elif cc == ('r'): - # 'r' = real number - return self._parse_real() - elif cc == 'u': - # 'u' = uuid - return self._parse_uuid() - elif cc in ("'", '"', 's'): - return self._parse_string(cc) - elif cc == 'l': - # 'l' = uri - delim = self._buffer[self._index] - self._index += 1 - val = uri(self._parse_string(delim)) - if len(val) == 0: - return None - return val - elif cc == ('d'): - # 'd' = date in seconds since epoch - return self._parse_date() - elif cc == 'b': - return self._parse_binary() - else: - raise LLSDParseError("invalid token at index %d: %d" % ( - self._index - 1, ord(cc))) - - def _parse_binary(self): - i = self._index - if self._buffer[i:i+2] == '64': - q = self._buffer[i+2] - e = self._buffer.find(q, i+3) - try: - return base64.decodestring(self._buffer[i+3:e]) - finally: - self._index = e + 1 - else: - raise LLSDParseError('random horrible binary format not supported') - - def _parse_map(self): - """ map: { string:object, string:object } """ - rv = {} - cc = self._buffer[self._index] - self._index += 1 - key = '' - found_key = False - while (cc != '}'): - if not found_key: - if cc in ("'", '"', 's'): - key = self._parse_string(cc) - found_key = True - elif cc.isspace() or cc == ',': - cc = self._buffer[self._index] - self._index += 1 - else: - raise LLSDParseError("invalid map key at byte %d." % ( - self._index - 1,)) - elif cc.isspace() or cc == ':': - cc = self._buffer[self._index] - self._index += 1 - continue - else: - self._index += 1 - value = self._parse() - rv[key] = value - found_key = False - cc = self._buffer[self._index] - self._index += 1 - - return rv - - def _parse_array(self): - """ array: [ object, object, object ] """ - rv = [] - cc = self._buffer[self._index] - while (cc != ']'): - if cc.isspace() or cc == ',': - self._index += 1 - cc = self._buffer[self._index] - continue - rv.append(self._parse()) - cc = self._buffer[self._index] - - if cc != ']': - raise LLSDParseError("invalid array close token at index %d." % ( - self._index,)) - self._index += 1 - return rv - - def _parse_uuid(self): - match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) - if not match: - raise LLSDParseError("invalid uuid token at index %d." % self._index) - - (start, end) = match.span() - start += self._index - end += self._index - self._index = end - return lluuid.UUID(self._buffer[start:end]) - - def _skip_alpha(self): - match = re.match(alpha_regex, self._buffer[self._index:]) - if match: - self._index += match.end() - - def _parse_date(self): - delim = self._buffer[self._index] - self._index += 1 - datestr = self._parse_string(delim) - return parse_datestr(datestr) - - def _parse_real(self): - match = re.match(real_regex, self._buffer[self._index:]) - if not match: - raise LLSDParseError("invalid real token at index %d." % self._index) - - (start, end) = match.span() - start += self._index - end += self._index - self._index = end - return float( self._buffer[start:end] ) - - def _parse_integer(self): - match = re.match(int_regex, self._buffer[self._index:]) - if not match: - raise LLSDParseError("invalid integer token at index %d." % self._index) - - (start, end) = match.span() - start += self._index - end += self._index - self._index = end - return int( self._buffer[start:end] ) - - def _parse_string(self, delim): - """ string: "g\'day" | 'have a "nice" day' | s(size)"raw data" """ - rv = "" - - if delim in ("'", '"'): - rv = self._parse_string_delim(delim) - elif delim == 's': - rv = self._parse_string_raw() - else: - raise LLSDParseError("invalid string token at index %d." % self._index) - - return rv - - - def _parse_string_delim(self, delim): - """ string: "g'day 'un" | 'have a "nice" day' """ - list = [] - found_escape = False - found_hex = False - found_digit = False - byte = 0 - while True: - cc = self._buffer[self._index] - self._index += 1 - if found_escape: - if found_hex: - if found_digit: - found_escape = False - found_hex = False - found_digit = False - byte <<= 4 - byte |= _hex_as_nybble(cc) - list.append(chr(byte)) - byte = 0 - else: - found_digit = True - byte = _hex_as_nybble(cc) - elif cc == 'x': - found_hex = True - else: - if cc == 'a': - list.append('\a') - elif cc == 'b': - list.append('\b') - elif cc == 'f': - list.append('\f') - elif cc == 'n': - list.append('\n') - elif cc == 'r': - list.append('\r') - elif cc == 't': - list.append('\t') - elif cc == 'v': - list.append('\v') - else: - list.append(cc) - found_escape = False - elif cc == '\\': - found_escape = True - elif cc == delim: - break - else: - list.append(cc) - return ''.join(list) - - def _parse_string_raw(self): - """ string: s(size)"raw data" """ - # Read the (size) portion. - cc = self._buffer[self._index] - self._index += 1 - if cc != '(': - raise LLSDParseError("invalid string token at index %d." % self._index) - - rparen = self._buffer.find(')', self._index) - if rparen == -1: - raise LLSDParseError("invalid string token at index %d." % self._index) - - size = int(self._buffer[self._index:rparen]) - - self._index = rparen + 1 - delim = self._buffer[self._index] - self._index += 1 - if delim not in ("'", '"'): - raise LLSDParseError("invalid string token at index %d." % self._index) - - rv = self._buffer[self._index:(self._index + size)] - self._index += size - cc = self._buffer[self._index] - self._index += 1 - if cc != delim: - raise LLSDParseError("invalid string token at index %d." % self._index) - - return rv - -def format_binary(something): - return '\n' + _format_binary_recurse(something) - -def _format_binary_recurse(something): - def _format_list(something): - array_builder = [] - array_builder.append('[' + struct.pack('!i', len(something))) - for item in something: - array_builder.append(_format_binary_recurse(item)) - array_builder.append(']') - return ''.join(array_builder) - - if something is None: - return '!' - elif isinstance(something, LLSD): - return _format_binary_recurse(something.thing) - elif isinstance(something, bool): - if something: - return '1' - else: - return '0' - elif isinstance(something, (int, long)): - return 'i' + struct.pack('!i', something) - elif isinstance(something, float): - return 'r' + struct.pack('!d', something) - elif isinstance(something, lluuid.UUID): - return 'u' + something._bits - elif isinstance(something, binary): - return 'b' + struct.pack('!i', len(something)) + something - elif isinstance(something, str): - return 's' + struct.pack('!i', len(something)) + something - elif isinstance(something, unicode): - something = something.encode('utf-8') - return 's' + struct.pack('!i', len(something)) + something - elif isinstance(something, uri): - return 'l' + struct.pack('!i', len(something)) + something - elif isinstance(something, datetime.datetime): - seconds_since_epoch = time.mktime(something.timetuple()) - return 'd' + struct.pack('!d', seconds_since_epoch) - elif isinstance(something, (list, tuple)): - return _format_list(something) - elif isinstance(something, dict): - map_builder = [] - map_builder.append('{' + struct.pack('!i', len(something))) - for key, value in something.items(): - if isinstance(key, unicode): - key = key.encode('utf-8') - map_builder.append('k' + struct.pack('!i', len(key)) + key) - map_builder.append(_format_binary_recurse(value)) - map_builder.append('}') - return ''.join(map_builder) - else: - try: - return _format_list(list(something)) - except TypeError: - raise LLSDSerializationError( - "Cannot serialize unknown type: %s (%s)" % - (type(something), something)) - - -def parse_binary(binary): - if binary.startswith(''): - just_binary = binary.split('\n', 1)[1] - else: - just_binary = binary - return LLSDBinaryParser().parse(just_binary) - -def parse_xml(something): - try: - return to_python(fromstring(something)[0]) - except ElementTreeError, err: - raise LLSDParseError(*err.args) - -def parse_notation(something): - return LLSDNotationParser().parse(something) - -def parse(something): - try: - something = string.lstrip(something) #remove any pre-trailing whitespace - if something.startswith(''): - return parse_binary(something) - # This should be better. - elif something.startswith('<'): - return parse_xml(something) - else: - return parse_notation(something) - except KeyError, e: - raise Exception('LLSD could not be parsed: %s' % (e,)) - -class LLSD(object): - def __init__(self, thing=None): - self.thing = thing - - def __str__(self): - return self.toXML(self.thing) - - parse = staticmethod(parse) - toXML = staticmethod(format_xml) - toPrettyXML = staticmethod(format_pretty_xml) - toBinary = staticmethod(format_binary) - toNotation = staticmethod(format_notation) - - -undef = LLSD(None) - -XML_MIME_TYPE = 'application/llsd+xml' -BINARY_MIME_TYPE = 'application/llsd+binary' - -# register converters for llsd in mulib, if it is available -try: - from mulib import stacked, mu - stacked.NoProducer() # just to exercise stacked - mu.safe_load(None) # just to exercise mu -except: - # mulib not available, don't print an error message since this is normal - pass -else: - mu.add_parser(parse, XML_MIME_TYPE) - mu.add_parser(parse, 'application/llsd+binary') - - def llsd_convert_xml(llsd_stuff, request): - request.write(format_xml(llsd_stuff)) - - def llsd_convert_binary(llsd_stuff, request): - request.write(format_binary(llsd_stuff)) - - for typ in [LLSD, dict, list, tuple, str, int, long, float, bool, unicode, type(None)]: - stacked.add_producer(typ, llsd_convert_xml, XML_MIME_TYPE) - stacked.add_producer(typ, llsd_convert_xml, 'application/xml') - stacked.add_producer(typ, llsd_convert_xml, 'text/xml') - - stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') - - stacked.add_producer(LLSD, llsd_convert_xml, '*/*') - - # in case someone is using the legacy mu.xml wrapper, we need to - # tell mu to produce application/xml or application/llsd+xml - # (based on the accept header) from raw xml. Phoenix 2008-07-21 - stacked.add_producer(mu.xml, mu.produce_raw, XML_MIME_TYPE) - stacked.add_producer(mu.xml, mu.produce_raw, 'application/xml') - - - -# mulib wsgi stuff -# try: -# from mulib import mu, adapters -# -# # try some known attributes from mulib to be ultra-sure we've imported it -# mu.get_current -# adapters.handlers -# except: -# # mulib not available, don't print an error message since this is normal -# pass -# else: -# def llsd_xml_handler(content_type): -# def handle_llsd_xml(env, start_response): -# llsd_stuff, _ = mu.get_current(env) -# result = format_xml(llsd_stuff) -# start_response("200 OK", [('Content-Type', content_type)]) -# env['mu.negotiated_type'] = content_type -# yield result -# return handle_llsd_xml -# -# def llsd_binary_handler(content_type): -# def handle_llsd_binary(env, start_response): -# llsd_stuff, _ = mu.get_current(env) -# result = format_binary(llsd_stuff) -# start_response("200 OK", [('Content-Type', content_type)]) -# env['mu.negotiated_type'] = content_type -# yield result -# return handle_llsd_binary -# -# adapters.DEFAULT_PARSERS[XML_MIME_TYPE] = parse - -# for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: -# for content_type in (XML_MIME_TYPE, 'application/xml'): -# adapters.handlers.set_handler(typ, llsd_xml_handler(content_type), content_type) -# -# adapters.handlers.set_handler(typ, llsd_binary_handler(BINARY_MIME_TYPE), BINARY_MIME_TYPE) -# -# adapters.handlers.set_handler(LLSD, llsd_xml_handler(XML_MIME_TYPE), '*/*') diff --git a/indra/viewer_components/manager/util/fastest_elementtree.py b/indra/viewer_components/manager/util/fastest_elementtree.py new file mode 100755 index 0000000000..4fcf662dd9 --- /dev/null +++ b/indra/viewer_components/manager/util/fastest_elementtree.py @@ -0,0 +1,64 @@ +"""\ +@file fastest_elementtree.py +@brief Concealing some gnarly import logic in here. This should export the interface of elementtree. + +$LicenseInfo:firstyear=2008&license=mit$ + +Copyright (c) 2008-2009, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +""" + +# The parsing exception raised by the underlying library depends +# on the ElementTree implementation we're using, so we provide an +# alias here. +# +# Use ElementTreeError as the exception type for catching parsing +# errors. + + +# Using cElementTree might cause some unforeseen problems, so here's a +# convenient off switch. +use_celementree = True + +try: + if not use_celementree: + raise ImportError() + # Python 2.3 and 2.4. + from cElementTree import * + ElementTreeError = SyntaxError +except ImportError: + try: + if not use_celementree: + raise ImportError() + # Python 2.5 and above. + from xml.etree.cElementTree import * + ElementTreeError = SyntaxError + except ImportError: + # Pure Python code. + try: + # Python 2.3 and 2.4. + from elementtree.ElementTree import * + except ImportError: + # Python 2.5 and above. + from xml.etree.ElementTree import * + + # The pure Python ElementTree module uses Expat for parsing. + from xml.parsers.expat import ExpatError as ElementTreeError -- cgit v1.2.3 From 4428ee77c29fb2e0af50bf6d650abfeb33329b3f Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 21 Jul 2016 10:18:58 -0700 Subject: SL-323: make changes to include llbase as an autobuild pkg, undelete files that will be deleted with MAINT-6585 and no need to copy local files in viewer-manifest. --- indra/viewer_components/manager/base/llsd.py | 1052 -------------------- indra/viewer_components/manager/base/lluuid.py | 319 ------ .../manager/util/fastest_elementtree.py | 64 -- 3 files changed, 1435 deletions(-) delete mode 100755 indra/viewer_components/manager/base/llsd.py delete mode 100755 indra/viewer_components/manager/base/lluuid.py delete mode 100755 indra/viewer_components/manager/util/fastest_elementtree.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/base/llsd.py b/indra/viewer_components/manager/base/llsd.py deleted file mode 100755 index 4527b115f9..0000000000 --- a/indra/viewer_components/manager/base/llsd.py +++ /dev/null @@ -1,1052 +0,0 @@ -"""\ -@file llsd.py -@brief Types as well as parsing and formatting functions for handling LLSD. - -$LicenseInfo:firstyear=2006&license=mit$ - -Copyright (c) 2006-2009, Linden Research, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -$/LicenseInfo$ -""" - -import datetime -import base64 -import string -import struct -import time -import types -import re - -from indra.util.fastest_elementtree import ElementTreeError, fromstring -from indra.base import lluuid - -# cllsd.c in server/server-1.25 has memory leaks, -# so disabling cllsd for now -#try: -# import cllsd -#except ImportError: -# cllsd = None -cllsd = None - -int_regex = re.compile(r"[-+]?\d+") -real_regex = re.compile(r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") -alpha_regex = re.compile(r"[a-zA-Z]+") -date_regex = re.compile(r"(?P\d{4})-(?P\d{2})-(?P\d{2})T" - r"(?P\d{2}):(?P\d{2}):(?P\d{2})" - r"(?P(\.\d+)?)Z") -#date: d"YYYY-MM-DDTHH:MM:SS.FFFFFFZ" - -class LLSDParseError(Exception): - pass - -class LLSDSerializationError(TypeError): - pass - - -class binary(str): - pass - -class uri(str): - pass - - -BOOL_TRUE = ('1', '1.0', 'true') -BOOL_FALSE = ('0', '0.0', 'false', '') - - -def format_datestr(v): - """ Formats a datetime or date object into the string format shared by xml and notation serializations.""" - if hasattr(v, 'microsecond'): - return v.isoformat() + 'Z' - else: - return v.strftime('%Y-%m-%dT%H:%M:%SZ') - -def parse_datestr(datestr): - """Parses a datetime object from the string format shared by xml and notation serializations.""" - if datestr == "": - return datetime.datetime(1970, 1, 1) - - match = re.match(date_regex, datestr) - if not match: - raise LLSDParseError("invalid date string '%s'." % datestr) - - year = int(match.group('year')) - month = int(match.group('month')) - day = int(match.group('day')) - hour = int(match.group('hour')) - minute = int(match.group('minute')) - second = int(match.group('second')) - seconds_float = match.group('second_float') - microsecond = 0 - if seconds_float: - microsecond = int(float('0' + seconds_float) * 1e6) - return datetime.datetime(year, month, day, hour, minute, second, microsecond) - - -def bool_to_python(node): - val = node.text or '' - if val in BOOL_TRUE: - return True - else: - return False - -def int_to_python(node): - val = node.text or '' - if not val.strip(): - return 0 - return int(val) - -def real_to_python(node): - val = node.text or '' - if not val.strip(): - return 0.0 - return float(val) - -def uuid_to_python(node): - return lluuid.UUID(node.text) - -def str_to_python(node): - return node.text or '' - -def bin_to_python(node): - return binary(base64.decodestring(node.text or '')) - -def date_to_python(node): - val = node.text or '' - if not val: - val = "1970-01-01T00:00:00Z" - return parse_datestr(val) - - -def uri_to_python(node): - val = node.text or '' - if not val: - return None - return uri(val) - -def map_to_python(node): - result = {} - for index in range(len(node))[::2]: - result[node[index].text] = to_python(node[index+1]) - return result - -def array_to_python(node): - return [to_python(child) for child in node] - - -NODE_HANDLERS = dict( - undef=lambda x: None, - boolean=bool_to_python, - integer=int_to_python, - real=real_to_python, - uuid=uuid_to_python, - string=str_to_python, - binary=bin_to_python, - date=date_to_python, - uri=uri_to_python, - map=map_to_python, - array=array_to_python, - ) - -def to_python(node): - return NODE_HANDLERS[node.tag](node) - -class Nothing(object): - pass - - -class LLSDXMLFormatter(object): - def __init__(self): - self.type_map = { - type(None) : self.UNDEF, - bool : self.BOOLEAN, - int : self.INTEGER, - long : self.INTEGER, - float : self.REAL, - lluuid.UUID : self.UUID, - binary : self.BINARY, - str : self.STRING, - unicode : self.STRING, - uri : self.URI, - datetime.datetime : self.DATE, - datetime.date : self.DATE, - list : self.ARRAY, - tuple : self.ARRAY, - types.GeneratorType : self.ARRAY, - dict : self.MAP, - LLSD : self.LLSD - } - - def elt(self, name, contents=None): - if(contents is None or contents is ''): - return "<%s />" % (name,) - else: - if type(contents) is unicode: - contents = contents.encode('utf-8') - return "<%s>%s" % (name, contents, name) - - def xml_esc(self, v): - if type(v) is unicode: - v = v.encode('utf-8') - return v.replace('&', '&').replace('<', '<').replace('>', '>') - - def LLSD(self, v): - return self.generate(v.thing) - def UNDEF(self, v): - return self.elt('undef') - def BOOLEAN(self, v): - if v: - return self.elt('boolean', 'true') - else: - return self.elt('boolean', 'false') - def INTEGER(self, v): - return self.elt('integer', v) - def REAL(self, v): - return self.elt('real', v) - def UUID(self, v): - if(v.isNull()): - return self.elt('uuid') - else: - return self.elt('uuid', v) - def BINARY(self, v): - return self.elt('binary', base64.encodestring(v)) - def STRING(self, v): - return self.elt('string', self.xml_esc(v)) - def URI(self, v): - return self.elt('uri', self.xml_esc(str(v))) - def DATE(self, v): - return self.elt('date', format_datestr(v)) - def ARRAY(self, v): - return self.elt('array', ''.join([self.generate(item) for item in v])) - def MAP(self, v): - return self.elt( - 'map', - ''.join(["%s%s" % (self.elt('key', self.xml_esc(str(key))), self.generate(value)) - for key, value in v.items()])) - - typeof = type - def generate(self, something): - t = self.typeof(something) - if self.type_map.has_key(t): - return self.type_map[t](something) - else: - raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( - t, something)) - - def _format(self, something): - return '' + self.elt("llsd", self.generate(something)) - - def format(self, something): - if cllsd: - return cllsd.llsd_to_xml(something) - return self._format(something) - -_g_xml_formatter = None -def format_xml(something): - global _g_xml_formatter - if _g_xml_formatter is None: - _g_xml_formatter = LLSDXMLFormatter() - return _g_xml_formatter.format(something) - -class LLSDXMLPrettyFormatter(LLSDXMLFormatter): - def __init__(self, indent_atom = None): - # Call the super class constructor so that we have the type map - super(LLSDXMLPrettyFormatter, self).__init__() - - # Override the type map to use our specialized formatters to - # emit the pretty output. - self.type_map[list] = self.PRETTY_ARRAY - self.type_map[tuple] = self.PRETTY_ARRAY - self.type_map[types.GeneratorType] = self.PRETTY_ARRAY, - self.type_map[dict] = self.PRETTY_MAP - - # Private data used for indentation. - self._indent_level = 1 - if indent_atom is None: - self._indent_atom = ' ' - else: - self._indent_atom = indent_atom - - def _indent(self): - "Return an indentation based on the atom and indentation level." - return self._indent_atom * self._indent_level - - def PRETTY_ARRAY(self, v): - rv = [] - rv.append('\n') - self._indent_level = self._indent_level + 1 - rv.extend(["%s%s\n" % - (self._indent(), - self.generate(item)) - for item in v]) - self._indent_level = self._indent_level - 1 - rv.append(self._indent()) - rv.append('') - return ''.join(rv) - - def PRETTY_MAP(self, v): - rv = [] - rv.append('\n') - self._indent_level = self._indent_level + 1 - keys = v.keys() - keys.sort() - rv.extend(["%s%s\n%s%s\n" % - (self._indent(), - self.elt('key', key), - self._indent(), - self.generate(v[key])) - for key in keys]) - self._indent_level = self._indent_level - 1 - rv.append(self._indent()) - rv.append('') - return ''.join(rv) - - def format(self, something): - data = [] - data.append('\n') - data.append(self.generate(something)) - data.append('\n') - return '\n'.join(data) - -def format_pretty_xml(something): - """@brief Serialize a python object as 'pretty' llsd xml. - - The output conforms to the LLSD DTD, unlike the output from the - standard python xml.dom DOM::toprettyxml() method which does not - preserve significant whitespace. - This function is not necessarily suited for serializing very large - objects. It is not optimized by the cllsd module, and sorts on - dict (llsd map) keys alphabetically to ease human reading. - """ - return LLSDXMLPrettyFormatter().format(something) - -class LLSDNotationFormatter(object): - def __init__(self): - self.type_map = { - type(None) : self.UNDEF, - bool : self.BOOLEAN, - int : self.INTEGER, - long : self.INTEGER, - float : self.REAL, - lluuid.UUID : self.UUID, - binary : self.BINARY, - str : self.STRING, - unicode : self.STRING, - uri : self.URI, - datetime.datetime : self.DATE, - datetime.date : self.DATE, - list : self.ARRAY, - tuple : self.ARRAY, - types.GeneratorType : self.ARRAY, - dict : self.MAP, - LLSD : self.LLSD - } - - def LLSD(self, v): - return self.generate(v.thing) - def UNDEF(self, v): - return '!' - def BOOLEAN(self, v): - if v: - return 'true' - else: - return 'false' - def INTEGER(self, v): - return "i%s" % v - def REAL(self, v): - return "r%s" % v - def UUID(self, v): - return "u%s" % v - def BINARY(self, v): - return 'b64"' + base64.encodestring(v) + '"' - def STRING(self, v): - if isinstance(v, unicode): - v = v.encode('utf-8') - return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") - def URI(self, v): - return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') - def DATE(self, v): - return 'd"%s"' % format_datestr(v) - def ARRAY(self, v): - return "[%s]" % ','.join([self.generate(item) for item in v]) - def MAP(self, v): - def fix(key): - if isinstance(key, unicode): - return key.encode('utf-8') - return key - return "{%s}" % ','.join(["'%s':%s" % (fix(key).replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) - for key, value in v.items()]) - - def generate(self, something): - t = type(something) - handler = self.type_map.get(t) - if handler: - return handler(something) - else: - try: - return self.ARRAY(iter(something)) - except TypeError: - raise LLSDSerializationError( - "Cannot serialize unknown type: %s (%s)" % (t, something)) - - def format(self, something): - return self.generate(something) - -def format_notation(something): - return LLSDNotationFormatter().format(something) - -def _hex_as_nybble(hex): - if (hex >= '0') and (hex <= '9'): - return ord(hex) - ord('0') - elif (hex >= 'a') and (hex <='f'): - return 10 + ord(hex) - ord('a') - elif (hex >= 'A') and (hex <='F'): - return 10 + ord(hex) - ord('A'); - -class LLSDBinaryParser(object): - def __init__(self): - pass - - def parse(self, buffer, ignore_binary = False): - """ - This is the basic public interface for parsing. - - @param buffer the binary data to parse in an indexable sequence. - @param ignore_binary parser throws away data in llsd binary nodes. - @return returns a python object. - """ - self._buffer = buffer - self._index = 0 - self._keep_binary = not ignore_binary - return self._parse() - - def _parse(self): - cc = self._buffer[self._index] - self._index += 1 - if cc == '{': - return self._parse_map() - elif cc == '[': - return self._parse_array() - elif cc == '!': - return None - elif cc == '0': - return False - elif cc == '1': - return True - elif cc == 'i': - # 'i' = integer - idx = self._index - self._index += 4 - return struct.unpack("!i", self._buffer[idx:idx+4])[0] - elif cc == ('r'): - # 'r' = real number - idx = self._index - self._index += 8 - return struct.unpack("!d", self._buffer[idx:idx+8])[0] - elif cc == 'u': - # 'u' = uuid - idx = self._index - self._index += 16 - return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) - elif cc == 's': - # 's' = string - return self._parse_string() - elif cc in ("'", '"'): - # delimited/escaped string - return self._parse_string_delim(cc) - elif cc == 'l': - # 'l' = uri - return uri(self._parse_string()) - elif cc == ('d'): - # 'd' = date in seconds since epoch - idx = self._index - self._index += 8 - seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] - return datetime.datetime.fromtimestamp(seconds) - elif cc == 'b': - binary = self._parse_string() - if self._keep_binary: - return binary - # *NOTE: maybe have a binary placeholder which has the - # length. - return None - else: - raise LLSDParseError("invalid binary token at byte %d: %d" % ( - self._index - 1, ord(cc))) - - def _parse_map(self): - rv = {} - size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] - self._index += 4 - count = 0 - cc = self._buffer[self._index] - self._index += 1 - key = '' - while (cc != '}') and (count < size): - if cc == 'k': - key = self._parse_string() - elif cc in ("'", '"'): - key = self._parse_string_delim(cc) - else: - raise LLSDParseError("invalid map key at byte %d." % ( - self._index - 1,)) - value = self._parse() - rv[key] = value - count += 1 - cc = self._buffer[self._index] - self._index += 1 - if cc != '}': - raise LLSDParseError("invalid map close token at byte %d." % ( - self._index,)) - return rv - - def _parse_array(self): - rv = [] - size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] - self._index += 4 - count = 0 - cc = self._buffer[self._index] - while (cc != ']') and (count < size): - rv.append(self._parse()) - count += 1 - cc = self._buffer[self._index] - if cc != ']': - raise LLSDParseError("invalid array close token at byte %d." % ( - self._index,)) - self._index += 1 - return rv - - def _parse_string(self): - size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] - self._index += 4 - rv = self._buffer[self._index:self._index+size] - self._index += size - return rv - - def _parse_string_delim(self, delim): - list = [] - found_escape = False - found_hex = False - found_digit = False - byte = 0 - while True: - cc = self._buffer[self._index] - self._index += 1 - if found_escape: - if found_hex: - if found_digit: - found_escape = False - found_hex = False - found_digit = False - byte <<= 4 - byte |= _hex_as_nybble(cc) - list.append(chr(byte)) - byte = 0 - else: - found_digit = True - byte = _hex_as_nybble(cc) - elif cc == 'x': - found_hex = True - else: - if cc == 'a': - list.append('\a') - elif cc == 'b': - list.append('\b') - elif cc == 'f': - list.append('\f') - elif cc == 'n': - list.append('\n') - elif cc == 'r': - list.append('\r') - elif cc == 't': - list.append('\t') - elif cc == 'v': - list.append('\v') - else: - list.append(cc) - found_escape = False - elif cc == '\\': - found_escape = True - elif cc == delim: - break - else: - list.append(cc) - return ''.join(list) - -class LLSDNotationParser(object): - """ Parse LLSD notation: - map: { string:object, string:object } - array: [ object, object, object ] - undef: ! - boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE - integer: i#### - real: r#### - uuid: u#### - string: "g\'day" | 'have a "nice" day' | s(size)"raw data" - uri: l"escaped" - date: d"YYYY-MM-DDTHH:MM:SS.FFZ" - binary: b##"ff3120ab1" | b(size)"raw data" - """ - def __init__(self): - pass - - def parse(self, buffer, ignore_binary = False): - """ - This is the basic public interface for parsing. - - @param buffer the notation string to parse. - @param ignore_binary parser throws away data in llsd binary nodes. - @return returns a python object. - """ - if buffer == "": - return False - - self._buffer = buffer - self._index = 0 - return self._parse() - - def _parse(self): - cc = self._buffer[self._index] - self._index += 1 - if cc == '{': - return self._parse_map() - elif cc == '[': - return self._parse_array() - elif cc == '!': - return None - elif cc == '0': - return False - elif cc == '1': - return True - elif cc in ('F', 'f'): - self._skip_alpha() - return False - elif cc in ('T', 't'): - self._skip_alpha() - return True - elif cc == 'i': - # 'i' = integer - return self._parse_integer() - elif cc == ('r'): - # 'r' = real number - return self._parse_real() - elif cc == 'u': - # 'u' = uuid - return self._parse_uuid() - elif cc in ("'", '"', 's'): - return self._parse_string(cc) - elif cc == 'l': - # 'l' = uri - delim = self._buffer[self._index] - self._index += 1 - val = uri(self._parse_string(delim)) - if len(val) == 0: - return None - return val - elif cc == ('d'): - # 'd' = date in seconds since epoch - return self._parse_date() - elif cc == 'b': - return self._parse_binary() - else: - raise LLSDParseError("invalid token at index %d: %d" % ( - self._index - 1, ord(cc))) - - def _parse_binary(self): - i = self._index - if self._buffer[i:i+2] == '64': - q = self._buffer[i+2] - e = self._buffer.find(q, i+3) - try: - return base64.decodestring(self._buffer[i+3:e]) - finally: - self._index = e + 1 - else: - raise LLSDParseError('random horrible binary format not supported') - - def _parse_map(self): - """ map: { string:object, string:object } """ - rv = {} - cc = self._buffer[self._index] - self._index += 1 - key = '' - found_key = False - while (cc != '}'): - if not found_key: - if cc in ("'", '"', 's'): - key = self._parse_string(cc) - found_key = True - elif cc.isspace() or cc == ',': - cc = self._buffer[self._index] - self._index += 1 - else: - raise LLSDParseError("invalid map key at byte %d." % ( - self._index - 1,)) - elif cc.isspace() or cc == ':': - cc = self._buffer[self._index] - self._index += 1 - continue - else: - self._index += 1 - value = self._parse() - rv[key] = value - found_key = False - cc = self._buffer[self._index] - self._index += 1 - - return rv - - def _parse_array(self): - """ array: [ object, object, object ] """ - rv = [] - cc = self._buffer[self._index] - while (cc != ']'): - if cc.isspace() or cc == ',': - self._index += 1 - cc = self._buffer[self._index] - continue - rv.append(self._parse()) - cc = self._buffer[self._index] - - if cc != ']': - raise LLSDParseError("invalid array close token at index %d." % ( - self._index,)) - self._index += 1 - return rv - - def _parse_uuid(self): - match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) - if not match: - raise LLSDParseError("invalid uuid token at index %d." % self._index) - - (start, end) = match.span() - start += self._index - end += self._index - self._index = end - return lluuid.UUID(self._buffer[start:end]) - - def _skip_alpha(self): - match = re.match(alpha_regex, self._buffer[self._index:]) - if match: - self._index += match.end() - - def _parse_date(self): - delim = self._buffer[self._index] - self._index += 1 - datestr = self._parse_string(delim) - return parse_datestr(datestr) - - def _parse_real(self): - match = re.match(real_regex, self._buffer[self._index:]) - if not match: - raise LLSDParseError("invalid real token at index %d." % self._index) - - (start, end) = match.span() - start += self._index - end += self._index - self._index = end - return float( self._buffer[start:end] ) - - def _parse_integer(self): - match = re.match(int_regex, self._buffer[self._index:]) - if not match: - raise LLSDParseError("invalid integer token at index %d." % self._index) - - (start, end) = match.span() - start += self._index - end += self._index - self._index = end - return int( self._buffer[start:end] ) - - def _parse_string(self, delim): - """ string: "g\'day" | 'have a "nice" day' | s(size)"raw data" """ - rv = "" - - if delim in ("'", '"'): - rv = self._parse_string_delim(delim) - elif delim == 's': - rv = self._parse_string_raw() - else: - raise LLSDParseError("invalid string token at index %d." % self._index) - - return rv - - - def _parse_string_delim(self, delim): - """ string: "g'day 'un" | 'have a "nice" day' """ - list = [] - found_escape = False - found_hex = False - found_digit = False - byte = 0 - while True: - cc = self._buffer[self._index] - self._index += 1 - if found_escape: - if found_hex: - if found_digit: - found_escape = False - found_hex = False - found_digit = False - byte <<= 4 - byte |= _hex_as_nybble(cc) - list.append(chr(byte)) - byte = 0 - else: - found_digit = True - byte = _hex_as_nybble(cc) - elif cc == 'x': - found_hex = True - else: - if cc == 'a': - list.append('\a') - elif cc == 'b': - list.append('\b') - elif cc == 'f': - list.append('\f') - elif cc == 'n': - list.append('\n') - elif cc == 'r': - list.append('\r') - elif cc == 't': - list.append('\t') - elif cc == 'v': - list.append('\v') - else: - list.append(cc) - found_escape = False - elif cc == '\\': - found_escape = True - elif cc == delim: - break - else: - list.append(cc) - return ''.join(list) - - def _parse_string_raw(self): - """ string: s(size)"raw data" """ - # Read the (size) portion. - cc = self._buffer[self._index] - self._index += 1 - if cc != '(': - raise LLSDParseError("invalid string token at index %d." % self._index) - - rparen = self._buffer.find(')', self._index) - if rparen == -1: - raise LLSDParseError("invalid string token at index %d." % self._index) - - size = int(self._buffer[self._index:rparen]) - - self._index = rparen + 1 - delim = self._buffer[self._index] - self._index += 1 - if delim not in ("'", '"'): - raise LLSDParseError("invalid string token at index %d." % self._index) - - rv = self._buffer[self._index:(self._index + size)] - self._index += size - cc = self._buffer[self._index] - self._index += 1 - if cc != delim: - raise LLSDParseError("invalid string token at index %d." % self._index) - - return rv - -def format_binary(something): - return '\n' + _format_binary_recurse(something) - -def _format_binary_recurse(something): - def _format_list(something): - array_builder = [] - array_builder.append('[' + struct.pack('!i', len(something))) - for item in something: - array_builder.append(_format_binary_recurse(item)) - array_builder.append(']') - return ''.join(array_builder) - - if something is None: - return '!' - elif isinstance(something, LLSD): - return _format_binary_recurse(something.thing) - elif isinstance(something, bool): - if something: - return '1' - else: - return '0' - elif isinstance(something, (int, long)): - return 'i' + struct.pack('!i', something) - elif isinstance(something, float): - return 'r' + struct.pack('!d', something) - elif isinstance(something, lluuid.UUID): - return 'u' + something._bits - elif isinstance(something, binary): - return 'b' + struct.pack('!i', len(something)) + something - elif isinstance(something, str): - return 's' + struct.pack('!i', len(something)) + something - elif isinstance(something, unicode): - something = something.encode('utf-8') - return 's' + struct.pack('!i', len(something)) + something - elif isinstance(something, uri): - return 'l' + struct.pack('!i', len(something)) + something - elif isinstance(something, datetime.datetime): - seconds_since_epoch = time.mktime(something.timetuple()) - return 'd' + struct.pack('!d', seconds_since_epoch) - elif isinstance(something, (list, tuple)): - return _format_list(something) - elif isinstance(something, dict): - map_builder = [] - map_builder.append('{' + struct.pack('!i', len(something))) - for key, value in something.items(): - if isinstance(key, unicode): - key = key.encode('utf-8') - map_builder.append('k' + struct.pack('!i', len(key)) + key) - map_builder.append(_format_binary_recurse(value)) - map_builder.append('}') - return ''.join(map_builder) - else: - try: - return _format_list(list(something)) - except TypeError: - raise LLSDSerializationError( - "Cannot serialize unknown type: %s (%s)" % - (type(something), something)) - - -def parse_binary(binary): - if binary.startswith(''): - just_binary = binary.split('\n', 1)[1] - else: - just_binary = binary - return LLSDBinaryParser().parse(just_binary) - -def parse_xml(something): - try: - return to_python(fromstring(something)[0]) - except ElementTreeError, err: - raise LLSDParseError(*err.args) - -def parse_notation(something): - return LLSDNotationParser().parse(something) - -def parse(something): - try: - something = string.lstrip(something) #remove any pre-trailing whitespace - if something.startswith(''): - return parse_binary(something) - # This should be better. - elif something.startswith('<'): - return parse_xml(something) - else: - return parse_notation(something) - except KeyError, e: - raise Exception('LLSD could not be parsed: %s' % (e,)) - -class LLSD(object): - def __init__(self, thing=None): - self.thing = thing - - def __str__(self): - return self.toXML(self.thing) - - parse = staticmethod(parse) - toXML = staticmethod(format_xml) - toPrettyXML = staticmethod(format_pretty_xml) - toBinary = staticmethod(format_binary) - toNotation = staticmethod(format_notation) - - -undef = LLSD(None) - -XML_MIME_TYPE = 'application/llsd+xml' -BINARY_MIME_TYPE = 'application/llsd+binary' - -# register converters for llsd in mulib, if it is available -try: - from mulib import stacked, mu - stacked.NoProducer() # just to exercise stacked - mu.safe_load(None) # just to exercise mu -except: - # mulib not available, don't print an error message since this is normal - pass -else: - mu.add_parser(parse, XML_MIME_TYPE) - mu.add_parser(parse, 'application/llsd+binary') - - def llsd_convert_xml(llsd_stuff, request): - request.write(format_xml(llsd_stuff)) - - def llsd_convert_binary(llsd_stuff, request): - request.write(format_binary(llsd_stuff)) - - for typ in [LLSD, dict, list, tuple, str, int, long, float, bool, unicode, type(None)]: - stacked.add_producer(typ, llsd_convert_xml, XML_MIME_TYPE) - stacked.add_producer(typ, llsd_convert_xml, 'application/xml') - stacked.add_producer(typ, llsd_convert_xml, 'text/xml') - - stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') - - stacked.add_producer(LLSD, llsd_convert_xml, '*/*') - - # in case someone is using the legacy mu.xml wrapper, we need to - # tell mu to produce application/xml or application/llsd+xml - # (based on the accept header) from raw xml. Phoenix 2008-07-21 - stacked.add_producer(mu.xml, mu.produce_raw, XML_MIME_TYPE) - stacked.add_producer(mu.xml, mu.produce_raw, 'application/xml') - - - -# mulib wsgi stuff -# try: -# from mulib import mu, adapters -# -# # try some known attributes from mulib to be ultra-sure we've imported it -# mu.get_current -# adapters.handlers -# except: -# # mulib not available, don't print an error message since this is normal -# pass -# else: -# def llsd_xml_handler(content_type): -# def handle_llsd_xml(env, start_response): -# llsd_stuff, _ = mu.get_current(env) -# result = format_xml(llsd_stuff) -# start_response("200 OK", [('Content-Type', content_type)]) -# env['mu.negotiated_type'] = content_type -# yield result -# return handle_llsd_xml -# -# def llsd_binary_handler(content_type): -# def handle_llsd_binary(env, start_response): -# llsd_stuff, _ = mu.get_current(env) -# result = format_binary(llsd_stuff) -# start_response("200 OK", [('Content-Type', content_type)]) -# env['mu.negotiated_type'] = content_type -# yield result -# return handle_llsd_binary -# -# adapters.DEFAULT_PARSERS[XML_MIME_TYPE] = parse - -# for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: -# for content_type in (XML_MIME_TYPE, 'application/xml'): -# adapters.handlers.set_handler(typ, llsd_xml_handler(content_type), content_type) -# -# adapters.handlers.set_handler(typ, llsd_binary_handler(BINARY_MIME_TYPE), BINARY_MIME_TYPE) -# -# adapters.handlers.set_handler(LLSD, llsd_xml_handler(XML_MIME_TYPE), '*/*') diff --git a/indra/viewer_components/manager/base/lluuid.py b/indra/viewer_components/manager/base/lluuid.py deleted file mode 100755 index 7413ffe10d..0000000000 --- a/indra/viewer_components/manager/base/lluuid.py +++ /dev/null @@ -1,319 +0,0 @@ -"""\ -@file lluuid.py -@brief UUID parser/generator. - -$LicenseInfo:firstyear=2004&license=mit$ - -Copyright (c) 2004-2009, Linden Research, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -$/LicenseInfo$ -""" - -import random, socket, string, time, re -import uuid -try: - # Python 2.6 - from hashlib import md5 -except ImportError: - # Python 2.5 and earlier - from md5 import new as md5 - -def _int2binstr(i,l): - s='' - for a in range(l): - s=chr(i&0xFF)+s - i>>=8 - return s - -def _binstr2int(s): - i = long(0) - for c in s: - i = (i<<8) + ord(c) - return i - -class UUID(object): - """ - A class which represents a 16 byte integer. Stored as a 16 byte 8 - bit character string. - - The string version is to be of the form: - AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex) - where A=network address, B=timestamp, C=random. - """ - - NULL_STR = "00000000-0000-0000-0000-000000000000" - - # the UUIDREGEX_STRING is helpful for parsing UUID's in text - hex_wildcard = r"[0-9a-fA-F]" - word = hex_wildcard + r"{4,4}-" - long_word = hex_wildcard + r"{8,8}-" - very_long_word = hex_wildcard + r"{12,12}" - UUID_REGEX_STRING = long_word + word + word + word + very_long_word - uuid_regex = re.compile(UUID_REGEX_STRING) - - rand = random.Random() - ip = '' - try: - ip = socket.gethostbyname(socket.gethostname()) - except(socket.gaierror, socket.error): - # no ip address, so just default to somewhere in 10.x.x.x - ip = '10' - for i in range(3): - ip += '.' + str(rand.randrange(1,254)) - hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) - lastid = '' - - def __init__(self, possible_uuid=None): - """ - Initialize to first valid UUID in argument (if a string), - or to null UUID if none found or argument is not supplied. - - If the argument is a UUID, the constructed object will be a copy of it. - """ - self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - if possible_uuid is None: - return - - if isinstance(possible_uuid, type(self)): - self.set(possible_uuid) - return - - uuid_match = UUID.uuid_regex.search(possible_uuid) - if uuid_match: - uuid_string = uuid_match.group() - s = string.replace(uuid_string, '-', '') - self._bits = _int2binstr(string.atol(s[:8],16),4) + \ - _int2binstr(string.atol(s[8:16],16),4) + \ - _int2binstr(string.atol(s[16:24],16),4) + \ - _int2binstr(string.atol(s[24:],16),4) - - def __len__(self): - """ - Used by the len() builtin. - """ - return 36 - - def __nonzero__(self): - return self._bits != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - - def __str__(self): - uuid_string = self.toString() - return uuid_string - - __repr__ = __str__ - - def __getitem__(self, index): - return str(self)[index] - - def __eq__(self, other): - if isinstance(other, (str, unicode)): - return other == str(self) - return self._bits == getattr(other, '_bits', '') - - def __ne__(self, other): - return not self.__eq__(other) - - def __le__(self, other): - return self._bits <= other._bits - - def __ge__(self, other): - return self._bits >= other._bits - - def __lt__(self, other): - return self._bits < other._bits - - def __gt__(self, other): - return self._bits > other._bits - - def __hash__(self): - return hash(self._bits) - - def set(self, uuid): - self._bits = uuid._bits - - def setFromString(self, uuid_string): - """ - Given a string version of a uuid, set self bits - appropriately. Returns self. - """ - s = string.replace(uuid_string, '-', '') - self._bits = _int2binstr(string.atol(s[:8],16),4) + \ - _int2binstr(string.atol(s[8:16],16),4) + \ - _int2binstr(string.atol(s[16:24],16),4) + \ - _int2binstr(string.atol(s[24:],16),4) - return self - - def setFromMemoryDump(self, gdb_string): - """ - We expect to get gdb_string as four hex units. eg: - 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2 - Which will be translated to: - db547d14-1b3f4bc3-9b984f71-d22f890a - Returns self. - """ - s = string.replace(gdb_string, '0x', '') - s = string.replace(s, ' ', '') - t = '' - for i in range(8,40,8): - for j in range(0,8,2): - t = t + s[i-j-2:i-j] - self.setFromString(t) - - def toString(self): - """ - Return as a string matching the LL standard - AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex) - where A=network address, B=timestamp, C=random. - """ - return uuid_bits_to_string(self._bits) - - def getAsString(self): - """ - Return a different string representation of the form - AAAAAAAA-AAAABBBB-BBBBBBBB-BBCCCCCC (a 128-bit number in hex) - where A=network address, B=timestamp, C=random. - """ - i1 = _binstr2int(self._bits[0:4]) - i2 = _binstr2int(self._bits[4:8]) - i3 = _binstr2int(self._bits[8:12]) - i4 = _binstr2int(self._bits[12:16]) - return '%08lx-%08lx-%08lx-%08lx' % (i1,i2,i3,i4) - - def generate(self): - """ - Generate a new uuid. This algorithm is slightly different - from c++ implementation for portability reasons. - Returns self. - """ - m = md5() - m.update(uuid.uuid1().bytes) - self._bits = m.digest() - return self - - def isNull(self): - """ - Returns 1 if the uuid is null - ie, equal to default uuid. - """ - return (self._bits == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") - - def xor(self, rhs): - """ - xors self with rhs. - """ - v1 = _binstr2int(self._bits[0:4]) ^ _binstr2int(rhs._bits[0:4]) - v2 = _binstr2int(self._bits[4:8]) ^ _binstr2int(rhs._bits[4:8]) - v3 = _binstr2int(self._bits[8:12]) ^ _binstr2int(rhs._bits[8:12]) - v4 = _binstr2int(self._bits[12:16]) ^ _binstr2int(rhs._bits[12:16]) - self._bits = _int2binstr(v1,4) + \ - _int2binstr(v2,4) + \ - _int2binstr(v3,4) + \ - _int2binstr(v4,4) - - -# module-level null constant -NULL = UUID() - -def printTranslatedMemory(four_hex_uints): - """ - We expect to get the string as four hex units. eg: - 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2 - Which will be translated to: - db547d14-1b3f4bc3-9b984f71-d22f890a - """ - uuid = UUID() - uuid.setFromMemoryDump(four_hex_uints) - print uuid.toString() - -def isUUID(id_str): - """ - This function returns: - - 1 if the string passed is a UUID - - 0 is the string passed is not a UUID - - None if it neither of the if's below is satisfied - """ - if not id_str or len(id_str) < 5 or len(id_str) > 36: - return 0 - - if isinstance(id_str, UUID) or UUID.uuid_regex.match(id_str): - return 1 - - return None - -def isPossiblyID(id_str): - """ - This function returns 1 if the string passed has some uuid-like - characteristics. Otherwise returns 0. - """ - - is_uuid = isUUID(id_str) - if is_uuid is not None: - return is_uuid - - # build a string which matches every character. - hex_wildcard = r"[0-9a-fA-F]" - chars = len(id_str) - next = min(chars, 8) - matcher = hex_wildcard+"{"+str(next)+","+str(next)+"}" - chars = chars - next - if chars > 0: - matcher = matcher + "-" - chars = chars - 1 - for block in range(3): - next = max(min(chars, 4), 0) - if next: - matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" - chars = chars - next - if chars > 0: - matcher = matcher + "-" - chars = chars - 1 - if chars > 0: - next = min(chars, 12) - matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" - #print matcher - uuid_matcher = re.compile(matcher) - if uuid_matcher.match(id_str): - return 1 - return 0 - -def uuid_bits_to_string(bits): - i1 = _binstr2int(bits[0:4]) - i2 = _binstr2int(bits[4:6]) - i3 = _binstr2int(bits[6:8]) - i4 = _binstr2int(bits[8:10]) - i5 = _binstr2int(bits[10:12]) - i6 = _binstr2int(bits[12:16]) - return '%08lx-%04lx-%04lx-%04lx-%04lx%08lx' % (i1,i2,i3,i4,i5,i6) - -def uuid_bits_to_uuid(bits): - return UUID(uuid_bits_to_string(bits)) - - -try: - from mulib import stacked - stacked.NoProducer() # just to exercise stacked -except: - #print "Couldn't import mulib.stacked, not registering UUID converter" - pass -else: - def convertUUID(uuid, req): - req.write(str(uuid)) - - stacked.add_producer(UUID, convertUUID, "*/*") - stacked.add_producer(UUID, convertUUID, "text/html") diff --git a/indra/viewer_components/manager/util/fastest_elementtree.py b/indra/viewer_components/manager/util/fastest_elementtree.py deleted file mode 100755 index 4fcf662dd9..0000000000 --- a/indra/viewer_components/manager/util/fastest_elementtree.py +++ /dev/null @@ -1,64 +0,0 @@ -"""\ -@file fastest_elementtree.py -@brief Concealing some gnarly import logic in here. This should export the interface of elementtree. - -$LicenseInfo:firstyear=2008&license=mit$ - -Copyright (c) 2008-2009, Linden Research, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -$/LicenseInfo$ -""" - -# The parsing exception raised by the underlying library depends -# on the ElementTree implementation we're using, so we provide an -# alias here. -# -# Use ElementTreeError as the exception type for catching parsing -# errors. - - -# Using cElementTree might cause some unforeseen problems, so here's a -# convenient off switch. -use_celementree = True - -try: - if not use_celementree: - raise ImportError() - # Python 2.3 and 2.4. - from cElementTree import * - ElementTreeError = SyntaxError -except ImportError: - try: - if not use_celementree: - raise ImportError() - # Python 2.5 and above. - from xml.etree.cElementTree import * - ElementTreeError = SyntaxError - except ImportError: - # Pure Python code. - try: - # Python 2.3 and 2.4. - from elementtree.ElementTree import * - except ImportError: - # Python 2.5 and above. - from xml.etree.ElementTree import * - - # The pure Python ElementTree module uses Expat for parsing. - from xml.parsers.expat import ExpatError as ElementTreeError -- cgit v1.2.3 From f44829f2aefe8c5fae9fba6bb838500719e1067e Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 27 Jul 2016 15:44:56 -0700 Subject: SL-321: basic launcher/updater integration test fixes --- indra/viewer_components/manager/SL_Launcher | 59 +++++++------ indra/viewer_components/manager/update_manager.py | 101 +++++++++++++--------- 2 files changed, 95 insertions(+), 65 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index a96f2392a7..dde7cd9c2e 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -17,43 +17,51 @@ # $/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 -import llsd -import os +from llbase import llsd import platform -import sys import subprocess import update_manager -def after_frame(my_message, timeout = 10000): - #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds - #note that this blocks the caller for the duration of timeout - frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif") - #this is done before basic_message so that we aren't blocked by mainloop() - frame.after(timout, lambda: frame._delete_window) - frame.basic_message(message = my_message) - + +def silent_write(log_file_handle, text): + #if we have a log file, write. If not, do nothing. + #this is so we don't have to keep trapping for an exception with a None handle + #oh and because it is best effort, it is also a holey_write ;) + if (log_file_handle): + #prepend text for easy grepping + log_file_handle.write("SL LAUNCHER: " + text + "\n") + def get_cmd_line(): platform_name = platform.system() #find the parent of the logs and user_settings directories - if (platform_name == 'mac'): + 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 == 'lnx'): + 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 == 'win'): + elif (platform_name == 'Windows'): settings_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'app_settings/cmd_line.xml') else: - settings_dir = None + 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 @@ -68,7 +76,6 @@ def get_settings(): 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. @@ -96,7 +103,8 @@ def capture_vmp_args(arg_list = None, cmd_line = None): cli_overrides[vmp_params[param]] = (setting_name, setting_value) else: #find out how many args this parameter has - count = cmd_line[param]['count'] + no_dashes = vmp_params[param] + count = cmd_line[no_dashes]['count'] param_args = [] if count: for argh in range(1,count): @@ -112,6 +120,7 @@ def capture_vmp_args(arg_list = None, cmd_line = None): except KeyError: cli_overrides[key] = None else: + cli_overrides["--set"] = {} for arg in vmp_setters: try: cli_overrides[key][arg] @@ -119,7 +128,10 @@ def capture_vmp_args(arg_list = None, cmd_line = None): cli_overrides[key][arg] = None return cli_overrides -cwd = os.path.dirname(os.path.realpath(__file__)) +#main entry point +#this and a few other update manager methods really should be refactored into a util lib +parent_dir = update_manager.get_parent_path(update_manager.get_platform_key()) +log_file_handle = update_manager.get_log_file_handle(parent_dir) executable_name = "" if sys.platform.startswith('darwin'): @@ -137,23 +149,18 @@ else: #SL doesn't run on VMS or punch cards sys.exit("Unsupported platform") -#check for an update -#TODO - #find the viewer to be lauched viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name) parser = argparse.ArgumentParser() args = parser.parse_known_args(sys.argv) -print args[1] -sys.exit() #args[1] looks like ['./SL_Launcher', '--set', 'foo', 'bar', '-X', '-Y', 'qux'], dump the progname args_list_to_pass = args[1][1:] vmp_args = capture_vmp_args(args_list_to_pass) #make a copy by value, not by reference command = list(args_list_to_pass) -(success, state, condition) = update_manager.update_manager(cli_overrides) +(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 @@ -167,8 +174,8 @@ command = list(args_list_to_pass) # No update, update succeeded in place in foreground, or background update started: silently launch the current viewer channel # Updated succeed to a different channel, launch that viewer and exit if not success: - msg = 'Update failed in the %s process. Please check logs. Viewer will launch starting momentarily.' - after_frame(msg) + 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 diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index a7e0a19aef..398c8bb55d 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -1,29 +1,35 @@ #!/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. - -""" -@file update_manager.py +"""\ +@file update_manager.py @author coyot -@date 2016-05-16 +@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$ """ from llbase import llrest +from llbase.llrest import RESTError from llbase import llsd from urlparse import urljoin @@ -55,9 +61,9 @@ def silent_write(log_file_handle, text): def after_frame(my_message, timeout = 10000): #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds #note that this blocks the caller for the duration of timeout - frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif") + 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(timout, lambda: frame._delete_window) + frame.after(timeout, lambda: frame._delete_window) frame.basic_message(message = my_message) def convert_version_file_style(version): @@ -146,8 +152,10 @@ def check_for_completed_download(download_dir): def get_settings(log_file_handle, parent_dir): #return the settings file parsed into a dict + print str(parent_dir) try: settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml')) + print "Settings file: " + str(settings_file) settings = llsd.parse((open(settings_file)).read()) except llsd.LLSDParseError as lpe: silent_write(log_file_handle, "Could not parse settings file %s" % lpe) @@ -213,7 +221,7 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ else: base_URI = 'https://update.secondlife.com/update/' channelname = summary_dict['Channel'] - #this is kind of a mess because the settings value a) in a map and b) is both the cohort and the version + #this is kind of a mess because the settings value is a) in a map and b) is both the cohort and the version version = summary_dict['Version'] platform_version = platform.release() #this will always return something usable, error handling in method @@ -250,7 +258,7 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ try: result_data = VVMService.get(query_string) except RESTError as re: - silent_write.write(log_file_handle, "Failed to query VVM using %s failed as %s" % (urljoin(base_URI,query_string, 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 @@ -342,6 +350,8 @@ def update_manager(cli_overrides = None): platform_key = get_platform_key() parent_dir = get_parent_path(platform_key) log_file_handle = get_log_file_handle(parent_dir) + settings = None + print "parent dir: " + str(parent_dir) #check to see if user has install rights #get the owner of the install and the current user @@ -367,10 +377,12 @@ def update_manager(cli_overrides = None): print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None) return (False, 'setup', None) - if cli_overrides['settings'] is not None: - settings = get_settings(log_file_handle, cli_overrides['settings']) - else: - settings = get_settings(log_file_handle, parent_dir) + 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']) + else: + settings = get_settings(log_file_handle, parent_dir) if settings is None: silent_write(log_file_handle, "Failed to load viewer settings") @@ -379,7 +391,7 @@ def update_manager(cli_overrides = None): #323: If a complete download of that update is found, check the update preference: #settings['UpdaterServiceSetting'] = 0 is manual install - """ + """ssh://hg@bitbucket.org/lindenlab/viewer-release-maint-6585 UpdaterServiceSetting Comment @@ -390,8 +402,10 @@ def update_manager(cli_overrides = None): 0 """ - if cli_overrides['set']['UpdaterServiceSetting'] is not None: - install_automatically = cli_overrides['set']['UpdaterServiceSetting'] + 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'] @@ -399,28 +413,37 @@ def update_manager(cli_overrides = None): except KeyError: install_automatically = 1 - #use default chunk size if none is given - if cli_overrides['set']['UpdaterMaximumBandwidth ']: - chunk_size = cli_overrides['set']['UpdaterMaximumBandwidth '] + #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 #get channel and version try: summary_dict = get_summary(platform_key, os.path.abspath(os.path.realpath(__file__))) - if cli_overrides['channel']: - summary_dict['Channel'] = cli_overrides['channel'] - except: + if cli_overrides is not None: + if 'channel' in cli_overrides.keys(): + summary_dict['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. - UpdaterServiceURL = cli_overrides['update-service'] + 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 result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict, UpdaterServiceURL) #nothing to do or error if not result_data: - silent_write.write(og_file_handle, "No update found.") + silent_write(log_file_handle, "No update found.") print "Update manager exited with (%s, %s, %s)" % (True, None, None) return (True, None, None) -- cgit v1.2.3 From fc7c0645fa9c9d47c1462387a3de82a2a4474467 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 28 Jul 2016 08:50:52 -0700 Subject: SL-321: add in resource files, more CLI handling fixes --- indra/viewer_components/Resources/README | 9 ++++ indra/viewer_components/Resources/summary.json | 1 + indra/viewer_components/manager/InstallerError.py | 49 +++++++++++++++++++++ indra/viewer_components/manager/SL_Launcher | 5 ++- indra/viewer_components/manager/icons/SL_Logo.gif | Bin 0 -> 1322 bytes indra/viewer_components/manager/icons/SL_Logo.png | Bin 0 -> 1484 bytes .../manager/icons/head-sl-logo.gif | Bin 0 -> 960 bytes .../manager/icons/head-sl-logo.png | Bin 0 -> 2316 bytes indra/viewer_components/manager/update_manager.py | 11 ++++- 9 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 indra/viewer_components/Resources/README create mode 100644 indra/viewer_components/Resources/summary.json create mode 100644 indra/viewer_components/manager/InstallerError.py create mode 100644 indra/viewer_components/manager/icons/SL_Logo.gif create mode 100644 indra/viewer_components/manager/icons/SL_Logo.png create mode 100644 indra/viewer_components/manager/icons/head-sl-logo.gif create mode 100644 indra/viewer_components/manager/icons/head-sl-logo.png (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/Resources/README b/indra/viewer_components/Resources/README new file mode 100644 index 0000000000..e1b35730d4 --- /dev/null +++ b/indra/viewer_components/Resources/README @@ -0,0 +1,9 @@ +This directory only exists as a place for the summary.json file to exist when the unit tests are run on a Mac, where the file goes to a sibling directory of the scripts dir. In Linux and Windows, the JSON file goes into the same directory as the script. + +See: + +test_get_summary.py +update_manager.get_summary() + +for more details +- coyot 201606.02 diff --git a/indra/viewer_components/Resources/summary.json b/indra/viewer_components/Resources/summary.json new file mode 100644 index 0000000000..b78859d427 --- /dev/null +++ b/indra/viewer_components/Resources/summary.json @@ -0,0 +1 @@ +{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"} diff --git a/indra/viewer_components/manager/InstallerError.py b/indra/viewer_components/manager/InstallerError.py new file mode 100644 index 0000000000..3b199ea231 --- /dev/null +++ b/indra/viewer_components/manager/InstallerError.py @@ -0,0 +1,49 @@ +#!/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 "", line 5, in +InstallerError.InstallerError: [Errno [Errno 17] File exists: '/tmp'] foo +""" + +class InstallerError(OSError): + def __init___(self, message): + Exception.__init__(self, message) diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index dde7cd9c2e..0403e01cec 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -106,11 +106,12 @@ def capture_vmp_args(arg_list = None, cmd_line = None): no_dashes = vmp_params[param] count = cmd_line[no_dashes]['count'] param_args = [] - if count: - for argh in range(1,count): + 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 + print "cli override param %s vmp_param %s args %s count %s" % (param, vmp_params[param], param_args, count) #to prevent KeyErrors on missing keys, set the remainder to None for key in vmp_params: diff --git a/indra/viewer_components/manager/icons/SL_Logo.gif b/indra/viewer_components/manager/icons/SL_Logo.gif new file mode 100644 index 0000000000..c24d6b08cb Binary files /dev/null and b/indra/viewer_components/manager/icons/SL_Logo.gif differ diff --git a/indra/viewer_components/manager/icons/SL_Logo.png b/indra/viewer_components/manager/icons/SL_Logo.png new file mode 100644 index 0000000000..5e376c72f9 Binary files /dev/null and b/indra/viewer_components/manager/icons/SL_Logo.png differ diff --git a/indra/viewer_components/manager/icons/head-sl-logo.gif b/indra/viewer_components/manager/icons/head-sl-logo.gif new file mode 100644 index 0000000000..d635348dcc Binary files /dev/null and b/indra/viewer_components/manager/icons/head-sl-logo.gif differ diff --git a/indra/viewer_components/manager/icons/head-sl-logo.png b/indra/viewer_components/manager/icons/head-sl-logo.png new file mode 100644 index 0000000000..5c214e96d1 Binary files /dev/null and b/indra/viewer_components/manager/icons/head-sl-logo.png differ diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index 398c8bb55d..ff0f69a1b1 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -252,8 +252,15 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ #normal case, no testing key test_ok = 'testok' UUID = make_VVM_UUID_hash(platform_key) + print repr(channelname) + print repr(version) + print repr(platform_key) + print repr(platform_version) + print repr(test_ok) + print repr(UUID) #because urljoin can't be arsed to take multiple elements - query_string = '/v1.0/' + channelname + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID + #channelname is a list because although it can only be one word, it is a kind of argument and viewer args can take multiple keywords. + query_string = '/v1.0/' + channelname[0] + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID VVMService = llrest.SimpleRESTService(name='VVM', baseurl=base_URI) try: result_data = VVMService.get(query_string) @@ -351,7 +358,7 @@ def update_manager(cli_overrides = None): parent_dir = get_parent_path(platform_key) log_file_handle = get_log_file_handle(parent_dir) settings = None - print "parent dir: " + str(parent_dir) + print "cli_overrides: " + str(cli_overrides) #check to see if user has install rights #get the owner of the install and the current user -- cgit v1.2.3 From 398fabf10c47c984c179e599af14fd27be4816cf Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 28 Jul 2016 11:38:13 -0700 Subject: SL-321: fix commithook issues in test files --- .../manager/tests/test_InstallerError.py | 40 ++++++++++++--------- .../tests/test_check_for_completed_download.py | 38 +++++++++++--------- .../tests/test_convert_version_file_style.py | 42 ++++++++++++---------- .../manager/tests/test_get_filename.py | 38 +++++++++++--------- .../manager/tests/test_get_log_file_handle.py | 39 +++++++++++--------- .../manager/tests/test_get_parent_path.py | 38 +++++++++++--------- .../manager/tests/test_get_platform_key.py | 38 +++++++++++--------- .../manager/tests/test_get_settings.py | 37 ++++++++++--------- .../manager/tests/test_make_VVM_UUID_hash.py | 37 ++++++++++--------- .../manager/tests/test_make_download_dir.py | 39 +++++++++++--------- .../manager/tests/test_query_vvm.py | 36 +++++++++++-------- .../manager/tests/test_summary.py | 38 +++++++++++--------- .../manager/tests/with_setup_args.py | 42 +++++++++++----------- 13 files changed, 284 insertions(+), 218 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/tests/test_InstallerError.py b/indra/viewer_components/manager/tests/test_InstallerError.py index d722208b7f..9139447932 100644 --- a/indra/viewer_components/manager/tests/test_InstallerError.py +++ b/indra/viewer_components/manager/tests/test_InstallerError.py @@ -1,27 +1,33 @@ #!/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$ - -""" +"""\ @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 diff --git a/indra/viewer_components/manager/tests/test_check_for_completed_download.py b/indra/viewer_components/manager/tests/test_check_for_completed_download.py index 388bc900e9..bcaaef4c3f 100644 --- a/indra/viewer_components/manager/tests/test_check_for_completed_download.py +++ b/indra/viewer_components/manager/tests/test_check_for_completed_download.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -50,4 +56,4 @@ def test_completed_check_for_completed_download(tmpdir1,tmpdir2): 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" \ No newline at end of file + assert incomplete, "False positive, should not mark complete without a marker" diff --git a/indra/viewer_components/manager/tests/test_convert_version_file_style.py b/indra/viewer_components/manager/tests/test_convert_version_file_style.py index d700f91b84..244c22c458 100644 --- a/indra/viewer_components/manager/tests/test_convert_version_file_style.py +++ b/indra/viewer_components/manager/tests/test_convert_version_file_style.py @@ -1,27 +1,33 @@ #!/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$ - -""" +"""\ @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 @@ -52,4 +58,4 @@ def test_none(): golden = None converted = update_manager.convert_version_file_style(version) - assert_equal(golden, converted) \ No newline at end of file + assert_equal(golden, converted) diff --git a/indra/viewer_components/manager/tests/test_get_filename.py b/indra/viewer_components/manager/tests/test_get_filename.py index 95771d75dc..efb6349374 100644 --- a/indra/viewer_components/manager/tests/test_get_filename.py +++ b/indra/viewer_components/manager/tests/test_get_filename.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -57,4 +63,4 @@ def test_get_filename(tmpdir1, tmpdir2, tmpdir3, tmpdir4): @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" \ No newline at end of file + assert not_found, "False positive, should not find an installable in an empty dir" diff --git a/indra/viewer_components/manager/tests/test_get_log_file_handle.py b/indra/viewer_components/manager/tests/test_get_log_file_handle.py index c5b3c89550..5ea821acf7 100644 --- a/indra/viewer_components/manager/tests/test_get_log_file_handle.py +++ b/indra/viewer_components/manager/tests/test_get_log_file_handle.py @@ -1,25 +1,30 @@ #!/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$ - """ @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 * @@ -60,4 +65,4 @@ def test_missing_get_log_file_handle(tmpdir1,tmpdir2,log_file_path): if not os.path.exists(log_file_path): print "Failed to touch new log file" assert False - assert True \ No newline at end of file + assert True diff --git a/indra/viewer_components/manager/tests/test_get_parent_path.py b/indra/viewer_components/manager/tests/test_get_parent_path.py index 3cfd72310e..6e4b9dfaff 100644 --- a/indra/viewer_components/manager/tests/test_get_parent_path.py +++ b/indra/viewer_components/manager/tests/test_get_parent_path.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -75,5 +81,3 @@ def test_get_parent_path(settings_dir): assert settings_dir, "test_get_parent_path failed to obtain parent path" assert_equal(settings_dir, got_settings_dir) - - \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_platform_key.py b/indra/viewer_components/manager/tests/test_get_platform_key.py index 37c570532c..eeaca1dff7 100644 --- a/indra/viewer_components/manager/tests/test_get_platform_key.py +++ b/indra/viewer_components/manager/tests/test_get_platform_key.py @@ -1,25 +1,30 @@ #!/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$ - """ @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 @@ -37,4 +42,3 @@ def test_get_platform_key(): assert_equal(platform.system(),'Windows') else: assert_equal(key, None) - \ No newline at end of file diff --git a/indra/viewer_components/manager/tests/test_get_settings.py b/indra/viewer_components/manager/tests/test_get_settings.py index 46b779cbd5..7efcf62673 100644 --- a/indra/viewer_components/manager/tests/test_get_settings.py +++ b/indra/viewer_components/manager/tests/test_get_settings.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -79,4 +85,3 @@ def test_get_settings(settings_dir): #test one key just to make sure it parsed assert_equal(got_settings['CurrentGrid']['Value'], 'util.agni.lindenlab.com') - diff --git a/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py index 513502a6ca..5939e5806a 100644 --- a/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py +++ b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -39,4 +45,3 @@ def test_make_VVM_UUID_hash(): #make_UUID_hash returned None assert UUID_hash, "make_UUID_hash failed to make a hash." - diff --git a/indra/viewer_components/manager/tests/test_make_download_dir.py b/indra/viewer_components/manager/tests/test_make_download_dir.py index 5198a6de05..e3f9cb83fe 100644 --- a/indra/viewer_components/manager/tests/test_make_download_dir.py +++ b/indra/viewer_components/manager/tests/test_make_download_dir.py @@ -1,25 +1,30 @@ #!/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$ - """ @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 * @@ -39,4 +44,4 @@ def test_make_download_dir(): 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) \ No newline at end of file + assert download_dir, "make_download_dir returned None for path %s and version %s" % (path, version) diff --git a/indra/viewer_components/manager/tests/test_query_vvm.py b/indra/viewer_components/manager/tests/test_query_vvm.py index 40a0f7b215..79c8ede480 100644 --- a/indra/viewer_components/manager/tests/test_query_vvm.py +++ b/indra/viewer_components/manager/tests/test_query_vvm.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * diff --git a/indra/viewer_components/manager/tests/test_summary.py b/indra/viewer_components/manager/tests/test_summary.py index b318012b54..f2f2af7347 100644 --- a/indra/viewer_components/manager/tests/test_summary.py +++ b/indra/viewer_components/manager/tests/test_summary.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -36,4 +42,4 @@ def test_get_summary(): #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') \ No newline at end of file + assert_equal(summary_json['Type'],'viewer') diff --git a/indra/viewer_components/manager/tests/with_setup_args.py b/indra/viewer_components/manager/tests/with_setup_args.py index 38350ab8c4..94d33aa5fe 100644 --- a/indra/viewer_components/manager/tests/with_setup_args.py +++ b/indra/viewer_components/manager/tests/with_setup_args.py @@ -1,29 +1,31 @@ #!/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$ -# -# Taken from: https://gist.github.com/garyvdm/392ae20c673c7ee58d76 - """ @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:: @@ -63,4 +65,4 @@ def with_setup_args(setup, teardown=None): if hasattr(func, 'teardown'): test_wrapped.teardown = func.teardown() return test_wrapped - return decorate \ No newline at end of file + return decorate -- cgit v1.2.3 From 2afcff5b013c778f55af77f27932e10792986a05 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 28 Jul 2016 11:44:01 -0700 Subject: SL-321: one more commithook issue in test file --- .../manager/tests/test_silent_write.py | 38 +++++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/tests/test_silent_write.py b/indra/viewer_components/manager/tests/test_silent_write.py index a55665799f..ad286787e2 100644 --- a/indra/viewer_components/manager/tests/test_silent_write.py +++ b/indra/viewer_components/manager/tests/test_silent_write.py @@ -1,25 +1,31 @@ #!/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$ """ @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 * @@ -40,4 +46,4 @@ def test_silent_write_to_null(): update_manager.silent_write(None, "This is a test.") except Exception, e: print "Test failed due to: %s" % str(e) - assert False \ No newline at end of file + assert False -- cgit v1.2.3 From 7ed9c85a1a28e494adbd2cef85b186e45dba2dc2 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Mon, 15 Aug 2016 14:48:09 -0700 Subject: SL-323: fixes to Tkinter race condition, post --channel and --settings testing, contains debugging statements to be removed after all testing complete --- .../manager/InstallerUserMessage.py | 13 +++- indra/viewer_components/manager/SL_Launcher | 8 +- indra/viewer_components/manager/download_update.py | 2 + indra/viewer_components/manager/update_manager.py | 87 ++++++++++++++-------- 4 files changed, 77 insertions(+), 33 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index f66af81d06..4f81aa9cd1 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -78,11 +78,17 @@ class InstallerUserMessage(tk.Tk): #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 @@ -217,8 +223,11 @@ class InstallerUserMessage(tk.Tk): def check_scheduler(self): if self.value < self.progress["maximum"]: - self.check_queue() - self.after(100, self.check_scheduler) + 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) def check_queue(self): while self.queue.qsize(): diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 0403e01cec..8da8dc236b 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -31,7 +31,12 @@ 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 -from llbase import llsd +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 @@ -158,6 +163,7 @@ 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) +print "vmp args: " + repr(vmp_args) #make a copy by value, not by reference command = list(args_list_to_pass) diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py index 23f784c6c1..a5e365fa37 100755 --- a/indra/viewer_components/manager/download_update.py +++ b/indra/viewer_components/manager/download_update.py @@ -45,6 +45,8 @@ def download_update(url = None, download_dir = None, size = None, progressbar = #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) diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index ff0f69a1b1..6e79e83605 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -28,9 +28,21 @@ Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA $/LicenseInfo$ """ -from llbase import llrest -from llbase.llrest import RESTError -from llbase import llsd +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 urlparse import urljoin import apply_update @@ -40,7 +52,6 @@ import fnmatch import hashlib import InstallerUserMessage import json -import os import platform import re import shutil @@ -58,13 +69,14 @@ def silent_write(log_file_handle, text): #prepend text for easy grepping log_file_handle.write("UPDATE MANAGER: " + text + "\n") -def after_frame(my_message, timeout = 10000): +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.basic_message(message = my_message) + #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 @@ -155,6 +167,10 @@ def get_settings(log_file_handle, parent_dir): print str(parent_dir) 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 print "Settings file: " + str(settings_file) settings = llsd.parse((open(settings_file)).read()) except llsd.LLSDParseError as lpe: @@ -223,9 +239,15 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ 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 version = summary_dict['Version'] - platform_version = platform.release() + #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 - hashed_UUID = make_VVM_UUID_hash(platform_key) + 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 """ @@ -251,16 +273,10 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ except KeyError: #normal case, no testing key test_ok = 'testok' - UUID = make_VVM_UUID_hash(platform_key) - print repr(channelname) - print repr(version) - print repr(platform_key) - print repr(platform_version) - print repr(test_ok) - print repr(UUID) #because urljoin can't be arsed to take multiple elements - #channelname is a list because although it can only be one word, it is a kind of argument and viewer args can take multiple keywords. - query_string = '/v1.0/' + channelname[0] + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID + #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) @@ -269,25 +285,28 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ return None return result_data -def download(url = None, version = None, download_dir = None, size = 0, background = False, chunk_size = 1024): +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.") + after_frame(message = "Downloading new version " + version + " Please wait.", timeout = 5000) else: - after_frame(message = "Trying again to download new version " + version + " Please wait.") + 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: + 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 @@ -308,6 +327,9 @@ def install(platform_key = None, download_dir = None, log_file_handle = None, in 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) + print download_dir + print success + version = download_dir.split('/')[-1] if success: silent_write(log_file_handle, "successfully updated to " + version) shutil.rmtree(download_dir) @@ -323,7 +345,7 @@ def download_and_install(downloaded = None, url = None, version = None, download #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): + 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, @@ -385,9 +407,10 @@ def update_manager(cli_overrides = 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']) + print "update manager settings file: " + str(cli_overrides['settings']) + 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) @@ -398,7 +421,7 @@ def update_manager(cli_overrides = None): #323: If a complete download of that update is found, check the update preference: #settings['UpdaterServiceSetting'] = 0 is manual install - """ssh://hg@bitbucket.org/lindenlab/viewer-release-maint-6585 + """ UpdaterServiceSetting Comment @@ -431,9 +454,11 @@ def update_manager(cli_overrides = None): #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(): - summary_dict['Channel'] = cli_overrides['channel'] + 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) @@ -447,7 +472,8 @@ def update_manager(cli_overrides = None): else: #tells query_vvm to use the default UpdaterServiceURL = None - result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict, UpdaterServiceURL) + 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.") @@ -465,6 +491,7 @@ def update_manager(cli_overrides = None): #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) @@ -502,7 +529,7 @@ def update_manager(cli_overrides = None): 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) + 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) -- cgit v1.2.3 From 5811b4721f8b2d848efeb37f2dff14f00bb7fc67 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Tue, 16 Aug 2016 08:40:39 -0700 Subject: SL-323: fixed update service redirect handling --- indra/viewer_components/manager/SL_Launcher | 2 -- indra/viewer_components/manager/update_manager.py | 33 +++++++++++------------ 2 files changed, 16 insertions(+), 19 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 8da8dc236b..1184a4c794 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -116,7 +116,6 @@ def capture_vmp_args(arg_list = None, cmd_line = None): 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 - print "cli override param %s vmp_param %s args %s count %s" % (param, vmp_params[param], param_args, count) #to prevent KeyErrors on missing keys, set the remainder to None for key in vmp_params: @@ -163,7 +162,6 @@ 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) -print "vmp args: " + repr(vmp_args) #make a copy by value, not by reference command = list(args_list_to_pass) diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index 6e79e83605..80b0f087ab 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -164,14 +164,12 @@ def check_for_completed_download(download_dir): def get_settings(log_file_handle, parent_dir): #return the settings file parsed into a dict - print str(parent_dir) 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 - print "Settings file: " + str(settings_file) settings = llsd.parse((open(settings_file)).read()) except llsd.LLSDParseError as lpe: silent_write(log_file_handle, "Could not parse settings file %s" % lpe) @@ -230,14 +228,19 @@ def make_VVM_UUID_hash(platform_key): 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 if UpdaterServiceURL: - baseURI = 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 + #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': @@ -327,8 +330,6 @@ def install(platform_key = None, download_dir = None, log_file_handle = None, in 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) - print download_dir - print success version = download_dir.split('/')[-1] if success: silent_write(log_file_handle, "successfully updated to " + version) @@ -380,7 +381,6 @@ def update_manager(cli_overrides = None): parent_dir = get_parent_path(platform_key) log_file_handle = get_log_file_handle(parent_dir) settings = None - print "cli_overrides: " + str(cli_overrides) #check to see if user has install rights #get the owner of the install and the current user @@ -407,12 +407,11 @@ def update_manager(cli_overrides = None): return (False, 'setup', None) if cli_overrides is not None: - print "update manager settings file: " + str(cli_overrides['settings']) 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) + else: + settings = get_settings(log_file_handle, parent_dir) if settings is None: silent_write(log_file_handle, "Failed to load viewer settings") @@ -433,9 +432,9 @@ def update_manager(cli_overrides = None): """ 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'] + 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'] @@ -445,9 +444,9 @@ def update_manager(cli_overrides = None): #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'] + if 'set' in cli_overrides.keys(): + if 'UpdaterMaximumBandwidth' in cli_overrides['set'].keys(): + chunk_size = cli_overrides['set']['UpdaterMaximumBandwidth'] else: chunk_size = 1024 @@ -468,7 +467,7 @@ def update_manager(cli_overrides = 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'] + UpdaterServiceURL = cli_overrides['update-service'] else: #tells query_vvm to use the default UpdaterServiceURL = None -- cgit v1.2.3 From 4a124b8b9d709c3ae1856666bb29f28e90ab5d05 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Tue, 16 Aug 2016 09:17:07 -0700 Subject: SL-323: logging improvements --- indra/viewer_components/manager/SL_Launcher | 5 +++-- indra/viewer_components/manager/update_manager.py | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 1184a4c794..257543187c 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -48,7 +48,8 @@ def silent_write(log_file_handle, text): #oh and because it is best effort, it is also a holey_write ;) if (log_file_handle): #prepend text for easy grepping - log_file_handle.write("SL LAUNCHER: " + text + "\n") + 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() @@ -136,7 +137,7 @@ def capture_vmp_args(arg_list = None, cmd_line = None): #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) +log_file_handle = update_manager.get_log_file_handle(parent_dir, 'launcher.log') executable_name = "" if sys.platform.startswith('darwin'): diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index 80b0f087ab..647e7a77e6 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -43,6 +43,7 @@ except: pass from copy import deepcopy +from datetime import datetime from urlparse import urljoin import apply_update @@ -67,7 +68,8 @@ def silent_write(log_file_handle, text): #oh and because it is best effort, it is also a holey_write ;) if (log_file_handle): #prepend text for easy grepping - log_file_handle.write("UPDATE MANAGER: " + text + "\n") + 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 @@ -176,10 +178,12 @@ def get_settings(log_file_handle, parent_dir): return None return settings -def get_log_file_handle(parent_dir): +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 - log_file = os.path.join(parent_dir, 'update_manager.log') + 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): @@ -234,6 +238,7 @@ def query_vvm(log_file_handle = None, platform_key = None, settings = None, summ #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/') @@ -379,7 +384,7 @@ def update_manager(cli_overrides = None): #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) + log_file_handle = get_log_file_handle(parent_dir, 'update_manager.log') settings = None #check to see if user has install rights @@ -466,10 +471,12 @@ def update_manager(cli_overrides = 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(): + if 'update-service' in cli_overrides.keys(): UpdaterServiceURL = cli_overrides['update-service'] + else: + #tells query_vvm to use the default + UpdaterServiceURL = None else: - #tells query_vvm to use the default UpdaterServiceURL = None result_data = query_vvm(log_file_handle, platform_key, settings, channel_override_summary, UpdaterServiceURL) -- cgit v1.2.3 From 3e55d8c77a21977f4023a82ebef3fed9879247bc Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 17 Aug 2016 08:20:12 -0700 Subject: SL-323: add icon support for macs, text flow in Tkinter windows --- .../manager/InstallerUserMessage.py | 26 ++++++++++++++-------- indra/viewer_components/manager/update_manager.py | 2 ++ 2 files changed, 19 insertions(+), 9 deletions(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py index 4f81aa9cd1..8002399659 100644 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ b/indra/viewer_components/manager/InstallerUserMessage.py @@ -50,7 +50,7 @@ class InstallerUserMessage(tk.Tk): #Linden standard green color, from Marketing linden_green = "#487A7B" - def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None): + 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) @@ -71,7 +71,8 @@ class InstallerUserMessage(tk.Tk): #find a few things self.script_dir = os.path.dirname(os.path.realpath(__file__)) - self.icon_dir = os.path.abspath(os.path.join(self.script_dir, 'icons')) + 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) @@ -104,6 +105,7 @@ class InstallerUserMessage(tk.Tk): 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) @@ -222,12 +224,16 @@ class InstallerUserMessage(tk.Tk): self.check_scheduler() def check_scheduler(self): - 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) + 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(): @@ -264,7 +270,7 @@ class ThreadedClient(threading.Thread): 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. The third will close by itself. + #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 @@ -278,6 +284,8 @@ if __name__ == "__main__": #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() diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py index 647e7a77e6..7962568119 100755 --- a/indra/viewer_components/manager/update_manager.py +++ b/indra/viewer_components/manager/update_manager.py @@ -452,6 +452,8 @@ def update_manager(cli_overrides = 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 -- cgit v1.2.3 From 8ae320edf1bf17a762b0477584b13b61ea94922c Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Wed, 17 Aug 2016 08:50:43 -0700 Subject: SL-323: remove vmp icons from viewer_components subtree --- indra/viewer_components/manager/icons/SL_Logo.gif | Bin 1322 -> 0 bytes indra/viewer_components/manager/icons/SL_Logo.png | Bin 1484 -> 0 bytes indra/viewer_components/manager/icons/head-sl-logo.gif | Bin 960 -> 0 bytes indra/viewer_components/manager/icons/head-sl-logo.png | Bin 2316 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 indra/viewer_components/manager/icons/SL_Logo.gif delete mode 100644 indra/viewer_components/manager/icons/SL_Logo.png delete mode 100644 indra/viewer_components/manager/icons/head-sl-logo.gif delete mode 100644 indra/viewer_components/manager/icons/head-sl-logo.png (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/icons/SL_Logo.gif b/indra/viewer_components/manager/icons/SL_Logo.gif deleted file mode 100644 index c24d6b08cb..0000000000 Binary files a/indra/viewer_components/manager/icons/SL_Logo.gif and /dev/null differ diff --git a/indra/viewer_components/manager/icons/SL_Logo.png b/indra/viewer_components/manager/icons/SL_Logo.png deleted file mode 100644 index 5e376c72f9..0000000000 Binary files a/indra/viewer_components/manager/icons/SL_Logo.png and /dev/null differ diff --git a/indra/viewer_components/manager/icons/head-sl-logo.gif b/indra/viewer_components/manager/icons/head-sl-logo.gif deleted file mode 100644 index d635348dcc..0000000000 Binary files a/indra/viewer_components/manager/icons/head-sl-logo.gif and /dev/null differ diff --git a/indra/viewer_components/manager/icons/head-sl-logo.png b/indra/viewer_components/manager/icons/head-sl-logo.png deleted file mode 100644 index 5c214e96d1..0000000000 Binary files a/indra/viewer_components/manager/icons/head-sl-logo.png and /dev/null differ -- cgit v1.2.3 From c8c143e7741d2b93b589d770b64c265228293564 Mon Sep 17 00:00:00 2001 From: Glenn Glazer Date: Thu, 18 Aug 2016 13:05:30 -0700 Subject: SL-323: first pass at ripping out old updater --- indra/viewer_components/CMakeLists.txt | 1 - indra/viewer_components/updater/CMakeLists.txt | 106 --- .../viewer_components/updater/llupdatechecker.cpp | 187 ----- indra/viewer_components/updater/llupdatechecker.h | 119 ---- .../updater/llupdatedownloader.cpp | 604 ---------------- .../viewer_components/updater/llupdatedownloader.h | 96 --- .../updater/llupdateinstaller.cpp | 98 --- .../viewer_components/updater/llupdateinstaller.h | 58 -- .../viewer_components/updater/llupdaterservice.cpp | 760 --------------------- indra/viewer_components/updater/llupdaterservice.h | 112 --- .../updater/scripts/darwin/janitor.py | 133 ---- .../updater/scripts/darwin/messageframe.py | 66 -- .../updater/scripts/darwin/update_install.py | 412 ----------- .../updater/scripts/linux/update_install | 220 ------ .../updater/scripts/windows/update_install.bat | 3 - .../updater/tests/llupdaterservice_test.cpp | 220 ------ 16 files changed, 3195 deletions(-) delete mode 100644 indra/viewer_components/updater/CMakeLists.txt delete mode 100644 indra/viewer_components/updater/llupdatechecker.cpp delete mode 100644 indra/viewer_components/updater/llupdatechecker.h delete mode 100644 indra/viewer_components/updater/llupdatedownloader.cpp delete mode 100644 indra/viewer_components/updater/llupdatedownloader.h delete mode 100644 indra/viewer_components/updater/llupdateinstaller.cpp delete mode 100644 indra/viewer_components/updater/llupdateinstaller.h delete mode 100644 indra/viewer_components/updater/llupdaterservice.cpp delete mode 100644 indra/viewer_components/updater/llupdaterservice.h delete mode 100644 indra/viewer_components/updater/scripts/darwin/janitor.py delete mode 100644 indra/viewer_components/updater/scripts/darwin/messageframe.py delete mode 100755 indra/viewer_components/updater/scripts/darwin/update_install.py delete mode 100755 indra/viewer_components/updater/scripts/linux/update_install delete mode 100644 indra/viewer_components/updater/scripts/windows/update_install.bat delete mode 100644 indra/viewer_components/updater/tests/llupdaterservice_test.cpp (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt index 74c9b4568d..642dada7b2 100644 --- a/indra/viewer_components/CMakeLists.txt +++ b/indra/viewer_components/CMakeLists.txt @@ -1,4 +1,3 @@ # -*- cmake -*- add_subdirectory(login) -add_subdirectory(updater) diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt deleted file mode 100644 index 73e18aacb3..0000000000 --- a/indra/viewer_components/updater/CMakeLists.txt +++ /dev/null @@ -1,106 +0,0 @@ -# -*- cmake -*- - -project(updater_service) - -include(00-Common) -if(LL_TESTS) - include(LLAddBuildTest) -endif(LL_TESTS) -include(Boost) -include(CMakeCopyIfDifferent) -include(CURL) -include(LLCommon) -include(LLCoreHttp) -include(LLMessage) -include(LLPlugin) -include(LLVFS) - -include_directories( - ${LLCOMMON_INCLUDE_DIRS} - ${LLCOREHTTP_INCLUDE_DIRS} - ${LLMESSAGE_INCLUDE_DIRS} - ${LLPLUGIN_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} - ${CURL_INCLUDE_DIRS} - ${CMAKE_SOURCE_DIR}/newview - ) -include_directories(SYSTEM - ${LLCOMMON_SYSTEM_INCLUDE_DIRS} - ) - -set(updater_service_SOURCE_FILES - llupdaterservice.cpp - llupdatechecker.cpp - llupdatedownloader.cpp - llupdateinstaller.cpp - ) - -set(updater_service_HEADER_FILES - llupdaterservice.h - llupdatechecker.h - llupdatedownloader.h - llupdateinstaller.h - ) - -set_source_files_properties(${updater_service_HEADER_FILES} - PROPERTIES HEADER_FILE_ONLY TRUE) - -set_source_files_properties( - llupdaterservice.cpp - PROPERTIES - COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" # see BuildVersion.cmake - ) - -list(APPEND - updater_service_SOURCE_FILES - ${updater_service_HEADER_FILES} - ) - -add_library(llupdaterservice - ${updater_service_SOURCE_FILES} - ) - -target_link_libraries(llupdaterservice - ${LLCOMMON_LIBRARIES} - ${LLMESSAGE_LIBRARIES} - ${LLCOREHTTP_LIBRARIES} - ${LLPLUGIN_LIBRARIES} - ${LLVFS_LIBRARIES} - ) - -if(LL_TESTS) -if (NOT LINUX) - SET(llupdater_service_TEST_SOURCE_FILES - llupdaterservice.cpp - ) - -set(test_libs - ${LLCOMMON_LIBRARIES} - ${BOOST_COROUTINE_LIBRARY} - ${BOOST_CONTEXT_LIBRARY} - ${BOOST_THREAD_LIBRARY} - ${BOOST_SYSTEM_LIBRARY}) - -set_source_files_properties( - llupdaterservice.cpp - PROPERTIES - LL_TEST_ADDITIONAL_LIBRARIES ${test_libs} -# *NOTE:Mani - I was trying to use the preprocessor seam to mock out -# llifstream (and other) llcommon classes. It didn't work -# because of the windows declspec(dllimport)attribute. -# LL_TEST_ADDITIONAL_CFLAGS "-Dllifstream=llus_mock_llifstream" - ) - - LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}" ${test_libs}) -endif (NOT LINUX) -endif(LL_TESTS) - -set(UPDATER_INCLUDE_DIRS - ${LIBS_OPEN_DIR}/viewer_components/updater - CACHE INTERNAL "" -) - -set(UPDATER_LIBRARIES - llupdaterservice - CACHE INTERNAL "" -) diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp deleted file mode 100644 index 1bb5e95740..0000000000 --- a/indra/viewer_components/updater/llupdatechecker.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @file llupdaterservice.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#include "linden_common.h" -#include -#include -#include "llsd.h" -#include "llupdatechecker.h" -#include "lluri.h" -#include "llcorehttputil.h" -#if LL_DARWIN -#include -#endif - -#if LL_WINDOWS -#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally -#endif - - -class LLUpdateChecker::CheckError: - public std::runtime_error -{ -public: - CheckError(const char * message): - std::runtime_error(message) - { - ; // No op. - } -}; - - -// LLUpdateChecker -//----------------------------------------------------------------------------- -LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client): - mImplementation(new LLUpdateChecker::Implementation(client)) -{ - ; // No op. -} - -void LLUpdateChecker::checkVersion(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test) -{ - mImplementation->checkVersion(urlBase, channel, version, platform, platform_version, uniqueid, willing_to_test); -} - - -// LLUpdateChecker::Implementation -//----------------------------------------------------------------------------- -const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.1"; - - -LLUpdateChecker::Implementation::Implementation(LLUpdateChecker::Client & client): - mClient(client), - mInProgress(false), - mProtocol(sProtocolVersion) -{ - ; // No op. -} - - -LLUpdateChecker::Implementation::~Implementation() -{ - ; // No op. -} - - -void LLUpdateChecker::Implementation::checkVersion(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test) -{ - if (!mInProgress) - { - mInProgress = true; - - mUrlBase = urlBase; - mChannel = channel; - mVersion = version; - mPlatform = platform; - mPlatformVersion = platform_version; - memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE); - mWillingToTest = willing_to_test; - - mProtocol = sProtocolVersion; - - std::string checkUrl = buildUrl(urlBase, channel, version, platform, platform_version, uniqueid, willing_to_test); - LL_INFOS("UpdaterService") << "checking for updates at " << checkUrl << LL_ENDL; - - LLCoros::instance().launch("LLUpdateChecker::Implementation::checkVersionCoro", - boost::bind(&Implementation::checkVersionCoro, this, checkUrl)); - - } - else - { - LL_WARNS("UpdaterService") << "attempting to restart a check when one is in progress; ignored" << LL_ENDL; - } -} - -void LLUpdateChecker::Implementation::checkVersionCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("checkVersionCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LL_INFOS("checkVersionCoro") << "Getting update information from " << url << LL_ENDL; - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - mInProgress = false; - - if (status != LLCore::HttpStatus(HTTP_OK)) - { - std::string server_error; - if (result.has("error_code")) - { - server_error += result["error_code"].asString(); - } - if (result.has("error_text")) - { - server_error += server_error.empty() ? "" : ": "; - server_error += result["error_text"].asString(); - } - - LL_WARNS("UpdaterService") << "response error " << status.getStatus() - << " " << status.toString() - << " (" << server_error << ")" - << LL_ENDL; - mClient.error(status.toString()); - return; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - mClient.response(result); -} - -std::string LLUpdateChecker::Implementation::buildUrl(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test) -{ - LLSD path; - path.append(mProtocol); - path.append(channel); - path.append(version); - path.append(platform); - path.append(platform_version); - path.append(willing_to_test ? "testok" : "testno"); - path.append((char*)uniqueid); - return LLURI::buildHTTP(urlBase, path).asString(); -} diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h deleted file mode 100644 index d10ea4cf42..0000000000 --- a/indra/viewer_components/updater/llupdatechecker.h +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @file llupdatechecker.h - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#ifndef LL_UPDATERCHECKER_H -#define LL_UPDATERCHECKER_H - - -#include - -#include "llmd5.h" -#include "lleventcoro.h" -#include "llcoros.h" - -// -// Implements asynchronous checking for updates. -// -class LLUpdateChecker { -public: - // - // The client interface implemented by a requestor checking for an update. - // - class Client - { - public: - // An error occurred while checking for an update. - virtual void error(std::string const & message) = 0; - - // A successful response was received from the viewer version manager - virtual void response(LLSD const & content) = 0; - }; - - // An exception that may be raised on check errors. - class CheckError; - - LLUpdateChecker(Client & client); - - // Check status of current app on the given host for the channel and version provided. - void checkVersion(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test); - -private: - class Implementation - { - public: - typedef boost::shared_ptr ptr_t; - - Implementation(Client & client); - ~Implementation(); - void checkVersion(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test - ); - - - private: - static const char * sLegacyProtocolVersion; - static const char * sProtocolVersion; - const char* mProtocol; - - Client & mClient; - bool mInProgress; - std::string mVersion; - std::string mUrlBase; - std::string mChannel; - std::string mPlatform; - std::string mPlatformVersion; - unsigned char mUniqueId[MD5HEX_STR_SIZE]; - bool mWillingToTest; - - std::string buildUrl(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test); - - void checkVersionCoro(std::string url); - - LOG_CLASS(LLUpdateChecker::Implementation); - }; - - - Implementation::ptr_t mImplementation; - //LLPointer mImplementation; -}; - -#endif diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp deleted file mode 100644 index 382689afa0..0000000000 --- a/indra/viewer_components/updater/llupdatedownloader.cpp +++ /dev/null @@ -1,604 +0,0 @@ -/** - * @file llupdatedownloader.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#include "linden_common.h" - -#include "llupdatedownloader.h" -#include "httpcommon.h" -#include -#include -#include -#include -#include "lldir.h" -#include "llevents.h" -#include "llfile.h" -#include "llmd5.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llthread.h" -#include "llupdaterservice.h" - -class LLUpdateDownloader::Implementation: - public LLThread -{ -public: - Implementation(LLUpdateDownloader::Client & client); - ~Implementation(); - void cancel(void); - void download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required); - bool isDownloading(void); - size_t onHeader(void * header, size_t size); - size_t onBody(void * header, size_t size); - int onProgress(curl_off_t downloadSize, curl_off_t bytesDownloaded); - void resume(void); - void setBandwidthLimit(U64 bytesPerSecond); - -private: - curl_off_t mBandwidthLimit; - bool mCancelled; - LLUpdateDownloader::Client & mClient; - LLCore::LLHttp::CURL_ptr mCurl; - LLSD mDownloadData; - llofstream mDownloadStream; - unsigned char mDownloadPercent; - std::string mDownloadRecordPath; - curl_slist * mHeaderList; - - void initializeCurlGet(std::string const & url, bool processHeader); - void resumeDownloading(size_t startByte); - void run(void); - void startDownloading(LLURI const & uri, std::string const & hash); - void throwOnCurlError(CURLcode code); - bool validateDownload(const std::string& filePath); - bool validateOrRemove(const std::string& filePath); - - LOG_CLASS(LLUpdateDownloader::Implementation); -}; - - -namespace { - class DownloadError: - public std::runtime_error - { - public: - DownloadError(const char * message): - std::runtime_error(message) - { - ; // No op. - } - }; - - - const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml"; -}; - - - -// LLUpdateDownloader -//----------------------------------------------------------------------------- - - - -std::string LLUpdateDownloader::downloadMarkerPath(void) -{ - return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, gSecondLifeUpdateRecord); -} - - -LLUpdateDownloader::LLUpdateDownloader(Client & client): - mImplementation(new LLUpdateDownloader::Implementation(client)) -{ - ; // No op. -} - - -void LLUpdateDownloader::cancel(void) -{ - mImplementation->cancel(); -} - - -void LLUpdateDownloader::download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required) -{ - mImplementation->download(uri, hash, updateChannel, updateVersion, info_url, required); -} - - -bool LLUpdateDownloader::isDownloading(void) -{ - return mImplementation->isDownloading(); -} - - -void LLUpdateDownloader::resume(void) -{ - mImplementation->resume(); -} - - -void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond) -{ - mImplementation->setBandwidthLimit(bytesPerSecond); -} - - - -// LLUpdateDownloader::Implementation -//----------------------------------------------------------------------------- - - -namespace { - size_t write_function(void * data, size_t blockSize, size_t blocks, void * downloader) - { - size_t bytes = blockSize * blocks; - return reinterpret_cast(downloader)->onBody(data, bytes); - } - - - size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader) - { - size_t bytes = blockSize * blocks; - return reinterpret_cast(downloader)->onHeader(data, bytes); - } - - - int xferinfo_callback(void * downloader, - curl_off_t dowloadTotal, - curl_off_t downloadNow, - curl_off_t uploadTotal, - curl_off_t uploadNow) - { - return reinterpret_cast(downloader)-> - onProgress(dowloadTotal, downloadNow); - } -} - - -LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client): - LLThread("LLUpdateDownloader"), - mBandwidthLimit(0), - mCancelled(false), - mClient(client), - mCurl(), - mDownloadPercent(0), - mHeaderList(0) -{ - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case. - llverify(code == CURLE_OK); // TODO: real error handling here. -} - - -LLUpdateDownloader::Implementation::~Implementation() -{ - if(isDownloading()) - { - cancel(); - shutdown(); - } - else - { - ; // No op. - } - mCurl.reset(); -} - - -void LLUpdateDownloader::Implementation::cancel(void) -{ - mCancelled = true; -} - - -void LLUpdateDownloader::Implementation::download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required) -{ - if(isDownloading()) mClient.downloadError("download in progress"); - - mDownloadRecordPath = downloadMarkerPath(); - mDownloadData = LLSD(); - mDownloadData["required"] = required; - mDownloadData["update_channel"] = updateChannel; - mDownloadData["update_version"] = updateVersion; - if (!info_url.empty()) - { - mDownloadData["info_url"] = info_url; - } - try - { - startDownloading(uri, hash); - } - catch(DownloadError const & e) - { - mClient.downloadError(e.what()); - } -} - - -bool LLUpdateDownloader::Implementation::isDownloading(void) -{ - return !isStopped(); -} - - -void LLUpdateDownloader::Implementation::resume(void) -{ - mCancelled = false; - - if(isDownloading()) - { - mClient.downloadError("download in progress"); - } - - mDownloadRecordPath = downloadMarkerPath(); - llifstream dataStream(mDownloadRecordPath.c_str()); - if(!dataStream) - { - mClient.downloadError("no download marker"); - return; - } - - LLSDSerialize::fromXMLDocument(mDownloadData, dataStream); - - if(!mDownloadData.asBoolean()) - { - mClient.downloadError("no download information in marker"); - return; - } - - std::string filePath = mDownloadData["path"].asString(); - try - { - if(LLFile::isfile(filePath)) - { - llstat fileStatus; - LLFile::stat(filePath, &fileStatus); - if(fileStatus.st_size != mDownloadData["size"].asInteger()) - { - resumeDownloading(fileStatus.st_size); - } - else if(!validateOrRemove(filePath)) - { - download(LLURI(mDownloadData["url"].asString()), - mDownloadData["hash"].asString(), - mDownloadData["update_channel"].asString(), - mDownloadData["update_version"].asString(), - mDownloadData["info_url"].asString(), - mDownloadData["required"].asBoolean()); - } - else - { - mClient.downloadComplete(mDownloadData); - } - } - else - { - download(LLURI(mDownloadData["url"].asString()), - mDownloadData["hash"].asString(), - mDownloadData["update_channel"].asString(), - mDownloadData["update_version"].asString(), - mDownloadData["info_url"].asString(), - mDownloadData["required"].asBoolean()); - } - } - catch(DownloadError & e) - { - mClient.downloadError(e.what()); - } -} - - -void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond) -{ - if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) - { - llassert(static_cast(mCurl)); - mBandwidthLimit = bytesPerSecond; - CURLcode code = curl_easy_setopt(mCurl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit); - if(code != CURLE_OK) - { - LL_WARNS("UpdaterService") << "unable to change dowload bandwidth" << LL_ENDL; - } - } - else - { - mBandwidthLimit = bytesPerSecond; - } -} - - -size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size) -{ - char const * headerPtr = reinterpret_cast (buffer); - std::string header(headerPtr, headerPtr + size); - size_t colonPosition = header.find(':'); - if(colonPosition == std::string::npos) return size; // HTML response; ignore. - - if(header.substr(0, colonPosition) == "Content-Length") { - try { - size_t firstDigitPos = header.find_first_of("0123456789", colonPosition); - size_t lastDigitPos = header.find_last_of("0123456789"); - std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1); - size_t size = boost::lexical_cast(contentLength); - LL_INFOS("UpdaterService") << "download size is " << size << LL_ENDL; - - mDownloadData["size"] = LLSD(LLSD::Integer(size)); - llofstream odataStream(mDownloadRecordPath.c_str()); - LLSDSerialize::toPrettyXML(mDownloadData, odataStream); - } catch (std::exception const & e) { - LL_WARNS("UpdaterService") << "unable to read content length (" - << e.what() << ")" << LL_ENDL; - } - } else { - ; // No op. - } - - return size; -} - - -size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size) -{ - if(mCancelled) return 0; // Forces a write error which will halt curl thread. - if((size == 0) || (buffer == 0)) return 0; - - mDownloadStream.write(static_cast(buffer), size); - if(mDownloadStream.bad()) { - return 0; - } else { - return size; - } -} - - -int LLUpdateDownloader::Implementation::onProgress(curl_off_t downloadSize, curl_off_t bytesDownloaded) -{ - int downloadPercent = static_cast(100.0 * ((double) bytesDownloaded / (double) downloadSize)); - if(downloadPercent > mDownloadPercent) { - mDownloadPercent = downloadPercent; - - LLSD event; - event["pump"] = LLUpdaterService::pumpName(); - LLSD payload; - payload["type"] = LLSD(LLUpdaterService::PROGRESS); - payload["download_size"] = (LLSD::Integer) downloadSize; - payload["bytes_downloaded"] = (LLSD::Integer) bytesDownloaded; - event["payload"] = payload; - LLEventPumps::instance().obtain("mainlooprepeater").post(event); - - LL_INFOS("UpdaterService") << "progress event " << payload << LL_ENDL; - } else { - ; // Keep events to a reasonalbe number. - } - - return 0; -} - - -void LLUpdateDownloader::Implementation::run(void) -{ - CURLcode code = curl_easy_perform(mCurl.get()); - mDownloadStream.close(); - if(code == CURLE_OK) - { - LLFile::remove(mDownloadRecordPath); - if(validateOrRemove(mDownloadData["path"])) - { - LL_INFOS("UpdaterService") << "download successful" << LL_ENDL; - mClient.downloadComplete(mDownloadData); - } - else - { - mClient.downloadError("failed hash check"); - } - } - else if(mCancelled && (code == CURLE_WRITE_ERROR)) - { - LL_INFOS("UpdaterService") << "download canceled by user" << LL_ENDL; - // Do not call back client. - } - else - { - LL_WARNS("UpdaterService") << "download failed with error '" << - curl_easy_strerror(code) << "'" << LL_ENDL; - LLFile::remove(mDownloadRecordPath); - if(mDownloadData.has("path")) - { - std::string filePath = mDownloadData["path"].asString(); - LL_INFOS("UpdaterService") << "removing " << filePath << LL_ENDL; - LLFile::remove(filePath); - } - mClient.downloadError("curl error"); - } - - if(mHeaderList) - { - curl_slist_free_all(mHeaderList); - mHeaderList = 0; - } -} - - -void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader) -{ - if(!mCurl) - { - mCurl = LLCore::LLHttp::createEasyHandle(); - } - else - { - curl_easy_reset(mCurl.get()); - } - - if(!mCurl) - { - throw DownloadError("failed to initialize curl"); - } - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_NOSIGNAL, true)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_FOLLOWLOCATION, true)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_WRITEFUNCTION, &write_function)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_WRITEDATA, this)); - if(processHeader) - { - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HEADERFUNCTION, &header_function)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HEADERDATA, this)); - } - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HTTPGET, true)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_URL, url.c_str())); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_XFERINFOFUNCTION, &xferinfo_callback)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_XFERINFODATA, this)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_NOPROGRESS, 0)); - // if it's a required update set the bandwidth limit to 0 (unlimited) - curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit; - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, limit)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str())); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_SSL_VERIFYHOST, 2)); - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_SSL_VERIFYPEER, 1)); - - mDownloadPercent = 0; -} - - -void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) -{ - LL_INFOS("UpdaterService") << "resuming download from " << mDownloadData["url"].asString() - << " at byte " << startByte << LL_ENDL; - - initializeCurlGet(mDownloadData["url"].asString(), false); - - // The header 'Range: bytes n-' will request the bytes remaining in the - // source begining with byte n and ending with the last byte. - boost::format rangeHeaderFormat("Range: bytes=%u-"); - rangeHeaderFormat % startByte; - mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str()); - if(mHeaderList == 0) - { - throw DownloadError("cannot add Range header"); - } - throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HTTPHEADER, mHeaderList)); - - mDownloadStream.open(mDownloadData["path"].asString().c_str(), - std::ios_base::out | std::ios_base::binary | std::ios_base::app); - start(); -} - - -void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash) -{ - mDownloadData["url"] = uri.asString(); - mDownloadData["hash"] = hash; - mDownloadData["current_version"] = ll_get_version(); - LLSD path = uri.pathArray(); - if(path.size() == 0) throw DownloadError("no file path"); - std::string fileName = path[path.size() - 1].asString(); - std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName); - mDownloadData["path"] = filePath; - - LL_INFOS("UpdaterService") << "downloading " << filePath - << " from " << uri.asString() << LL_ENDL; - LL_INFOS("UpdaterService") << "hash of file is " << hash << LL_ENDL; - - llofstream dataStream(mDownloadRecordPath.c_str()); - LLSDSerialize::toPrettyXML(mDownloadData, dataStream); - - mDownloadStream.open(filePath.c_str(), std::ios_base::out | std::ios_base::binary); - initializeCurlGet(uri.asString(), true); - start(); -} - - -void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code) -{ - if(code != CURLE_OK) { - const char * errorString = curl_easy_strerror(code); - if(errorString != 0) { - throw DownloadError(curl_easy_strerror(code)); - } else { - throw DownloadError("unknown curl error"); - } - } else { - ; // No op. - } -} - -bool LLUpdateDownloader::Implementation::validateOrRemove(const std::string& filePath) -{ - bool valid = validateDownload(filePath); - if (! valid) - { - LL_INFOS("UpdaterService") << "removing " << filePath << LL_ENDL; - LLFile::remove(filePath); - } - return valid; -} - -bool LLUpdateDownloader::Implementation::validateDownload(const std::string& filePath) -{ - llifstream fileStream(filePath.c_str(), std::ios_base::in | std::ios_base::binary); - if(!fileStream) - { - LL_INFOS("UpdaterService") << "can't open " << filePath << ", invalid" << LL_ENDL; - return false; - } - - std::string hash = mDownloadData["hash"].asString(); - if (! hash.empty()) - { - char digest[33]; - LLMD5(fileStream).hex_digest(digest); - if (hash == digest) - { - LL_INFOS("UpdaterService") << "verified hash " << hash - << " for downloaded " << filePath << LL_ENDL; - return true; - } - else - { - LL_WARNS("UpdaterService") << "download hash mismatch for " - << filePath << ": expected " << hash - << " but computed " << digest << LL_ENDL; - return false; - } - } - else - { - LL_INFOS("UpdaterService") << "no hash specified for " << filePath - << ", unverified" << LL_ENDL; - return true; // No hash check provided. - } -} diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h deleted file mode 100644 index f759988f12..0000000000 --- a/indra/viewer_components/updater/llupdatedownloader.h +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @file llupdatedownloader.h - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#ifndef LL_UPDATE_DOWNLOADER_H -#define LL_UPDATE_DOWNLOADER_H - - -#include -#include -#include "lluri.h" - - -// -// An asynchronous download service for fetching updates. -// -class LLUpdateDownloader -{ -public: - class Client; - class Implementation; - - // Returns the path to the download marker file containing details of the - // latest download. - static std::string downloadMarkerPath(void); - - LLUpdateDownloader(Client & client); - - // Cancel any in progress download; a no op if none is in progress. The - // client will not receive a complete or error callback. - void cancel(void); - - // Start a new download. - void download(LLURI const & uri, - std::string const & hash, - std::string const & updateChannel, - std::string const & updateVersion, - std::string const & info_url, - bool required=false); - - // Returns true if a download is in progress. - bool isDownloading(void); - - // Resume a partial download. - void resume(void); - - // Set a limit on the dowload rate. - void setBandwidthLimit(U64 bytesPerSecond); - -private: - boost::shared_ptr mImplementation; -}; - - -// -// An interface to be implemented by clients initiating a update download. -// -class LLUpdateDownloader::Client { -public: - - // The download has completed successfully. - // data is a map containing the following items: - // url - source (remote) location - // hash - the md5 sum that should match the installer file. - // path - destination (local) location - // required - boolean indicating if this is a required update. - // size - the size of the installer in bytes - virtual void downloadComplete(LLSD const & data) = 0; - - // The download failed. - virtual void downloadError(std::string const & message) = 0; -}; - - -#endif diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp deleted file mode 100644 index a0e2c0b362..0000000000 --- a/indra/viewer_components/updater/llupdateinstaller.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @file llupdateinstaller.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#include "linden_common.h" -#include -#include "llapr.h" -#include "llprocess.h" -#include "llupdateinstaller.h" -#include "lldir.h" -#include "llsd.h" - -#if defined(LL_WINDOWS) -#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!). -#endif -#include - - -namespace { - class RelocateError {}; - - - std::string copy_to_temp(std::string const & path) - { - std::string scriptFile = gDirUtilp->getBaseFileName(path); - std::string newPath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, scriptFile); - apr_status_t status = apr_file_copy(path.c_str(), newPath.c_str(), APR_FILE_SOURCE_PERMS, gAPRPoolp); - if(status != APR_SUCCESS) throw RelocateError(); - - return newPath; - } -} - - -int ll_install_update(std::string const & script, - std::string const & updatePath, - bool required, - LLInstallScriptMode mode) -{ - std::string actualScriptPath; - switch(mode) { - case LL_COPY_INSTALL_SCRIPT_TO_TEMP: - try { - actualScriptPath = copy_to_temp(script); - } - catch (RelocateError &) { - return -1; - } - break; - case LL_RUN_INSTALL_SCRIPT_IN_PLACE: - actualScriptPath = script; - break; - default: - llassert(!"unpossible copy mode"); - } - - LL_INFOS("Updater") << "UpdateInstaller: installing " << updatePath << " using " << - actualScriptPath << LL_ENDL; - - LLProcess::Params params; - params.executable = actualScriptPath; - params.args.add(updatePath); - params.args.add(ll_install_failed_marker_path()); - params.args.add(boost::lexical_cast(required)); - params.autokill = false; - return LLProcess::create(params)? 0 : -1; -} - - -std::string const & ll_install_failed_marker_path(void) -{ - static std::string path; - if(path.empty()) { - path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLifeInstallFailed.marker"); - } - return path; -} diff --git a/indra/viewer_components/updater/llupdateinstaller.h b/indra/viewer_components/updater/llupdateinstaller.h deleted file mode 100644 index fe5b1d19b5..0000000000 --- a/indra/viewer_components/updater/llupdateinstaller.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file llupdateinstaller.h - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#ifndef LL_UPDATE_INSTALLER_H -#define LL_UPDATE_INSTALLER_H - - -#include - - -enum LLInstallScriptMode { - LL_RUN_INSTALL_SCRIPT_IN_PLACE, - LL_COPY_INSTALL_SCRIPT_TO_TEMP -}; - -// -// Launch the installation script. -// -// The updater will overwrite the current installation, so it is highly recommended -// that the current application terminate once this function is called. -// -int ll_install_update( - std::string const & script, // Script to execute. - std::string const & updatePath, // Path to update file. - bool required, // Is the update required. - LLInstallScriptMode mode=LL_COPY_INSTALL_SCRIPT_TO_TEMP); // Run in place or copy to temp? - - -// -// Returns the path which points to the failed install marker file, should it -// exist. -// -std::string const & ll_install_failed_marker_path(void); - - -#endif diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp deleted file mode 100644 index 788955a1b2..0000000000 --- a/indra/viewer_components/updater/llupdaterservice.cpp +++ /dev/null @@ -1,760 +0,0 @@ -/** - * @file llupdaterservice.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#include "linden_common.h" - -#include "llupdaterservice.h" - -#include "llupdatedownloader.h" -#include "llevents.h" -#include "lltimer.h" -#include "llupdatechecker.h" -#include "llupdateinstaller.h" - -#include -#include -#include "lldir.h" -#include "llsdserialize.h" -#include "llfile.h" -#include "llviewernetwork.h" - -#if LL_WINDOWS -#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally -#endif - -#if ! defined(LL_VIEWER_VERSION_MAJOR) \ - || ! defined(LL_VIEWER_VERSION_MINOR) \ - || ! defined(LL_VIEWER_VERSION_PATCH) \ - || ! defined(LL_VIEWER_VERSION_BUILD) -#error "Version information is undefined" -#endif - -namespace -{ - boost::weak_ptr gUpdater; - - const std::string UPDATE_MARKER_FILENAME("SecondLifeUpdateReady.xml"); - std::string update_marker_path() - { - return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - UPDATE_MARKER_FILENAME); - } - - std::string install_script_path(void) - { -#ifdef LL_WINDOWS - std::string scriptFile = "update_install.bat"; -#elif LL_DARWIN - std::string scriptFile = "update_install.py"; -#else - std::string scriptFile = "update_install"; -#endif - return gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, scriptFile); - } - - LLInstallScriptMode install_script_mode(void) - { -#ifdef LL_WINDOWS - return LL_COPY_INSTALL_SCRIPT_TO_TEMP; -#else - // This is important on Mac because update_install.py looks at its own - // script pathname to discover the viewer app bundle to update. - return LL_RUN_INSTALL_SCRIPT_IN_PLACE; -#endif - }; - -} - -class LLUpdaterServiceImpl : - public LLUpdateChecker::Client, - public LLUpdateDownloader::Client -{ - static const std::string sListenerName; - - std::string mProtocolVersion; - std::string mChannel; - std::string mVersion; - std::string mPlatform; - std::string mPlatformVersion; - unsigned char mUniqueId[MD5HEX_STR_SIZE]; - bool mWillingToTest; - - unsigned int mCheckPeriod; - bool mIsChecking; - bool mIsDownloading; - - LLUpdateChecker mUpdateChecker; - LLUpdateDownloader mUpdateDownloader; - LLTimer mTimer; - - LLUpdaterService::app_exit_callback_t mAppExitCallback; - - LLUpdaterService::eUpdaterState mState; - - LOG_CLASS(LLUpdaterServiceImpl); - -public: - LLUpdaterServiceImpl(); - virtual ~LLUpdaterServiceImpl(); - - void initialize(const std::string& channel, - const std::string& version, - const std::string& platform, - const std::string& platform_version, - const unsigned char uniqueid[MD5HEX_STR_SIZE], - const bool& willing_to_test - ); - - void setCheckPeriod(unsigned int seconds); - void setBandwidthLimit(U64 bytesPerSecond); - - void startChecking(bool install_if_ready); - void stopChecking(); - bool forceCheck(); - bool isChecking(); - LLUpdaterService::eUpdaterState getState(); - - void setAppExitCallback(LLUpdaterService::app_exit_callback_t aecb) { mAppExitCallback = aecb;} - std::string updatedVersion(void); - - bool checkForInstall(bool launchInstaller); // Test if a local install is ready. - bool checkForResume(); // Test for resumeable d/l. - - // LLUpdateChecker::Client: - virtual void error(std::string const & message); - - // A successful response was received from the viewer version manager - virtual void response(LLSD const & content); - - // LLUpdateDownloader::Client - void downloadComplete(LLSD const & data); - void downloadError(std::string const & message); - - bool onMainLoop(LLSD const & event); - -private: - std::string mNewChannel; - std::string mNewVersion; - - void restartTimer(unsigned int seconds); - void setState(LLUpdaterService::eUpdaterState state); - void stopTimer(); -}; - -const std::string LLUpdaterServiceImpl::sListenerName = "LLUpdaterServiceImpl"; - -LLUpdaterServiceImpl::LLUpdaterServiceImpl() : - mIsChecking(false), - mIsDownloading(false), - mCheckPeriod(0), - mUpdateChecker(*this), - mUpdateDownloader(*this), - mState(LLUpdaterService::INITIAL) -{ -} - -LLUpdaterServiceImpl::~LLUpdaterServiceImpl() -{ - LL_INFOS("UpdaterService") << "shutting down updater service" << LL_ENDL; - LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName); -} - -void LLUpdaterServiceImpl::initialize(const std::string& channel, - const std::string& version, - const std::string& platform, - const std::string& platform_version, - const unsigned char uniqueid[MD5HEX_STR_SIZE], - const bool& willing_to_test) -{ - if(mIsChecking || mIsDownloading) - { - throw LLUpdaterService::UsageError("LLUpdaterService::initialize call " - "while updater is running."); - } - - mChannel = channel; - mVersion = version; - mPlatform = platform; - mPlatformVersion = platform_version; - memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE); - mWillingToTest = willing_to_test; - LL_DEBUGS("UpdaterService") - << "\n channel: " << mChannel - << "\n version: " << mVersion - << "\n uniqueid: " << mUniqueId - << "\n willing: " << ( mWillingToTest ? "testok" : "testno" ) - << LL_ENDL; -} - -void LLUpdaterServiceImpl::setCheckPeriod(unsigned int seconds) -{ - mCheckPeriod = seconds; -} - -void LLUpdaterServiceImpl::setBandwidthLimit(U64 bytesPerSecond) -{ - mUpdateDownloader.setBandwidthLimit(bytesPerSecond); -} - -void LLUpdaterServiceImpl::startChecking(bool install_if_ready) -{ - if(mChannel.empty() || mVersion.empty()) - { - throw LLUpdaterService::UsageError("Set params before call to " - "LLUpdaterService::startCheck()."); - } - - mIsChecking = true; - - // Check to see if an install is ready. - bool has_install = checkForInstall(install_if_ready); - if(!has_install) - { - checkForResume(); // will set mIsDownloading to true if resuming - - if(!mIsDownloading) - { - setState(LLUpdaterService::CHECKING_FOR_UPDATE); - - // Checking can only occur during the mainloop. - // reset the timer to 0 so that the next mainloop event - // triggers a check; - restartTimer(0); - } - else - { - setState(LLUpdaterService::DOWNLOADING); - } - } -} - -void LLUpdaterServiceImpl::stopChecking() -{ - if(mIsChecking) - { - mIsChecking = false; - stopTimer(); - } - - if(mIsDownloading) - { - mUpdateDownloader.cancel(); - mIsDownloading = false; - } - - setState(LLUpdaterService::TERMINAL); -} - -bool LLUpdaterServiceImpl::forceCheck() -{ - if (!mIsDownloading && getState() != LLUpdaterService::CHECKING_FOR_UPDATE) - { - if (mIsChecking) - { - // Service is running, just reset the timer - if (mTimer.getStarted()) - { - mTimer.setTimerExpirySec(0); - setState(LLUpdaterService::CHECKING_FOR_UPDATE); - return true; - } - } - else if (!mChannel.empty() && !mVersion.empty()) - { - // one time check - bool has_install = checkForInstall(false); - if (!has_install) - { - std::string query_url = LLGridManager::getInstance()->getUpdateServiceURL(); - if (!query_url.empty()) - { - setState(LLUpdaterService::CHECKING_FOR_UPDATE); - mUpdateChecker.checkVersion(query_url, mChannel, mVersion, - mPlatform, mPlatformVersion, mUniqueId, - mWillingToTest); - return true; - } - else - { - LL_WARNS("UpdaterService") - << "No updater service defined for grid '" << LLGridManager::getInstance()->getGrid() << LL_ENDL; - } - } - } - } - return false; -} - -bool LLUpdaterServiceImpl::isChecking() -{ - return mIsChecking; -} - -LLUpdaterService::eUpdaterState LLUpdaterServiceImpl::getState() -{ - return mState; -} - -std::string LLUpdaterServiceImpl::updatedVersion(void) -{ - return mNewVersion; -} - -bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller) -{ - bool foundInstall = false; // return true if install is found. - - llifstream update_marker(update_marker_path().c_str(), - std::ios::in | std::ios::binary); - - if(update_marker.is_open()) - { - // Found an update info - now lets see if its valid. - LLSD update_info; - LLSDSerialize::fromXMLDocument(update_info, update_marker); - update_marker.close(); - - // Get the path to the installer file. - std::string path(update_info.get("path")); - std::string downloader_version(update_info["current_version"]); - if (downloader_version != ll_get_version()) - { - // This viewer is not the same version as the one that downloaded - // the update. Do not install this update. - LL_INFOS("UpdaterService") << "ignoring update downloaded by " - << "different viewer version " - << downloader_version << LL_ENDL; - if (! path.empty()) - { - LL_INFOS("UpdaterService") << "removing " << path << LL_ENDL; - LLFile::remove(path); - LLFile::remove(update_marker_path()); - } - - foundInstall = false; - } - else if (path.empty()) - { - LL_WARNS("UpdaterService") << "Marker file " << update_marker_path() - << " 'path' entry empty, ignoring" << LL_ENDL; - foundInstall = false; - } - else if (! LLFile::isfile(path)) - { - LL_WARNS("UpdaterService") << "Nonexistent installer " << path - << ", ignoring" << LL_ENDL; - foundInstall = false; - } - else - { - if(launchInstaller) - { - setState(LLUpdaterService::INSTALLING); - - LLFile::remove(update_marker_path()); - - int result = ll_install_update(install_script_path(), - path, - update_info["required"].asBoolean(), - install_script_mode()); - - if((result == 0) && mAppExitCallback) - { - mAppExitCallback(); - } - else if(result != 0) - { - LL_WARNS("UpdaterService") << "failed to run update install script" << LL_ENDL; - } - else - { - ; // No op. - } - } - - foundInstall = true; - } - } - return foundInstall; -} - -bool LLUpdaterServiceImpl::checkForResume() -{ - bool result = false; - std::string download_marker_path = mUpdateDownloader.downloadMarkerPath(); - if(LLFile::isfile(download_marker_path)) - { - llifstream download_marker_stream(download_marker_path.c_str(), - std::ios::in | std::ios::binary); - if(download_marker_stream.is_open()) - { - LLSD download_info; - LLSDSerialize::fromXMLDocument(download_info, download_marker_stream); - download_marker_stream.close(); - std::string downloader_version(download_info["current_version"]); - if (downloader_version == ll_get_version()) - { - mIsDownloading = true; - mNewVersion = download_info["update_version"].asString(); - mNewChannel = download_info["update_channel"].asString(); - mUpdateDownloader.resume(); - result = true; - } - else - { - // The viewer that started this download is not the same as this viewer; ignore. - LL_INFOS("UpdaterService") << "ignoring partial download " - << "from different viewer version " - << downloader_version << LL_ENDL; - std::string path = download_info["path"].asString(); - if(!path.empty()) - { - LL_INFOS("UpdaterService") << "removing " << path << LL_ENDL; - LLFile::remove(path); - } - LLFile::remove(download_marker_path); - } - } - } - return result; -} - -void LLUpdaterServiceImpl::error(std::string const & message) -{ - setState(LLUpdaterService::TEMPORARY_ERROR); - if(mIsChecking) - { - restartTimer(mCheckPeriod); - } -} - -// A successful response was received from the viewer version manager -void LLUpdaterServiceImpl::response(LLSD const & content) -{ - if(!content.asBoolean()) // an empty response means "no update" - { - LL_INFOS("UpdaterService") << "up to date" << LL_ENDL; - if(mIsChecking) - { - restartTimer(mCheckPeriod); - } - - setState(LLUpdaterService::UP_TO_DATE); - } - else if ( content.isMap() && content.has("url") ) - { - // there is an update available... - stopTimer(); - mNewChannel = content["channel"].asString(); - if (mNewChannel.empty()) - { - LL_INFOS("UpdaterService") << "no channel supplied, assuming current channel" << LL_ENDL; - mNewChannel = mChannel; - } - mNewVersion = content["version"].asString(); - mIsDownloading = true; - setState(LLUpdaterService::DOWNLOADING); - BOOL required = content["required"].asBoolean(); - LLURI url(content["url"].asString()); - std::string more_info = content["more_info"].asString(); - LL_DEBUGS("UpdaterService") - << "Starting download of " - << ( required ? "required" : "optional" ) << " update" - << " to channel '" << mNewChannel << "' version " << mNewVersion - << " more info '" << more_info << "'" - << LL_ENDL; - mUpdateDownloader.download(url, content["hash"].asString(), mNewChannel, mNewVersion, more_info, required); - } - else - { - LL_WARNS("UpdaterService") << "Invalid update query response ignored; retry in " - << mCheckPeriod << " seconds" << LL_ENDL; - setState(LLUpdaterService::TEMPORARY_ERROR); - if (mIsChecking) - { - restartTimer(mCheckPeriod); - } - } -} - -void LLUpdaterServiceImpl::downloadComplete(LLSD const & data) -{ - mIsDownloading = false; - - // Save out the download data to the SecondLifeUpdateReady - // marker file. - llofstream update_marker(update_marker_path().c_str()); - LLSDSerialize::toPrettyXML(data, update_marker); - - LLSD event; - event["pump"] = LLUpdaterService::pumpName(); - LLSD payload; - payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE); - payload["required"] = data["required"]; - payload["version"] = mNewVersion; - payload["channel"] = mNewChannel; - payload["info_url"] = data["info_url"]; - event["payload"] = payload; - LL_DEBUGS("UpdaterService") - << "Download complete " - << ( data["required"].asBoolean() ? "required" : "optional" ) - << " channel " << mNewChannel - << " version " << mNewVersion - << " info " << data["info_url"].asString() - << LL_ENDL; - - LLEventPumps::instance().obtain("mainlooprepeater").post(event); - - setState(LLUpdaterService::TERMINAL); -} - -void LLUpdaterServiceImpl::downloadError(std::string const & message) -{ - LL_INFOS("UpdaterService") << "Error downloading: " << message << LL_ENDL; - - mIsDownloading = false; - - // Restart the timer on error - if(mIsChecking) - { - restartTimer(mCheckPeriod); - } - - LLSD event; - event["pump"] = LLUpdaterService::pumpName(); - LLSD payload; - payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_ERROR); - payload["message"] = message; - event["payload"] = payload; - LLEventPumps::instance().obtain("mainlooprepeater").post(event); - - setState(LLUpdaterService::FAILURE); -} - -void LLUpdaterServiceImpl::restartTimer(unsigned int seconds) -{ - LL_INFOS("UpdaterService") << "will check for update again in " << - seconds << " seconds" << LL_ENDL; - mTimer.start(); - mTimer.setTimerExpirySec((F32)seconds); - LLEventPumps::instance().obtain("mainloop").listen( - sListenerName, boost::bind(&LLUpdaterServiceImpl::onMainLoop, this, _1)); -} - -void LLUpdaterServiceImpl::setState(LLUpdaterService::eUpdaterState state) -{ - if(state != mState) - { - mState = state; - - LLSD event; - event["pump"] = LLUpdaterService::pumpName(); - LLSD payload; - payload["type"] = LLSD(LLUpdaterService::STATE_CHANGE); - payload["state"] = state; - event["payload"] = payload; - LLEventPumps::instance().obtain("mainlooprepeater").post(event); - - LL_INFOS("UpdaterService") << "setting state to " << state << LL_ENDL; - } - else - { - ; // State unchanged; noop. - } -} - -void LLUpdaterServiceImpl::stopTimer() -{ - mTimer.stop(); - LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName); -} - -bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event) -{ - if(mTimer.getStarted() && mTimer.hasExpired()) - { - stopTimer(); - - // Check for failed install. - if(LLFile::isfile(ll_install_failed_marker_path())) - { - LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL; - int requiredValue = 0; - { - llifstream stream(ll_install_failed_marker_path().c_str()); - stream >> requiredValue; - if(stream.fail()) - { - requiredValue = 0; - } - } - // TODO: notify the user. - LL_WARNS("UpdaterService") << "last install attempt failed" << LL_ENDL;; - LLFile::remove(ll_install_failed_marker_path()); - - LLSD event; - event["type"] = LLSD(LLUpdaterService::INSTALL_ERROR); - event["required"] = LLSD(requiredValue); - LLEventPumps::instance().obtain(LLUpdaterService::pumpName()).post(event); - - setState(LLUpdaterService::TERMINAL); - } - else - { - std::string query_url = LLGridManager::getInstance()->getUpdateServiceURL(); - if ( !query_url.empty() ) - { - mUpdateChecker.checkVersion(query_url, mChannel, mVersion, - mPlatform, mPlatformVersion, mUniqueId, - mWillingToTest); - setState(LLUpdaterService::CHECKING_FOR_UPDATE); - } - else - { - LL_WARNS("UpdaterService") - << "No updater service defined for grid '" << LLGridManager::getInstance()->getGrid() - << "' will check again in " << mCheckPeriod << " seconds" - << LL_ENDL; - // Because the grid can be changed after the viewer is started (when the first check takes place) - // but before the user logs in, the next check may be on a different grid, so set the retry timer - // even though this check did not happen. The default time is once an hour, and if we're not - // doing the check anyway the performance impact is completely insignificant. - restartTimer(mCheckPeriod); - } - } - } - else - { - // Keep on waiting... - } - - return false; -} - - -//----------------------------------------------------------------------- -// Facade interface - -std::string const & LLUpdaterService::pumpName(void) -{ - static std::string name("updater_service"); - return name; -} - -bool LLUpdaterService::updateReadyToInstall(void) -{ - return LLFile::isfile(update_marker_path()); -} - -LLUpdaterService::LLUpdaterService() -{ - if(gUpdater.expired()) - { - mImpl = - boost::shared_ptr(new LLUpdaterServiceImpl()); - gUpdater = mImpl; - } - else - { - mImpl = gUpdater.lock(); - } -} - -LLUpdaterService::~LLUpdaterService() -{ -} - -void LLUpdaterService::initialize(const std::string& channel, - const std::string& version, - const std::string& platform, - const std::string& platform_version, - const unsigned char uniqueid[MD5HEX_STR_SIZE], - const bool& willing_to_test -) -{ - mImpl->initialize(channel, version, platform, platform_version, uniqueid, willing_to_test); -} - -void LLUpdaterService::setCheckPeriod(unsigned int seconds) -{ - mImpl->setCheckPeriod(seconds); -} - -void LLUpdaterService::setBandwidthLimit(U64 bytesPerSecond) -{ - mImpl->setBandwidthLimit(bytesPerSecond); -} - -void LLUpdaterService::startChecking(bool install_if_ready) -{ - mImpl->startChecking(install_if_ready); -} - -void LLUpdaterService::stopChecking() -{ - mImpl->stopChecking(); -} - -bool LLUpdaterService::forceCheck() -{ - return mImpl->forceCheck(); -} - -bool LLUpdaterService::isChecking() -{ - return mImpl->isChecking(); -} - -LLUpdaterService::eUpdaterState LLUpdaterService::getState() -{ - return mImpl->getState(); -} - -void LLUpdaterService::setImplAppExitCallback(LLUpdaterService::app_exit_callback_t aecb) -{ - return mImpl->setAppExitCallback(aecb); -} - -std::string LLUpdaterService::updatedVersion(void) -{ - return mImpl->updatedVersion(); -} - - -std::string const & ll_get_version(void) { - static std::string version(""); - - if (version.empty()) { - std::ostringstream stream; - stream << LL_VIEWER_VERSION_MAJOR << "." - << LL_VIEWER_VERSION_MINOR << "." - << LL_VIEWER_VERSION_PATCH << "." - << LL_VIEWER_VERSION_BUILD; - version = stream.str(); - } - - return version; -} - diff --git a/indra/viewer_components/updater/llupdaterservice.h b/indra/viewer_components/updater/llupdaterservice.h deleted file mode 100644 index 95bbe1695c..0000000000 --- a/indra/viewer_components/updater/llupdaterservice.h +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @file llupdaterservice.h - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -#ifndef LL_UPDATERSERVICE_H -#define LL_UPDATERSERVICE_H - -#include -#include -#include "llhasheduniqueid.h" - -class LLUpdaterServiceImpl; - -class LLUpdaterService -{ -public: - class UsageError: public std::runtime_error - { - public: - UsageError(const std::string& msg) : std::runtime_error(msg) {} - }; - - // Name of the event pump through which update events will be delivered. - static std::string const & pumpName(void); - - // Returns true if an update has been completely downloaded and is now ready to install. - static bool updateReadyToInstall(void); - - // Type codes for events posted by this service. Stored the event's 'type' element. - enum eUpdaterEvent { - INVALID, - DOWNLOAD_COMPLETE, - DOWNLOAD_ERROR, - INSTALL_ERROR, - PROGRESS, - STATE_CHANGE - }; - - enum eUpdaterState { - INITIAL, - CHECKING_FOR_UPDATE, - TEMPORARY_ERROR, - DOWNLOADING, - INSTALLING, - UP_TO_DATE, - TERMINAL, - FAILURE - }; - - LLUpdaterService(); - ~LLUpdaterService(); - - void initialize(const std::string& channel, - const std::string& version, - const std::string& platform, - const std::string& platform_version, - const unsigned char uniqueid[MD5HEX_STR_SIZE], - const bool& willing_to_test - ); - - void setCheckPeriod(unsigned int seconds); - void setBandwidthLimit(U64 bytesPerSecond); - - void startChecking(bool install_if_ready = false); - void stopChecking(); - bool forceCheck(); - bool isChecking(); - eUpdaterState getState(); - - typedef boost::function app_exit_callback_t; - template - void setAppExitCallback(F const &callable) - { - app_exit_callback_t aecb = callable; - setImplAppExitCallback(aecb); - } - - // If an update is or has been downloaded, this method will return the - // version string for that update. An empty string will be returned - // otherwise. - std::string updatedVersion(void); - -private: - boost::shared_ptr mImpl; - void setImplAppExitCallback(app_exit_callback_t aecb); -}; - -// Returns the full version as a string. -std::string const & ll_get_version(void); - -#endif // LL_UPDATERSERVICE_H diff --git a/indra/viewer_components/updater/scripts/darwin/janitor.py b/indra/viewer_components/updater/scripts/darwin/janitor.py deleted file mode 100644 index cdf33df731..0000000000 --- a/indra/viewer_components/updater/scripts/darwin/janitor.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/python -"""\ -@file janitor.py -@author Nat Goodspeed -@date 2011-09-14 -@brief Janitor class to clean up arbitrary resources - -2013-01-04 cloned from vita because it's exactly what update_install.py needs. - -$LicenseInfo:firstyear=2011&license=viewerlgpl$ -Copyright (c) 2011, Linden Research, Inc. -$/LicenseInfo$ -""" - -import sys -import functools -import itertools - -class Janitor(object): - """ - Usage: - - Basic: - self.janitor = Janitor(sys.stdout) # report cleanup actions on stdout - ... - self.janitor.later(os.remove, some_temp_file) - self.janitor.later(os.remove, some_other_file) - ... - self.janitor.cleanup() # perform cleanup actions - - Context Manager: - with Janitor() as janitor: # clean up quietly - ... - janitor.later(shutil.rmtree, some_temp_directory) - ... - # exiting 'with' block performs cleanup - - Test Class: - class TestMySoftware(unittest.TestCase, Janitor): - def __init__(self): - Janitor.__init__(self) # quiet cleanup - ... - - def setUp(self): - ... - self.later(os.rename, saved_file, original_location) - ... - - def tearDown(self): - Janitor.tearDown(self) # calls cleanup() - ... - # Or, if you have no other tearDown() logic for - # TestMySoftware, you can omit the TestMySoftware.tearDown() - # def entirely and let it inherit Janitor.tearDown(). - """ - def __init__(self, stream=None): - """ - If you pass stream= (e.g.) sys.stdout or sys.stderr, Janitor will - report its cleanup operations as it performs them. If you don't, it - will perform them quietly -- unless one or more of the actions throws - an exception, in which case you'll get output on stderr. - """ - self.stream = stream - self.cleanups = [] - - def later(self, func, *args, **kwds): - """ - Pass the callable you want to call at cleanup() time, plus any - positional or keyword args you want to pass it. - """ - # Get a name string for 'func' - try: - # A free function has a __name__ - name = func.__name__ - except AttributeError: - try: - # A class object (even builtin objects like ints!) support - # __class__.__name__ - name = func.__class__.__name__ - except AttributeError: - # Shrug! Just use repr() to get a string describing this func. - name = repr(func) - # Construct a description of this operation in Python syntax from - # args, kwds. - desc = "%s(%s)" % \ - (name, ", ".join(itertools.chain((repr(a) for a in args), - ("%s=%r" % (k, v) for (k, v) in kwds.iteritems())))) - # Use functools.partial() to bind passed args and keywords to the - # passed func so we get a nullary callable that does what caller - # wants. - bound = functools.partial(func, *args, **kwds) - self.cleanups.append((desc, bound)) - - def cleanup(self): - """ - Perform all the actions saved with later() calls. - """ - # Typically one allocates resource A, then allocates resource B that - # depends on it. In such a scenario it's appropriate to delete B - # before A -- so perform cleanup actions in reverse order. (This is - # the same strategy used by atexit().) - while self.cleanups: - # Until our list is empty, pop the last pair. - desc, bound = self.cleanups.pop(-1) - - # If requested, report the action. - if self.stream is not None: - print >>self.stream, desc - - try: - # Call the bound callable - bound() - except Exception, err: - # This is cleanup. Report the problem but continue. - print >>(self.stream or sys.stderr), "Calling %s\nraised %s: %s" % \ - (desc, err.__class__.__name__, err) - - def tearDown(self): - """ - If a unittest.TestCase subclass (or a nose test class) adds Janitor as - one of its base classes, and has no other tearDown() logic, let it - inherit Janitor.tearDown(). - """ - self.cleanup() - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - # Perform cleanup no matter how we exit this 'with' statement - self.cleanup() - # Propagate any exception from the 'with' statement, don't swallow it - return False diff --git a/indra/viewer_components/updater/scripts/darwin/messageframe.py b/indra/viewer_components/updater/scripts/darwin/messageframe.py deleted file mode 100644 index 8f58848882..0000000000 --- a/indra/viewer_components/updater/scripts/darwin/messageframe.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python -"""\ -@file messageframe.py -@author Nat Goodspeed -@date 2013-01-03 -@brief Define MessageFrame class for popping up messages from a command-line - script. - -$LicenseInfo:firstyear=2013&license=viewerlgpl$ -Copyright (c) 2013, Linden Research, Inc. -$/LicenseInfo$ -""" - -import Tkinter as tk -import os - -# Tricky way to obtain the filename of the main script (default title string) -import __main__ - -# This class is intended for displaying messages from a command-line script. -# Getting the base class right took a bit of trial and error. -# If you derive from tk.Frame, the destroy() method doesn't actually close it. -# If you derive from tk.Toplevel, it pops up a separate Tk frame too. destroy() -# closes this frame, but not that one. -# Deriving from tk.Tk appears to do the right thing. -class MessageFrame(tk.Tk): - def __init__(self, text="", title=os.path.splitext(os.path.basename(__main__.__file__))[0], - width=320, height=120): - tk.Tk.__init__(self) - self.grid() - self.title(title) - self.var = tk.StringVar() - self.var.set(text) - self.msg = tk.Label(self, textvariable=self.var) - self.msg.grid() - # from http://stackoverflow.com/questions/3352918/how-to-center-a-window-on-the-screen-in-tkinter : - self.update_idletasks() - - # The constants below are to adjust for typical overhead from the - # frame borders. - xp = (self.winfo_screenwidth() / 2) - (width / 2) - 8 - yp = (self.winfo_screenheight() / 2) - (height / 2) - 20 - self.geometry('{0}x{1}+{2}+{3}'.format(width, height, xp, yp)) - self.update() - - def set(self, text): - self.var.set(text) - self.update() - -if __name__ == "__main__": - # When run as a script, just test the MessageFrame. - import sys - import time - - frame = MessageFrame("something in the way she moves....") - time.sleep(3) - frame.set("smaller") - time.sleep(3) - frame.set("""this has -several -lines""") - time.sleep(3) - frame.destroy() - print "Destroyed!" - sys.stdout.flush() - time.sleep(3) diff --git a/indra/viewer_components/updater/scripts/darwin/update_install.py b/indra/viewer_components/updater/scripts/darwin/update_install.py deleted file mode 100755 index 08f4f0ebb9..0000000000 --- a/indra/viewer_components/updater/scripts/darwin/update_install.py +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/python -"""\ -@file update_install.py -@author Nat Goodspeed -@date 2012-12-20 -@brief Update the containing Second Life application bundle to the version in - the specified disk image file. - - This Python implementation is derived from the previous mac-updater - application, a funky mix of C++, classic C and Objective-C. - -$LicenseInfo:firstyear=2012&license=viewerlgpl$ -Copyright (c) 2012, Linden Research, Inc. -$/LicenseInfo$ -""" - -import os -import sys -import cgitb -from contextlib import contextmanager -import errno -import glob -import plistlib -import re -import shutil -import subprocess -import tempfile -import time -from janitor import Janitor -from messageframe import MessageFrame -import Tkinter, tkMessageBox - -TITLE = "Second Life Viewer Updater" -# Magic bundle identifier used by all Second Life viewer bundles -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") - -# Global handle to the MessageFrame so we can update message -FRAME = None -# Global handle to logfile, once it's open -LOGF = None - -# **************************************************************************** -# Logging and messaging -# -# This script is normally run implicitly by the old viewer to update to the -# new viewer. Its UI consists of a MessageFrame and possibly a Tk error box. -# Log details to updater.log -- especially uncaught exceptions! -# **************************************************************************** -def log(message): - """write message only to LOGF (also called by status() and fail())""" - # If we don't even have LOGF open yet, at least write to Console log - logf = LOGF or sys.stderr - logf.writelines((time.strftime("%Y-%m-%dT%H:%M:%SZ ", time.gmtime()), message, '\n')) - logf.flush() - -def status(message): - """display and log normal progress message""" - log(message) - - global FRAME - if not FRAME: - FRAME = MessageFrame(message, TITLE) - else: - FRAME.set(message) - -def fail(message): - """log message, produce error box, then terminate with nonzero rc""" - log(message) - - # If we haven't yet called status() (we don't yet have a FRAME), perform a - # bit of trickery to bypass the spurious "main window" that Tkinter would - # otherwise pop up if the first call is showerror(). - if not FRAME: - root = Tkinter.Tk() - root.withdraw() - - # If we do have a LOGF available, mention it in the error box. - if LOGF: - message = "%s\n(Updater log in %s)" % (message, LOGF.name) - - # We explicitly specify the WARNING icon because, at least on the Tkinter - # bundled with the system-default Python 2.7 on Mac OS X 10.7.4, the - # ERROR, QUESTION and INFO icons are all the silly Tk rocket ship. At - # least WARNING has an exclamation in a yellow triangle, even though - # overlaid by a smaller image of the rocket ship. - tkMessageBox.showerror(TITLE, -"""An error occurred while updating Second Life: -%s -Please download the latest viewer from www.secondlife.com.""" % message, - icon=tkMessageBox.WARNING) - sys.exit(1) - -def exception(err): - """call fail() with an exception instance""" - fail("%s exception: %s" % (err.__class__.__name__, str(err))) - -def excepthook(type, value, traceback): - """ - Store this hook function into sys.excepthook until we have a logfile. - """ - # At least in older Python versions, it could be tricky to produce a - # string from 'type' and 'value'. For instance, an OSError exception would - # pass type=OSError and value=some_tuple. Empirically, this funky - # expression seems to work. - exception(type(*value)) -sys.excepthook = excepthook - -class ExceptHook(object): - """ - Store an instance of this class into sys.excepthook once we have a logfile - open. - """ - def __init__(self, logfile): - # There's no magic to the cgitb.enable() function -- it merely stores - # an instance of cgitb.Hook into sys.excepthook, passing enable()'s - # params into Hook.__init__(). Sadly, enable() doesn't forward all its - # params using (*args, **kwds) syntax -- another story. But the point - # is that all the goodness is in the cgitb.Hook class. Capture an - # instance. - self.hook = cgitb.Hook(file=logfile, format="text") - - def __call__(self, type, value, traceback): - # produce nice text traceback to logfile - self.hook(type, value, traceback) - # Now display an error box. - excepthook(type, value, traceback) - -def write_marker(markerfile, markertext): - log("writing %r to %s" % (markertext, markerfile)) - try: - with open(markerfile, "w") as markerf: - markerf.write(markertext) - except IOError, err: - # write_marker() is invoked by fail(), and fail() is invoked by other - # error-handling functions. If we try to invoke any of those, we'll - # get infinite recursion. If for any reason we can't write markerfile, - # try to log it -- otherwise shrug. - log("%s exception: %s" % (err.__class__.__name__, err)) - -# **************************************************************************** -# Utility -# **************************************************************************** -@contextmanager -def allow_errno(errn): - """ - Execute body of 'with' statement, accepting OSError with specific errno - 'errn'. Propagate any other exception, or an OSError with any other errno. - """ - try: - # run the body of the 'with' statement - yield - except OSError, err: - # unless errno == passed errn, re-raise the exception - if err.errno != errn: - raise - -# **************************************************************************** -# Main script logic -# **************************************************************************** -def main(dmgfile, markerfile, markertext): - # Should we fail, we're supposed to write 'markertext' to 'markerfile'. - # Wrap the fail() function so we do that. - global fail - oldfail = fail - def fail(message): - write_marker(markerfile, markertext) - oldfail(message) - - try: - # Starting with the Cocoafied viewer, we'll find viewer logs in - # ~/Library/Application Support/$CFBundleIdentifier/logs rather than in - # ~/Library/Application Support/SecondLife/logs as before. This could be - # obnoxious -- but we Happen To Know that markerfile is a path specified - # within the viewer's logs directory. Use that. - logsdir = os.path.dirname(markerfile) - - # Move the old updater.log file out of the way - logname = os.path.join(logsdir, "updater.log") - # Nonexistence is okay. Anything else, not so much. - with allow_errno(errno.ENOENT): - os.rename(logname, logname + ".old") - - # Open new updater.log. - global LOGF - LOGF = open(logname, "w") - - # Now that LOGF is in fact open for business, use it to log any further - # uncaught exceptions. - sys.excepthook = ExceptHook(LOGF) - - # log how this script was invoked - log(' '.join(repr(arg) for arg in sys.argv)) - - # prepare for other cleanup - with Janitor(LOGF) as janitor: - - # Under some circumstances, this script seems to be invoked with a - # nonexistent pathname. Check for that. - if not os.path.isfile(dmgfile): - fail(dmgfile + " has been deleted") - - # Try to derive the name of the running viewer app bundle from our - # own pathname. (Hopefully the old viewer won't copy this script - # to a temp dir before running!) - # Somewhat peculiarly, this script is currently packaged in - # Appname.app/Contents/MacOS with the viewer executable. But even - # if we decide to move it to Appname.app/Contents/Resources, we'll - # still find Appname.app two levels up from dirname(__file__). - appdir = os.path.abspath(os.path.join(os.path.dirname(__file__), - os.pardir, os.pardir)) - if not appdir.endswith(".app"): - # This can happen if either this script has been copied before - # being executed, or if it's in an unexpected place in the app - # bundle. - fail(appdir + " is not an application directory") - - # We need to install into appdir's parent directory -- can we? - installdir = os.path.abspath(os.path.join(appdir, os.pardir)) - if not os.access(installdir, os.W_OK): - fail("Can't modify " + installdir) - - # invent a temporary directory - tempdir = tempfile.mkdtemp() - log("created " + tempdir) - # clean it up when we leave - janitor.later(shutil.rmtree, tempdir) - - status("Mounting image...") - - mntdir = os.path.join(tempdir, "mnt") - log("mkdir " + mntdir) - os.mkdir(mntdir) - command = ["hdiutil", "attach", dmgfile, "-mountpoint", mntdir] - log(' '.join(command)) - # Instantiating subprocess.Popen launches a child process with the - # specified command line. stdout=PIPE passes a pipe to its stdout. - hdiutil = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=LOGF) - # Popen.communicate() reads that pipe until the child process - # terminates, returning (stdout, stderr) output. Select just stdout. - hdiutil_out = hdiutil.communicate()[0] - if hdiutil.returncode != 0: - fail("Couldn't mount " + dmgfile) - # hdiutil should report the devnode. Find that. - found = re.search(r"/dev/[^ ]*\b", hdiutil_out) - if not found: - # If we don't spot the devnode, log it and continue -- we only - # use it to detach it. Don't fail the whole update if we can't - # clean up properly. - log("Couldn't spot devnode in hdiutil output:\n" + hdiutil_out) - else: - # If we do spot the devnode, detach it when done. - janitor.later(subprocess.call, ["hdiutil", "detach", found.group(0)], - stdout=LOGF, stderr=subprocess.STDOUT) - - status("Searching for app bundle...") - - for candidate in glob.glob(os.path.join(mntdir, "*.app")): - log("Considering " + candidate) - try: - # By convention, a valid Mac app bundle has a - # Contents/Info.plist file containing at least - # CFBundleIdentifier. - CFBundleIdentifier = \ - plistlib.readPlist(os.path.join(candidate, "Contents", - "Info.plist"))["CFBundleIdentifier"] - except Exception, err: - # might be IOError, xml.parsers.expat.ExpatError, KeyError - # Any of these means it's not a valid app bundle. Instead - # of aborting, just skip this candidate and continue. - log("%s not a valid app bundle: %s: %s" % - (candidate, err.__class__.__name__, err)) - continue - - if CFBundleIdentifier == BUNDLE_IDENTIFIER: - break - - log("unrecognized CFBundleIdentifier: " + CFBundleIdentifier) - - else: - fail("Could not find Second Life viewer in " + dmgfile) - - # Here 'candidate' is the new viewer to install - log("Found " + candidate) - - # This logic was changed to make Mac updates behave more like - # Windows. Most of the time, the user doesn't change the name of - # the app bundle on our .dmg installer (e.g. "Second Life Beta - # Viewer.app"). Most of the time, the version manager directs a - # given viewer to update to another .dmg containing an app bundle - # with THE SAME name. In that case, everything behaves as usual. - - # The case that was changed is when the version manager offers (or - # mandates) an update to a .dmg containing a different app bundle - # name. This can happen, for instance, to a user who's downloaded - # a "project beta" viewer, and the project subsequently publishes - # a Release Candidate viewer. Say the project beta's app bundle - # name is something like "Second Life Beta Neato.app". Anyone - # launching that viewer will be offered an update to the - # corresponding Release Candidate viewer -- which will be built as - # a release viewer, with app bundle name "Second Life Viewer.app". - - # On Windows, we run the NSIS installer, which will update/replace - # the embedded install directory name, e.g. Second Life Viewer. - # But the Mac installer used to locate the app bundle name in the - # mounted .dmg file, then ignore that name, copying its contents - # into the app bundle directory of the running viewer. That is, - # we'd install the Release Candidate from the .dmg's "Second - # Life.app" into "/Applications/Second Life Beta Neato.app". This - # is undesired behavior. - - # Instead, having found the app bundle name on the mounted .dmg, - # we try to install that app bundle name into the parent directory - # of the running app bundle. - - # Are we installing a different app bundle name? If so, call it - # out, both in the log and for the user -- this is an odd case. - # (Presumably they've already agreed to a similar notification in - # the viewer before the viewer launched this script, but still.) - bundlename = os.path.basename(candidate) - if os.path.basename(appdir) == bundlename: - # updating the running app bundle, which we KNOW exists - appexists = True - else: - # installing some other app bundle - newapp = os.path.join(installdir, bundlename) - appexists = os.path.exists(newapp) - message = "Note: %s %s %s" % \ - (appdir, "updating" if appexists else "installing new", newapp) - status(message) - # okay, we have no further need of the name of the running app - # bundle. - appdir = newapp - - status("Preparing to copy files...") - - if appexists: - # move old viewer to temp location in case copy from .dmg fails - aside = os.path.join(tempdir, os.path.basename(appdir)) - log("mv %r %r" % (appdir, aside)) - # Use shutil.move() instead of os.rename(). move() first tries - # os.rename(), but falls back to shutil.copytree() if the dest is - # on a different filesystem. - shutil.move(appdir, aside) - - status("Copying files...") - - # shutil.copytree()'s target must not already exist. But we just - # moved appdir out of the way. - log("cp -p %r %r" % (candidate, appdir)) - try: - # The viewer app bundle does include internal symlinks. Keep them - # as symlinks. - shutil.copytree(candidate, appdir, symlinks=True) - except Exception, err: - # copy failed -- try to restore previous viewer before crumping - type, value, traceback = sys.exc_info() - if appexists: - log("exception response: mv %r %r" % (aside, appdir)) - shutil.move(aside, appdir) - # let our previously-set sys.excepthook handle this - raise type, value, traceback - - status("Cleaning up...") - - log("touch " + appdir) - os.utime(appdir, None) # set to current time - - # MAINT-3331: remove STATE_DIR. Empirically, this resolves a - # persistent, mysterious crash after updating our viewer on an OS - # X 10.7.5 system. - log("rm -rf '%s'" % STATE_DIR) - with allow_errno(errno.ENOENT): - shutil.rmtree(STATE_DIR) - - command = ["open", appdir] - log(' '.join(command)) - subprocess.check_call(command, stdout=LOGF, stderr=subprocess.STDOUT) - - # If all the above succeeded, delete the .dmg file. We don't do this - # as a janitor.later() operation because we only want to do it if we - # get this far successfully. Note that this is out of the scope of the - # Janitor: we must detach the .dmg before removing it! - log("rm " + dmgfile) - os.remove(dmgfile) - - except Exception, err: - # Because we carefully set sys.excepthook -- and even modify it to log - # the problem once we have our log file open -- you might think we - # could just let exceptions propagate. But when we do that, on - # exception in this block, we FIRST restore the no-side-effects fail() - # and THEN implicitly call sys.excepthook(), which calls the (no-side- - # effects) fail(). Explicitly call sys.excepthook() BEFORE restoring - # fail(). Only then do we get the enriched fail() behavior. - sys.excepthook(*sys.exc_info()) - - finally: - # When we leave main() -- for whatever reason -- reset fail() the way - # it was before, because the bound markerfile, markertext params - # passed to this main() call are no longer applicable. - fail = oldfail - -if __name__ == "__main__": - # We expect this script to be invoked with: - # - the pathname to the .dmg we intend to install; - # - the pathname to an update-error marker file to create on failure; - # - the content to write into the marker file. - main(*sys.argv[1:]) diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install deleted file mode 100755 index 03089f192e..0000000000 --- a/indra/viewer_components/updater/scripts/linux/update_install +++ /dev/null @@ -1,220 +0,0 @@ -#! /bin/bash - -# @file update_install -# @author Nat Goodspeed -# @date 2013-01-09 -# @brief Update the containing Second Life application bundle to the version in -# the specified tarball. -# -# This bash implementation is derived from the previous linux-updater.bin -# application. -# -# $LicenseInfo:firstyear=2013&license=viewerlgpl$ -# Copyright (c) 2013, Linden Research, Inc. -# $/LicenseInfo$ - -# **************************************************************************** -# script parameters -# **************************************************************************** -tarball="$1" # the file to install -markerfile="$2" # create this file on failure -mandatory="$3" # what to write to markerfile on failure - -# **************************************************************************** -# helper functions -# **************************************************************************** -# empty array -cleanups=() - -# add a cleanup action to execute on exit -function cleanup { - # wacky bash syntax for appending to array - cleanups[${#cleanups[*]}]="$*" -} - -# called implicitly on exit -function onexit { - for action in "${cleanups[@]}" - do # don't quote, support actions consisting of multiple words - $action - done -} -trap 'onexit' EXIT - -# write to log file -function log { - # our log file will be open as stderr -- but until we set up that - # redirection, logging to stderr is better than nothing - echo "$*" 1>&2 -} - -# We display status by leaving one background xmessage process running. This -# is the pid of that process. -statuspid="" - -function clear_message { - [ -n "$statuspid" ] && kill $statuspid - statuspid="" -} - -# make sure we remove any message box we might have put up -cleanup clear_message - -# can we use zenity, or must we fall back to xmessage? -zenpath="$(which zenity)" -if [ -n "$zenpath" ] -then # zenity on PATH and is executable - # display a message box and continue - function status { - # clear any previous message - clear_message - # put up a new zenity box and capture its pid -## "$zenpath" --info --title "Second Life Viewer Updater" \ -## --width=320 --height=120 --text="$*" & - # MAINT-2333: use bouncing progress bar - "$zenpath" --progress --pulsate --no-cancel --title "Second Life Viewer Updater" \ - --width=320 --height=120 --text "$*" "$markerfile" - # add boilerplate - errorbox "An error occurred while updating Second Life: -$* -Please download the latest viewer from www.secondlife.com." - exit 1 -} - -# Find a graphical sudo program and define mysudo function. On error, $? is -# nonzero; output is in $err instead of being written to stdout/stderr. -gksudo="$(which gksudo)" -kdesu="$(which kdesu)" -if [ -n "$gksudo" ] -then function mysudo { - # gksudo allows you to specify description - err="$("$gksudo" --description "Second Life Viewer Updater" "$@" 2>&1)" - } -elif [ -n "$kdesu" ] -then function mysudo { - err="$("$kdesu" "$@" 2>&1)" - } -else # couldn't find either one, just try it anyway - function mysudo { - err="$("$@" 2>&1)" - } -fi - -# Move directories, using mysudo if we think it necessary. On error, $? is -# nonzero; output is in $err instead of being written to stdout/stderr. -function sudo_mv { - # If we have write permission to both parent directories, shouldn't need - # sudo. - if [ -w "$(dirname "$1")" -a -w "$(dirname "$2")" ] - then err="$(mv "$@" 2>&1)" - else # use available sudo program; mysudo sets $? and $err - mysudo mv "$@" - fi -} - -# **************************************************************************** -# main script logic -# **************************************************************************** -mydir="$(dirname "$0")" -# We happen to know that the viewer specifies a marker-file pathname within -# the logs directory. -logsdir="$(dirname "$markerfile")" -logname="$logsdir/updater.log" - -# move aside old updater.log; we're about to create a new one -[ -f "$logname" ] && mv "$logname" "$logname.old" - -# Set up redirections for this script such that stderr is logged. (But first -# move the previous stderr to file descriptor 3.) -exec 3>&2- 2> "$logname" - -# Rather than setting up a special pipeline to timestamp every line of stderr, -# produce header lines into log file indicating timestamp and the arguments -# with which we were invoked. -date 1>&2 -log "$0 $*" - -# Log every command we execute, along with any stderr it might produce -set -x - -status 'Installing Second Life...' - -# Creating tempdir under /tmp means it's possible that tempdir is on a -# different filesystem than INSTALL_DIR. One is tempted to create tempdir on a -# path derived from `dirname INSTALL_DIR` -- but it seems modern 'mv' can -# handle moving across filesystems?? -tempdir="$(mktemp -d)" -tempinstall="$tempdir/install" -# capture the actual error message, if any -err="$(mkdir -p "$tempinstall" 2>&1)" || fail "$err" -cleanup rm -rf "$tempdir" - -# If we already knew the name of the tarball's top-level directory, we could -# just move that when all was said and done. Since we don't, untarring to the -# 'install' subdir with --strip 1 effectively renames that top-level -# directory. -# untar failures tend to be voluminous -- don't even try to capture, just log -tar --strip 1 -xjf "$tarball" -C "$tempinstall" || fail "Untar command failed" - -INSTALL_DIR="$(cd "$mydir/.." ; pwd)" - -# Considering we're launched from a subdirectory of INSTALL_DIR, would be -# surprising if it did NOT already exist... -if [ -e "$INSTALL_DIR" ] -then backup="$INSTALL_DIR.backup" - backupn=1 - while [ -e "$backup" ] - do backup="$INSTALL_DIR.backup.$backupn" - ((backupn += 1)) - done - # on error, fail with actual error message from sudo_mv: permissions, - # cross-filesystem mv, ...? - sudo_mv "$INSTALL_DIR" "$backup" || fail "$err" -fi -# We unpacked the tarball into tempinstall. Move that. -if ! sudo_mv "$tempinstall" "$INSTALL_DIR" -then # If we failed to move the temp install to INSTALL_DIR, try to restore - # INSTALL_DIR from backup. Save $err because next sudo_mv will trash it! - realerr="$err" - sudo_mv "$backup" "$INSTALL_DIR" - fail "$realerr" -fi - -# Removing the tarball here, rather than with a 'cleanup' action, means we -# only remove it if we succeeded. -rm -f "$tarball" - -# Launch the updated viewer. Restore original stderr from file descriptor 3, -# though -- otherwise updater.log gets cluttered with the viewer log! -"$INSTALL_DIR/secondlife" 2>&3- & diff --git a/indra/viewer_components/updater/scripts/windows/update_install.bat b/indra/viewer_components/updater/scripts/windows/update_install.bat deleted file mode 100644 index 96687226a8..0000000000 --- a/indra/viewer_components/updater/scripts/windows/update_install.bat +++ /dev/null @@ -1,3 +0,0 @@ -start /WAIT %1 /SKIP_DIALOGS -IF ERRORLEVEL 1 ECHO %3 > %2 -DEL %1 diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp deleted file mode 100644 index 759e41ef4c..0000000000 --- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp +++ /dev/null @@ -1,220 +0,0 @@ -/** - * @file llupdaterservice_test.cpp - * @brief Tests of llupdaterservice.cpp. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -// Precompiled header -#include "linden_common.h" -// associated header -#include "../llupdaterservice.h" -#include "../llupdatechecker.h" -#include "../llupdatedownloader.h" -#include "../llupdateinstaller.h" - -#include "../../../test/lltut.h" -//#define DEBUG_ON -#include "../../../test/debug.h" - -#include "llevents.h" -#include "lldir.h" - -/***************************************************************************** -* MOCK'd -*****************************************************************************/ -LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client) -{} -void LLUpdateChecker::checkVersion(std::string const & urlBase, - std::string const & channel, - std::string const & version, - std::string const & platform, - std::string const & platform_version, - unsigned char uniqueid[MD5HEX_STR_SIZE], - bool willing_to_test) -{} -LLUpdateDownloader::LLUpdateDownloader(Client & ) {} -void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, std::string const &, std::string const &, bool){} - -class LLDir_Mock : public LLDir -{ - void initAppDirs(const std::string &app_name, - const std::string& app_read_only_data_dir = "") {} - U32 countFilesInDir(const std::string &dirname, const std::string &mask) - { - return 0; - } - - void getRandomFileInDir(const std::string &dirname, - const std::string &mask, - std::string &fname) {} - std::string getCurPath() { return ""; } - bool fileExists(const std::string &filename) const { return false; } - std::string getLLPluginLauncher() { return ""; } - std::string getLLPluginFilename(std::string base_name) { return ""; } - -} gDirUtil; -LLDir* gDirUtilp = &gDirUtil; -LLDir::LLDir() {} -LLDir::~LLDir() {} -S32 LLDir::deleteFilesInDir(const std::string &dirname, - const std::string &mask) -{ return 0; } - -void LLDir::setChatLogsDir(const std::string &path){} -void LLDir::setPerAccountChatLogsDir(const std::string &username){} -void LLDir::setLindenUserDir(const std::string &username){} -void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language){} -std::string LLDir::getSkinFolder() const { return "default"; } -std::string LLDir::getLanguage() const { return "en"; } -bool LLDir::setCacheDir(const std::string &path){ return true; } -void LLDir::dumpCurrentDirectories() {} -void LLDir::updatePerAccountChatLogsDir() {} - -#include "llviewernetwork.h" -LLGridManager::LLGridManager() : - mGrid("test.grid.lindenlab.com"), - mIsInProductionGrid(false) -{ -} -std::string LLGridManager::getUpdateServiceURL() -{ - return "https://update.secondlife.com/update"; -} -LLGridManager::~LLGridManager() -{ -} - - -std::string LLDir::getExpandedFilename(ELLPath location, - const std::string &filename) const -{ - return ""; -} - -std::string LLUpdateDownloader::downloadMarkerPath(void) -{ - return ""; -} - -void LLUpdateDownloader::resume(void) {} -void LLUpdateDownloader::cancel(void) {} -void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond) {} - -int ll_install_update(std::string const &, std::string const &, bool, LLInstallScriptMode) -{ - return 0; -} - -std::string const & ll_install_failed_marker_path() -{ - static std::string wubba; - return wubba; -} - -/* -#pragma warning(disable: 4273) -llus_mock_llifstream::llus_mock_llifstream(const std::string& _Filename, - ios_base::openmode _Mode, - int _Prot) : - std::basic_istream >(NULL,true) -{} - -llus_mock_llifstream::~llus_mock_llifstream() {} -bool llus_mock_llifstream::is_open() const {return true;} -void llus_mock_llifstream::close() {} -*/ - -/***************************************************************************** -* TUT -*****************************************************************************/ -namespace tut -{ - struct llupdaterservice_data - { - llupdaterservice_data() : - pumps(LLEventPumps::instance()), - test_url("dummy_url"), - test_channel("dummy_channel"), - test_version("dummy_version") - {} - LLEventPumps& pumps; - std::string test_url; - std::string test_channel; - std::string test_version; - }; - - typedef test_group llupdaterservice_group; - typedef llupdaterservice_group::object llupdaterservice_object; - llupdaterservice_group llupdaterservicegrp("LLUpdaterService"); - - template<> template<> - void llupdaterservice_object::test<1>() - { - DEBUG; - LLUpdaterService updater; - bool got_usage_error = false; - try - { - updater.startChecking(); - } - catch(LLUpdaterService::UsageError) - { - got_usage_error = true; - } - ensure("Caught start before params", got_usage_error); - } - - template<> template<> - void llupdaterservice_object::test<2>() - { - DEBUG; - LLUpdaterService updater; - bool got_usage_error = false; - try - { - unsigned char id1[MD5HEX_STR_SIZE] = "11111111111111111111111111111111"; - updater.initialize(test_channel, test_version, "win", "1.2.3", id1, true); - updater.startChecking(); - unsigned char id2[MD5HEX_STR_SIZE] = "22222222222222222222222222222222"; - updater.initialize(test_channel, test_version, "win", "4.5.6", id2, true); - } - catch(LLUpdaterService::UsageError) - { - got_usage_error = true; - } - ensure("Caught params while running", got_usage_error); - } - - template<> template<> - void llupdaterservice_object::test<3>() - { - DEBUG; - LLUpdaterService updater; - unsigned char id[MD5HEX_STR_SIZE] = "33333333333333333333333333333333"; - updater.initialize(test_channel, test_version, "win", "7.8.9", id, true); - updater.startChecking(); - ensure(updater.isChecking()); - updater.stopChecking(); - ensure(!updater.isChecking()); - } -} -- cgit v1.2.3 From 069c938eb6ebfd77f6a415207331c66f72270e5f Mon Sep 17 00:00:00 2001 From: "coyot@coyot-sager-PC" Date: Tue, 28 Feb 2017 22:35:01 +0000 Subject: pull from rev d22beb597e52ecbf1c98f25d4489ea0425eda4b0 of sl-321 --- indra/viewer_components/manager/InstallerError.py | 49 - .../manager/InstallerUserMessage.py | 316 ------ indra/viewer_components/manager/SL_Launcher | 199 ---- indra/viewer_components/manager/apply_update.py | 277 ----- indra/viewer_components/manager/download_update.py | 105 -- .../manager/tests/data/settings.xml | 1184 -------------------- indra/viewer_components/manager/tests/summary.json | 1 - .../manager/tests/test_InstallerError.py | 45 - .../tests/test_check_for_completed_download.py | 59 - .../tests/test_convert_version_file_style.py | 61 - .../manager/tests/test_get_filename.py | 66 -- .../manager/tests/test_get_log_file_handle.py | 68 -- .../manager/tests/test_get_parent_path.py | 83 -- .../manager/tests/test_get_platform_key.py | 44 - .../manager/tests/test_get_settings.py | 87 -- .../manager/tests/test_make_VVM_UUID_hash.py | 47 - .../manager/tests/test_make_download_dir.py | 47 - .../manager/tests/test_query_vvm.py | 73 -- .../manager/tests/test_silent_write.py | 49 - .../manager/tests/test_summary.py | 45 - .../manager/tests/with_setup_args.py | 68 -- indra/viewer_components/manager/update_manager.py | 547 --------- 22 files changed, 3520 deletions(-) delete mode 100644 indra/viewer_components/manager/InstallerError.py delete mode 100644 indra/viewer_components/manager/InstallerUserMessage.py delete mode 100755 indra/viewer_components/manager/SL_Launcher delete mode 100755 indra/viewer_components/manager/apply_update.py delete mode 100755 indra/viewer_components/manager/download_update.py delete mode 100644 indra/viewer_components/manager/tests/data/settings.xml delete mode 100644 indra/viewer_components/manager/tests/summary.json delete mode 100644 indra/viewer_components/manager/tests/test_InstallerError.py delete mode 100644 indra/viewer_components/manager/tests/test_check_for_completed_download.py delete mode 100644 indra/viewer_components/manager/tests/test_convert_version_file_style.py delete mode 100644 indra/viewer_components/manager/tests/test_get_filename.py delete mode 100644 indra/viewer_components/manager/tests/test_get_log_file_handle.py delete mode 100644 indra/viewer_components/manager/tests/test_get_parent_path.py delete mode 100644 indra/viewer_components/manager/tests/test_get_platform_key.py delete mode 100644 indra/viewer_components/manager/tests/test_get_settings.py delete mode 100644 indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py delete mode 100644 indra/viewer_components/manager/tests/test_make_download_dir.py delete mode 100644 indra/viewer_components/manager/tests/test_query_vvm.py delete mode 100644 indra/viewer_components/manager/tests/test_silent_write.py delete mode 100644 indra/viewer_components/manager/tests/test_summary.py delete mode 100644 indra/viewer_components/manager/tests/with_setup_args.py delete mode 100755 indra/viewer_components/manager/update_manager.py (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/manager/InstallerError.py b/indra/viewer_components/manager/InstallerError.py deleted file mode 100644 index 3b199ea231..0000000000 --- a/indra/viewer_components/manager/InstallerError.py +++ /dev/null @@ -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 "", line 5, in -InstallerError.InstallerError: [Errno [Errno 17] File exists: '/tmp'] foo -""" - -class InstallerError(OSError): - def __init___(self, message): - Exception.__init__(self, message) diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py deleted file mode 100644 index 8002399659..0000000000 --- a/indra/viewer_components/manager/InstallerUserMessage.py +++ /dev/null @@ -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() diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher deleted file mode 100755 index 257543187c..0000000000 --- a/indra/viewer_components/manager/SL_Launcher +++ /dev/null @@ -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 -- {opt1} {opt2} - cli_overrides = {} - cmd_line = get_cmd_line() - - vmp_params = {'--channel':'channel', '--settings':'settings', '--update-service':'update-service', '--set':'set'} - #the settings set with --set. All such settings have only one argument. - vmp_setters = ('UpdaterMaximumBandwidth', 'UpdaterServiceCheckPeriod', 'UpdaterServicePath', 'UpdaterServiceSetting', 'UpdaterServiceURL', 'UpdaterWillingToTest') - - #Here turn the list into a queue, popping off the left as we go. Note that deque() makes a copy by value, not by reference - #Because of the complexity introduced by the uncertainty of how many options a parameter can take, this is far less complicated code than the more - #pythonic (x,y) = since we will sometimes have (x), sometimes (x,y) and sometimes (x,y,z) - #also, because the pop is destructive, we prevent ourselves from iterating back over list elements that iterator methods would peek ahead at - vmp_queue = collections.deque(arg_list) - while (len(vmp_queue)): - param = vmp_queue.popleft() - #if it is not one of ours, pop through args until we get to the next parameter - if param in vmp_params.keys(): - if param == '--set': - setting_name = vmp_queue.popleft() - setting_value = vmp_queue.popleft() - if setting_name in vmp_setters: - cli_overrides[vmp_params[param]] = (setting_name, setting_value) - else: - #find out how many args this parameter has - no_dashes = vmp_params[param] - count = cmd_line[no_dashes]['count'] - param_args = [] - if count > 0: - for argh in range(0,count): - param_args.append(vmp_queue.popleft()) - #the parameter name is the key, the (possibly empty) list of args is the value - cli_overrides[vmp_params[param]] = param_args - - #to prevent KeyErrors on missing keys, set the remainder to None - for key in vmp_params: - if key != '--set': - try: - cli_overrides[key] - except KeyError: - cli_overrides[key] = None - else: - cli_overrides["--set"] = {} - for arg in vmp_setters: - try: - cli_overrides[key][arg] - except KeyError: - cli_overrides[key][arg] = None - return cli_overrides - -#main entry point -#this and a few other update manager methods really should be refactored into a util lib -parent_dir = update_manager.get_parent_path(update_manager.get_platform_key()) -log_file_handle = update_manager.get_log_file_handle(parent_dir, 'launcher.log') - -executable_name = "" -if sys.platform.startswith('darwin'): - executable_name = "Second Life" -elif sys.platform.startswith("win") or sys.platform.startswith("cyg"): - if os.path.isfile(os.path.join(cwd,"SecondLifeViewer.exe")): - executable_name = "SecondLifeViewer.exe" - elif os.path.isfile(os.path.join(cwd,"SecondLifeTest.exe")): - executable_name = "SecondLifeTest.exe" - else: - sys.exit("Can't find Windows viewer binary") -elif sys.platform.startswith("linux"): - executable_name = "secondlife" -else: - #SL doesn't run on VMS or punch cards - sys.exit("Unsupported platform") - -#find the viewer to be lauched -viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name) - -parser = argparse.ArgumentParser() -args = parser.parse_known_args(sys.argv) -#args[1] looks like ['./SL_Launcher', '--set', 'foo', 'bar', '-X', '-Y', 'qux'], dump the progname -args_list_to_pass = args[1][1:] -vmp_args = capture_vmp_args(args_list_to_pass) -#make a copy by value, not by reference -command = list(args_list_to_pass) - -(success, state, condition) = update_manager.update_manager(vmp_args) -# From update_manager: -# (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing) -# (False, 'download', version): we failed to download the new version -# (False, 'apply', version): we failed to apply the new version -# (True, None, None): No update found -# (True, 'in place', True): update applied in place -# (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location -# (True, 'background', True): background download initiated -#These boil down three cases: -# Success is False, then pop up a message and launch the current viewer -# No update, update succeeded in place in foreground, or background update started: silently launch the current viewer channel -# Updated succeed to a different channel, launch that viewer and exit -if not success: - msg = 'Update failed in the %s process. Please check logs. Viewer will launch starting momentarily.' % state - update_manager.after_frame(msg) - command.insert(0,viewer_binary) - viewer_process = subprocess.Popen(command) - #at the moment, we just exit here. Later, the crash monitor will be launched at this point -elif (success == True and - (state == None - or (state == 'background' and condition == True) - or (state == 'in_place' and condition == True))): - command.insert(0,viewer_binary) - viewer_process = subprocess.Popen(command) - #at the moment, we just exit here. Later, the crash monitor will be launched at this point -else: - #'condition' is the path to the new launcher. - command.insert(0,condition) - viewer_process = subprocess.Popen(command) - sys.exit(0) diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py deleted file mode 100755 index 643e4ad2bc..0000000000 --- a/indra/viewer_components/manager/apply_update.py +++ /dev/null @@ -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() diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py deleted file mode 100755 index a5e365fa37..0000000000 --- a/indra/viewer_components/manager/download_update.py +++ /dev/null @@ -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() diff --git a/indra/viewer_components/manager/tests/data/settings.xml b/indra/viewer_components/manager/tests/data/settings.xml deleted file mode 100644 index 07e420dcb3..0000000000 --- a/indra/viewer_components/manager/tests/data/settings.xml +++ /dev/null @@ -1,1184 +0,0 @@ - - - AllowMultipleViewers - - Comment - Allow multiple viewers. - Type - Boolean - Value - 1 - - AllowTapTapHoldRun - - Comment - Tapping a direction key twice and holding it down makes avatar run - Type - Boolean - Value - 0 - - AppearanceCameraMovement - - Comment - When entering appearance editing mode, camera zooms in on currently selected portion of avatar - Type - Boolean - Value - 0 - - AudioLevelMedia - - Comment - Audio level of Quicktime movies - Type - F32 - Value - 0.699999988079071044921875 - - AudioLevelMic - - Comment - Audio level of microphone input - Type - F32 - Value - 0.1749999970197677612304688 - - AudioLevelMusic - - Comment - Audio level of streaming music - Type - F32 - Value - 0 - - AudioLevelSFX - - Comment - Audio level of in-world sound effects - Type - F32 - Value - 0.699999988079071044921875 - - AudioLevelVoice - - Comment - Audio level of voice chat - Type - F32 - Value - 1 - - AudioStreamingMedia - - Comment - Enable streaming - Type - Boolean - Value - 0 - - AvatarAxisDeadZone0 - - Comment - Avatar axis 0 dead zone. - Type - F32 - Value - 0.1000000014901161193847656 - - AvatarAxisDeadZone1 - - Comment - Avatar axis 1 dead zone. - Type - F32 - Value - 0.1000000014901161193847656 - - AvatarAxisDeadZone2 - - Comment - Avatar axis 2 dead zone. - Type - F32 - Value - 0.1000000014901161193847656 - - AvatarAxisDeadZone3 - - Comment - Avatar axis 3 dead zone. - Type - F32 - Value - 1 - - AvatarAxisDeadZone4 - - Comment - Avatar axis 4 dead zone. - Type - F32 - Value - 0.01999999955296516418457031 - - AvatarAxisDeadZone5 - - Comment - Avatar axis 5 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - AvatarAxisScale3 - - Comment - Avatar axis 3 scaler. - Type - F32 - Value - 0 - - AvatarAxisScale4 - - Comment - Avatar axis 4 scaler. - Type - F32 - Value - 2 - - AvatarAxisScale5 - - Comment - Avatar axis 5 scaler. - Type - F32 - Value - 2 - - AvatarFeathering - - Comment - Avatar feathering (less is softer) - Type - F32 - Value - 6 - - AvatarFileName - - Comment - Alternative avatar file name - Type - String - Value - avatar_lad_tentacles.xml - - BuildAxisDeadZone0 - - Comment - Build axis 0 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - BuildAxisDeadZone1 - - Comment - Build axis 1 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - BuildAxisDeadZone2 - - Comment - Build axis 2 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - BuildAxisDeadZone3 - - Comment - Build axis 3 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - BuildAxisDeadZone4 - - Comment - Build axis 4 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - BuildAxisDeadZone5 - - Comment - Build axis 5 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - BuildAxisScale0 - - Comment - Build axis 0 scaler. - Type - F32 - Value - 6 - - BuildAxisScale1 - - Comment - Build axis 1 scaler. - Type - F32 - Value - 6 - - BuildAxisScale2 - - Comment - Build axis 2 scaler. - Type - F32 - Value - 6 - - BuildAxisScale3 - - Comment - Build axis 3 scaler. - Type - F32 - Value - 6 - - BuildAxisScale4 - - Comment - Build axis 4 scaler. - Type - F32 - Value - 6 - - BuildAxisScale5 - - Comment - Build axis 5 scaler. - Type - F32 - Value - 6 - - BuildFeathering - - Comment - Build feathering (less is softer) - Type - F32 - Value - 12 - - BulkChangeEveryoneCopy - - Comment - Bulk changed objects can be copied by everyone - Type - Boolean - Value - 1 - - BulkChangeNextOwnerCopy - - Comment - Bulk changed objects can be copied by next owner - Type - Boolean - Value - 1 - - BulkChangeNextOwnerModify - - Comment - Bulk changed objects can be modified by next owner - Type - Boolean - Value - 1 - - BulkChangeShareWithGroup - - Comment - Bulk changed objects are shared with the currently active group - Type - Boolean - Value - 1 - - CacheValidateCounter - - Comment - Used to distribute cache validation - Type - U32 - Value - 122 - - CameraPosOnLogout - - Comment - Camera position when last logged out (global coordinates) - Type - Vector3D - Value - - 288290.4477181434631347656 - 275988.5277819633483886719 - 49.10921102762222290039062 - - - ClickToWalk - - Comment - Click in world to walk to location - Type - Boolean - Value - 0 - - ConversationSortOrder - - Comment - Specifies sort key for conversations - Type - U32 - Value - 0 - - CurrentGrid - - Comment - Currently Selected Grid - Type - String - Value - util.agni.lindenlab.com - - Cursor3D - - Comment - Treat Joystick values as absolute positions (not deltas). - Type - Boolean - Value - 0 - - FirstLoginThisInstall - - Comment - Specifies that you have not logged in with the viewer since you performed a clean install - Type - Boolean - Value - 0 - - FirstRunThisInstall - - Comment - Specifies that you have not run the viewer since you performed a clean install - Type - Boolean - Value - 0 - - FlycamAxisDeadZone0 - - Comment - Flycam axis 0 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - FlycamAxisDeadZone1 - - Comment - Flycam axis 1 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - FlycamAxisDeadZone2 - - Comment - Flycam axis 2 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - FlycamAxisDeadZone3 - - Comment - Flycam axis 3 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - FlycamAxisDeadZone4 - - Comment - Flycam axis 4 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - FlycamAxisDeadZone5 - - Comment - Flycam axis 5 dead zone. - Type - F32 - Value - 0.009999999776482582092285156 - - FlycamAxisDeadZone6 - - Comment - Flycam axis 6 dead zone. - Type - F32 - Value - 1 - - FlycamAxisScale0 - - Comment - Flycam axis 0 scaler. - Type - F32 - Value - 42 - - FlycamAxisScale1 - - Comment - Flycam axis 1 scaler. - Type - F32 - Value - 40 - - FlycamAxisScale2 - - Comment - Flycam axis 2 scaler. - Type - F32 - Value - 40 - - FlycamAxisScale3 - - Comment - Flycam axis 3 scaler. - Type - F32 - Value - 0 - - FlycamAxisScale4 - - Comment - Flycam axis 4 scaler. - Type - F32 - Value - 2 - - FlycamAxisScale5 - - Comment - Flycam axis 5 scaler. - Type - F32 - Value - 3 - - FlycamAxisScale6 - - Comment - Flycam axis 6 scaler. - Type - F32 - Value - 0 - - FlycamFeathering - - Comment - Flycam feathering (less is softer) - Type - F32 - Value - 5 - - FocusPosOnLogout - - Comment - Camera focus point when last logged out (global coordinates) - Type - Vector3D - Value - - 288287.8830481640761718154 - 275991.5973855691263452172 - 47.96361158013021963597566 - - - ForceShowGrid - - Comment - Always show grid dropdown on login screen - Type - Boolean - Value - 1 - - HttpProxyType - - Comment - Proxy type to use for HTTP operations - Type - String - Value - None - - JoystickInitialized - - Comment - Whether or not a joystick has been detected and initiailized. - Type - String - Value - UnknownDevice - - LSLFindCaseInsensitivity - - Comment - Use case insensitivity when searching in LSL editor - Type - Boolean - Value - 1 - - LastFeatureVersion - - Comment - [DO NOT MODIFY] Feature Table Version number for tracking rendering system changes - Type - S32 - Value - 37 - - LastGPUString - - Comment - [DO NOT MODIFY] previous GPU id string for tracking hardware changes - Type - String - Value - NVIDIA Corporation NVIDIA GeForce GT 750M OpenGL Engine - - LastPrefTab - - Comment - Last selected tab in preferences window - Type - S32 - Value - 1 - - LastRunVersion - - Comment - Version number of last instance of the viewer that you ran - Type - String - Value - Second Life Project Bento 5.0.0.315657 - - LocalCacheVersion - - Comment - Version number of cache - Type - S32 - Value - 7 - - LoginLocation - - Comment - Default Login location ('last', 'home') preference - Type - String - Value - home - - MapScale - - Comment - World map zoom level (pixels per region) - Type - F32 - Value - 256 - - MaxJointsPerMeshObject - - Comment - Maximum joints per rigged mesh object - Type - U32 - Value - 51 - - MediaEnablePopups - - Comment - If true, enable targeted links and javascript in media to open new media browser windows without a prompt. - Type - Boolean - Value - 1 - - MediaShowOnOthers - - Comment - Whether or not to show media on other avatars - Type - Boolean - Value - 1 - - MigrateCacheDirectory - - Comment - Check for old version of disk cache to migrate to current location - Type - Boolean - Value - 0 - - NavBarShowParcelProperties - - Comment - Show parcel property icons in navigation bar - Type - Boolean - Value - 0 - - NextLoginLocation - - Comment - Location to log into for this session - set from command line or the login panel, cleared following a successfull login. - Type - String - Value - home - - NotificationConferenceIMOptions - - Comment - - Specifies how the UI responds to Conference IM Notifications. - Allowed values: [openconversations,toast,flash,noaction] - - Type - String - Value - none - - NotificationFriendIMOptions - - Comment - - Specifies how the UI responds to Friend IM Notifications. - Allowed values: [openconversations,toast,flash,noaction] - - Type - String - Value - openconversations - - NotificationGroupChatOptions - - Comment - - Specifies how the UI responds to Group Chat Notifications. - Allowed values: [openconversations,toast,flash,noaction] - - Type - String - Value - none - - NotificationNearbyChatOptions - - Comment - - Specifies how the UI responds to Nearby Chat Notifications. - Allowed values: [openconversations,toast,flash,noaction] - - Type - String - Value - none - - NotificationNonFriendIMOptions - - Comment - - Specifies how the UI responds to Non Friend IM Notifications. - Allowed values: [openconversations,toast,flash,noaction] - - Type - String - Value - openconversations - - NumSessions - - Comment - Number of successful logins to Second Life - Type - S32 - Value - 1674 - - PlayTypingAnim - - Comment - Your avatar plays the typing animation whenever you type in the chat bar - Type - Boolean - Value - 0 - - PoolSizeExpCache - - Comment - Coroutine Pool size for ExpCache - Type - U32 - Value - 5 - - PreferredBrowserBehavior - - Comment - Use system browser for any links (0), use builtin browser for SL links and system one for others (1) or use builtin browser only (2). - Type - U32 - Value - 0 - - PreferredMaturity - - Comment - Setting for the user's preferred maturity level (consts in indra_constants.h) - Type - U32 - Value - 42 - - PresetGraphicActive - - Comment - Name of currently selected preference - Type - String - Value - Default - - ProbeHardwareOnStartup - - Comment - Query current hardware configuration on application startup - Type - Boolean - Value - 0 - - QAMode - - Comment - Enable Testing Features. - Type - Boolean - Value - 1 - - RenderAnisotropic - - Comment - Render textures using anisotropic filtering - Type - Boolean - Value - 1 - - RenderAvatarCloth - - Comment - Controls if avatars use wavy cloth - Type - Boolean - Value - 0 - - RenderAvatarLODFactor - - Comment - Controls level of detail of avatars (multiplier for current screen area when calculated level of detail) - Type - F32 - Value - 1 - - RenderFSAASamples - - Comment - Number of samples to use for FSAA (0 = no AA). - Type - U32 - Value - 2 - - RenderFarClip - - Comment - Distance of far clip plane from camera (meters) - Type - F32 - Value - 128 - - RenderMaxPartCount - - Comment - Maximum number of particles to display on screen - Type - S32 - Value - 2048 - - RenderQualityPerformance - - Comment - Which graphics settings you've chosen - Type - U32 - Value - 4 - - RenderReflectionDetail - - Comment - Detail of reflection render pass. - Type - S32 - Value - 0 - - RenderTerrainLODFactor - - Comment - Controls level of detail of terrain (multiplier for current screen area when calculated level of detail) - Type - F32 - Value - 2 - - RenderVBOEnable - - Comment - Use GL Vertex Buffer Objects - Type - Boolean - Value - 0 - - RenderVolumeLODFactor - - Comment - Controls level of detail of primitives (multiplier for current screen area when calculated level of detail) - Type - F32 - Value - 1.125 - - ShowAdvancedGraphicsSettings - - Comment - Show advanced graphics settings - Type - Boolean - Value - 1 - - ShowBanLines - - Comment - Show in-world ban/access borders - Type - Boolean - Value - 0 - - ShowStartLocation - - Comment - Display starting location menu on login screen - Type - Boolean - Value - 1 - - SkeletonFileName - - Comment - Alternative skeleton file name - Type - String - Value - avatar_skeleton_tentacles.xml - - SkyPresetName - - Comment - Sky preset to use. May be superseded by region settings or by a day cycle (see DayCycleName). - Type - String - Value - Sunset - - SnapshotConfigURL - - Comment - URL to fetch Snapshot Sharing configuration data from. - Type - String - Value - http://photos.apps.avatarsunited.com/viewer_config - - SnapshotFormat - - Comment - Save snapshots in this format (0 = PNG, 1 = JPEG, 2 = BMP) - Type - S32 - Value - 1 - - SnapshotQuality - - Comment - Quality setting of postcard JPEGs (0 = worst, 100 = best) - Type - S32 - Value - 100 - - SpellCheck - - Comment - Enable spellchecking on line and text editors - Type - Boolean - Value - 0 - - SpellCheckDictionary - - Comment - Current primary and secondary dictionaries used for spell checking - Type - String - Value - English (United States) - - TextureMemory - - Comment - Amount of memory to use for textures in MB (0 = autodetect) - Type - S32 - Value - 256 - - UseDayCycle - - Comment - Whether to use use a day cycle or a fixed sky. - Type - Boolean - Value - 0 - - UseDebugMenus - - Comment - Turns on "Debug" menu - Type - Boolean - Value - 1 - - UseEnvironmentFromRegion - - Comment - Choose whether to use the region's environment settings, or override them with the local settings. - Type - Boolean - Value - 0 - - VFSOldSize - - Comment - [DO NOT MODIFY] Controls resizing of local file cache - Type - U32 - Value - 102 - - VFSSalt - - Comment - [DO NOT MODIFY] Controls local file caching behavior - Type - U32 - Value - 260093998 - - VersionChannelName - - Comment - Version information generated by running the viewer - Type - String - Value - Second Life Release - - VertexShaderEnable - - Comment - Enable/disable all GLSL shaders (debug) - Type - Boolean - Value - 1 - - VoiceInputAudioDevice - - Comment - Audio input device to use for voice - Type - String - Value - C-Media USB Audio Device - - VoiceOutputAudioDevice - - Comment - Audio output device to use for voice - Type - String - Value - C-Media USB Audio Device - - WLSkyDetail - - Comment - Controls vertex detail on the WindLight sky. Lower numbers will give better performance and uglier skies. - Type - U32 - Value - 48 - - WebProfileFloaterRect - - Comment - Web profile floater dimensions - Type - Rect - Value - - 1189 - 957 - 1674 - 277 - - - WindowHeight - - Comment - SL viewer window height - Type - U32 - Value - 1000 - - WindowWidth - - Comment - SL viewer window width - Type - U32 - Value - 1626 - - WindowX - - Comment - X coordinate of upper left corner of SL viewer window, relative to upper left corner of primary display (pixels) - Type - S32 - Value - 50 - - WindowY - - Comment - Y coordinate of upper left corner of SL viewer window, relative to upper left corner of primary display (pixels) - Type - S32 - Value - 50 - - - diff --git a/indra/viewer_components/manager/tests/summary.json b/indra/viewer_components/manager/tests/summary.json deleted file mode 100644 index b78859d427..0000000000 --- a/indra/viewer_components/manager/tests/summary.json +++ /dev/null @@ -1 +0,0 @@ -{"Type":"viewer","Version":"4.0.5.315117","Channel":"Second Life Release"} diff --git a/indra/viewer_components/manager/tests/test_InstallerError.py b/indra/viewer_components/manager/tests/test_InstallerError.py deleted file mode 100644 index 9139447932..0000000000 --- a/indra/viewer_components/manager/tests/test_InstallerError.py +++ /dev/null @@ -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) diff --git a/indra/viewer_components/manager/tests/test_check_for_completed_download.py b/indra/viewer_components/manager/tests/test_check_for_completed_download.py deleted file mode 100644 index bcaaef4c3f..0000000000 --- a/indra/viewer_components/manager/tests/test_check_for_completed_download.py +++ /dev/null @@ -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" diff --git a/indra/viewer_components/manager/tests/test_convert_version_file_style.py b/indra/viewer_components/manager/tests/test_convert_version_file_style.py deleted file mode 100644 index 244c22c458..0000000000 --- a/indra/viewer_components/manager/tests/test_convert_version_file_style.py +++ /dev/null @@ -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) diff --git a/indra/viewer_components/manager/tests/test_get_filename.py b/indra/viewer_components/manager/tests/test_get_filename.py deleted file mode 100644 index efb6349374..0000000000 --- a/indra/viewer_components/manager/tests/test_get_filename.py +++ /dev/null @@ -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" diff --git a/indra/viewer_components/manager/tests/test_get_log_file_handle.py b/indra/viewer_components/manager/tests/test_get_log_file_handle.py deleted file mode 100644 index 5ea821acf7..0000000000 --- a/indra/viewer_components/manager/tests/test_get_log_file_handle.py +++ /dev/null @@ -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 diff --git a/indra/viewer_components/manager/tests/test_get_parent_path.py b/indra/viewer_components/manager/tests/test_get_parent_path.py deleted file mode 100644 index 6e4b9dfaff..0000000000 --- a/indra/viewer_components/manager/tests/test_get_parent_path.py +++ /dev/null @@ -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) diff --git a/indra/viewer_components/manager/tests/test_get_platform_key.py b/indra/viewer_components/manager/tests/test_get_platform_key.py deleted file mode 100644 index eeaca1dff7..0000000000 --- a/indra/viewer_components/manager/tests/test_get_platform_key.py +++ /dev/null @@ -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) diff --git a/indra/viewer_components/manager/tests/test_get_settings.py b/indra/viewer_components/manager/tests/test_get_settings.py deleted file mode 100644 index 7efcf62673..0000000000 --- a/indra/viewer_components/manager/tests/test_get_settings.py +++ /dev/null @@ -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') diff --git a/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py b/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py deleted file mode 100644 index 5939e5806a..0000000000 --- a/indra/viewer_components/manager/tests/test_make_VVM_UUID_hash.py +++ /dev/null @@ -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." diff --git a/indra/viewer_components/manager/tests/test_make_download_dir.py b/indra/viewer_components/manager/tests/test_make_download_dir.py deleted file mode 100644 index e3f9cb83fe..0000000000 --- a/indra/viewer_components/manager/tests/test_make_download_dir.py +++ /dev/null @@ -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) diff --git a/indra/viewer_components/manager/tests/test_query_vvm.py b/indra/viewer_components/manager/tests/test_query_vvm.py deleted file mode 100644 index 79c8ede480..0000000000 --- a/indra/viewer_components/manager/tests/test_query_vvm.py +++ /dev/null @@ -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 diff --git a/indra/viewer_components/manager/tests/test_silent_write.py b/indra/viewer_components/manager/tests/test_silent_write.py deleted file mode 100644 index ad286787e2..0000000000 --- a/indra/viewer_components/manager/tests/test_silent_write.py +++ /dev/null @@ -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 diff --git a/indra/viewer_components/manager/tests/test_summary.py b/indra/viewer_components/manager/tests/test_summary.py deleted file mode 100644 index f2f2af7347..0000000000 --- a/indra/viewer_components/manager/tests/test_summary.py +++ /dev/null @@ -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') diff --git a/indra/viewer_components/manager/tests/with_setup_args.py b/indra/viewer_components/manager/tests/with_setup_args.py deleted file mode 100644 index 94d33aa5fe..0000000000 --- a/indra/viewer_components/manager/tests/with_setup_args.py +++ /dev/null @@ -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 diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py deleted file mode 100755 index 7962568119..0000000000 --- a/indra/viewer_components/manager/update_manager.py +++ /dev/null @@ -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 - """ - test - - Comment - Tell update manager you aren't willing to test. - Type - String - Value - testno - - - """ - 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 - """ - UpdaterServiceSetting - - Comment - Configure updater service. - Type - U32 - Value - 0 - - """ - 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() -- cgit v1.2.3 From e044902aaf1aa963d41a30a249b564e1b8910de7 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Wed, 24 May 2017 09:41:44 -0400 Subject: SL-702: refactor to make the viewer-manager easier for TPVs to integrate --- indra/viewer_components/Resources/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/viewer_components') diff --git a/indra/viewer_components/Resources/README b/indra/viewer_components/Resources/README index e1b35730d4..b0863a7f25 100644 --- a/indra/viewer_components/Resources/README +++ b/indra/viewer_components/Resources/README @@ -1,4 +1,4 @@ -This directory only exists as a place for the summary.json file to exist when the unit tests are run on a Mac, where the file goes to a sibling directory of the scripts dir. In Linux and Windows, the JSON file goes into the same directory as the script. +This directory only exists as a place for the build_data.json file to exist when the unit tests are run on a Mac, where the file goes to a sibling directory of the scripts dir. In Linux and Windows, the JSON file goes into the same directory as the script. See: -- cgit v1.2.3