class UpdateChannel(BaseLib): """Class to handle the update channel as listed in channel-prefs.js""" REGEX_UPDATE_CHANNEL = re.compile( r'("app\.update\.channel", ")([^"].*)(?=")') def __init__(self, marionette): BaseLib.__init__(self, marionette) self.prefs = Preferences(marionette) self.file_path = self.marionette.execute_script(""" Components.utils.import('resource://gre/modules/Services.jsm'); let file = Services.dirsvc.get('PrfDef', Components.interfaces.nsIFile); file.append('channel-prefs.js'); return file.path; """) @property def file_contents(self): """The contents of the channel-prefs.js file.""" with open(self.file_path) as f: return f.read() @property def channel(self): """The name of the update channel as stored in the app.update.channel pref.""" return self.prefs.get_pref('app.update.channel', True) @property def default_channel(self): """Get the default update channel :returns: Current default update channel """ matches = re.search(self.REGEX_UPDATE_CHANNEL, self.file_contents).groups() assert len(matches) == 2, 'Update channel value has been found' return matches[1] @default_channel.setter def default_channel(self, channel): """Set default update channel. :param channel: New default update channel """ assert channel, 'Update channel has been specified' new_content = re.sub(self.REGEX_UPDATE_CHANNEL, r'\g<1>' + channel, self.file_contents) with open(self.file_path, 'w') as f: f.write(new_content)
class UpdateChannel(BaseLib): """Class to handle the update channel as listed in channel-prefs.js""" REGEX_UPDATE_CHANNEL = re.compile(r'("app\.update\.channel", ")([^"].*)(?=")') def __init__(self, marionette_getter): BaseLib.__init__(self, marionette_getter) self.prefs = Preferences(marionette_getter) self.file_path = self.marionette.execute_script( """ Components.utils.import('resource://gre/modules/Services.jsm'); let file = Services.dirsvc.get('PrfDef', Components.interfaces.nsIFile); file.append('channel-prefs.js'); return file.path; """ ) @property def file_contents(self): """The contents of the channel-prefs.js file.""" with open(self.file_path) as f: return f.read() @property def channel(self): """The name of the update channel as stored in the app.update.channel pref.""" return self.prefs.get_pref("app.update.channel", True) @property def default_channel(self): """Get the default update channel :returns: Current default update channel """ matches = re.search(self.REGEX_UPDATE_CHANNEL, self.file_contents).groups() assert len(matches) == 2, "Update channel value has been found" return matches[1] @default_channel.setter def default_channel(self, channel): """Set default update channel. :param channel: New default update channel """ assert channel, "Update channel has been specified" new_content = re.sub(self.REGEX_UPDATE_CHANNEL, r"\g<1>" + channel, self.file_contents) with open(self.file_path, "w") as f: f.write(new_content)
class SoftwareUpdate(BaseLib): """The SoftwareUpdate API adds support for an easy access to the update process.""" PREF_APP_DISTRIBUTION = 'distribution.id' PREF_APP_DISTRIBUTION_VERSION = 'distribution.version' PREF_APP_UPDATE_CHANNEL = 'app.update.channel' PREF_APP_UPDATE_URL = 'app.update.url' PREF_DISABLED_ADDONS = 'extensions.disabledAddons' def __init__(self, marionette): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette) @property def ABI(self): """Get the customized ABI for the update service. :returns: ABI version """ abi = self.app_info.XPCOMABI if mozinfo.isMac: abi += self.marionette.execute_script(""" let macutils = Components.classes['@mozilla.org/xpcom/mac-utils;1'] .getService(Components.interfaces.nsIMacUtils); if (macutils.isUniversalBinary) { return '-u-' + macutils.architecturesInBinary; } return ''; """) return abi @property def active_update(self): """ Holds a reference to an :class:`ActiveUpdate` object.""" return self._active_update @property def allowed(self): """Check if the user has permissions to run the software update :returns: Status if the user has the permissions """ return self.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.canCheckForUpdates && aus.canApplyUpdates; """) @property def build_info(self): """Return information of the current build version :returns: A dictionary of build information """ update_url = self.get_formatted_update_url(True) return { 'buildid': self.app_info.appBuildID, 'channel': self.update_channel, 'disabled_addons': self.prefs.get_pref(self.PREF_DISABLED_ADDONS), 'locale': self.app_info.locale, 'mar_channels': self.mar_channels.channels, 'update_url': update_url, 'update_snippet': self.get_update_snippet(update_url), 'user_agent': self.app_info.user_agent, 'version': self.app_info.version } @property def is_complete_update(self): """Return true if the offered update is a complete update :returns: True if the offered update is a complete update """ # Throw when isCompleteUpdate is called without an update. This should # never happen except if the test is incorrectly written. assert self.active_update.exists, 'An active update has been found' patch_count = self.active_update.patch_count assert patch_count == 1 or patch_count == 2,\ 'An update must have one or two patches included' # Ensure Partial and Complete patches produced have unique urls if patch_count == 2: patch0_url = self.active_update.get_patch_at(0)['URL'] patch1_url = self.active_update.get_patch_at(1)['URL'] assert patch0_url != patch1_url,\ 'Partial and Complete download URLs are different' return self.active_update.selected_patch['type'] == 'complete' @property def mar_channels(self): """ Holds a reference to a :class:`MARChannels` object.""" return self._mar_channels @property def os_version(self): """Returns information about the OS version :returns: The OS version """ return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (ex) { } if (osVersion) { try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, // so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; """) @property def patch_info(self): """ Returns information of the active update in the queue.""" info = {'channel': self.update_channel} if (self.active_update.exists): info['buildid'] = self.active_update.buildID info['is_complete'] = self.is_complete_update info['size'] = self.active_update.selected_patch['size'] info['type'] = self.update_type info['url_mirror'] = \ self.active_update.selected_patch['finalURL'] or 'n/a' info['version'] = self.active_update.appVersion return info @property def staging_directory(self): """ Returns the path to the updates staging directory.""" return self.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.getUpdatesDirectory().path; """) @property def update_channel(self): """Return the currently used update channel.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_CHANNEL, default_branch=True) @update_channel.setter def update_channel(self, channel): """Set the update channel to be used for update checks. :param channel: New update channel to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_CHANNEL, channel, default_branch=True) @property def update_url(self): """Return the update URL used for update checks.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_URL, default_branch=True) @update_url.setter def update_url(self, url): """Set the update URL to be used for update checks. :param url: New update URL to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_URL, url, default_branch=True) @property def update_type(self): """Returns the type of the active update.""" return self.active_update.type def force_fallback(self): """Update the update.status file and set the status to 'failed:6'""" with open(os.path.join(self.staging_directory, 'update.status'), 'w') as f: f.write('failed: 6\n') def get_update_snippet(self, update_url): """Retrieve contents of the update snippet. :param update_url: URL to the update snippet """ snippet = None try: import urllib2 response = urllib2.urlopen(update_url) snippet = response.read() except Exception: pass return snippet def get_formatted_update_url(self, force=False): """Retrieve the formatted AUS update URL the update snippet is retrieved from. :param force: Boolean flag to force an update check :returns: The URL of the update snippet """ # Format the URL by replacing placeholders url = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """, script_args=[self.update_url]) if force: if '?' in url: url += '&' else: url += '?' url += 'force=1' return url
class SoftwareUpdate(BaseLib): """The SoftwareUpdate API adds support for an easy access to the update process.""" PREF_APP_DISTRIBUTION = "distribution.id" PREF_APP_DISTRIBUTION_VERSION = "distribution.version" PREF_APP_UPDATE_CHANNEL = "app.update.channel" PREF_APP_UPDATE_URL = "app.update.url" PREF_DISABLED_ADDONS = "extensions.disabledAddons" def __init__(self, marionette): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette) @property def ABI(self): """Get the customized ABI for the update service. :returns: ABI version """ abi = self.app_info.XPCOMABI if mozinfo.isMac: abi += self.marionette.execute_script( """ let macutils = Components.classes['@mozilla.org/xpcom/mac-utils;1'] .getService(Components.interfaces.nsIMacUtils); if (macutils.isUniversalBinary) { return '-u-' + macutils.architecturesInBinary; } return ''; """ ) return abi @property def active_update(self): """ Holds a reference to an :class:`ActiveUpdate` object.""" return self._active_update @property def allowed(self): """Check if the user has permissions to run the software update :returns: Status if the user has the permissions """ return self.marionette.execute_script( """ let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.canCheckForUpdates && aus.canApplyUpdates; """ ) @property def build_info(self): """Return information of the current build version :returns: A dictionary of build information """ update_url = self.get_formatted_update_url(True) return { "buildid": self.app_info.appBuildID, "channel": self.update_channel, "disabled_addons": self.prefs.get_pref(self.PREF_DISABLED_ADDONS), "locale": self.app_info.locale, "mar_channels": self.mar_channels.channels, "update_url": update_url, "update_snippet": self.get_update_snippet(update_url), "user_agent": self.app_info.user_agent, "version": self.app_info.version, } @property def is_complete_update(self): """Return true if the offered update is a complete update :returns: True if the offered update is a complete update """ # Throw when isCompleteUpdate is called without an update. This should # never happen except if the test is incorrectly written. assert self.active_update.exists, "An active update has been found" patch_count = self.active_update.patch_count assert patch_count == 1 or patch_count == 2, "An update must have one or two patches included" # Ensure Partial and Complete patches produced have unique urls if patch_count == 2: patch0_url = self.active_update.get_patch_at(0)["URL"] patch1_url = self.active_update.get_patch_at(1)["URL"] assert patch0_url != patch1_url, "Partial and Complete download URLs are different" return self.active_update.selected_patch["type"] == "complete" @property def mar_channels(self): """ Holds a reference to a :class:`MARChannels` object.""" return self._mar_channels @property def os_version(self): """Returns information about the OS version :returns: The OS version """ return self.marionette.execute_script( """ Components.utils.import("resource://gre/modules/Services.jsm"); let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (ex) { } if (osVersion) { try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, // so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; """ ) @property def patch_info(self): """ Returns information of the active update in the queue.""" info = {"channel": self.update_channel} if self.active_update.exists: info["buildid"] = self.active_update.buildID info["is_complete"] = self.is_complete_update info["size"] = self.active_update.selected_patch["size"] info["type"] = self.update_type info["url_mirror"] = self.active_update.selected_patch["finalURL"] or "n/a" info["version"] = self.active_update.appVersion return info @property def staging_directory(self): """ Returns the path to the updates staging directory.""" return self.marionette.execute_script( """ let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.getUpdatesDirectory().path; """ ) @property def update_channel(self): """Return the currently used update channel.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_CHANNEL, default_branch=True) @update_channel.setter def update_channel(self, channel): """Set the update channel to be used for update checks. :param channel: New update channel to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_CHANNEL, channel, default_branch=True) @property def update_url(self): """Return the update URL used for update checks.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_URL, default_branch=True) @update_url.setter def update_url(self, url): """Set the update URL to be used for update checks. :param url: New update URL to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_URL, url, default_branch=True) @property def update_type(self): """Returns the type of the active update.""" return self.active_update.type def force_fallback(self): """Update the update.status file and set the status to 'failed:6'""" with open(os.path.join(self.staging_directory, "update.status"), "w") as f: f.write("failed: 6\n") def get_update_snippet(self, update_url): """Retrieve contents of the update snippet. :param update_url: URL to the update snippet """ snippet = None try: import urllib2 response = urllib2.urlopen(update_url) snippet = response.read() except Exception: pass return snippet def get_formatted_update_url(self, force=False): """Retrieve the formatted AUS update URL the update snippet is retrieved from. :param force: Boolean flag to force an update check :returns: The URL of the update snippet """ # Format the URL by replacing placeholders url = self.marionette.execute_script( """ Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """, script_args=[self.update_url], ) if force: if "?" in url: url += "&" else: url += "?" url += "force=1" return url
class SoftwareUpdate(BaseLib): """The SoftwareUpdate API adds support for an easy access to the update process.""" PREF_APP_DISTRIBUTION = 'distribution.id' PREF_APP_DISTRIBUTION_VERSION = 'distribution.version' PREF_APP_UPDATE_URL = 'app.update.url' PREF_APP_UPDATE_URL_OVERRIDE = 'app.update.url.override' PREF_DISABLED_ADDONS = 'extensions.disabledAddons' def __init__(self, marionette): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._update_channel = UpdateChannel(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette) @property def ABI(self): """Get the customized ABI for the update service. :returns: ABI version """ abi = self.app_info.XPCOMABI if mozinfo.isMac: abi += self.marionette.execute_script(""" let macutils = Components.classes['@mozilla.org/xpcom/mac-utils;1'] .getService(Components.interfaces.nsIMacUtils); if (macutils.isUniversalBinary) { return '-u-' + macutils.architecturesInBinary; } return ''; """) return abi @property def active_update(self): """ Holds a reference to an :class:`ActiveUpdate` object.""" return self._active_update @property def allowed(self): """Check if the user has permissions to run the software update :returns: Status if the user has the permissions """ return self.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.canCheckForUpdates && aus.canApplyUpdates; """) @property def build_info(self): """Return information of the current build version :returns: A dictionary of build information """ update_url = self.get_update_url(True) return { 'buildid': self.app_info.appBuildID, 'channel': self.update_channel.channel, 'disabled_addons': self.prefs.get_pref(self.PREF_DISABLED_ADDONS), 'locale': self.app_info.locale, 'mar_channels': self.mar_channels.channels, 'update_url': update_url, 'update_snippet': self.get_update_snippet(update_url), 'user_agent': self.app_info.user_agent, 'version': self.app_info.version } @property def is_complete_update(self): """Return true if the offered update is a complete update :returns: True if the offered update is a complete update """ # Throw when isCompleteUpdate is called without an update. This should # never happen except if the test is incorrectly written. assert self.active_update.exists, 'An active update has been found' patch_count = self.active_update.patch_count assert patch_count == 1 or patch_count == 2,\ 'An update must have one or two patches included' # Ensure Partial and Complete patches produced have unique urls if patch_count == 2: patch0_url = self.active_update.get_patch_at(0)['URL'] patch1_url = self.active_update.get_patch_at(1)['URL'] assert patch0_url != patch1_url,\ 'Partial and Complete download URLs are different' return self.active_update.selected_patch['type'] == 'complete' @property def mar_channels(self): """ Holds a reference to a :class:`MARChannels` object.""" return self._mar_channels @property def os_version(self): """Returns information about the OS version :returns: The OS version """ return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (ex) { } if (osVersion) { try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, // so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; """) @property def patch_info(self): """ Returns information of the active update in the queue.""" info = {'channel': self.update_channel.channel} if (self.active_update.exists): info['buildid'] = self.active_update.buildID info['is_complete'] = self.is_complete_update info['size'] = self.active_update.selected_patch['size'] info['type'] = self.update_type info['url_mirror'] = \ self.active_update.selected_patch['finalURL'] or 'n/a' info['version'] = self.active_update.appVersion return info @property def staging_directory(self): """ Returns the path to the updates staging directory.""" return self.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.getUpdatesDirectory().path; """) @property def update_channel(self): """ Holds a reference to an :class:`UpdateChannel` object.""" return self._update_channel @property def update_type(self): """Returns the type of the active update.""" return self.active_update.type def force_fallback(self): """Update the update.status file and set the status to 'failed:6'""" with open(os.path.join(self.staging_directory, 'update.status'), 'w') as f: f.write('failed: 6\n') def get_update_snippet(self, update_url): """Retrieve contents of the update snippet. :param update_url: URL to the update snippet """ snippet = None try: import urllib2 response = urllib2.urlopen(update_url) snippet = response.read() except Exception: pass return snippet def get_update_url(self, force=False): """Retrieve the AUS update URL the update snippet is retrieved from. :param force: Boolean flag to force an update check :returns: The URL of the update snippet """ url = self.prefs.get_pref(self.PREF_APP_UPDATE_URL_OVERRIDE) if not url: url = self.prefs.get_pref(self.PREF_APP_UPDATE_URL) # Format the URL by replacing placeholders url = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """, script_args=[url]) if force: if '?' in url: url += '&' else: url += '?' url += 'force=1' return url