big source code cleanup
This commit is contained in:
@@ -121,11 +121,7 @@ class DaemonInstance:
|
|||||||
pending = self._get_pending_tasks()
|
pending = self._get_pending_tasks()
|
||||||
if pending:
|
if pending:
|
||||||
if self._timeout:
|
if self._timeout:
|
||||||
try:
|
future = asyncio.gather(*pending)
|
||||||
future = asyncio.shield(asyncio.gather(*pending))
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self._log.info(
|
self._log.info(
|
||||||
f"wait {self._timeout} seconds for {len(pending)} "
|
f"wait {self._timeout} seconds for {len(pending)} "
|
||||||
f"remaining task(s) to complete")
|
f"remaining task(s) to complete")
|
||||||
@@ -133,22 +129,21 @@ class DaemonInstance:
|
|||||||
await asyncio.wait_for(future, self._timeout)
|
await asyncio.wait_for(future, self._timeout)
|
||||||
pending = []
|
pending = []
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
future.cancel()
|
||||||
|
future.exception()
|
||||||
self._log.warning(
|
self._log.warning(
|
||||||
f"shutdown timeout exceeded")
|
"shutdown timeout exceeded, remaining task(s) killed")
|
||||||
|
else:
|
||||||
pending = [t for t in pending if not t.done()]
|
|
||||||
|
|
||||||
if pending:
|
|
||||||
self._log.warning(
|
self._log.warning(
|
||||||
f"cancel {len(pending)} remaining task(s)")
|
f"cancel {len(pending)} remaining task(s)")
|
||||||
|
|
||||||
for task in pending:
|
for task in pending:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
#try:
|
try:
|
||||||
# await asyncio.gather(*pending)
|
await asyncio.gather(*pending)
|
||||||
#except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
# pass
|
pass
|
||||||
|
|
||||||
asyncio.get_event_loop().stop()
|
asyncio.get_event_loop().stop()
|
||||||
self._shutdown = False
|
self._shutdown = False
|
||||||
|
|||||||
@@ -18,140 +18,116 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from inspect import iscoroutinefunction
|
||||||
from shlex import quote as shell_quote
|
from shlex import quote as shell_quote
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
class _Task:
|
def event_to_str(event):
|
||||||
def __init__(self, event, delay, task_id, task, callback=None,
|
return f"maskname={event.maskname}, pathname={event.pathname}"
|
||||||
logname="task"):
|
|
||||||
self._event = event
|
|
||||||
self._path = event.pathname
|
|
||||||
self._delay = delay
|
|
||||||
self._task_id = task_id
|
|
||||||
self._job = task
|
|
||||||
self._callback = callback
|
|
||||||
|
|
||||||
self._task = None
|
|
||||||
self._log = logging.getLogger((logname or __name__))
|
|
||||||
|
|
||||||
async def _start(self):
|
|
||||||
try:
|
|
||||||
if self._delay > 0:
|
|
||||||
await asyncio.sleep(self._delay)
|
|
||||||
|
|
||||||
if self._callback is not None:
|
|
||||||
self._callback(self._event)
|
|
||||||
|
|
||||||
self._task = None
|
|
||||||
|
|
||||||
self._log.info(f"execute task {self._task_id}")
|
|
||||||
await self._job(self._event, self._task_id)
|
|
||||||
self._log.info(f"task {self._task_id} finished")
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
self._log.info(f"task {self._task_id} cancelled")
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
if self._task is None:
|
|
||||||
self._task = asyncio.create_task(self._start())
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
if self._task is not None:
|
|
||||||
self._task.cancel()
|
|
||||||
self._task = None
|
|
||||||
|
|
||||||
def restart(self):
|
|
||||||
self.cancel()
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def task_id(self):
|
|
||||||
return self._task_id
|
|
||||||
|
|
||||||
|
|
||||||
class TaskScheduler:
|
class Task:
|
||||||
def __init__(self, task, files, dirs, delay=0, logname="sched"):
|
def __init__(self, task=None, logname="task"):
|
||||||
assert callable(task), \
|
assert task is None or iscoroutinefunction(task), \
|
||||||
f"task: expected callable, got {type(task)}"
|
f"task: expected asynchronous method or None, " \
|
||||||
|
f"got {type(task)}"
|
||||||
|
logname = (logname or __name__)
|
||||||
|
|
||||||
self._task = task
|
self._task = task
|
||||||
|
self._log = logging.getLogger(logname)
|
||||||
|
|
||||||
assert isinstance(delay, int), \
|
def start(self, event, *args, **kwargs):
|
||||||
f"delay: expected {type(int)}, got {type(delay)}"
|
assert self._task, "task not set"
|
||||||
self._delay = delay
|
|
||||||
|
|
||||||
assert isinstance(files, bool), \
|
|
||||||
f"files: expected {type(bool)}, got {type(files)}"
|
|
||||||
self._files = files
|
|
||||||
|
|
||||||
assert isinstance(dirs, bool), \
|
|
||||||
f"dirs: expected {type(bool)}, got {type(dirs)}"
|
|
||||||
self._dirs = dirs
|
|
||||||
|
|
||||||
self._tasks = {}
|
|
||||||
self._logname = (logname or __name__)
|
|
||||||
self._log = logging.getLogger(self._logname)
|
|
||||||
|
|
||||||
def _task_started(self, event):
|
|
||||||
path = event.pathname
|
|
||||||
if path in self._tasks:
|
|
||||||
del self._tasks[path]
|
|
||||||
|
|
||||||
def schedule(self, event):
|
|
||||||
self._log.debug(f"received {event}")
|
|
||||||
|
|
||||||
if (not event.dir and not self._files) or \
|
|
||||||
(event.dir and not self._dirs):
|
|
||||||
return
|
|
||||||
|
|
||||||
path = event.pathname
|
|
||||||
maskname = event.maskname.split("|", 1)[0]
|
|
||||||
|
|
||||||
if path in self._tasks:
|
|
||||||
task = self._tasks[path]
|
|
||||||
task_id = task.task_id()
|
|
||||||
self._log.info(
|
|
||||||
f"received event {maskname} on '{path}', "
|
|
||||||
f"re-schedule task {task_id} (delay={self._delay}s)")
|
|
||||||
task.restart()
|
|
||||||
else:
|
|
||||||
task_id = str(uuid4())
|
task_id = str(uuid4())
|
||||||
self._log.info(
|
task = asyncio.create_task(
|
||||||
f"received event {maskname} on '{path}', "
|
self._task(
|
||||||
f"schedule task {task_id} (delay={self._delay}s)")
|
event, task_id, *args, **kwargs))
|
||||||
task = _Task(
|
return (task_id, task)
|
||||||
event, self._delay, task_id, self._task,
|
|
||||||
callback=self._task_started, logname=self._logname)
|
|
||||||
self._tasks[path] = task
|
|
||||||
task.start()
|
|
||||||
|
|
||||||
def cancel(self, event):
|
|
||||||
self._log.debug(f"received {event}")
|
|
||||||
|
|
||||||
path = event.pathname
|
|
||||||
maskname = event.maskname.split("|", 1)[0]
|
|
||||||
if path in self._tasks:
|
|
||||||
task = self._tasks[path]
|
|
||||||
task_id = task.task_id()
|
|
||||||
self._log.info(
|
|
||||||
f"received event {maskname} on '{path}', "
|
|
||||||
f"cancel scheduled task {task_id}")
|
|
||||||
task.cancel()
|
|
||||||
del self._tasks[path]
|
|
||||||
|
|
||||||
def log(self, event):
|
def log(self, event):
|
||||||
self._log.info(f"LOG: received {event}")
|
self._log.info(f"LOG: {event}")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _TaskState:
|
||||||
|
task_id: str = ""
|
||||||
|
task: asyncio.Task = None
|
||||||
|
waiting: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class TaskScheduler(Task):
|
||||||
|
def __init__(self, task=None, files=True, dirs=False, delay=0,
|
||||||
|
*args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs, task=self._schedule_task)
|
||||||
|
|
||||||
|
assert task is None or iscoroutinefunction(task), \
|
||||||
|
f"TaskScheduler: expected asynchronous method or None, " \
|
||||||
|
f"got {type(task)}"
|
||||||
|
assert isinstance(files, bool), \
|
||||||
|
f"files: expected {type(bool)}, got {type(files)}"
|
||||||
|
assert isinstance(dirs, bool), \
|
||||||
|
f"dirs: expected {type(bool)}, got {type(dirs)}"
|
||||||
|
|
||||||
|
self._delayed_task = task
|
||||||
|
self._files = files
|
||||||
|
self._dirs = dirs
|
||||||
|
self._delay = delay
|
||||||
|
|
||||||
|
self._tasks = {}
|
||||||
|
|
||||||
|
async def _schedule_task(self, event, task_id, task_state):
|
||||||
|
if self._delay > 0:
|
||||||
|
task_state.waiting = True
|
||||||
|
self._log.debug(
|
||||||
|
f"schedule task ({event_to_str(event)}, "
|
||||||
|
f"task_id={task_id}, delay={self._delay})")
|
||||||
|
await asyncio.sleep(self._delay)
|
||||||
|
task_state.waiting = False
|
||||||
|
|
||||||
|
self._log.debug(
|
||||||
|
f"start task ({event_to_str(event)}, task_id={task_id})")
|
||||||
|
await self._delayed_task(event, task_id)
|
||||||
|
self._log.debug(
|
||||||
|
f"task finished ({event_to_str(event)}, task_id={task_id})")
|
||||||
|
del self._tasks[event.pathname]
|
||||||
|
|
||||||
|
def start(self, event, *args, **kwargs):
|
||||||
|
if not ((not event.dir and self._files) or
|
||||||
|
(event.dir and self._dirs)):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.cancel(event)
|
||||||
|
|
||||||
|
task_state = _TaskState()
|
||||||
|
task_state.task_id, task_state.task = super().start(
|
||||||
|
event, task_state, *args, **kwargs)
|
||||||
|
self._tasks[event.pathname] = task_state
|
||||||
|
|
||||||
|
def cancel(self, event):
|
||||||
|
if event.pathname in self._tasks:
|
||||||
|
task_state = self._tasks[event.pathname]
|
||||||
|
|
||||||
|
if task_state.waiting:
|
||||||
|
self._log.debug(
|
||||||
|
f"cancel task ({event_to_str(event)}, "
|
||||||
|
f"task_id={task_state.task_id})")
|
||||||
|
task_state.task.cancel()
|
||||||
|
del self._tasks[event.pathname]
|
||||||
|
|
||||||
|
|
||||||
class ShellScheduler(TaskScheduler):
|
class ShellScheduler(TaskScheduler):
|
||||||
def __init__(self, cmd, task=None, *args, **kwargs):
|
def __init__(self, cmd, task=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs, task=self._shell_task)
|
||||||
|
|
||||||
assert isinstance(cmd, str), \
|
assert isinstance(cmd, str), \
|
||||||
f"cmd: expected {type('')}, got {type(cmd)}"
|
f"cmd: expected {type('')}, got {type(cmd)}"
|
||||||
|
|
||||||
self._cmd = cmd
|
self._cmd = cmd
|
||||||
|
|
||||||
super().__init__(*args, task=self.task, **kwargs)
|
async def _shell_task(self, event, task_id, *args, **kwargs):
|
||||||
|
|
||||||
async def task(self, event, task_id):
|
|
||||||
maskname = event.maskname.split("|", 1)[0]
|
maskname = event.maskname.split("|", 1)[0]
|
||||||
|
|
||||||
if hasattr(event, "src_pathname"):
|
if hasattr(event, "src_pathname"):
|
||||||
src_pathname = event.src_pathname
|
src_pathname = event.src_pathname
|
||||||
else:
|
else:
|
||||||
@@ -178,54 +154,46 @@ class FileManagerRule:
|
|||||||
valid = f"{', '.join(FileManagerRule.valid_actions)}"
|
valid = f"{', '.join(FileManagerRule.valid_actions)}"
|
||||||
assert action in self.valid_actions, \
|
assert action in self.valid_actions, \
|
||||||
f"action: expected [{valid}], got{action}"
|
f"action: expected [{valid}], got{action}"
|
||||||
self.action = action
|
assert isinstance(src_re, str), \
|
||||||
|
f"src_re: expected {type('')}, got {type(src_re)}"
|
||||||
self.src_re = re.compile(src_re)
|
|
||||||
|
|
||||||
assert isinstance(dst_re, str), \
|
assert isinstance(dst_re, str), \
|
||||||
f"dst_re: expected {type('')}, got {type(dst_re)}"
|
f"dst_re: expected {type('')}, got {type(dst_re)}"
|
||||||
self.dst_re = dst_re
|
|
||||||
|
|
||||||
assert isinstance(auto_create, bool), \
|
assert isinstance(auto_create, bool), \
|
||||||
f"auto_create: expected {type(bool)}, got {type(auto_create)}"
|
f"auto_create: expected {type(bool)}, got {type(auto_create)}"
|
||||||
self.auto_create = auto_create
|
assert dirmode is None or isinstance(dirmode, int), \
|
||||||
|
|
||||||
if dirmode is not None:
|
|
||||||
assert isinstance(dirmode, int), \
|
|
||||||
f"dirmode: expected {type(int)}, got {type(dirmode)}"
|
f"dirmode: expected {type(int)}, got {type(dirmode)}"
|
||||||
self.dirmode = dirmode
|
assert filemode is None or isinstance(filemode, int), \
|
||||||
|
|
||||||
if filemode is not None:
|
|
||||||
assert isinstance(filemode, int), \
|
|
||||||
f"filemode: expected {type(int)}, got {type(filemode)}"
|
f"filemode: expected {type(int)}, got {type(filemode)}"
|
||||||
self.filemode = filemode
|
assert user is None or isinstance(user, str), \
|
||||||
|
|
||||||
if user is not None:
|
|
||||||
assert isinstance(user, str), \
|
|
||||||
f"user: expected {type('')}, got {type(user)}"
|
f"user: expected {type('')}, got {type(user)}"
|
||||||
self.user = user
|
assert group is None or isinstance(group, str), \
|
||||||
|
|
||||||
if group is not None:
|
|
||||||
assert isinstance(group, str), \
|
|
||||||
f"group: expected {type('')}, got {type(group)}"
|
f"group: expected {type('')}, got {type(group)}"
|
||||||
self.group = group
|
|
||||||
|
|
||||||
assert isinstance(rec, bool), \
|
assert isinstance(rec, bool), \
|
||||||
f"rec: expected {type(bool)}, got {type(rec)}"
|
f"rec: expected {type(bool)}, got {type(rec)}"
|
||||||
|
|
||||||
|
self.action = action
|
||||||
|
self.src_re = re.compile(src_re)
|
||||||
|
self.dst_re = dst_re
|
||||||
|
self.auto_create = auto_create
|
||||||
|
self.dirmode = dirmode
|
||||||
|
self.filemode = filemode
|
||||||
|
self.user = user
|
||||||
|
self.group = group
|
||||||
self.rec = rec
|
self.rec = rec
|
||||||
|
|
||||||
|
|
||||||
class FileManagerScheduler(TaskScheduler):
|
class FileManagerScheduler(TaskScheduler):
|
||||||
def __init__(self, rules, task=None, *args, **kwargs):
|
def __init__(self, rules, task=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs, task=self._manager_task)
|
||||||
|
|
||||||
if not isinstance(rules, list):
|
if not isinstance(rules, list):
|
||||||
rules = [rules]
|
rules = [rules]
|
||||||
|
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
assert isinstance(rule, FileManagerRule), \
|
assert isinstance(rule, FileManagerRule), \
|
||||||
f"rules: expected {type(FileManagerRule)}, got {type(rule)}"
|
f"rules: expected {type(FileManagerRule)}, got {type(rule)}"
|
||||||
self._rules = rules
|
|
||||||
|
|
||||||
super().__init__(*args, task=self.task, **kwargs)
|
self._rules = rules
|
||||||
|
|
||||||
async def _chmod_and_chown(self, path, mode, chown, task_id):
|
async def _chmod_and_chown(self, path, mode, chown, task_id):
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
@@ -283,14 +251,14 @@ class FileManagerScheduler(TaskScheduler):
|
|||||||
|
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
def schedule(self, event):
|
def start(self, event):
|
||||||
if self._get_rule_by_event(event):
|
if self._get_rule_by_event(event):
|
||||||
super().schedule(event)
|
super().start(event)
|
||||||
else:
|
else:
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
f"no rule in ruleset matches path '{event.pathname}'")
|
f"no rule in ruleset matches path '{event.pathname}'")
|
||||||
|
|
||||||
async def task(self, event, task_id):
|
async def _manager_task(self, event, task_id, *args, **kwargs):
|
||||||
path = event.pathname
|
path = event.pathname
|
||||||
rule = self._get_rule_by_event(event)
|
rule = self._get_rule_by_event(event)
|
||||||
|
|
||||||
|
|||||||
@@ -76,11 +76,10 @@ class _TaskList:
|
|||||||
|
|
||||||
|
|
||||||
class Watch:
|
class Watch:
|
||||||
def __init__(self, path, event_map, rec=False, auto_add=False):
|
def __init__(self, path, event_map, rec=False, auto_add=False,
|
||||||
|
logname="watch"):
|
||||||
assert isinstance(path, str), \
|
assert isinstance(path, str), \
|
||||||
f"path: expected {type('')}, got {type(path)}"
|
f"path: expected {type('')}, got {type(path)}"
|
||||||
self._path = path
|
|
||||||
|
|
||||||
if isinstance(event_map, EventMap):
|
if isinstance(event_map, EventMap):
|
||||||
self._event_map = event_map
|
self._event_map = event_map
|
||||||
elif isinstance(event_map, dict):
|
elif isinstance(event_map, dict):
|
||||||
@@ -92,10 +91,11 @@ class Watch:
|
|||||||
|
|
||||||
assert isinstance(rec, bool), \
|
assert isinstance(rec, bool), \
|
||||||
f"rec: expected {type(bool)}, got {type(rec)}"
|
f"rec: expected {type(bool)}, got {type(rec)}"
|
||||||
self._rec = rec
|
|
||||||
|
|
||||||
assert isinstance(auto_add, bool), \
|
assert isinstance(auto_add, bool), \
|
||||||
f"auto_add: expected {type(bool)}, got {type(auto_add)}"
|
f"auto_add: expected {type(bool)}, got {type(auto_add)}"
|
||||||
|
|
||||||
|
self._path = path
|
||||||
|
self._rec = rec
|
||||||
self._auto_add = auto_add
|
self._auto_add = auto_add
|
||||||
|
|
||||||
def path(self):
|
def path(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user