|  |  |  | @ -35,26 +35,42 @@ OVERLAY_INIT = Path(os.path.join(BASEDIR, ".overlay_init")) | 
			
		
	
		
			
				
					|  |  |  |  | DAYS_NO_CONNECTIVITY_MAX = 14     # do not allow to engage after this many days | 
			
		
	
		
			
				
					|  |  |  |  | DAYS_NO_CONNECTIVITY_PROMPT = 10  # send an offroad prompt after this many days | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | class UserRequest: | 
			
		
	
		
			
				
					|  |  |  |  |   NONE = 0 | 
			
		
	
		
			
				
					|  |  |  |  |   CHECK = 1 | 
			
		
	
		
			
				
					|  |  |  |  |   FETCH = 2 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | class WaitTimeHelper: | 
			
		
	
		
			
				
					|  |  |  |  |   def __init__(self): | 
			
		
	
		
			
				
					|  |  |  |  |     self.ready_event = threading.Event() | 
			
		
	
		
			
				
					|  |  |  |  |     self.only_check_for_update = False | 
			
		
	
		
			
				
					|  |  |  |  |     self.user_request = UserRequest.NONE | 
			
		
	
		
			
				
					|  |  |  |  |     signal.signal(signal.SIGHUP, self.update_now) | 
			
		
	
		
			
				
					|  |  |  |  |     signal.signal(signal.SIGUSR1, self.check_now) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def update_now(self, signum: int, frame) -> None: | 
			
		
	
		
			
				
					|  |  |  |  |     cloudlog.info("caught SIGHUP, attempting to downloading update") | 
			
		
	
		
			
				
					|  |  |  |  |     self.only_check_for_update = False | 
			
		
	
		
			
				
					|  |  |  |  |     self.user_request = UserRequest.FETCH | 
			
		
	
		
			
				
					|  |  |  |  |     self.ready_event.set() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def check_now(self, signum: int, frame) -> None: | 
			
		
	
		
			
				
					|  |  |  |  |     cloudlog.info("caught SIGUSR1, checking for updates") | 
			
		
	
		
			
				
					|  |  |  |  |     self.only_check_for_update = True | 
			
		
	
		
			
				
					|  |  |  |  |     self.user_request = UserRequest.CHECK | 
			
		
	
		
			
				
					|  |  |  |  |     self.ready_event.set() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def sleep(self, t: float) -> None: | 
			
		
	
		
			
				
					|  |  |  |  |     self.ready_event.wait(timeout=t) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def write_time_to_param(params, param) -> None: | 
			
		
	
		
			
				
					|  |  |  |  |   t = datetime.datetime.utcnow() | 
			
		
	
		
			
				
					|  |  |  |  |   params.put(param, t.isoformat().encode('utf8')) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def read_time_from_param(params, param) -> Optional[datetime.datetime]: | 
			
		
	
		
			
				
					|  |  |  |  |   t = params.get(param, encoding='utf8') | 
			
		
	
		
			
				
					|  |  |  |  |   try: | 
			
		
	
		
			
				
					|  |  |  |  |     return datetime.datetime.fromisoformat(t) | 
			
		
	
		
			
				
					|  |  |  |  |   except (TypeError, ValueError): | 
			
		
	
		
			
				
					|  |  |  |  |     pass | 
			
		
	
		
			
				
					|  |  |  |  |   return None | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def run(cmd: List[str], cwd: Optional[str] = None) -> str: | 
			
		
	
		
			
				
					|  |  |  |  |   return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') | 
			
		
	
	
		
			
				
					|  |  |  | @ -266,14 +282,11 @@ class Updater: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     last_update = datetime.datetime.utcnow() | 
			
		
	
		
			
				
					|  |  |  |  |     if update_success: | 
			
		
	
		
			
				
					|  |  |  |  |       t = last_update.isoformat() | 
			
		
	
		
			
				
					|  |  |  |  |       self.params.put("LastUpdateTime", t.encode('utf8')) | 
			
		
	
		
			
				
					|  |  |  |  |       write_time_to_param(self.params, "LastUpdateTime") | 
			
		
	
		
			
				
					|  |  |  |  |     else: | 
			
		
	
		
			
				
					|  |  |  |  |       try: | 
			
		
	
		
			
				
					|  |  |  |  |         t = self.params.get("LastUpdateTime", encoding='utf8') | 
			
		
	
		
			
				
					|  |  |  |  |         last_update = datetime.datetime.fromisoformat(t) | 
			
		
	
		
			
				
					|  |  |  |  |       except (TypeError, ValueError): | 
			
		
	
		
			
				
					|  |  |  |  |         pass | 
			
		
	
		
			
				
					|  |  |  |  |       t = read_time_from_param(self.params, "LastUpdateTime") | 
			
		
	
		
			
				
					|  |  |  |  |       if t is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         last_update = t | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     if exception is None: | 
			
		
	
		
			
				
					|  |  |  |  |       self.params.remove("LastUpdateException") | 
			
		
	
	
		
			
				
					|  |  |  | @ -421,10 +434,7 @@ def main() -> None: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   updater = Updater() | 
			
		
	
		
			
				
					|  |  |  |  |   update_failed_count = 0 # TODO: Load from param? | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   # no fetch on the first time | 
			
		
	
		
			
				
					|  |  |  |  |   wait_helper = WaitTimeHelper() | 
			
		
	
		
			
				
					|  |  |  |  |   wait_helper.only_check_for_update = True | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   # invalidate old finalized update | 
			
		
	
		
			
				
					|  |  |  |  |   set_consistent_flag(False) | 
			
		
	
	
		
			
				
					|  |  |  | @ -458,10 +468,16 @@ def main() -> None: | 
			
		
	
		
			
				
					|  |  |  |  |       updater.check_for_update() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |       # download update | 
			
		
	
		
			
				
					|  |  |  |  |       if wait_helper.only_check_for_update: | 
			
		
	
		
			
				
					|  |  |  |  |         cloudlog.info("skipping fetch this cycle") | 
			
		
	
		
			
				
					|  |  |  |  |       last_fetch = read_time_from_param(params, "UpdaterLastFetchTime") | 
			
		
	
		
			
				
					|  |  |  |  |       timed_out = last_fetch is None or (datetime.datetime.utcnow() - last_fetch > datetime.timedelta(days=3)) | 
			
		
	
		
			
				
					|  |  |  |  |       user_requested_fetch = wait_helper.user_request == UserRequest.FETCH | 
			
		
	
		
			
				
					|  |  |  |  |       if params.get_bool("NetworkMetered") and not timed_out and not user_requested_fetch: | 
			
		
	
		
			
				
					|  |  |  |  |         cloudlog.info("skipping fetch, connection metered") | 
			
		
	
		
			
				
					|  |  |  |  |       elif wait_helper.user_request == UserRequest.CHECK: | 
			
		
	
		
			
				
					|  |  |  |  |         cloudlog.info("skipping fetch, only checking") | 
			
		
	
		
			
				
					|  |  |  |  |       else: | 
			
		
	
		
			
				
					|  |  |  |  |         updater.fetch_update() | 
			
		
	
		
			
				
					|  |  |  |  |         write_time_to_param(params, "UpdaterLastFetchTime") | 
			
		
	
		
			
				
					|  |  |  |  |       update_failed_count = 0 | 
			
		
	
		
			
				
					|  |  |  |  |     except subprocess.CalledProcessError as e: | 
			
		
	
		
			
				
					|  |  |  |  |       cloudlog.event( | 
			
		
	
	
		
			
				
					|  |  |  | @ -485,7 +501,7 @@ def main() -> None: | 
			
		
	
		
			
				
					|  |  |  |  |       cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     # infrequent attempts if we successfully updated recently | 
			
		
	
		
			
				
					|  |  |  |  |     wait_helper.only_check_for_update = False | 
			
		
	
		
			
				
					|  |  |  |  |     wait_helper.user_request = UserRequest.NONE | 
			
		
	
		
			
				
					|  |  |  |  |     wait_helper.sleep(5*60 if update_failed_count > 0 else 1.5*60*60) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | 
 |