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. 4 5import logging 6import os 7import traceback 8 9from chroot_file_system import ChrootFileSystem 10from content_provider import ContentProvider 11import environment 12from extensions_paths import CONTENT_PROVIDERS, LOCAL_DEBUG_DIR 13from future import Future 14from local_file_system import LocalFileSystem 15from third_party.json_schema_compiler.memoize import memoize 16 17 18_IGNORE_MISSING_CONTENT_PROVIDERS = [False] 19 20 21def 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 32 33 34class 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. 38 39 Returns ContentProvider instances based on how they're configured there. 40 ''' 41 42 def __init__(self, 43 object_store_creator, 44 compiled_fs_factory, 45 host_file_system, 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 53 self._cache = None 54 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) 60 conf_stat = None 61 try: 62 conf_stat = local_fs.Stat(CONTENT_PROVIDERS) 63 except: 64 pass 65 66 if conf_stat: 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) 70 71 if not self._cache: 72 self._cache = compiled_fs_factory.ForJson(host_file_system) 73 74 @memoize 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. 78 ''' 79 config = self._GetConfig().get(name) 80 if config is None: 81 logging.error('No content provider found with name "%s"' % name) 82 return None 83 return self._CreateContentProvider(name, config) 84 85 @memoize 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|. 91 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, 94 "foo/bar", "baz"). 95 96 Returns (None, '', |path|) if no ContentProvider serves from |path|. 97 ''' 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], 106 name_and_config[1]), 107 '/'.join(path_parts[:i]), 108 '/'.join(path_parts[i:])) 109 return None, '', path 110 111 def _GetConfig(self): 112 return self._cache.GetFromFile(CONTENT_PROVIDERS).Get() 113 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) 118 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) 123 return None 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) 130 return None 131 bucket = gcs_config['bucket'] 132 if not bucket.startswith('gs://'): 133 logging.error('%s: bucket %s should start with gs://' % (name, bucket)) 134 return None 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']) 139 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) 144 return None 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']) 149 150 else: 151 logging.error('%s: content provider type not supported' % name) 152 return None 153 154 return ContentProvider(name, 155 self._compiled_fs_factory, 156 file_system, 157 self._object_store_creator, 158 default_extensions=default_extensions, 159 supports_templates=supports_templates, 160 supports_zip=supports_zip) 161 162 def Cron(self): 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. 167 ''' 168 try: 169 return callback() 170 except: 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())) 174 return None 175 176 futures = [(name, safe(name, 177 'initializing', 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]) 182