1""" 2BitBake 'Event' implementation 3 4Classes and functions for manipulating 'events' in the 5BitBake build tools. 6""" 7 8# Copyright (C) 2003, 2004 Chris Larson 9# 10# SPDX-License-Identifier: GPL-2.0-only 11# 12 13import ast 14import atexit 15import collections 16import logging 17import pickle 18import sys 19import threading 20import traceback 21 22import bb.exceptions 23import bb.utils 24 25# This is the pid for which we should generate the event. This is set when 26# the runqueue forks off. 27worker_pid = 0 28worker_fire = None 29 30logger = logging.getLogger('BitBake.Event') 31 32class Event(object): 33 """Base class for events""" 34 35 def __init__(self): 36 self.pid = worker_pid 37 38 39class HeartbeatEvent(Event): 40 """Triggered at regular time intervals of 10 seconds. Other events can fire much more often 41 (runQueueTaskStarted when there are many short tasks) or not at all for long periods 42 of time (again runQueueTaskStarted, when there is just one long-running task), so this 43 event is more suitable for doing some task-independent work occasionally.""" 44 def __init__(self, time): 45 Event.__init__(self) 46 self.time = time 47 48Registered = 10 49AlreadyRegistered = 14 50 51def get_class_handlers(): 52 return _handlers 53 54def set_class_handlers(h): 55 global _handlers 56 _handlers = h 57 58def clean_class_handlers(): 59 return collections.OrderedDict() 60 61# Internal 62_handlers = clean_class_handlers() 63_ui_handlers = {} 64_ui_logfilters = {} 65_ui_handler_seq = 0 66_event_handler_map = {} 67_catchall_handlers = {} 68_eventfilter = None 69_uiready = False 70_thread_lock = threading.Lock() 71_heartbeat_enabled = False 72_should_exit = threading.Event() 73 74def enable_threadlock(): 75 # Always needed now 76 return 77 78def disable_threadlock(): 79 # Always needed now 80 return 81 82def enable_heartbeat(): 83 global _heartbeat_enabled 84 _heartbeat_enabled = True 85 86def disable_heartbeat(): 87 global _heartbeat_enabled 88 _heartbeat_enabled = False 89 90# 91# In long running code, this function should be called periodically 92# to check if we should exit due to an interuption (.e.g Ctrl+C from the UI) 93# 94def check_for_interrupts(d): 95 global _should_exit 96 if _should_exit.is_set(): 97 bb.warn("Exiting due to interrupt.") 98 raise bb.BBHandledException() 99 100def execute_handler(name, handler, event, d): 101 event.data = d 102 try: 103 ret = handler(event, d) 104 except (bb.parse.SkipRecipe, bb.BBHandledException): 105 raise 106 except Exception: 107 etype, value, tb = sys.exc_info() 108 logger.error("Execution of event handler '%s' failed" % name, 109 exc_info=(etype, value, tb.tb_next)) 110 raise 111 except SystemExit as exc: 112 if exc.code != 0: 113 logger.error("Execution of event handler '%s' failed" % name) 114 raise 115 finally: 116 del event.data 117 118 119def fire_class_handlers(event, d): 120 if isinstance(event, logging.LogRecord): 121 return 122 123 eid = str(event.__class__)[8:-2] 124 evt_hmap = _event_handler_map.get(eid, {}) 125 for name, handler in list(_handlers.items()): 126 if name in _catchall_handlers or name in evt_hmap: 127 if _eventfilter: 128 if not _eventfilter(name, handler, event, d): 129 continue 130 if d is not None and not name in (d.getVar("__BBHANDLERS_MC") or set()): 131 continue 132 execute_handler(name, handler, event, d) 133 134ui_queue = [] 135@atexit.register 136def print_ui_queue(): 137 global ui_queue 138 """If we're exiting before a UI has been spawned, display any queued 139 LogRecords to the console.""" 140 logger = logging.getLogger("BitBake") 141 if not _uiready: 142 from bb.msg import BBLogFormatter 143 # Flush any existing buffered content 144 try: 145 sys.stdout.flush() 146 except: 147 pass 148 try: 149 sys.stderr.flush() 150 except: 151 pass 152 stdout = logging.StreamHandler(sys.stdout) 153 stderr = logging.StreamHandler(sys.stderr) 154 formatter = BBLogFormatter("%(levelname)s: %(message)s") 155 stdout.setFormatter(formatter) 156 stderr.setFormatter(formatter) 157 158 # First check to see if we have any proper messages 159 msgprint = False 160 msgerrs = False 161 162 # Should we print to stderr? 163 for event in ui_queue[:]: 164 if isinstance(event, logging.LogRecord) and event.levelno >= logging.WARNING: 165 msgerrs = True 166 break 167 168 if msgerrs: 169 logger.addHandler(stderr) 170 else: 171 logger.addHandler(stdout) 172 173 for event in ui_queue[:]: 174 if isinstance(event, logging.LogRecord): 175 if event.levelno > logging.DEBUG: 176 logger.handle(event) 177 msgprint = True 178 179 # Nope, so just print all of the messages we have (including debug messages) 180 if not msgprint: 181 for event in ui_queue[:]: 182 if isinstance(event, logging.LogRecord): 183 logger.handle(event) 184 if msgerrs: 185 logger.removeHandler(stderr) 186 else: 187 logger.removeHandler(stdout) 188 ui_queue = [] 189 190def fire_ui_handlers(event, d): 191 global _thread_lock 192 193 if not _uiready: 194 # No UI handlers registered yet, queue up the messages 195 ui_queue.append(event) 196 return 197 198 with bb.utils.lock_timeout(_thread_lock): 199 errors = [] 200 for h in _ui_handlers: 201 #print "Sending event %s" % event 202 try: 203 if not _ui_logfilters[h].filter(event): 204 continue 205 # We use pickle here since it better handles object instances 206 # which xmlrpc's marshaller does not. Events *must* be serializable 207 # by pickle. 208 if hasattr(_ui_handlers[h].event, "sendpickle"): 209 _ui_handlers[h].event.sendpickle((pickle.dumps(event))) 210 else: 211 _ui_handlers[h].event.send(event) 212 except: 213 errors.append(h) 214 for h in errors: 215 del _ui_handlers[h] 216 217def fire(event, d): 218 """Fire off an Event""" 219 220 # We can fire class handlers in the worker process context and this is 221 # desired so they get the task based datastore. 222 # UI handlers need to be fired in the server context so we defer this. They 223 # don't have a datastore so the datastore context isn't a problem. 224 225 fire_class_handlers(event, d) 226 if worker_fire: 227 worker_fire(event, d) 228 else: 229 # If messages have been queued up, clear the queue 230 global _uiready, ui_queue 231 if _uiready and ui_queue: 232 for queue_event in ui_queue: 233 fire_ui_handlers(queue_event, d) 234 ui_queue = [] 235 fire_ui_handlers(event, d) 236 237def fire_from_worker(event, d): 238 fire_ui_handlers(event, d) 239 240noop = lambda _: None 241def register(name, handler, mask=None, filename=None, lineno=None, data=None): 242 """Register an Event handler""" 243 244 if data is not None and data.getVar("BB_CURRENT_MC"): 245 mc = data.getVar("BB_CURRENT_MC") 246 name = '%s%s' % (mc.replace('-', '_'), name) 247 248 # already registered 249 if name in _handlers: 250 if data is not None: 251 bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set()) 252 bbhands_mc.add(name) 253 data.setVar("__BBHANDLERS_MC", bbhands_mc) 254 return AlreadyRegistered 255 256 if handler is not None: 257 # handle string containing python code 258 if isinstance(handler, str): 259 tmp = "def %s(e, d):\n%s" % (name, handler) 260 # Inject empty lines to make code match lineno in filename 261 if lineno is not None: 262 tmp = "\n" * (lineno-1) + tmp 263 try: 264 code = bb.methodpool.compile_cache(tmp) 265 if not code: 266 if filename is None: 267 filename = "%s(e, d)" % name 268 code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST) 269 code = compile(code, filename, "exec") 270 bb.methodpool.compile_cache_add(tmp, code) 271 except SyntaxError: 272 logger.error("Unable to register event handler '%s':\n%s", name, 273 ''.join(traceback.format_exc(limit=0))) 274 _handlers[name] = noop 275 return 276 env = {} 277 bb.utils.better_exec(code, env) 278 func = bb.utils.better_eval(name, env) 279 _handlers[name] = func 280 else: 281 _handlers[name] = handler 282 283 if not mask or '*' in mask: 284 _catchall_handlers[name] = True 285 else: 286 for m in mask: 287 if _event_handler_map.get(m, None) is None: 288 _event_handler_map[m] = {} 289 _event_handler_map[m][name] = True 290 291 if data is not None: 292 bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set()) 293 bbhands_mc.add(name) 294 data.setVar("__BBHANDLERS_MC", bbhands_mc) 295 296 return Registered 297 298def remove(name, handler, data=None): 299 """Remove an Event handler""" 300 if data is not None: 301 if data.getVar("BB_CURRENT_MC"): 302 mc = data.getVar("BB_CURRENT_MC") 303 name = '%s%s' % (mc.replace('-', '_'), name) 304 305 _handlers.pop(name) 306 if name in _catchall_handlers: 307 _catchall_handlers.pop(name) 308 for event in _event_handler_map.keys(): 309 if name in _event_handler_map[event]: 310 _event_handler_map[event].pop(name) 311 312 if data is not None: 313 bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set()) 314 if name in bbhands_mc: 315 bbhands_mc.remove(name) 316 data.setVar("__BBHANDLERS_MC", bbhands_mc) 317 318def get_handlers(): 319 return _handlers 320 321def set_handlers(handlers): 322 global _handlers 323 _handlers = handlers 324 325def set_eventfilter(func): 326 global _eventfilter 327 _eventfilter = func 328 329def register_UIHhandler(handler, mainui=False): 330 with bb.utils.lock_timeout(_thread_lock): 331 bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1 332 _ui_handlers[_ui_handler_seq] = handler 333 level, debug_domains = bb.msg.constructLogOptions() 334 _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains) 335 if mainui: 336 global _uiready 337 _uiready = _ui_handler_seq 338 return _ui_handler_seq 339 340def unregister_UIHhandler(handlerNum, mainui=False): 341 if mainui: 342 global _uiready 343 _uiready = False 344 with bb.utils.lock_timeout(_thread_lock): 345 if handlerNum in _ui_handlers: 346 del _ui_handlers[handlerNum] 347 return 348 349def get_uihandler(): 350 if _uiready is False: 351 return None 352 return _uiready 353 354# Class to allow filtering of events and specific filtering of LogRecords *before* we put them over the IPC 355class UIEventFilter(object): 356 def __init__(self, level, debug_domains): 357 self.update(None, level, debug_domains) 358 359 def update(self, eventmask, level, debug_domains): 360 self.eventmask = eventmask 361 self.stdlevel = level 362 self.debug_domains = debug_domains 363 364 def filter(self, event): 365 if isinstance(event, logging.LogRecord): 366 if event.levelno >= self.stdlevel: 367 return True 368 if event.name in self.debug_domains and event.levelno >= self.debug_domains[event.name]: 369 return True 370 return False 371 eid = str(event.__class__)[8:-2] 372 if self.eventmask and eid not in self.eventmask: 373 return False 374 return True 375 376def set_UIHmask(handlerNum, level, debug_domains, mask): 377 if not handlerNum in _ui_handlers: 378 return False 379 if '*' in mask: 380 _ui_logfilters[handlerNum].update(None, level, debug_domains) 381 else: 382 _ui_logfilters[handlerNum].update(mask, level, debug_domains) 383 return True 384 385def getName(e): 386 """Returns the name of a class or class instance""" 387 if getattr(e, "__name__", None) is None: 388 return e.__class__.__name__ 389 else: 390 return e.__name__ 391 392class OperationStarted(Event): 393 """An operation has begun""" 394 def __init__(self, msg = "Operation Started"): 395 Event.__init__(self) 396 self.msg = msg 397 398class OperationCompleted(Event): 399 """An operation has completed""" 400 def __init__(self, total, msg = "Operation Completed"): 401 Event.__init__(self) 402 self.total = total 403 self.msg = msg 404 405class OperationProgress(Event): 406 """An operation is in progress""" 407 def __init__(self, current, total, msg = "Operation in Progress"): 408 Event.__init__(self) 409 self.current = current 410 self.total = total 411 self.msg = msg + ": %s/%s" % (current, total); 412 413class ConfigParsed(Event): 414 """Configuration Parsing Complete""" 415 416class MultiConfigParsed(Event): 417 """Multi-Config Parsing Complete""" 418 def __init__(self, mcdata): 419 self.mcdata = mcdata 420 Event.__init__(self) 421 422class RecipeEvent(Event): 423 def __init__(self, fn): 424 self.fn = fn 425 Event.__init__(self) 426 427class RecipePreFinalise(RecipeEvent): 428 """ Recipe Parsing Complete but not yet finalised""" 429 430class RecipePostKeyExpansion(RecipeEvent): 431 """ Recipe Parsing Complete but not yet finalised""" 432 433 434class RecipeTaskPreProcess(RecipeEvent): 435 """ 436 Recipe Tasks about to be finalised 437 The list of tasks should be final at this point and handlers 438 are only able to change interdependencies 439 """ 440 def __init__(self, fn, tasklist): 441 self.fn = fn 442 self.tasklist = tasklist 443 Event.__init__(self) 444 445class RecipeParsed(RecipeEvent): 446 """ Recipe Parsing Complete """ 447 448class BuildBase(Event): 449 """Base class for bitbake build events""" 450 451 def __init__(self, n, p, failures = 0): 452 self._name = n 453 self._pkgs = p 454 Event.__init__(self) 455 self._failures = failures 456 457 def getPkgs(self): 458 return self._pkgs 459 460 def setPkgs(self, pkgs): 461 self._pkgs = pkgs 462 463 def getName(self): 464 return self._name 465 466 def setName(self, name): 467 self._name = name 468 469 def getFailures(self): 470 """ 471 Return the number of failed packages 472 """ 473 return self._failures 474 475 pkgs = property(getPkgs, setPkgs, None, "pkgs property") 476 name = property(getName, setName, None, "name property") 477 478class BuildInit(BuildBase): 479 """buildFile or buildTargets was invoked""" 480 def __init__(self, p=[]): 481 name = None 482 BuildBase.__init__(self, name, p) 483 484class BuildStarted(BuildBase, OperationStarted): 485 """Event when builds start""" 486 def __init__(self, n, p, failures = 0): 487 OperationStarted.__init__(self, "Building Started") 488 BuildBase.__init__(self, n, p, failures) 489 490class BuildCompleted(BuildBase, OperationCompleted): 491 """Event when builds have completed""" 492 def __init__(self, total, n, p, failures=0, interrupted=0): 493 if not failures: 494 OperationCompleted.__init__(self, total, "Building Succeeded") 495 else: 496 OperationCompleted.__init__(self, total, "Building Failed") 497 self._interrupted = interrupted 498 BuildBase.__init__(self, n, p, failures) 499 500class DiskFull(Event): 501 """Disk full case build halted""" 502 def __init__(self, dev, type, freespace, mountpoint): 503 Event.__init__(self) 504 self._dev = dev 505 self._type = type 506 self._free = freespace 507 self._mountpoint = mountpoint 508 509class DiskUsageSample: 510 def __init__(self, available_bytes, free_bytes, total_bytes): 511 # Number of bytes available to non-root processes. 512 self.available_bytes = available_bytes 513 # Number of bytes available to root processes. 514 self.free_bytes = free_bytes 515 # Total capacity of the volume. 516 self.total_bytes = total_bytes 517 518class MonitorDiskEvent(Event): 519 """If BB_DISKMON_DIRS is set, then this event gets triggered each time disk space is checked. 520 Provides information about devices that are getting monitored.""" 521 def __init__(self, disk_usage): 522 Event.__init__(self) 523 # hash of device root path -> DiskUsageSample 524 self.disk_usage = disk_usage 525 526class NoProvider(Event): 527 """No Provider for an Event""" 528 529 def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None): 530 Event.__init__(self) 531 self._item = item 532 self._runtime = runtime 533 self._dependees = dependees 534 self._reasons = reasons 535 self._close_matches = close_matches 536 537 def getItem(self): 538 return self._item 539 540 def isRuntime(self): 541 return self._runtime 542 543 def __str__(self): 544 msg = '' 545 if self._runtime: 546 r = "R" 547 else: 548 r = "" 549 550 extra = '' 551 if not self._reasons: 552 if self._close_matches: 553 extra = ". Close matches:\n %s" % '\n '.join(sorted(set(self._close_matches))) 554 555 if self._dependees: 556 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % (r, self._item, ", ".join(self._dependees), r, extra) 557 else: 558 msg = "Nothing %sPROVIDES '%s'%s" % (r, self._item, extra) 559 if self._reasons: 560 for reason in self._reasons: 561 msg += '\n' + reason 562 return msg 563 564 565class MultipleProviders(Event): 566 """Multiple Providers""" 567 568 def __init__(self, item, candidates, runtime = False): 569 Event.__init__(self) 570 self._item = item 571 self._candidates = candidates 572 self._is_runtime = runtime 573 574 def isRuntime(self): 575 """ 576 Is this a runtime issue? 577 """ 578 return self._is_runtime 579 580 def getItem(self): 581 """ 582 The name for the to be build item 583 """ 584 return self._item 585 586 def getCandidates(self): 587 """ 588 Get the possible Candidates for a PROVIDER. 589 """ 590 return self._candidates 591 592 def __str__(self): 593 msg = "Multiple providers are available for %s%s (%s)" % (self._is_runtime and "runtime " or "", 594 self._item, 595 ", ".join(self._candidates)) 596 rtime = "" 597 if self._is_runtime: 598 rtime = "R" 599 msg += "\nConsider defining a PREFERRED_%sPROVIDER entry to match %s" % (rtime, self._item) 600 return msg 601 602class ParseStarted(OperationStarted): 603 """Recipe parsing for the runqueue has begun""" 604 def __init__(self, total): 605 OperationStarted.__init__(self, "Recipe parsing Started") 606 self.total = total 607 608class ParseCompleted(OperationCompleted): 609 """Recipe parsing for the runqueue has completed""" 610 def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total): 611 OperationCompleted.__init__(self, total, "Recipe parsing Completed") 612 self.cached = cached 613 self.parsed = parsed 614 self.skipped = skipped 615 self.virtuals = virtuals 616 self.masked = masked 617 self.errors = errors 618 self.sofar = cached + parsed 619 620class ParseProgress(OperationProgress): 621 """Recipe parsing progress""" 622 def __init__(self, current, total): 623 OperationProgress.__init__(self, current, total, "Recipe parsing") 624 625 626class CacheLoadStarted(OperationStarted): 627 """Loading of the dependency cache has begun""" 628 def __init__(self, total): 629 OperationStarted.__init__(self, "Loading cache Started") 630 self.total = total 631 632class CacheLoadProgress(OperationProgress): 633 """Cache loading progress""" 634 def __init__(self, current, total): 635 OperationProgress.__init__(self, current, total, "Loading cache") 636 637class CacheLoadCompleted(OperationCompleted): 638 """Cache loading is complete""" 639 def __init__(self, total, num_entries): 640 OperationCompleted.__init__(self, total, "Loading cache Completed") 641 self.num_entries = num_entries 642 643class TreeDataPreparationStarted(OperationStarted): 644 """Tree data preparation started""" 645 def __init__(self): 646 OperationStarted.__init__(self, "Preparing tree data Started") 647 648class TreeDataPreparationProgress(OperationProgress): 649 """Tree data preparation is in progress""" 650 def __init__(self, current, total): 651 OperationProgress.__init__(self, current, total, "Preparing tree data") 652 653class TreeDataPreparationCompleted(OperationCompleted): 654 """Tree data preparation completed""" 655 def __init__(self, total): 656 OperationCompleted.__init__(self, total, "Preparing tree data Completed") 657 658class DepTreeGenerated(Event): 659 """ 660 Event when a dependency tree has been generated 661 """ 662 663 def __init__(self, depgraph): 664 Event.__init__(self) 665 self._depgraph = depgraph 666 667class TargetsTreeGenerated(Event): 668 """ 669 Event when a set of buildable targets has been generated 670 """ 671 def __init__(self, model): 672 Event.__init__(self) 673 self._model = model 674 675class ReachableStamps(Event): 676 """ 677 An event listing all stamps reachable after parsing 678 which the metadata may use to clean up stale data 679 """ 680 681 def __init__(self, stamps): 682 Event.__init__(self) 683 self.stamps = stamps 684 685class StaleSetSceneTasks(Event): 686 """ 687 An event listing setscene tasks which are 'stale' and will 688 be rerun. The metadata may use to clean up stale data. 689 tasks is a mapping of tasks and matching stale stamps. 690 """ 691 692 def __init__(self, tasks): 693 Event.__init__(self) 694 self.tasks = tasks 695 696class FilesMatchingFound(Event): 697 """ 698 Event when a list of files matching the supplied pattern has 699 been generated 700 """ 701 def __init__(self, pattern, matches): 702 Event.__init__(self) 703 self._pattern = pattern 704 self._matches = matches 705 706class ConfigFilesFound(Event): 707 """ 708 Event when a list of appropriate config files has been generated 709 """ 710 def __init__(self, variable, values): 711 Event.__init__(self) 712 self._variable = variable 713 self._values = values 714 715class ConfigFilePathFound(Event): 716 """ 717 Event when a path for a config file has been found 718 """ 719 def __init__(self, path): 720 Event.__init__(self) 721 self._path = path 722 723class MsgBase(Event): 724 """Base class for messages""" 725 726 def __init__(self, msg): 727 self._message = msg 728 Event.__init__(self) 729 730class MsgDebug(MsgBase): 731 """Debug Message""" 732 733class MsgNote(MsgBase): 734 """Note Message""" 735 736class MsgWarn(MsgBase): 737 """Warning Message""" 738 739class MsgError(MsgBase): 740 """Error Message""" 741 742class MsgFatal(MsgBase): 743 """Fatal Message""" 744 745class MsgPlain(MsgBase): 746 """General output""" 747 748class LogExecTTY(Event): 749 """Send event containing program to spawn on tty of the logger""" 750 def __init__(self, msg, prog, sleep_delay, retries): 751 Event.__init__(self) 752 self.msg = msg 753 self.prog = prog 754 self.sleep_delay = sleep_delay 755 self.retries = retries 756 757class LogHandler(logging.Handler): 758 """Dispatch logging messages as bitbake events""" 759 760 def emit(self, record): 761 if record.exc_info: 762 etype, value, tb = record.exc_info 763 if hasattr(tb, 'tb_next'): 764 tb = list(bb.exceptions.extract_traceback(tb, context=3)) 765 # Need to turn the value into something the logging system can pickle 766 record.bb_exc_info = (etype, value, tb) 767 record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) 768 value = str(value) 769 record.exc_info = None 770 fire(record, None) 771 772 def filter(self, record): 773 record.taskpid = worker_pid 774 return True 775 776class MetadataEvent(Event): 777 """ 778 Generic event that target for OE-Core classes 779 to report information during asynchronous execution 780 """ 781 def __init__(self, eventtype, eventdata): 782 Event.__init__(self) 783 self.type = eventtype 784 self._localdata = eventdata 785 786class ProcessStarted(Event): 787 """ 788 Generic process started event (usually part of the initial startup) 789 where further progress events will be delivered 790 """ 791 def __init__(self, processname, total): 792 Event.__init__(self) 793 self.processname = processname 794 self.total = total 795 796class ProcessProgress(Event): 797 """ 798 Generic process progress event (usually part of the initial startup) 799 """ 800 def __init__(self, processname, progress): 801 Event.__init__(self) 802 self.processname = processname 803 self.progress = progress 804 805class ProcessFinished(Event): 806 """ 807 Generic process finished event (usually part of the initial startup) 808 """ 809 def __init__(self, processname): 810 Event.__init__(self) 811 self.processname = processname 812 813class SanityCheck(Event): 814 """ 815 Event to run sanity checks, either raise errors or generate events as return status. 816 """ 817 def __init__(self, generateevents = True): 818 Event.__init__(self) 819 self.generateevents = generateevents 820 821class SanityCheckPassed(Event): 822 """ 823 Event to indicate sanity check has passed 824 """ 825 826class SanityCheckFailed(Event): 827 """ 828 Event to indicate sanity check has failed 829 """ 830 def __init__(self, msg, network_error=False): 831 Event.__init__(self) 832 self._msg = msg 833 self._network_error = network_error 834 835class NetworkTest(Event): 836 """ 837 Event to run network connectivity tests, either raise errors or generate events as return status. 838 """ 839 def __init__(self, generateevents = True): 840 Event.__init__(self) 841 self.generateevents = generateevents 842 843class NetworkTestPassed(Event): 844 """ 845 Event to indicate network test has passed 846 """ 847 848class NetworkTestFailed(Event): 849 """ 850 Event to indicate network test has failed 851 """ 852 853class FindSigInfoResult(Event): 854 """ 855 Event to return results from findSigInfo command 856 """ 857 def __init__(self, result): 858 Event.__init__(self) 859 self.result = result 860 861class GetTaskSignatureResult(Event): 862 """ 863 Event to return results from GetTaskSignatures command 864 """ 865 def __init__(self, sig): 866 Event.__init__(self) 867 self.sig = sig 868 869class ParseError(Event): 870 """ 871 Event to indicate parse failed 872 """ 873 def __init__(self, msg): 874 super().__init__() 875 self._msg = msg 876