diff options
author | Glenn Glazer <coyot@lindenlab.com> | 2016-08-15 14:48:09 -0700 |
---|---|---|
committer | Glenn Glazer <coyot@lindenlab.com> | 2016-08-15 14:48:09 -0700 |
commit | 7ed9c85a1a28e494adbd2cef85b186e45dba2dc2 (patch) | |
tree | 74efe7b74f31234ac1e82c82a7c170f2471e30bd /indra/viewer_components | |
parent | 2afcff5b013c778f55af77f27932e10792986a05 (diff) |
SL-323: fixes to Tkinter race condition, post --channel and --settings testing, contains debugging statements to be removed after all testing complete
Diffstat (limited to 'indra/viewer_components')
4 files changed, 77 insertions, 33 deletions
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 + """ <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) |