1"""Tornado handlers for frontend config storage.""" 2 3# Copyright (c) Jupyter Development Team. 4# Distributed under the terms of the Modified BSD License. 5from concurrent.futures import ThreadPoolExecutor 6import json 7from threading import Event 8 9from jupyter_server.base.handlers import APIHandler 10from jupyter_server.extension.handler import ExtensionHandlerMixin 11from tornado import gen, web 12from tornado.concurrent import run_on_executor 13 14from ..commands import build, clean, build_check, AppOptions, _ensure_options 15from ..coreconfig import CoreConfig 16 17 18class Builder(object): 19 building = False 20 executor = ThreadPoolExecutor(max_workers=5) 21 canceled = False 22 _canceling = False 23 _kill_event = None 24 _future = None 25 26 def __init__(self, core_mode, app_options=None): 27 app_options = _ensure_options(app_options) 28 self.log = app_options.logger 29 self.core_mode = core_mode 30 self.app_dir = app_options.app_dir 31 self.core_config = app_options.core_config 32 self.labextensions_path = app_options.labextensions_path 33 34 @gen.coroutine 35 def get_status(self): 36 if self.core_mode: 37 raise gen.Return(dict(status='stable', message='')) 38 if self.building: 39 raise gen.Return(dict(status='building', message='')) 40 41 try: 42 messages = yield self._run_build_check( 43 self.app_dir, self.log, self.core_config, self.labextensions_path) 44 status = 'needed' if messages else 'stable' 45 if messages: 46 self.log.warn('Build recommended') 47 [self.log.warn(m) for m in messages] 48 else: 49 self.log.info('Build is up to date') 50 except ValueError as e: 51 self.log.warn( 52 'Could not determine jupyterlab build status without nodejs' 53 ) 54 status = 'stable' 55 messages = [] 56 57 raise gen.Return(dict(status=status, message='\n'.join(messages))) 58 59 @gen.coroutine 60 def build(self): 61 if self._canceling: 62 raise ValueError('Cancel in progress') 63 if not self.building: 64 self.canceled = False 65 self._future = future = gen.Future() 66 self.building = True 67 self._kill_event = evt = Event() 68 try: 69 yield self._run_build( 70 self.app_dir, self.log, evt, self.core_config, self.labextensions_path) 71 future.set_result(True) 72 except Exception as e: 73 if str(e) == 'Aborted': 74 future.set_result(False) 75 else: 76 future.set_exception(e) 77 finally: 78 self.building = False 79 try: 80 yield self._future 81 except Exception as e: 82 raise e 83 84 @gen.coroutine 85 def cancel(self): 86 if not self.building: 87 raise ValueError('No current build') 88 self._canceling = True 89 yield self._future 90 self._canceling = False 91 self.canceled = True 92 93 @run_on_executor 94 def _run_build_check(self, app_dir, logger, core_config, labextensions_path): 95 return build_check(app_options=AppOptions( 96 app_dir=app_dir, logger=logger, core_config=core_config, labextensions_path=labextensions_path)) 97 98 @run_on_executor 99 def _run_build(self, app_dir, logger, kill_event, core_config, labextensions_path): 100 app_options = AppOptions( 101 app_dir=app_dir, logger=logger, kill_event=kill_event, 102 core_config=core_config, labextensions_path=labextensions_path) 103 try: 104 return build(app_options=app_options) 105 except Exception as e: 106 if self._kill_event.is_set(): 107 return 108 self.log.warn('Build failed, running a clean and rebuild') 109 clean(app_options=app_options) 110 return build(app_options=app_options) 111 112 113class BuildHandler(ExtensionHandlerMixin, APIHandler): 114 115 def initialize(self, builder=None, name=None): 116 super(BuildHandler, self).initialize(name=name) 117 self.builder = builder 118 119 @web.authenticated 120 @gen.coroutine 121 def get(self): 122 data = yield self.builder.get_status() 123 self.finish(json.dumps(data)) 124 125 @web.authenticated 126 @gen.coroutine 127 def delete(self): 128 self.log.warning('Canceling build') 129 try: 130 yield self.builder.cancel() 131 except Exception as e: 132 raise web.HTTPError(500, str(e)) 133 self.set_status(204) 134 135 @web.authenticated 136 @gen.coroutine 137 def post(self): 138 self.log.debug('Starting build') 139 try: 140 yield self.builder.build() 141 except Exception as e: 142 raise web.HTTPError(500, str(e)) 143 144 if self.builder.canceled: 145 raise web.HTTPError(400, 'Build canceled') 146 147 self.log.debug('Build succeeded') 148 self.set_status(200) 149 150 151# The path for lab build. 152build_path = r"/lab/api/build" 153