class Profile(object): """Handles all operations regarding profile. Created new profiles, installs extensions, sets preferences and handles cleanup.""" def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: locations to proxy :param proxy: setup a proxy - dict of server-loc,server-port,ssl-port :param restore: If true remove all installed addons preferences when cleaning up """ # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=apps) self.webapps.update_manifests() def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() if self.create_new: profile = None else: profile = self.profile self.__init__(profile=profile, addons=self.addon_manager.installed_addons, addon_manifests=self.addon_manager.installed_manifests, preferences=self._preferences, locations=self._locations, proxy=self._proxy) @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name rmtree(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def create_new_profile(self): """Create a new clean temporary profile which is a simple empty folder""" return tempfile.mkdtemp(suffix='.mozrunner') ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ lines = file(os.path.join(self.profile, filename)).read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % ( self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) f = file(os.path.join(self.profile, 'user.js'), 'w') f.write(cleaned_prefs) f.close() return True def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break ### cleanup def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def cleanup(self): """Cleanup operations for the profile.""" if self.restore: if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) else: self.clean_preferences() self.addon_manager.clean_addons() self.permissions.clean_db() self.webapps.clean() __del__ = cleanup
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. """ def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._apps = apps self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix='.mozrunner') self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests() def __del__(self): self.cleanup() ### cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.clean_preferences() if getattr(self, 'addon_manager', None) is not None: self.addon_manager.clean() if getattr(self, 'permissions', None) is not None: self.permissions.clean_db() if getattr(self, 'webapps', None) is not None: self.webapps.clean() # If it's a temporary profile we have to remove it if self.create_new: mozfile.remove(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name mozfile.remove( tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): mozfile.remove(self.profile) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def set_persistent_preferences(self, preferences): """ Adds preferences dict to profile preferences and save them during a profile reset """ # this is a dict sometimes, convert if isinstance(preferences, dict): preferences = preferences.items() # add new prefs to preserve them during reset for new_pref in preferences: # if dupe remove item from original list self._preferences = [ pref for pref in self._preferences if not new_pref[0] == pref[0] ] self._preferences.append(new_pref) self.set_preferences(preferences, filename='user.js') def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % ( self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % mozfile.tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 line_length_buffer = 10 # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' % ('\n'.join([ '%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys()) ])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get( 'network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip( ): network_proxy_autoconfig = network_proxy_autoconfig.strip( ) lines = network_proxy_autoconfig.replace( ';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [ lines[0][:start], lines[0][start:start + len(origins_string) - 1], ] splitline.extend( lines[0][start + len(origins_string):end].replace( ',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append( ('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join( ['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary
class Profile(object): """Handles all operations regarding profile. Created new profiles, installs extensions, sets preferences and handles cleanup.""" def __init__(self, profile=None, addons=None, addon_manifests=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ :param preferences: Dictionary or class of preferences :param locations: locations to proxy :param proxy: setup a proxy - dict of server-loc,server-port,ssl-port :param restore: If true remove all installed addons preferences when cleaning up """ # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce,'#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests) def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() if self.create_new: profile = None else: profile = self.profile self.__init__(profile=profile, addons=self.addon_manager.installed_addons, addon_manifests=self.addon_manager.installed_manifests, preferences=self._preferences, locations=self._locations, proxy = self._proxy) def create_new_profile(self): """Create a new clean profile in tmp which is a simple empty folder""" profile = tempfile.mkdtemp(suffix='.mozrunner') return profile ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) if isinstance(preferences, dict): # order doesn't matter preferences = preferences.items() # write the preferences f.write('\n%s\n' % self.delimeters[0]) _prefs = [(json.dumps(k), json.dumps(v) ) for k, v in preferences] for _pref in _prefs: f.write('user_pref(%s, %s);\n' % _pref) f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ lines = file(os.path.join(self.profile, filename)).read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) f = file(os.path.join(self.profile, 'user.js'), 'w') f.write(cleaned_prefs) f.close() return True def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break ### cleanup def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def cleanup(self): """Cleanup operations for the profile.""" if self.restore: if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) else: self.clean_preferences() self.addon_manager.clean_addons() self.permissions.clean_db() __del__ = cleanup
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. The files associated with the profile will be removed automatically after the object is garbage collected: :: profile = Profile() print profile.profile # this is the path to the created profile del profile # the profile path has been removed from disk :meth:`cleanup` is called under the hood to remove the profile files. You can ensure this method is called (even in the case of exception) by using the profile as a context manager: :: with Profile() as profile: # do things with the profile pass # profile.cleanup() has been called here """ def __init__(self, profile=None, addons=None, addon_manifests=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix='.mozrunner') self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) def __enter__(self): return self def __exit__(self, type, value, traceback): self.cleanup() def __del__(self): self.cleanup() # cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.clean_preferences() if getattr(self, 'addon_manager', None) is not None: self.addon_manager.clean() if getattr(self, 'permissions', None) is not None: self.permissions.clean_db() # If it's a temporary profile we have to remove it if self.create_new: mozfile.remove(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name mozfile.remove(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) c = cls(path_to, **kwargs) c.create_new = True # deletes a cloned profile when restore is True return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) # methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def set_persistent_preferences(self, preferences): """ Adds preferences dict to profile preferences and save them during a profile reset """ # this is a dict sometimes, convert if isinstance(preferences, dict): preferences = preferences.items() # add new prefs to preserve them during reset for new_pref in preferences: # if dupe remove item from original list self._preferences = [ pref for pref in self._preferences if not new_pref[0] == pref[0]] self._preferences.append(new_pref) self.set_preferences(preferences, filename='user.js') def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True # methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % mozfile.tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 # buffer for 80 character display: # length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer = 10 line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' % ('\n'.join( ['%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys())])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get('network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip(): network_proxy_autoconfig = network_proxy_autoconfig.strip() lines = network_proxy_autoconfig.replace(';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [lines[0][:start], lines[0][start:start + len(origins_string) - 1], ] splitline.extend(lines[0][start + len(origins_string):end].replace( ',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append(('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join(['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. """ def __init__( self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True, ): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._apps = apps self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix=".mozrunner") self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = "%s %s" % (str(time.time()), uuid.uuid4()) self.delimeters = ("#MozRunner Prefs Start %s" % nonce, "#MozRunner Prefs End %s" % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, "preferences"): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, "prefs.js") self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests() def __del__(self): self.cleanup() ### cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.clean_preferences() if getattr(self, "addon_manager", None) is not None: self.addon_manager.clean() if getattr(self, "permissions", None) is not None: self.permissions.clean_db() if getattr(self, "webapps", None) is not None: self.webapps.clean() # If it's a temporary profile we have to remove it if self.create_new: mozfile.remove(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name mozfile.remove(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): mozfile.remove(self.profile) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) ### methods for preferences def set_preferences(self, preferences, filename="user.js"): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, "a") if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write("\n%s\n" % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write("%s\n" % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, "%s found without %s" % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, "%s found without %s" % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, "%s found at %s, while %s found at %s" % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = "\n".join(lines[:s] + lines[e + 1 :]) with file(path, "w") as f: f.write(cleaned_prefs) return True ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [("Path", self.profile)] # profile path # directory tree parts.append(("Files", "\n%s" % mozfile.tree(self.profile))) # preferences for prefs_file in ("user.js", "prefs.js"): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ["network.proxy.autoconfig_url"] line_length = 80 line_length_buffer = ( 10 ) # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(": ") def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = "%s..." % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append( ( prefs_file, "\n%s" % ( "\n".join( ["%s: %s" % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys())] ) ), ) ) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get("network.proxy.autoconfig_url") if network_proxy_autoconfig and network_proxy_autoconfig.strip(): network_proxy_autoconfig = network_proxy_autoconfig.strip() lines = network_proxy_autoconfig.replace(";", ";\n").splitlines() lines = [line.strip() for line in lines] origins_string = "var origins = [" origins_end = "];" if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [lines[0][:start], lines[0][start : start + len(origins_string) - 1]] splitline.extend( lines[0][start + len(origins_string) : end].replace(",", ",\n").splitlines() ) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append(("Network Proxy Autoconfig, %s" % (prefs_file), "\n%s" % "\n".join(lines))) if return_parts: return parts retval = "%s\n" % ("\n\n".join(["[%s]: %s" % (key, value) for key, value in parts])) return retval __str__ = summary
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. """ def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._apps = apps self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix='.mozrunner') self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests() def __del__(self): self.cleanup() ### cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.addon_manager.clean() self.clean_preferences() self.permissions.clean_db() self.webapps.clean() # If it's a temporary profile we have to remove it if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name rmtree(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 line_length_buffer = 10 # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' %('\n'.join(['%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys()) ])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get('network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip(): network_proxy_autoconfig = network_proxy_autoconfig.strip() lines = network_proxy_autoconfig.replace(';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start); splitline = [lines[0][:start], lines[0][start:start+len(origins_string)-1], ] splitline.extend(lines[0][start+len(origins_string):end].replace(',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append(('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join(['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary
class Profile(object): """Handles all operations regarding profile. Created new profiles, installs extensions, sets preferences and handles cleanup.""" def __init__( self, profile=None, # Path to the profile addons=None, # String of one or list of addons to install addon_manifests=None, # Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ preferences=None, # Dictionary or class of preferences locations=None, # locations to proxy proxy=False, # setup a proxy restore=True # If true remove all installed addons preferences when cleaning up ): # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers self.delimeters = ('#MozRunner Prefs Start', '#MozRunner Prefs End') # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests) def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() if self.create_new: profile = None else: profile = self.profile self.__init__(profile=profile, addons=self.addon_manager.addons, addon_manifests=self.addon_manager.manifests, preferences=self._preferences, locations=self._locations, proxy=self._proxy) def create_new_profile(self): """Create a new clean profile in tmp which is a simple empty folder""" profile = tempfile.mkdtemp(suffix='.mozrunner') return profile ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) if isinstance(preferences, dict): # order doesn't matter preferences = preferences.items() # write the preferences f.write('\n%s\n' % self.delimeters[0]) _prefs = [(simplejson.dumps(k), simplejson.dumps(v)) for k, v in preferences] for _pref in _prefs: f.write('user_pref(%s, %s);\n' % _pref) f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ lines = file(os.path.join(self.profile, filename)).read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % ( self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) f = file(os.path.join(self.profile, 'user.js'), 'w') f.write(cleaned_prefs) f.close() return True def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: while True: if not self.pop_preferences(filename): break ### cleanup def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def cleanup(self): """Cleanup operations for the profile.""" if self.restore: if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) else: self.clean_preferences() self.addon_manager.clean_addons() self.permissions.clean_db() __del__ = cleanup
class Profile(object): """Handles all operations regarding profile. Created new profiles, installs extensions, sets preferences and handles cleanup. :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: setup a proxy :param restore: If true remove all added addons and preferences when cleaning up """ def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(addons, addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=apps) self.webapps.update_manifests() def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() if self.create_new: profile = None else: profile = self.profile self.__init__(profile=profile, addons=self.addon_manager.installed_addons, addon_manifests=self.addon_manager.installed_manifests, preferences=self._preferences, locations=self._locations, proxy=self._proxy) @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name rmtree(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def create_new_profile(self): """Create a new clean temporary profile which is a simple empty folder""" return tempfile.mkdtemp(suffix='.mozrunner') ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % ( self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break ### cleanup def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def cleanup(self): """Cleanup operations for the profile.""" if self.restore: if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) else: self.clean_preferences() self.addon_manager.clean_addons() self.permissions.clean_db() self.webapps.clean() __del__ = cleanup ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 line_length_buffer = 10 # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' % ('\n'.join([ '%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys()) ])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get( 'network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip( ): network_proxy_autoconfig = network_proxy_autoconfig.strip( ) lines = network_proxy_autoconfig.replace( ';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [ lines[0][:start], lines[0][start:start + len(origins_string) - 1], ] splitline.extend( lines[0][start + len(origins_string):end].replace( ',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append( ('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join( ['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary