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.
9 from chroot_file_system
import ChrootFileSystem
10 from content_provider
import ContentProvider
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.
24 def run(*args
, **optargs
):
25 saved
= _IGNORE_MISSING_CONTENT_PROVIDERS
[0]
26 _IGNORE_MISSING_CONTENT_PROVIDERS
[0] = True
28 return fn(*args
, **optargs
)
30 _IGNORE_MISSING_CONTENT_PROVIDERS
[0] = saved
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.
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
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
)
60 conf_stat
= local_fs
.Stat(CONTENT_PROVIDERS
)
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
)
70 self
._cache
= compiled_fs_factory
.ForJson(host_file_system
)
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.
77 config
= self
._GetConfig
().get(name
)
79 logging
.error('No content provider found with name "%s"' % name
)
81 return self
._CreateContentProvider
(name
, config
)
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,
94 Returns (None, '', |path|) if no ContentProvider serves from |path|.
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],
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
)
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
)
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
)
139 bucket
= gcs_config
['bucket']
140 if not bucket
.startswith('gs://'):
141 logging
.error('%s: bucket %s should start with gs://' % (name
, bucket
))
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'])
149 logging
.error('%s: content provider type not supported' % name
)
152 return ContentProvider(name
,
153 self
._compiled
_fs
_factory
,
155 self
._object
_store
_creator
,
156 default_extensions
=default_extensions
,
157 supports_templates
=supports_templates
,
158 supports_zip
=supports_zip
)
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.
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()))
174 def refresh_provider(path
, config
):
175 provider
= self
._CreateContentProvider
(path
, config
)
178 self
._CreateContentProvider
(path
, config
).Refresh
)
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())