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