view libervia/server/tasks/implicit/task_brython.py @ 1257:1ec41ac1e7cf

server: seperation between production build dir and dev build dir: LiberviaRootResource instances's `build_path` is the path where generated files are put and served by the HTTP server, while `dev_buid_path` is used for temporary files/libraries needed for the generation/compilation of files.
author Goffi <goffi@goffi.org>
date Sun, 03 May 2020 18:25:11 +0200
parents 08cd652dea14
children 3fc3f2cde6a1
line wrap: on
line source

#!/ur/bin/env python3

import shutil
import json
from pathlib import Path
from ast import literal_eval
from sat.core.i18n import _
from sat.core.log import getLogger
from sat.core import exceptions
from libervia.server.constants import Const as C
from libervia.server.tasks import task


log = getLogger(__name__)


class Task(task.Task):

    def prepare(self):
        if "brython" not in self.resource.browser_modules:
            raise exceptions.CancelError(f"No brython module found")

        brython_js = self.build_path / "brython.js"
        if not brython_js.is_file():
            installed_ver = None
        else:
            with brython_js.open() as f:
                for line in f:
                    if line.startswith('// implementation ['):
                        installed_ver = literal_eval(line[18:])[:3]
                        log.debug(
                            f"brython v{'.'.join(str(v) for v in installed_ver)} already "
                            f"installed")
                        break
                else:
                    log.warning(
                        f"brython file at {brython_js} doesn't has implementation "
                        f"version"
                    )
                    installed_ver = None

        try:
            import brython
        except ModuleNotFoundError as e:
            log.error('"brython" module is missing, can\'t use browser code for Brython')
            raise e
        ver = [int(v) for v in brython.implementation.split('.')[:3]]
        if ver != installed_ver:
            log.info(_("Installing Brython v{version}").format(
                version='.'.join(str(v) for v in ver)))
            data_path = Path(brython.__file__).parent / 'data'
            # shutil has blocking method, but the task is run before we start
            # the web server, so it's not a big deal
            shutil.copyfile(data_path / "brython.js", brython_js)
        else:
            log.debug("Brython is already installed")

        self.WATCH_DIRS = []

        for dyn_data in self.resource.browser_modules["brython"]:
            url_hash = dyn_data['url_hash']
            import_url = f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}"
            on_load_opts = {
                "debug": 1,
                "cache": True,
                "pythonpath": [f"/{C.BUILD_DIR}", import_url],
            }
            dyn_data.setdefault('scripts', set()).extend([
                    {"src": f"/{C.BUILD_DIR}/brython.js"},
                ])
            dyn_data.setdefault('template', {})['body_onload'] = (
                f"brython({json.dumps(on_load_opts)})")
            self.WATCH_DIRS.append(dyn_data['path'].resolve())

    def copyFiles(self, files_paths, dest):
        for p in files_paths:
            log.debug(f"copying {p}")
            if p.is_dir():
                shutil.copytree(p, dest)
            else:
                shutil.copy(p, dest)

    def start(self):
        dyn_path = self.build_path / C.BUILD_DIR_DYN
        for dyn_data in self.resource.browser_modules["brython"]:
            url_hash = dyn_data['url_hash']
            if url_hash is None:
                # root modules
                url_prefix = dyn_data.get('url_prefix')
                if url_prefix is None:
                    dest = self.build_path
                    init_dest_url = f"/{C.BUILD_DIR}/__init__.py"
                else:
                    dest = self.build_path / url_prefix
                    dest.mkdir(exist_ok = True)
                    init_dest_url = f"/{C.BUILD_DIR}/{url_prefix}/__init__.py"

                self.copyFiles(dyn_data['path'].glob('*py'), dest)

                init_file = dyn_data['path'] / '__init__.py'
                if init_file.is_file():
                    common_scripts = self.resource.dyn_data_common['scripts']
                    script = {
                        'type': 'text/python',
                        'src': init_dest_url
                    }
                    if script not in common_scripts:
                        common_scripts.append(script)
            else:
                page_dyn_path = dyn_path / url_hash
                if page_dyn_path.exists():
                    log.debug("cleaning existing path")
                    shutil.rmtree(page_dyn_path)

                page_dyn_path.mkdir(parents=True, exist_ok=True)
                log.debug("copying browser python files")
                self.copyFiles(dyn_data['path'].iterdir(), page_dyn_path)

                script = {
                    'type': 'text/python',
                    'src': f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}/__init__.py"
                }
                scripts = dyn_data['scripts']
                if script not in scripts:
                    scripts.append(script)