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.
9 from environment_wrappers
import CreateUrlFetcher
13 class ChannelInfo(object):
14 '''Represents a Chrome channel with three pieces of information. |channel| is
15 one of 'stable', 'beta', 'dev', or 'master'. |branch| and |version| correspond
16 with each other, and represent different releases of Chrome. Note that
17 |branch| and |version| can occasionally be the same for separate channels
18 (i.e. 'beta' and 'dev'), so all three fields are required to uniquely
22 def __init__(self
, channel
, branch
, version
):
23 assert isinstance(channel
, basestring
), channel
24 assert isinstance(branch
, basestring
), branch
25 # TODO(kalman): Assert that this is a string. One day Chromium will probably
26 # be served out of a git repository and the versions will no longer be ints.
27 assert isinstance(version
, int) or version
== 'master', version
28 self
.channel
= channel
30 self
.version
= version
32 def __eq__(self
, other
):
33 return self
.__dict
__ == other
.__dict
__
35 def __ne__(self
, other
):
36 return not (self
== other
)
39 return '%s%s' % (type(self
).__name
__, repr(self
.__dict
__))
45 class BranchUtility(object):
46 '''Provides methods for working with Chrome channel, branch, and version
47 data served from OmahaProxy.
50 def __init__(self
, fetch_url
, history_url
, fetcher
, object_store_creator
):
51 self
._fetcher
= fetcher
52 def create_object_store(category
):
53 return object_store_creator
.Create(BranchUtility
, category
=category
)
54 self
._branch
_object
_store
= create_object_store('branch')
55 self
._version
_object
_store
= create_object_store('version')
56 self
._fetch
_result
= self
._fetcher
.FetchAsync(fetch_url
)
57 self
._history
_result
= self
._fetcher
.FetchAsync(history_url
)
60 def Create(object_store_creator
):
61 return BranchUtility(url_constants
.OMAHA_PROXY_URL
,
62 url_constants
.OMAHA_DEV_HISTORY
,
67 def GetAllChannelNames():
68 return ('stable', 'beta', 'dev', 'master')
71 def NewestChannel(channels
):
72 channels
= set(channels
)
73 for channel
in reversed(BranchUtility
.GetAllChannelNames()):
74 if channel
in channels
:
77 def Newer(self
, channel_info
):
78 '''Given a ChannelInfo object, returns a new ChannelInfo object
79 representing the next most recent Chrome version/branch combination.
81 if channel_info
.channel
== 'master':
83 if channel_info
.channel
== 'stable':
84 stable_info
= self
.GetChannelInfo('stable')
85 if channel_info
.version
< stable_info
.version
:
86 return self
.GetStableChannelInfo(channel_info
.version
+ 1)
87 names
= self
.GetAllChannelNames()
88 return self
.GetAllChannelInfo()[names
.index(channel_info
.channel
) + 1]
90 def Older(self
, channel_info
):
91 '''Given a ChannelInfo object, returns a new ChannelInfo object
92 representing the previous Chrome version/branch combination.
94 if channel_info
.channel
== 'stable':
95 if channel_info
.version
<= 5:
96 # BranchUtility can't access branch data from before Chrome version 5.
98 return self
.GetStableChannelInfo(channel_info
.version
- 1)
99 names
= self
.GetAllChannelNames()
100 return self
.GetAllChannelInfo()[names
.index(channel_info
.channel
) - 1]
103 def SplitChannelNameFromPath(path
):
104 '''Splits the channel name out of |path|, returning the tuple
105 (channel_name, real_path). If the channel cannot be determined then returns
109 first
, second
= path
.split('/', 1)
111 first
, second
= (path
, '')
112 if first
in BranchUtility
.GetAllChannelNames():
113 return (first
, second
)
116 def GetAllBranches(self
):
117 return tuple((channel
, self
.GetChannelInfo(channel
).branch
)
118 for channel
in BranchUtility
.GetAllChannelNames())
120 def GetAllVersions(self
):
121 return tuple(self
.GetChannelInfo(channel
).version
122 for channel
in BranchUtility
.GetAllChannelNames())
124 def GetAllChannelInfo(self
):
125 return tuple(self
.GetChannelInfo(channel
)
126 for channel
in BranchUtility
.GetAllChannelNames())
129 def GetChannelInfo(self
, channel
):
130 version
= self
._ExtractFromVersionJson
(channel
, 'version')
131 if version
!= 'master':
132 version
= int(version
)
133 return ChannelInfo(channel
,
134 self
._ExtractFromVersionJson
(channel
, 'branch'),
137 def GetStableChannelInfo(self
, version
):
138 '''Given a |version| corresponding to a 'stable' version of Chrome, returns
139 a ChannelInfo object representing that version.
141 return ChannelInfo('stable', self
.GetBranchForVersion(version
), version
)
143 def _ExtractFromVersionJson(self
, channel_name
, data_type
):
144 '''Returns the branch or version number for a channel name.
146 if channel_name
== 'master':
149 if data_type
== 'branch':
150 object_store
= self
._branch
_object
_store
151 elif data_type
== 'version':
152 object_store
= self
._version
_object
_store
154 data
= object_store
.Get(channel_name
).Get()
159 version_json
= json
.loads(self
._fetch
_result
.Get().content
)
160 except Exception as e
:
161 # This can happen if omahaproxy is misbehaving, which we've seen before.
162 # Quick hack fix: just serve from master until it's fixed.
163 logging
.error('Failed to fetch or parse branch from omahaproxy: %s! '
164 'Falling back to "master".' % e
)
168 for entry
in version_json
:
169 if entry
['os'] not in ('win', 'linux', 'mac', 'cros'):
171 for version
in entry
['versions']:
172 if version
['channel'] != channel_name
:
174 if data_type
== 'branch':
175 number
= version
['version'].split('.')[2]
176 elif data_type
== 'version':
177 number
= version
['version'].split('.')[0]
178 if number
not in numbers
:
183 sorted_numbers
= sorted(numbers
.iteritems(),
184 key
=operator
.itemgetter(1),
186 object_store
.Set(channel_name
, sorted_numbers
[0][0])
187 return sorted_numbers
[0][0]
189 def GetBranchForVersion(self
, version
):
190 '''Returns the most recent branch for a given chrome version number using
191 data stored on omahaproxy (see url_constants).
193 if version
== 'master':
196 branch
= self
._branch
_object
_store
.Get(str(version
)).Get()
197 if branch
is not None:
200 version_json
= json
.loads(self
._history
_result
.Get().content
)
201 for entry
in version_json
:
202 version_title
= entry
['version'].split('.')
203 if version_title
[0] == str(version
):
204 self
._branch
_object
_store
.Set(str(version
), version_title
[2])
205 return version_title
[2]
207 raise ValueError('The branch for %s could not be found.' % version
)
209 def GetChannelForVersion(self
, version
):
210 '''Returns the name of the development channel corresponding to a given
213 for channel_info
in self
.GetAllChannelInfo():
214 if channel_info
.channel
== 'stable' and version
<= channel_info
.version
:
215 return channel_info
.channel
216 if version
== channel_info
.version
:
217 return channel_info
.channel
219 def GetLatestVersionNumber(self
):
220 '''Returns the most recent version number found using data stored on
223 latest_version
= self
._version
_object
_store
.Get('latest').Get()
224 if latest_version
is not None:
225 return latest_version
227 version_json
= json
.loads(self
._history
_result
.Get().content
)
229 for entry
in version_json
:
230 version_title
= entry
['version'].split('.')
231 version
= int(version_title
[0])
232 if version
> latest_version
:
233 latest_version
= version
235 self
._version
_object
_store
.Set('latest', latest_version
)
236 return latest_version