1# Copyright 2014 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 posixpath
7
8from custom_logger import CustomLogger
9from extensions_paths import EXAMPLES
10from file_system_util import CreateURLsFromPaths
11from future import Future
12from render_servlet import RenderServlet
13from special_paths import SITE_VERIFICATION_FILE
14from timer import Timer
15
16
17_SUPPORTED_TARGETS = {
18  'examples': (EXAMPLES, 'extensions/examples'),
19}
20
21
22_log = CustomLogger('render_refresher')
23
24
25class _SingletonRenderServletDelegate(RenderServlet.Delegate):
26  def __init__(self, server_instance):
27    self._server_instance = server_instance
28
29  def CreateServerInstance(self):
30    return self._server_instance
31
32
33def _RequestEachItem(title, items, request_callback):
34  '''Runs a task |request_callback| named |title| for each item in |items|.
35  |request_callback| must take an item and return a servlet response.
36  Returns true if every item was successfully run, false if any return a
37  non-200 response or raise an exception.
38  '''
39  _log.info('%s: starting', title)
40  success_count, failure_count = 0, 0
41  timer = Timer()
42  try:
43    for i, item in enumerate(items):
44      def error_message(detail):
45        return '%s: error rendering %s (%s of %s): %s' % (
46            title, item, i + 1, len(items), detail)
47      try:
48        response = request_callback(item)
49        if response.status == 200:
50          success_count += 1
51        else:
52          _log.error(error_message('response status %s' % response.status))
53          failure_count += 1
54      except Exception as e:
55        _log.error(error_message(traceback.format_exc()))
56        failure_count += 1
57        if IsDeadlineExceededError(e): raise
58  finally:
59    _log.info('%s: rendered %s of %s with %s failures in %s',
60        title, success_count, len(items), failure_count,
61        timer.Stop().FormatElapsed())
62  return success_count == len(items)
63
64
65class RenderRefresher(object):
66  '''Used to refresh any set of renderable resources. Currently only supports
67  assets related to extensions examples.'''
68  def __init__(self, server_instance, request):
69    self._server_instance = server_instance
70    self._request = request
71
72  def GetRefreshPaths(self):
73    return _SUPPORTED_TARGETS.keys()
74
75  def Refresh(self, path):
76    def render(path):
77      request = Request(path, self._request.host, self._request.headers)
78      delegate = _SingletonRenderServletDelegate(self._server_instance)
79      return RenderServlet(request, delegate).Get()
80
81    def request_files_in_dir(path, prefix='', strip_ext=None):
82      '''Requests every file found under |path| in this host file system, with
83      a request prefix of |prefix|. |strip_ext| is an optional list of file
84      extensions that should be stripped from paths before requesting.
85      '''
86      def maybe_strip_ext(name):
87        if name == SITE_VERIFICATION_FILE or not strip_ext:
88          return name
89        base, ext = posixpath.splitext(name)
90        return base if ext in strip_ext else name
91      files = [maybe_strip_ext(name)
92               for name, _ in CreateURLsFromPaths(master_fs, path, prefix)]
93      return _RequestEachItem(path, files, render)
94
95    # Only support examples for now.
96    if path not in _SUPPORTED_TARGETS:
97      return Future(callback=lambda: False)
98
99    dir = _SUPPORTED_TARGETS[path][0]
100    prefix = _SUPPORTED_TARGETS[path][1]
101    return request_files_in_dir(dir, prefix=prefix)
102