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 from datetime import datetime, timedelta
      6 from file_system import FileNotFoundError
      7 from future import Future
      8 from patcher import Patcher
      9 
     10 _VERSION_CACHE_MAXAGE = timedelta(seconds=5)
     11 
     12 ''' Append @version for keys to distinguish between different patchsets of
     13 an issue.
     14 '''
     15 def _MakeKey(path, version):
     16   return '%s@%s' % (path, version)
     17 
     18 def _ToObjectStoreValue(raw_value, version):
     19   return dict((_MakeKey(key, version), raw_value[key])
     20               for key in raw_value)
     21 
     22 def _FromObjectStoreValue(raw_value):
     23   return dict((key[0:key.rfind('@')], raw_value[key]) for key in raw_value)
     24 
     25 class _AsyncUncachedFuture(object):
     26   def __init__(self,
     27                version,
     28                paths,
     29                cached_value,
     30                missing_paths,
     31                fetch_delegate,
     32                object_store):
     33     self._version = version
     34     self._paths = paths
     35     self._cached_value = cached_value
     36     self._missing_paths = missing_paths
     37     self._fetch_delegate = fetch_delegate
     38     self._object_store = object_store
     39 
     40   def Get(self):
     41     uncached_value = self._fetch_delegate.Get()
     42     self._object_store.SetMulti(_ToObjectStoreValue(uncached_value,
     43                                                     self._version))
     44 
     45     for path in self._missing_paths:
     46       if uncached_value.get(path) is None:
     47         raise FileNotFoundError('File %s was not found in the patch.' % path)
     48       self._cached_value[path] = uncached_value[path]
     49 
     50     return self._cached_value
     51 
     52 class CachingRietveldPatcher(Patcher):
     53   ''' CachingRietveldPatcher implements a caching layer on top of |patcher|.
     54   In theory, it can be used with any class that implements Patcher. But this
     55   class assumes that applying to all patched files at once is more efficient
     56   than applying to individual files.
     57   '''
     58   def __init__(self,
     59                rietveld_patcher,
     60                object_store_creator,
     61                test_datetime=datetime):
     62     self._patcher = rietveld_patcher
     63     def create_object_store(category):
     64       return object_store_creator.Create(
     65           CachingRietveldPatcher,
     66           category='%s/%s' % (rietveld_patcher.GetIdentity(), category))
     67     self._version_object_store = create_object_store('version')
     68     self._list_object_store = create_object_store('list')
     69     self._file_object_store = create_object_store('file')
     70     self._datetime = test_datetime
     71 
     72   def GetVersion(self):
     73     key = 'version'
     74     value = self._version_object_store.Get(key).Get()
     75     if value is not None:
     76       version, time = value
     77       if self._datetime.now() - time < _VERSION_CACHE_MAXAGE:
     78         return version
     79 
     80     version = self._patcher.GetVersion()
     81     self._version_object_store.Set(key,
     82                                 (version, self._datetime.now()))
     83     return version
     84 
     85   def GetPatchedFiles(self, version=None):
     86     if version is None:
     87       version = self.GetVersion()
     88     patched_files = self._list_object_store.Get(version).Get()
     89     if patched_files is not None:
     90       return patched_files
     91 
     92     patched_files = self._patcher.GetPatchedFiles(version)
     93     self._list_object_store.Set(version, patched_files)
     94     return patched_files
     95 
     96   def Apply(self, paths, file_system, version=None):
     97     if version is None:
     98       version = self.GetVersion()
     99     added, deleted, modified = self.GetPatchedFiles(version)
    100     cached_value = _FromObjectStoreValue(self._file_object_store.
    101         GetMulti([_MakeKey(path, version) for path in paths]).Get())
    102     missing_paths = list(set(paths) - set(cached_value.keys()))
    103     if len(missing_paths) == 0:
    104       return Future(value=cached_value)
    105 
    106     return _AsyncUncachedFuture(version,
    107                                 paths,
    108                                 cached_value,
    109                                 missing_paths,
    110                                 self._patcher.Apply(set(added) | set(modified),
    111                                                     None,
    112                                                     version),
    113                                 self._file_object_store)
    114 
    115   def GetIdentity(self):
    116     return self._patcher.GetIdentity()
    117