def handle_crashes(self, max_crashes=5, max_consecutive_crash_time=20): crash_count = 0 last_crash = 0 while not self.waitForAbort(1): # Initial check to avoid using the lock most of the time if self._daemon.daemon_poll() is None: continue with self._lock: if self._enabled and self._daemon.daemon_poll() is not None: logging.info("Deamon crashed") kodi.notification(kodi.translate(30105)) self._stop() crash_time = time.time() time_between_crashes = crash_time - last_crash if 0 < max_consecutive_crash_time < time_between_crashes: crash_count = 1 else: crash_count += 1 if last_crash > 0: logging.info("%s seconds passed since last crash", time_between_crashes) last_crash = crash_time if crash_count <= max_crashes: logging.info("Re-starting daemon - %s/%s", crash_count, max_crashes) self._start() self._wait(timeout=get_daemon_timeout(), notification=True)
def wait_for_metadata(info_hash): close_busy_dialog() percent = 0 timeout = get_metadata_timeout() start_time = time.time() monitor = Monitor() progress = DialogProgress() progress.create(ADDON_NAME, translate(30237)) try: while not api.torrent_status(info_hash).has_metadata: if monitor.waitForAbort(0.5): raise PlayError("Abort requested") passed_time = time.time() - start_time if 0 < timeout: if timeout < passed_time: notification(translate(30238)) raise PlayError("No metadata after timeout") percent = int(100 * passed_time / timeout) else: percent = 0 if percent == 100 else (percent + 5) progress.update(percent) if progress.iscanceled(): raise CanceledError("User canceled metadata", info_hash) finally: progress.close()
def play_info_hash(info_hash, buffer=True): if not api.torrent_status(info_hash).has_metadata: wait_for_metadata(info_hash) files = api.files(info_hash, status=False) min_candidate_size = get_min_candidate_size() * 1024 * 1024 candidate_files = [ f for f in files if is_video(f.path) and f.length >= min_candidate_size ] if not candidate_files: notification(translate(30239)) raise PlayError("No candidate files found for {}".format(info_hash)) elif len(candidate_files) == 1: chosen_file = candidate_files[0] else: sort_files(candidate_files) chosen_index = Dialog().select(translate(30240), [f.name for f in candidate_files]) if chosen_index < 0: raise PlayError("User canceled dialog select") chosen_file = candidate_files[chosen_index] if buffer: buffer_and_play(info_hash, chosen_file.id) else: play(info_hash, chosen_file.id)
def clear_entries(): entries = Entries() if entries.length() == 0: notification(translate(30010)) else: entries.clear() entries.save() update_repository() notification(translate(30011))
def import_entries(): path = str_to_unicode( xbmc.translatePath(xbmcgui.Dialog().browse(1, translate(30002), "files", ".json|.zip"))) if path: entries = Entries() entries.add_entries_from_file(path) entries.save() update_repository() notification(translate(30012))
def dialog_insert(): window = DialogInsert("DialogInsert.xml", ADDON_PATH, "Default") window.doModal() if window.type == DialogInsert.TYPE_PATH: api.add_torrent(window.ret_val, download=download_after_insert()) elif window.type == DialogInsert.TYPE_URL: api.add_magnet(window.ret_val, download=download_after_insert()) else: return notification(translate(30243), time=2000)
def _wait(self, timeout=-1, notification=False): start = time.time() while not 0 < timeout < time.time() - start: try: self._request("get", "") if notification: kodi.notification(kodi.translate(30104)) return except requests.exceptions.ConnectionError: if self.waitForAbort(0.5): raise AbortRequestedError("Abort requested") raise DaemonTimeoutError("Timeout reached")
def handle_crashes(self, max_crashes=5, max_consecutive_crash_time=20): crash_count = 0 last_crash = 0 while not self.waitForAbort(1): # Initial check to avoid using the lock most of the time if self._daemon.daemon_poll() is None: continue with self._lock: if self._enabled and self._daemon.daemon_poll() is not None: logging.warning("Deamon crashed") kodi.notification(kodi.translate(30105)) self._stop() if os.path.exists(self._log_path): path = os.path.join( kodi.ADDON_DATA, time.strftime("%Y%m%d_%H%M%S.") + self.log_name) shutil.copy(self._log_path, path) crash_time = time.time() time_between_crashes = crash_time - last_crash if 0 < max_consecutive_crash_time < time_between_crashes: crash_count = 1 else: crash_count += 1 if last_crash > 0: logging.info("%.2f seconds passed since last crash", time_between_crashes) last_crash = crash_time if crash_count <= max_crashes: logging.info("Re-starting daemon - %s/%s", crash_count, max_crashes) if crash_count > 1 and os.path.exists( self._settings_path): logging.info("Removing old settings file") os.remove(self._settings_path) self._start() try: self._wait(timeout=get_daemon_timeout(), notification=True) self._update_daemon_settings() except DaemonTimeoutError: logging.error("Timed out waiting for daemon") last_crash = time.time() else: logging.info("Max crashes (%d) reached", max_crashes)
def delete_entries(): entries = Entries() if entries.length() == 0: notification(translate(30010)) else: selected = xbmcgui.Dialog().multiselect(translate(30003), entries.ids) if selected: for index in selected: entries.remove(index) entries.save() update_repository() notification(translate(30011))
def play_info_hash(info_hash, timeout=30, buffer=True): start_time = time.time() monitor = Monitor() progress = DialogProgress() progress.create(ADDON_NAME, translate(30237)) try: while not api.torrent_status(info_hash).has_metadata: if monitor.waitForAbort(0.5): raise PlayError("Abort requested") passed_time = time.time() - start_time if 0 < timeout < passed_time: notification(translate(30238)) raise PlayError("No metadata after timeout") progress.update(int(100 * passed_time / timeout)) if progress.iscanceled(): raise PlayError("User canceled metadata") finally: progress.close() files = api.files(info_hash, status=False) min_candidate_size = get_min_candidate_size() * 1024 * 1024 candidate_files = [ f for f in files if is_video(f.path) and f.length >= min_candidate_size ] if not candidate_files: notification(translate(30239)) raise PlayError("No candidate files found for {}".format(info_hash)) elif len(candidate_files) == 1: chosen_file = candidate_files[0] else: sort_files(candidate_files) chosen_index = Dialog().select(translate(30240), [f.name for f in candidate_files]) if chosen_index < 0: raise PlayError("User canceled dialog select") chosen_file = candidate_files[chosen_index] if buffer: buffer_and_play(info_hash, chosen_file.id) else: play(info_hash, chosen_file.id)
def wait_for_buffering_completion(info_hash, file_id): close_busy_dialog() info = api.file_info(info_hash, file_id) of = translate(30244) timeout = get_buffering_timeout() last_time = last_done = 0 start_time = time.time() monitor = Monitor() progress = DialogProgress() progress.create(ADDON_NAME) try: while True: current_time = time.time() status = api.file_status(info_hash, file_id) if status.buffering_progress >= 100: break total_done = status.buffering_total * status.buffering_progress / 100 speed = float(total_done - last_done) / (current_time - last_time) last_time = current_time last_done = total_done progress.update( int(status.buffering_progress), "{} - {:.2f}%\n{} {} {} - {}/s\n{}\n".format( get_state_string(status.state), status.buffering_progress, sizeof_fmt(total_done), of, sizeof_fmt(status.buffering_total), sizeof_fmt(speed), info.name)) if progress.iscanceled(): raise CanceledError("User canceled buffering", info_hash) if 0 < timeout < current_time - start_time: notification(translate(30236)) raise PlayError("Buffering timeout reached") if monitor.waitForAbort(1): raise PlayError("Abort requested") finally: progress.close()
def buffer_and_play(info_hash, file_id): api.download_file(info_hash, file_id, buffer=True) monitor = Monitor() progress = DialogProgress() progress.create(ADDON_NAME) try: timeout = get_buffering_timeout() start_time = time.time() last_time = 0 last_done = 0 while True: current_time = time.time() status = api.file_status(info_hash, file_id) if status.buffering_progress >= 100: break total_done = status.buffering_total * status.buffering_progress / 100 speed = float(total_done - last_done) / (current_time - last_time) last_time = current_time last_done = total_done progress.update( int(status.buffering_progress), "{} - {:.2f}%\n{} {} {} - {}/s\n\n".format( get_state_string(status.state), status.buffering_progress, sizeof_fmt(total_done), translate(30244), sizeof_fmt(status.buffering_total), sizeof_fmt(speed))) if progress.iscanceled(): raise PlayError("User canceled buffering") if 0 < timeout < current_time - start_time: notification(translate(30236)) raise PlayError("Buffering timeout reached") if monitor.waitForAbort(1): raise PlayError("Abort requested") finally: progress.close() play(info_hash, file_id)
def install_app(APP_PARAMS): import traceback import requests import xbmc import time from lib import kodi try: user_params = {} apk_OK = False ANDROID_STORAGE = os.getenv('ANDROID_STORAGE') if not ANDROID_STORAGE: ANDROID_STORAGE = '/storage' LOCAL_DOWNLOAD_PATH = os.path.join(ANDROID_STORAGE, 'emulated', '0', 'Download') USER_APP_PATH = os.path.join(ANDROID_STORAGE, 'emulated', '0', 'Android', 'data') for user_addon, user_params in list(APP_PARAMS.items()): if not user_params['ACTIVE']: continue for apk_path in user_params['USER_APK']: if apk_path.endswith('.apk'): download_path = LOCAL_DOWNLOAD_PATH elif user_params['USER_ADDON_STATUS']: download_path = user_params['USER_ADDON_USERDATA'] else: continue if apk_path.startswith('http'): try: apk_body = requests.get(apk_path, timeout=10) except Exception as e: apk_body = requests.Response() apk_body.status_code = str(e) if apk_body.status_code != 200: logging.info("## Install_app: Invalid app requests response: %s", apk_body.status_code) apk_OK = False continue with open(os.path.join(download_path, \ os.path.basename(apk_path)), "wb") as f: f.write(apk_body.content) apk_OK = True else: if os.path.exists(apk_path): shutil.copy(apk_path, download_path) apk_OK = True else: continue if not apk_OK: break if apk_OK: logging.info("## Install_app: Installing the APK from: %s", LOCAL_DOWNLOAD_PATH) kodi.notification('Install your Assistant %s from folder %s' % \ (os.path.basename(user_params['USER_APK'][0]), \ LOCAL_DOWNLOAD_PATH)) cmd_android = 'StartAndroidActivity("%s", "", "%s", "%s")' % (user_params['USER_APP'], 'open', 'about:blank') cmd_android_permissions = 'StartAndroidActivity("%s", "", "%s", "%s")' % (user_params['USER_APP'], 'checkPermissions', 'about:blank') cmd_android_close = 'StartAndroidActivity("%s", "", "%s", "%s")' % (user_params['USER_APP'], 'terminate', 'about:blank') xbmc.executebuiltin(cmd_android) time.sleep(1) # Lets give the user 5 minutes to install the app an retry automatically for x in range(300): if os.path.exists(os.path.join(USER_APP_PATH, user_params['USER_APP'])): logging.info("## Install_app: APP installed: %s", user_params['USER_APP']) kodi.notification('Accept Assistant permissions') logging.info("## Install_app: Requesting permissions: %s", user_params['USER_APP']) time.sleep(5) xbmc.executebuiltin(cmd_android_permissions) time.sleep(15) logging.info("## Install_app: closing APP: %s", user_params['USER_APP']) kodi.notification('Accept Assistant permissions') xbmc.executebuiltin(cmd_android_close) logging.info("## Install_app: APP closed: %s", user_params['USER_APP']) time.sleep(10) return user_params xbmc.executebuiltin(cmd_android) time.sleep(1) break except: logging.info(traceback.format_exc()) user_params = {} return user_params
def torrent_status(info_hash): status = api.torrent_status(info_hash) notification("{:s} ({:.2f}%)".format(get_state_string(status.state), status.progress), api.torrent_info(info_hash).name, sound=False)
def update_repository(notify=False): get_request("http://127.0.0.1:{}/update".format(get_repository_port()), timeout=2) if notify: notification(translate(30013))
def run(): try: plugin.run() except Exception as e: logging.error("Caught exception:", exc_info=True) notification(str(e))
def binary_stat(p, action, retry=False, init=False, app_response={}): if init: logging.info('## Binary_stat: action: %s; PID: %s; retry: %s; init: %s; awake: %s; app_r: %s', \ action, p.pid, retry, init, p.binary_awake, app_response) import traceback import base64 import requests import json import time import xbmc try: if action in ['poll', 'communicate']: url = p.url_app + '/getBinaryStatus?pid=%s&flushAfterRead=true' % str(p.pid) url_alt = p.url_app_alt + '/getBinaryStatus?pid=%s&flushAfterRead=true' % str(p.pid) if action == 'killBinary': url = p.url_app + '/killBinary?pid=%s' % str(p.pid) url_alt = p.url_app_alt + '/killBinary?pid=%s' % str(p.pid) url_close = p.url_app + '/terminate' cmd_android = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'open', 'about:blank') cmd_android_quit = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'quit', 'about:blank') cmd_android_close = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'terminate', 'about:blank') cmd_android_permissions = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'checkPermissions', 'about:blank') finished = False retry_req = False retry_app = False stdout_acum = '' stderr_acum = '' msg = '' binary_awake = 0 binary_awake_safe = 300*1000 while not finished: if not app_response.get('retCode', 0) >= 999: try: resp = p.sess.get(url, timeout=5) except Exception as e: resp = requests.Response() resp.status_code = str(e) if resp.status_code != 200 and not retry_req: if action == 'killBinary' or p.monitor.abortRequested(): app_response = {'pid': p.pid, 'retCode': 998} else: logging.info("## Binary_stat: Invalid app requests response for PID: %s: %s - retry: %s - awake: %s", \ p.pid, resp.status_code, retry_req, p.binary_awake) retry_req = True url = url_alt msg += str(resp.status_code) stdout_acum += str(resp.status_code) xbmc.executebuiltin(cmd_android) binary_awake = (int(time.time()) - int(p.binary_time)) * 1000 - binary_awake_safe if binary_awake < binary_awake_safe: binary_awake = 0 logging.info('## Time.awake: %s; binary_awake: %s; p.binary_awake: %s', \ (int(time.time()) - int(p.binary_time))*1000, binary_awake, p.binary_awake) time.sleep(5) continue if resp.status_code != 200 and retry_req and app_response.get('retCode', 0) != 999: logging.info("## Binary_stat: Invalid app requests response for PID: %s: %s - retry: %s - awake: %s. Closing Assistant", \ p.pid, resp.status_code, retry_req, p.binary_awake) msg += str(resp.status_code) stdout_acum += str(resp.status_code) app_response = {'pid': p.pid, 'retCode': 999} xbmc.executebuiltin(cmd_android_close) time.sleep(5) xbmc.executebuiltin(cmd_android) binary_awake = (int(time.time()) - int(p.binary_time)) * 1000 - binary_awake_safe if binary_awake > binary_awake_safe: if p.binary_awake: if binary_awake < p.binary_awake: p.binary_awake = binary_awake else: p.binary_awake = binary_awake time.sleep(5) logging.info('## Time.awake: %s; binary_awake: %s; p.binary_awake: %s; awakingInterval: True', \ (int(time.time()) - int(p.binary_time))*1000, binary_awake, p.binary_awake) try: if not 'awakingInterval' in url: url += '&awakingInterval=%s' % p.binary_awake resp = p.sess.get(url, timeout=5) except: pass time.sleep(1) continue time.sleep(5) continue if resp.status_code == 200: try: app_response = resp.content if init: logging.debug(app_response) if PY3 and isinstance(app_response, bytes): app_response = app_response.decode() app_response_save = app_response app_response = re.sub('\n|\r|\t', '', app_response) app_response = json.loads(app_response) test_json = app_response["pid"] except: status_code = resp.content logging.info("## Binary_stat: Invalid app response for PID: %s: %s - retry: %s - awake: %s", \ p.pid, resp.content, retry_app, binary_awake) if retry_app: app_response = {'pid': p.pid} app_response['retCode'] = 999 msg += app_response_save stdout_acum += app_response_save else: retry_app = True app_response = {} if not 'awakingInterval' in url and (binary_awake > 0 or p.binary_awake > 0): if p.binary_awake: if binary_awake and binary_awake < p.binary_awake: p.binary_awake = binary_awake else: p.binary_awake = binary_awake url += '&awakingInterval=%s' % p.binary_awake logging.info('## Time.awake: %s; binary_awake: %s; p.binary_awake: %s; awakingInterval: True', \ (int(time.time()) - int(p.binary_time))*1000, binary_awake, p.binary_awake) time.sleep(1) continue if app_response.get("pid", 0): if app_response.get('output'): stdout_acum += base64.b64decode(app_response['output']).decode('utf-8') msg += base64.b64decode(app_response['output']).decode('utf-8') if app_response.get('error'): stderr_acum += base64.b64decode(app_response['error']).decode('utf-8') msg += base64.b64decode(app_response['error']).decode('utf-8') if app_response.get('startDate'): p.startDate = base64.b64decode(app_response['startDate']).decode('utf-8') if app_response.get('endDate'): p.endDate = base64.b64decode(app_response['endDate']).decode('utf-8') if app_response.get('cmd'): p.cmd_app = base64.b64decode(app_response['cmd']).decode('utf-8') if app_response.get('finalCmd'): p.finalCmd = base64.b64decode(app_response['finalCmd']).decode('utf-8') # If still app permissions not allowed, give it a retry if 'permission denied' in msg: from lib import kodi kodi.notification('Accept Assitant permissions', time=15000) time.sleep(5) xbmc.executebuiltin(cmd_android_permissions) time.sleep(10) xbmc.executebuiltin(cmd_android_quit) time.sleep(3) if msg: try: for line in msg.split('\n'): line += '\n' if PY3 and not isinstance(line, (bytes, bytearray)): line = line.encode('utf-8') p.stdin.write(line) p.stdin.flush() except: pass p.returncode = None if action == 'killBinary' and not app_response.get('retCode', ''): app_response['retCode'] = 137 if app_response.get('retCode', '') or action == 'killBinary' or \ (action == 'communicate' and app_response.get('retCode', '') != ''): try: p.stdin.flush() p.stdin.close() except: pass try: p.returncode = int(app_response['retCode']) except: p.returncode = app_response['retCode'] if action == 'communicate' and p.returncode is not None: logging.info("## Binary_stat: communicate Torrest: %s - Returncode: %s", p.pid, p.returncode) return stdout_acum, stderr_acum elif action == 'poll': if init and msg: logging.debug('## Binary_stat: Torrest initial response: %s', msg) return True return p.returncode elif action == 'killBinary': logging.info("## Binary_stat: killBinary Torrest: %s - Returncode: %s", p.pid, p.returncode) try: if p.monitor.abortRequested(): try: resp_t = p.sess.get(url_close, timeout=1) except: pass elif p.returncode == 998: xbmc.executebuiltin(cmd_android_close) time.sleep(5) except: logging.info(traceback.format_exc()) time.sleep(1) xbmc.executebuiltin(cmd_android_close) time.sleep(2) return p time.sleep(5) msg = '' app_response = {} except: logging.info(traceback.format_exc()) return None