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 |