diff options
Diffstat (limited to 'bitbake/lib/bb/tinfoil.py')
| -rw-r--r-- | bitbake/lib/bb/tinfoil.py | 215 |
1 files changed, 195 insertions, 20 deletions
diff --git a/bitbake/lib/bb/tinfoil.py b/bitbake/lib/bb/tinfoil.py index 763c329810..e7fbcbca0a 100644 --- a/bitbake/lib/bb/tinfoil.py +++ b/bitbake/lib/bb/tinfoil.py | |||
| @@ -10,10 +10,12 @@ | |||
| 10 | import logging | 10 | import logging |
| 11 | import os | 11 | import os |
| 12 | import sys | 12 | import sys |
| 13 | import time | ||
| 13 | import atexit | 14 | import atexit |
| 14 | import re | 15 | import re |
| 15 | from collections import OrderedDict, defaultdict | 16 | from collections import OrderedDict, defaultdict |
| 16 | from functools import partial | 17 | from functools import partial, wraps |
| 18 | from contextlib import contextmanager | ||
| 17 | 19 | ||
| 18 | import bb.cache | 20 | import bb.cache |
| 19 | import bb.cooker | 21 | import bb.cooker |
| @@ -25,6 +27,135 @@ import bb.remotedata | |||
| 25 | from bb.main import setup_bitbake, BitBakeConfigParameters | 27 | from bb.main import setup_bitbake, BitBakeConfigParameters |
| 26 | import bb.fetch2 | 28 | import bb.fetch2 |
| 27 | 29 | ||
| 30 | def wait_for(f): | ||
| 31 | """ | ||
| 32 | Wrap a function that makes an asynchronous tinfoil call using | ||
| 33 | self.run_command() and wait for events to say that the call has been | ||
| 34 | successful, or an error has occurred. | ||
| 35 | """ | ||
| 36 | @wraps(f) | ||
| 37 | def wrapper(self, *args, handle_events=True, extra_events=None, event_callback=None, **kwargs): | ||
| 38 | if handle_events: | ||
| 39 | # A reasonable set of default events matching up with those we handle below | ||
| 40 | eventmask = [ | ||
| 41 | 'bb.event.BuildStarted', | ||
| 42 | 'bb.event.BuildCompleted', | ||
| 43 | 'logging.LogRecord', | ||
| 44 | 'bb.event.NoProvider', | ||
| 45 | 'bb.command.CommandCompleted', | ||
| 46 | 'bb.command.CommandFailed', | ||
| 47 | 'bb.build.TaskStarted', | ||
| 48 | 'bb.build.TaskFailed', | ||
| 49 | 'bb.build.TaskSucceeded', | ||
| 50 | 'bb.build.TaskFailedSilent', | ||
| 51 | 'bb.build.TaskProgress', | ||
| 52 | 'bb.runqueue.runQueueTaskStarted', | ||
| 53 | 'bb.runqueue.sceneQueueTaskStarted', | ||
| 54 | 'bb.event.ProcessStarted', | ||
| 55 | 'bb.event.ProcessProgress', | ||
| 56 | 'bb.event.ProcessFinished', | ||
| 57 | ] | ||
| 58 | if extra_events: | ||
| 59 | eventmask.extend(extra_events) | ||
| 60 | ret = self.set_event_mask(eventmask) | ||
| 61 | |||
| 62 | includelogs = self.config_data.getVar('BBINCLUDELOGS') | ||
| 63 | loglines = self.config_data.getVar('BBINCLUDELOGS_LINES') | ||
| 64 | |||
| 65 | # Call actual function | ||
| 66 | ret = f(self, *args, **kwargs) | ||
| 67 | |||
| 68 | if handle_events: | ||
| 69 | lastevent = time.time() | ||
| 70 | result = False | ||
| 71 | # Borrowed from knotty, instead somewhat hackily we use the helper | ||
| 72 | # as the object to store "shutdown" on | ||
| 73 | helper = bb.ui.uihelper.BBUIHelper() | ||
| 74 | helper.shutdown = 0 | ||
| 75 | parseprogress = None | ||
| 76 | termfilter = bb.ui.knotty.TerminalFilter(helper, helper, self.logger.handlers, quiet=self.quiet) | ||
| 77 | try: | ||
| 78 | while True: | ||
| 79 | try: | ||
| 80 | event = self.wait_event(0.25) | ||
| 81 | if event: | ||
| 82 | lastevent = time.time() | ||
| 83 | if event_callback and event_callback(event): | ||
| 84 | continue | ||
| 85 | if helper.eventHandler(event): | ||
| 86 | if isinstance(event, bb.build.TaskFailedSilent): | ||
| 87 | self.logger.warning("Logfile for failed setscene task is %s" % event.logfile) | ||
| 88 | elif isinstance(event, bb.build.TaskFailed): | ||
| 89 | bb.ui.knotty.print_event_log(event, includelogs, loglines, termfilter) | ||
| 90 | continue | ||
| 91 | if isinstance(event, bb.event.ProcessStarted): | ||
| 92 | if self.quiet > 1: | ||
| 93 | continue | ||
| 94 | parseprogress = bb.ui.knotty.new_progress(event.processname, event.total) | ||
| 95 | parseprogress.start(False) | ||
| 96 | continue | ||
| 97 | if isinstance(event, bb.event.ProcessProgress): | ||
| 98 | if self.quiet > 1: | ||
| 99 | continue | ||
| 100 | if parseprogress: | ||
| 101 | parseprogress.update(event.progress) | ||
| 102 | else: | ||
| 103 | bb.warn("Got ProcessProgress event for something that never started?") | ||
| 104 | continue | ||
| 105 | if isinstance(event, bb.event.ProcessFinished): | ||
| 106 | if self.quiet > 1: | ||
| 107 | continue | ||
| 108 | if parseprogress: | ||
| 109 | parseprogress.finish() | ||
| 110 | parseprogress = None | ||
| 111 | continue | ||
| 112 | if isinstance(event, bb.command.CommandCompleted): | ||
| 113 | result = True | ||
| 114 | break | ||
| 115 | if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit)): | ||
| 116 | self.logger.error(str(event)) | ||
| 117 | result = False | ||
| 118 | break | ||
| 119 | if isinstance(event, logging.LogRecord): | ||
| 120 | if event.taskpid == 0 or event.levelno > logging.INFO: | ||
| 121 | self.logger.handle(event) | ||
| 122 | continue | ||
| 123 | if isinstance(event, bb.event.NoProvider): | ||
| 124 | self.logger.error(str(event)) | ||
| 125 | result = False | ||
| 126 | break | ||
| 127 | elif helper.shutdown > 1: | ||
| 128 | break | ||
| 129 | termfilter.updateFooter() | ||
| 130 | if time.time() > (lastevent + (3*60)): | ||
| 131 | if not self.run_command('ping', handle_events=False): | ||
| 132 | print("\nUnable to ping server and no events, closing down...\n") | ||
| 133 | return False | ||
| 134 | except KeyboardInterrupt: | ||
| 135 | termfilter.clearFooter() | ||
| 136 | if helper.shutdown == 1: | ||
| 137 | print("\nSecond Keyboard Interrupt, stopping...\n") | ||
| 138 | ret = self.run_command("stateForceShutdown") | ||
| 139 | if ret and ret[2]: | ||
| 140 | self.logger.error("Unable to cleanly stop: %s" % ret[2]) | ||
| 141 | elif helper.shutdown == 0: | ||
| 142 | print("\nKeyboard Interrupt, closing down...\n") | ||
| 143 | interrupted = True | ||
| 144 | ret = self.run_command("stateShutdown") | ||
| 145 | if ret and ret[2]: | ||
| 146 | self.logger.error("Unable to cleanly shutdown: %s" % ret[2]) | ||
| 147 | helper.shutdown = helper.shutdown + 1 | ||
| 148 | termfilter.clearFooter() | ||
| 149 | finally: | ||
| 150 | termfilter.finish() | ||
| 151 | if helper.failed_tasks: | ||
| 152 | result = False | ||
| 153 | return result | ||
| 154 | else: | ||
| 155 | return ret | ||
| 156 | |||
| 157 | return wrapper | ||
| 158 | |||
| 28 | 159 | ||
| 29 | # We need this in order to shut down the connection to the bitbake server, | 160 | # We need this in order to shut down the connection to the bitbake server, |
| 30 | # otherwise the process will never properly exit | 161 | # otherwise the process will never properly exit |
| @@ -52,6 +183,10 @@ class TinfoilDataStoreConnectorVarHistory: | |||
| 52 | def remoteCommand(self, cmd, *args, **kwargs): | 183 | def remoteCommand(self, cmd, *args, **kwargs): |
| 53 | return self.tinfoil.run_command('dataStoreConnectorVarHistCmd', self.dsindex, cmd, args, kwargs) | 184 | return self.tinfoil.run_command('dataStoreConnectorVarHistCmd', self.dsindex, cmd, args, kwargs) |
| 54 | 185 | ||
| 186 | def emit(self, var, oval, val, o, d): | ||
| 187 | ret = self.tinfoil.run_command('dataStoreConnectorVarHistCmdEmit', self.dsindex, var, oval, val, d.dsindex) | ||
| 188 | o.write(ret) | ||
| 189 | |||
| 55 | def __getattr__(self, name): | 190 | def __getattr__(self, name): |
| 56 | if not hasattr(bb.data_smart.VariableHistory, name): | 191 | if not hasattr(bb.data_smart.VariableHistory, name): |
| 57 | raise AttributeError("VariableHistory has no such method %s" % name) | 192 | raise AttributeError("VariableHistory has no such method %s" % name) |
| @@ -183,11 +318,19 @@ class TinfoilCookerAdapter: | |||
| 183 | self._cache[name] = attrvalue | 318 | self._cache[name] = attrvalue |
| 184 | return attrvalue | 319 | return attrvalue |
| 185 | 320 | ||
| 321 | class TinfoilSkiplistByMcAdapter: | ||
| 322 | def __init__(self, tinfoil): | ||
| 323 | self.tinfoil = tinfoil | ||
| 324 | |||
| 325 | def __getitem__(self, mc): | ||
| 326 | return self.tinfoil.get_skipped_recipes(mc) | ||
| 327 | |||
| 186 | def __init__(self, tinfoil): | 328 | def __init__(self, tinfoil): |
| 187 | self.tinfoil = tinfoil | 329 | self.tinfoil = tinfoil |
| 188 | self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split() | 330 | self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split() |
| 189 | self.collections = {} | 331 | self.collections = {} |
| 190 | self.recipecaches = {} | 332 | self.recipecaches = {} |
| 333 | self.skiplist_by_mc = self.TinfoilSkiplistByMcAdapter(tinfoil) | ||
| 191 | for mc in self.multiconfigs: | 334 | for mc in self.multiconfigs: |
| 192 | self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc) | 335 | self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc) |
| 193 | self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc) | 336 | self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc) |
| @@ -196,8 +339,6 @@ class TinfoilCookerAdapter: | |||
| 196 | # Grab these only when they are requested since they aren't always used | 339 | # Grab these only when they are requested since they aren't always used |
| 197 | if name in self._cache: | 340 | if name in self._cache: |
| 198 | return self._cache[name] | 341 | return self._cache[name] |
| 199 | elif name == 'skiplist': | ||
| 200 | attrvalue = self.tinfoil.get_skipped_recipes() | ||
| 201 | elif name == 'bbfile_config_priorities': | 342 | elif name == 'bbfile_config_priorities': |
| 202 | ret = self.tinfoil.run_command('getLayerPriorities') | 343 | ret = self.tinfoil.run_command('getLayerPriorities') |
| 203 | bbfile_config_priorities = [] | 344 | bbfile_config_priorities = [] |
| @@ -320,11 +461,11 @@ class Tinfoil: | |||
| 320 | self.recipes_parsed = False | 461 | self.recipes_parsed = False |
| 321 | self.quiet = 0 | 462 | self.quiet = 0 |
| 322 | self.oldhandlers = self.logger.handlers[:] | 463 | self.oldhandlers = self.logger.handlers[:] |
| 464 | self.localhandlers = [] | ||
| 323 | if setup_logging: | 465 | if setup_logging: |
| 324 | # This is the *client-side* logger, nothing to do with | 466 | # This is the *client-side* logger, nothing to do with |
| 325 | # logging messages from the server | 467 | # logging messages from the server |
| 326 | bb.msg.logger_create('BitBake', output) | 468 | bb.msg.logger_create('BitBake', output) |
| 327 | self.localhandlers = [] | ||
| 328 | for handler in self.logger.handlers: | 469 | for handler in self.logger.handlers: |
| 329 | if handler not in self.oldhandlers: | 470 | if handler not in self.oldhandlers: |
| 330 | self.localhandlers.append(handler) | 471 | self.localhandlers.append(handler) |
| @@ -440,11 +581,17 @@ class Tinfoil: | |||
| 440 | to initialise Tinfoil and use it with config_only=True first and | 581 | to initialise Tinfoil and use it with config_only=True first and |
| 441 | then conditionally call this function to parse recipes later. | 582 | then conditionally call this function to parse recipes later. |
| 442 | """ | 583 | """ |
| 443 | config_params = TinfoilConfigParameters(config_only=False) | 584 | config_params = TinfoilConfigParameters(config_only=False, quiet=self.quiet) |
| 444 | self.run_actions(config_params) | 585 | self.run_actions(config_params) |
| 445 | self.recipes_parsed = True | 586 | self.recipes_parsed = True |
| 446 | 587 | ||
| 447 | def run_command(self, command, *params): | 588 | def modified_files(self): |
| 589 | """ | ||
| 590 | Notify the server it needs to revalidate it's caches since the client has modified files | ||
| 591 | """ | ||
| 592 | self.run_command("revalidateCaches") | ||
| 593 | |||
| 594 | def run_command(self, command, *params, handle_events=True): | ||
| 448 | """ | 595 | """ |
| 449 | Run a command on the server (as implemented in bb.command). | 596 | Run a command on the server (as implemented in bb.command). |
| 450 | Note that there are two types of command - synchronous and | 597 | Note that there are two types of command - synchronous and |
| @@ -464,7 +611,7 @@ class Tinfoil: | |||
| 464 | try: | 611 | try: |
| 465 | result = self.server_connection.connection.runCommand(commandline) | 612 | result = self.server_connection.connection.runCommand(commandline) |
| 466 | finally: | 613 | finally: |
| 467 | while True: | 614 | while handle_events: |
| 468 | event = self.wait_event() | 615 | event = self.wait_event() |
| 469 | if not event: | 616 | if not event: |
| 470 | break | 617 | break |
| @@ -489,7 +636,7 @@ class Tinfoil: | |||
| 489 | Wait for an event from the server for the specified time. | 636 | Wait for an event from the server for the specified time. |
| 490 | A timeout of 0 means don't wait if there are no events in the queue. | 637 | A timeout of 0 means don't wait if there are no events in the queue. |
| 491 | Returns the next event in the queue or None if the timeout was | 638 | Returns the next event in the queue or None if the timeout was |
| 492 | reached. Note that in order to recieve any events you will | 639 | reached. Note that in order to receive any events you will |
| 493 | first need to set the internal event mask using set_event_mask() | 640 | first need to set the internal event mask using set_event_mask() |
| 494 | (otherwise whatever event mask the UI set up will be in effect). | 641 | (otherwise whatever event mask the UI set up will be in effect). |
| 495 | """ | 642 | """ |
| @@ -503,12 +650,12 @@ class Tinfoil: | |||
| 503 | """ | 650 | """ |
| 504 | return defaultdict(list, self.run_command('getOverlayedRecipes', mc)) | 651 | return defaultdict(list, self.run_command('getOverlayedRecipes', mc)) |
| 505 | 652 | ||
| 506 | def get_skipped_recipes(self): | 653 | def get_skipped_recipes(self, mc=''): |
| 507 | """ | 654 | """ |
| 508 | Find recipes which were skipped (i.e. SkipRecipe was raised | 655 | Find recipes which were skipped (i.e. SkipRecipe was raised |
| 509 | during parsing). | 656 | during parsing). |
| 510 | """ | 657 | """ |
| 511 | return OrderedDict(self.run_command('getSkippedRecipes')) | 658 | return OrderedDict(self.run_command('getSkippedRecipes', mc)) |
| 512 | 659 | ||
| 513 | def get_all_providers(self, mc=''): | 660 | def get_all_providers(self, mc=''): |
| 514 | return defaultdict(list, self.run_command('allProviders', mc)) | 661 | return defaultdict(list, self.run_command('allProviders', mc)) |
| @@ -522,6 +669,7 @@ class Tinfoil: | |||
| 522 | def get_runtime_providers(self, rdep): | 669 | def get_runtime_providers(self, rdep): |
| 523 | return self.run_command('getRuntimeProviders', rdep) | 670 | return self.run_command('getRuntimeProviders', rdep) |
| 524 | 671 | ||
| 672 | # TODO: teach this method about mc | ||
| 525 | def get_recipe_file(self, pn): | 673 | def get_recipe_file(self, pn): |
| 526 | """ | 674 | """ |
| 527 | Get the file name for the specified recipe/target. Raises | 675 | Get the file name for the specified recipe/target. Raises |
| @@ -530,6 +678,7 @@ class Tinfoil: | |||
| 530 | """ | 678 | """ |
| 531 | best = self.find_best_provider(pn) | 679 | best = self.find_best_provider(pn) |
| 532 | if not best or (len(best) > 3 and not best[3]): | 680 | if not best or (len(best) > 3 and not best[3]): |
| 681 | # TODO: pass down mc | ||
| 533 | skiplist = self.get_skipped_recipes() | 682 | skiplist = self.get_skipped_recipes() |
| 534 | taskdata = bb.taskdata.TaskData(None, skiplist=skiplist) | 683 | taskdata = bb.taskdata.TaskData(None, skiplist=skiplist) |
| 535 | skipreasons = taskdata.get_reasons(pn) | 684 | skipreasons = taskdata.get_reasons(pn) |
| @@ -622,6 +771,29 @@ class Tinfoil: | |||
| 622 | fn = self.get_recipe_file(pn) | 771 | fn = self.get_recipe_file(pn) |
| 623 | return self.parse_recipe_file(fn) | 772 | return self.parse_recipe_file(fn) |
| 624 | 773 | ||
| 774 | @contextmanager | ||
| 775 | def _data_tracked_if_enabled(self): | ||
| 776 | """ | ||
| 777 | A context manager to enable data tracking for a code segment if data | ||
| 778 | tracking was enabled for this tinfoil instance. | ||
| 779 | """ | ||
| 780 | if self.tracking: | ||
| 781 | # Enable history tracking just for the operation | ||
| 782 | self.run_command('enableDataTracking') | ||
| 783 | |||
| 784 | # Here goes the operation with the optional data tracking | ||
| 785 | yield | ||
| 786 | |||
| 787 | if self.tracking: | ||
| 788 | self.run_command('disableDataTracking') | ||
| 789 | |||
| 790 | def finalizeData(self): | ||
| 791 | """ | ||
| 792 | Run anonymous functions and expand keys | ||
| 793 | """ | ||
| 794 | with self._data_tracked_if_enabled(): | ||
| 795 | return self._reconvert_type(self.run_command('finalizeData'), 'DataStoreConnectionHandle') | ||
| 796 | |||
| 625 | def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None): | 797 | def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None): |
| 626 | """ | 798 | """ |
| 627 | Parse the specified recipe file (with or without bbappends) | 799 | Parse the specified recipe file (with or without bbappends) |
| @@ -634,10 +806,7 @@ class Tinfoil: | |||
| 634 | appendlist: optional list of bbappend files to apply, if you | 806 | appendlist: optional list of bbappend files to apply, if you |
| 635 | want to filter them | 807 | want to filter them |
| 636 | """ | 808 | """ |
| 637 | if self.tracking: | 809 | with self._data_tracked_if_enabled(): |
| 638 | # Enable history tracking just for the parse operation | ||
| 639 | self.run_command('enableDataTracking') | ||
| 640 | try: | ||
| 641 | if appends and appendlist == []: | 810 | if appends and appendlist == []: |
| 642 | appends = False | 811 | appends = False |
| 643 | if config_data: | 812 | if config_data: |
| @@ -649,9 +818,6 @@ class Tinfoil: | |||
| 649 | return self._reconvert_type(dscon, 'DataStoreConnectionHandle') | 818 | return self._reconvert_type(dscon, 'DataStoreConnectionHandle') |
| 650 | else: | 819 | else: |
| 651 | return None | 820 | return None |
| 652 | finally: | ||
| 653 | if self.tracking: | ||
| 654 | self.run_command('disableDataTracking') | ||
| 655 | 821 | ||
| 656 | def build_file(self, buildfile, task, internal=True): | 822 | def build_file(self, buildfile, task, internal=True): |
| 657 | """ | 823 | """ |
| @@ -663,6 +829,10 @@ class Tinfoil: | |||
| 663 | """ | 829 | """ |
| 664 | return self.run_command('buildFile', buildfile, task, internal) | 830 | return self.run_command('buildFile', buildfile, task, internal) |
| 665 | 831 | ||
| 832 | @wait_for | ||
| 833 | def build_file_sync(self, *args): | ||
| 834 | self.build_file(*args) | ||
| 835 | |||
| 666 | def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None): | 836 | def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None): |
| 667 | """ | 837 | """ |
| 668 | Builds the specified targets. This is equivalent to a normal invocation | 838 | Builds the specified targets. This is equivalent to a normal invocation |
| @@ -725,6 +895,7 @@ class Tinfoil: | |||
| 725 | 895 | ||
| 726 | ret = self.run_command('buildTargets', targets, task) | 896 | ret = self.run_command('buildTargets', targets, task) |
| 727 | if handle_events: | 897 | if handle_events: |
| 898 | lastevent = time.time() | ||
| 728 | result = False | 899 | result = False |
| 729 | # Borrowed from knotty, instead somewhat hackily we use the helper | 900 | # Borrowed from knotty, instead somewhat hackily we use the helper |
| 730 | # as the object to store "shutdown" on | 901 | # as the object to store "shutdown" on |
| @@ -737,6 +908,7 @@ class Tinfoil: | |||
| 737 | try: | 908 | try: |
| 738 | event = self.wait_event(0.25) | 909 | event = self.wait_event(0.25) |
| 739 | if event: | 910 | if event: |
| 911 | lastevent = time.time() | ||
| 740 | if event_callback and event_callback(event): | 912 | if event_callback and event_callback(event): |
| 741 | continue | 913 | continue |
| 742 | if helper.eventHandler(event): | 914 | if helper.eventHandler(event): |
| @@ -757,7 +929,7 @@ class Tinfoil: | |||
| 757 | if parseprogress: | 929 | if parseprogress: |
| 758 | parseprogress.update(event.progress) | 930 | parseprogress.update(event.progress) |
| 759 | else: | 931 | else: |
| 760 | bb.warn("Got ProcessProgress event for someting that never started?") | 932 | bb.warn("Got ProcessProgress event for something that never started?") |
| 761 | continue | 933 | continue |
| 762 | if isinstance(event, bb.event.ProcessFinished): | 934 | if isinstance(event, bb.event.ProcessFinished): |
| 763 | if self.quiet > 1: | 935 | if self.quiet > 1: |
| @@ -769,7 +941,7 @@ class Tinfoil: | |||
| 769 | if isinstance(event, bb.command.CommandCompleted): | 941 | if isinstance(event, bb.command.CommandCompleted): |
| 770 | result = True | 942 | result = True |
| 771 | break | 943 | break |
| 772 | if isinstance(event, bb.command.CommandFailed): | 944 | if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit)): |
| 773 | self.logger.error(str(event)) | 945 | self.logger.error(str(event)) |
| 774 | result = False | 946 | result = False |
| 775 | break | 947 | break |
| @@ -781,10 +953,13 @@ class Tinfoil: | |||
| 781 | self.logger.error(str(event)) | 953 | self.logger.error(str(event)) |
| 782 | result = False | 954 | result = False |
| 783 | break | 955 | break |
| 784 | |||
| 785 | elif helper.shutdown > 1: | 956 | elif helper.shutdown > 1: |
| 786 | break | 957 | break |
| 787 | termfilter.updateFooter() | 958 | termfilter.updateFooter() |
| 959 | if time.time() > (lastevent + (3*60)): | ||
| 960 | if not self.run_command('ping', handle_events=False): | ||
| 961 | print("\nUnable to ping server and no events, closing down...\n") | ||
| 962 | return False | ||
| 788 | except KeyboardInterrupt: | 963 | except KeyboardInterrupt: |
| 789 | termfilter.clearFooter() | 964 | termfilter.clearFooter() |
| 790 | if helper.shutdown == 1: | 965 | if helper.shutdown == 1: |
