| Index: scripts/master/try_job_rietveld.py | 
| diff --git a/scripts/master/try_job_rietveld.py b/scripts/master/try_job_rietveld.py | 
| index a351ad38e4f57afbd620b163be61c139d926a251..983c2e5979670d8b26867ea7cf16b1fc2be1e19d 100644 | 
| --- a/scripts/master/try_job_rietveld.py | 
| +++ b/scripts/master/try_job_rietveld.py | 
| @@ -117,6 +117,100 @@ class _ValidUserPoller(internet.TimerService): | 
| self._valid = True | 
|  | 
|  | 
| +class _RietveldPoller(base.PollingChangeSource): | 
| +  """Polls Rietveld for any pending patch sets to build. | 
| + | 
| +  Periodically polls Rietveld to see if any patch sets have been marked by | 
| +  users to be tried.  If so, send them to the trybots. | 
| +  """ | 
| + | 
| +  def __init__(self, get_pending_endpoint, interval, cachepath=None): | 
| +    """ | 
| +    Args: | 
| +      get_pending_endpoint: Rietveld URL string used to retrieve jobs to try. | 
| +      interval: Interval used to poll Rietveld, in seconds. | 
| +      cachepath: Path to file where state to persist between master restarts | 
| +                 will be stored. | 
| +    """ | 
| +    # Set interval time in base class. | 
| +    self.pollInterval = interval | 
| + | 
| +    # A string URL for the Rietveld endpoint to query for pending try jobs. | 
| +    self._get_pending_endpoint = get_pending_endpoint | 
| + | 
| +    # Cursor used to keep track of next patchset(s) to try.  If the cursor | 
| +    # is None, then try from the beginning. | 
| +    self._cursor = None | 
| + | 
| +    # Try job parent of this poller. | 
| +    self._try_job_rietveld = None | 
| + | 
| +    self._cachepath = cachepath | 
| + | 
| +    if self._cachepath: | 
| +      if os.path.exists(self._cachepath): | 
| +        with open(self._cachepath) as f: | 
| +          # Using JSON allows us to be flexible and extend the format | 
| +          # in a compatible way. | 
| +          data = json.load(f) | 
| +          self._cursor = data.get('cursor').encode('utf-8') | 
| + | 
| +  # base.PollingChangeSource overrides: | 
| +  def poll(self): | 
| +    """Polls Rietveld for any pending try jobs and submit them. | 
| + | 
| +    Returns: | 
| +      A deferred objects to be called once the operation completes. | 
| +    """ | 
| +    log.msg('RietveldPoller.poll') | 
| +    d = defer.succeed(None) | 
| +    d.addCallback(self._OpenUrl) | 
| +    d.addCallback(self._ParseJson) | 
| +    d.addErrback(log.err, 'error in RietveldPoller')  # eat errors | 
| +    return d | 
| + | 
| +  def setServiceParent(self, parent): | 
| +    base.PollingChangeSource.setServiceParent(self, parent) | 
| +    self._try_job_rietveld = parent | 
| + | 
| +  def _OpenUrl(self, _): | 
| +    """Downloads pending patch sets from Rietveld. | 
| + | 
| +    Returns: A string containing the pending patchsets from Rietveld | 
| +        encoded as JSON. | 
| +    """ | 
| +    endpoint = self._get_pending_endpoint | 
| +    if self._cursor: | 
| +      sep = '&' if '?' in endpoint else '?' | 
| +      endpoint = endpoint + '%scursor=%s' % (sep, self._cursor) | 
| + | 
| +    log.msg('RietveldPoller._OpenUrl: %s' % endpoint) | 
| +    return client.getPage(endpoint, agent='buildbot', timeout=2*60) | 
| + | 
| +  def _ParseJson(self, json_string): | 
| +    """Parses the JSON pending patch set information. | 
| + | 
| +    Args: | 
| +      json_string: A string containing the serialized JSON jobs. | 
| + | 
| +    Returns: A list of pending try jobs.  This is the list of all jobs returned | 
| +        by Rietveld, not simply the ones we tried this time. | 
| +    """ | 
| +    data = json.loads(json_string) | 
| +    d = self._try_job_rietveld.SubmitJobs(data['jobs']) | 
| +    def success_callback(value): | 
| +      self._cursor = str(data['cursor']) | 
| +      self._try_job_rietveld.processed_keys.clear() | 
| + | 
| +      if self._cachepath: | 
| +        with open(self._cachepath, 'w') as f: | 
| +          json.dump({'cursor': self._cursor}, f) | 
| + | 
| +      return value | 
| +    d.addCallback(success_callback) | 
| +    return d | 
| + | 
| + | 
| class _RietveldPollerWithCache(base.PollingChangeSource): | 
| """Polls Rietveld for any pending patch sets to build. | 
|  | 
|  |