Index: chromite/lib/binpkg.py |
diff --git a/chromite/lib/binpkg.py b/chromite/lib/binpkg.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..31febfdcc25ffe6d48e4b4cad7fa64c5ff741f22 |
--- /dev/null |
+++ b/chromite/lib/binpkg.py |
@@ -0,0 +1,153 @@ |
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+# |
+# Adapted from portage/getbinpkg.py -- Portage binary-package helper functions |
+# Copyright 2003-2004 Gentoo Foundation |
+# Distributed under the terms of the GNU General Public License v2 |
+ |
+import operator |
+import os |
+import time |
+import urllib2 |
+ |
+class PackageIndex(object): |
dianders
2010/11/23 21:47:39
Docstring?
...would be nice to include a spec for
davidjames
2010/11/23 22:53:14
Thanks for all the feedback! Will address your fee
davidjames
2010/11/29 21:18:55
Done. (See http://codereview.chromium.org/5344002/
|
+ |
+ def __init__(self): |
+ """Constructor.""" |
dianders
2010/11/23 21:47:39
All of these members are public? Can you document
davidjames
2010/11/29 21:18:55
Done.
|
+ self.header = {} |
+ self.packages = [] |
+ self.modified = False |
+ |
+ def _ReadPkgIndex(self, pkgfile): |
+ """Read entry from packages file. |
dianders
2010/11/23 21:47:39
Read entries (plural).
davidjames
2010/11/29 21:18:55
Done.
|
+ |
dianders
2010/11/23 21:47:39
Docstring:
This will read entries that look like
davidjames
2010/11/29 21:18:55
Done.
|
+ Args: |
+ pkgfile: A python file object. |
dianders
2010/11/23 21:47:39
Returns: ?
davidjames
2010/11/29 21:18:55
Done.
|
+ """ |
+ d = {} |
+ for line in pkgfile: |
+ line = line.rstrip('\n') |
+ if not line: |
+ break |
+ line = line.split(': ', 1) |
+ if len(line) == 2: |
+ k, v = line |
+ d[k] = v |
+ return d |
+ |
+ def _WritePkgIndex(self, pkgfile, items): |
+ """Write entry to packages file. |
+ |
dianders
2010/11/23 21:47:39
This will terminate the list of items with a blank
davidjames
2010/11/29 21:18:55
Done.
|
+ Args: |
+ pkgfile: A python file object. |
+ items: A list of tuples containing the entries to write. |
dianders
2010/11/23 21:47:39
...containing (key, value) pairs to write.
davidjames
2010/11/29 21:18:55
Done.
|
+ """ |
+ for k, v in items: |
dianders
2010/11/23 21:47:39
assert ':' not in k
davidjames
2010/11/29 21:18:55
Skipped this one.
|
+ pkgfile.write('%s: %s\n' % (k, v)) |
+ pkgfile.write('\n') |
+ |
+ def Read(self, pkgfile): |
+ """Read entire packages file. |
+ |
dianders
2010/11/23 21:47:39
This is a shortcut for calling ReadHeader() and Re
davidjames
2010/11/29 21:18:55
Made those two functions private.
|
+ Args: |
+ pkgfile: A python file object. |
+ """ |
+ self.ReadHeader(pkgfile) |
+ self.ReadBody(pkgfile) |
+ |
+ def ReadHeader(self, pkgfile): |
+ """Read header of packages file. |
+ |
+ Args: |
+ pkgfile: A python file object. |
+ """ |
+ self.header.update(self._ReadPkgIndex(pkgfile)) |
+ |
+ def ReadBody(self, pkgfile): |
+ """Read body of packages file. |
+ |
+ Args: |
+ pkgfile: A python file object. |
dianders
2010/11/23 21:47:39
The header and the blank line following the header
|
+ """ |
dianders
2010/11/23 21:47:39
Comment: Read all the sections in the body by loop
|
+ while True: |
+ d = self._ReadPkgIndex(pkgfile) |
+ if not d: |
+ break |
dianders
2010/11/23 21:47:39
In order to be a valid body section, there must be
|
+ if d.get('CPV'): |
dianders
2010/11/23 21:47:39
if 'CPV' in d:
|
+ self.packages.append(d) |
+ |
+ def Write(self, pkgfile): |
+ """Write a packages file to disk. |
+ |
dianders
2010/11/23 21:47:39
Document: This has a side effect of updating the T
davidjames
2010/11/23 22:53:14
That'd be a better name, but Portage calls the var
|
+ Args: |
+ pkgfile: A python file object. |
+ """ |
+ if self.modified: |
+ self.header['TIMESTAMP'] = str(long(time.time())) |
+ self.header['PACKAGES'] = str(len(self.packages)) |
dianders
2010/11/23 21:47:39
Should we be clearing .modified?
|
+ keys = list(self.header) |
+ keys.sort() |
+ self._WritePkgIndex(pkgfile, [(k, self.header[k]) \ |
+ for k in keys if self.header[k]]) |
dianders
2010/11/23 21:47:39
AKA:
self._WritePkgIndex(pkgfile, sorted((k, v) f
|
+ for metadata in sorted(self.packages, key=operator.itemgetter('CPV')): |
dianders
2010/11/23 21:47:39
Comment: Will write each item in self.packages (is
|
+ cpv = metadata['CPV'] |
+ keys = list(metadata) |
+ keys.sort() |
+ self._WritePkgIndex(pkgfile, |
+ [(k, metadata[k]) for k in keys if metadata[k]]) |
+ |
+ |
+def GrabRemotePackageIndex(binhost_url): |
dianders
2010/11/23 21:47:39
Technically, this could be a class method on Packa
|
+ """Grab the latest binary package database from the specified URL. |
+ |
+ Args: |
+ binhost_url: Base URL of remote packages (PORTAGE_BINHOST). |
+ |
+ Returns: |
+ A PackageIndex object |
dianders
2010/11/23 21:47:39
COmment: Might return None in certain error condit
|
+ """ |
+ |
+ def _RetryUrlOpen(url, tries=3): |
dianders
2010/11/23 21:47:39
Why does this need to be an encapsulated function?
|
+ """Open the specified url, retrying if we run into temporary errors. |
+ |
+ We retry for both network errors and 5xx Server Errors. We do not retry |
+ for HTTP errors with a non-5xx code. |
dianders
2010/11/23 21:47:39
Does this timeout, or can it ever get stuck foreve
dianders
2010/11/23 21:47:39
Comment: We will sleep for 10 seconds before retry
|
+ |
+ Args: |
+ url: The specified url. |
+ tries: The number of times to try. |
+ |
+ Returns: |
+ The result of urllib2.urlopen(url). |
dianders
2010/11/23 21:47:39
This is a file-like object.
|
+ """ |
+ for i in range(tries): |
+ try: |
+ return urllib2.urlopen(url) |
+ except urllib2.HTTPError as e: |
+ if i + 1 >= tries or e.code < 500: |
+ raise |
+ else: |
+ print 'Cannot GET %s: %s' % (url, str(e)) |
+ except urllib2.URLError as e: |
+ if i + 1 >= tries: |
+ raise |
+ else: |
+ print 'Cannot GET %s: %s' % (url, str(e)) |
+ print "Sleeping for 10 seconds before retrying..." |
+ time.sleep(10) |
+ |
+ url = os.path.join(binhost_url, 'Packages') |
dianders
2010/11/23 21:47:39
No, not os.path.join. This will fail on windows o
|
+ try: |
+ f = _RetryUrlOpen(url) |
+ except urllib2.HTTPError as e: |
+ if e.code == 404: |
+ return None |
+ raise |
+ |
+ pkgindex = PackageIndex() |
+ pkgindex.Read(f) |
+ f.close() |
+ return pkgindex |
+ |
+ |