diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 2ee2ba3..03c93f6 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -106,7 +106,7 @@ jobs: run: | python -c " from PIL import Image - img = Image.open('src/core/interface/assets/logo.png') + img = Image.open('src/interface/assets/logo.png') img.save('app_icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)]) print('Icon converted successfully') " diff --git a/src/core/data/__init__.py b/src/core/data/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/data/scrapers/__init__.py b/src/core/data/scrapers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/data/scrapers/monkrus.py b/src/core/data/scrapers/monkrus.py deleted file mode 100644 index bf4c1cf..0000000 --- a/src/core/data/scrapers/monkrus.py +++ /dev/null @@ -1,87 +0,0 @@ -from core.utils.data.tracker import get_magnet_link -from core.utils.data.state import state -from bs4 import BeautifulSoup -from typing import Dict -import requests -import time - -cache = { - "data": [], - "last_fetched": 0 -} - -cache_expiry = 300 - -def _get_telegram_posts(): - post = 100 - max_posts = 250 - posts = [] - added = set() - - current_time = time.time() - - if cache["data"] and (current_time - cache["last_fetched"] < cache_expiry): - return cache["data"] - - while post <= max_posts: - url = f"https://t.me/s/real_monkrus/{post}" - response = requests.get(url, timeout=10) - soup = BeautifulSoup(response.text, "html.parser") - bubbles = soup.find_all("div", class_="tgme_widget_message_bubble") - - if not bubbles: - break - - for bubble in bubbles: - post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text") - if post_txt is None or post_txt.b is None: - continue - - title = post_txt.b.text - link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net")) - - if link: - post_url = link["href"] - if post_url not in added: - added.add(post_url) - posts.append(dict( - title=title, - author="m0nkrus", - id=len(posts) + 1, - url=post_url - )) - - post += 22 - - posts.reverse() - - cache["data"] = posts - cache["last_fetched"] = current_time - - return(posts) - -def scrape_m0nkrus(query): - posts = _get_telegram_posts() - filtered_posts = [] - - for post in posts: - if query.lower() in post["title"].lower(): - filtered_post = post.copy() - filtered_post["id"] = len(filtered_posts) + 1 - filtered_posts.append(filtered_post) - - return filtered_posts - -def get_magnet(post: Dict): - return get_magnet_link(post["url"]) - -Metadata = { - "name" : "m0nkrus", - "headers" : ["Post Title", "Author"], - "scrapeFunc" : scrape_m0nkrus, - "linkFunc" : get_magnet, - "isMagnet" : True, -} - -def init_m0nkrus(): - state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/data/scrapers/rutracker.py b/src/core/data/scrapers/rutracker.py deleted file mode 100644 index e6a0905..0000000 --- a/src/core/data/scrapers/rutracker.py +++ /dev/null @@ -1,52 +0,0 @@ -from core.utils.network.jsonhandler import split_data, format_data -from core.utils.data.tracker import get_magnet_link -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state -from typing import Dict -import requests - -def scrape_rutracker(query): - search = requests.get(f"{state.api_url}/search?q={query}", timeout=15) - consoleLog("Sent request to server") - if search: - _, data, _, success, cached = split_data(search.text) - if cached: - consoleLog("Server response cached") - if success: - sorted_data = [] - - for entry in data: - sorted_data.append( - { - "title" : entry["title"], - "author" : entry["author"], - "seeders" : entry["seeders"], - "leechers" : entry["leechers"], - "url" : entry["url"], - "id" : entry["id"], - } - ) - - return sorted_data - else: - consoleLog("Scraping failed server-side, unable to fetch posts from rutracker") - return [] - else: - consoleLog("No response from server, returning nothing") - return [] - - -def get_magnet(post: Dict): - _, post_links, _, _, _ = format_data([post]) - return get_magnet_link(post_links[0]) - -Metadata = { - "name" : "rutracker", - "headers" : ["Post Title", "Author", "Seeders", "Leechers"], - "scrapeFunc" : scrape_rutracker, - "linkFunc" : get_magnet, - "isMagnet" : True, -} - -def init_rutracker(): - state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py deleted file mode 100644 index 24c0165..0000000 --- a/src/core/data/scrapers/steamrip.py +++ /dev/null @@ -1,122 +0,0 @@ -from core.data.scrapers.provider.buzzheavier import scrape_buzzheavier -from core.data.scrapers.provider.gofile import scrape_gofile -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state -from bs4 import BeautifulSoup -from typing import Dict -import webbrowser -import requests -import time -import re - - -cache = { - "data": [], - "last_fetched": 0 -} - -cache_expiry = 300 - -def get_Metadata(): - return Metadata - -def scrape_steamrip_links(): - - current_time = time.time() - - if cache["data"] != [] and (current_time - cache["last_fetched"] < cache_expiry): - return cache["data"] - - url = f"https://steamrip.com/games-list-page/" - response = requests.get(url) - text = response.text - - soup = BeautifulSoup(text, "html.parser") - games = soup.find_all("li", class_="az-list-item") - - if len(games) == 0: - return [] - - links = [] - names = [] - for gamehtml in games: - link = gamehtml.find("a", href=lambda x: x and x.startswith("/")) - links += re.findall(r'(?<=href=")[^"]*', link.__str__()) - name = gamehtml.find("a", href=lambda x: x and x.startswith("/")) - names += re.findall(r'(?<=\/">)[^<]*', name.__str__()) - - - - # construct the list[dict[str,str]] - - ret = [] - - for i in range(len(names)): - ret.append({"title" : names[i], "url" : links[i]}) - - return ret - -def scrape_steamrip_game_downloads(gamelink): - url = "https://steamrip.com" + gamelink - response = requests.get(url) - soup = BeautifulSoup(response.text, "html.parser") - download_link_elements = soup.find_all("a",class_="shortc-button") - download_links = ["buzzheavier", "gofile"] - - for download_link in download_link_elements: - pure = download_link.attrs.get("href") - if pure[2] == "b": - download_links[0] = "https:" + pure - if pure[2] == "g": - download_links[1] = "https:" + pure - - ret = [] - - for link in download_links: - if len(link) != 1: - ret.append(link) - - cache["data"] = ret - - return ret - -def get_download_link(post: Dict): - url = post["url"] - links = scrape_steamrip_game_downloads(url) - best = "" - for link in links: - if link[0] == "h": - best = link - break - - if links.index(best) == 0: - link = scrape_buzzheavier(best) - return link - elif links.index(best) == 1: - link, headers = scrape_gofile(best) - return link, headers - else: - consoleLog("Unable to retrieve download link due to captcha, launching browser...") - webbrowser.open(best) - return None, None - -def filter_steamrip(query: str): - games = scrape_steamrip_links() - - filtered_games = [ - game for game in games - if query.lower() in game["title"].lower() - ] - - return filtered_games - -Metadata = { - "name" : "steamrip", - "headers" : ["Game"], - "scrapeFunc" : filter_steamrip, - "linkFunc" : get_download_link, - "isMagnet" : False, -} - -def init_steamrip(): - state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/data/scrapers/uztracker.py b/src/core/data/scrapers/uztracker.py deleted file mode 100644 index c5b118b..0000000 --- a/src/core/data/scrapers/uztracker.py +++ /dev/null @@ -1,55 +0,0 @@ -from core.utils.data.tracker import get_magnet_link -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state -from urllib.parse import urljoin -from bs4 import BeautifulSoup -from typing import Dict -import requests - - -def scrape_uztracker(query): - base_url="https://uztracker.net/" - search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}" - consoleLog(search_url) - posts = [] - - try: - response = requests.get(search_url) - response.raise_for_status() - soup = BeautifulSoup(response.text, 'html.parser') - links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_')) - for link in links: - - theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic')) - if not theme_link or not theme_link.b: - continue - url = urljoin(base_url, theme_link['href']) - title = theme_link.b.text - author_link = link.find('a', class_="med") - author = author_link.text.strip() if author_link else "Unknown" - - posts.append(dict( - title=title, - author=author, - url=url, - )) - - return posts - - except requests.RequestException as e: - consoleLog(f"Failed to fetch {search_url}: {e}") - return None - -def get_magnet(post: Dict): - return get_magnet_link(post["url"]) - -Metadata = { - "name" : "uztracker", - "headers" : ["Post Title", "Author"], - "scrapeFunc" : scrape_uztracker, - "linkFunc" : get_magnet, - "isMagnet" : True, -} - -def init_uztracker(): - state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/interface/utils/searchhelper.py b/src/core/interface/utils/searchhelper.py deleted file mode 100644 index 118d754..0000000 --- a/src/core/interface/utils/searchhelper.py +++ /dev/null @@ -1,26 +0,0 @@ -from core.data.scrapers.rutracker import init_rutracker -from core.data.scrapers.uztracker import init_uztracker -from core.data.scrapers.steamrip import init_steamrip -from core.data.scrapers.monkrus import init_m0nkrus -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state - -init_rutracker() -init_uztracker() -init_m0nkrus() -init_steamrip() - -def return_pressed(self): - self.show_empty_results(False) - search_text = self.searchbar.text() - if search_text == "": - consoleLog("Error: Can't search for nothing") - return - consoleLog(f"User searched for: {search_text}") - - tracker = state.trackers[state.currenttracker] - scrapefunc = tracker["scrapeFunc"] - state.posts = scrapefunc(search_text) - - from core.interface.gui import MainWindow - MainWindow._instance.search_results_signal.emit(tracker["headers"]) diff --git a/src/core/interface/utils/tabhelper.py b/src/core/interface/utils/tabhelper.py deleted file mode 100644 index 74c4dd1..0000000 --- a/src/core/interface/utils/tabhelper.py +++ /dev/null @@ -1,56 +0,0 @@ -from PySide6.QtWidgets import QWidget, QVBoxLayout - - -def create_tab(title, searchbar, software_list, tabs, dlbutton, layout2): - tab = QWidget() - layout = QVBoxLayout() - if searchbar: - layout.addWidget(searchbar) - if layout2: - layout.addLayout(layout2) - else: - layout.addWidget(software_list) - if dlbutton: - layout.addWidget(dlbutton) - tab.setLayout(layout) - tabs.addTab(tab, title) - return tab - -################# -# SETTINGS TABS # -################# -def general_tab(title, autoresume, update_checkbox, transparent_window, tabs): - tab = QWidget() - layout = QVBoxLayout() - layout.addWidget(autoresume) - layout.addWidget(update_checkbox) - layout.addWidget(transparent_window) - layout.addStretch() - tab.setLayout(layout) - tabs.addTab(tab, title) - return tab - -def paths_tab(title, download_path, image_path, tabs): - tab = QWidget() - layout = QVBoxLayout() - layout.addWidget(download_path) - layout.addWidget(image_path) - layout.addStretch() - tab.setLayout(layout) - tabs.addTab(tab, title) - return tab - - -def network_tab(title, network_interface, max_connections, max_downloads, up_speed_limit, down_speed_limit, api_url, tabs): - tab = QWidget() - layout = QVBoxLayout() - layout.addWidget(network_interface) - layout.addWidget(max_connections) - layout.addWidget(max_downloads) - layout.addWidget(up_speed_limit) - layout.addWidget(down_speed_limit) - layout.addWidget(api_url) - layout.addStretch() - tab.setLayout(layout) - tabs.addTab(tab, title) - return tab diff --git a/src/core/network/__init__.py b/src/core/network/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/utils/__init__.py b/src/core/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/data/scrapers/provider/buzzheavier.py b/src/data/hosts/buzzheavier.py similarity index 100% rename from src/core/data/scrapers/provider/buzzheavier.py rename to src/data/hosts/buzzheavier.py diff --git a/src/core/data/scrapers/provider/gofile.py b/src/data/hosts/gofile.py similarity index 96% rename from src/core/data/scrapers/provider/gofile.py rename to src/data/hosts/gofile.py index 342b789..a7a86a8 100644 --- a/src/core/data/scrapers/provider/gofile.py +++ b/src/data/hosts/gofile.py @@ -1,4 +1,4 @@ -from core.utils.logging.logs import consoleLog +from utils.logging.logs import consoleLog import requests import re diff --git a/src/data/sources/__init__.py b/src/data/sources/__init__.py new file mode 100644 index 0000000..569e6a1 --- /dev/null +++ b/src/data/sources/__init__.py @@ -0,0 +1,12 @@ +from data.sources.rutracker import RutrackerScraper +from data.sources.uztracker import UztrackerScraper +from data.sources.steamrip import SteamripScraper +from data.sources.monkrus import MonkrusScraper + +# Register Scrapers here +SCRAPERS = [ + RutrackerScraper(), + SteamripScraper(), + MonkrusScraper(), + UztrackerScraper(), +] \ No newline at end of file diff --git a/src/data/sources/monkrus.py b/src/data/sources/monkrus.py new file mode 100644 index 0000000..f45e32a --- /dev/null +++ b/src/data/sources/monkrus.py @@ -0,0 +1,78 @@ +from utils.data.tracker import get_magnet_link +from bs4 import BeautifulSoup +from typing import Dict +import requests +import time + + +class MonkrusScraper: + name = "m0nkrus" + headers = ["Post Title", "Author"] + is_magnet = True + + def __init__(self): + self.cache = { "data": [], "last_fetched": 0 } + self.cache_expiry = 300 + + def _get_telegram_posts(self): + post = 100 + max_posts = 250 + posts = [] + added = set() + + current_time = time.time() + + if self.cache["data"] and (current_time - self.cache["last_fetched"] < self.cache_expiry): + return self.cache["data"] + + while post <= max_posts: + url = f"https://t.me/s/real_monkrus/{post}" + response = requests.get(url, timeout=10) + soup = BeautifulSoup(response.text, "html.parser") + bubbles = soup.find_all("div", class_="tgme_widget_message_bubble") + + if not bubbles: + break + + for bubble in bubbles: + post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text") + if post_txt is None or post_txt.b is None: + continue + + title = post_txt.b.text + link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net")) + + if link: + post_url = link["href"] + if post_url not in added: + added.add(post_url) + posts.append(dict( + title=title, + author="m0nkrus", + id=len(posts) + 1, + url=post_url + )) + + post += 22 + + posts.reverse() + + self.cache["data"] = posts + self.cache["last_fetched"] = current_time + + return(posts) + + def search(self, query): + posts = self._get_telegram_posts() + filtered_posts = [] + + for post in posts: + if query.lower() in post["title"].lower(): + filtered_post = post.copy() + filtered_post["id"] = len(filtered_posts) + 1 + filtered_posts.append(filtered_post) + + return filtered_posts + + def get_download_link(self, post: Dict): + return get_magnet_link(post["url"]) diff --git a/src/data/sources/rutracker.py b/src/data/sources/rutracker.py new file mode 100644 index 0000000..73b6a4b --- /dev/null +++ b/src/data/sources/rutracker.py @@ -0,0 +1,46 @@ +from utils.network.jsonhandler import split_data, format_data +from utils.data.tracker import get_magnet_link +from utils.logging.logs import consoleLog +from utils.data.state import state +from typing import Dict +import requests + +class RutrackerScraper: + name = "rutracker" + headers = ["Post Title", "Author", "Seeders", "Leechers"] + is_magnet = True + + def search(self, query): + search = requests.get(f"{state.api_url}/search?q={query}", timeout=15) + consoleLog("Sent request to server") + if search: + _, data, _, success, cached = split_data(search.text) + if cached: + consoleLog("Server response cached") + if success: + sorted_data = [] + + for entry in data: + sorted_data.append( + { + "title" : entry["title"], + "author" : entry["author"], + "seeders" : entry["seeders"], + "leechers" : entry["leechers"], + "url" : entry["url"], + "id" : entry["id"], + } + ) + + return sorted_data + else: + consoleLog("Scraping failed server-side, unable to fetch posts from rutracker") + return [] + else: + consoleLog("No response from server, returning nothing") + return [] + + def get_download_link(self, post: Dict): + _, post_links, _, _, _ = format_data([post]) + return get_magnet_link(post_links[0]) + diff --git a/src/data/sources/steamrip.py b/src/data/sources/steamrip.py new file mode 100644 index 0000000..71de40e --- /dev/null +++ b/src/data/sources/steamrip.py @@ -0,0 +1,107 @@ +from data.hosts.buzzheavier import scrape_buzzheavier +from data.hosts.gofile import scrape_gofile +from utils.logging.logs import consoleLog +from bs4 import BeautifulSoup +from typing import Dict +import webbrowser +import requests +import time +import re + +class SteamripScraper: + name = "steamrip" + headers = ["Game"] + is_magnet = False + + + def __init__(self): + self.cache = { "data": [], "last_fetched": 0 } + self.cache_expiry = 300 + + def scrape_steamrip_links(self): + + current_time = time.time() + + if self.cache["data"] != [] and (current_time - self.cache["last_fetched"] < self.cache_expiry): + return self.cache["data"] + + url = f"https://steamrip.com/games-list-page/" + response = requests.get(url) + text = response.text + + soup = BeautifulSoup(text, "html.parser") + games = soup.find_all("li", class_="az-list-item") + + if len(games) == 0: + return [] + + links = [] + names = [] + for gamehtml in games: + link = gamehtml.find("a", href=lambda x: x and x.startswith("/")) + links += re.findall(r'(?<=href=")[^"]*', link.__str__()) + name = gamehtml.find("a", href=lambda x: x and x.startswith("/")) + names += re.findall(r'(?<=\/">)[^<]*', name.__str__()) + + # construct the list[dict[str,str]] + + ret = [] + + for i in range(len(names)): + ret.append({"title" : names[i], "url" : links[i]}) + + return ret + + def scrape_steamrip_game_downloads(self, gamelink): + url = "https://steamrip.com" + gamelink + response = requests.get(url) + soup = BeautifulSoup(response.text, "html.parser") + download_link_elements = soup.find_all("a",class_="shortc-button") + download_links = ["buzzheavier", "gofile"] + + for download_link in download_link_elements: + pure = download_link.attrs.get("href") + if pure[2] == "b": + download_links[0] = "https:" + pure + if pure[2] == "g": + download_links[1] = "https:" + pure + + ret = [] + + for link in download_links: + if len(link) != 1: + ret.append(link) + + self.cache["data"] = ret + + return ret + + def get_download_link(self, post: Dict): + url = post["url"] + links = self.scrape_steamrip_game_downloads(url) + best = "" + for link in links: + if link[0] == "h": + best = link + break + + if links.index(best) == 0: + link = scrape_buzzheavier(best) + return link + elif links.index(best) == 1: + link, headers = scrape_gofile(best) + return link, headers + else: + consoleLog("Unable to retrieve download link due to captcha, launching browser...") + webbrowser.open(best) + return None, None + + def search(self, query: str): + games = self.scrape_steamrip_links() + + filtered_games = [ + game for game in games + if query.lower() in game["title"].lower() + ] + + return filtered_games \ No newline at end of file diff --git a/src/data/sources/uztracker.py b/src/data/sources/uztracker.py new file mode 100644 index 0000000..027394e --- /dev/null +++ b/src/data/sources/uztracker.py @@ -0,0 +1,50 @@ +from utils.data.tracker import get_magnet_link +from utils.logging.logs import consoleLog +from utils.data.state import state +from urllib.parse import urljoin +from bs4 import BeautifulSoup +from typing import Dict +import requests + +class UztrackerScraper: + name = "uztracker" + headers = ["Post Title", "Author"] + is_magnet = True + + + def search(self, query): + base_url="https://uztracker.net/" + search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}" + consoleLog(search_url) + posts = [] + + try: + response = requests.get(search_url) + response.raise_for_status() + soup = BeautifulSoup(response.text, 'html.parser') + links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_')) + for link in links: + + theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic')) + if not theme_link or not theme_link.b: + continue + url = urljoin(base_url, theme_link['href']) + title = theme_link.b.text + author_link = link.find('a', class_="med") + author = author_link.text.strip() if author_link else "Unknown" + + posts.append(dict( + title=title, + author=author, + url=url, + )) + + return posts + + except requests.RequestException as e: + consoleLog(f"Failed to fetch {search_url}: {e}") + return None + + def get_download_link(self, post: Dict): + return get_magnet_link(post["url"]) + diff --git a/src/core/interface/assets/base64_icons.py b/src/interface/assets/base64_icons.py similarity index 100% rename from src/core/interface/assets/base64_icons.py rename to src/interface/assets/base64_icons.py diff --git a/src/core/interface/assets/logo.png b/src/interface/assets/logo.png similarity index 100% rename from src/core/interface/assets/logo.png rename to src/interface/assets/logo.png diff --git a/src/core/interface/dialogs/contextmenu.py b/src/interface/dialogs/contextmenu.py similarity index 98% rename from src/core/interface/dialogs/contextmenu.py rename to src/interface/dialogs/contextmenu.py index 1b7362e..fb8c98f 100644 --- a/src/core/interface/dialogs/contextmenu.py +++ b/src/interface/dialogs/contextmenu.py @@ -1,7 +1,7 @@ -from core.utils.logging.logs import consoleLog, remove_download_log +from utils.logging.logs import consoleLog, remove_download_log from PySide6.QtWidgets import QMessageBox -from core.utils.data.state import state from PySide6.QtCore import Qt, QPoint +from utils.data.state import state from PySide6 import QtWidgets import subprocess import platform diff --git a/src/core/interface/dialogs/downloadlist.py b/src/interface/dialogs/downloadlist.py similarity index 93% rename from src/core/interface/dialogs/downloadlist.py rename to src/interface/dialogs/downloadlist.py index 1c6e66b..d381e24 100644 --- a/src/core/interface/dialogs/downloadlist.py +++ b/src/interface/dialogs/downloadlist.py @@ -1,9 +1,9 @@ -from core.interface.dialogs.pauseresumedelegate import PauseResumeDelegate -from core.interface.dialogs.hoverrowdelegate import HoverRowDelegate -from core.interface.dialogs.downloadmodel import DownloadModel -from core.interface.dialogs.theme import _table_stylesheet +from interface.dialogs.pauseresumedelegate import PauseResumeDelegate +from interface.dialogs.hoverrowdelegate import HoverRowDelegate +from interface.dialogs.downloadmodel import DownloadModel +from interface.dialogs.theme import _table_stylesheet from PySide6.QtWidgets import QTableView, QHeaderView -from core.utils.logging.logs import consoleLog +from utils.logging.logs import consoleLog from PySide6 import QtWidgets from PySide6.QtCore import Qt diff --git a/src/core/interface/dialogs/downloadmodel.py b/src/interface/dialogs/downloadmodel.py similarity index 98% rename from src/core/interface/dialogs/downloadmodel.py rename to src/interface/dialogs/downloadmodel.py index 7785fe9..6cb0237 100644 --- a/src/core/interface/dialogs/downloadmodel.py +++ b/src/interface/dialogs/downloadmodel.py @@ -1,6 +1,6 @@ from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.logging.logs import consoleLog +from utils.data.state import state import libtorrent as lt import subprocess import platform diff --git a/src/core/interface/dialogs/elideditemdelegate.py b/src/interface/dialogs/elideditemdelegate.py similarity index 93% rename from src/core/interface/dialogs/elideditemdelegate.py rename to src/interface/dialogs/elideditemdelegate.py index 5ef8671..6957ae2 100644 --- a/src/core/interface/dialogs/elideditemdelegate.py +++ b/src/interface/dialogs/elideditemdelegate.py @@ -1,4 +1,4 @@ -from core.interface.dialogs.theme import _theme_colors +from interface.dialogs.theme import _theme_colors from PySide6.QtWidgets import QStyledItemDelegate from PySide6.QtCore import Qt diff --git a/src/core/interface/dialogs/eventfilter.py b/src/interface/dialogs/eventfilter.py similarity index 95% rename from src/core/interface/dialogs/eventfilter.py rename to src/interface/dialogs/eventfilter.py index 4bddc92..760ccab 100644 --- a/src/core/interface/dialogs/eventfilter.py +++ b/src/interface/dialogs/eventfilter.py @@ -1,5 +1,5 @@ -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.logging.logs import consoleLog +from utils.data.state import state from PySide6.QtCore import QEvent def eventFilter(self, obj, event): diff --git a/src/core/interface/dialogs/hoverrowdelegate.py b/src/interface/dialogs/hoverrowdelegate.py similarity index 86% rename from src/core/interface/dialogs/hoverrowdelegate.py rename to src/interface/dialogs/hoverrowdelegate.py index 3293099..11b16ab 100644 --- a/src/core/interface/dialogs/hoverrowdelegate.py +++ b/src/interface/dialogs/hoverrowdelegate.py @@ -1,11 +1,11 @@ -from core.interface.dialogs.theme import _theme_colors +from interface.dialogs.theme import _theme_colors from PySide6.QtWidgets import QStyledItemDelegate from PySide6 import QtWidgets class HoverRowDelegate(QStyledItemDelegate): def paint(self, painter, option, index): - from core.interface.gui import MainWindow + from interface.gui import MainWindow opt = QtWidgets.QStyleOptionViewItem(option) option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus diff --git a/src/core/interface/dialogs/image.py b/src/interface/dialogs/image.py similarity index 96% rename from src/core/interface/dialogs/image.py rename to src/interface/dialogs/image.py index 0abb90c..f39c327 100644 --- a/src/core/interface/dialogs/image.py +++ b/src/interface/dialogs/image.py @@ -1,6 +1,6 @@ from PySide6.QtGui import QImage, QPixmap -from core.utils.data.state import state from PySide6.QtWidgets import QLabel +from utils.data.state import state from PySide6.QtCore import Qt import os diff --git a/src/core/interface/dialogs/pauseresumedelegate.py b/src/interface/dialogs/pauseresumedelegate.py similarity index 93% rename from src/core/interface/dialogs/pauseresumedelegate.py rename to src/interface/dialogs/pauseresumedelegate.py index 7570a5b..ec4668b 100644 --- a/src/core/interface/dialogs/pauseresumedelegate.py +++ b/src/interface/dialogs/pauseresumedelegate.py @@ -1,8 +1,8 @@ -from core.interface.dialogs.theme import _theme_colors, SVG_PLAY, SVG_PAUSE, SVG_FOLDER +from interface.dialogs.theme import _theme_colors, SVG_PLAY, SVG_PAUSE, SVG_FOLDER from PySide6.QtWidgets import QStyledItemDelegate, QWidget, QHBoxLayout -from core.interface.utils.svghelper import svg_icon +from interface.utils.svghelper import svg_icon from PySide6.QtCore import Qt, QSize, Signal -from core.utils.data.state import state +from utils.data.state import state from PySide6 import QtWidgets import libtorrent as lt @@ -10,7 +10,7 @@ class PauseResumeDelegate(QStyledItemDelegate): clicked = Signal(int) def paint(self, painter, option, index): - from core.interface.gui import MainWindow + from interface.gui import MainWindow opt = QtWidgets.QStyleOptionViewItem(option) option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus diff --git a/src/core/interface/dialogs/settings.py b/src/interface/dialogs/settings.py similarity index 84% rename from src/core/interface/dialogs/settings.py rename to src/interface/dialogs/settings.py index 5589b68..0682323 100644 --- a/src/core/interface/dialogs/settings.py +++ b/src/interface/dialogs/settings.py @@ -1,11 +1,9 @@ -from core.interface.utils.tabhelper import general_tab -from core.interface.utils.tabhelper import network_tab -from core.utils.config.settings import save_settings -from core.interface.utils.tabhelper import paths_tab -from core.interface.utils.svghelper import svg_icon -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from interface.utils.tabhelper import create_tab +from utils.config.settings import save_settings +from interface.utils.svghelper import svg_icon +from utils.logging.logs import consoleLog from PySide6.QtCore import Qt, QSize +from utils.data.state import state from PySide6 import QtWidgets from PySide6.QtWidgets import ( QLineEdit, @@ -19,7 +17,6 @@ from PySide6.QtWidgets import ( QCheckBox, QFileDialog, QComboBox, - QTabWidget, ) import platform @@ -46,7 +43,7 @@ def settings_dialog(self): update_checkbox_container = QWidget() update_checkbox_layout = QHBoxLayout() - # ignore updates checkbox + # Ignore updates checkbox if platform.system() == "Windows": update_checkbox = QCheckBox() @@ -57,7 +54,7 @@ def settings_dialog(self): update_checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked)) update_checkbox_layout.addWidget(update_checkbox) - # auto-resume downloads checkbox + # Auto-resume downloads checkbox autoresume_container = QWidget() autoresume_layout = QHBoxLayout() @@ -71,7 +68,7 @@ def settings_dialog(self): autoresume_layout.addWidget(autoresume_checkbox) - # transparent window checkbox + # Transparent window checkbox transparent_window_container = QWidget() transparent_window_layout = QHBoxLayout() @@ -84,10 +81,8 @@ def settings_dialog(self): transparent_window_checkbox.toggled.connect(lambda checked: setattr(state, 'window_transparency', checked)) transparent_window_layout.addWidget(transparent_window_checkbox) - ################## - # SERVER SETTING # - ################## + # API Server Setting api_url_container = QWidget() api_url_layout = QHBoxLayout() @@ -98,10 +93,8 @@ def settings_dialog(self): api_url_container.setLayout(api_url_layout) api_url.setText(state.api_url) - ################# - # DOWNLOAD PATH # - ################# + # Download Path download_path_container = QWidget() download_path_layout = QHBoxLayout() @@ -133,10 +126,8 @@ def settings_dialog(self): download_path_layout.addWidget(browse_button) browse_button.clicked.connect(browse_download_path) - ############### - # IMAGE PATH # - ############### + # Image Path image_path_container = QWidget() image_path_layout = QHBoxLayout() @@ -167,10 +158,8 @@ def settings_dialog(self): image_path_layout.addWidget(browse_button) browse_button.clicked.connect(browse_image_path) - ################## - # SPEED LIMITING # - ################## + # Speed Limiting down_speed_limit_container = QWidget() down_speed_limit_layout = QHBoxLayout() @@ -197,10 +186,8 @@ def settings_dialog(self): up_speed_limit.setFixedWidth(180) up_speed_limit.setFixedHeight(30) - ###################### - # CONNECTION CONFIGS # - ###################### - + + # Connection Configs max_connections_container = QWidget() max_connections_layout = QHBoxLayout() @@ -214,10 +201,8 @@ def settings_dialog(self): max_connections.setFixedWidth(180) max_connections.setFixedHeight(30) - #################### - # DOWNLOAD CONFIGS # - #################### + # Download Configs max_downloads_container = QWidget() max_downloads_layout = QHBoxLayout() @@ -231,10 +216,8 @@ def settings_dialog(self): max_downloads.setFixedWidth(180) max_downloads.setFixedHeight(30) - ##################### - # INTERFACE BINDING # - ##################### + # Interface Binding interface_container = QWidget() interface_layout = QHBoxLayout() @@ -257,10 +240,7 @@ def settings_dialog(self): interface_container.setLayout(interface_layout) - ############### - # SAVE/CANCEL # - ############### - + # Save / Cancel buttons layout = QHBoxLayout() save_btn = QPushButton("Save") @@ -281,12 +261,12 @@ def settings_dialog(self): layout.addWidget(cancel_btn) layout.addWidget(save_btn) - self.tabs = QTabWidget() - self.tab1 = general_tab("General", autoresume_container, update_checkbox_container, transparent_window_container, self.tabs) - self.tab2 = paths_tab("Paths", download_path_container, image_path_container, self.tabs) - self.tab3 = network_tab("Network", interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container, self.tabs) + tabs = QtWidgets.QTabWidget() + create_tab("General", [autoresume_container, update_checkbox_container, transparent_window_container], tabs=tabs, stretch=True) + create_tab("Paths", [download_path_container, image_path_container], tabs=tabs, stretch=True) + create_tab("Network", [interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container], tabs=tabs, stretch=True) - dialog_layout.addWidget(self.tabs) + dialog_layout.addWidget(tabs) dialog_layout.addLayout(layout) dialog.exec() diff --git a/src/core/interface/dialogs/theme.py b/src/interface/dialogs/theme.py similarity index 100% rename from src/core/interface/dialogs/theme.py rename to src/interface/dialogs/theme.py diff --git a/src/core/interface/dialogs/trackerhoverdelegate.py b/src/interface/dialogs/trackerhoverdelegate.py similarity index 90% rename from src/core/interface/dialogs/trackerhoverdelegate.py rename to src/interface/dialogs/trackerhoverdelegate.py index c83af6d..f6af434 100644 --- a/src/core/interface/dialogs/trackerhoverdelegate.py +++ b/src/interface/dialogs/trackerhoverdelegate.py @@ -1,4 +1,4 @@ -from core.interface.dialogs.theme import _theme_colors +from interface.dialogs.theme import _theme_colors from PySide6.QtWidgets import QStyledItemDelegate class TrackerHoverDelegate(QStyledItemDelegate): diff --git a/src/core/interface/dialogs/trackertable.py b/src/interface/dialogs/trackertable.py similarity index 95% rename from src/core/interface/dialogs/trackertable.py rename to src/interface/dialogs/trackertable.py index a382e75..68f188d 100644 --- a/src/core/interface/dialogs/trackertable.py +++ b/src/interface/dialogs/trackertable.py @@ -1,4 +1,4 @@ -from core.interface.dialogs.theme import _table_stylesheet +from interface.dialogs.theme import _table_stylesheet from PySide6.QtWidgets import QTableWidget from PySide6 import QtWidgets from PySide6.QtCore import Qt diff --git a/src/core/interface/dialogs/update.py b/src/interface/dialogs/update.py similarity index 86% rename from src/core/interface/dialogs/update.py rename to src/interface/dialogs/update.py index f2184e0..a45a329 100644 --- a/src/core/interface/dialogs/update.py +++ b/src/interface/dialogs/update.py @@ -1,8 +1,8 @@ -from core.utils.network.update_checker import get_updates -from core.utils.network.updater import download_update +from utils.network.update_checker import get_updates from PySide6.QtWidgets import QDialog, QMessageBox -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.network.updater import download_update +from utils.logging.logs import consoleLog +from utils.data.state import state from pathlib import Path import platform import json @@ -24,7 +24,7 @@ def _get_build_info_path() -> (Path | None): def get_version() -> None: # Get build info filepath build_info_path = _get_build_info_path() - if os.path.exists(build_info_path): + if build_info_path and os.path.exists(build_info_path): with open(build_info_path, "r") as f: build_info = json.load(f) state.version = build_info.get("version") @@ -46,3 +46,4 @@ class UpdateDialog(QDialog): response = msg.exec_() if response == QMessageBox.StandardButton.Ok: download_update(assets) + diff --git a/src/core/interface/gui.py b/src/interface/gui.py similarity index 87% rename from src/core/interface/gui.py rename to src/interface/gui.py index 50dfbf5..1b048ef 100644 --- a/src/core/interface/gui.py +++ b/src/interface/gui.py @@ -1,25 +1,25 @@ -from core.interface.dialogs.trackerhoverdelegate import TrackerHoverDelegate -from core.interface.dialogs.elideditemdelegate import ElidedItemDelegate -from core.interface.dialogs.trackertable import _create_tracker_table -from core.interface.dialogs.downloadlist import _create_download_list -from core.interface.assets.base64_icons import settings_white_base64 -from core.interface.assets.base64_icons import settings_black_base64 -from core.interface.dialogs.downloadlist import download_list_update -from core.interface.dialogs.update import get_version, UpdateDialog -from core.utils.logging.logs import consoleLog, flush_log_buffer -from core.interface.dialogs.downloadmodel import DownloadModel -from core.interface.utils.searchhelper import return_pressed -from core.interface.dialogs.settings import settings_dialog -from core.interface.assets.base64_icons import logo_base64 -from core.interface.dialogs.eventfilter import eventFilter -from core.utils.network.download import download_selected -from core.interface.utils.tabhelper import create_tab -from core.utils.general.shutdown import closehelper -from core.utils.general.wrappers import run_thread -from core.interface.dialogs.image import Image -from core.utils.data.state import state +from interface.dialogs.trackerhoverdelegate import TrackerHoverDelegate +from interface.dialogs.elideditemdelegate import ElidedItemDelegate +from interface.dialogs.trackertable import _create_tracker_table +from interface.dialogs.downloadlist import _create_download_list +from interface.assets.base64_icons import settings_white_base64 +from interface.assets.base64_icons import settings_black_base64 +from interface.dialogs.downloadlist import download_list_update +from interface.dialogs.update import get_version, UpdateDialog +from utils.logging.logs import consoleLog, flush_log_buffer +from interface.dialogs.downloadmodel import DownloadModel +from interface.dialogs.settings import settings_dialog +from interface.assets.base64_icons import logo_base64 +from interface.dialogs.eventfilter import eventFilter +from utils.network.download import download_selected +from interface.utils.searchhelper import run_search +from interface.utils.tabhelper import create_tab +from utils.general.shutdown import closehelper +from utils.general.wrappers import run_thread +from interface.dialogs.image import Image +from utils.data.state import state -import core.interface.dialogs.contextmenu +import interface.dialogs.contextmenu from PySide6 import QtWidgets from PySide6.QtCore import ( @@ -86,7 +86,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): consoleLog(f"Could not set app ID: {e}") # Get current version - # core.interface.dialogs.update + # interface.dialogs.update get_version() UpdateDialog() @@ -137,7 +137,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): containerLayout.addWidget(self.searchbar) containerLayout.addWidget(state.trackertable) - # core.interface.dialogs.hoverrowdelegate + # interface.dialogs.hoverrowdelegate self.download_model = DownloadModel() self.downloadList = _create_download_list(self) self.downloadList.viewport().installEventFilter(self) @@ -164,8 +164,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.horizontal_layout.addWidget(state.trackertable) # Tabs - self.tab1 = create_tab("Search", self.searchbar, state.trackertable, self.tabs, self.dlbutton, self.horizontal_layout) - self.tab2 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None) + create_tab("Search", [self.searchbar, self.horizontal_layout, self.dlbutton], tabs=self.tabs, stretch=False) + create_tab("Downloads", [self.emptyDownload, self.downloadList], tabs=self.tabs, stretch=False) # Corner Widget (Settings Button, Tracker list, Tab button container) self.corner_widget = QWidget() @@ -232,7 +232,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.statusbar.addPermanentWidget(self.version, Qt.AlignmentFlag.AlignLeft) # (\u2193) Arrow down, (\u2191) Arrow up - self.speed_label = QLabel("\u2193 0.0 kB/s \u2191 0.0 kB/s") + self.speed_label = QLabel("↓ 0.0 kB/s ↑ 0.0 kB/s") self.speed_label.setStyleSheet("padding-right: 6px;") self.statusbar.addPermanentWidget(self.speed_label) @@ -246,7 +246,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.active_timer.timeout.connect(self.show_empty_downloads) self.active_timer.start(500) - self._context_menu = core.interface.dialogs.contextmenu.ContextMenu(self) + self._context_menu = interface.dialogs.contextmenu.ContextMenu(self) self.image_overlay = Image(self) def _apply_default_headers(self, table): @@ -292,10 +292,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): table.setItemDelegateForColumn(i, self._tracker_hover_delegate) def _start_search(self): - self.searchbar.setEnabled(False) def _search_thread(): try: - return_pressed(self) + run_search(self) except Exception as e: consoleLog(f"Search error: {e}", True) self.search_results_signal.emit([]) @@ -384,7 +383,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): def closeEvent(self, event: QCloseEvent): closehelper() event.accept() - from core.utils.general.shutdown import force_exit + from utils.general.shutdown import force_exit force_exit() def _invalidate_hover_row(self, row): diff --git a/src/interface/utils/searchhelper.py b/src/interface/utils/searchhelper.py new file mode 100644 index 0000000..eeefdf3 --- /dev/null +++ b/src/interface/utils/searchhelper.py @@ -0,0 +1,27 @@ +from utils.logging.logs import consoleLog +from utils.data.state import state +from data.sources import SCRAPERS + +for scraper in SCRAPERS: + state.trackers[scraper.name] = scraper + +def run_search(self) -> None: + state.trackertable.setRowCount(0) + self.show_empty_results(False) + + search_text = self.searchbar.text() + # Check for empty search + if search_text == "": + consoleLog("Error: Can't search for nothing") + return + + state.posts = [] + self.searchbar.setEnabled(False) + consoleLog(f"User searched for: {search_text}") + + # Get current tracker and call its search function + scraper = state.trackers[state.currenttracker] + state.posts = scraper.search(search_text) + + # Update GUI headers to match the current scraper + self.search_results_signal.emit(scraper.headers) \ No newline at end of file diff --git a/src/core/interface/utils/svghelper.py b/src/interface/utils/svghelper.py similarity index 95% rename from src/core/interface/utils/svghelper.py rename to src/interface/utils/svghelper.py index 393b321..d9f8763 100644 --- a/src/core/interface/utils/svghelper.py +++ b/src/interface/utils/svghelper.py @@ -1,16 +1,13 @@ - - -from PySide6 import QtWidgets -from PySide6.QtCore import Qt, QByteArray from PySide6.QtGui import QIcon, QPixmap, QPainter +from PySide6.QtCore import Qt, QByteArray from PySide6.QtSvg import QSvgRenderer +from PySide6 import QtWidgets import darkdetect - +# Check if darkmode is enabled def _is_dark_mode(): return darkdetect.isDark() - def svg_icon(svg_str, size=20): app = QtWidgets.QApplication.instance() if app: diff --git a/src/interface/utils/tabhelper.py b/src/interface/utils/tabhelper.py new file mode 100644 index 0000000..5a61aa7 --- /dev/null +++ b/src/interface/utils/tabhelper.py @@ -0,0 +1,22 @@ +from PySide6.QtWidgets import QWidget, QVBoxLayout, QLayout + +# Tab creation helper function +def create_tab(title, items, tabs, stretch): + tab = QWidget() + layout = QVBoxLayout() + + for item in items: + if item is None: + continue + + if isinstance(item, QLayout): + layout.addLayout(item) + else: + layout.addWidget(item) + + if stretch: + layout.addStretch() + + tab.setLayout(layout) + tabs.addTab(tab, title) + return tab diff --git a/src/main.py b/src/main.py index 2b47fe4..b7b3405 100644 --- a/src/main.py +++ b/src/main.py @@ -1,16 +1,16 @@ -from core.network.libtorrent_misc import send_notification, update_log, check_deleted_files -from core.utils.logging.loghandler import split_data, check_completed, check_downloads -from core.network.interface import list_interfaces, init_interfaces -from core.utils.logging.logs import get_download_logs -from core.utils.logging.logs import set_main_window -from core.network.libtorrent_int import check_space -from core.utils.general.shutdown import closehelper -from core.utils.general.wrappers import run_thread -from core.utils.general.shutdown import force_exit -from core.utils.config.config import read_config -from core.utils.logging.logs import consoleLog -from core.interface.gui import MainWindow -from core.utils.data.state import state +from network.libtorrent_misc import send_notification, update_log, check_deleted_files +from utils.logging.loghandler import split_data, check_completed, check_downloads +from network.interface import list_interfaces, init_interfaces +from utils.logging.logs import get_download_logs +from utils.logging.logs import set_main_window +from network.libtorrent_int import check_space +from utils.general.shutdown import closehelper +from utils.general.wrappers import run_thread +from utils.general.shutdown import force_exit +from utils.config.config import read_config +from utils.logging.logs import consoleLog +from interface.gui import MainWindow +from utils.data.state import state from PySide6 import QtWidgets from PySide6.QtCore import Qt import qdarktheme diff --git a/src/core/network/direct_download/__init__.py b/src/network/direct_download/__init__.py similarity index 80% rename from src/core/network/direct_download/__init__.py rename to src/network/direct_download/__init__.py index fdbc305..a082b51 100644 --- a/src/core/network/direct_download/__init__.py +++ b/src/network/direct_download/__init__.py @@ -1,9 +1,10 @@ +from utils.logging.logs import consoleLog, add_download_log +from utils.data.state import state from typing import Optional -from core.utils.data.state import state -from core.utils.logging.logs import consoleLog, add_download_log -from .handle import DirectDownloadHandle -from .utils import ( + +from network.direct_download.handle import DirectDownloadHandle +from network.direct_download.utils import ( sanitize_filename, extract_filename_from_url, detect_filename_from_headers, diff --git a/src/core/network/direct_download/handle.py b/src/network/direct_download/handle.py similarity index 98% rename from src/core/network/direct_download/handle.py rename to src/network/direct_download/handle.py index 4b4def6..023191b 100644 --- a/src/core/network/direct_download/handle.py +++ b/src/network/direct_download/handle.py @@ -1,8 +1,8 @@ -from core.utils.logging.logs import consoleLog, update_download_completed +from network.direct_download.status import DirectDownloadStatus, ChunkSpec +from utils.logging.logs import consoleLog, update_download_completed from concurrent.futures import ThreadPoolExecutor, as_completed -from .status import DirectDownloadStatus, ChunkSpec -from core.utils.data.state import state -from .utils import format_size +from network.direct_download.utils import format_size +from utils.data.state import state from typing import Optional import libtorrent as lt import threading diff --git a/src/core/network/direct_download/status.py b/src/network/direct_download/status.py similarity index 100% rename from src/core/network/direct_download/status.py rename to src/network/direct_download/status.py diff --git a/src/core/network/direct_download/utils.py b/src/network/direct_download/utils.py similarity index 97% rename from src/core/network/direct_download/utils.py rename to src/network/direct_download/utils.py index 238c6a7..2b70b58 100644 --- a/src/core/network/direct_download/utils.py +++ b/src/network/direct_download/utils.py @@ -1,5 +1,5 @@ -from core.utils.logging.logs import consoleLog from urllib.parse import urlparse, unquote +from utils.logging.logs import consoleLog from typing import Optional import requests import os diff --git a/src/core/network/interface.py b/src/network/interface.py similarity index 95% rename from src/core/network/interface.py rename to src/network/interface.py index ff07f34..aca0e20 100644 --- a/src/core/network/interface.py +++ b/src/network/interface.py @@ -1,5 +1,5 @@ -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.logging.logs import consoleLog +from utils.data.state import state import psutil def get_net_interfaces(): diff --git a/src/core/network/libtorrent_int.py b/src/network/libtorrent_int.py similarity index 96% rename from src/core/network/libtorrent_int.py rename to src/network/libtorrent_int.py index 931d223..db31889 100644 --- a/src/core/network/libtorrent_int.py +++ b/src/network/libtorrent_int.py @@ -1,7 +1,7 @@ -from core.network.interface import get_interface_ip -from core.utils.general.wrappers import run_thread -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from network.interface import get_interface_ip +from utils.general.wrappers import run_thread +from utils.logging.logs import consoleLog +from utils.data.state import state import libtorrent as lt import threading import platform @@ -23,7 +23,7 @@ def get_free_space_mb(dirname): def check_space(): while not state.shutdown_event.is_set(): - for magnet_uri, magnetdl in list(state.active_downloads.items()): + for _, magnetdl in list(state.active_downloads.items()): try: status = magnetdl.status() except RuntimeError: @@ -79,7 +79,6 @@ def add_download(magnet_uri): state.active_downloads = {} init_session() - free_space = get_free_space_mb(state.download_path) if magnet_uri in state.active_downloads: @@ -110,7 +109,6 @@ def add_download(magnet_uri): params.save_path = state.download_path handle = state.dl_session.add_torrent(params) - while not handle.has_metadata(): time.sleep(1) diff --git a/src/core/network/libtorrent_misc.py b/src/network/libtorrent_misc.py similarity index 95% rename from src/core/network/libtorrent_misc.py rename to src/network/libtorrent_misc.py index 729809c..eeb471e 100644 --- a/src/core/network/libtorrent_misc.py +++ b/src/network/libtorrent_misc.py @@ -1,6 +1,6 @@ -from core.utils.logging.logs import update_download_completed_by_hash -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.logging.logs import update_download_completed_by_hash +from utils.logging.logs import consoleLog +from utils.data.state import state from plyer import notification import libtorrent as lt import time @@ -17,9 +17,6 @@ def cleanup_session(): state.dl_session = None state.active_downloads.clear() - - - def send_notification(shutdown_event): notified = set() while not shutdown_event.is_set(): diff --git a/src/core/network/libtorrent_wrapper.py b/src/network/libtorrent_wrapper.py similarity index 68% rename from src/core/network/libtorrent_wrapper.py rename to src/network/libtorrent_wrapper.py index da9f4c2..bcf51a4 100644 --- a/src/core/network/libtorrent_wrapper.py +++ b/src/network/libtorrent_wrapper.py @@ -1,5 +1,5 @@ -from core.network.libtorrent_int import add_download -from core.utils.logging.logs import consoleLog +from network.libtorrent_int import add_download +from utils.logging.logs import consoleLog def add_magnet(uri): diff --git a/src/core/utils/config/config.py b/src/utils/config/config.py similarity index 98% rename from src/core/utils/config/config.py rename to src/utils/config/config.py index 651c125..3036ea8 100644 --- a/src/core/utils/config/config.py +++ b/src/utils/config/config.py @@ -1,4 +1,4 @@ -from core.utils.data.state import state +from utils.data.state import state import configparser import platform import os diff --git a/src/core/utils/config/settings.py b/src/utils/config/settings.py similarity index 85% rename from src/core/utils/config/settings.py rename to src/utils/config/settings.py index 9dd986b..1d330fc 100644 --- a/src/core/utils/config/settings.py +++ b/src/utils/config/settings.py @@ -1,7 +1,8 @@ -from core.network.libtorrent_int import update_settings -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state -from .config import create_config +from network.libtorrent_int import update_settings +from utils.config.config import create_config +from utils.logging.logs import consoleLog +from utils.data.state import state + def save_settings(close=lambda: None, apiurl=None, download_path=None, down_speed_limit=None, up_speed_limit=None, image_path=None, autoresume=None, max_connections=None, max_downloads=None, bound_interface=None): diff --git a/src/core/utils/data/models.py b/src/utils/data/models.py similarity index 100% rename from src/core/utils/data/models.py rename to src/utils/data/models.py diff --git a/src/core/utils/data/state.py b/src/utils/data/state.py similarity index 75% rename from src/core/utils/data/state.py rename to src/utils/data/state.py index a98223a..75c3e79 100644 --- a/src/core/utils/data/state.py +++ b/src/utils/data/state.py @@ -9,43 +9,44 @@ class AppState(QObject): def __init__(self): super().__init__() - self.posts: list[Dict[str,str]] | None = None # titles, urls, author, seederm leecher - self.version: str = "dev" - self._image_path: str = "" - self.ignore_updates: bool = False - self.debug: bool = False - self.autoresume: bool = True - self.currenttracker: str = "rutracker" - self.trackertable: QTableWidget - self.trackers: Dict[str,Dict[str,Any]] = {} # each tracker should add itself here - ''' - an example: - "rutracker" : { - "name" : "rutracker", # name of the tracker - "headers" : ["author", "title"], # keys shown in the table - "scrapeFunc" : function, - } - ''' - self.api_url: str = "https://api.michijackson.xyz" - self.seeded_magnets: set = set() + # General + self.version: str = "Unknown" + self.debug: bool = False + self.main_window: Any = None + self.loop_running: bool = False + self.shutdown_event = threading.Event() + self.log_buffer: List[str] = [] + + # Paths + self.settings_path: str = "" + self._image_path: str = "" self.download_path: str = str(Path.home() / "Downloads") + + # GUI + self.window_transparency: bool = False + self.trackertable: QTableWidget + self.interfaces: List = [] + self.active_interfaces: List = [] + self.bound_interface: Any = None + + # Trackers / Scraping + self.posts: list[Dict[str,str]] | None = None # titles, urls, author, seeders, leechers + self.currenttracker: str = "rutracker" + self.api_url: str = "https://api.michijackson.xyz" + self.trackers: Dict[str,Dict[str,Any]] = {} + + # LibTorrent / Download related stuff + self.dl_session: Any = None + self.active_downloads: Dict = {} + self.seeded_magnets: set = set() + self.ignore_updates: bool = False + self.autoresume: bool = True self.up_speed_limit: int = 0 self.down_speed_limit: int = 0 self.max_connections: int = 200 self.max_downloads: int = 10 - self.settings_path: str = "" - self.dl_session: Any = None - self.active_downloads: Dict = {} - self.window_transparency: bool = False - self.interfaces: List = [] - self.active_interfaces: List = [] - self.bound_interface: Any = None - self.log_buffer: List[str] = [] self.downloads_lock = threading.RLock() - self.main_window: Any = None - self.loop_running: bool = False - self.shutdown_event = threading.Event() @property def image_path(self) -> str: diff --git a/src/core/utils/data/tracker.py b/src/utils/data/tracker.py similarity index 94% rename from src/core/utils/data/tracker.py rename to src/utils/data/tracker.py index eed9bc3..85ee3f1 100644 --- a/src/core/utils/data/tracker.py +++ b/src/utils/data/tracker.py @@ -1,4 +1,4 @@ -from core.utils.logging.logs import consoleLog +from utils.logging.logs import consoleLog from bs4 import BeautifulSoup import requests diff --git a/src/core/utils/general/shutdown.py b/src/utils/general/shutdown.py similarity index 61% rename from src/core/utils/general/shutdown.py rename to src/utils/general/shutdown.py index 1ec60fd..b3aac38 100644 --- a/src/core/utils/general/shutdown.py +++ b/src/utils/general/shutdown.py @@ -1,6 +1,6 @@ -from core.network.libtorrent_misc import cleanup_session -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from network.libtorrent_misc import cleanup_session +from utils.logging.logs import consoleLog +from utils.data.state import state import os def closehelper(): diff --git a/src/core/utils/general/wrappers.py b/src/utils/general/wrappers.py similarity index 84% rename from src/core/utils/general/wrappers.py rename to src/utils/general/wrappers.py index 8046354..4e9e999 100644 --- a/src/core/utils/general/wrappers.py +++ b/src/utils/general/wrappers.py @@ -1,4 +1,4 @@ -from core.utils.logging.logs import consoleLog +from utils.logging.logs import consoleLog def run_thread(thread): target_name = thread._target.__name__ diff --git a/src/core/utils/logging/loghandler.py b/src/utils/logging/loghandler.py similarity index 85% rename from src/core/utils/logging/loghandler.py rename to src/utils/logging/loghandler.py index c105618..ae52c8b 100644 --- a/src/core/utils/logging/loghandler.py +++ b/src/utils/logging/loghandler.py @@ -1,5 +1,5 @@ -from core.utils.network.download import run_download_direct, seed_magnet -from core.utils.logging.logs import consoleLog, remove_download_log +from utils.network.download import run_download_direct, seed_magnet +from utils.logging.logs import consoleLog, remove_download_log import os def split_data(data): @@ -18,7 +18,7 @@ def check_completed(downloads, resume): run_download_direct(download.magnet_uri, download.title) consoleLog(f"Resuming Magnet: {download.title}") elif download.url: - from core.network.direct_download import add_direct_download + from network.direct_download import add_direct_download add_direct_download(download.url, download.title) consoleLog(f"Resuming Direct Download: {download.title}") diff --git a/src/core/utils/logging/logs.py b/src/utils/logging/logs.py similarity index 97% rename from src/core/utils/logging/logs.py rename to src/utils/logging/logs.py index 036864b..da8ade4 100644 --- a/src/core/utils/logging/logs.py +++ b/src/utils/logging/logs.py @@ -1,5 +1,5 @@ -from core.utils.data.models import Download, DownloadList -from core.utils.data.state import state +from utils.data.models import Download, DownloadList +from utils.data.state import state from dataclasses import asdict from datetime import datetime import json @@ -243,7 +243,7 @@ def set_main_window(window): def flush_log_buffer(): # credits to claude if state.log_buffer: try: - from core.interface.gui import MainWindow + from interface.gui import MainWindow for log_entry in state.log_buffer: MainWindow.add_log(log_entry) state.log_buffer = [] @@ -256,7 +256,7 @@ def consoleLog(text, printAnyways = False): formatted_text = f"[{current_time}] {text}" try: - from core.interface.gui import MainWindow + from interface.gui import MainWindow if not MainWindow.add_log(formatted_text): state.log_buffer.append(formatted_text) except Exception: diff --git a/src/core/utils/network/download.py b/src/utils/network/download.py similarity index 74% rename from src/core/utils/network/download.py rename to src/utils/network/download.py index 5ea18fb..86a8152 100644 --- a/src/core/utils/network/download.py +++ b/src/utils/network/download.py @@ -1,11 +1,11 @@ -from core.network.direct_download import add_direct_download -from core.network.libtorrent_wrapper import add_magnet -from core.utils.logging.logs import add_download_log -from core.utils.general.wrappers import run_thread -from core.network.libtorrent_int import add_seed +from network.direct_download import add_direct_download +from network.libtorrent_wrapper import add_magnet from PySide6.QtWidgets import QTableWidgetItem -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.logging.logs import add_download_log +from utils.general.wrappers import run_thread +from network.libtorrent_int import add_seed +from utils.logging.logs import consoleLog +from utils.data.state import state from PySide6.QtCore import Qt from typing import Optional import threading @@ -31,9 +31,9 @@ def download_selected(items: list[QTableWidgetItem]): run_thread(threading.Thread(target=run_download, args=(post,))) def run_download(post, headers: Optional[dict] = None): - linkfunc = state.trackers[state.currenttracker]["linkFunc"] - ismagnet = state.trackers[state.currenttracker]["isMagnet"] - result = linkfunc(post) + link_method = state.trackers[state.currenttracker].get_download_link + ismagnet = state.trackers[state.currenttracker].is_magnet + result = link_method(post) if ismagnet: link = result diff --git a/src/core/utils/network/jsonhandler.py b/src/utils/network/jsonhandler.py similarity index 100% rename from src/core/utils/network/jsonhandler.py rename to src/utils/network/jsonhandler.py diff --git a/src/core/utils/network/update_checker.py b/src/utils/network/update_checker.py similarity index 93% rename from src/core/utils/network/update_checker.py rename to src/utils/network/update_checker.py index b8ecb24..efd574c 100644 --- a/src/core/utils/network/update_checker.py +++ b/src/utils/network/update_checker.py @@ -1,9 +1,8 @@ -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from utils.logging.logs import consoleLog +from utils.data.state import state import requests - def get_updates(): url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases/latest" try: diff --git a/src/core/utils/network/updater.py b/src/utils/network/updater.py similarity index 94% rename from src/core/utils/network/updater.py rename to src/utils/network/updater.py index 7acf76d..118cb4d 100644 --- a/src/core/utils/network/updater.py +++ b/src/utils/network/updater.py @@ -1,6 +1,6 @@ -from core.network.direct_download.handle import DirectDownloadHandle -from core.utils.logging.logs import consoleLog -from core.utils.data.state import state +from network.direct_download.handle import DirectDownloadHandle +from utils.logging.logs import consoleLog +from utils.data.state import state from PySide6.QtCore import Qt from PySide6 import QtWidgets import libtorrent as lt