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 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 torrents(): for torrent in api.torrents(): torrent_li = list_item(torrent.name, "download.png") torrent_li.addContextMenuItems([ (translate(30235), media(play_info_hash, info_hash=torrent.info_hash)), (translate(30208), action(torrent_action, torrent.info_hash, "stop")) if torrent.status.total == torrent.status.total_wanted else (translate(30209), action(torrent_action, torrent.info_hash, "download")), (translate(30210), action(torrent_action, torrent.info_hash, "resume")) if torrent.status.paused else (translate(30211), action(torrent_action, torrent.info_hash, "pause")), (translate(30242), action(torrent_action, torrent.info_hash, "remove_torrent")), (translate(30212), action(torrent_action, torrent.info_hash, "remove_torrent_and_files")), (translate(30245), action(torrent_action, torrent.info_hash, "torrent_status")) ]) addDirectoryItem(plugin.handle, plugin.url_for(torrent_files, torrent.info_hash), torrent_li, isFolder=True)
def _set_type(self, t): self.getControl(self._radio_button_1_id).setSelected(t == self.TYPE_URL) # Radio Button 1 self.getControl(self._radio_button_2_id).setSelected(t == self.TYPE_PATH) # Radio Button 2 self.getControl(self._label_id).setLabel(translate(30203 if t == self.TYPE_URL else 30204)) # Box label self._type = t label = self._ret_val[t] self.getControl(self._input_button_id).setLabel(label if label else " ")
def handle_player_stop(info_hash, name=None, initial_delay=0.5, listing_timeout=10): if not ask_to_delete_torrent(): return try: info = api.torrent_info(info_hash) except TorrestError: return if name is None: name = info.name sleep(int(initial_delay * 1000)) start_time = time.time() while getCondVisibility( "Window.IsActive(busydialog)" ) and not 0 < listing_timeout < time.time() - start_time: sleep(100) remove_torrent = Dialog().yesno(ADDON_NAME, name + "\n" + translate(30241)) if remove_torrent: api.remove_torrent(info_hash, delete=True) current_folder = getInfoLabel("Container.FolderPath") if current_folder == plugin.url_for(torrent_files, info_hash): executebuiltin("Action(Back)") elif current_folder == plugin.url_for(torrents): refresh()
def _update_progress(self): torrents = [ t for t in self._api.torrents() if t.status.state not in (STATUS_FINISHED, STATUS_SEEDING, STATUS_PAUSED) ] torrents_count = len(torrents) if torrents_count > 0: if self._index >= torrents_count: download_rate = sum(t.status.download_rate for t in torrents) upload_rate = sum(t.status.upload_rate for t in torrents) progress = sum(t.status.progress for t in torrents) / torrents_count name = kodi.translate(30106) self._index = 0 else: download_rate = torrents[self._index].status.download_rate upload_rate = torrents[self._index].status.upload_rate progress = torrents[self._index].status.progress name = torrents[self._index].name if len(name) > 30: name = name[:30] + "..." self._index += 1 message = "{} - D:{}/s U:{}/s".format(name, sizeof_fmt(download_rate), sizeof_fmt(upload_rate)) self._get_dialog().update(int(progress), kodi.ADDON_NAME, message) else: self._close_dialog()
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 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 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(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 run(): kodi.set_logger() handle_first_run() try: with DaemonMonitor() as monitor: monitor.handle_crashes() except DaemonNotFoundError: logging.info("Daemon not found. Aborting service...") if service_enabled(): set_service_enabled(False) xbmcgui.Dialog().ok(kodi.ADDON_NAME, kodi.translate(30103)) kodi.open_settings()
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 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 torrent_files(info_hash): files = api.files(info_hash) sort_files(files) for f in files: serve_url = api.serve_url(info_hash, f.id) file_li = list_item(f.name, "download.png") file_li.setPath(serve_url) context_menu_items = [] info_labels = {"title": f.name} if is_picture(f.name): url = plugin.url_for(display_picture, info_hash, f.id) file_li.setInfo("pictures", info_labels) elif is_text(f.name): url = plugin.url_for(display_text, info_hash, f.id) else: url = serve_url if is_video(f.name): info_type = "video" elif is_music(f.name): info_type = "music" else: info_type = None if info_type is not None: kwargs = dict(info_hash=info_hash, file_id=f.id) url = plugin.url_for(play, **kwargs) file_li.setInfo(info_type, info_labels) file_li.setProperty("IsPlayable", "true") context_menu_items.append( (translate(30235), media(buffer_and_play, **kwargs))) context_menu_items.append(( translate(30209), action(file_action, info_hash, f.id, "download") ) if f.status.priority == 0 else ( translate(30208), action(file_action, info_hash, f.id, "stop"))) file_li.addContextMenuItems(context_menu_items) addDirectoryItem(plugin.handle, url, file_li)
def _update_daemon_settings(self): daemon_settings = self._get_daemon_settings() if daemon_settings is None: return False kodi_settings = self._get_kodi_settings() if daemon_settings != kodi_settings: logging.debug("Need to update daemon settings") r = self._request("post", self._settings_set_uri, json=kodi_settings) if r.status_code != 200: xbmcgui.Dialog().ok(kodi.translate(30102), r.json()["error"]) return False return True
def run(): if len(sys.argv) == 1: selected = xbmcgui.Dialog().select( ADDON_NAME, [translate(30002 + i) for i in range(4)]) elif len(sys.argv) == 2: method = sys.argv[1] try: selected = ("import_entries", "delete_entries", "clear_entries", "update_repository").index(method) except ValueError: raise NotImplementedError("Unknown method '{}'".format(method)) else: raise NotImplementedError("Unknown arguments") if selected == 0: import_entries() elif selected == 1: delete_entries() elif selected == 2: clear_entries() elif selected == 3: update_repository(True)
def handle_first_run(): logging.info("Handling first run") xbmcgui.Dialog().ok(kodi.translate(30100), kodi.translate(30101)) kodi.open_settings()
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 get_state_string(state): if 0 <= state <= 9: return translate(30220 + state) return translate(30230)
def li(tid, icon): return list_item(translate(tid), icon)