cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / chrome / common / extensions / docs / server2 / content_provider.py
blobbaa779cb079031bd73e60bbb9b837995ec0b8bdf
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 mimetypes
7 import posixpath
8 import traceback
10 from compiled_file_system import SingleFile
11 from directory_zipper import DirectoryZipper
12 from docs_server_utils import ToUnicode
13 from file_system import FileNotFoundError
14 from future import All, Future
15 from path_canonicalizer import PathCanonicalizer
16 from path_util import AssertIsValid, IsDirectory, Join, ToDirectory
17 from special_paths import SITE_VERIFICATION_FILE
18 from third_party.markdown import markdown
19 from third_party.motemplate import Motemplate
22 _MIMETYPE_OVERRIDES = {
23 # SVG is not supported by mimetypes.guess_type on AppEngine.
24 '.svg': 'image/svg+xml',
28 class ContentAndType(object):
29 '''Return value from ContentProvider.GetContentAndType.
30 '''
32 def __init__(self, content, content_type, version):
33 self.content = content
34 self.content_type = content_type
35 self.version = version
38 class ContentProvider(object):
39 '''Returns file contents correctly typed for their content-types (in the HTTP
40 sense). Content-type is determined from Python's mimetype library which
41 guesses based on the file extension.
43 Typically the file contents will be either str (for binary content) or
44 unicode (for text content). However, HTML files *may* be returned as
45 Motemplate templates (if |supports_templates| is True on construction), in
46 which case the caller will presumably want to Render them.
48 Zip file are automatically created and returned for .zip file extensions if
49 |supports_zip| is True.
51 |default_extensions| is a list of file extensions which are queried when no
52 file extension is given to GetCanonicalPath/GetContentAndType. Typically
53 this will include .html.
54 '''
56 def __init__(self,
57 name,
58 compiled_fs_factory,
59 file_system,
60 object_store_creator,
61 default_extensions=(),
62 supports_templates=False,
63 supports_zip=False):
64 # Public.
65 self.name = name
66 self.file_system = file_system
67 # Private.
68 self._content_cache = compiled_fs_factory.Create(file_system,
69 self._CompileContent,
70 ContentProvider)
71 self._path_canonicalizer = PathCanonicalizer(file_system,
72 object_store_creator,
73 default_extensions)
74 self._default_extensions = default_extensions
75 self._supports_templates = supports_templates
76 if supports_zip:
77 self._directory_zipper = DirectoryZipper(compiled_fs_factory, file_system)
78 else:
79 self._directory_zipper = None
81 @SingleFile
82 def _CompileContent(self, path, text):
83 assert text is not None, path
84 try:
85 _, ext = posixpath.splitext(path)
86 mimetype = _MIMETYPE_OVERRIDES.get(ext, mimetypes.guess_type(path)[0])
87 if ext == '.md':
88 # See http://pythonhosted.org/Markdown/extensions
89 # for details on "extensions=".
90 content = markdown(ToUnicode(text),
91 extensions=('extra', 'headerid', 'sane_lists'))
92 mimetype = 'text/html'
93 if self._supports_templates:
94 content = Motemplate(content, name=path)
95 elif mimetype is None:
96 content = text
97 mimetype = 'text/plain'
98 elif mimetype == 'text/html':
99 content = ToUnicode(text)
100 if self._supports_templates:
101 content = Motemplate(content, name=path)
102 elif (mimetype.startswith('text/') or
103 mimetype in ('application/javascript', 'application/json')):
104 content = ToUnicode(text)
105 else:
106 content = text
107 return ContentAndType(content,
108 mimetype,
109 self.file_system.Stat(path).version)
110 except Exception as e:
111 logging.warn('In file %s: %s' % (path, e.message))
112 return ContentAndType('', mimetype, self.file_system.Stat(path).version)
114 def GetCanonicalPath(self, path):
115 '''Gets the canonical location of |path|. This class is tolerant of
116 spelling errors and missing files that are in other directories, and this
117 returns the correct/canonical path for those.
119 For example, the canonical path of "browseraction" is probably
120 "extensions/browserAction.html".
122 Note that the canonical path is relative to this content provider i.e.
123 given relative to |path|. It does not add the "serveFrom" prefix which
124 would have been pulled out in ContentProviders, callers must do that
125 themselves.
127 AssertIsValid(path)
128 base, ext = posixpath.splitext(path)
129 if self._directory_zipper and ext == '.zip':
130 # The canonical location of zip files is the canonical location of the
131 # directory to zip + '.zip'.
132 return self._path_canonicalizer.Canonicalize(base + '/').rstrip('/') + ext
133 return self._path_canonicalizer.Canonicalize(path)
135 def GetContentAndType(self, path):
136 '''Returns a Future to the ContentAndType of the file at |path|.
138 AssertIsValid(path)
139 base, ext = posixpath.splitext(path)
140 if self._directory_zipper and ext == '.zip':
141 return (self._directory_zipper.Zip(ToDirectory(base))
142 .Then(lambda zipped: ContentAndType(zipped,
143 'application/zip',
144 None)))
145 return self._FindFileForPath(path).Then(self._content_cache.GetFromFile)
147 def GetVersion(self, path):
148 '''Returns a Future to the version of the file at |path|.
150 AssertIsValid(path)
151 base, ext = posixpath.splitext(path)
152 if self._directory_zipper and ext == '.zip':
153 stat_future = self.file_system.StatAsync(ToDirectory(base))
154 else:
155 stat_future = self._FindFileForPath(path).Then(self.file_system.StatAsync)
156 return stat_future.Then(lambda stat: stat.version)
158 def _FindFileForPath(self, path):
159 '''Finds the real file backing |path|. This may require looking for the
160 correct file extension, or looking for an 'index' file if it's a directory.
161 Returns None if no path is found.
163 AssertIsValid(path)
164 _, ext = posixpath.splitext(path)
166 if ext:
167 # There was already an extension, trust that it's a path. Elsewhere
168 # up the stack this will be caught if it's not.
169 return Future(value=path)
171 def find_file_with_name(name):
172 '''Tries to find a file in the file system called |name| with one of the
173 default extensions of this content provider.
174 If none is found, returns None.
176 paths = [name + ext for ext in self._default_extensions]
177 def get_first_path_which_exists(existence):
178 for exists, path in zip(existence, paths):
179 if exists:
180 return path
181 return None
182 return (All(self.file_system.Exists(path) for path in paths)
183 .Then(get_first_path_which_exists))
185 def find_index_file():
186 '''Tries to find an index file in |path|, if |path| is a directory.
187 If not, or if there is no index file, returns None.
189 def get_index_if_directory_exists(directory_exists):
190 if not directory_exists:
191 return None
192 return find_file_with_name(Join(path, 'index'))
193 return (self.file_system.Exists(ToDirectory(path))
194 .Then(get_index_if_directory_exists))
196 # Try to find a file with the right name. If not, and it's a directory,
197 # look for an index file in that directory. If nothing at all is found,
198 # return the original |path| - its nonexistence will be caught up the stack.
199 return (find_file_with_name(path)
200 .Then(lambda found: found or find_index_file())
201 .Then(lambda found: found or path))
203 def Refresh(self):
204 futures = [self._path_canonicalizer.Refresh()]
205 for root, _, files in self.file_system.Walk(''):
206 for f in files:
207 futures.append(self.GetContentAndType(Join(root, f)))
208 # Also cache the extension-less version of the file if needed.
209 base, ext = posixpath.splitext(f)
210 if f != SITE_VERIFICATION_FILE and ext in self._default_extensions:
211 futures.append(self.GetContentAndType(Join(root, base)))
212 # TODO(kalman): Cache .zip files for each directory (if supported).
213 return All(futures, except_pass=Exception, except_pass_log=True)
215 def __repr__(self):
216 return 'ContentProvider of <%s>' % repr(self.file_system)