comparison libervia/server/tasks/manager.py @ 1247:a6c7f07f1e4d

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.
author Goffi <goffi@goffi.org>
date Sun, 26 Apr 2020 22:07:18 +0200
parents 079e8eb6e327
children 80a92eb82b7f
comparison
equal deleted inserted replaced
1246:aaf28d45ae67 1247:a6c7f07f1e4d
15 15
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import os 18 import os
19 import os.path 19 import os.path
20 from pathlib import Path
20 import importlib.util 21 import importlib.util
22 from twisted.internet import defer
21 from sat.core.log import getLogger 23 from sat.core.log import getLogger
22 from sat.core import exceptions 24 from sat.core import exceptions
23 from sat.core.i18n import _ 25 from sat.core.i18n import _
24 from sat.tools import utils 26 from sat.tools import utils
25 from libervia.server.constants import Const as C 27 from libervia.server.constants import Const as C
28 from . import implicit
26 29
27 log = getLogger(__name__) 30 log = getLogger(__name__)
28 31
29 32
30 class TasksManager: 33 class TasksManager:
31 """Handle tasks of a Libervia site""" 34 """Handle tasks of a Libervia site"""
32 FILE_EXTS = {'py'}
33 35
34 def __init__(self, host, site_resource): 36 def __init__(self, host, site_resource):
35 """ 37 """
36 @param site_resource(LiberviaRootResource): root resource of the site to manage 38 @param site_resource(LiberviaRootResource): root resource of the site to manage
37 """ 39 """
38 self.host = host 40 self.host = host
39 self.resource = site_resource 41 self.resource = site_resource
40 self.tasks_dir = os.path.join(self.resource.site_path, C.TASKS_DIR) 42 self.tasks_dir = self.site_path / C.TASKS_DIR
41 self.tasks = {} 43 self.tasks = {}
42 self._build_path = None 44 self._build_path = None
43 self._current_task = None 45 self._current_task = None
44 46
45 @property 47 @property
46 def site_path(self): 48 def site_path(self):
47 return self.resource.site_path 49 return Path(self.resource.site_path)
48 50
49 @property 51 @property
50 def build_path(self): 52 def build_path(self):
51 """path where generated files will be build for this site""" 53 """path where generated files will be build for this site"""
52 if self._build_path is None: 54 if self._build_path is None:
75 else: 77 else:
76 if not value in allowed: 78 if not value in allowed:
77 raise ValueError(_("Unexpected value for {var}: {value!r}").format( 79 raise ValueError(_("Unexpected value for {var}: {value!r}").format(
78 var=var, value=value)) 80 var=var, value=value))
79 81
80 async def parseTasks(self): 82 async def parseTasksDir(self, dir_path):
81 if not os.path.isdir(self.tasks_dir): 83 log.debug(f"parsing tasks in {dir_path}")
82 log.debug(_("{name} has no task to launch.").format( 84 tasks_paths = sorted(dir_path.glob('task_*.py'))
83 name = self.resource.site_name or "default site")) 85 for task_path in tasks_paths:
84 return 86 if not task_path.is_file():
85 filenames = os.listdir(self.tasks_dir)
86 filenames.sort()
87 for filename in filenames:
88 filepath = os.path.join(self.tasks_dir, filename)
89 if not filename.startswith('task_') or not os.path.isfile(filepath):
90 continue 87 continue
91 task_name, ext = os.path.splitext(filename) 88 task_name = task_path.stem[5:].lower().strip()
92 task_name = task_name[5:].lower().strip()
93 if not task_name: 89 if not task_name:
94 continue
95 if ext[1:] not in self.FILE_EXTS:
96 continue 90 continue
97 if task_name in self.tasks: 91 if task_name in self.tasks:
98 raise exceptions.ConflictError( 92 raise exceptions.ConflictError(
99 "A task with the name [{name}] already exists".format( 93 "A task with the name [{name}] already exists".format(
100 name=task_name)) 94 name=task_name))
95 log.debug(f"task {task_name} found")
101 module_name = f"{self.site_name}.task.{task_name}" 96 module_name = f"{self.site_name}.task.{task_name}"
102 97
103 spec = importlib.util.spec_from_file_location(module_name, filepath) 98 spec = importlib.util.spec_from_file_location(module_name, task_path)
104 task_module = importlib.util.module_from_spec(spec) 99 task_module = importlib.util.module_from_spec(spec)
105 spec.loader.exec_module(task_module) 100 spec.loader.exec_module(task_module)
106 task = task_module.Task(self) 101 task = task_module.Task(self)
107 self.tasks[task_name] = task
108
109 102
110 # we launch prepare, which is a method used to prepare 103 # we launch prepare, which is a method used to prepare
111 # data at runtime (e.g. set WATCH_DIRS using config) 104 # data at runtime (e.g. set WATCH_DIRS using config)
112 try: 105 try:
113 prepare = task.prepare 106 prepare = task.prepare
114 except AttributeError: 107 except AttributeError:
115 pass 108 pass
116 else: 109 else:
117 await utils.asDeferred(prepare) 110 try:
111 await utils.asDeferred(prepare)
112 except exceptions.CancelError as e:
113 log.debug(f"Skipping {task_name} which cancelled itself: {e}")
114 continue
115
116 self.tasks[task_name] = task
118 self.validateData(task) 117 self.validateData(task)
119 if self.host.options['dev_mode']: 118 if self.host.options['dev_mode']:
120 dirs = task.WATCH_DIRS or [] 119 dirs = task.WATCH_DIRS or []
121 for dir_ in dirs: 120 for dir_ in dirs:
122 self.host.files_watcher.watchDir( 121 self.host.files_watcher.watchDir(
123 dir_, auto_add=True, recursive=True, 122 dir_, auto_add=True, recursive=True,
124 callback=self._autorunTask, task_name=task_name) 123 callback=self._autorunTask, task_name=task_name)
124
125 async def parseTasks(self):
126 # implicit tasks are always run
127 implicit_path = Path(implicit.__file__).parent
128 await self.parseTasksDir(implicit_path)
129 # now we check if there are tasks specific to this site
130 if not self.tasks_dir.is_dir():
131 log.debug(_("{name} has no task to launch.").format(
132 name = self.resource.site_name or "default site"))
133 return
134 else:
135 await self.parseTasksDir(self.tasks_dir)
125 136
126 def _autorunTask(self, host, filepath, flags, task_name): 137 def _autorunTask(self, host, filepath, flags, task_name):
127 """Called when an event is received from a watched directory""" 138 """Called when an event is received from a watched directory"""
128 if flags == ['create']: 139 if flags == ['create']:
129 return 140 return