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