| from copy import copy |
| import os |
| import re |
| from util.algorithms import getChunk |
| |
| |
| class UpdateVerifyError(Exception): |
| pass |
| |
| |
| class UpdateVerifyConfig(object): |
| comment_regex = re.compile("^#") |
| key_write_order = ("release", "product", "platform", "build_id", "locales", |
| "channel", "patch_types", "from", "aus_server", |
| "ftp_server_from", "ftp_server_to", "to", |
| "mar_channel_IDs") |
| global_keys = ("product", "channel", "aus_server", "to") |
| release_keys = ("release", "build_id", "locales", "patch_types", "from", |
| "ftp_server_from", "ftp_server_to", "mar_channel_IDs", |
| "platform") |
| first_only_keys = ("from", "aus_server", "to") |
| compare_attrs = global_keys + ("releases",) |
| |
| def __init__(self, product=None, channel=None, |
| aus_server=None, to=None): |
| self.product = product |
| self.channel = channel |
| self.aus_server = aus_server |
| self.to = to |
| self.releases = [] |
| |
| def __eq__(self, other): |
| self_list = [getattr(self, attr) for attr in self.compare_attrs] |
| other_list = [getattr(other, attr) for attr in self.compare_attrs] |
| return self_list == other_list |
| |
| def __ne__(self, other): |
| return not self.__eq__(other) |
| |
| def _parseLine(self, line): |
| entry = {} |
| items = re.findall("\w+=[\"'][^\"']*[\"']", line) |
| for i in items: |
| m = re.search( |
| "(?P<key>\w+)=[\"'](?P<value>.+)[\"']", i).groupdict() |
| if m["key"] not in self.global_keys and m["key"] not in self.release_keys: |
| raise UpdateVerifyError( |
| "Unknown key '%s' found on line:\n%s" % (m["key"], line)) |
| if m["key"] in entry: |
| raise UpdateVerifyError("Multiple values found for key '%s' on line:\n%s" % (m["key"], line)) |
| entry[m["key"]] = m["value"] |
| if not entry: |
| raise UpdateVerifyError("No parseable data in line '%s'" % line) |
| return entry |
| |
| def _addEntry(self, entry, first): |
| releaseKeys = {} |
| for k, v in entry.items(): |
| if k in self.global_keys: |
| setattr(self, k, entry[k]) |
| elif k in self.release_keys: |
| # "from" is reserved in Python |
| if k == "from": |
| releaseKeys["from_path"] = v |
| else: |
| releaseKeys[k] = v |
| self.addRelease(**releaseKeys) |
| |
| def read(self, config): |
| f = open(config) |
| # Only the first non-comment line of an update verify config should |
| # have a "from" and"ausServer". Ignore any subsequent lines with them. |
| first = True |
| for line in f.readlines(): |
| # Skip comment lines |
| if self.comment_regex.search(line): |
| continue |
| self._addEntry(self._parseLine(line), first) |
| first = False |
| |
| def write(self, fh): |
| first = True |
| for releaseInfo in self.releases: |
| for key in self.key_write_order: |
| if key in self.global_keys and (first or key not in self.first_only_keys): |
| value = getattr(self, key) |
| elif key in self.release_keys: |
| value = releaseInfo[key] |
| else: |
| value = None |
| if value is not None: |
| fh.write(key) |
| fh.write("=") |
| if isinstance(value, (list, tuple)): |
| fh.write('"%s" ' % " ".join(value)) |
| else: |
| fh.write('"%s" ' % str(value)) |
| # Rewind one character to avoid having a trailing space |
| fh.seek(-1, os.SEEK_CUR) |
| fh.write("\n") |
| first = False |
| |
| def addRelease(self, release=None, build_id=None, locales=[], |
| patch_types=['complete'], from_path=None, |
| ftp_server_from=None, ftp_server_to=None, |
| mar_channel_IDs=None, platform=None): |
| """Locales and patch_types can be passed as either a string or a list. |
| If a string is passed, they will be converted to a list for internal |
| storage""" |
| if self.getRelease(build_id, from_path): |
| raise UpdateVerifyError("Couldn't add release identified by build_id '%s' and from_path '%s': already exists in config" % (build_id, from_path)) |
| if isinstance(locales, basestring): |
| locales = sorted(list(locales.split())) |
| if isinstance(patch_types, basestring): |
| patch_types = list(patch_types.split()) |
| self.releases.append({ |
| "release": release, |
| "build_id": build_id, |
| "locales": locales, |
| "patch_types": patch_types, |
| "from": from_path, |
| "ftp_server_from": ftp_server_from, |
| "ftp_server_to": ftp_server_to, |
| "mar_channel_IDs": mar_channel_IDs, |
| "platform": platform, |
| }) |
| |
| def addLocaleToRelease(self, build_id, locale, from_path=None): |
| r = self.getRelease(build_id, from_path) |
| if not r: |
| raise UpdateVerifyError("Couldn't add '%s' to release identified by build_id '%s' and from_path '%s': '%s' doesn't exist in this config." % (locale, build_id, from_path, build_id)) |
| r["locales"].append(locale) |
| r["locales"] = sorted(r["locales"]) |
| |
| def getRelease(self, build_id, from_path): |
| for r in self.releases: |
| if r["build_id"] == build_id and r["from"] == from_path: |
| return r |
| return {} |
| |
| def getFullReleaseTests(self): |
| return [r for r in self.releases if r["from"] is not None] |
| |
| def getQuickReleaseTests(self): |
| return [r for r in self.releases if r["from"] is None] |
| |
| def getChunk(self, chunks, thisChunk): |
| fullTests = [] |
| quickTests = [] |
| for test in self.getFullReleaseTests(): |
| for locale in test["locales"]: |
| fullTests.append([test["build_id"], locale, test["from"]]) |
| for test in self.getQuickReleaseTests(): |
| for locale in test["locales"]: |
| quickTests.append([test["build_id"], locale, test["from"]]) |
| allTests = getChunk(fullTests, chunks, thisChunk) |
| allTests.extend(getChunk(quickTests, chunks, thisChunk)) |
| |
| newConfig = UpdateVerifyConfig(self.product, self.channel, |
| self.aus_server, self.to) |
| for t in allTests: |
| build_id, locale, from_path = t |
| if from_path == "None": |
| from_path = None |
| r = self.getRelease(build_id, from_path) |
| try: |
| newConfig.addRelease(r["release"], build_id, locales=[], |
| ftp_server_from=r["ftp_server_from"], |
| ftp_server_to=r["ftp_server_to"], |
| patch_types=r["patch_types"], from_path=from_path, |
| mar_channel_IDs=r["mar_channel_IDs"], |
| platform=r["platform"]) |
| except UpdateVerifyError: |
| pass |
| newConfig.addLocaleToRelease(build_id, locale, from_path) |
| return newConfig |