OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 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 posixpath | 5 import posixpath |
6 | 6 |
7 from compiled_file_system import SingleFile, Unicode | 7 from compiled_file_system import SingleFile, Unicode |
8 from extensions_paths import API_PATHS | 8 from extensions_paths import API_PATHS |
9 from features_bundle import HasParent, GetParentName | 9 from features_bundle import HasParent, GetParentName |
10 from file_system import FileNotFoundError | 10 from file_system import FileNotFoundError |
11 from future import All, Future | 11 from future import All, Future, Race |
12 from operator import itemgetter | 12 from operator import itemgetter |
| 13 from path_util import Join |
13 from platform_util import PlatformToExtensionType | 14 from platform_util import PlatformToExtensionType |
14 from schema_util import ProcessSchema | 15 from schema_util import ProcessSchema |
15 from third_party.json_schema_compiler.json_schema import DeleteNodes | 16 from third_party.json_schema_compiler.json_schema import DeleteNodes |
16 from third_party.json_schema_compiler.model import Namespace, UnixName | 17 from third_party.json_schema_compiler.model import Namespace, UnixName |
17 | 18 |
18 | 19 |
19 def GetNodeCategories(): | 20 def GetNodeCategories(): |
20 '''Returns a tuple of the possible categories a node may belong to. | 21 '''Returns a tuple of the possible categories a node may belong to. |
21 ''' | 22 ''' |
22 return ('types', 'functions', 'events', 'properties') | 23 return ('types', 'functions', 'events', 'properties') |
(...skipping 28 matching lines...) Expand all Loading... |
51 '''Tracks APIs and their Models. | 52 '''Tracks APIs and their Models. |
52 ''' | 53 ''' |
53 | 54 |
54 def __init__(self, | 55 def __init__(self, |
55 features_bundle, | 56 features_bundle, |
56 compiled_fs_factory, | 57 compiled_fs_factory, |
57 file_system, | 58 file_system, |
58 object_store_creator, | 59 object_store_creator, |
59 platform): | 60 platform): |
60 self._features_bundle = features_bundle | 61 self._features_bundle = features_bundle |
| 62 self._file_system = file_system |
61 self._platform = PlatformToExtensionType(platform) | 63 self._platform = PlatformToExtensionType(platform) |
62 self._model_cache = compiled_fs_factory.Create( | 64 self._model_cache = compiled_fs_factory.Create( |
63 file_system, self._CreateAPIModel, APIModels, category=self._platform) | 65 file_system, self._CreateAPIModel, APIModels, category=self._platform) |
64 self._object_store = object_store_creator.Create(APIModels) | 66 self._object_store = object_store_creator.Create(APIModels) |
65 | 67 |
66 @SingleFile | 68 @SingleFile |
67 @Unicode | 69 @Unicode |
68 def _CreateAPIModel(self, path, data): | 70 def _CreateAPIModel(self, path, data): |
69 def does_not_include_platform(node): | 71 def does_not_include_platform(node): |
70 return ('extension_types' in node and | 72 return ('extension_types' in node and |
71 node['extension_types'] != 'all' and | 73 node['extension_types'] != 'all' and |
72 self._platform not in node['extension_types']) | 74 self._platform not in node['extension_types']) |
73 | 75 |
74 schema = ProcessSchema(path, data)[0] | 76 schema = ProcessSchema(path, data)[0] |
75 if not schema: | 77 if not schema: |
76 raise ValueError('No schema for %s' % path) | 78 raise ValueError('No schema for %s' % path) |
77 return Namespace(DeleteNodes( | 79 return Namespace(DeleteNodes( |
78 schema, matcher=does_not_include_platform), schema['namespace']) | 80 schema, matcher=does_not_include_platform), schema['namespace']) |
79 | 81 |
80 def GetNames(self): | 82 def GetNames(self): |
81 # API names appear alongside some of their methods/events/etc in the | 83 # API names appear alongside some of their methods/events/etc in the |
82 # features file. APIs are those which either implicitly or explicitly have | 84 # features file. APIs are those which either implicitly or explicitly have |
83 # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are | 85 # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are |
84 # APIs; runtime.onConnectNative is not). | 86 # APIs; runtime.onConnectNative is not). |
85 api_features = self._features_bundle.GetAPIFeatures().Get() | 87 api_features = self._features_bundle.GetAPIFeatures().Get() |
86 return [name for name, feature in api_features.iteritems() | 88 return [name for name, feature in api_features.iteritems() |
87 if not HasParent(name, feature, api_features)] | 89 if not HasParent(name, feature, api_features)] |
88 | 90 |
89 def GetModel(self, api_name): | 91 def _GetPotentialPathsForModel(self, api_name): |
| 92 '''Returns the list of file system paths that the model for |api_name| |
| 93 might be located at. |
| 94 ''' |
90 # By default |api_name| is assumed to be given without a path or extension, | 95 # By default |api_name| is assumed to be given without a path or extension, |
91 # so combinations of known paths and extension types will be searched. | 96 # so combinations of known paths and extension types will be searched. |
92 api_extensions = ('.json', '.idl') | 97 api_extensions = ('.json', '.idl') |
93 api_paths = API_PATHS | 98 api_paths = API_PATHS |
94 | 99 |
95 # Callers sometimes include a file extension and/or prefix path with the | 100 # Callers sometimes include a file extension and/or prefix path with the |
96 # |api_name| argument. We believe them and narrow the search space | 101 # |api_name| argument. We believe them and narrow the search space |
97 # accordingly. | 102 # accordingly. |
98 name, ext = posixpath.splitext(api_name) | 103 name, ext = posixpath.splitext(api_name) |
99 if ext in api_extensions: | 104 if ext in api_extensions: |
100 api_extensions = (ext,) | 105 api_extensions = (ext,) |
101 api_name = name | 106 api_name = name |
102 for api_path in api_paths: | 107 for api_path in api_paths: |
103 if api_name.startswith(api_path): | 108 if api_name.startswith(api_path): |
104 api_name = api_name[len(api_path):] | 109 api_name = api_name[len(api_path):] |
105 api_paths = (api_path,) | 110 api_paths = (api_path,) |
106 break | 111 break |
107 | 112 |
108 # API names are given as declarativeContent and app.window but file names | 113 # API names are given as declarativeContent and app.window but file names |
109 # will be declarative_content and app_window. | 114 # will be declarative_content and app_window. |
110 file_name = UnixName(api_name).replace('.', '_') | 115 file_name = UnixName(api_name).replace('.', '_') |
111 # Devtools APIs are in API/devtools/ not API/, and have their | 116 # Devtools APIs are in API/devtools/ not API/, and have their |
112 # "devtools" names removed from the file names. | 117 # "devtools" names removed from the file names. |
113 basename = posixpath.basename(file_name) | 118 basename = posixpath.basename(file_name) |
114 if 'devtools_' in basename: | 119 if 'devtools_' in basename: |
115 file_name = posixpath.join( | 120 file_name = posixpath.join( |
116 'devtools', file_name.replace(basename, | 121 'devtools', file_name.replace(basename, |
117 basename.replace('devtools_' , ''))) | 122 basename.replace('devtools_' , ''))) |
118 | 123 |
119 futures = [self._model_cache.GetFromFile( | 124 return [Join(path, file_name + ext) for ext in api_extensions |
120 posixpath.join(path, '%s%s' % (file_name, ext))) | 125 for path in api_paths] |
121 for ext in api_extensions | 126 |
122 for path in api_paths] | 127 def GetModel(self, api_name): |
123 def resolve(): | 128 futures = [self._model_cache.GetFromFile(path) |
124 for future in futures: | 129 for path in self._GetPotentialPathsForModel(api_name)] |
125 try: | 130 return Race(futures, except_pass=(FileNotFoundError, ValueError)) |
126 return future.Get() | |
127 # Either the file wasn't found or there was no schema for the file | |
128 except (FileNotFoundError, ValueError): pass | |
129 # Propagate the first error if neither were found. | |
130 futures[0].Get() | |
131 return Future(callback=resolve) | |
132 | 131 |
133 def GetContentScriptAPIs(self): | 132 def GetContentScriptAPIs(self): |
134 '''Creates a dict of APIs and nodes supported by content scripts in | 133 '''Creates a dict of APIs and nodes supported by content scripts in |
135 this format: | 134 this format: |
136 | 135 |
137 { | 136 { |
138 'extension': '<ContentScriptAPI name='extension', | 137 'extension': '<ContentScriptAPI name='extension', |
139 restrictedTo=[{'node': 'onRequest'}]>', | 138 restrictedTo=[{'node': 'onRequest'}]>', |
140 ... | 139 ... |
141 } | 140 } |
(...skipping 20 matching lines...) Expand all Loading... |
162 content_script_apis[parent] = ContentScriptAPI(parent) | 161 content_script_apis[parent] = ContentScriptAPI(parent) |
163 if content_script_apis[parent].restrictedTo: | 162 if content_script_apis[parent].restrictedTo: |
164 content_script_apis[parent].restrictedTo.append(node) | 163 content_script_apis[parent].restrictedTo.append(node) |
165 else: | 164 else: |
166 content_script_apis[parent].restrictedTo = [node] | 165 content_script_apis[parent].restrictedTo = [node] |
167 | 166 |
168 self._object_store.Set('content_script_apis', content_script_apis) | 167 self._object_store.Set('content_script_apis', content_script_apis) |
169 return content_script_apis | 168 return content_script_apis |
170 return Future(callback=resolve) | 169 return Future(callback=resolve) |
171 | 170 |
172 def Cron(self): | |
173 futures = [self.GetModel(name) for name in self.GetNames()] | |
174 return All(futures, except_pass=(FileNotFoundError, ValueError)) | |
175 | |
176 def IterModels(self): | 171 def IterModels(self): |
177 future_models = [(name, self.GetModel(name)) for name in self.GetNames()] | 172 future_models = [(name, self.GetModel(name)) for name in self.GetNames()] |
178 for name, future_model in future_models: | 173 for name, future_model in future_models: |
179 try: | 174 try: |
180 model = future_model.Get() | 175 model = future_model.Get() |
181 except FileNotFoundError: | 176 except FileNotFoundError: |
182 continue | 177 continue |
183 if model: | 178 if model: |
184 yield name, model | 179 yield name, model |
| 180 |
| 181 def Cron(self): |
| 182 def map_cron_paths(op): |
| 183 results = [] |
| 184 for name in self.GetNames(): |
| 185 results += [op(path) for path in self._GetPotentialPathsForModel(name)] |
| 186 return results |
| 187 |
| 188 # Immediately stat everything so that files are guaranteed to be eventually |
| 189 # up to date. See http://crbug.com/398042 for background. |
| 190 # XXX see comment below, return a future for this, hopefully crons will |
| 191 # complete and we don't need to immediately resolve? |
| 192 All(map_cron_paths(self._file_system.StatAsync), |
| 193 except_pass=(FileNotFoundError, ValueError)).Get() |
| 194 |
| 195 # Update content in the future. |
| 196 # XXX don't do this for now, see how the crons and instances behave with |
| 197 # just getting the versions. |
| 198 return All(map_cron_paths(self.GetModel), |
| 199 except_pass=(FileNotFoundError, ValueError)) |
OLD | NEW |