Home | History | Annotate | Download | only in server2
      1 # Copyright 2013 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 
      5 import json
      6 import tarfile
      7 from StringIO import StringIO
      8 
      9 from file_system import FileNotFoundError
     10 from future import Future
     11 from patcher import Patcher
     12 
     13 
     14 _CHROMIUM_REPO_BASEURLS = [
     15   'https://src.chromium.org/svn/trunk/src/',
     16   'http://src.chromium.org/svn/trunk/src/',
     17   'svn://svn.chromium.org/chrome/trunk/src',
     18   'https://chromium.googlesource.com/chromium/src.git@master',
     19   'http://git.chromium.org/chromium/src.git@master',
     20 ]
     21 
     22 
     23 class RietveldPatcherError(Exception):
     24   def __init__(self, message):
     25     self.message = message
     26 
     27 
     28 def _GetAsyncFetchCallback(issue, patchset, files, fetcher):
     29   tarball = fetcher.FetchAsync('tarball/%s/%s' % (issue, patchset))
     30 
     31   def resolve():
     32     tarball_result = tarball.Get()
     33     if tarball_result.status_code != 200:
     34       raise RietveldPatcherError(
     35           'Failed to download tarball for issue %s patchset %s. Status: %s' %
     36           (issue, patchset, tarball_result.status_code))
     37 
     38     try:
     39       tar = tarfile.open(fileobj=StringIO(tarball_result.content))
     40     except tarfile.TarError as e:
     41       raise RietveldPatcherError(
     42           'Error loading tarball for issue %s patchset %s.' % (issue, patchset))
     43 
     44     value = {}
     45     for path in files:
     46       tar_path = 'b/%s' % path
     47 
     48       patched_file = None
     49       try:
     50         patched_file = tar.extractfile(tar_path)
     51         data = patched_file.read()
     52       except tarfile.TarError as e:
     53         # Show appropriate error message in the unlikely case that the tarball
     54         # is corrupted.
     55         raise RietveldPatcherError(
     56             'Error extracting tarball for issue %s patchset %s file %s.' %
     57             (issue, patchset, tar_path))
     58       except KeyError as e:
     59         raise FileNotFoundError(
     60             'File %s not found in the tarball for issue %s patchset %s' %
     61             (tar_path, issue, patchset))
     62       finally:
     63         if patched_file:
     64           patched_file.close()
     65 
     66       value[path] = data
     67 
     68     return value
     69 
     70   return resolve
     71 
     72 
     73 class RietveldPatcher(Patcher):
     74   ''' Class to fetch resources from a patchset in Rietveld.
     75   '''
     76   def __init__(self,
     77                issue,
     78                fetcher):
     79     self._issue = issue
     80     self._fetcher = fetcher
     81     self._cache = None
     82 
     83   # In RietveldPatcher, the version is the latest patchset number.
     84   def GetVersion(self):
     85     try:
     86       issue_json = json.loads(self._fetcher.Fetch(
     87           'api/%s' % self._issue).content)
     88     except Exception as e:
     89       raise RietveldPatcherError(
     90           'Failed to fetch information for issue %s.' % self._issue)
     91 
     92     if issue_json.get('closed'):
     93       raise RietveldPatcherError('Issue %s has been closed.' % self._issue)
     94 
     95     patchsets = issue_json.get('patchsets')
     96     if not isinstance(patchsets, list) or len(patchsets) == 0:
     97       raise RietveldPatcherError('Cannot parse issue %s.' % self._issue)
     98 
     99     if not issue_json.get('base_url') in _CHROMIUM_REPO_BASEURLS:
    100       raise RietveldPatcherError('Issue %s\'s base url is unknown.' %
    101           self._issue)
    102 
    103     return str(patchsets[-1])
    104 
    105   def GetPatchedFiles(self, version=None):
    106     if version is None:
    107       patchset = self.GetVersion()
    108     else:
    109       patchset = version
    110     try:
    111       patchset_json = json.loads(self._fetcher.Fetch(
    112           'api/%s/%s' % (self._issue, patchset)).content)
    113     except Exception as e:
    114       raise RietveldPatcherError(
    115           'Failed to fetch details for issue %s patchset %s.' % (self._issue,
    116                                                                  patchset))
    117 
    118     files = patchset_json.get('files')
    119     if files is None or not isinstance(files, dict):
    120       raise RietveldPatcherError('Failed to parse issue %s patchset %s.' %
    121           (self._issue, patchset))
    122 
    123     added = []
    124     deleted = []
    125     modified = []
    126     for f in files:
    127       status = (files[f].get('status') or 'M')
    128       # status can be 'A   ' or 'A + '
    129       if 'A' in status:
    130         added.append(f)
    131       elif 'D' in status:
    132         deleted.append(f)
    133       elif 'M' in status:
    134         modified.append(f)
    135       else:
    136         raise RietveldPatcherError('Unknown file status for file %s: "%s."' %
    137                                                                 (key, status))
    138 
    139     return (added, deleted, modified)
    140 
    141   def Apply(self, paths, file_system, version=None):
    142     if version is None:
    143       version = self.GetVersion()
    144     return Future(callback=_GetAsyncFetchCallback(self._issue,
    145                                                   version,
    146                                                   paths,
    147                                                   self._fetcher))
    148 
    149   def GetIdentity(self):
    150     return self._issue
    151