branch_utility.py revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import json
6import logging
7import operator
8
9from appengine_url_fetcher import AppEngineUrlFetcher
10import url_constants
11
12class ChannelInfo(object):
13  def __init__(self, channel, branch, version):
14    self.channel = channel
15    self.branch = branch
16    self.version = version
17
18class BranchUtility(object):
19  def __init__(self, fetch_url, history_url, fetcher, object_store_creator):
20    self._fetcher = fetcher
21    def create_object_store(category):
22      return object_store_creator.Create(BranchUtility, category=category)
23    self._branch_object_store = create_object_store('branch')
24    self._version_object_store = create_object_store('version')
25    self._fetch_result = self._fetcher.FetchAsync(fetch_url)
26    self._history_result = self._fetcher.FetchAsync(history_url)
27
28  @staticmethod
29  def GetAllChannelNames():
30    return ('stable', 'beta', 'dev', 'trunk')
31
32  @staticmethod
33  def NewestChannel(channels):
34    channels = set(channels)
35    for channel in reversed(BranchUtility.GetAllChannelNames()):
36      if channel in channels:
37        return channel
38
39  @staticmethod
40  def Create(object_store_creator):
41    return BranchUtility(url_constants.OMAHA_PROXY_URL,
42                         url_constants.OMAHA_DEV_HISTORY,
43                         AppEngineUrlFetcher(),
44                         object_store_creator)
45
46  @staticmethod
47  def SplitChannelNameFromPath(path):
48    '''Splits the channel name out of |path|, returning the tuple
49    (channel_name, real_path). If the channel cannot be determined then returns
50    (None, path).
51    '''
52    if '/' in path:
53      first, second = path.split('/', 1)
54    else:
55      first, second = (path, '')
56    if first in BranchUtility.GetAllChannelNames():
57      return (first, second)
58    return (None, path)
59
60  def GetAllBranchNumbers(self):
61    return ((channel, self.GetChannelInfo(channel).branch)
62            for channel in BranchUtility.GetAllChannelNames())
63
64  def GetAllVersionNumbers(self):
65    return (self.GetChannelInfo(channel).version
66            for channel in BranchUtility.GetAllChannelNames())
67
68  def GetAllChannelInfo(self):
69    return (self.GetChannelInfo(channel)
70            for channel in BranchUtility.GetAllChannelNames())
71
72
73  def GetChannelInfo(self, channel):
74    return ChannelInfo(channel,
75                       self._ExtractFromVersionJson(channel, 'branch'),
76                       self._ExtractFromVersionJson(channel, 'version'))
77
78  def _ExtractFromVersionJson(self, channel_name, data_type):
79    '''Returns the branch or version number for a channel name.
80    '''
81    if channel_name == 'trunk':
82      return 'trunk'
83
84    if data_type == 'branch':
85      object_store = self._branch_object_store
86    elif data_type == 'version':
87      object_store = self._version_object_store
88
89    data = object_store.Get(channel_name).Get()
90    if data is not None:
91      return data
92
93    try:
94      version_json = json.loads(self._fetch_result.Get().content)
95    except Exception as e:
96      # This can happen if omahaproxy is misbehaving, which we've seen before.
97      # Quick hack fix: just serve from trunk until it's fixed.
98      logging.error('Failed to fetch or parse branch from omahaproxy: %s! '
99                    'Falling back to "trunk".' % e)
100      return 'trunk'
101
102    numbers = {}
103    for entry in version_json:
104      if entry['os'] not in ['win', 'linux', 'mac', 'cros']:
105        continue
106      for version in entry['versions']:
107        if version['channel'] != channel_name:
108          continue
109        if data_type == 'branch':
110          number = version['version'].split('.')[2]
111        elif data_type == 'version':
112          number = version['version'].split('.')[0]
113        if number not in numbers:
114          numbers[number] = 0
115        else:
116          numbers[number] += 1
117
118    sorted_numbers = sorted(numbers.iteritems(),
119                            None,
120                            operator.itemgetter(1),
121                            True)
122    object_store.Set(channel_name, int(sorted_numbers[0][0]))
123    return int(sorted_numbers[0][0])
124
125  def GetBranchForVersion(self, version):
126    '''Returns the most recent branch for a given chrome version number using
127    data stored on omahaproxy (see url_constants).
128    '''
129    if version == 'trunk':
130      return 'trunk'
131
132    branch = self._branch_object_store.Get(version).Get()
133    if branch is not None:
134      return branch
135
136    version_json = json.loads(self._history_result.Get().content)
137    for entry in version_json['events']:
138      # Here, entry['title'] looks like: '<title> - <version>.##.<branch>.##'
139      version_title = entry['title'].split(' - ')[1].split('.')
140      if version_title[0] == str(version):
141        self._branch_object_store.Set(str(version), version_title[2])
142        return int(version_title[2])
143
144    raise ValueError('The branch for %s could not be found.' % version)
145
146  def GetChannelForVersion(self, version):
147    '''Returns the name of the development channel corresponding to a given
148    version number.
149    '''
150    for channel_info in self.GetAllChannelInfo():
151      if channel_info.channel == 'stable' and version <= channel_info.version:
152        return channel_info.channel
153      if version == channel_info.version:
154        return channel_info.channel
155
156  def GetLatestVersionNumber(self):
157    '''Returns the most recent version number found using data stored on
158    omahaproxy.
159    '''
160    latest_version = self._version_object_store.Get('latest').Get()
161    if latest_version is not None:
162      return latest_version
163
164    version_json = json.loads(self._history_result.Get().content)
165    latest_version = 0
166    for entry in version_json['events']:
167      version_title = entry['title'].split(' - ')[1].split('.')
168      version = int(version_title[0])
169      if version > latest_version:
170        latest_version = version
171
172    self._version_object_store.Set('latest', latest_version)
173    return latest_version
174