SL-323: fixes to Tkinter race condition, post --channel and --settings testing, contains debugging statements to be removed after all testing complete

master
Glenn Glazer 2016-08-15 14:48:09 -07:00
parent 2afcff5b01
commit 7ed9c85a1a
4 changed files with 77 additions and 33 deletions

View File

@ -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():

View File

@ -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)

View File

@ -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)

View File

@ -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
"""
<key>UpdaterServiceSetting</key>
<map>
<key>Comment</key>
@ -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)