2019-09-10 00:54:28 +00:00
|
|
|
# Created By: Virgil Dupras
|
|
|
|
# Created On: 2010-11-19
|
|
|
|
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
|
2020-01-01 02:16:27 +00:00
|
|
|
#
|
|
|
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
|
|
# which should be included with this package. The terms are also available at
|
2019-09-10 00:54:28 +00:00
|
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
|
|
|
|
from threading import Thread
|
|
|
|
import sys
|
2022-05-11 05:50:34 +00:00
|
|
|
from typing import Callable, Tuple, Union
|
2019-09-10 00:54:28 +00:00
|
|
|
|
2022-05-09 06:40:08 +00:00
|
|
|
from hscommon.jobprogress.job import Job, JobInProgressError, JobCancelled
|
2019-09-10 00:54:28 +00:00
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
class ThreadedJobPerformer:
|
|
|
|
"""Run threaded jobs and track progress.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
To run a threaded job, first create a job with _create_job(), then call _run_threaded(), with
|
2019-09-10 00:54:28 +00:00
|
|
|
your work function as a parameter.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Example:
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
j = self._create_job()
|
|
|
|
self._run_threaded(self.some_work_func, (arg1, arg2, j))
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
_job_running = False
|
|
|
|
last_error = None
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Protected
|
2022-05-11 05:50:34 +00:00
|
|
|
def create_job(self) -> Job:
|
2019-09-10 00:54:28 +00:00
|
|
|
if self._job_running:
|
|
|
|
raise JobInProgressError()
|
2022-05-11 05:50:34 +00:00
|
|
|
self.last_progress: Union[int, None] = -1
|
2020-01-01 02:16:27 +00:00
|
|
|
self.last_desc = ""
|
2019-09-10 00:54:28 +00:00
|
|
|
self.job_cancelled = False
|
|
|
|
return Job(1, self._update_progress)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-11 05:50:34 +00:00
|
|
|
def _async_run(self, *args) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
target = args[0]
|
|
|
|
args = tuple(args[1:])
|
|
|
|
self._job_running = True
|
|
|
|
self.last_error = None
|
|
|
|
try:
|
|
|
|
target(*args)
|
|
|
|
except JobCancelled:
|
|
|
|
pass
|
|
|
|
except Exception as e:
|
|
|
|
self.last_error = e
|
|
|
|
self.last_traceback = sys.exc_info()[2]
|
|
|
|
finally:
|
|
|
|
self._job_running = False
|
|
|
|
self.last_progress = None
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-11 05:50:34 +00:00
|
|
|
def reraise_if_error(self) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
"""Reraises the error that happened in the thread if any.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Call this after the caller of run_threaded detected that self._job_running returned to False
|
|
|
|
"""
|
|
|
|
if self.last_error is not None:
|
|
|
|
raise self.last_error.with_traceback(self.last_traceback)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-11 05:50:34 +00:00
|
|
|
def _update_progress(self, newprogress: int, newdesc: str = "") -> bool:
|
2019-09-10 00:54:28 +00:00
|
|
|
self.last_progress = newprogress
|
|
|
|
if newdesc:
|
|
|
|
self.last_desc = newdesc
|
|
|
|
return not self.job_cancelled
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2022-05-11 05:50:34 +00:00
|
|
|
def run_threaded(self, target: Callable, args: Tuple = ()) -> None:
|
2019-09-10 00:54:28 +00:00
|
|
|
if self._job_running:
|
|
|
|
raise JobInProgressError()
|
2020-01-01 02:16:27 +00:00
|
|
|
args = (target,) + args
|
2019-09-10 00:54:28 +00:00
|
|
|
Thread(target=self._async_run, args=args).start()
|