From f05195778709c69a6959f00b70aab7f7866b07b5 Mon Sep 17 00:00:00 2001 From: ozankaraali Date: Sat, 28 Sep 2024 18:56:08 +0200 Subject: [PATCH 1/2] with handlings? --- channel_list.py | 150 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 7 deletions(-) diff --git a/channel_list.py b/channel_list.py index c5b82b8..bd7ac66 100644 --- a/channel_list.py +++ b/channel_list.py @@ -1,3 +1,4 @@ +import asyncio import os import platform import random @@ -7,8 +8,10 @@ import subprocess from urllib.parse import urlparse +import aiohttp +import orjson import requests -from PySide6.QtCore import Qt, Signal +from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QColor, QIcon, QPixmap from PySide6.QtWidgets import ( QCheckBox, @@ -19,6 +22,7 @@ QListWidget, QListWidgetItem, QMainWindow, + QProgressBar, QPushButton, QVBoxLayout, QWidget, @@ -28,6 +32,82 @@ from options import OptionsDialog +class ContentLoader(QThread): + content_loaded = Signal(list) + progress_updated = Signal(int, int) + + def __init__(self, url, headers, content_type): + super().__init__() + self.url = url + self.headers = headers + self.content_type = content_type + + async def fetch_page(self, session, page, max_retries=3): + for attempt in range(max_retries): + try: + fetchurl = f"{self.url}/server/load.php?type=vod&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" + async with session.get( + fetchurl, headers=self.headers, timeout=30 + ) as response: + if response.status == 503: + wait_time = (2**attempt) + random.uniform(0, 1) + print( + f"Received 503 error. Retrying in {wait_time:.2f} seconds..." + ) + await asyncio.sleep(wait_time) + continue + content = await response.read() + result = orjson.loads(content) + return result["js"]["data"] + except ( + aiohttp.ClientError, + orjson.JSONDecodeError, + asyncio.TimeoutError, + ) as e: + print(f"Error fetching page {page}: {e}") + if attempt == max_retries - 1: + raise + wait_time = (2**attempt) + random.uniform(0, 1) + print(f"Retrying in {wait_time:.2f} seconds...") + await asyncio.sleep(wait_time) + return [] + + async def load_content(self): + async with aiohttp.ClientSession() as session: + # Fetch initial data to get total items and max page items + initial_url = f"{self.url}/server/load.php?type=vod&action=get_ordered_list" + async with session.get(initial_url, headers=self.headers) as response: + content = await response.read() + initial_result = orjson.loads(content) + total_items = int(initial_result["js"]["total_items"]) + max_page_items = int(initial_result["js"]["max_page_items"]) + pages = (total_items + max_page_items - 1) // max_page_items + + semaphore = asyncio.Semaphore(5) # Limit to 5 concurrent requests + + async def fetch_with_semaphore(page): + async with semaphore: + return await self.fetch_page(session, page) + + tasks = [fetch_with_semaphore(page) for page in range(pages)] + items = [] + for i, task in enumerate(asyncio.as_completed(tasks), 1): + page_items = await task + items.extend(page_items) + self.progress_updated.emit(i, pages) + if ( + len(items) % 100 == 0 or i == pages + ): # Emit every 100 items or on last page + self.content_loaded.emit(items) + + def run(self): + try: + asyncio.run(self.load_content()) + except Exception as e: + print(f"Error in content loading: {e}") + # You might want to emit a signal here to inform the main thread about the error + + class ChannelList(QMainWindow): content_loaded = Signal(list) @@ -115,6 +195,12 @@ def create_left_panel(self): self.content_switch.stateChanged.connect(self.toggle_content_type) left_layout.addWidget(self.content_switch) + self.progress_bar = QProgressBar(self) + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.setVisible(False) + left_layout.addWidget(self.progress_bar) + def toggle_favorite(self): selected_item = self.content_list.currentItem() if selected_item: @@ -147,10 +233,14 @@ def toggle_content_type(self, value): def display_content(self, items): self.content_list.clear() for item in items: - list_item = QListWidgetItem(item["name"]) - list_item.setData(31, item["cmd"]) + item_name = item.get("name") or item.get("title") + list_item = QListWidgetItem(item_name) + cmd = item.get("cmd") or item.get( + "id" + ) # if we append id, we need to handle + list_item.setData(31, cmd) self.content_list.addItem(list_item) - if self.check_if_favorite(item["name"]): + if self.check_if_favorite(item_name): list_item.setBackground(QColor(0, 0, 255, 20)) def filter_content(self, text=""): @@ -447,14 +537,23 @@ def do_handshake(self, url, mac, serverload="/server/load.php", load=True): def load_stb_content(self, url, options): url = URLObject(url) url = f"{url.scheme}://{url.netloc}" + items = [] try: if self.content_type == "channels": fetchurl = f"{url}/server/load.php?type=itv&action=get_all_channels" response = requests.get(fetchurl, headers=options["headers"]) result = response.json() items = result["js"]["data"] - else: - fetchurl = f"{url}/server/load.php?type=vod&action=get_ordered_list" + elif self.content_type == "movies": + self.content_loader = ContentLoader( + url, options["headers"], self.content_type + ) + self.content_loader.content_loaded.connect(self.update_content_list) + self.content_loader.progress_updated.connect(self.update_progress) + self.content_loader.finished.connect(self.content_loader_finished) + self.content_loader.start() + elif self.content_type == "series": + fetchurl = f"{url}/server/load.php?type=series&action=get_ordered_list" response = requests.get(fetchurl, headers=options["headers"]) result = response.json() total_items = int(result["js"]["total_items"]) @@ -462,10 +561,25 @@ def load_stb_content(self, url, options): pages = (total_items + max_page_items - 1) // max_page_items items = [] for page in range(pages): - fetchurl = f"{url}/server/load.php?type=vod&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" + fetchurl = f"{url}/server/load.php?type=series&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" response = requests.get(fetchurl, headers=options["headers"]) result = response.json() items.extend(result["js"]["data"]) + elif self.content_type == "genres": + fetchurl = f"{url}/server/load.php?type=itv&action=get_genres" + response = requests.get(fetchurl, headers=options["headers"]) + result = response.json() + items = result["js"] + elif self.content_type == "vod_categories": + fetchurl = f"{url}/server/load.php?type=vod&action=get_categories" + response = requests.get(fetchurl, headers=options["headers"]) + result = response.json() + items = result["js"] + elif self.content_type == "series_categories": + fetchurl = f"{url}/server/load.php?type=series&action=get_categories" + response = requests.get(fetchurl, headers=options["headers"]) + result = response.json() + items = result["js"] self.display_content(items) self.config["data"][self.config["selected"]]["options"] = options @@ -474,6 +588,28 @@ def load_stb_content(self, url, options): except Exception as e: print(f"Error loading STB content: {e}") + def content_loader_finished(self): + if hasattr(self, "content_loader"): + if self.content_loader.exception: + print(f"Error loading content: {self.content_loader.exception}") + # Handle the error (e.g., show a message to the user) + self.content_loader.deleteLater() + del self.content_loader + + def update_content_list(self, items): + self.display_content(items) + self.config["data"][self.config["selected"]][self.content_type] = items + self.save_config() + + # Update the update_progress method + def update_progress(self, current, total): + progress_percentage = int((current / total) * 100) + self.progress_bar.setValue(progress_percentage) + if progress_percentage == 100: + self.progress_bar.setVisible(False) + else: + self.progress_bar.setVisible(True) + def create_link(self, cmd): try: selected_provider = self.config["data"][self.config["selected"]] From bbb76bced7d07e73f4abfb0c8afd3ae8bd839eb0 Mon Sep 17 00:00:00 2001 From: ozankaraali Date: Sat, 28 Sep 2024 19:18:48 +0200 Subject: [PATCH 2/2] this should introduce the updating and stuff. --- channel_list.py | 126 +++++++++++++++++++++++++++++++++------------- config_manager.py | 2 +- 2 files changed, 93 insertions(+), 35 deletions(-) diff --git a/channel_list.py b/channel_list.py index bd7ac66..8917a0b 100644 --- a/channel_list.py +++ b/channel_list.py @@ -14,6 +14,7 @@ from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QColor, QIcon, QPixmap from PySide6.QtWidgets import ( + QButtonGroup, QCheckBox, QFileDialog, QGridLayout, @@ -22,8 +23,10 @@ QListWidget, QListWidgetItem, QMainWindow, + QMessageBox, QProgressBar, QPushButton, + QRadioButton, QVBoxLayout, QWidget, ) @@ -45,7 +48,13 @@ def __init__(self, url, headers, content_type): async def fetch_page(self, session, page, max_retries=3): for attempt in range(max_retries): try: - fetchurl = f"{self.url}/server/load.php?type=vod&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" + if self.content_type == "movies": + fetchurl = f"{self.url}/server/load.php?type=vod&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" + elif self.content_type == "series": + fetchurl = f"{self.url}/server/load.php?type=series&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" + else: + raise ValueError(f"Unsupported content type: {self.content_type}") + async with session.get( fetchurl, headers=self.headers, timeout=30 ) as response: @@ -75,7 +84,17 @@ async def fetch_page(self, session, page, max_retries=3): async def load_content(self): async with aiohttp.ClientSession() as session: # Fetch initial data to get total items and max page items - initial_url = f"{self.url}/server/load.php?type=vod&action=get_ordered_list" + if self.content_type == "movies": + initial_url = ( + f"{self.url}/server/load.php?type=vod&action=get_ordered_list" + ) + elif self.content_type == "series": + initial_url = ( + f"{self.url}/server/load.php?type=series&action=get_ordered_list" + ) + else: + raise ValueError(f"Unsupported content type: {self.content_type}") + async with session.get(initial_url, headers=self.headers) as response: content = await response.read() initial_result = orjson.loads(content) @@ -105,7 +124,6 @@ def run(self): asyncio.run(self.load_content()) except Exception as e: print(f"Error in content loading: {e}") - # You might want to emit a signal here to inform the main thread about the error class ChannelList(QMainWindow): @@ -191,9 +209,27 @@ def create_left_panel(self): ) left_layout.addWidget(self.favorites_only_checkbox) - self.content_switch = QCheckBox("Show Movies") - self.content_switch.stateChanged.connect(self.toggle_content_type) - left_layout.addWidget(self.content_switch) + self.content_switch_layout = QHBoxLayout() + self.content_switch_group = QButtonGroup(self) + self.content_switch_group.setExclusive(True) + + self.channels_radio = QRadioButton("Channels") + self.movies_radio = QRadioButton("Movies") + # self.series_radio = QRadioButton("Series") + + self.content_switch_group.addButton(self.channels_radio) + self.content_switch_group.addButton(self.movies_radio) + # self.content_switch_group.addButton(self.series_radio) + + self.channels_radio.setChecked(True) + + self.content_switch_layout.addWidget(self.channels_radio) + self.content_switch_layout.addWidget(self.movies_radio) + # self.content_switch_layout.addWidget(self.series_radio) + + left_layout.addLayout(self.content_switch_layout) + + self.content_switch_group.buttonClicked.connect(self.toggle_content_type) self.progress_bar = QProgressBar(self) self.progress_bar.setRange(0, 100) @@ -201,6 +237,11 @@ def create_left_panel(self): self.progress_bar.setVisible(False) left_layout.addWidget(self.progress_bar) + self.cancel_button = QPushButton("Cancel") + self.cancel_button.clicked.connect(self.cancel_content_loading) + self.cancel_button.setVisible(False) + left_layout.addWidget(self.cancel_button) + def toggle_favorite(self): selected_item = self.content_list.currentItem() if selected_item: @@ -225,9 +266,13 @@ def remove_from_favorites(self, item_name): def check_if_favorite(self, item_name): return item_name in self.config["favorites"] - def toggle_content_type(self, value): - state = Qt.CheckState(value) - self.content_type = "movies" if state == Qt.Checked else "channels" + def toggle_content_type(self, button): + if button == self.channels_radio: + self.content_type = "channels" + elif button == self.movies_radio: + self.content_type = "movies" + elif button == self.series_radio: + self.content_type = "series" self.load_content() def display_content(self, items): @@ -393,10 +438,13 @@ def save_config(self): self.config_manager.save_config() def load_content(self): + self.content_list.clear() if self.content_type == "channels": self.load_channels() - else: + elif self.content_type == "movies": self.load_movies() + elif self.content_type == "series": + self.load_series() def load_channels(self): channels = self.config["data"][self.config["selected"]].get("channels", []) @@ -412,6 +460,13 @@ def load_movies(self): else: self.update_content() + def load_series(self): + series = self.config["data"][self.config["selected"]].get("series", []) + if series: + self.display_content(series) + else: + self.update_content() + def update_content(self): selected_provider = self.config["data"][self.config["selected"]] config_type = selected_provider.get("type", "") @@ -537,14 +592,16 @@ def do_handshake(self, url, mac, serverload="/server/load.php", load=True): def load_stb_content(self, url, options): url = URLObject(url) url = f"{url.scheme}://{url.netloc}" - items = [] try: if self.content_type == "channels": fetchurl = f"{url}/server/load.php?type=itv&action=get_all_channels" response = requests.get(fetchurl, headers=options["headers"]) result = response.json() items = result["js"]["data"] - elif self.content_type == "movies": + self.display_content(items) + self.config["data"][self.config["selected"]]["channels"] = items + self.save_config() + elif self.content_type in ["movies", "series"]: self.content_loader = ContentLoader( url, options["headers"], self.content_type ) @@ -552,19 +609,8 @@ def load_stb_content(self, url, options): self.content_loader.progress_updated.connect(self.update_progress) self.content_loader.finished.connect(self.content_loader_finished) self.content_loader.start() - elif self.content_type == "series": - fetchurl = f"{url}/server/load.php?type=series&action=get_ordered_list" - response = requests.get(fetchurl, headers=options["headers"]) - result = response.json() - total_items = int(result["js"]["total_items"]) - max_page_items = int(result["js"]["max_page_items"]) - pages = (total_items + max_page_items - 1) // max_page_items - items = [] - for page in range(pages): - fetchurl = f"{url}/server/load.php?type=series&action=get_ordered_list&genre=0&category=*&p={page}&sortby=added" - response = requests.get(fetchurl, headers=options["headers"]) - result = response.json() - items.extend(result["js"]["data"]) + self.progress_bar.setVisible(True) + self.cancel_button.setVisible(True) elif self.content_type == "genres": fetchurl = f"{url}/server/load.php?type=itv&action=get_genres" response = requests.get(fetchurl, headers=options["headers"]) @@ -581,18 +627,27 @@ def load_stb_content(self, url, options): result = response.json() items = result["js"] - self.display_content(items) - self.config["data"][self.config["selected"]]["options"] = options - self.config["data"][self.config["selected"]][self.content_type] = items - self.save_config() + if self.content_type not in ["movies", "series"]: + self.display_content(items) + self.config["data"][self.config["selected"]]["options"] = options + self.config["data"][self.config["selected"]][self.content_type] = items + self.save_config() except Exception as e: print(f"Error loading STB content: {e}") + def cancel_content_loading(self): + if hasattr(self, "content_loader") and self.content_loader.isRunning(): + self.content_loader.terminate() + self.content_loader.wait() + self.content_loader_finished() + QMessageBox.information( + self, "Cancelled", "Content loading has been cancelled." + ) + def content_loader_finished(self): + self.progress_bar.setVisible(False) + self.cancel_button.setVisible(False) if hasattr(self, "content_loader"): - if self.content_loader.exception: - print(f"Error loading content: {self.content_loader.exception}") - # Handle the error (e.g., show a message to the user) self.content_loader.deleteLater() del self.content_loader @@ -601,7 +656,6 @@ def update_content_list(self, items): self.config["data"][self.config["selected"]][self.content_type] = items self.save_config() - # Update the update_progress method def update_progress(self, current, total): progress_percentage = int((current / total) * 100) self.progress_bar.setValue(progress_percentage) @@ -617,7 +671,11 @@ def create_link(self, cmd): url = URLObject(url) url = f"{url.scheme}://{url.netloc}" options = selected_provider["options"] - content_type = "vod" if self.content_type == "movies" else "itv" + content_type = ( + "vod" + if self.content_type == "movies" + else "itv" if self.content_type == "channels" else "series" + ) fetchurl = ( f"{url}/server/load.php?type={content_type}&action=create_link" f"&cmd={requests.utils.quote(cmd)}&JsHttpRequest=1-xml" diff --git a/config_manager.py b/config_manager.py index da15791..43ef74d 100644 --- a/config_manager.py +++ b/config_manager.py @@ -6,7 +6,7 @@ class ConfigManager: - CURRENT_VERSION = "1.4.7" # Set your current version here + CURRENT_VERSION = "1.4.9" # Set your current version here def __init__(self): self.config = {}