| 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 hashlib | 5 import hashlib |
| 6 import json | 6 import json |
| 7 import logging | 7 import logging |
| 8 import posixpath | 8 import posixpath |
| 9 import re | 9 import re |
| 10 import traceback | 10 import traceback |
| 11 | 11 |
| 12 from compiled_file_system import CompiledFileSystem | 12 from compiled_file_system import CompiledFileSystem |
| 13 from extensions_paths import EXAMPLES |
| 13 import third_party.json_schema_compiler.json_comment_eater as json_comment_eater | 14 import third_party.json_schema_compiler.json_comment_eater as json_comment_eater |
| 14 import third_party.json_schema_compiler.model as model | 15 import third_party.json_schema_compiler.model as model |
| 15 import url_constants | 16 import url_constants |
| 16 | 17 |
| 17 DEFAULT_ICON_PATH = 'images/sample-default-icon.png' | 18 |
| 19 _DEFAULT_ICON_PATH = 'images/sample-default-icon.png' |
| 20 |
| 18 | 21 |
| 19 class SamplesDataSource(object): | 22 class SamplesDataSource(object): |
| 20 '''Constructs a list of samples and their respective files and api calls. | 23 '''Constructs a list of samples and their respective files and api calls. |
| 21 ''' | 24 ''' |
| 22 class Factory(object): | 25 class Factory(object): |
| 23 '''A factory to create SamplesDataSource instances bound to individual | 26 '''A factory to create SamplesDataSource instances bound to individual |
| 24 Requests. | 27 Requests. |
| 25 ''' | 28 ''' |
| 26 def __init__(self, | 29 def __init__(self, |
| 27 host_file_system, | 30 host_file_system, |
| 28 app_samples_file_system, | 31 app_samples_file_system, |
| 29 compiled_fs_factory, | 32 compiled_fs_factory, |
| 30 ref_resolver_factory, | 33 ref_resolver_factory, |
| 31 extension_samples_path, | |
| 32 base_path): | 34 base_path): |
| 33 self._host_file_system = host_file_system | 35 self._host_file_system = host_file_system |
| 34 self._app_samples_file_system = app_samples_file_system | 36 self._app_samples_file_system = app_samples_file_system |
| 35 self._ref_resolver = ref_resolver_factory.Create() | 37 self._ref_resolver = ref_resolver_factory.Create() |
| 36 self._extension_samples_path = extension_samples_path | |
| 37 self._base_path = base_path | 38 self._base_path = base_path |
| 38 self._extensions_cache = compiled_fs_factory.Create( | 39 self._extensions_cache = compiled_fs_factory.Create( |
| 39 host_file_system, | 40 host_file_system, |
| 40 self._MakeSamplesList, | 41 self._MakeSamplesList, |
| 41 SamplesDataSource, | 42 SamplesDataSource, |
| 42 category='extensions') | 43 category='extensions') |
| 43 self._apps_cache = compiled_fs_factory.Create( | 44 self._apps_cache = compiled_fs_factory.Create( |
| 44 app_samples_file_system, | 45 app_samples_file_system, |
| 45 lambda *args: self._MakeSamplesList(*args, is_apps=True), | 46 lambda *args: self._MakeSamplesList(*args, is_apps=True), |
| 46 SamplesDataSource, | 47 SamplesDataSource, |
| 47 category='apps') | 48 category='apps') |
| 48 | 49 |
| 49 def Create(self, request): | 50 def Create(self, request): |
| 50 '''Returns a new SamplesDataSource bound to |request|. | 51 '''Returns a new SamplesDataSource bound to |request|. |
| 51 ''' | 52 ''' |
| 52 return SamplesDataSource(self._extensions_cache, | 53 return SamplesDataSource(self._extensions_cache, |
| 53 self._apps_cache, | 54 self._apps_cache, |
| 54 self._extension_samples_path, | |
| 55 self._base_path, | 55 self._base_path, |
| 56 request) | 56 request) |
| 57 | 57 |
| 58 def _GetAPIItems(self, js_file): | 58 def _GetAPIItems(self, js_file): |
| 59 chrome_pattern = r'chrome[\w.]+' | 59 chrome_pattern = r'chrome[\w.]+' |
| 60 # Add API calls that appear normally, like "chrome.runtime.connect". | 60 # Add API calls that appear normally, like "chrome.runtime.connect". |
| 61 calls = set(re.findall(chrome_pattern, js_file)) | 61 calls = set(re.findall(chrome_pattern, js_file)) |
| 62 # Add API calls that have been assigned into variables, like | 62 # Add API calls that have been assigned into variables, like |
| 63 # "var storageArea = chrome.storage.sync; storageArea.get", which should | 63 # "var storageArea = chrome.storage.sync; storageArea.get", which should |
| 64 # be expanded like "chrome.storage.sync.get". | 64 # be expanded like "chrome.storage.sync.get". |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 96 locales_json = [(locale_path, json.loads(contents)) | 96 locales_json = [(locale_path, json.loads(contents)) |
| 97 for locale_path, contents in | 97 for locale_path, contents in |
| 98 locales_files.iteritems()] | 98 locales_files.iteritems()] |
| 99 except ValueError as e: | 99 except ValueError as e: |
| 100 logging.error('Error parsing locales files for %s: %s' % (path, e)) | 100 logging.error('Error parsing locales files for %s: %s' % (path, e)) |
| 101 else: | 101 else: |
| 102 for path, json_ in locales_json: | 102 for path, json_ in locales_json: |
| 103 l10n_data['locales'][path[len(locales_path):].split('/')[0]] = json_ | 103 l10n_data['locales'][path[len(locales_path):].split('/')[0]] = json_ |
| 104 return l10n_data | 104 return l10n_data |
| 105 | 105 |
| 106 def _MakeSamplesList(self, base_dir, files, is_apps=False): | 106 def _MakeSamplesList(self, base_path, files, is_apps=False): |
| 107 # HACK(kalman): The code here (for legacy reasons) assumes that |files| is | |
| 108 # prefixed by |base_dir|, so make it true. | |
| 109 files = ['%s%s' % (base_dir, f) for f in files] | |
| 110 file_system = (self._app_samples_file_system if is_apps else | 107 file_system = (self._app_samples_file_system if is_apps else |
| 111 self._host_file_system) | 108 self._host_file_system) |
| 112 samples_list = [] | 109 samples_list = [] |
| 113 for filename in sorted(files): | 110 for filename in sorted(files): |
| 114 if filename.rsplit('/')[-1] != 'manifest.json': | 111 if filename.rsplit('/')[-1] != 'manifest.json': |
| 115 continue | 112 continue |
| 116 | 113 |
| 117 # This is a little hacky, but it makes a sample page. | 114 # This is a little hacky, but it makes a sample page. |
| 118 sample_path = filename.rsplit('/', 1)[-2] | 115 sample_path = filename.rsplit('/', 1)[-2] |
| 119 sample_files = [path for path in files | 116 sample_files = [path for path in files |
| 120 if path.startswith(sample_path + '/')] | 117 if path.startswith(sample_path + '/')] |
| 121 js_files = [path for path in sample_files if path.endswith('.js')] | 118 js_files = [path for path in sample_files if path.endswith('.js')] |
| 122 js_contents = file_system.Read(js_files).Get() | 119 js_contents = file_system.Read( |
| 120 [posixpath.join(base_path, js_file) for js_file in js_files]).Get() |
| 123 api_items = set() | 121 api_items = set() |
| 124 for js in js_contents.values(): | 122 for js in js_contents.itervalues(): |
| 125 api_items.update(self._GetAPIItems(js)) | 123 api_items.update(self._GetAPIItems(js)) |
| 126 | 124 |
| 127 api_calls = [] | 125 api_calls = [] |
| 128 for item in sorted(api_items): | 126 for item in sorted(api_items): |
| 129 if len(item.split('.')) < 3: | 127 if len(item.split('.')) < 3: |
| 130 continue | 128 continue |
| 131 if item.endswith('.removeListener') or item.endswith('.hasListener'): | 129 if item.endswith('.removeListener') or item.endswith('.hasListener'): |
| 132 continue | 130 continue |
| 133 if item.endswith('.addListener'): | 131 if item.endswith('.addListener'): |
| 134 item = item[:-len('.addListener')] | 132 item = item[:-len('.addListener')] |
| 135 if item.startswith('chrome.'): | 133 if item.startswith('chrome.'): |
| 136 item = item[len('chrome.'):] | 134 item = item[len('chrome.'):] |
| 137 ref_data = self._ref_resolver.GetLink(item) | 135 ref_data = self._ref_resolver.GetLink(item) |
| 138 # TODO(kalman): What about references like chrome.storage.sync.get? | 136 # TODO(kalman): What about references like chrome.storage.sync.get? |
| 139 # That should link to either chrome.storage.sync or | 137 # That should link to either chrome.storage.sync or |
| 140 # chrome.storage.StorageArea.get (or probably both). | 138 # chrome.storage.StorageArea.get (or probably both). |
| 141 # TODO(kalman): Filter out API-only references? This can happen when | 139 # TODO(kalman): Filter out API-only references? This can happen when |
| 142 # the API namespace is assigned to a variable, but it's very hard to | 140 # the API namespace is assigned to a variable, but it's very hard to |
| 143 # to disambiguate. | 141 # to disambiguate. |
| 144 if ref_data is None: | 142 if ref_data is None: |
| 145 continue | 143 continue |
| 146 api_calls.append({ | 144 api_calls.append({ |
| 147 'name': ref_data['text'], | 145 'name': ref_data['text'], |
| 148 'link': ref_data['href'] | 146 'link': ref_data['href'] |
| 149 }) | 147 }) |
| 150 | 148 |
| 151 sample_base_path = sample_path.split('/', 1)[1] | 149 sample_base_path = posixpath.join('examples', sample_path) |
| 152 if is_apps: | 150 if is_apps: |
| 153 url = url_constants.GITHUB_BASE + '/' + sample_base_path | 151 url = url_constants.GITHUB_BASE + '/' + sample_base_path |
| 154 icon_base = url_constants.RAW_GITHUB_BASE + '/' + sample_base_path | 152 icon_base = url_constants.RAW_GITHUB_BASE + '/' + sample_base_path |
| 155 download_url = url | 153 download_url = url |
| 156 else: | 154 else: |
| 157 url = sample_base_path | 155 url = sample_base_path |
| 158 icon_base = sample_base_path | 156 icon_base = sample_base_path |
| 159 download_url = sample_base_path + '.zip' | 157 download_url = sample_base_path + '.zip' |
| 160 | 158 |
| 161 manifest_data = self._GetDataFromManifest(sample_path, file_system) | 159 manifest_data = self._GetDataFromManifest( |
| 160 posixpath.join(base_path, sample_path), file_system) |
| 162 if manifest_data['icon'] is None: | 161 if manifest_data['icon'] is None: |
| 163 icon_path = posixpath.join( | 162 icon_path = posixpath.join( |
| 164 self._base_path, 'static', DEFAULT_ICON_PATH) | 163 self._base_path, 'static', _DEFAULT_ICON_PATH) |
| 165 else: | 164 else: |
| 166 icon_path = '%s/%s' % (icon_base, manifest_data['icon']) | 165 icon_path = '%s/%s' % (icon_base, manifest_data['icon']) |
| 167 manifest_data.update({ | 166 manifest_data.update({ |
| 168 'icon': icon_path, | 167 'icon': icon_path, |
| 169 'download_url': download_url, | 168 'download_url': download_url, |
| 170 'url': url, | 169 'url': url, |
| 171 'files': [f.replace(sample_path + '/', '') for f in sample_files], | 170 'files': [f.replace(sample_path + '/', '') for f in sample_files], |
| 172 'api_calls': api_calls | 171 'api_calls': api_calls |
| 173 }) | 172 }) |
| 174 samples_list.append(manifest_data) | 173 samples_list.append(manifest_data) |
| 175 | 174 |
| 176 return samples_list | 175 return samples_list |
| 177 | 176 |
| 178 def __init__(self, | 177 def __init__(self, |
| 179 extensions_cache, | 178 extensions_cache, |
| 180 apps_cache, | 179 apps_cache, |
| 181 extension_samples_path, | |
| 182 base_path, | 180 base_path, |
| 183 request): | 181 request): |
| 184 self._extensions_cache = extensions_cache | 182 self._extensions_cache = extensions_cache |
| 185 self._apps_cache = apps_cache | 183 self._apps_cache = apps_cache |
| 186 self._extension_samples_path = extension_samples_path | |
| 187 self._base_path = base_path | 184 self._base_path = base_path |
| 188 self._request = request | 185 self._request = request |
| 189 | 186 |
| 190 def _GetSampleId(self, sample_name): | 187 def _GetSampleId(self, sample_name): |
| 191 return sample_name.lower().replace(' ', '-') | 188 return sample_name.lower().replace(' ', '-') |
| 192 | 189 |
| 193 def _GetAcceptedLanguages(self): | 190 def _GetAcceptedLanguages(self): |
| 194 accept_language = self._request.headers.get('Accept-Language', None) | 191 accept_language = self._request.headers.get('Accept-Language', None) |
| 195 if accept_language is None: | 192 if accept_language is None: |
| 196 return [] | 193 return [] |
| 197 return [lang_with_q.split(';')[0].strip() | 194 return [lang_with_q.split(';')[0].strip() |
| 198 for lang_with_q in accept_language.split(',')] | 195 for lang_with_q in accept_language.split(',')] |
| 199 | 196 |
| 200 def FilterSamples(self, key, api_name): | 197 def FilterSamples(self, key, api_name): |
| 201 '''Fetches and filters the list of samples specified by |key|, returning | 198 '''Fetches and filters the list of samples specified by |key|, returning |
| 202 only the samples that use the API |api_name|. |key| is either 'apps' or | 199 only the samples that use the API |api_name|. |key| is either 'apps' or |
| 203 'extensions'. | 200 'extensions'. |
| 204 ''' | 201 ''' |
| 205 return [sample for sample in self.get(key) if any( | 202 return [sample for sample in self.get(key) if any( |
| 206 call['name'].startswith(api_name + '.') | 203 call['name'].startswith(api_name + '.') |
| 207 for call in sample['api_calls'])] | 204 for call in sample['api_calls'])] |
| 208 | 205 |
| 209 def _CreateSamplesDict(self, key): | 206 def _CreateSamplesDict(self, key): |
| 210 if key == 'apps': | 207 if key == 'apps': |
| 211 samples_list = self._apps_cache.GetFromFileListing('/').Get() | 208 samples_list = self._apps_cache.GetFromFileListing('/').Get() |
| 212 else: | 209 else: |
| 213 samples_list = self._extensions_cache.GetFromFileListing( | 210 samples_list = self._extensions_cache.GetFromFileListing(EXAMPLES).Get() |
| 214 self._extension_samples_path + '/').Get() | |
| 215 return_list = [] | 211 return_list = [] |
| 216 for dict_ in samples_list: | 212 for dict_ in samples_list: |
| 217 name = dict_['name'] | 213 name = dict_['name'] |
| 218 description = dict_['description'] | 214 description = dict_['description'] |
| 219 if description is None: | 215 if description is None: |
| 220 description = '' | 216 description = '' |
| 221 if name.startswith('__MSG_') or description.startswith('__MSG_'): | 217 if name.startswith('__MSG_') or description.startswith('__MSG_'): |
| 222 try: | 218 try: |
| 223 # Copy the sample dict so we don't change the dict in the cache. | 219 # Copy the sample dict so we don't change the dict in the cache. |
| 224 sample_data = dict_.copy() | 220 sample_data = dict_.copy() |
| (...skipping 16 matching lines...) Expand all Loading... |
| 241 else: | 237 else: |
| 242 dict_['id'] = self._GetSampleId(name) | 238 dict_['id'] = self._GetSampleId(name) |
| 243 return_list.append(dict_) | 239 return_list.append(dict_) |
| 244 return return_list | 240 return return_list |
| 245 | 241 |
| 246 def get(self, key): | 242 def get(self, key): |
| 247 return { | 243 return { |
| 248 'apps': lambda: self._CreateSamplesDict('apps'), | 244 'apps': lambda: self._CreateSamplesDict('apps'), |
| 249 'extensions': lambda: self._CreateSamplesDict('extensions') | 245 'extensions': lambda: self._CreateSamplesDict('extensions') |
| 250 }.get(key, lambda: {})() | 246 }.get(key, lambda: {})() |
| OLD | NEW |