15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import logging
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import operator
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)from appengine_url_fetcher import AppEngineUrlFetcher
10b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)import url_constants
11c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
12424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
137d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)class ChannelInfo(object):
14424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''Represents a Chrome channel with three pieces of information. |channel| is
151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  one of 'stable', 'beta', 'dev', or 'master'. |branch| and |version| correspond
16424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  with each other, and represent different releases of Chrome. Note that
17424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  |branch| and |version| can occasionally be the same for separate channels
18424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  (i.e. 'beta' and 'dev'), so all three fields are required to uniquely
19424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  identify a channel.
20424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''
21424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def __init__(self, channel, branch, version):
234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    assert isinstance(channel, basestring), channel
244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    assert isinstance(branch, basestring), branch
254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # TODO(kalman): Assert that this is a string. One day Chromium will probably
264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # be served out of a git repository and the versions will no longer be ints.
271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert isinstance(version, int) or version == 'master', version
287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self.channel = channel
297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self.branch = branch
307d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self.version = version
317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
32424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def __eq__(self, other):
33424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return self.__dict__ == other.__dict__
34424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
35424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def __ne__(self, other):
36424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return not (self == other)
37424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __repr__(self):
394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return '%s%s' % (type(self).__name__, repr(self.__dict__))
404e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
414e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __str__(self):
424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return repr(self)
434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
44424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class BranchUtility(object):
46424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''Provides methods for working with Chrome channel, branch, and version
47424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  data served from OmahaProxy.
48424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''
49424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
507d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def __init__(self, fetch_url, history_url, fetcher, object_store_creator):
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._fetcher = fetcher
52ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    def create_object_store(category):
53ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      return object_store_creator.Create(BranchUtility, category=category)
54ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self._branch_object_store = create_object_store('branch')
55ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self._version_object_store = create_object_store('version')
567d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._fetch_result = self._fetcher.FetchAsync(fetch_url)
577d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._history_result = self._fetcher.FetchAsync(history_url)
587d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
597d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  @staticmethod
60424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def Create(object_store_creator):
61424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return BranchUtility(url_constants.OMAHA_PROXY_URL,
62424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                         url_constants.OMAHA_DEV_HISTORY,
63424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                         AppEngineUrlFetcher(),
64424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                         object_store_creator)
65424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
66424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  @staticmethod
677d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def GetAllChannelNames():
681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return ('stable', 'beta', 'dev', 'master')
697d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
707d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  @staticmethod
717d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def NewestChannel(channels):
72ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    channels = set(channels)
737d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    for channel in reversed(BranchUtility.GetAllChannelNames()):
747d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      if channel in channels:
757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        return channel
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
77424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def Newer(self, channel_info):
78424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Given a ChannelInfo object, returns a new ChannelInfo object
79424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    representing the next most recent Chrome version/branch combination.
80424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if channel_info.channel == 'master':
82424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return None
83424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if channel_info.channel == 'stable':
84424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      stable_info = self.GetChannelInfo('stable')
85424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if channel_info.version < stable_info.version:
86424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        return self.GetStableChannelInfo(channel_info.version + 1)
87424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    names = self.GetAllChannelNames()
88424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return self.GetAllChannelInfo()[names.index(channel_info.channel) + 1]
89424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
90424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def Older(self, channel_info):
91424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Given a ChannelInfo object, returns a new ChannelInfo object
92424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    representing the previous Chrome version/branch combination.
93424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
94424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if channel_info.channel == 'stable':
95424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if channel_info.version <= 5:
96424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        # BranchUtility can't access branch data from before Chrome version 5.
97424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        return None
98424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return self.GetStableChannelInfo(channel_info.version - 1)
99424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    names = self.GetAllChannelNames()
100424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return self.GetAllChannelInfo()[names.index(channel_info.channel) - 1]
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
102b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  @staticmethod
103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def SplitChannelNameFromPath(path):
1047d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''Splits the channel name out of |path|, returning the tuple
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    (channel_name, real_path). If the channel cannot be determined then returns
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    (None, path).
1077d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''
1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if '/' in path:
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      first, second = path.split('/', 1)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      first, second = (path, '')
1127d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if first in BranchUtility.GetAllChannelNames():
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return (first, second)
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (None, path)
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
116424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def GetAllBranches(self):
117424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return tuple((channel, self.GetChannelInfo(channel).branch)
1187d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)            for channel in BranchUtility.GetAllChannelNames())
1197d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
120424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def GetAllVersions(self):
121424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return tuple(self.GetChannelInfo(channel).version
1227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)            for channel in BranchUtility.GetAllChannelNames())
1237d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1247d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def GetAllChannelInfo(self):
125424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return tuple(self.GetChannelInfo(channel)
1267d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)            for channel in BranchUtility.GetAllChannelNames())
1277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def GetChannelInfo(self, channel):
1304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    version = self._ExtractFromVersionJson(channel, 'version')
1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if version != 'master':
1324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      version = int(version)
1337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    return ChannelInfo(channel,
1347d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)                       self._ExtractFromVersionJson(channel, 'branch'),
1354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                       version)
1367d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
137424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def GetStableChannelInfo(self, version):
138424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Given a |version| corresponding to a 'stable' version of Chrome, returns
139424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    a ChannelInfo object representing that version.
140424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
141424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return ChannelInfo('stable', self.GetBranchForVersion(version), version)
142424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
1437d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def _ExtractFromVersionJson(self, channel_name, data_type):
1447d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''Returns the branch or version number for a channel name.
1457d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''
1461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if channel_name == 'master':
1471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return 'master'
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1497d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if data_type == 'branch':
1507d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      object_store = self._branch_object_store
1517d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    elif data_type == 'version':
1527d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      object_store = self._version_object_store
1537d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1547d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    data = object_store.Get(channel_name).Get()
1557d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if data is not None:
1567d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      return data
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
1597d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      version_json = json.loads(self._fetch_result.Get().content)
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except Exception as e:
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # This can happen if omahaproxy is misbehaving, which we've seen before.
1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      # Quick hack fix: just serve from master until it's fixed.
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      logging.error('Failed to fetch or parse branch from omahaproxy: %s! '
1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    'Falling back to "master".' % e)
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return 'master'
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1677d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    numbers = {}
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for entry in version_json:
16946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if entry['os'] not in ('win', 'linux', 'mac', 'cros'):
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for version in entry['versions']:
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if version['channel'] != channel_name:
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
1747d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        if data_type == 'branch':
1757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          number = version['version'].split('.')[2]
1767d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        elif data_type == 'version':
1777d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          number = version['version'].split('.')[0]
1787d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        if number not in numbers:
1797d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          numbers[number] = 0
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
1817d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)          numbers[number] += 1
1827d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1837d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    sorted_numbers = sorted(numbers.iteritems(),
1844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                            key=operator.itemgetter(1),
1854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                            reverse=True)
1864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    object_store.Set(channel_name, sorted_numbers[0][0])
1874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return sorted_numbers[0][0]
1887d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
1897d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def GetBranchForVersion(self, version):
1907d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''Returns the most recent branch for a given chrome version number using
1917d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    data stored on omahaproxy (see url_constants).
1927d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''
1931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if version == 'master':
1941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return 'master'
1957d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
196a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    branch = self._branch_object_store.Get(str(version)).Get()
1977d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if branch is not None:
1987d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      return branch
1997d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
2007d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    version_json = json.loads(self._history_result.Get().content)
20146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    for entry in version_json:
20246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      version_title = entry['version'].split('.')
2037d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      if version_title[0] == str(version):
2044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self._branch_object_store.Set(str(version), version_title[2])
2054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        return version_title[2]
2067d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
207eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    raise ValueError('The branch for %s could not be found.' % version)
208eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
209eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def GetChannelForVersion(self, version):
210eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''Returns the name of the development channel corresponding to a given
211eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    version number.
212eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
213eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    for channel_info in self.GetAllChannelInfo():
214eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if channel_info.channel == 'stable' and version <= channel_info.version:
215eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        return channel_info.channel
216eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if version == channel_info.version:
217eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        return channel_info.channel
2187d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)
2197d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def GetLatestVersionNumber(self):
2207d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''Returns the most recent version number found using data stored on
2217d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    omahaproxy.
2227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    '''
2237d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    latest_version = self._version_object_store.Get('latest').Get()
2247d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    if latest_version is not None:
2257d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      return latest_version
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    version_json = json.loads(self._history_result.Get().content)
2287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    latest_version = 0
22946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    for entry in version_json:
23046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      version_title = entry['version'].split('.')
2317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      version = int(version_title[0])
2327d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      if version > latest_version:
2337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)        latest_version = version
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2357d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    self._version_object_store.Set('latest', latest_version)
2367d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    return latest_version
237