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 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 github_file_system_provider
,
47 gcs_file_system_provider
):
48 self
._object
_store
_creator
= object_store_creator
49 self
._compiled
_fs
_factory
= compiled_fs_factory
50 self
._host
_file
_system
= host_file_system
51 self
._github
_file
_system
_provider
= github_file_system_provider
52 self
._gcs
_file
_system
_provider
= gcs_file_system_provider
55 # If running the devserver and there is a LOCAL_DEBUG_DIR, we
56 # will read the content_provider configuration from there instead
57 # of fetching it from SVN trunk or patch.
58 if environment
.IsDevServer() and os
.path
.exists(LOCAL_DEBUG_DIR
):
59 local_fs
= LocalFileSystem(LOCAL_DEBUG_DIR
)
62 conf_stat
= local_fs
.Stat(CONTENT_PROVIDERS
)
67 logging
.warn(("Using local debug folder (%s) for "
68 "content_provider.json configuration") % LOCAL_DEBUG_DIR
)
69 self
._cache
= compiled_fs_factory
.ForJson(local_fs
)
72 self
._cache
= compiled_fs_factory
.ForJson(host_file_system
)
75 def GetByName(self
, name
):
76 '''Gets the ContentProvider keyed by |name| in content_providers.json, or
77 None of there is no such content provider.
79 config
= self
._GetConfig
().get(name
)
81 logging
.error('No content provider found with name "%s"' % name
)
83 return self
._CreateContentProvider
(name
, config
)
86 def GetByServeFrom(self
, path
):
87 '''Gets a (content_provider, serve_from, path_in_content_provider) tuple,
88 where content_provider is the ContentProvider with the longest "serveFrom"
89 property that is a subpath of |path|, serve_from is that property, and
90 path_in_content_provider is the remainder of |path|.
92 For example, if content provider A serves from "foo" and content provider B
93 serves from "foo/bar", GetByServeFrom("foo/bar/baz") will return (B,
96 Returns (None, '', |path|) if no ContentProvider serves from |path|.
98 serve_from_to_config
= dict(
99 (config
['serveFrom'], (name
, config
))
100 for name
, config
in self
._GetConfig
().iteritems())
101 path_parts
= path
.split('/')
102 for i
in xrange(len(path_parts
), -1, -1):
103 name_and_config
= serve_from_to_config
.get('/'.join(path_parts
[:i
]))
104 if name_and_config
is not None:
105 return (self
._CreateContentProvider
(name_and_config
[0],
107 '/'.join(path_parts
[:i
]),
108 '/'.join(path_parts
[i
:]))
109 return None, '', path
111 def _GetConfig(self
):
112 return self
._cache
.GetFromFile(CONTENT_PROVIDERS
).Get()
114 def _CreateContentProvider(self
, name
, config
):
115 default_extensions
= config
.get('defaultExtensions', ())
116 supports_templates
= config
.get('supportsTemplates', False)
117 supports_zip
= config
.get('supportsZip', False)
119 if 'chromium' in config
:
120 chromium_config
= config
['chromium']
121 if 'dir' not in chromium_config
:
122 logging
.error('%s: "chromium" must have a "dir" property' % name
)
124 file_system
= ChrootFileSystem(self
._host
_file
_system
,
125 chromium_config
['dir'])
126 elif 'gcs' in config
:
127 gcs_config
= config
['gcs']
128 if 'bucket' not in gcs_config
:
129 logging
.error('%s: "gcs" must have a "bucket" property' % name
)
131 bucket
= gcs_config
['bucket']
132 if not bucket
.startswith('gs://'):
133 logging
.error('%s: bucket %s should start with gs://' % (name
, bucket
))
135 bucket
= bucket
[len('gs://'):]
136 file_system
= self
._gcs
_file
_system
_provider
.Create(bucket
)
137 if 'dir' in gcs_config
:
138 file_system
= ChrootFileSystem(file_system
, gcs_config
['dir'])
140 elif 'github' in config
:
141 github_config
= config
['github']
142 if 'owner' not in github_config
or 'repo' not in github_config
:
143 logging
.error('%s: "github" must provide an "owner" and "repo"' % name
)
145 file_system
= self
._github
_file
_system
_provider
.Create(
146 github_config
['owner'], github_config
['repo'])
147 if 'dir' in github_config
:
148 file_system
= ChrootFileSystem(file_system
, github_config
['dir'])
151 logging
.error('%s: content provider type not supported' % name
)
154 return ContentProvider(name
,
155 self
._compiled
_fs
_factory
,
157 self
._object
_store
_creator
,
158 default_extensions
=default_extensions
,
159 supports_templates
=supports_templates
,
160 supports_zip
=supports_zip
)
163 def safe(name
, action
, callback
):
164 '''Safely runs |callback| for a ContentProvider called |name| by
165 swallowing exceptions and turning them into a None return value. It's
166 important to run all ContentProvider Crons even if some of them fail.
171 if not _IGNORE_MISSING_CONTENT_PROVIDERS
[0]:
172 logging
.error('Error %s Cron for ContentProvider "%s":\n%s' %
173 (action
, name
, traceback
.format_exc()))
176 futures
= [(name
, safe(name
,
178 self
._CreateContentProvider
(name
, config
).Cron
))
179 for name
, config
in self
._GetConfig
().iteritems()]
180 return Future(callback
=
181 lambda: [safe(name
, 'resolving', f
.Get
) for name
, f
in futures
if f
])