# HG changeset patch # User Goffi # Date 1587931638 -7200 # Node ID a6c7f07f1e4d0fda6eab3933aece6fcbc90de54e # Parent aaf28d45ae679f903da4cdf060e2dba36be914ae tasks: implicit tasks + Brython task: - implicit tasks are tasks launched for every site. - a first one is made for Brython: it will install Brython and copy suitable files and set template data if Brython code is written in page `_browser` subdirectory. diff -r aaf28d45ae67 -r a6c7f07f1e4d libervia/server/tasks/implicit/__init__.py diff -r aaf28d45ae67 -r a6c7f07f1e4d libervia/server/tasks/implicit/task_brython.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/server/tasks/implicit/task_brython.py Sun Apr 26 22:07:18 2020 +0200 @@ -0,0 +1,95 @@ +#!/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 sat.tools.common.template import safe +from libervia.server.constants import Const as C +from libervia.server.tasks import task + + +log = getLogger(__name__) + + +class Task(task.Task): + DOC_DIRS_DEFAULT = ('doc', 'docs') + + 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 + + import brython + 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, + "pythonpath": [import_url], + } + dyn_data['template'] = { + "scripts": [{"src": f"/{C.BUILD_DIR}/brython.js"}], + "body_onload": f"brython({json.dumps(on_load_opts)})", + } + self.WATCH_DIRS.append(dyn_data['path'].resolve()) + + 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'] + 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") + for p in dyn_data['path'].iterdir(): + log.debug(f"copying {p}") + if p.is_dir(): + shutil.copytree(p, page_dyn_path) + else: + shutil.copy(p, page_dyn_path) + + script = { + 'type': 'text/python', + 'src': f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}/__init__.py" + } + scripts = dyn_data['template']['scripts'] + if script not in scripts: + scripts.append(script) diff -r aaf28d45ae67 -r a6c7f07f1e4d libervia/server/tasks/manager.py --- a/libervia/server/tasks/manager.py Sun Apr 26 22:01:13 2020 +0200 +++ b/libervia/server/tasks/manager.py Sun Apr 26 22:07:18 2020 +0200 @@ -17,19 +17,21 @@ # along with this program. If not, see . import os import os.path +from pathlib import Path import importlib.util +from twisted.internet import defer from sat.core.log import getLogger from sat.core import exceptions from sat.core.i18n import _ from sat.tools import utils from libervia.server.constants import Const as C +from . import implicit log = getLogger(__name__) class TasksManager: """Handle tasks of a Libervia site""" - FILE_EXTS = {'py'} def __init__(self, host, site_resource): """ @@ -37,14 +39,14 @@ """ self.host = host self.resource = site_resource - self.tasks_dir = os.path.join(self.resource.site_path, C.TASKS_DIR) + self.tasks_dir = self.site_path / C.TASKS_DIR self.tasks = {} self._build_path = None self._current_task = None @property def site_path(self): - return self.resource.site_path + return Path(self.resource.site_path) @property def build_path(self): @@ -77,35 +79,26 @@ raise ValueError(_("Unexpected value for {var}: {value!r}").format( var=var, value=value)) - async def parseTasks(self): - if not os.path.isdir(self.tasks_dir): - log.debug(_("{name} has no task to launch.").format( - name = self.resource.site_name or "default site")) - return - filenames = os.listdir(self.tasks_dir) - filenames.sort() - for filename in filenames: - filepath = os.path.join(self.tasks_dir, filename) - if not filename.startswith('task_') or not os.path.isfile(filepath): + async def parseTasksDir(self, dir_path): + log.debug(f"parsing tasks in {dir_path}") + tasks_paths = sorted(dir_path.glob('task_*.py')) + for task_path in tasks_paths: + if not task_path.is_file(): continue - task_name, ext = os.path.splitext(filename) - task_name = task_name[5:].lower().strip() + task_name = task_path.stem[5:].lower().strip() if not task_name: continue - if ext[1:] not in self.FILE_EXTS: - continue if task_name in self.tasks: raise exceptions.ConflictError( "A task with the name [{name}] already exists".format( name=task_name)) + log.debug(f"task {task_name} found") module_name = f"{self.site_name}.task.{task_name}" - spec = importlib.util.spec_from_file_location(module_name, filepath) + spec = importlib.util.spec_from_file_location(module_name, task_path) task_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(task_module) task = task_module.Task(self) - self.tasks[task_name] = task - # we launch prepare, which is a method used to prepare # data at runtime (e.g. set WATCH_DIRS using config) @@ -114,7 +107,13 @@ except AttributeError: pass else: - await utils.asDeferred(prepare) + try: + await utils.asDeferred(prepare) + except exceptions.CancelError as e: + log.debug(f"Skipping {task_name} which cancelled itself: {e}") + continue + + self.tasks[task_name] = task self.validateData(task) if self.host.options['dev_mode']: dirs = task.WATCH_DIRS or [] @@ -123,6 +122,18 @@ dir_, auto_add=True, recursive=True, callback=self._autorunTask, task_name=task_name) + async def parseTasks(self): + # implicit tasks are always run + implicit_path = Path(implicit.__file__).parent + await self.parseTasksDir(implicit_path) + # now we check if there are tasks specific to this site + if not self.tasks_dir.is_dir(): + log.debug(_("{name} has no task to launch.").format( + name = self.resource.site_name or "default site")) + return + else: + await self.parseTasksDir(self.tasks_dir) + def _autorunTask(self, host, filepath, flags, task_name): """Called when an event is received from a watched directory""" if flags == ['create']: