# Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import json import logging import operator from appengine_url_fetcher import AppEngineUrlFetcher import url_constants class ChannelInfo(object): def __init__(self, channel, branch, version): self.channel = channel self.branch = branch self.version = version class BranchUtility(object): def __init__(self, fetch_url, history_url, fetcher, object_store_creator): self._fetcher = fetcher # BranchUtility is obviously cross-channel, so set the channel to None. self._branch_object_store = object_store_creator.Create(BranchUtility, category='branch', channel=None) self._version_object_store = object_store_creator.Create(BranchUtility, category='version', channel=None) self._fetch_result = self._fetcher.FetchAsync(fetch_url) self._history_result = self._fetcher.FetchAsync(history_url) @staticmethod def GetAllChannelNames(): return ('stable', 'beta', 'dev', 'trunk') @staticmethod def NewestChannel(channels): for channel in reversed(BranchUtility.GetAllChannelNames()): if channel in channels: return channel @staticmethod def Create(object_store_creator): return BranchUtility(url_constants.OMAHA_PROXY_URL, url_constants.OMAHA_DEV_HISTORY, AppEngineUrlFetcher(), object_store_creator) @staticmethod def SplitChannelNameFromPath(path): '''Splits the channel name out of |path|, returning the tuple (channel_name, real_path). If the channel cannot be determined then returns (None, path). ''' if '/' in path: first, second = path.split('/', 1) else: first, second = (path, '') if first in BranchUtility.GetAllChannelNames(): return (first, second) return (None, path) def GetAllBranchNumbers(self): return ((channel, self.GetChannelInfo(channel).branch) for channel in BranchUtility.GetAllChannelNames()) def GetAllVersionNumbers(self): return (self.GetChannelInfo(channel).version for channel in BranchUtility.GetAllChannelNames()) def GetAllChannelInfo(self): return (self.GetChannelInfo(channel) for channel in BranchUtility.GetAllChannelNames()) def GetChannelInfo(self, channel): return ChannelInfo(channel, self._ExtractFromVersionJson(channel, 'branch'), self._ExtractFromVersionJson(channel, 'version')) def _ExtractFromVersionJson(self, channel_name, data_type): '''Returns the branch or version number for a channel name. ''' if channel_name == 'trunk': return 'trunk' if data_type == 'branch': object_store = self._branch_object_store elif data_type == 'version': object_store = self._version_object_store data = object_store.Get(channel_name).Get() if data is not None: return data try: version_json = json.loads(self._fetch_result.Get().content) except Exception as e: # This can happen if omahaproxy is misbehaving, which we've seen before. # Quick hack fix: just serve from trunk until it's fixed. logging.error('Failed to fetch or parse branch from omahaproxy: %s! ' 'Falling back to "trunk".' % e) return 'trunk' numbers = {} for entry in version_json: if entry['os'] not in ['win', 'linux', 'mac', 'cros']: continue for version in entry['versions']: if version['channel'] != channel_name: continue if data_type == 'branch': number = version['version'].split('.')[2] elif data_type == 'version': number = version['version'].split('.')[0] if number not in numbers: numbers[number] = 0 else: numbers[number] += 1 sorted_numbers = sorted(numbers.iteritems(), None, operator.itemgetter(1), True) object_store.Set(channel_name, int(sorted_numbers[0][0])) return int(sorted_numbers[0][0]) def GetBranchForVersion(self, version): '''Returns the most recent branch for a given chrome version number using data stored on omahaproxy (see url_constants). ''' if version == 'trunk': return 'trunk' branch = self._branch_object_store.Get(version).Get() if branch is not None: return branch version_json = json.loads(self._history_result.Get().content) for entry in version_json['events']: # Here, entry['title'] looks like: ' - <version>.##.<branch>.##' version_title = entry['title'].split(' - ')[1].split('.') if version_title[0] == str(version): self._branch_object_store.Set(str(version), version_title[2]) return int(version_title[2]) raise ValueError('The branch for %s could not be found.' % version) def GetChannelForVersion(self, version): '''Returns the name of the development channel corresponding to a given version number. ''' for channel_info in self.GetAllChannelInfo(): if channel_info.channel == 'stable' and version <= channel_info.version: return channel_info.channel if version == channel_info.version: return channel_info.channel def GetLatestVersionNumber(self): '''Returns the most recent version number found using data stored on omahaproxy. ''' latest_version = self._version_object_store.Get('latest').Get() if latest_version is not None: return latest_version version_json = json.loads(self._history_result.Get().content) latest_version = 0 for entry in version_json['events']: version_title = entry['title'].split(' - ')[1].split('.') version = int(version_title[0]) if version > latest_version: latest_version = version self._version_object_store.Set('latest', latest_version) return latest_version