| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import sys | 5 import sys |
| 6 | 6 |
| 7 import schema_util | |
| 8 from docs_server_utils import ToUnicode | 7 from docs_server_utils import ToUnicode |
| 9 from file_system import FileNotFoundError | 8 from file_system import FileNotFoundError |
| 10 from future import Future | 9 from future import Future |
| 11 from path_util import AssertIsDirectory, AssertIsFile, ToDirectory | 10 from path_util import AssertIsDirectory, AssertIsFile, ToDirectory |
| 12 from third_party.json_schema_compiler import json_parse | 11 from third_party.json_schema_compiler import json_parse |
| 13 from third_party.json_schema_compiler.memoize import memoize | 12 from third_party.json_schema_compiler.memoize import memoize |
| 14 from third_party.motemplate import Motemplate | 13 from third_party.motemplate import Motemplate |
| 15 | 14 |
| 16 | 15 |
| 16 _CACHEABLE_FUNCTIONS = set() |
| 17 _SINGLE_FILE_FUNCTIONS = set() | 17 _SINGLE_FILE_FUNCTIONS = set() |
| 18 | 18 |
| 19 | 19 |
| 20 def _GetUnboundFunction(fn): |
| 21 '''Functions bound to an object are separate from the unbound |
| 22 defintion. This causes issues when checking for cache membership, |
| 23 so always get the unbound function, if possible. |
| 24 ''' |
| 25 return getattr(fn, 'im_func', fn) |
| 26 |
| 27 |
| 28 def Cache(fn): |
| 29 '''A decorator which can be applied to the compilation function |
| 30 passed to CompiledFileSystem.Create, indicating that file/list data |
| 31 should be cached. |
| 32 |
| 33 This decorator should be listed first in any list of decorators, along |
| 34 with the SingleFile decorator below. |
| 35 ''' |
| 36 _CACHEABLE_FUNCTIONS.add(_GetUnboundFunction(fn)) |
| 37 return fn |
| 38 |
| 39 |
| 20 def SingleFile(fn): | 40 def SingleFile(fn): |
| 21 '''A decorator which can be optionally applied to the compilation function | 41 '''A decorator which can be optionally applied to the compilation function |
| 22 passed to CompiledFileSystem.Create, indicating that the function only | 42 passed to CompiledFileSystem.Create, indicating that the function only |
| 23 needs access to the file which is given in the function's callback. When | 43 needs access to the file which is given in the function's callback. When |
| 24 this is the case some optimisations can be done. | 44 this is the case some optimisations can be done. |
| 25 | 45 |
| 26 Note that this decorator must be listed first in any list of decorators to | 46 Note that this decorator must be listed first in any list of decorators to |
| 27 have any effect. | 47 have any effect. |
| 28 ''' | 48 ''' |
| 29 _SINGLE_FILE_FUNCTIONS.add(fn) | 49 _SINGLE_FILE_FUNCTIONS.add(_GetUnboundFunction(fn)) |
| 30 return fn | 50 return fn |
| 31 | 51 |
| 32 | 52 |
| 33 def Unicode(fn): | 53 def Unicode(fn): |
| 34 '''A decorator which can be optionally applied to the compilation function | 54 '''A decorator which can be optionally applied to the compilation function |
| 35 passed to CompiledFileSystem.Create, indicating that the function processes | 55 passed to CompiledFileSystem.Create, indicating that the function processes |
| 36 the file's data as Unicode text. | 56 the file's data as Unicode text. |
| 37 ''' | 57 ''' |
| 38 | 58 |
| 39 # The arguments passed to fn can be (self, path, data) or (path, data). In | 59 # The arguments passed to fn can be (self, path, data) or (path, data). In |
| 40 # either case the last argument is |data|, which should be converted to | 60 # either case the last argument is |data|, which should be converted to |
| 41 # Unicode. | 61 # Unicode. |
| 42 def convert_args(args): | 62 def convert_args(args): |
| 43 args = list(args) | 63 args = list(args) |
| 44 args[-1] = ToUnicode(args[-1]) | 64 args[-1] = ToUnicode(args[-1]) |
| 45 return args | 65 return args |
| 46 | 66 |
| 47 return lambda *args: fn(*convert_args(args)) | 67 return lambda *args: fn(*convert_args(args)) |
| 48 | 68 |
| 49 | 69 |
| 50 class _CacheEntry(object): | 70 class _CacheEntry(object): |
| 51 def __init__(self, cache_data, version): | 71 def __init__(self, cache_data, version): |
| 52 | 72 |
| 53 self._cache_data = cache_data | 73 self.cache_data = cache_data |
| 54 self.version = version | 74 self.version = version |
| 55 | 75 |
| 56 | 76 |
| 57 class CompiledFileSystem(object): | 77 class CompiledFileSystem(object): |
| 58 '''This class caches FileSystem data that has been processed. | 78 '''This class caches FileSystem data that has been processed. |
| 59 ''' | 79 ''' |
| 60 | 80 |
| 61 class Factory(object): | 81 class Factory(object): |
| 62 '''A class to build a CompiledFileSystem backed by |file_system|. | 82 '''A class to build a CompiledFileSystem backed by |file_system|. |
| 63 ''' | 83 ''' |
| (...skipping 29 matching lines...) Expand all Loading... |
| 93 compilation_function, | 113 compilation_function, |
| 94 create_object_store('file'), | 114 create_object_store('file'), |
| 95 create_object_store('list')) | 115 create_object_store('list')) |
| 96 | 116 |
| 97 @memoize | 117 @memoize |
| 98 def ForJson(self, file_system): | 118 def ForJson(self, file_system): |
| 99 '''A CompiledFileSystem specifically for parsing JSON configuration data. | 119 '''A CompiledFileSystem specifically for parsing JSON configuration data. |
| 100 These are memoized over file systems tied to different branches. | 120 These are memoized over file systems tied to different branches. |
| 101 ''' | 121 ''' |
| 102 return self.Create(file_system, | 122 return self.Create(file_system, |
| 103 SingleFile(lambda _, data: | 123 Cache(SingleFile(lambda _, data: |
| 104 json_parse.Parse(ToUnicode(data))), | 124 json_parse.Parse(ToUnicode(data)))), |
| 105 CompiledFileSystem, | 125 CompiledFileSystem, |
| 106 category='json') | 126 category='json') |
| 107 | 127 |
| 108 @memoize | 128 @memoize |
| 109 def ForTemplates(self, file_system): | 129 def ForTemplates(self, file_system): |
| 110 '''Creates a CompiledFileSystem for parsing templates. | 130 '''Creates a CompiledFileSystem for parsing templates. |
| 111 ''' | 131 ''' |
| 112 return self.Create( | 132 return self.Create( |
| 113 file_system, | 133 file_system, |
| 114 SingleFile(lambda path, text: Motemplate(ToUnicode(text), name=path)), | 134 SingleFile(lambda path, text: Motemplate(ToUnicode(text), name=path)), |
| (...skipping 12 matching lines...) Expand all Loading... |
| 127 def __init__(self, | 147 def __init__(self, |
| 128 file_system, | 148 file_system, |
| 129 compilation_function, | 149 compilation_function, |
| 130 file_object_store, | 150 file_object_store, |
| 131 list_object_store): | 151 list_object_store): |
| 132 self._file_system = file_system | 152 self._file_system = file_system |
| 133 self._compilation_function = compilation_function | 153 self._compilation_function = compilation_function |
| 134 self._file_object_store = file_object_store | 154 self._file_object_store = file_object_store |
| 135 self._list_object_store = list_object_store | 155 self._list_object_store = list_object_store |
| 136 | 156 |
| 157 def _Get(self, store, key): |
| 158 if _GetUnboundFunction(self._compilation_function) in _CACHEABLE_FUNCTIONS: |
| 159 return store.Get(key) |
| 160 return Future(value=None) |
| 161 |
| 162 def _Set(self, store, key, value): |
| 163 if _GetUnboundFunction(self._compilation_function) in _CACHEABLE_FUNCTIONS: |
| 164 store.Set(key, value) |
| 165 |
| 137 def _RecursiveList(self, path): | 166 def _RecursiveList(self, path): |
| 138 '''Returns a Future containing the recursive directory listing of |path| as | 167 '''Returns a Future containing the recursive directory listing of |path| as |
| 139 a flat list of paths. | 168 a flat list of paths. |
| 140 ''' | 169 ''' |
| 141 def split_dirs_from_files(paths): | 170 def split_dirs_from_files(paths): |
| 142 '''Returns a tuple (dirs, files) where |dirs| contains the directory | 171 '''Returns a tuple (dirs, files) where |dirs| contains the directory |
| 143 names in |paths| and |files| contains the files. | 172 names in |paths| and |files| contains the files. |
| 144 ''' | 173 ''' |
| 145 result = [], [] | 174 result = [], [] |
| 146 for path in paths: | 175 for path in paths: |
| (...skipping 25 matching lines...) Expand all Loading... |
| 172 # |path| so that |file_system| can find the files. | 201 # |path| so that |file_system| can find the files. |
| 173 dirs += add_prefix(dir_name, new_dirs) | 202 dirs += add_prefix(dir_name, new_dirs) |
| 174 # |files| are not for reading, they are for returning to the caller. | 203 # |files| are not for reading, they are for returning to the caller. |
| 175 # This entire function set (i.e. GetFromFileListing) is defined to | 204 # This entire function set (i.e. GetFromFileListing) is defined to |
| 176 # not include the fetched-path in the result, however, |dir_name| | 205 # not include the fetched-path in the result, however, |dir_name| |
| 177 # will be prefixed with |path|. Strip it. | 206 # will be prefixed with |path|. Strip it. |
| 178 assert dir_name.startswith(path) | 207 assert dir_name.startswith(path) |
| 179 files += add_prefix(dir_name[len(path):], new_files) | 208 files += add_prefix(dir_name[len(path):], new_files) |
| 180 if dirs: | 209 if dirs: |
| 181 files += self._file_system.Read(dirs).Then( | 210 files += self._file_system.Read(dirs).Then( |
| 182 lambda results: get_from_future_listing(results)).Get() | 211 get_from_future_listing).Get() |
| 183 return files | 212 return files |
| 184 | 213 |
| 185 return self._file_system.Read(add_prefix(path, first_layer_dirs)).Then( | 214 return self._file_system.Read(add_prefix(path, first_layer_dirs)).Then( |
| 186 lambda results: first_layer_files + get_from_future_listing(results)) | 215 lambda results: first_layer_files + get_from_future_listing(results)) |
| 187 | 216 |
| 188 def GetFromFile(self, path, skip_not_found=False): | 217 def GetFromFile(self, path, skip_not_found=False): |
| 189 '''Calls |compilation_function| on the contents of the file at |path|. | 218 '''Calls |compilation_function| on the contents of the file at |path|. |
| 190 If |skip_not_found| is True, then None is passed to |compilation_function|. | 219 If |skip_not_found| is True, then None is passed to |compilation_function|. |
| 191 ''' | 220 ''' |
| 192 AssertIsFile(path) | 221 AssertIsFile(path) |
| 193 | 222 |
| 194 try: | 223 try: |
| 195 version = self._file_system.Stat(path).version | 224 version = self._file_system.Stat(path).version |
| 196 except FileNotFoundError: | 225 except FileNotFoundError: |
| 197 if skip_not_found: | 226 if skip_not_found: |
| 198 version = None | 227 version = None |
| 199 else: | 228 else: |
| 200 return Future(exc_info=sys.exc_info()) | 229 return Future(exc_info=sys.exc_info()) |
| 201 | 230 |
| 202 cache_entry = self._file_object_store.Get(path).Get() | 231 cache_entry = self._Get(self._file_object_store, path).Get() |
| 203 if (cache_entry is not None) and (version == cache_entry.version): | 232 if (cache_entry is not None) and (version == cache_entry.version): |
| 204 return Future(value=cache_entry._cache_data) | 233 return Future(value=cache_entry.cache_data) |
| 205 | 234 |
| 206 def compile_(files): | 235 def compile_(files): |
| 207 cache_data = self._compilation_function(path, files) | 236 cache_data = self._compilation_function(path, files) |
| 208 self._file_object_store.Set(path, _CacheEntry(cache_data, version)) | 237 self._Set(self._file_object_store, path, _CacheEntry(cache_data, version)) |
| 209 return cache_data | 238 return cache_data |
| 210 | 239 |
| 211 return self._file_system.ReadSingle( | 240 return self._file_system.ReadSingle( |
| 212 path, skip_not_found=skip_not_found).Then(compile_) | 241 path, skip_not_found=skip_not_found).Then(compile_) |
| 213 | 242 |
| 214 def GetFromFileListing(self, path): | 243 def GetFromFileListing(self, path): |
| 215 '''Calls |compilation_function| on the listing of the files at |path|. | 244 '''Calls |compilation_function| on the listing of the files at |path|. |
| 216 Assumes that the path given is to a directory. | 245 Assumes that the path given is to a directory. |
| 217 ''' | 246 ''' |
| 218 AssertIsDirectory(path) | 247 AssertIsDirectory(path) |
| 219 | 248 |
| 220 try: | 249 try: |
| 221 version = self._file_system.Stat(path).version | 250 version = self._file_system.Stat(path).version |
| 222 except FileNotFoundError: | 251 except FileNotFoundError: |
| 223 return Future(exc_info=sys.exc_info()) | 252 return Future(exc_info=sys.exc_info()) |
| 224 | 253 |
| 225 cache_entry = self._list_object_store.Get(path).Get() | 254 cache_entry = self._Get(self._list_object_store, path).Get() |
| 226 if (cache_entry is not None) and (version == cache_entry.version): | 255 if (cache_entry is not None) and (version == cache_entry.version): |
| 227 return Future(value=cache_entry._cache_data) | 256 return Future(value=cache_entry.cache_data) |
| 228 | 257 |
| 229 def next(files): | 258 def compile_(files): |
| 230 cache_data = self._compilation_function(path, files) | 259 cache_data = self._compilation_function(path, files) |
| 231 self._list_object_store.Set(path, _CacheEntry(cache_data, version)) | 260 self._Set(self._list_object_store, path, _CacheEntry(cache_data, version)) |
| 232 return cache_data | 261 return cache_data |
| 233 return self._RecursiveList(path).Then(next) | 262 return self._RecursiveList(path).Then(compile_) |
| 234 | 263 |
| 235 # _GetFileVersionFromCache and _GetFileListingVersionFromCache are exposed | 264 # _GetFileVersionFromCache and _GetFileListingVersionFromCache are exposed |
| 236 # *only* so that ChainedCompiledFileSystem can optimise its caches. *Do not* | 265 # *only* so that ChainedCompiledFileSystem can optimise its caches. *Do not* |
| 237 # use these methods otherwise, they don't do what you want. Use | 266 # use these methods otherwise, they don't do what you want. Use |
| 238 # FileSystem.Stat on the FileSystem that this CompiledFileSystem uses. | 267 # FileSystem.Stat on the FileSystem that this CompiledFileSystem uses. |
| 239 | 268 |
| 240 def _GetFileVersionFromCache(self, path): | 269 def _GetFileVersionFromCache(self, path): |
| 241 cache_entry = self._file_object_store.Get(path).Get() | 270 cache_entry = self._Get(self._file_object_store, path).Get() |
| 242 if cache_entry is not None: | 271 if cache_entry is not None: |
| 243 return Future(value=cache_entry.version) | 272 return Future(value=cache_entry.version) |
| 244 stat_future = self._file_system.StatAsync(path) | 273 stat_future = self._file_system.StatAsync(path) |
| 245 return Future(callback=lambda: stat_future.Get().version) | 274 return Future(callback=lambda: stat_future.Get().version) |
| 246 | 275 |
| 247 def _GetFileListingVersionFromCache(self, path): | 276 def _GetFileListingVersionFromCache(self, path): |
| 248 path = ToDirectory(path) | 277 path = ToDirectory(path) |
| 249 cache_entry = self._list_object_store.Get(path).Get() | 278 cache_entry = self._Get(self._list_object_store, path).Get() |
| 250 if cache_entry is not None: | 279 if cache_entry is not None: |
| 251 return Future(value=cache_entry.version) | 280 return Future(value=cache_entry.version) |
| 252 stat_future = self._file_system.StatAsync(path) | 281 stat_future = self._file_system.StatAsync(path) |
| 253 return Future(callback=lambda: stat_future.Get().version) | 282 return Future(callback=lambda: stat_future.Get().version) |
| 254 | 283 |
| 255 def GetIdentity(self): | 284 def GetIdentity(self): |
| 256 return self._file_system.GetIdentity() | 285 return self._file_system.GetIdentity() |
| OLD | NEW |