File size: 5,157 Bytes
d1ceb73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""Tornado handlers for frontend config storage."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import json
from concurrent.futures import ThreadPoolExecutor
from threading import Event

from jupyter_server.base.handlers import APIHandler
from jupyter_server.extension.handler import ExtensionHandlerMixin
from tornado import gen, web
from tornado.concurrent import run_on_executor

from jupyterlab.commands import AppOptions, _ensure_options, build, build_check, clean


class Builder:
    building = False
    executor = ThreadPoolExecutor(max_workers=5)
    canceled = False
    _canceling = False
    _kill_event = None
    _future = None

    def __init__(self, core_mode, app_options=None):
        app_options = _ensure_options(app_options)
        self.log = app_options.logger
        self.core_mode = core_mode
        self.app_dir = app_options.app_dir
        self.core_config = app_options.core_config
        self.labextensions_path = app_options.labextensions_path

    @gen.coroutine
    def get_status(self):
        if self.core_mode:
            raise gen.Return({"status": "stable", "message": ""})
        if self.building:
            raise gen.Return({"status": "building", "message": ""})

        try:
            messages = yield self._run_build_check(
                self.app_dir, self.log, self.core_config, self.labextensions_path
            )
            status = "needed" if messages else "stable"
            if messages:
                self.log.warning("Build recommended")
                [self.log.warning(m) for m in messages]
            else:
                self.log.info("Build is up to date")
        except ValueError:
            self.log.warning("Could not determine jupyterlab build status without nodejs")
            status = "stable"
            messages = []

        raise gen.Return({"status": status, "message": "\n".join(messages)})

    @gen.coroutine
    def build(self):
        if self._canceling:
            msg = "Cancel in progress"
            raise ValueError(msg)
        if not self.building:
            self.canceled = False
            self._future = future = gen.Future()
            self.building = True
            self._kill_event = evt = Event()
            try:
                yield self._run_build(
                    self.app_dir, self.log, evt, self.core_config, self.labextensions_path
                )
                future.set_result(True)
            except Exception as e:
                if str(e) == "Aborted":
                    future.set_result(False)
                else:
                    future.set_exception(e)
            finally:
                self.building = False
        try:
            yield self._future
        except Exception as e:
            raise e

    @gen.coroutine
    def cancel(self):
        if not self.building:
            msg = "No current build"
            raise ValueError(msg)
        self._canceling = True
        yield self._future
        self._canceling = False
        self.canceled = True

    @run_on_executor
    def _run_build_check(self, app_dir, logger, core_config, labextensions_path):
        return build_check(
            app_options=AppOptions(
                app_dir=app_dir,
                logger=logger,
                core_config=core_config,
                labextensions_path=labextensions_path,
            )
        )

    @run_on_executor
    def _run_build(self, app_dir, logger, kill_event, core_config, labextensions_path):
        app_options = AppOptions(
            app_dir=app_dir,
            logger=logger,
            kill_event=kill_event,
            core_config=core_config,
            labextensions_path=labextensions_path,
        )
        try:
            return build(app_options=app_options)
        except Exception:
            if self._kill_event.is_set():
                return
            self.log.warning("Build failed, running a clean and rebuild")
            clean(app_options=app_options)
            return build(app_options=app_options)


class BuildHandler(ExtensionHandlerMixin, APIHandler):
    def initialize(self, builder=None, name=None):
        super().initialize(name=name)
        self.builder = builder

    @web.authenticated
    @gen.coroutine
    def get(self):
        data = yield self.builder.get_status()
        self.finish(json.dumps(data))

    @web.authenticated
    @gen.coroutine
    def delete(self):
        self.log.warning("Canceling build")
        try:
            yield self.builder.cancel()
        except Exception as e:
            raise web.HTTPError(500, str(e)) from None
        self.set_status(204)

    @web.authenticated
    @gen.coroutine
    def post(self):
        self.log.debug("Starting build")
        try:
            yield self.builder.build()
        except Exception as e:
            raise web.HTTPError(500, str(e)) from None

        if self.builder.canceled:
            raise web.HTTPError(400, "Build canceled")

        self.log.debug("Build succeeded")
        self.set_status(200)


# The path for lab build.
build_path = r"/lab/api/build"