def execute_macro(self, name): if name != prefs['current_macro']: prefs['current_macro'] = name self.mark_current_macro() macro = prefs['macros'].get(name, None) if not macro: return log = GUILog() log.outputs.append(ANSIStream()) try: if macro.get('execfromfile'): with open(macro['macrofile'], 'rU') as file: self.execute(file, log) elif macro['program']: program = macro['program'] encoding = self.get_encoding(program) if encoding: program = program.encode(encoding) self.execute(program, log) except: log.exception(_('Failed to execute macro')) error_dialog( self.gui, _('Failed to execute macro'), _('Failed to execute macro, click "Show details" for more information.' ), det_msg=log.plain_text, show=True)
def execute_button_clicked(self): try: log = GUILog() log.outputs.append(QtLogStream()) log.outputs[1].html_log.connect(self.log_outputted) log.outputs.append(ANSIStream()) if self.fromfile_checkbox.isChecked(): self.load_file_button_clicked() program = self.program.toPlainText() encoding = self.ia.get_encoding(program) if encoding: program = program.encode(encoding) self.textBrowser.clear() self.ia.execute(program, log) except: log.exception('Failed to execute macro:') lineno = 0 for tb in traceback.extract_tb(sys.exc_info()[2]): if tb[0] == '<string>': lineno = tb[1] if 0 < lineno: self.program.go_to_line(lineno)
class ThreadedJob(BaseJob): def __init__(self, type_, description, func, args, kwargs, callback, max_concurrent_count=1, killable=True, log=None): ''' A job that is run in its own thread in the calibre main process :param type_: The type of this job (a string). The type is used in conjunction with max_concurrent_count to prevent too many jobs of the same type from running :param description: A user viewable job description :func: The function that actually does the work. This function *must* accept at least three keyword arguments: abort, log and notifications. abort is An Event object. func should periodically check abort.is_set() and if it is True, it should stop processing as soon as possible. notifications is a Queue. func should put progress notifications into it in the form of a tuple (frac, msg). frac is a number between 0 and 1 indicating progress and msg is a string describing the progress. log is a Log object which func should use for all debugging output. func should raise an Exception to indicate failure. This exception is stored in job.exception and can thus be used to pass arbitrary information to callback. :param args,kwargs: These are passed to func when it is called :param callback: A callable that is called on completion of this job. Note that it is not called if the user kills the job. Check job.failed to see if the job succeeded or not. And use job.log to get the job log. :param killable: If False the GUI wont let the user kill this job :param log: Must be a subclass of GUILog or None. If None a default GUILog is created. ''' BaseJob.__init__(self, description) self.type = type_ self.max_concurrent_count = max_concurrent_count self.killable = killable self.callback = callback self.abort = Event() self.exception = None kwargs['notifications'] = self.notifications kwargs['abort'] = self.abort self.log = GUILog() if log is None else log kwargs['log'] = self.log self.func, self.args, self.kwargs = func, args, kwargs self.consolidated_log = None def start_work(self): self.start_time = time.time() self.log('Starting job:', self.description) try: self.result = self.func(*self.args, **self.kwargs) except Exception as e: self.exception = e self.failed = True self.log.exception('Job: "%s" failed with error:' % self.description) self.log.debug('Called with args:', self.args, self.kwargs) self.duration = time.time() - self.start_time try: self.callback(self) except: import traceback traceback.print_exc() self._cleanup() def _cleanup(self): try: self.consolidate_log() except: if self.log is not None: self.log.exception('Log consolidation failed') # No need to keep references to these around anymore self.func = self.args = self.kwargs = self.notifications = None # We can't delete self.callback as it might be a Dispatch object and if # it is garbage collected it won't work def kill(self): if self.start_time is None: self.start_time = time.time() self.duration = 0.0001 else: self.duration = time.time() - self.start_time self.abort.set() self.log('Aborted job:', self.description) self.killed = True self.failed = True self._cleanup() def consolidate_log(self): logs = [self.log.html, self.log.plain_text] bdir = base_dir() log_dir = os.path.join(bdir, 'threaded_job_logs') if not os.path.exists(log_dir): os.makedirs(log_dir) fd, path = tempfile.mkstemp(suffix='.json', prefix='log-', dir=log_dir) with os.fdopen(fd, 'wb') as f: f.write( json.dumps(logs, ensure_ascii=False, indent=2).encode('utf-8')) self.consolidated_log = path self.log = None def read_consolidated_log(self): with open(self.consolidated_log, 'rb') as f: return json.loads(f.read().decode('utf-8')) @property def details(self): if self.consolidated_log is None: return self.log.plain_text return self.read_consolidated_log()[1] @property def html_details(self): if self.consolidated_log is None: return self.log.html return self.read_consolidated_log()[0]
class ThreadedJob(BaseJob): def __init__(self, type_, description, func, args, kwargs, callback, max_concurrent_count=1, killable=True, log=None): ''' A job that is run in its own thread in the calibre main process :param type_: The type of this job (a string). The type is used in conjunction with max_concurrent_count to prevent too many jobs of the same type from running :param description: A user viewable job description :func: The function that actually does the work. This function *must* accept at least three keyword arguments: abort, log and notifications. abort is An Event object. func should periodically check abort.is_set() and if it is True, it should stop processing as soon as possible. notifications is a Queue. func should put progress notifications into it in the form of a tuple (frac, msg). frac is a number between 0 and 1 indicating progress and msg is a string describing the progress. log is a Log object which func should use for all debugging output. func should raise an Exception to indicate failure. This exception is stored in job.exception and can thus be used to pass arbitrary information to callback. :param args,kwargs: These are passed to func when it is called :param callback: A callable that is called on completion of this job. Note that it is not called if the user kills the job. Check job.failed to see if the job succeeded or not. And use job.log to get the job log. :param killable: If False the GUI wont let the user kill this job :param log: Must be a subclass of GUILog or None. If None a default GUILog is created. ''' BaseJob.__init__(self, description) self.type = type_ self.max_concurrent_count = max_concurrent_count self.killable = killable self.callback = callback self.abort = Event() self.exception = None kwargs['notifications'] = self.notifications kwargs['abort'] = self.abort self.log = GUILog() if log is None else log kwargs['log'] = self.log self.func, self.args, self.kwargs = func, args, kwargs self.consolidated_log = None def start_work(self): self.start_time = time.time() self.log('Starting job:', self.description) try: self.result = self.func(*self.args, **self.kwargs) except Exception as e: self.exception = e self.failed = True self.log.exception('Job: "%s" failed with error:'%self.description) self.log.debug('Called with args:', self.args, self.kwargs) self.duration = time.time() - self.start_time try: self.callback(self) except: import traceback traceback.print_exc() self._cleanup() def _cleanup(self): try: self.consolidate_log() except: if self.log is not None: self.log.exception('Log consolidation failed') # No need to keep references to these around anymore self.func = self.args = self.kwargs = self.notifications = None # We can't delete self.callback as it might be a Dispatch object and if # it is garbage collected it won't work def kill(self): if self.start_time is None: self.start_time = time.time() self.duration = 0.0001 else: self.duration = time.time() - self.start_time self.abort.set() self.log('Aborted job:', self.description) self.killed = True self.failed = True self._cleanup() def consolidate_log(self): logs = [self.log.html, self.log.plain_text] bdir = base_dir() log_dir = os.path.join(bdir, 'threaded_job_logs') if not os.path.exists(log_dir): os.makedirs(log_dir) fd, path = tempfile.mkstemp(suffix='.json', prefix='log-', dir=log_dir) with os.fdopen(fd, 'wb') as f: f.write(json.dumps(logs, ensure_ascii=False, indent=2).encode('utf-8')) self.consolidated_log = path self.log = None def read_consolidated_log(self): with open(self.consolidated_log, 'rb') as f: return json.loads(f.read().decode('utf-8')) @property def details(self): if self.consolidated_log is None: return self.log.plain_text return self.read_consolidated_log()[1] @property def html_details(self): if self.consolidated_log is None: return self.log.html return self.read_consolidated_log()[0]