Add a NavigationThrottle to the public content/ interface
[chromium-blink-merge.git] / chrome / common / extensions / docs / server2 / content_providers.py
blob65009f03ba1752fd0f163650ca9ca76c27543f59
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.
5 import logging
6 import os
7 import traceback
9 from chroot_file_system import ChrootFileSystem
10 from content_provider import ContentProvider
11 import environment
12 from extensions_paths import CONTENT_PROVIDERS, LOCAL_DEBUG_DIR
13 from future import All, Future
14 from local_file_system import LocalFileSystem
15 from third_party.json_schema_compiler.memoize import memoize
18 _IGNORE_MISSING_CONTENT_PROVIDERS = [False]
21 def IgnoreMissingContentProviders(fn):
22 '''Decorates |fn| to ignore missing content providers during its run.
23 '''
24 def run(*args, **optargs):
25 saved = _IGNORE_MISSING_CONTENT_PROVIDERS[0]
26 _IGNORE_MISSING_CONTENT_PROVIDERS[0] = True
27 try:
28 return fn(*args, **optargs)
29 finally:
30 _IGNORE_MISSING_CONTENT_PROVIDERS[0] = saved
31 return run
34 class ContentProviders(object):
35 '''Implements the content_providers.json configuration; see
36 chrome/common/extensions/docs/templates/json/content_providers.json for its
37 current state and a description of the format.
39 Returns ContentProvider instances based on how they're configured there.
40 '''
42 def __init__(self,
43 object_store_creator,
44 compiled_fs_factory,
45 host_file_system,
46 gcs_file_system_provider):
47 self._object_store_creator = object_store_creator
48 self._compiled_fs_factory = compiled_fs_factory
49 self._host_file_system = host_file_system
50 self._gcs_file_system_provider = gcs_file_system_provider
51 self._cache = None
53 # If running the devserver and there is a LOCAL_DEBUG_DIR, we
54 # will read the content_provider configuration from there instead
55 # of fetching it from Gitiles or patch.
56 if environment.IsDevServer() and os.path.exists(LOCAL_DEBUG_DIR):
57 local_fs = LocalFileSystem(LOCAL_DEBUG_DIR)
58 conf_stat = None
59 try:
60 conf_stat = local_fs.Stat(CONTENT_PROVIDERS)
61 except:
62 pass
64 if conf_stat:
65 logging.warn(("Using local debug folder (%s) for "
66 "content_provider.json configuration") % LOCAL_DEBUG_DIR)
67 self._cache = compiled_fs_factory.ForJson(local_fs)
69 if not self._cache:
70 self._cache = compiled_fs_factory.ForJson(host_file_system)
72 @memoize
73 def GetByName(self, name):
74 '''Gets the ContentProvider keyed by |name| in content_providers.json, or
75 None of there is no such content provider.
76 '''
77 config = self._GetConfig().get(name)
78 if config is None:
79 logging.error('No content provider found with name "%s"' % name)
80 return None
81 return self._CreateContentProvider(name, config)
83 @memoize
84 def GetByServeFrom(self, path):
85 '''Gets a (content_provider, serve_from, path_in_content_provider) tuple,
86 where content_provider is the ContentProvider with the longest "serveFrom"
87 property that is a subpath of |path|, serve_from is that property, and
88 path_in_content_provider is the remainder of |path|.
90 For example, if content provider A serves from "foo" and content provider B
91 serves from "foo/bar", GetByServeFrom("foo/bar/baz") will return (B,
92 "foo/bar", "baz").
94 Returns (None, '', |path|) if no ContentProvider serves from |path|.
95 '''
96 serve_from_to_config = dict(
97 (config['serveFrom'], (name, config))
98 for name, config in self._GetConfig().iteritems())
99 path_parts = path.split('/')
100 for i in xrange(len(path_parts), -1, -1):
101 name_and_config = serve_from_to_config.get('/'.join(path_parts[:i]))
102 if name_and_config is not None:
103 return (self._CreateContentProvider(name_and_config[0],
104 name_and_config[1]),
105 '/'.join(path_parts[:i]),
106 '/'.join(path_parts[i:]))
107 return None, '', path
109 def _GetConfig(self):
110 return self._cache.GetFromFile(CONTENT_PROVIDERS).Get()
112 def _CreateContentProvider(self, name, config):
113 default_extensions = config.get('defaultExtensions', ())
114 supports_templates = config.get('supportsTemplates', False)
115 supports_zip = config.get('supportsZip', False)
117 if 'chromium' in config:
118 chromium_config = config['chromium']
119 if 'dir' not in chromium_config:
120 logging.error('%s: "chromium" must have a "dir" property' % name)
121 return None
122 file_system = ChrootFileSystem(self._host_file_system,
124 chromium_config['dir'])
125 # TODO(rockot): Remove this in a future patch. It should not be needed once
126 # the new content_providers.json is committed.
127 elif 'gitiles' in config:
128 chromium_config = config['gitiles']
129 if 'dir' not in chromium_config:
130 logging.error('%s: "chromium" must have a "dir" property' % name)
131 return None
132 file_system = ChrootFileSystem(self._host_file_system,
133 chromium_config['dir'])
134 elif 'gcs' in config:
135 gcs_config = config['gcs']
136 if 'bucket' not in gcs_config:
137 logging.error('%s: "gcs" must have a "bucket" property' % name)
138 return None
139 bucket = gcs_config['bucket']
140 if not bucket.startswith('gs://'):
141 logging.error('%s: bucket %s should start with gs://' % (name, bucket))
142 return None
143 bucket = bucket[len('gs://'):]
144 file_system = self._gcs_file_system_provider.Create(bucket)
145 if 'dir' in gcs_config:
146 file_system = ChrootFileSystem(file_system, gcs_config['dir'])
148 else:
149 logging.error('%s: content provider type not supported' % name)
150 return None
152 return ContentProvider(name,
153 self._compiled_fs_factory,
154 file_system,
155 self._object_store_creator,
156 default_extensions=default_extensions,
157 supports_templates=supports_templates,
158 supports_zip=supports_zip)
160 def Refresh(self):
161 def safe(name, action, callback):
162 '''Safely runs |callback| for a ContentProvider called |name| by
163 swallowing exceptions and turning them into a None return value. It's
164 important to run all ContentProvider Refreshes even if some of them fail.
166 try:
167 return callback()
168 except:
169 if not _IGNORE_MISSING_CONTENT_PROVIDERS[0]:
170 logging.error('Error %s Refresh for ContentProvider "%s":\n%s' %
171 (action, name, traceback.format_exc()))
172 return None
174 def refresh_provider(path, config):
175 provider = self._CreateContentProvider(path, config)
176 future = safe(path,
177 'initializing',
178 self._CreateContentProvider(path, config).Refresh)
179 if future is None:
180 return Future(callback=lambda: True)
181 return Future(callback=lambda: safe(path, 'resolving', future.Get))
183 return All(refresh_provider(path, config)
184 for path, config in self._GetConfig().iteritems())