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