From 95299340d38a7777303ad270e4748b93edfb0d18 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Mon, 12 Feb 2024 17:43:48 +0300 Subject: [PATCH 1/9] Initial commit to next --- po/Varia.pot | 15 + src/download/actionrow.py | 116 +++++ src/download/communicate.py | 75 +++ src/download/thread.py | 180 +++++++ src/gtk/help-overlay.ui | 29 -- src/initiate.py | 83 ++++ src/meson.build | 25 +- src/varia-py.in | 29 +- src/varia.gresource.xml | 6 - src/varia.in | 3 +- src/variamain.py | 936 +++--------------------------------- src/window/content.py | 58 +++ src/window/preferences.py | 362 ++++++++++++++ src/window/sidebar.py | 153 ++++++ 14 files changed, 1146 insertions(+), 924 deletions(-) create mode 100644 src/download/actionrow.py create mode 100644 src/download/communicate.py create mode 100644 src/download/thread.py delete mode 100644 src/gtk/help-overlay.ui create mode 100644 src/initiate.py delete mode 100644 src/varia.gresource.xml create mode 100644 src/window/content.py create mode 100644 src/window/preferences.py create mode 100644 src/window/sidebar.py diff --git a/po/Varia.pot b/po/Varia.pot index 79fcfb2..128d74f 100644 --- a/po/Varia.pot +++ b/po/Varia.pot @@ -191,6 +191,21 @@ msgstr "" msgid "Failed to open directory." msgstr "" +msgid "Remote Mode" +msgstr "" + +msgid "Remote aria2 IP" +msgstr "" + +msgid "Remote aria2 Port" +msgstr "" + +msgid "Remote aria2 RPC Secret" +msgstr "" + +msgid "Remote Download Location" +msgstr "" + #: src/gtk/help-overlay.ui:11 msgctxt "shortcut window" msgid "General" diff --git a/src/download/actionrow.py b/src/download/actionrow.py new file mode 100644 index 0000000..2ddc49c --- /dev/null +++ b/src/download/actionrow.py @@ -0,0 +1,116 @@ +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw + +def on_download_clicked(button, self, entry, DownloadThread): + url = entry.get_text() + entry.set_text("") + if url: + objectlist = create_actionrow(self, url) + download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3]) + self.downloads.append(download_thread) + download_thread.start() + self.filter_download_list("no", self.applied_filter) + +def create_actionrow(self, url): + filename = url.split("/")[-1].split("?")[0] + filename_shortened = filename[:40] + if (filename != filename_shortened): + filename_shortened = filename_shortened + "..." + + download_item = Adw.Bin() + style_context = download_item.get_style_context() + style_context.add_class('card') + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + box_1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + box_1.set_margin_bottom(10) + + box_2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + box_2.set_margin_start(10) + box_2.set_margin_end(10) + box_2.set_margin_top(10) + box_2.set_margin_bottom(10) + + download_item.set_child(box_2) + + filename_label = Gtk.Label(label=filename_shortened) + filename_label.set_halign(Gtk.Align.START) + box.append(filename_label) + + progress_bar = Gtk.ProgressBar() + + speed_label = Gtk.Label() + speed_label.set_halign(Gtk.Align.START) + box.append(speed_label) + + button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + + self.pause_buttons.append(Gtk.Button.new_from_icon_name("media-playback-pause-symbolic")) + self.pause_buttons[len(self.pause_buttons)-1].connect("clicked", on_pause_clicked, self, self.pause_buttons[len(self.pause_buttons)-1], download_item) + button_box.append(self.pause_buttons[len(self.pause_buttons)-1]) + + stop_button = Gtk.Button.new_from_icon_name("process-stop-symbolic") + stop_button.connect("clicked", on_stop_clicked, self, download_item) + button_box.append(stop_button) + + box_1.append(box) + + box_1_expanding_box = Gtk.Box() + Gtk.Widget.set_hexpand(box_1_expanding_box, True) + box_1.append(box_1_expanding_box) + + box_1.append(button_box) + box_2.append(box_1) + box_2.append(progress_bar) + + self.download_list.append(download_item) + download_item.index = len(self.downloads)-1 + + return [progress_bar, speed_label, self.pause_buttons[len(self.pause_buttons)-1], download_item] + +def on_pause_clicked(button, self, pause_button, download_item): + self.all_paused = False + download_thread = self.downloads[download_item.index+1] + if download_thread.download.is_paused: + download_thread.resume() + image = Gtk.Image.new() + image.set_from_icon_name("media-playback-pause-symbolic") + pause_button.set_child(image) + self.all_paused = False + self.header_pause_content.set_icon_name("media-playback-pause-symbolic") + self.header_pause_content.set_label(_("Pause All")) + self.header_pause_button.set_sensitive(True) + else: + download_thread.pause() + image = Gtk.Image.new() + image.set_from_icon_name("media-playback-start-symbolic") + pause_button.set_child(image) + download_thread.save_state() + + all_paused = True + for download_thread in self.downloads: + if (download_thread.download): + if (download_thread.download.is_paused) == False: + all_paused = False + if (all_paused == True): + self.all_paused = True + self.header_pause_content.set_icon_name("media-playback-start-symbolic") + self.header_pause_content.set_label(_("Resume All")) + self.header_pause_button.set_sensitive(True) + +def on_stop_clicked(button, self, download_item): + index = download_item.index + download_thread = self.downloads[index+1] + try: + download_thread.stop(True) + except: + pass + self.download_list.remove(download_item) + if (download_item in self.downloads): + self.downloads.remove(download_item) + if (self.download_list.get_first_child() == None): + self.header_pause_content.set_icon_name("media-playback-pause-symbolic") + self.header_pause_content.set_label(_("Pause All")) + self.header_pause_button.set_sensitive(False) diff --git a/src/download/communicate.py b/src/download/communicate.py new file mode 100644 index 0000000..388ef90 --- /dev/null +++ b/src/download/communicate.py @@ -0,0 +1,75 @@ +import requests +import json + +def set_speed_limit(self, download_limit): + self.sidebar_speed_limited_label.set_text("") + if ((download_limit[:-1] != "0") and (self.appconf["download_speed_limit_enabled"] == "1")): + self.sidebar_speed_limited_label.set_text(_("Speed limited")) + self.sidebar_speed_limited_label.get_style_context().add_class('warning') + else: + download_limit = "0K" + + token = "token:" + self.appconf['remote_secret'] + json_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.changeGlobalOption", + "params": [ + token, + {"max-overall-download-limit": download_limit} + ], + } + + response = requests.post(self.aria2cLocation + '/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) + +def set_aria2c_download_directory(self): + token = "token:" + self.appconf['remote_secret'] + if (self.appconf["remote"] == '0'): + json_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.changeGlobalOption", + "params": [ + token, + {"dir": self.appconf["download_directory"]} + ], + } + else: + json_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.changeGlobalOption", + "params": [ + token, + {"dir": self.appconf["remote_location"]} + ], + } + + response = requests.post(self.aria2cLocation + '/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) + +def set_aria2c_download_simultaneous_amount(self): + downloads_that_will_restart = [] + + for download_thread in self.downloads: + if (download_thread.download): + if (download_thread.return_is_paused() == False): + downloads_that_will_restart.append(download_thread.return_gid()) + download_thread.download.pause() + + token = "token:" + self.appconf['remote_secret'] + json_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.changeGlobalOption", + "params": [ + token, + {"max-concurrent-downloads": str(self.appconf["download_simultaneous_amount"])} + ] + } + + response = requests.post(self.aria2cLocation + '/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) + + for download_thread in self.downloads: + if (download_thread.download): + if (download_thread.return_gid() in downloads_that_will_restart): + download_thread.download.resume() diff --git a/src/download/thread.py b/src/download/thread.py new file mode 100644 index 0000000..d824784 --- /dev/null +++ b/src/download/thread.py @@ -0,0 +1,180 @@ +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw, GLib, Gio +import threading +from urllib.parse import unquote, urlparse +import requests +import textwrap +import time +import os +import json + +class DownloadThread(threading.Thread): + def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow): + threading.Thread.__init__(self) + self.api = app.api + self.downloaddir = app.appconf["download_directory"] + self.download = None + self.url = url + self.speed_label = speed_label + self.stop_event = threading.Event() + self.auth = app.appconf["auth"] + self.auth_username = app.appconf["auth_username"] + self.auth_password = app.appconf["auth_password"] + self.progress_bar = progress_bar + self.pause_button = pause_button + self.actionrow = actionrow + self.app = app + self.cancelled = False + + def is_valid_url(self): + try: + result = urlparse(self.url) + if not ((self.url[0:7] == "http://") or (self.url[0:8] == "https://")): + self.url = "http://" + self.url + return all([result.scheme, result.netloc]) + except ValueError: + return False + + def run(self): + if (self.url == "sus"): + try: + GLib.idle_add(self.show_message("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠟⠉⠉⠉⠉⠉⠉⠉⠙⠻⢶⣄⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣷⡀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⠀⣠⣶⠛⠛⠛⠛⠛⠛⠳⣦⡀⠀⠘⣿⡄⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠁⠀⢹⣿⣦⣀⣀⣀⣀⣀⣠⣼⡇⠀⠀⠸⣷⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡏⠀⠀⠀⠉⠛⠿⠿⠿⠿⠛⠋⠁⠀⠀⠀⠀⣿⡄⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⣸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣧⠀\n⠀⠀⠀⠀⠀⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⠀\n⠀⠀⠀⠀⠀⠀⠀⣾⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⠀⠀⠀⠀⢰⣿⠀⠀⠀⠀⣠⡶⠶⠿⠿⠿⠿⢷⣦⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⣀⣀⣀⠀⣸⡇⠀⠀⠀⠀⣿⡀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⣿⠀\n⣠⡿⠛⠛⠛⠛⠻⠀⠀⠀⠀⠀⢸⣇⠀⠀⠀⠀⠀⠀⣿⠇⠀⠀⠀⠀⠀⠀⣿⠀\n⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⡟⠀⠀⢀⣤⣤⣴⣿⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠈⠙⢷⣶⣦⣤⣤⣤⣴⣶⣾⠿⠛⠁⢀⣶⡟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡟⠀\n⠀⠀⠀⠀⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠈⣿⣆⡀⠀⠀⠀⠀⠀⠀⢀⣠⣴⡾⠃⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⢿⣿⣾⣿⡿⠿⠟⠋⠁⠀⠀⠀")) + except: + pass + self.pause_button.hide() + self.progress_bar.hide() + elif not (self.is_valid_url()): + try: + GLib.idle_add(self.show_message(_("This is not a valid URL."))) + print("Error: Not a valid url.") + except: + print("Error: Couldn't display 'not a valid url' error, for some reason.") + else: + response = requests.head(self.url) + if ((response.status_code == 401) and (self.auth == '1')): + if (self.url[0:7] == "http://"): + self.url = self.url[:7] + self.auth_username + ":" + self.auth_password + "@" + self.url[7:] + elif (self.url[0:8] == "https://"): + self.url = self.url[:8] + self.auth_username + ":" + self.auth_password + "@" + self.url[8:] + else: + self.url = self.auth_username + ":" + self.auth_password + "@" + self.url + print ("Authentication enabled.") + print(self.url) + self.download = self.api.add_uris([self.url]) + downloadname = self.download.name + print("Download added.\n" + self.downloaddir + "\n" + self.url) + GLib.idle_add(self.update_header_pause_button) + while (self.cancelled == False): + try: + self.download.update() + GLib.idle_add(self.update_labels_and_things) + if (self.download.is_complete): + if os.path.exists(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))): + os.remove(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))) + break + elif (self.download.status == "error"): + return + except: + return + time.sleep(1) + + def update_header_pause_button(self): + self.app.all_paused = False + self.app.header_pause_content.set_icon_name("media-playback-pause-symbolic") + self.app.header_pause_content.set_label(_("Pause All")) + self.app.header_pause_button.set_sensitive(True) + + def show_message(self, message): + self.speed_label.set_text(message) + + def update_labels_and_things(self): + self.progress_bar.set_fraction(self.download.progress / 100) + download_speed_mb = (self.download.download_speed / 1024 / 1024) + if int(str(download_speed_mb)[0]) == 0: + download_speed_kb = (self.download.download_speed / 1024) + if int(str(download_speed_kb)[0]) == 0: + self.speed_label.set_text(f"{round(self.download.progress)}% | {round(self.download.download_speed, 2)} B/s") + else: + self.speed_label.set_text(f"{round(self.download.progress)}% | {round(self.download.download_speed / 1024, 2)} KB/s") + else: + self.speed_label.set_text(f"{round(self.download.progress)}% | {round(self.download.download_speed / 1024 / 1024, 2)} MB/s") + + def pause(self): + if self.download: + if self.download.is_paused == False: + try: + self.download.pause() + print ("Download paused.") + except: + try: + self.download.pause([self.download.gid]) + print ("Download paused.") + except: + self.stop(False) + print ("Something went wrong when pausing. Stopping download without removing files.") + + def resume(self): + if self.download: + if self.download.is_paused == True: + try: + self.download.resume() + print ("Download resumed.") + except: + try: + self.download.pause([self.download.gid]) + print ("Download paused.") + except: + try: + self.speed_label.set_text(_("An error occurred:") + " " + self.download.error_message.split("status=")[1]) + print ("An error occurred when resuming. " + self.download.error_message.split("status=")[1]) + except: + pass + + def stop(self, deletefiles): + if self.download: + downloadgid = self.download.gid + downloadname = self.download.name + self.download.remove(force=True) + if not self.download.is_complete: + if (deletefiles == True): + if os.path.exists(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))): + os.remove(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))) + if os.path.exists(os.path.join(self.downloaddir, downloadname)): + os.remove(os.path.join(self.downloaddir, downloadname)) + print ("Download stopped.") + self.stop_event.set() + + def save_state(self): + if self.download: + try: + self.download.update() + except: + print ("Couldn't update the status of the download. Skipping state saving.") + return + state = { + 'url': self.url, + 'downloaded': self.download.completed_length, + } + with open(os.path.join(self.downloaddir, f'{self.download.gid}.varia.json'), 'w') as f: + json.dump(state, f) + print ("State saved for download.") + + def return_gid(self): + if self.download: + return self.download.gid + + def return_is_paused(self): + if (self.pause_button.get_child().get_icon_name() == "media-playback-pause-symbolic"): + return False + else: + return True + + @classmethod + def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow): + with open(os.path.join(app.appconf["download_directory"], filename), 'r') as f: + state = json.load(f) + os.remove(os.path.join(app.appconf["download_directory"], filename)) + instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow) + return instance diff --git a/src/gtk/help-overlay.ui b/src/gtk/help-overlay.ui deleted file mode 100644 index ef12f02..0000000 --- a/src/gtk/help-overlay.ui +++ /dev/null @@ -1,29 +0,0 @@ - - - - True - - - shortcuts - 10 - - - General - - - Show Shortcuts - win.show-help-overlay - - - - - Quit - app.quit - - - - - - - - diff --git a/src/initiate.py b/src/initiate.py new file mode 100644 index 0000000..ad99be4 --- /dev/null +++ b/src/initiate.py @@ -0,0 +1,83 @@ +import aria2p +import os +import json +import gi +import requests +import sys +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw, GLib + +def initiate(self): + self.downloaddir = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD) + + self.applied_filter = "show_all" + + remote_successful = False + + if (self.appconf['remote'] == '1'): + self.aria2cLocation = self.appconf['remote_protocol'] + self.appconf['remote_ip'] + ':' + self.appconf['remote_port'] + token = "token:" + self.appconf['remote_secret'] + json_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.tellActive", + "params":[token] + } + try: + response = requests.post(self.aria2cLocation + '/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) + print(response) + if (response.status_code == 200): + remote_successful = True + self.api = aria2p.API( + aria2p.Client( + host=self.appconf['remote_protocol'] + self.appconf['remote_ip'], + port=self.appconf['remote_port'], + secret=self.appconf['remote_secret'] + ) + ) + except: + pass + + if (self.appconf['remote'] == '1'): + if (remote_successful == False): + self.appconf["remote"] = "0" + self.save_appconf() + dialog = Adw.MessageDialog() + dialog.set_body(_("Couldn't connect to remote aria2c instance. Disabling remote mode. Please restart Varia.")) + dialog.add_response("ok", _("OK")) + dialog.set_default_response("ok") + dialog.set_close_response("ok") + dialog.connect("response", on_dialog_dismiss) + dialog.show() + return -1 + else: + self.api = aria2p.API( + aria2p.Client( + host="http://localhost", + port=6801 + ) + ) + + self.aria2cLocation = "http://localhost:6801" + + self.set_default_size(800, 600) + self.set_size_request(650, 450) + self.set_titlebar(Gtk.Box()) + + self.total_download_speed = "" + self.terminating = False + + self.set_title("Varia") + Gtk.Settings.get_default().set_property("gtk-icon-theme-name", "Adwaita") + + self.overlay_split_view = Adw.OverlaySplitView.new() + self.set_child(child=self.overlay_split_view) + + self.downloads = [] + self.pause_buttons = [] + self.all_paused = False + +def on_dialog_dismiss(dialog, response_id): + dialog.destroy() + sys.exit() diff --git a/src/meson.build b/src/meson.build index 98125d1..dee723a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,13 +1,7 @@ pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name() moduledir = pkgdatadir / 'varia' -gnome = import('gnome') - -gnome.compile_resources('varia', - 'varia.gresource.xml', - gresource_bundle: true, - install: true, - install_dir: pkgdatadir, -) +moduledir_window = pkgdatadir / 'varia' / 'window' +moduledir_download = pkgdatadir / 'varia' / 'download' python = import('python') @@ -37,6 +31,21 @@ configure_file( varia_sources = [ 'variamain.py', + 'initiate.py', +] + +varia_sources_window = [ + 'window/content.py', + 'window/sidebar.py', + 'window/preferences.py', +] + +varia_sources_download = [ + 'download/actionrow.py', + 'download/thread.py', + 'download/communicate.py', ] install_data(varia_sources, install_dir: moduledir) +install_data(varia_sources_window, install_dir: moduledir_window) +install_data(varia_sources_download, install_dir: moduledir_download) diff --git a/src/varia-py.in b/src/varia-py.in index b137e91..3f894dd 100755 --- a/src/varia-py.in +++ b/src/varia-py.in @@ -6,6 +6,7 @@ import sys import signal import locale import gettext +import json VERSION = '@VERSION@' pkgdatadir = '@pkgdatadir@' @@ -15,25 +16,21 @@ sys.path.insert(1, pkgdatadir) signal.signal(signal.SIGINT, signal.SIG_DFL) gettext.install('varia', localedir) +aria2cexec = sys.argv[1] + try: - locale.bindtextdomain('varia', localedir) - locale.textdomain('varia') + locale.bindtextdomain('varia', localedir) + locale.textdomain('varia') except: - print('Cannot set locale.') + print('Cannot set locale.') try: - gettext.bindtextdomain('varia', localedir) - gettext.textdomain('varia') + gettext.bindtextdomain('varia', localedir) + gettext.textdomain('varia') except: - print('Cannot load translations.') + print('Cannot load translations.') if __name__ == '__main__': - import gi - - from gi.repository import Gio - resource = Gio.Resource.load(os.path.join(pkgdatadir, 'varia.gresource')) - resource._register() - - mymodule_dir = os.path.join(pkgdatadir, 'varia') - sys.path.append( mymodule_dir ) - from variamain import main - sys.exit(main(VERSION)) + mymodule_dir = os.path.join(pkgdatadir, 'varia') + sys.path.append( mymodule_dir ) + from variamain import main + sys.exit(main(VERSION, aria2cexec)) diff --git a/src/varia.gresource.xml b/src/varia.gresource.xml deleted file mode 100644 index 6f8c048..0000000 --- a/src/varia.gresource.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - gtk/help-overlay.ui - - diff --git a/src/varia.in b/src/varia.in index 75f9781..0513a0d 100644 --- a/src/varia.in +++ b/src/varia.in @@ -12,5 +12,4 @@ then exit fi fi -$aria2cexec --enable-rpc --rpc-listen-port=6801 & -$pythonexec $pkgdatadir/../../bin/varia-py.py +$pythonexec $pkgdatadir/../../bin/varia-py.py "$aria2cexec" diff --git a/src/variamain.py b/src/variamain.py index 6f2fbe8..40aa1eb 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -6,234 +6,36 @@ import time import json import os -import time -import aria2p -import subprocess import threading +import subprocess from pathlib import Path gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, GLib, Gio -import requests -from urllib.parse import unquote, urlparse -import textwrap + +from window.sidebar import window_create_sidebar +from window.content import window_create_content +from download.actionrow import create_actionrow, on_pause_clicked, on_stop_clicked +from download.thread import DownloadThread +from download.communicate import set_speed_limit, set_aria2c_download_directory, set_aria2c_download_simultaneous_amount +from initiate import initiate class MainWindow(Gtk.Window): - def __init__(self, variaapp, *args, **kwargs): + def __init__(self, variaapp, appdir, appconf, *args, **kwargs): super().__init__(*args, **kwargs) - if "FLATPAK_ID" in os.environ: - self.appdir = os.path.join('/var', 'data') - else: - self.appdir = os.path.join(os.path.expanduser('~'), '.varia') - if not os.path.exists(self.appdir): - os.makedirs(self.appdir) - - self.appconf = {'download_speed_limit_enabled': '0', 'download_speed_limit': '0', 'auth': '0', 'auth_username': '', 'auth_password': '', 'download_directory': GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD), 'download_simultaneous_amount': '5'} - self.downloaddir = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD) - - self.applied_filter = "show_all" - - if os.path.exists(os.path.join(self.appdir, 'varia.conf')): - with open(os.path.join(self.appdir, 'varia.conf'), 'r') as f: - self.appconf.update(json.load(f)) - else: - with open(os.path.join(self.appdir, 'varia.conf'), 'w') as f: - json.dump(self.appconf, f) - - self.api = aria2p.API( - aria2p.Client( - host="http://localhost", - port=6801 - ) - ) - - self.set_default_size(800, 600) - self.set_size_request(650, 450) - self.set_titlebar(Gtk.Box()) - - self.total_download_speed = "" - self.terminating = False - - self.set_title("Varia") - Gtk.Settings.get_default().set_property("gtk-icon-theme-name", "Adwaita") - - self.overlay_split_view = Adw.OverlaySplitView.new() - self.set_child(child=self.overlay_split_view) - - self.downloads = [] - self.pause_buttons = [] - self.all_paused = False - - # Sidebar - sidebar_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - sidebar_content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - - header_bar = Adw.HeaderBar() - header_bar.get_style_context().add_class('flat') - sidebar_box.append(header_bar) - - preferences_button = Gtk.Button(tooltip_text=_("Preferences")) - preferences_button.set_icon_name("emblem-system-symbolic") - preferences_button.connect("clicked", self.show_preferences) - - hamburger_button = Gtk.MenuButton(tooltip_text=_("Other")) - hamburger_button.set_icon_name("open-menu-symbolic") - hamburger_menu_model = Gio.Menu() - - cancel_all_action = Gio.SimpleAction.new("cancel_all_downloads", None) - cancel_all_action.connect("activate", self.stop_all) - variaapp.add_action(cancel_all_action) - - about_action = Gio.SimpleAction.new("downloads_folder", None) - about_action.connect("activate", self.open_downloads_folder) - variaapp.add_action(about_action) - - downloads_folder_action = Gio.SimpleAction.new("about", None) - downloads_folder_action.connect("activate", self.show_about) - variaapp.add_action(downloads_folder_action) - - hamburger_menu_item_cancel_all = Gio.MenuItem.new(_("Cancel All"), "app.cancel_all_downloads") - hamburger_menu_model.append_item(hamburger_menu_item_cancel_all) - - hamburger_menu_item_open_downloads_folder = Gio.MenuItem.new(_("Open Download Folder"), "app.downloads_folder") - hamburger_menu_model.append_item(hamburger_menu_item_open_downloads_folder) - - hamburger_menu_item_about = Gio.MenuItem.new(_("About Varia"), "app.about") - hamburger_menu_model.append_item(hamburger_menu_item_about) - - hamburger_button.set_menu_model(hamburger_menu_model) - - header_bar.pack_start(preferences_button) - header_bar.pack_end(hamburger_button) - - download_entry = Gtk.Entry() - download_entry.set_placeholder_text(_("URL")) - - download_button = Gtk.Button(label=_("Download")) - download_button.get_style_context().add_class("pill") - download_button.get_style_context().add_class("suggested-action") - download_button.connect("clicked", self.on_download_clicked, download_entry) - - self.filter_button_show_all = Gtk.ToggleButton() - self.filter_button_show_all.get_style_context().add_class('flat') - filter_button_show_all_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - filter_button_show_all_box.set_margin_top(8) - filter_button_show_all_box.set_margin_bottom(8) - filter_button_show_all_box.append(Gtk.Image.new_from_icon_name("switch-off-symbolic")) - filter_button_show_all_label = Gtk.Label(label=_("All")) - filter_button_show_all_label.get_style_context().add_class('body') - filter_button_show_all_box.append(filter_button_show_all_label) - self.filter_button_show_all.set_child(filter_button_show_all_box) - self.filter_button_show_all.set_active(True) - self.filter_button_show_all.connect("clicked", self.filter_download_list, "show_all") - - self.filter_button_show_downloading = Gtk.ToggleButton() - self.filter_button_show_downloading.get_style_context().add_class('flat') - filter_button_show_downloading_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - filter_button_show_downloading_box.set_margin_top(8) - filter_button_show_downloading_box.set_margin_bottom(8) - filter_button_show_downloading_box.append(Gtk.Image.new_from_icon_name("content-loading-symbolic")) - filter_button_show_downloading_label = Gtk.Label(label=_("In Progress")) - filter_button_show_downloading_label.get_style_context().add_class('body') - filter_button_show_downloading_box.append(filter_button_show_downloading_label) - self.filter_button_show_downloading.set_child(filter_button_show_downloading_box) - self.filter_button_show_downloading.connect("clicked", self.filter_download_list, "show_downloading") - - self.filter_button_show_completed = Gtk.ToggleButton() - self.filter_button_show_completed.get_style_context().add_class('flat') - filter_button_show_completed_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - filter_button_show_completed_box.set_margin_top(8) - filter_button_show_completed_box.set_margin_bottom(8) - filter_button_show_completed_box.append(Gtk.Image.new_from_icon_name("emblem-ok-symbolic")) - filter_button_show_completed_label = Gtk.Label(label=_("Completed")) - filter_button_show_completed_label.get_style_context().add_class('body') - filter_button_show_completed_box.append(filter_button_show_completed_label) - self.filter_button_show_completed.set_child(filter_button_show_completed_box) - self.filter_button_show_completed.connect("clicked", self.filter_download_list, "show_completed") - - sidebar_separator = Gtk.Separator() - sidebar_separator.set_margin_top(8) - sidebar_separator.set_margin_bottom(8) - - sidebar_expanding_box = Gtk.Box() - Gtk.Widget.set_vexpand(sidebar_expanding_box, True) - - self.sidebar_speed_limited_label = Gtk.Label() - - sidebar_content_box.set_margin_start(6) - sidebar_content_box.set_margin_end(6) - sidebar_content_box.set_margin_top(6) - sidebar_content_box.set_margin_bottom(6) - - sidebar_filter_buttons_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) - sidebar_filter_buttons_box.append(self.filter_button_show_all) - sidebar_filter_buttons_box.append(self.filter_button_show_downloading) - sidebar_filter_buttons_box.append(self.filter_button_show_completed) - - sidebar_content_box.append(download_entry) - sidebar_content_box.append(download_button) - sidebar_content_box.append(sidebar_separator) - sidebar_content_box.append(sidebar_filter_buttons_box) - sidebar_content_box.append(sidebar_expanding_box) - sidebar_content_box.append(self.sidebar_speed_limited_label) - sidebar_box.append(sidebar_content_box) - - self.overlay_split_view.set_sidebar(sidebar_box) - - # Content - content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - header_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - - self.total_download_speed_label = Gtk.Label(label=self.total_download_speed) - - header_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) - - self.header_pause_content = Adw.ButtonContent() - self.header_pause_content.set_icon_name("media-playback-pause-symbolic") - self.header_pause_content.set_label(_("Pause All")) - self.header_pause_button = Gtk.Button() - self.header_pause_button.set_sensitive(False) - self.header_pause_button.set_child(self.header_pause_content) - self.header_pause_button.connect("clicked", lambda button: self.pause_all(self.header_pause_content)) - - header_button_box.append(self.header_pause_button) - - header_expanding_box = Gtk.Box() - Gtk.Widget.set_hexpand(header_expanding_box, True) - header_expanding_box_1 = Gtk.Box() - Gtk.Widget.set_hexpand(header_expanding_box_1, True) - - header_box.append(header_expanding_box) - header_box.append(self.total_download_speed_label) - header_box.append(header_expanding_box_1) - header_box.append(header_button_box) - - header_bar = Adw.HeaderBar() - header_bar.get_style_context().add_class('flat') - header_bar.set_title_widget(header_box) - content_box.append(header_bar) - - self.download_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - self.download_list.set_margin_start(6) - self.download_list.set_margin_end(6) - self.download_list.set_margin_bottom(6) - self.download_list.set_margin_top(6) + self.appdir = appdir + self.appconf = appconf - scrolled_window = Gtk.ScrolledWindow() - scrolled_window.set_child(self.download_list) - scrolled_window.set_hexpand(True) - scrolled_window.set_vexpand(True) + # Set up variables and all: + aria2_connection_successful = initiate(self) - content_box.append(scrolled_window) + if (aria2_connection_successful == -1): + return - self.overlay_split_view.set_content(content_box) - - self.total_download_speed_calculator_thread = threading.Thread(target=self.total_download_speed_get, args=(self.downloads, self.total_download_speed_label)) - self.total_download_speed_calculator_thread.start() - - self.check_download_status_thread = threading.Thread(target=self.check_download_status) - self.check_download_status_thread.start() + # Create window contents: + window_create_sidebar(self, variaapp, DownloadThread, variaVersion) + window_create_content(self, threading) # Check if the download path still exists: if not (os.path.exists(self.appconf["download_directory"])): @@ -242,34 +44,28 @@ def __init__(self, variaapp, *args, **kwargs): # Set download speed limit from appconf: if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): - self.set_speed_limit(self.appconf["download_speed_limit"]) + set_speed_limit(self, self.appconf["download_speed_limit"]) # Set download speed limit from appconf: if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): - self.set_speed_limit(self.appconf["download_speed_limit"]) + set_speed_limit(self, self.appconf["download_speed_limit"]) # Set download directory from appconf: - self.set_aria2c_download_directory() + set_aria2c_download_directory(self) # Set the maximum simultaneous download amount from appconf: - self.set_aria2c_download_simultaneous_amount() + set_aria2c_download_simultaneous_amount(self) + # Load incomplete downloads: for filename in os.listdir(self.appconf["download_directory"]): if filename.endswith('.varia.json'): with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: state = json.load(f) - objectlist = self.create_actionrow(state['url']) + objectlist = create_actionrow(self, state['url']) download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3]) self.downloads.append(download_thread) download_thread.start() - def on_popover_menu_row_activated(self, listbox, row): - if row == self.hamburger_menu_item_open_downloads_folder: - self.open_downloads_folder(self) - elif row == self.hamburger_menu_item_about: - self.show_about(self) - self.hamburger_button.popdown() - def filter_download_list(self, button, filter_mode): if (button != "no"): self.filter_button_show_all.set_active(False) @@ -301,187 +97,6 @@ def filter_download_list(self, button, filter_mode): download_thread.actionrow.show() self.filter_button_show_completed.set_active(True) - def show_preferences(self, app): - preferences = Adw.PreferencesWindow(transient_for=self) - preferences.set_search_enabled(False) - - page = Adw.PreferencesPage(title=_("Preferences")) - preferences.add(page) - group_1 = Adw.PreferencesGroup() - #group_1.set_title(_("Basic")) - page.add(group_1) - #group_2 = Adw.PreferencesGroup() - #group_2.set_title(_("Advanced")) - #page.add(group_2) - - download_directory_actionrow = Adw.ActionRow() - download_directory_actionrow.set_title(_("Download Directory")) - download_directory_actionrow.set_subtitle(self.appconf["download_directory"]) - - download_directory_change_button = Gtk.Button(label=_("Change")) - download_directory_change_button.get_style_context().add_class("suggested-action") - download_directory_change_button.set_halign(Gtk.Align.START) - download_directory_change_button.set_valign(Gtk.Align.CENTER) - download_directory_change_button.connect("clicked", lambda clicked: self.on_download_directory_change(preferences, download_directory_actionrow)) - - download_directory_actionrow.add_suffix(download_directory_change_button) - - speed_limit_unit_names = Gtk.ListStore(int, str) - speed_limit_unit_names.append([1, _("KB/s")]) - speed_limit_unit_names.append([2, _("MB/s")]) - speed_limit_unit_names.append([3, _("GB/s")]) - speed_limit_unit_names_dropdown = Gtk.ComboBox.new_with_model(speed_limit_unit_names) - speed_limit_unit_names_dropdown.set_active(0) - speed_limit_unit_names_dropdown_renderer_text = Gtk.CellRendererText() - speed_limit_unit_names_dropdown.pack_start(speed_limit_unit_names_dropdown_renderer_text, True) - speed_limit_unit_names_dropdown.add_attribute(speed_limit_unit_names_dropdown_renderer_text, "text", 1) - speed_limit_unit_names_dropdown.connect("changed", lambda clicked: self.on_speed_limit_changed(speed_limit_entry, speed_limit_unit_names_dropdown, speed_limit_expander_switch)) - - speed_limit_expander_box = Adw.ExpanderRow() - speed_limit_expander_box.set_title(_("Speed Limit")) - - speed_limit_expander_switch = Gtk.Switch() - speed_limit_expander_switch.set_halign(Gtk.Align.START) - speed_limit_expander_switch.set_valign(Gtk.Align.CENTER) - speed_limit_expander_switch.connect("state-set", self.on_switch_speed_limit, preferences) - - speed_limit_entry = Adw.EntryRow() - speed_limit_entry.set_title(_("Speed")) - speed_limit_entry.set_input_purpose(Gtk.InputPurpose.NUMBER) - speed_limit_entry.set_show_apply_button(True) - speed_limit_entry.connect('changed', self.speed_limit_text_filter) - speed_limit_entry.connect('apply', lambda clicked: self.on_speed_limit_changed(speed_limit_entry, speed_limit_unit_names_dropdown, speed_limit_expander_switch)) - - if (self.appconf["download_speed_limit"] != "0"): - speed_limit_expander_switch.set_sensitive(True) - speed_limit_entry.set_text(self.appconf["download_speed_limit"][:-1]) - match (self.appconf["download_speed_limit"][-1]): - case "K": - speed_limit_unit_names_dropdown.set_active(0) - case "M": - speed_limit_unit_names_dropdown.set_active(1) - case "G": - speed_limit_unit_names_dropdown.set_active(2) - else: - speed_limit_expander_switch.set_sensitive(False) - - if (self.appconf["download_speed_limit_enabled"] == "1"): - speed_limit_expander_switch.set_active("active") - - speed_limit_expander_box.add_action(speed_limit_expander_switch) - - preferences_hexpanding_box_left = Gtk.Box() - Gtk.Widget.set_hexpand(preferences_hexpanding_box_left, True) - preferences_hexpanding_box_right = Gtk.Box() - Gtk.Widget.set_hexpand(preferences_hexpanding_box_right, True) - - speed_limit_dropdown_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) - speed_limit_dropdown_box.append(preferences_hexpanding_box_left) - speed_limit_dropdown_box.append(speed_limit_unit_names_dropdown) - speed_limit_dropdown_box.append(preferences_hexpanding_box_right) - - speed_limit_dropdown_box.set_margin_top(5) - speed_limit_dropdown_box.set_margin_bottom(5) - - speed_limit_expander_box.add_row(speed_limit_dropdown_box) - speed_limit_expander_box.add_row(speed_limit_entry) - - auth_expander = Adw.ExpanderRow() - auth_expander.set_title(_("Authentication")) - - auth_expander_switch = Gtk.Switch() - auth_expander_switch.set_halign(Gtk.Align.START) - auth_expander_switch.set_valign(Gtk.Align.CENTER) - auth_expander_switch.connect("state-set", self.on_switch_auth, preferences) - - username_entry = Adw.EntryRow() - username_entry.set_title(_("Username")) - username_entry.set_show_apply_button(True) - username_entry.connect('apply', lambda entry: self.set_auth_credentials(username_entry, password_entry, auth_expander_switch)) - - password_entry = Adw.PasswordEntryRow() - password_entry.set_title(_("Password")) - password_entry.set_show_apply_button(True) - password_entry.connect('apply', lambda entry: self.set_auth_credentials(username_entry, password_entry, auth_expander_switch)) - - username_entry.set_text(self.appconf["auth_username"]) - password_entry.set_text(self.appconf["auth_password"]) - - if ((self.appconf["auth_username"] != "") and (self.appconf["auth_password"] != "")): - auth_expander_switch.set_sensitive(True) - else: - auth_expander_switch.set_sensitive(False) - - if (self.appconf["auth"] == "1"): - auth_expander_switch.set_active("active") - - auth_expander.add_action(auth_expander_switch) - - auth_expander.add_row(username_entry) - auth_expander.add_row(password_entry) - - simultaneous_download_amount_unit_names = Gio.ListStore.new(Gtk.StringObject) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 1")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 2")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 3")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 4")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 5")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 6")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 7")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 8")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 9")) - simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 10")) - - simultaneous_download_amount_unit_names_box = Adw.ComboRow() - simultaneous_download_amount_unit_names_box.set_model(simultaneous_download_amount_unit_names) - simultaneous_download_amount_unit_names_box.set_title(_("Simultaneous Download Amount")) - simultaneous_download_amount_unit_names_box.set_selected(int(self.appconf["download_simultaneous_amount"])- 1) - simultaneous_download_amount_unit_names_box.connect("notify::selected", self.on_simultaneous_download_amount_changed) - - group_1.add(download_directory_actionrow) - group_1.add(speed_limit_expander_box) - group_1.add(auth_expander) - group_1.add(simultaneous_download_amount_unit_names_box) - - preferences.present() - - def on_switch_speed_limit(self, switch, state): - if state: - self.appconf["download_speed_limit_enabled"] = "1" - else: - self.appconf["download_speed_limit_enabled"] = "0" - self.set_speed_limit(self.appconf["download_speed_limit"]) - self.save_appconf() - - def on_speed_limit_changed(self, speed, speed_type): - speed = speed.get_text() - if (speed == ""): - speed = "0" - speed_type = speed_type.get_active() - match speed_type: - case 0: - download_limit = speed + "K" - case 1: - download_limit = speed + "M" - case 2: - download_limit = speed + "G" - - self.set_speed_limit(download_limit) - self.appconf = {'download_speed_limit': download_limit} - self.save_appconf() - - def on_switch_auth(self, switch, state): - if state: - self.appconf["auth"] = "1" - else: - self.appconf["auth"] = "0" - self.save_appconf() - - def set_auth_credentials(self, username_entry, password_entry): - self.appconf["auth_username"] = username_entry.get_text() - self.appconf["auth_password"] = password_entry.get_text() - self.save_appconf() - def check_download_status(self): while (self.terminating == False): i = 0 @@ -541,118 +156,6 @@ def total_download_speed_get(self, downloads, total_download_speed_label): total_download_speed_label.set_text(str(round(total_download_speed / 1024 / 1024, 2)) + _(" MB/s")) time.sleep(1) - def create_actionrow(self, url): - filename = url.split("/")[-1].split("?")[0] - filename_shortened = filename[:40] - if (filename != filename_shortened): - filename_shortened = filename_shortened + "..." - - download_item = Adw.Bin() - style_context = download_item.get_style_context() - style_context.add_class('card') - - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box_1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - box_1.set_margin_bottom(10) - - box_2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box_2.set_margin_start(10) - box_2.set_margin_end(10) - box_2.set_margin_top(10) - box_2.set_margin_bottom(10) - - download_item.set_child(box_2) - - filename_label = Gtk.Label(label=filename_shortened) - filename_label.set_halign(Gtk.Align.START) - box.append(filename_label) - - progress_bar = Gtk.ProgressBar() - - speed_label = Gtk.Label() - speed_label.set_halign(Gtk.Align.START) - box.append(speed_label) - - button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) - - self.pause_buttons.append(Gtk.Button.new_from_icon_name("media-playback-pause-symbolic")) - self.pause_buttons[len(self.pause_buttons)-1].connect("clicked", self.on_pause_clicked, self.pause_buttons[len(self.pause_buttons)-1], download_item) - button_box.append(self.pause_buttons[len(self.pause_buttons)-1]) - - stop_button = Gtk.Button.new_from_icon_name("process-stop-symbolic") - stop_button.connect("clicked", self.on_stop_clicked, download_item) - button_box.append(stop_button) - - box_1.append(box) - - box_1_expanding_box = Gtk.Box() - Gtk.Widget.set_hexpand(box_1_expanding_box, True) - box_1.append(box_1_expanding_box) - - box_1.append(button_box) - box_2.append(box_1) - box_2.append(progress_bar) - - self.download_list.append(download_item) - download_item.index = len(self.downloads)-1 - - return [progress_bar, speed_label, self.pause_buttons[len(self.pause_buttons)-1], download_item] - - def on_download_clicked(self, button, entry): - url = entry.get_text() - entry.set_text("") - if url: - objectlist = self.create_actionrow(url) - download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3]) - self.downloads.append(download_thread) - download_thread.start() - self.filter_download_list("no", self.applied_filter) - - def on_pause_clicked(self, button, pause_button, download_item): - self.all_paused = False - download_thread = self.downloads[download_item.index+1] - if download_thread.download.is_paused: - download_thread.resume() - image = Gtk.Image.new() - image.set_from_icon_name("media-playback-pause-symbolic") - pause_button.set_child(image) - self.all_paused = False - self.header_pause_content.set_icon_name("media-playback-pause-symbolic") - self.header_pause_content.set_label(_("Pause All")) - self.header_pause_button.set_sensitive(True) - else: - download_thread.pause() - image = Gtk.Image.new() - image.set_from_icon_name("media-playback-start-symbolic") - pause_button.set_child(image) - download_thread.save_state() - - all_paused = True - for download_thread in self.downloads: - if (download_thread.download): - if (download_thread.download.is_paused) == False: - all_paused = False - if (all_paused == True): - self.all_paused = True - self.header_pause_content.set_icon_name("media-playback-start-symbolic") - self.header_pause_content.set_label(_("Resume All")) - self.header_pause_button.set_sensitive(True) - - def on_stop_clicked(self, button, download_item): - index = download_item.index - download_thread = self.downloads[index+1] - try: - download_thread.stop(True) - except: - pass - self.download_list.remove(download_item) - if (download_item in self.downloads): - self.downloads.remove(download_item) - if (self.download_list.get_first_child() == None): - self.header_pause_content.set_icon_name("media-playback-pause-symbolic") - self.header_pause_content.set_label(_("Pause All")) - self.header_pause_button.set_sensitive(False) - def pause_all(self, header_pause_content): i = 0 pause_button_images = [] @@ -698,367 +201,74 @@ def stop_all(self, app, variaapp): self.header_pause_content.set_label(_("Pause All")) self.header_pause_button.set_sensitive(False) - def set_speed_limit(self, download_limit): - self.sidebar_speed_limited_label.set_text("") - if ((download_limit[:-1] != "0") and (self.appconf["download_speed_limit_enabled"] == "1")): - self.sidebar_speed_limited_label.set_text(_("Speed limited")) - self.sidebar_speed_limited_label.get_style_context().add_class('warning') - else: - download_limit = "0K" - - json_request = { - "jsonrpc": "2.0", - "id": "1", - "method": "aria2.changeGlobalOption", - "params": [ - {"max-overall-download-limit": download_limit} - ] - } - - response = requests.post('http://localhost:6801/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) - - def set_aria2c_download_directory(self): - json_request = { - "jsonrpc": "2.0", - "id": "1", - "method": "aria2.changeGlobalOption", - "params": [ - {"dir": self.appconf["download_directory"]} - ] - } - - response = requests.post('http://localhost:6801/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) - - def set_aria2c_download_simultaneous_amount(self): - downloads_that_will_restart = [] - - for download_thread in self.downloads: - if (download_thread.download): - if (download_thread.return_is_paused() == False): - downloads_that_will_restart.append(download_thread.return_gid()) - download_thread.download.pause() - - json_request = { - "jsonrpc": "2.0", - "id": "1", - "method": "aria2.changeGlobalOption", - "params": [ - {"max-concurrent-downloads": str(self.appconf["download_simultaneous_amount"])} - ] - } - - response = requests.post('http://localhost:6801/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) - - for download_thread in self.downloads: - if (download_thread.download): - if (download_thread.return_gid() in downloads_that_will_restart): - download_thread.download.resume() - def save_appconf(self): with open(os.path.join(self.appdir, 'varia.conf'), 'w') as f: json.dump(self.appconf, f) print("Config saved") - def open_downloads_folder(self, app, variaapp): - if (os.name == 'nt'): - os.startfile(self.appconf["download_directory"]) - else: - subprocess.Popen(["xdg-open", self.appconf["download_directory"]]) - - def show_about(self, app, variaapp): - dialog = Adw.AboutWindow(transient_for=self) - dialog.set_application_name("Varia") - dialog.set_version(variaVersion) - dialog.set_developer_name("Giant Pink Robots!") - dialog.set_license_type(Gtk.License(Gtk.License.MPL_2_0)) - dialog.set_comments(_("aria2 based download manager utilizing GTK4 and Libadwaita.")) - dialog.set_website("https://github.com/giantpinkrobots/varia") - dialog.set_issue_url("https://github.com/giantpinkrobots/varia/issues") - dialog.set_copyright("2023 Giant Pink Robots!\n\n" + _("This application relies on the following pieces of software:") + - "\n\n- aria2\n- GTK4\n- Libadwaita\n- Meson\n- OpenSSL\n- Python-appdirs\n- Python-aria2p\n- Python-certifi\n- Python-charset-normalizer\n- Python-gettext\n- Python-idna\n- Python-loguru\n- Python-requests\n- Python-setuptools\n- Python-urllib3\n- Python-websocket-client\n\n" + - _("The licenses of all of these pieces of software can be found in the dependencies_information directory in this application's app directory.")) - dialog.set_developers(["Giant Pink Robots! (@giantpinkrobots) https://github.com/giantpinkrobots"]) - dialog.set_application_icon("io.github.giantpinkrobots.varia") - dialog.set_translator_credits(_("translator-credits")) - dialog.set_artists(["Jakub Steiner"]) - dialog.show() - - def speed_limit_text_filter(self, entry): - text = entry.get_text() - new_text = "" - for char in text: - if (char.isdigit()): - new_text += char - if (new_text != text): - GLib.idle_add(entry.set_text, new_text) - GLib.idle_add(entry.set_position, -1) - - def on_download_directory_change(self, prefswindow, actionrow): - Gtk.FileDialog().select_folder(None, None, self.on_download_directory_selected, prefswindow, actionrow) - - def on_download_directory_selected(self, dialog, result, prefswindow, actionrow): - try: - folder = dialog.select_folder_finish(result) - self.appconf["download_directory"] = folder.get_path() - self.save_appconf() - self.set_aria2c_download_directory() - actionrow.set_subtitle(self.appconf["download_directory"]) - except: - error_dialog = Adw.MessageDialog() - error_dialog.set_body(_("Failed to open directory.")) - error_dialog.add_response("ok", _("OK")) - error_dialog.set_default_response("ok") - error_dialog.set_close_response("ok") - error_dialog.set_transient_for(prefswindow) - error_dialog.connect("response", self.on_dialog_dismiss) - error_dialog.show() - - def on_dialog_dismiss(self, dialog, response_id): - dialog.destroy() - - def on_switch_speed_limit(self, switch, state, preferencesWindow): - if state: - self.appconf["download_speed_limit_enabled"] = "1" - else: - self.appconf["download_speed_limit_enabled"] = "0" - self.set_speed_limit(self.appconf["download_speed_limit"]) - - self.save_appconf() - - def on_speed_limit_changed(self, speed, speed_type, switch): - speed = speed.get_text() - if (speed == ""): - speed = "0" - - if (speed != "0"): - speed_type = speed_type.get_active() - match speed_type: - case 0: - download_limit = speed + "K" - case 1: - download_limit = speed + "M" - case 2: - download_limit = speed + "G" - else: - download_limit = speed - - self.set_speed_limit(download_limit) - self.appconf['download_speed_limit'] = download_limit - - if ((self.appconf["download_speed_limit"] == "0K") or (self.appconf["download_speed_limit"] == "0M") or (self.appconf["download_speed_limit"] == "0G")): - switch.set_active(False) - switch.set_sensitive(False) - else: - switch.set_sensitive(True) - - self.save_appconf() - - def on_switch_auth(self, switch, state, preferencesWindow): - if state: - self.appconf["auth"] = "1" - else: - self.appconf["auth"] = "0" - - self.save_appconf() - - def set_auth_credentials(self, username_entry, password_entry, switch): - self.appconf["auth_username"] = username_entry.get_text() - self.appconf["auth_password"] = password_entry.get_text() - - if ((self.appconf["auth_username"] == "") or (self.appconf["auth_password"] == "")): - switch.set_active(False) - switch.set_sensitive(False) - else: - switch.set_sensitive(True) - - self.save_appconf() - - def on_simultaneous_download_amount_changed(self, comborow, parameters): - self.appconf["download_simultaneous_amount"] = str(comborow.get_selected() + 1) - print(str(comborow.get_selected() + 1)) - self.save_appconf() - - self.set_aria2c_download_simultaneous_amount() - def exitProgram(self, app): self.terminating = True self.all_paused = False self.pause_all("no") - self.api.client.shutdown() + if (self.appconf['remote'] == '0'): + self.api.client.shutdown() self.destroy() -class DownloadThread(threading.Thread): - def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow): - threading.Thread.__init__(self) - self.api = app.api - self.downloaddir = app.appconf["download_directory"] - self.download = None - self.url = url - self.speed_label = speed_label - self.stop_event = threading.Event() - self.auth = app.appconf["auth"] - self.auth_username = app.appconf["auth_username"] - self.auth_password = app.appconf["auth_password"] - self.progress_bar = progress_bar - self.pause_button = pause_button - self.actionrow = actionrow - self.app = app - self.cancelled = False - - def is_valid_url(self): - if not ((self.url[0:7] == "http://") or (self.url[0:8] == "https://")): - self.url = "http://" + self.url - result = urlparse(self.url) - try: - return all([result.scheme, result.netloc]) - except ValueError: - return False - - def run(self): - if not (self.is_valid_url()): - try: - GLib.idle_add(self.show_message(_("This is not a valid URL."))) - print("Error: Not a valid url.") - except: - print("Error: Couldn't display 'not a valid url' error, for some reason.") - else: - response = requests.head(self.url) - if ((response.status_code == 401) and (self.auth == '1')): - if (self.url[0:7] == "http://"): - self.url = self.url[:7] + self.auth_username + ":" + self.auth_password + "@" + self.url[7:] - elif (self.url[0:8] == "https://"): - self.url = self.url[:8] + self.auth_username + ":" + self.auth_password + "@" + self.url[8:] - else: - self.url = self.auth_username + ":" + self.auth_password + "@" + self.url - print ("Authentication enabled.") - print(self.url) - self.download = self.api.add_uris([self.url]) - downloadname = self.download.name - print("Download added.\n" + self.downloaddir + "\n" + self.url) - GLib.idle_add(self.update_header_pause_button) - while (self.cancelled == False): - try: - self.download.update() - GLib.idle_add(self.update_labels_and_things) - if (self.download.is_complete): - if os.path.exists(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))): - os.remove(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))) - break - elif (self.download.status == "error"): - return - except: - return - time.sleep(1) - - def update_header_pause_button(self): - self.app.all_paused = False - self.app.header_pause_content.set_icon_name("media-playback-pause-symbolic") - self.app.header_pause_content.set_label(_("Pause All")) - self.app.header_pause_button.set_sensitive(True) - - def show_message(self, message): - self.speed_label.set_text(message) - - def update_labels_and_things(self): - self.progress_bar.set_fraction(self.download.progress / 100) - download_speed_mb = (self.download.download_speed / 1024 / 1024) - if int(str(download_speed_mb)[0]) == 0: - download_speed_kb = (self.download.download_speed / 1024) - if int(str(download_speed_kb)[0]) == 0: - self.speed_label.set_text(f"{round(self.download.progress)}% | {round(self.download.download_speed, 2)} B/s") - else: - self.speed_label.set_text(f"{round(self.download.progress)}% | {round(self.download.download_speed / 1024, 2)} KB/s") - else: - self.speed_label.set_text(f"{round(self.download.progress)}% | {round(self.download.download_speed / 1024 / 1024, 2)} MB/s") - - def pause(self): - if self.download: - if self.download.is_paused == False: - try: - self.download.pause() - print ("Download paused.") - except: - try: - self.download.pause([self.download.gid]) - print ("Download paused.") - except: - self.stop(False) - print ("Something went wrong when pausing. Stopping download without removing files.") - - def resume(self): - if self.download: - if self.download.is_paused == True: - try: - self.download.resume() - print ("Download resumed.") - except: - self.speed_label.set_text(_("An error occurred:") + " " + self.download.error_message.split("status=")[1]) - print ("An error occurred when resuming. " + self.download.error_message.split("status=")[1]) - - def stop(self, deletefiles): - if self.download: - downloadgid = self.download.gid - downloadname = self.download.name - self.download.remove(force=True) - if not self.download.is_complete: - if (deletefiles == True): - if os.path.exists(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))): - os.remove(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))) - if os.path.exists(os.path.join(self.downloaddir, downloadname)): - os.remove(os.path.join(self.downloaddir, downloadname)) - print ("Download stopped.") - self.stop_event.set() - - def save_state(self): - if self.download: - try: - self.download.update() - except: - print ("Couldn't update the status of the download. Skipping state saving.") - return - state = { - 'url': self.url, - 'downloaded': self.download.completed_length, - } - with open(os.path.join(self.downloaddir, f'{self.download.gid}.varia.json'), 'w') as f: - json.dump(state, f) - print ("State saved for download.") - - def return_gid(self): - if self.download: - return self.download.gid - - def return_is_paused(self): - if (self.pause_button.get_child().get_icon_name() == "media-playback-pause-symbolic"): - return False - else: - return True - - @classmethod - def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow): - with open(os.path.join(app.appconf["download_directory"], filename), 'r') as f: - state = json.load(f) - os.remove(os.path.join(app.appconf["download_directory"], filename)) - instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow) - return instance - class MyApp(Adw.Application): - def __init__(self, **kwargs): + def __init__(self, appdir, appconf, **kwargs): super().__init__(**kwargs) - self.connect('activate', self.on_activate) + self.connect('activate', self.on_activate, appdir, appconf) - def on_activate(self, app): - self.win = MainWindow(application=app, variaapp=self) + def on_activate(self, app, appdir, appconf): + self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf) self.win.present() -def main(version): - app = MyApp(application_id="io.github.giantpinkrobots.varia") +def main(version, aria2cexec): + if "FLATPAK_ID" in os.environ: + appdir = os.path.join('/var', 'data') + else: + appdir = os.path.join(os.path.expanduser('~'), '.varia') + if not os.path.exists(appdir): + os.makedirs(appdir) + + appconf = { + 'download_speed_limit_enabled': '0', + 'download_speed_limit': '0', + 'auth': '0', + 'auth_username': '', + 'auth_password': '', + 'download_directory': GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD), + 'download_simultaneous_amount': '5', + 'remote': '0', + 'remote_protocol': 'https://', + 'remote_ip': '', + 'remote_port': '', + 'remote_secret': '', + 'remote_location': ''} + + if os.path.exists(os.path.join(appdir, 'varia.conf')): + with open(os.path.join(appdir, 'varia.conf'), 'r') as f: + appconf.update(json.load(f)) + else: + with open(os.path.join(appdir, 'varia.conf'), 'w') as f: + json.dump(appconf, f) + + if (appconf['remote'] == '0'): + if (os.name == 'nt'): + subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"], shell=True) + else: + subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"]) + + arguments = sys.argv + if (len(arguments) > 1): + arguments = arguments[:-1] + + app = MyApp(appdir, appconf, application_id="io.github.giantpinkrobots.varia") try: - app.run(sys.argv) + app.run(arguments) finally: app.win.exitProgram(app) if ((__name__ == '__main__') and (os.name == 'nt')): - subprocess.Popen(["aria2c", "--enable-rpc", "--rpc-listen-port=6801"], shell=True) - sys.exit(main(variaVersion)) + sys.exit(main(variaVersion, "aria2c")) diff --git a/src/window/content.py b/src/window/content.py new file mode 100644 index 0000000..a45d462 --- /dev/null +++ b/src/window/content.py @@ -0,0 +1,58 @@ +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw, GLib, Gio + +def window_create_content(self, threading): + content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + header_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + + self.total_download_speed_label = Gtk.Label(label=self.total_download_speed) + + header_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) + + self.header_pause_content = Adw.ButtonContent() + self.header_pause_content.set_icon_name("media-playback-pause-symbolic") + self.header_pause_content.set_label(_("Pause All")) + self.header_pause_button = Gtk.Button() + self.header_pause_button.set_sensitive(False) + self.header_pause_button.set_child(self.header_pause_content) + self.header_pause_button.connect("clicked", lambda button: self.pause_all(self.header_pause_content)) + + header_button_box.append(self.header_pause_button) + + header_expanding_box = Gtk.Box() + Gtk.Widget.set_hexpand(header_expanding_box, True) + header_expanding_box_1 = Gtk.Box() + Gtk.Widget.set_hexpand(header_expanding_box_1, True) + + header_box.append(header_expanding_box) + header_box.append(self.total_download_speed_label) + header_box.append(header_expanding_box_1) + header_box.append(header_button_box) + + header_bar = Adw.HeaderBar() + header_bar.get_style_context().add_class('flat') + header_bar.set_title_widget(header_box) + content_box.append(header_bar) + + self.download_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + self.download_list.set_margin_start(6) + self.download_list.set_margin_end(6) + self.download_list.set_margin_bottom(6) + self.download_list.set_margin_top(6) + + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.set_child(self.download_list) + scrolled_window.set_hexpand(True) + scrolled_window.set_vexpand(True) + + content_box.append(scrolled_window) + + self.overlay_split_view.set_content(content_box) + + self.total_download_speed_calculator_thread = threading.Thread(target=self.total_download_speed_get, args=(self.downloads, self.total_download_speed_label)) + self.total_download_speed_calculator_thread.start() + + self.check_download_status_thread = threading.Thread(target=self.check_download_status) + self.check_download_status_thread.start() diff --git a/src/window/preferences.py b/src/window/preferences.py new file mode 100644 index 0000000..557c092 --- /dev/null +++ b/src/window/preferences.py @@ -0,0 +1,362 @@ +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw, GLib, Gio + +from download.communicate import set_speed_limit, set_aria2c_download_directory, set_aria2c_download_simultaneous_amount + +def show_preferences(button, self, app): + preferences = Adw.PreferencesWindow(transient_for=self) + preferences.set_search_enabled(False) + + page = Adw.PreferencesPage(title=_("Preferences")) + preferences.add(page) + group_1 = Adw.PreferencesGroup() + page.add(group_1) + group_2 = Adw.PreferencesGroup() + page.add(group_2) + + # Basic settings: + + download_directory_actionrow = Adw.ActionRow() + download_directory_actionrow.set_title(_("Download Directory")) + if (self.appconf["remote"] == "0"): + download_directory_actionrow.set_subtitle(self.appconf["download_directory"]) + + download_directory_change_button = Gtk.Button(label=_("Change")) + download_directory_change_button.get_style_context().add_class("suggested-action") + download_directory_change_button.set_halign(Gtk.Align.START) + download_directory_change_button.set_valign(Gtk.Align.CENTER) + download_directory_change_button.connect("clicked", lambda clicked: on_download_directory_change(self, preferences, download_directory_actionrow)) + + download_directory_change_remote_label = Gtk.Label(label=_("Remote Mode")) + download_directory_change_remote_label.set_halign(Gtk.Align.START) + download_directory_change_remote_label.set_valign(Gtk.Align.CENTER) + + if (self.appconf["remote"] == "0"): + download_directory_actionrow.add_suffix(download_directory_change_button) + else: + download_directory_actionrow.add_suffix(download_directory_change_remote_label) + + speed_limit_unit_names_dropdown = Gtk.DropDown.new_from_strings(["KB/s", "MB/s", "GB/s"]) + speed_limit_unit_names_dropdown.set_selected(0) + speed_limit_unit_names_dropdown.connect("notify::selected-item", lambda dropdown, param: on_speed_limit_changed(self, speed_limit_entry, speed_limit_unit_names_dropdown, speed_limit_expander_switch)) + + speed_limit_expander_box = Adw.ExpanderRow() + speed_limit_expander_box.set_title(_("Speed Limit")) + + speed_limit_expander_switch = Gtk.Switch() + speed_limit_expander_switch.set_halign(Gtk.Align.START) + speed_limit_expander_switch.set_valign(Gtk.Align.CENTER) + speed_limit_expander_switch.connect("state-set", on_switch_speed_limit, self, preferences) + + speed_limit_entry = Adw.EntryRow() + speed_limit_entry.set_title(_("Speed")) + speed_limit_entry.set_input_purpose(Gtk.InputPurpose.NUMBER) + speed_limit_entry.set_show_apply_button(True) + speed_limit_entry.connect('changed', speed_limit_text_filter, self) + speed_limit_entry.connect('apply', lambda clicked: on_speed_limit_changed(self, speed_limit_entry, speed_limit_unit_names_dropdown, speed_limit_expander_switch)) + + if (self.appconf["download_speed_limit"] != "0"): + speed_limit_expander_switch.set_sensitive(True) + speed_limit_entry.set_text(self.appconf["download_speed_limit"][:-1]) + match (self.appconf["download_speed_limit"][-1]): + case "K": + speed_limit_unit_names_dropdown.set_selected(0) + case "M": + speed_limit_unit_names_dropdown.set_selected(1) + case "G": + speed_limit_unit_names_dropdown.set_selected(2) + else: + speed_limit_expander_switch.set_sensitive(False) + + if (self.appconf["download_speed_limit_enabled"] == "1"): + speed_limit_expander_switch.set_active("active") + + speed_limit_expander_box.add_action(speed_limit_expander_switch) + + speed_limit_dropdown_hexpanding_box_left = Gtk.Box() + Gtk.Widget.set_hexpand(speed_limit_dropdown_hexpanding_box_left, True) + speed_limit_dropdown_hexpanding_box_right = Gtk.Box() + Gtk.Widget.set_hexpand(speed_limit_dropdown_hexpanding_box_right, True) + + speed_limit_dropdown_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) + speed_limit_dropdown_box.append(speed_limit_dropdown_hexpanding_box_left) + speed_limit_dropdown_box.append(speed_limit_unit_names_dropdown) + speed_limit_dropdown_box.append(speed_limit_dropdown_hexpanding_box_right) + + speed_limit_dropdown_box.set_margin_top(5) + speed_limit_dropdown_box.set_margin_bottom(5) + + speed_limit_expander_box.add_row(speed_limit_dropdown_box) + speed_limit_expander_box.add_row(speed_limit_entry) + + auth_expander = Adw.ExpanderRow() + auth_expander.set_title(_("Authentication")) + + auth_expander_switch = Gtk.Switch() + auth_expander_switch.set_halign(Gtk.Align.START) + auth_expander_switch.set_valign(Gtk.Align.CENTER) + auth_expander_switch.connect("state-set", on_switch_auth, self, preferences) + + username_entry = Adw.EntryRow() + username_entry.set_title(_("Username")) + username_entry.set_show_apply_button(True) + username_entry.connect('apply', lambda entry: set_auth_credentials(self, username_entry, password_entry, auth_expander_switch)) + + password_entry = Adw.PasswordEntryRow() + password_entry.set_title(_("Password")) + password_entry.set_show_apply_button(True) + password_entry.connect('apply', lambda entry: set_auth_credentials(self, username_entry, password_entry, auth_expander_switch)) + + username_entry.set_text(self.appconf["auth_username"]) + password_entry.set_text(self.appconf["auth_password"]) + + if ((self.appconf["auth_username"] != "") and (self.appconf["auth_password"] != "")): + auth_expander_switch.set_sensitive(True) + else: + auth_expander_switch.set_sensitive(False) + + if (self.appconf["auth"] == "1"): + auth_expander_switch.set_active("active") + + auth_expander.add_action(auth_expander_switch) + + auth_expander.add_row(username_entry) + auth_expander.add_row(password_entry) + + simultaneous_download_amount_unit_names = Gio.ListStore.new(Gtk.StringObject) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 1")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 2")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 3")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 4")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 5")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 6")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 7")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 8")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 9")) + simultaneous_download_amount_unit_names.append(Gtk.StringObject.new(" 10")) + + simultaneous_download_amount_unit_names_box = Adw.ComboRow() + simultaneous_download_amount_unit_names_box.set_model(simultaneous_download_amount_unit_names) + simultaneous_download_amount_unit_names_box.set_title(_("Simultaneous Download Amount")) + simultaneous_download_amount_unit_names_box.set_selected(int(self.appconf["download_simultaneous_amount"])- 1) + simultaneous_download_amount_unit_names_box.connect("notify::selected", on_simultaneous_download_amount_changed, self) + + group_1.add(download_directory_actionrow) + group_1.add(speed_limit_expander_box) + group_1.add(auth_expander) + group_1.add(simultaneous_download_amount_unit_names_box) + + # Advanced settings: + + remote_aria2_expander_box = Adw.ExpanderRow() + remote_aria2_expander_box.set_title(_("Remote Mode")) + + remote_aria2_expander_switch = Gtk.Switch() + remote_aria2_expander_switch.set_halign(Gtk.Align.START) + remote_aria2_expander_switch.set_valign(Gtk.Align.CENTER) + + if ((self.appconf["remote_protocol"] == "") or (self.appconf["remote_ip"] == "") or (self.appconf["remote_port"] == "") or (self.appconf["remote_location"] == "")): + remote_aria2_expander_switch.set_sensitive(False) + else: + remote_aria2_expander_switch.set_sensitive(True) + + if (self.appconf["remote"] == "1"): + remote_aria2_expander_switch.set_active("active") + + remote_aria2_expander_switch.connect("state-set", on_switch_remote, self, preferences) + + remote_aria2_expander_box.add_action(remote_aria2_expander_switch) + + remote_aria2_ip_protocol_names_dropdown_hexpanding_box_left = Gtk.Box() + Gtk.Widget.set_hexpand(remote_aria2_ip_protocol_names_dropdown_hexpanding_box_left, True) + remote_aria2_ip_protocol_names_dropdown_hexpanding_box_right = Gtk.Box() + Gtk.Widget.set_hexpand(remote_aria2_ip_protocol_names_dropdown_hexpanding_box_right, True) + + remote_aria2_ip_protocol_names_dropdown = Gtk.DropDown.new_from_strings(["https://", "http://"]) + if (self.appconf['remote_protocol'] == 'https://'): + remote_aria2_ip_protocol_names_dropdown.set_selected(0) + else: + remote_aria2_ip_protocol_names_dropdown.set_selected(1) + remote_aria2_ip_protocol_names_dropdown.connect("notify::selected-item", lambda dropdown, param: set_remote(self, remote_aria2_ip_protocol_names_dropdown, remote_aria2_ip_entry, remote_aria2_port_entry, remote_aria2_rpc_entry, remote_aria2_location_entry, remote_aria2_expander_switch, preferences)) + + remote_aria2_ip_protocol_names_dropdown_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4) + remote_aria2_ip_protocol_names_dropdown_box.append(remote_aria2_ip_protocol_names_dropdown_hexpanding_box_left) + remote_aria2_ip_protocol_names_dropdown_box.append(remote_aria2_ip_protocol_names_dropdown) + remote_aria2_ip_protocol_names_dropdown_box.append(remote_aria2_ip_protocol_names_dropdown_hexpanding_box_right) + + remote_aria2_ip_protocol_names_dropdown_box.set_margin_top(5) + remote_aria2_ip_protocol_names_dropdown_box.set_margin_bottom(5) + + remote_aria2_ip_entry = Adw.EntryRow() + remote_aria2_ip_entry.set_title(_("Remote aria2 IP")) + remote_aria2_ip_entry.set_show_apply_button(True) + remote_aria2_ip_entry.connect('apply', lambda entry: set_remote(self, remote_aria2_ip_protocol_names_dropdown, remote_aria2_ip_entry, remote_aria2_port_entry, remote_aria2_rpc_entry, remote_aria2_location_entry, remote_aria2_expander_switch, preferences)) + + remote_aria2_port_entry = Adw.EntryRow() + remote_aria2_port_entry.set_title(_("Remote aria2 Port")) + remote_aria2_port_entry.set_show_apply_button(True) + remote_aria2_port_entry.connect('apply', lambda entry: set_remote(self, remote_aria2_ip_protocol_names_dropdown, remote_aria2_ip_entry, remote_aria2_port_entry, remote_aria2_rpc_entry, remote_aria2_location_entry, remote_aria2_expander_switch, preferences)) + + remote_aria2_rpc_entry = Adw.EntryRow() + remote_aria2_rpc_entry.set_title(_("Remote aria2 RPC Secret")) + remote_aria2_rpc_entry.set_show_apply_button(True) + remote_aria2_rpc_entry.connect('apply', lambda entry: set_remote(self, remote_aria2_ip_protocol_names_dropdown, remote_aria2_ip_entry, remote_aria2_port_entry, remote_aria2_rpc_entry, remote_aria2_location_entry, remote_aria2_expander_switch, preferences)) + + remote_aria2_location_entry = Adw.EntryRow() + remote_aria2_location_entry.set_title(_("Remote Download Location")) + remote_aria2_location_entry.set_show_apply_button(True) + remote_aria2_location_entry.connect('apply', lambda entry: set_remote(self, remote_aria2_ip_protocol_names_dropdown, remote_aria2_ip_entry, remote_aria2_port_entry, remote_aria2_rpc_entry, remote_aria2_location_entry, remote_aria2_expander_switch, preferences)) + + remote_aria2_ip_entry.set_text(self.appconf["remote_ip"]) + remote_aria2_port_entry.set_text(self.appconf["remote_port"]) + remote_aria2_rpc_entry.set_text(self.appconf["remote_secret"]) + remote_aria2_location_entry.set_text(self.appconf["remote_location"]) + + remote_aria2_expander_box.add_row(remote_aria2_ip_protocol_names_dropdown_box) + remote_aria2_expander_box.add_row(remote_aria2_ip_entry) + remote_aria2_expander_box.add_row(remote_aria2_port_entry) + remote_aria2_expander_box.add_row(remote_aria2_rpc_entry) + remote_aria2_expander_box.add_row(remote_aria2_location_entry) + + group_2.add(remote_aria2_expander_box) + + preferences.present() + +def speed_limit_text_filter(entry, self): + text = entry.get_text() + new_text = "" + for char in text: + if (char.isdigit()): + new_text += char + if (new_text != text): + GLib.idle_add(entry.set_text, new_text) + GLib.idle_add(entry.set_position, -1) + +def on_download_directory_change(self, prefswindow, actionrow): + Gtk.FileDialog().select_folder(None, None, on_download_directory_selected, self, prefswindow, actionrow) + +def on_download_directory_selected(dialog, result, self, prefswindow, actionrow): + try: + folder = dialog.select_folder_finish(result) + self.appconf["download_directory"] = folder.get_path() + self.save_appconf() + set_aria2c_download_directory(self) + actionrow.set_subtitle(self.appconf["download_directory"]) + except: + error_dialog = Adw.MessageDialog() + error_dialog.set_body(_("Failed to open directory.")) + error_dialog.add_response("ok", _("OK")) + error_dialog.set_default_response("ok") + error_dialog.set_close_response("ok") + error_dialog.set_transient_for(prefswindow) + error_dialog.connect("response", on_dialog_dismiss, self) + error_dialog.show() + +def on_switch_speed_limit(switch, state, self, preferencesWindow): + if state: + self.appconf["download_speed_limit_enabled"] = "1" + else: + self.appconf["download_speed_limit_enabled"] = "0" + set_speed_limit(self, self.appconf["download_speed_limit"]) + + self.save_appconf() + +def on_speed_limit_changed(self, speed, speed_type, switch): + speed = speed.get_text() + if (speed == ""): + speed = "0" + + if (speed != "0"): + speed_type = speed_type.get_selected() + print(speed_type) + match speed_type: + case 0: + download_limit = speed + "K" + case 1: + download_limit = speed + "M" + case 2: + download_limit = speed + "G" + else: + download_limit = speed + + set_speed_limit(self, download_limit) + self.appconf['download_speed_limit'] = download_limit + + if ((self.appconf["download_speed_limit"] == "0K") or (self.appconf["download_speed_limit"] == "0M") or (self.appconf["download_speed_limit"] == "0G")): + switch.set_active(False) + switch.set_sensitive(False) + else: + switch.set_sensitive(True) + + self.save_appconf() + +def on_switch_auth(switch, state, self, preferencesWindow): + if state: + self.appconf["auth"] = "1" + else: + self.appconf["auth"] = "0" + + self.save_appconf() + +def set_auth_credentials(self, username_entry, password_entry, switch): + self.appconf["auth_username"] = username_entry.get_text() + self.appconf["auth_password"] = password_entry.get_text() + + if ((self.appconf["auth_username"] == "") or (self.appconf["auth_password"] == "")): + switch.set_active(False) + switch.set_sensitive(False) + else: + switch.set_sensitive(True) + + self.save_appconf() + +def on_simultaneous_download_amount_changed(comborow, parameters, self): + self.appconf["download_simultaneous_amount"] = str(comborow.get_selected() + 1) + print(str(comborow.get_selected() + 1)) + self.save_appconf() + + set_aria2c_download_simultaneous_amount(self) + +def set_remote(self, remote_protocol, remote_ip, remote_port, remote_secret, remote_location, switch, preferencesWindow): + if (remote_protocol.get_selected() == 0): + self.appconf["remote_protocol"] = "https://" + else: + self.appconf["remote_protocol"] = "http://" + self.appconf["remote_ip"] = remote_ip.get_text() + self.appconf["remote_port"] = remote_port.get_text() + self.appconf["remote_secret"] = remote_secret.get_text() + self.appconf["remote_location"] = remote_location.get_text() + + if ((self.appconf["remote_protocol"] == "") or (self.appconf["remote_ip"] == "") or (self.appconf["remote_port"] == "") or (self.appconf["remote_location"] == "")): + switch.set_sensitive(False) + else: + switch.set_sensitive(True) + + self.save_appconf() + + if (switch.get_active()): + restart_varia_dialog(preferencesWindow) + +def on_switch_remote(switch, state, self, preferencesWindow): + if state: + self.appconf["remote"] = "1" + else: + self.appconf["remote"] = "0" + + self.save_appconf() + restart_varia_dialog(preferencesWindow) + +def restart_varia_dialog(preferencesWindow): + dialog = Adw.MessageDialog() + dialog.set_body(_("Please restart Varia to apply the change.")) + dialog.add_response("ok", _("OK")) + dialog.set_default_response("ok") + dialog.set_close_response("ok") + dialog.set_transient_for(preferencesWindow) + dialog.connect("response", on_dialog_dismiss) + dialog.show() + +def on_dialog_dismiss(dialog, response_id): + dialog.destroy() diff --git a/src/window/sidebar.py b/src/window/sidebar.py new file mode 100644 index 0000000..fa4ddc7 --- /dev/null +++ b/src/window/sidebar.py @@ -0,0 +1,153 @@ +import os +import subprocess +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw, GLib, Gio + +from window.preferences import show_preferences +from download.actionrow import on_download_clicked + +def window_create_sidebar(self, variaapp, DownloadThread, variaVersion): + sidebar_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + sidebar_content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + + header_bar = Adw.HeaderBar() + header_bar.get_style_context().add_class('flat') + sidebar_box.append(header_bar) + + preferences_button = Gtk.Button(tooltip_text=_("Preferences")) + preferences_button.set_icon_name("emblem-system-symbolic") + preferences_button.connect("clicked", show_preferences, self, variaapp) + + hamburger_button = Gtk.MenuButton(tooltip_text=_("Other")) + hamburger_button.set_icon_name("open-menu-symbolic") + hamburger_menu_model = Gio.Menu() + + cancel_all_action = Gio.SimpleAction.new("cancel_all_downloads", None) + cancel_all_action.connect("activate", self.stop_all) + variaapp.add_action(cancel_all_action) + + about_action = Gio.SimpleAction.new("downloads_folder", None) + about_action.connect("activate", open_downloads_folder, self, self.appconf) + variaapp.add_action(about_action) + + downloads_folder_action = Gio.SimpleAction.new("about", None) + downloads_folder_action.connect("activate", show_about, self, variaVersion) + variaapp.add_action(downloads_folder_action) + + hamburger_menu_item_cancel_all = Gio.MenuItem.new(_("Cancel All"), "app.cancel_all_downloads") + hamburger_menu_model.append_item(hamburger_menu_item_cancel_all) + + hamburger_menu_item_open_downloads_folder = Gio.MenuItem.new(_("Open Download Folder"), "app.downloads_folder") + hamburger_menu_model.append_item(hamburger_menu_item_open_downloads_folder) + + hamburger_menu_item_about = Gio.MenuItem.new(_("About Varia"), "app.about") + hamburger_menu_model.append_item(hamburger_menu_item_about) + + hamburger_button.set_menu_model(hamburger_menu_model) + + header_bar.pack_start(preferences_button) + header_bar.pack_end(hamburger_button) + + download_entry = Gtk.Entry() + download_entry.set_placeholder_text(_("URL")) + + download_button = Gtk.Button(label=_("Download")) + download_button.get_style_context().add_class("pill") + download_button.get_style_context().add_class("suggested-action") + download_button.connect("clicked", on_download_clicked, self, download_entry, DownloadThread) + + self.filter_button_show_all = Gtk.ToggleButton() + self.filter_button_show_all.get_style_context().add_class('flat') + filter_button_show_all_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + filter_button_show_all_box.set_margin_top(8) + filter_button_show_all_box.set_margin_bottom(8) + filter_button_show_all_box.append(Gtk.Image.new_from_icon_name("switch-off-symbolic")) + filter_button_show_all_label = Gtk.Label(label=_("All")) + filter_button_show_all_label.get_style_context().add_class('body') + filter_button_show_all_box.append(filter_button_show_all_label) + self.filter_button_show_all.set_child(filter_button_show_all_box) + self.filter_button_show_all.set_active(True) + self.filter_button_show_all.connect("clicked", self.filter_download_list, "show_all") + + self.filter_button_show_downloading = Gtk.ToggleButton() + self.filter_button_show_downloading.get_style_context().add_class('flat') + filter_button_show_downloading_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + filter_button_show_downloading_box.set_margin_top(8) + filter_button_show_downloading_box.set_margin_bottom(8) + filter_button_show_downloading_box.append(Gtk.Image.new_from_icon_name("content-loading-symbolic")) + filter_button_show_downloading_label = Gtk.Label(label=_("In Progress")) + filter_button_show_downloading_label.get_style_context().add_class('body') + filter_button_show_downloading_box.append(filter_button_show_downloading_label) + self.filter_button_show_downloading.set_child(filter_button_show_downloading_box) + self.filter_button_show_downloading.connect("clicked", self.filter_download_list, "show_downloading") + + self.filter_button_show_completed = Gtk.ToggleButton() + self.filter_button_show_completed.get_style_context().add_class('flat') + filter_button_show_completed_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + filter_button_show_completed_box.set_margin_top(8) + filter_button_show_completed_box.set_margin_bottom(8) + filter_button_show_completed_box.append(Gtk.Image.new_from_icon_name("emblem-ok-symbolic")) + filter_button_show_completed_label = Gtk.Label(label=_("Completed")) + filter_button_show_completed_label.get_style_context().add_class('body') + filter_button_show_completed_box.append(filter_button_show_completed_label) + self.filter_button_show_completed.set_child(filter_button_show_completed_box) + self.filter_button_show_completed.connect("clicked", self.filter_download_list, "show_completed") + + sidebar_separator = Gtk.Separator() + sidebar_separator.set_margin_top(8) + sidebar_separator.set_margin_bottom(8) + + sidebar_expanding_box = Gtk.Box() + Gtk.Widget.set_vexpand(sidebar_expanding_box, True) + + self.sidebar_remote_mode_label = Gtk.Label() + if (self.appconf['remote'] == '1'): + self.sidebar_remote_mode_label.set_text(_("Remote Mode")) + self.sidebar_speed_limited_label = Gtk.Label() + + sidebar_content_box.set_margin_start(6) + sidebar_content_box.set_margin_end(6) + sidebar_content_box.set_margin_top(6) + sidebar_content_box.set_margin_bottom(6) + + sidebar_filter_buttons_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) + sidebar_filter_buttons_box.append(self.filter_button_show_all) + sidebar_filter_buttons_box.append(self.filter_button_show_downloading) + sidebar_filter_buttons_box.append(self.filter_button_show_completed) + + sidebar_content_box.append(download_entry) + sidebar_content_box.append(download_button) + sidebar_content_box.append(sidebar_separator) + sidebar_content_box.append(sidebar_filter_buttons_box) + sidebar_content_box.append(sidebar_expanding_box) + sidebar_content_box.append(self.sidebar_remote_mode_label) + sidebar_content_box.append(self.sidebar_speed_limited_label) + sidebar_box.append(sidebar_content_box) + + self.overlay_split_view.set_sidebar(sidebar_box) + +def show_about(app, variaapp, self, variaVersion): + dialog = Adw.AboutWindow(transient_for=self) + dialog.set_application_name("Varia") + dialog.set_version(variaVersion) + dialog.set_developer_name("Giant Pink Robots!") + dialog.set_license_type(Gtk.License(Gtk.License.MPL_2_0)) + dialog.set_comments(_("aria2 based download manager utilizing GTK4 and Libadwaita.")) + dialog.set_website("https://giantpinkrobots.github.io/varia") + dialog.set_issue_url("https://github.com/giantpinkrobots/varia/issues") + dialog.set_copyright("2023 Giant Pink Robots!\n\n" + _("This application relies on the following pieces of software:") + + "\n\n- aria2\n- GTK4\n- Libadwaita\n- Meson\n- OpenSSL\n- Python-appdirs\n- Python-aria2p\n- Python-certifi\n- Python-charset-normalizer\n- Python-gettext\n- Python-idna\n- Python-loguru\n- Python-requests\n- Python-setuptools\n- Python-urllib3\n- Python-websocket-client\n\n" + + _("The licenses of all of these pieces of software can be found in the dependencies_information directory in this application's app directory.")) + dialog.set_developers(["Giant Pink Robots! (@giantpinkrobots) https://github.com/giantpinkrobots"]) + dialog.set_application_icon("io.github.giantpinkrobots.varia") + dialog.set_translator_credits(_("translator-credits")) + dialog.set_artists(["Jakub Steiner"]) + dialog.show() + +def open_downloads_folder(self, app, variaapp, appconf): + if (os.name == 'nt'): + os.startfile(appconf["download_directory"]) + else: + subprocess.Popen(["xdg-open", appconf["download_directory"]]) From 7d75e8d27feaa402331418399a0add5e53ccf713 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Sat, 17 Feb 2024 22:33:54 +0300 Subject: [PATCH 2/9] Adjustments and a browser extension This commit brings: - A new "Exiting Varia" screen to make sure aria2c exits before the UI quits to hopefully mitigate issue #53 - A browser extension for Chromium and Firefox - Code to check the aria2c daemon regularly to get the downloads added by the browser extension --- browser-extension/arrow-right.png | Bin 0 -> 457 bytes browser-extension/background.js | 26 ++++++ browser-extension/chrome/arrow-right.png | Bin 0 -> 457 bytes browser-extension/chrome/background.js | 26 ++++++ browser-extension/chrome/download.png | Bin 0 -> 733 bytes browser-extension/chrome/icon128.png | Bin 0 -> 11160 bytes browser-extension/chrome/icon16.png | Bin 0 -> 1548 bytes browser-extension/chrome/icon48.png | Bin 0 -> 6301 bytes .../chrome/manifest-chromium.json | 20 ++++ browser-extension/chrome/popup.html | 86 ++++++++++++++++++ browser-extension/chrome/popup.js | 10 ++ browser-extension/download.png | Bin 0 -> 733 bytes browser-extension/icon128.png | Bin 0 -> 11160 bytes browser-extension/icon16.png | Bin 0 -> 1548 bytes browser-extension/icon48.png | Bin 0 -> 6301 bytes browser-extension/manifest-chromium.json | 20 ++++ browser-extension/manifest-firefox.json | 23 +++++ browser-extension/popup.html | 86 ++++++++++++++++++ browser-extension/popup.js | 10 ++ po/Varia.pot | 3 + po/tr.po | 3 + src/download/actionrow.py | 2 +- src/download/listen.py | 32 +++++++ src/download/thread.py | 83 +++++++++-------- src/meson.build | 1 + src/variamain.py | 74 ++++++++++++--- 26 files changed, 453 insertions(+), 52 deletions(-) create mode 100644 browser-extension/arrow-right.png create mode 100644 browser-extension/background.js create mode 100644 browser-extension/chrome/arrow-right.png create mode 100644 browser-extension/chrome/background.js create mode 100644 browser-extension/chrome/download.png create mode 100644 browser-extension/chrome/icon128.png create mode 100644 browser-extension/chrome/icon16.png create mode 100644 browser-extension/chrome/icon48.png create mode 100644 browser-extension/chrome/manifest-chromium.json create mode 100644 browser-extension/chrome/popup.html create mode 100644 browser-extension/chrome/popup.js create mode 100644 browser-extension/download.png create mode 100644 browser-extension/icon128.png create mode 100644 browser-extension/icon16.png create mode 100644 browser-extension/icon48.png create mode 100644 browser-extension/manifest-chromium.json create mode 100644 browser-extension/manifest-firefox.json create mode 100644 browser-extension/popup.html create mode 100644 browser-extension/popup.js create mode 100644 src/download/listen.py diff --git a/browser-extension/arrow-right.png b/browser-extension/arrow-right.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f02a7ef2689791089f2d0cbde03d5f07a4f12a GIT binary patch literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&U|7|wYy{+S7I;L0OgI3>?gj5p02%Bh zp1!W^FPH>4l;y(K>wv7D;pyTSVsSb-LBgnEe&c+Tr`i(Rj4}k*H})rLAFglvyCmac z(v9;U=08+uHv1T5@^X>u+(i9dVrfez2&O)8Znl@mGn6ozVf4aaPI3ij;4zMHyU7Rp z9@@9b{+DEt*dZw)!Dq-~xFRyDHqJ({2z zXxZu6{N#vx(I+)aW?l_oIPmMT|FADF0J>4N#5JNMC9x#cD!C{XNHG{07@6oA80i|C zg&0^^8Jk!cnrRytSQ!{hJy$4(q9HdwB{QuOw}vN^Pp$%L(16=el9`)YT#}eufUd{X Y%Gdy6N$tjsJU~4Rp00i_>zopr0KgB4%>V!Z literal 0 HcmV?d00001 diff --git a/browser-extension/background.js b/browser-extension/background.js new file mode 100644 index 0000000..96becbe --- /dev/null +++ b/browser-extension/background.js @@ -0,0 +1,26 @@ +chrome.downloads.onCreated.addListener(function(downloadItem) { + chrome.storage.sync.get('enabled', function(data) { + if (data.enabled) { + sendToAria2(downloadItem); + } + }); +}); + +function sendToAria2(downloadItem) { + fetch('http://localhost:6801/jsonrpc', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: '1', + method: 'aria2.addUri', + params: [[downloadItem.url]] + }) + }).then(response => { + chrome.downloads.cancel(downloadItem.id); + }).catch(error => { + + }); +} diff --git a/browser-extension/chrome/arrow-right.png b/browser-extension/chrome/arrow-right.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f02a7ef2689791089f2d0cbde03d5f07a4f12a GIT binary patch literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&U|7|wYy{+S7I;L0OgI3>?gj5p02%Bh zp1!W^FPH>4l;y(K>wv7D;pyTSVsSb-LBgnEe&c+Tr`i(Rj4}k*H})rLAFglvyCmac z(v9;U=08+uHv1T5@^X>u+(i9dVrfez2&O)8Znl@mGn6ozVf4aaPI3ij;4zMHyU7Rp z9@@9b{+DEt*dZw)!Dq-~xFRyDHqJ({2z zXxZu6{N#vx(I+)aW?l_oIPmMT|FADF0J>4N#5JNMC9x#cD!C{XNHG{07@6oA80i|C zg&0^^8Jk!cnrRytSQ!{hJy$4(q9HdwB{QuOw}vN^Pp$%L(16=el9`)YT#}eufUd{X Y%Gdy6N$tjsJU~4Rp00i_>zopr0KgB4%>V!Z literal 0 HcmV?d00001 diff --git a/browser-extension/chrome/background.js b/browser-extension/chrome/background.js new file mode 100644 index 0000000..96becbe --- /dev/null +++ b/browser-extension/chrome/background.js @@ -0,0 +1,26 @@ +chrome.downloads.onCreated.addListener(function(downloadItem) { + chrome.storage.sync.get('enabled', function(data) { + if (data.enabled) { + sendToAria2(downloadItem); + } + }); +}); + +function sendToAria2(downloadItem) { + fetch('http://localhost:6801/jsonrpc', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: '1', + method: 'aria2.addUri', + params: [[downloadItem.url]] + }) + }).then(response => { + chrome.downloads.cancel(downloadItem.id); + }).catch(error => { + + }); +} diff --git a/browser-extension/chrome/download.png b/browser-extension/chrome/download.png new file mode 100644 index 0000000000000000000000000000000000000000..0174d8ef3f25ebc7095080b385b3d5429be0ba54 GIT binary patch literal 733 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P0wkGC6jgzgLb6AYF9SmrP@a@a2CEqi4B`cIb_Lpiv?O`EyD%`U>Qy!Z@;D1TB8!3a0Wfwicz*)OU@!6X zb!C6SB*398da(9u69WU|8c!F;5Rc<;uk6nf2^2a0@qFGkj$y>6I=O-<>aQ&RTC64SaL7TWZ&S;<#Bn_hjc{G8=}HfXQ>+n?C_mZ^+Un{oEG zw8LwIlV6#wnW^`!iM7_zUD<9uchlD&S1t&C(G@wJ6W_AzYVsZZ?1-x_Df=VRCX0P# zIoMY{^US*!98DglT`u~?ZPO59+5KwX(G9Z=U+pd{3~74J)nsn?Xzlf;1dDbiuqV?XT7xC-4&Q@`TT_N`LAF1eh8VeTi#g8{qy9CWv+6!4_e<% zT(^~JSKa4V^*>o#uf!cQD&%~_5Tnhzdtz4~D~Da?s#)P5O|8$TrnC}Q!>*kacel5_WLbRg9hA&lFZ!H;*!MN0(3p5Rz`*pOT=?_ff6r+r>mdK II;Vst0Lx7oI{*Lx literal 0 HcmV?d00001 diff --git a/browser-extension/chrome/icon128.png b/browser-extension/chrome/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..18c40b371cefc1a12615e1f4d26929d8828e80aa GIT binary patch literal 11160 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 literal 0 HcmV?d00001 diff --git a/browser-extension/chrome/manifest-chromium.json b/browser-extension/chrome/manifest-chromium.json new file mode 100644 index 0000000..d8dd2af --- /dev/null +++ b/browser-extension/chrome/manifest-chromium.json @@ -0,0 +1,20 @@ +{ + "name": "Varia Integrator", + "description": "Route all downloads to Varia if it's running.", + "version": "1.0", + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "action": { + "default_popup": "popup.html" + }, + "permissions": ["downloads", "storage"], + "host_permissions": ["http://localhost:6801/jsonrpc"], + "manifest_version": 3, + "background": { + "service_worker": "background.js" + } +} + diff --git a/browser-extension/chrome/popup.html b/browser-extension/chrome/popup.html new file mode 100644 index 0000000..c19fa34 --- /dev/null +++ b/browser-extension/chrome/popup.html @@ -0,0 +1,86 @@ + + + + + + +
+
+
+ +         + +         + +
+
+ +
+
+ + + diff --git a/browser-extension/chrome/popup.js b/browser-extension/chrome/popup.js new file mode 100644 index 0000000..8abe2c4 --- /dev/null +++ b/browser-extension/chrome/popup.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function () { + var toggleSwitch = document.getElementById('toggleSwitch'); + chrome.storage.sync.get('enabled', function(data) { + toggleSwitch.checked = data.enabled; + }); + toggleSwitch.addEventListener('change', function () { + var enabled = this.checked; + chrome.storage.sync.set({enabled: enabled}); + }); +}); diff --git a/browser-extension/download.png b/browser-extension/download.png new file mode 100644 index 0000000000000000000000000000000000000000..0174d8ef3f25ebc7095080b385b3d5429be0ba54 GIT binary patch literal 733 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P0wkGC6jgzgLb6AYF9SmrP@a@a2CEqi4B`cIb_Lpiv?O`EyD%`U>Qy!Z@;D1TB8!3a0Wfwicz*)OU@!6X zb!C6SB*398da(9u69WU|8c!F;5Rc<;uk6nf2^2a0@qFGkj$y>6I=O-<>aQ&RTC64SaL7TWZ&S;<#Bn_hjc{G8=}HfXQ>+n?C_mZ^+Un{oEG zw8LwIlV6#wnW^`!iM7_zUD<9uchlD&S1t&C(G@wJ6W_AzYVsZZ?1-x_Df=VRCX0P# zIoMY{^US*!98DglT`u~?ZPO59+5KwX(G9Z=U+pd{3~74J)nsn?Xzlf;1dDbiuqV?XT7xC-4&Q@`TT_N`LAF1eh8VeTi#g8{qy9CWv+6!4_e<% zT(^~JSKa4V^*>o#uf!cQD&%~_5Tnhzdtz4~D~Da?s#)P5O|8$TrnC}Q!>*kacel5_WLbRg9hA&lFZ!H;*!MN0(3p5Rz`*pOT=?_ff6r+r>mdK II;Vst0Lx7oI{*Lx literal 0 HcmV?d00001 diff --git a/browser-extension/icon128.png b/browser-extension/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..18c40b371cefc1a12615e1f4d26929d8828e80aa GIT binary patch literal 11160 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 literal 0 HcmV?d00001 diff --git a/browser-extension/manifest-chromium.json b/browser-extension/manifest-chromium.json new file mode 100644 index 0000000..d8dd2af --- /dev/null +++ b/browser-extension/manifest-chromium.json @@ -0,0 +1,20 @@ +{ + "name": "Varia Integrator", + "description": "Route all downloads to Varia if it's running.", + "version": "1.0", + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "action": { + "default_popup": "popup.html" + }, + "permissions": ["downloads", "storage"], + "host_permissions": ["http://localhost:6801/jsonrpc"], + "manifest_version": 3, + "background": { + "service_worker": "background.js" + } +} + diff --git a/browser-extension/manifest-firefox.json b/browser-extension/manifest-firefox.json new file mode 100644 index 0000000..75aacff --- /dev/null +++ b/browser-extension/manifest-firefox.json @@ -0,0 +1,23 @@ +{ + "name": "Varia Integrator", + "description": "Route all downloads to Varia if it's running.", + "version": "1.0", + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "browser_action": { + "default_popup": "popup.html" + }, + "permissions": ["downloads", "storage", ""], + "manifest_version": 2, + "background": { + "scripts": ["background.js"] + }, + "browser_specific_settings": { + "gecko": { + "id": "giantpinkrobots@protonmail.com" + } + } +} diff --git a/browser-extension/popup.html b/browser-extension/popup.html new file mode 100644 index 0000000..c19fa34 --- /dev/null +++ b/browser-extension/popup.html @@ -0,0 +1,86 @@ + + + + + + +
+
+
+ +         + +         + +
+
+ +
+
+ + + diff --git a/browser-extension/popup.js b/browser-extension/popup.js new file mode 100644 index 0000000..8abe2c4 --- /dev/null +++ b/browser-extension/popup.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function () { + var toggleSwitch = document.getElementById('toggleSwitch'); + chrome.storage.sync.get('enabled', function(data) { + toggleSwitch.checked = data.enabled; + }); + toggleSwitch.addEventListener('change', function () { + var enabled = this.checked; + chrome.storage.sync.set({enabled: enabled}); + }); +}); diff --git a/po/Varia.pot b/po/Varia.pot index 128d74f..a99a664 100644 --- a/po/Varia.pot +++ b/po/Varia.pot @@ -206,6 +206,9 @@ msgstr "" msgid "Remote Download Location" msgstr "" +msgid "Exiting Varia..." +msgstr "" + #: src/gtk/help-overlay.ui:11 msgctxt "shortcut window" msgid "General" diff --git a/po/tr.po b/po/tr.po index 2a66f41..e1157c8 100644 --- a/po/tr.po +++ b/po/tr.po @@ -195,6 +195,9 @@ msgstr "Yetkilendirme başarısız oldu." msgid "Failed to open directory." msgstr "Dizin açılamadı." +msgid "Exiting Varia..." +msgstr "Varia'dan çıkılıyor..." + #: src/gtk/help-overlay.ui:11 msgctxt "shortcut window" msgid "General" diff --git a/src/download/actionrow.py b/src/download/actionrow.py index 2ddc49c..78ba496 100644 --- a/src/download/actionrow.py +++ b/src/download/actionrow.py @@ -8,7 +8,7 @@ def on_download_clicked(button, self, entry, DownloadThread): entry.set_text("") if url: objectlist = create_actionrow(self, url) - download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3]) + download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], None) self.downloads.append(download_thread) download_thread.start() self.filter_download_list("no", self.applied_filter) diff --git a/src/download/listen.py b/src/download/listen.py new file mode 100644 index 0000000..b7a4f25 --- /dev/null +++ b/src/download/listen.py @@ -0,0 +1,32 @@ +import aria2p +import requests +import json +import gi +from gi.repository import GLib +from download.actionrow import create_actionrow +from download.thread import DownloadThread +import threading + +def listen_to_aria2(self): + aria2_total_downloads = self.api.get_downloads() + + if (len(aria2_total_downloads) > len(self.downloads)): + downloads_in_frontend = [] + for frontend_download_item in self.downloads: + downloads_in_frontend.append(frontend_download_item.download.gid) + + for download_item_to_be_added in aria2_total_downloads: + if (download_item_to_be_added.gid not in downloads_in_frontend): + if (download_item_to_be_added.is_torrent == False): + print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) + GLib.idle_add(lambda: add_download_to_ui(self, download_item_to_be_added)) + + if (self.terminating == False): + GLib.timeout_add(1000, lambda: listen_to_aria2(self)) + +def add_download_to_ui(self, download_item_to_be_added): + download_item_url = download_item_to_be_added.files[0].uris[0]["uri"].split("?")[0] + objectlist = create_actionrow(self, download_item_url) + download_thread = DownloadThread(self, download_item_url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], download_item_to_be_added) + self.downloads.append(download_thread) + download_thread.start() diff --git a/src/download/thread.py b/src/download/thread.py index d824784..7ba42d7 100644 --- a/src/download/thread.py +++ b/src/download/thread.py @@ -11,11 +11,11 @@ import json class DownloadThread(threading.Thread): - def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow): + def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, download): threading.Thread.__init__(self) self.api = app.api self.downloaddir = app.appconf["download_directory"] - self.download = None + self.download = download self.url = url self.speed_label = speed_label self.stop_event = threading.Event() @@ -45,13 +45,15 @@ def run(self): pass self.pause_button.hide() self.progress_bar.hide() - elif not (self.is_valid_url()): - try: - GLib.idle_add(self.show_message(_("This is not a valid URL."))) - print("Error: Not a valid url.") - except: - print("Error: Couldn't display 'not a valid url' error, for some reason.") + return else: + if not (self.is_valid_url()): + try: + GLib.idle_add(self.show_message(_("This is not a valid URL."))) + print("Error: Not a valid url.") + except: + print("Error: Couldn't display 'not a valid url' error, for some reason.") + return response = requests.head(self.url) if ((response.status_code == 401) and (self.auth == '1')): if (self.url[0:7] == "http://"): @@ -61,24 +63,28 @@ def run(self): else: self.url = self.auth_username + ":" + self.auth_password + "@" + self.url print ("Authentication enabled.") - print(self.url) - self.download = self.api.add_uris([self.url]) - downloadname = self.download.name - print("Download added.\n" + self.downloaddir + "\n" + self.url) - GLib.idle_add(self.update_header_pause_button) - while (self.cancelled == False): - try: - self.download.update() - GLib.idle_add(self.update_labels_and_things) - if (self.download.is_complete): - if os.path.exists(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))): - os.remove(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))) - break - elif (self.download.status == "error"): - return - except: + print(self.url) + try: + if (self.download == None): + self.download = self.api.add_uris([self.url]) + except: + pass + downloadname = self.download.name + print("Download added.\n" + self.downloaddir + "\n" + self.url) + GLib.idle_add(self.update_header_pause_button) + while (self.cancelled == False): + try: + self.download.update() + GLib.idle_add(self.update_labels_and_things) + if (self.download.is_complete): + if os.path.exists(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))): + os.remove(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))) + break + elif (self.download.status == "error"): return - time.sleep(1) + except: + return + time.sleep(1) def update_header_pause_button(self): self.app.all_paused = False @@ -134,16 +140,19 @@ def resume(self): def stop(self, deletefiles): if self.download: - downloadgid = self.download.gid - downloadname = self.download.name - self.download.remove(force=True) - if not self.download.is_complete: - if (deletefiles == True): - if os.path.exists(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))): - os.remove(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))) - if os.path.exists(os.path.join(self.downloaddir, downloadname)): - os.remove(os.path.join(self.downloaddir, downloadname)) - print ("Download stopped.") + try: + downloadgid = self.download.gid + downloadname = self.download.name + self.download.remove(force=True) + if not self.download.is_complete: + if (deletefiles == True): + if os.path.exists(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))): + os.remove(os.path.join(self.downloaddir,(downloadgid + ".varia.json"))) + if os.path.exists(os.path.join(self.downloaddir, downloadname)): + os.remove(os.path.join(self.downloaddir, downloadname)) + print ("Download stopped.") + except: + pass self.stop_event.set() def save_state(self): @@ -172,9 +181,9 @@ def return_is_paused(self): return True @classmethod - def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow): + def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow, download): with open(os.path.join(app.appconf["download_directory"], filename), 'r') as f: state = json.load(f) os.remove(os.path.join(app.appconf["download_directory"], filename)) - instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow) + instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow, None) return instance diff --git a/src/meson.build b/src/meson.build index dee723a..7f046da 100644 --- a/src/meson.build +++ b/src/meson.build @@ -44,6 +44,7 @@ varia_sources_download = [ 'download/actionrow.py', 'download/thread.py', 'download/communicate.py', + 'download/listen.py', ] install_data(varia_sources, install_dir: moduledir) diff --git a/src/variamain.py b/src/variamain.py index 40aa1eb..fba4cda 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -12,6 +12,7 @@ gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, GLib, Gio +import requests from window.sidebar import window_create_sidebar from window.content import window_create_content @@ -19,6 +20,7 @@ from download.thread import DownloadThread from download.communicate import set_speed_limit, set_aria2c_download_directory, set_aria2c_download_simultaneous_amount from initiate import initiate +from download.listen import listen_to_aria2 class MainWindow(Gtk.Window): def __init__(self, variaapp, appdir, appconf, *args, **kwargs): @@ -26,6 +28,8 @@ def __init__(self, variaapp, appdir, appconf, *args, **kwargs): self.appdir = appdir self.appconf = appconf + self.set_hide_on_close(True) + self.connect("close-request", self.exitProgram) # Set up variables and all: aria2_connection_successful = initiate(self) @@ -56,13 +60,16 @@ def __init__(self, variaapp, appdir, appconf, *args, **kwargs): # Set the maximum simultaneous download amount from appconf: set_aria2c_download_simultaneous_amount(self) + thread = threading.Thread(target=listen_to_aria2(self)) + thread.start() + # Load incomplete downloads: for filename in os.listdir(self.appconf["download_directory"]): if filename.endswith('.varia.json'): with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: state = json.load(f) objectlist = create_actionrow(self, state['url']) - download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3]) + download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], None) self.downloads.append(download_thread) download_thread.start() @@ -137,15 +144,18 @@ def total_download_speed_get(self, downloads, total_download_speed_label): speed_label_text_first_digit = download_thread.speed_label.get_text()[0] except: speed_label_text_first_digit = "0" - if (speed_label_text_first_digit.isdigit()): - download_speed = (float(download_thread.speed_label.get_text().split(" ")[4])) - if (download_thread.speed_label.get_text().split(" ")[5] == _("GB/s")): - download_speed = download_speed * 1024 * 1024 * 1024 - elif (download_thread.speed_label.get_text().split(" ")[5] == _("MB/s")): - download_speed = download_speed * 1024 * 1024 - elif (download_thread.speed_label.get_text().split(" ")[5] == _("KB/s")): - download_speed = download_speed * 1024 - total_download_speed = total_download_speed + download_speed + try: + if (speed_label_text_first_digit.isdigit()): + download_speed = (float(download_thread.speed_label.get_text().split(" ")[4])) + if (download_thread.speed_label.get_text().split(" ")[5] == _("GB/s")): + download_speed = download_speed * 1024 * 1024 * 1024 + elif (download_thread.speed_label.get_text().split(" ")[5] == _("MB/s")): + download_speed = download_speed * 1024 * 1024 + elif (download_thread.speed_label.get_text().split(" ")[5] == _("KB/s")): + download_speed = download_speed * 1024 + total_download_speed = total_download_speed + download_speed + except: + continue if (total_download_speed == 0): total_download_speed_label.set_text("0" + _(" B/s")) elif (total_download_speed < 1024): @@ -207,12 +217,48 @@ def save_appconf(self): print("Config saved") def exitProgram(self, app): + self.hide() + self.set_sensitive(False) self.terminating = True self.all_paused = False - self.pause_all("no") + if (self.appconf['remote'] == '0'): + self.pause_all("no") self.api.client.shutdown() - self.destroy() + + exiting_dialog = Adw.MessageDialog() + exiting_dialog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=25) + exiting_dialog.set_child(exiting_dialog_box) + exiting_dialog_box.set_margin_top(30) + exiting_dialog_box.set_margin_bottom(30) + exiting_dialog_spinner = Gtk.Spinner() + exiting_dialog_spinner.set_size_request(30, 30) + exiting_dialog_spinner.start() + exiting_dialog_box.append(exiting_dialog_spinner) + exiting_dialog_label = Gtk.Label(label=_("Exiting Varia...")) + exiting_dialog_label.get_style_context().add_class("title-1") + exiting_dialog_box.append(exiting_dialog_label) + exiting_dialog.set_transient_for(self) + GLib.idle_add(exiting_dialog.show) + + GLib.timeout_add(3000, self.aria2c_exiting_check, app) + + else: + self.destroy() + + def aria2c_exiting_check(self, app): + json_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.tellActive", + "params":["token:" + self.appconf['remote_secret']] + } + try: + response = requests.post(self.aria2cLocation + '/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) + except: + self.destroy() + return + GLib.timeout_add(20, self.aria2c_exiting_check, app) class MyApp(Adw.Application): def __init__(self, appdir, appconf, **kwargs): @@ -266,8 +312,8 @@ def main(version, aria2cexec): app = MyApp(appdir, appconf, application_id="io.github.giantpinkrobots.varia") try: app.run(arguments) - finally: - app.win.exitProgram(app) + except: + pass if ((__name__ == '__main__') and (os.name == 'nt')): sys.exit(main(variaVersion, "aria2c")) From 02dda2fc30cae782e2b1d2322da765b14b9a87f6 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Tue, 20 Feb 2024 02:30:02 +0300 Subject: [PATCH 3/9] Background downloads support 20 Feb 2024 Varia can now run downloads in the background when ther user closes the app whilst a download is running. Also the exit function is improved. --- src/variamain.py | 64 +++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/variamain.py b/src/variamain.py index fba4cda..e1c2ae0 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -23,13 +23,14 @@ from download.listen import listen_to_aria2 class MainWindow(Gtk.Window): - def __init__(self, variaapp, appdir, appconf, *args, **kwargs): + def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, *args, **kwargs): super().__init__(*args, **kwargs) self.appdir = appdir self.appconf = appconf + self.aria2c_subprocess = aria2c_subprocess self.set_hide_on_close(True) - self.connect("close-request", self.exitProgram) + self.connect("close-request", self.exitProgram, variaapp) # Set up variables and all: aria2_connection_successful = initiate(self) @@ -216,10 +217,26 @@ def save_appconf(self): json.dump(self.appconf, f) print("Config saved") - def exitProgram(self, app): - self.hide() - self.set_sensitive(False) + def exitProgram(self, app, variaapp): self.terminating = True + + if (len(self.downloads) > 0): + active_downloads = False + for download_thread in self.downloads: + try: + download_thread.download.update() + if ((download_thread.download.status == "active") or (download_thread.download.status == "waiting")): + active_downloads = True + except: + pass + if (active_downloads == True): + self.hide() + notification = Gio.Notification.new(_("Varia")) + notification.set_body(_("Continuing the downloads in the background.")) + variaapp.send_notification(None, notification) + return + + self.set_sensitive(False) self.all_paused = False if (self.appconf['remote'] == '0'): @@ -241,32 +258,29 @@ def exitProgram(self, app): exiting_dialog.set_transient_for(self) GLib.idle_add(exiting_dialog.show) - GLib.timeout_add(3000, self.aria2c_exiting_check, app) + GLib.timeout_add(3000, self.aria2c_exiting_check, app, 0) else: self.destroy() - def aria2c_exiting_check(self, app): - json_request = { - "jsonrpc": "2.0", - "id": "1", - "method": "aria2.tellActive", - "params":["token:" + self.appconf['remote_secret']] - } - try: - response = requests.post(self.aria2cLocation + '/jsonrpc', headers={'Content-Type': 'application/json'}, data=json.dumps(json_request)) - except: + def aria2c_exiting_check(self, app, counter): + print(counter) + if ((counter < 15) and (self.aria2c_subprocess.poll() is None)): + counter += 1 + GLib.timeout_add(250, self.aria2c_exiting_check, app, counter) + else: + self.aria2c_subprocess.terminate() + self.aria2c_subprocess.wait() self.destroy() - return - GLib.timeout_add(20, self.aria2c_exiting_check, app) + return class MyApp(Adw.Application): - def __init__(self, appdir, appconf, **kwargs): + def __init__(self, appdir, appconf, aria2c_subprocess, **kwargs): super().__init__(**kwargs) - self.connect('activate', self.on_activate, appdir, appconf) + self.connect('activate', self.on_activate, appdir, appconf, aria2c_subprocess) - def on_activate(self, app, appdir, appconf): - self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf) + def on_activate(self, app, appdir, appconf, aria2c_subprocess): + self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess) self.win.present() def main(version, aria2cexec): @@ -301,15 +315,15 @@ def main(version, aria2cexec): if (appconf['remote'] == '0'): if (os.name == 'nt'): - subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"], shell=True) + aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"], shell=True) else: - subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"]) + aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"]) arguments = sys.argv if (len(arguments) > 1): arguments = arguments[:-1] - app = MyApp(appdir, appconf, application_id="io.github.giantpinkrobots.varia") + app = MyApp(appdir, appconf, aria2c_subprocess, application_id="io.github.giantpinkrobots.varia") try: app.run(arguments) except: From e082de2552a46b1fb4ac85fd03cff52b28412523 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Tue, 20 Feb 2024 04:52:12 +0300 Subject: [PATCH 4/9] Initial torrenting support --- browser-extension/chrome/arrow-right.png | Bin 457 -> 0 bytes browser-extension/chrome/background.js | 26 ------ browser-extension/chrome/download.png | Bin 733 -> 0 bytes browser-extension/chrome/icon128.png | Bin 11160 -> 0 bytes browser-extension/chrome/icon16.png | Bin 1548 -> 0 bytes browser-extension/chrome/icon48.png | Bin 6301 -> 0 bytes .../chrome/manifest-chromium.json | 20 ---- browser-extension/chrome/popup.html | 86 ------------------ browser-extension/chrome/popup.js | 10 -- io.github.giantpinkrobots.varia.json | 4 - src/download/actionrow.py | 7 +- src/download/listen.py | 23 ++++- src/download/thread.py | 75 ++++++++------- src/variamain.py | 6 +- 14 files changed, 69 insertions(+), 188 deletions(-) delete mode 100644 browser-extension/chrome/arrow-right.png delete mode 100644 browser-extension/chrome/background.js delete mode 100644 browser-extension/chrome/download.png delete mode 100644 browser-extension/chrome/icon128.png delete mode 100644 browser-extension/chrome/icon16.png delete mode 100644 browser-extension/chrome/icon48.png delete mode 100644 browser-extension/chrome/manifest-chromium.json delete mode 100644 browser-extension/chrome/popup.html delete mode 100644 browser-extension/chrome/popup.js diff --git a/browser-extension/chrome/arrow-right.png b/browser-extension/chrome/arrow-right.png deleted file mode 100644 index d9f02a7ef2689791089f2d0cbde03d5f07a4f12a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&U|7|wYy{+S7I;L0OgI3>?gj5p02%Bh zp1!W^FPH>4l;y(K>wv7D;pyTSVsSb-LBgnEe&c+Tr`i(Rj4}k*H})rLAFglvyCmac z(v9;U=08+uHv1T5@^X>u+(i9dVrfez2&O)8Znl@mGn6ozVf4aaPI3ij;4zMHyU7Rp z9@@9b{+DEt*dZw)!Dq-~xFRyDHqJ({2z zXxZu6{N#vx(I+)aW?l_oIPmMT|FADF0J>4N#5JNMC9x#cD!C{XNHG{07@6oA80i|C zg&0^^8Jk!cnrRytSQ!{hJy$4(q9HdwB{QuOw}vN^Pp$%L(16=el9`)YT#}eufUd{X Y%Gdy6N$tjsJU~4Rp00i_>zopr0KgB4%>V!Z diff --git a/browser-extension/chrome/background.js b/browser-extension/chrome/background.js deleted file mode 100644 index 96becbe..0000000 --- a/browser-extension/chrome/background.js +++ /dev/null @@ -1,26 +0,0 @@ -chrome.downloads.onCreated.addListener(function(downloadItem) { - chrome.storage.sync.get('enabled', function(data) { - if (data.enabled) { - sendToAria2(downloadItem); - } - }); -}); - -function sendToAria2(downloadItem) { - fetch('http://localhost:6801/jsonrpc', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: '1', - method: 'aria2.addUri', - params: [[downloadItem.url]] - }) - }).then(response => { - chrome.downloads.cancel(downloadItem.id); - }).catch(error => { - - }); -} diff --git a/browser-extension/chrome/download.png b/browser-extension/chrome/download.png deleted file mode 100644 index 0174d8ef3f25ebc7095080b385b3d5429be0ba54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 733 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P0wkGC6jgzgLb6AYF9SmrP@a@a2CEqi4B`cIb_Lpiv?O`EyD%`U>Qy!Z@;D1TB8!3a0Wfwicz*)OU@!6X zb!C6SB*398da(9u69WU|8c!F;5Rc<;uk6nf2^2a0@qFGkj$y>6I=O-<>aQ&RTC64SaL7TWZ&S;<#Bn_hjc{G8=}HfXQ>+n?C_mZ^+Un{oEG zw8LwIlV6#wnW^`!iM7_zUD<9uchlD&S1t&C(G@wJ6W_AzYVsZZ?1-x_Df=VRCX0P# zIoMY{^US*!98DglT`u~?ZPO59+5KwX(G9Z=U+pd{3~74J)nsn?Xzlf;1dDbiuqV?XT7xC-4&Q@`TT_N`LAF1eh8VeTi#g8{qy9CWv+6!4_e<% zT(^~JSKa4V^*>o#uf!cQD&%~_5Tnhzdtz4~D~Da?s#)P5O|8$TrnC}Q!>*kacel5_WLbRg9hA&lFZ!H;*!MN0(3p5Rz`*pOT=?_ff6r+r>mdK II;Vst0Lx7oI{*Lx diff --git a/browser-extension/chrome/icon128.png b/browser-extension/chrome/icon128.png deleted file mode 100644 index 18c40b371cefc1a12615e1f4d26929d8828e80aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11160 zcmai)2T)VNx9~$C(iD)cfPhFxdhd#~(4-?RbdcUrY7h_wX;MNj(nUb&J%C6RkQzc4 z2oQR2f$-x0&Agd!zIpS0GnLF*C_7b zK9j53Ki~w5t(u+|01(K5`#ll>xcYZ}69DiN1OT?I008N90D#UjyG2(Pcj1nWhUyEP z#Br(A8io_Zo@%draMJ2O?ZD;vDDDfPuez2p;UkK7KX25lq(k*He;_ zEc)mFDN4njRh<*R1alWlEc85*yL`g1-k;PcNY)6WM^<3bs*_NyZVqU+jQNIhRwXe z>|S#$dp^NlM8pLwEttpTXFT2G%=7Hp@Z4I7mzim@Q_ngC@W&q}Fs)X}N9!;B$@hS) z6WiIE+CIVq@*x9J^LUc|IIW7jk<-Aee<@t4zBsW7G~2%6dh# zynz^`0Tf^Nt$9{GSG+ArjRr81m9lr!0HS~XYkx_gIDLR{s%Zi)drBgRbzO5aM5+Li z*|cgtTDd;BD|LcHI^~XQxEq`CEW&iDl^+2;B_*;^jy%vHKsHCm-}Ja7m66lf(+acT z1j0YE>WZLSV)whBR0_wO2*?;`;9*Ph`QLHmcOKtgT5@zRyad5HMO%hjzRnhCq6%H2<2_j`$Bsh*{AMlxOLjGb>+<4VN@lH#K$M3dc954JLj@K>#CF)4-vHT7vkz!fN!b z69b!oPOH>73<6HKHLR_lgkL*^&ko1K9y;Z!7E7i z%|?_Wqc#I|u+HaEwuAb&cjobzJ!b)*>4VHM8l@QLA?&d|hEi5`I!$x4TNuKnU*>pVN7sEkIQ#6j$$FX7)*lwNW>L78O%~DurEE zf^TxkiHPpry_+)oqValLFl*Q$dEj^kfj~5(Tjm;rhjh@Wry&+>#sCSTjgcG~STcT5 z$s@_{VG36Drj_GWM}Xzkf8gI`5Oln z%N8A1b6-r2!oLpsU<2E69hMrXJAVO}4tB!)$p6lghJRKp*Ia+l40K9jl=0u087n-! z?J7nT7ZnxlECk(9ho3Z$zZE^XPGG`pR~M?zeMVKcf8N(i$geV2T;(I|sNZV;&}~>y z5P^MJM&g0F+HT@g_P6dIOqI*M_036(4lmCdVv5qDSl0@x^d&&QC{{GRn}1naTaRxSt-hf=ke(<| ziY8-{20Xx#nu@@NmX{Nf6%_%zn>9I-#ojMV#Q&;RH^321R*5`9cofySlNSid0U#EC zHKBUah&iZ-V~O$lpb{wKu=&-?~7j zP_^wRmgm?v6}*VH8y&Swlx>b#LrG~^=v(joc~);$&UP-zSn?t$(xPp*!4}?e`u60I zP4&s^<7}_toiIwGW?$!|Al&rD{o8P$hpT-F@N-KRK|wAqt`DT4L-p4%NFO9jr{L_? z(GixaOA;x&yq%U88#CK_K4m;tkIxZ28&GoYUOyXDxOM-O7ZRM5_}$%NcOv+6CxZXN z9M4m^69mV5xAe#F+HV86CE>yX8?uW8jPSrxV-%I6em#zXB3)~n7XT|gaW+X4#7{UO zL#Kxn(C9nn2QKX%NUr8HV}F!Om}_IJ`4^&ZmLZ#o3uHTU0b3~xdzaZ}9&vG}N5H-I zMh{37FmXsgK7kH$U0&u~p{=SixcAKU7F(mcrtJ;uTITnZQ_fL}-pj*YZJw=9lr{sG zW1ys=YI5 z?AuSglO1GL&T~A?%*jRpjYA>Yr_#RtqdEos148tG49&qagyy=%Q(1!L0zA5F=ipOA za18RU>#Xli`V`6a#cE!dDW4*EFhhh)^#*aMVGABaPx#9S`Z^)1oW=6TzH=-sF7l*m z-nq*_%e0FE6_q$R%H+Uub+kuDa-#Tb{6KW@r|<%*h}@mJV2KS&i9HQCOXtXxx!AgtF_zmLgRPxeZ<7iMi zJZzKv83T$c^y)SBXV2HY`F{_ zXEpj)j?L6p$jt;4`f1pss`iJ-&fhFrHR*+u8w0Xqsk?AnVZ+T8?@L`AmX_z+5$|wB zgkM}7u5xM~MAEz68#qGr*+9N=RcTp&FNC_yX=hVJZq<_R_`JW1p4O_ov)8)6o|Ou= zI#{l^;BTZSTYFpth4{vqlHIrUgayk7HahihASi=XVGIB%lgW zlR77if8l+WNcM}7T)*ab@2F$e)_Pt%f9P?QfU_R&yE-$WCB?&AuoqB|JXFi}`gy|C}+t7b|}uy6o5M zB>M2)9jl2FNa!p0;so3G$}%mq^wBYnBPb*k+2nN-=)a(1du)bXH|-Pf!>iI_1+yw! zkdOd2JzI!CT4A{P^vp?W0ch|CnyI#Emj`*`d?-XqJS67tXs-ReVX{ZE0ov4fIeXn8 zD9u4X3dVsMsTMw=Y#@4fOKMr6ELW_qo+V*y0BCMTq22+gsp;d~B@O}?OUmE7f(K;V z-uF#RV2TZHK%xsdUE6>v5oH`vQP(Y5dItgxJEwE`g;hc_t#t;0oPcy`F@Eie(oGA3F$$r@T(;;lNy7j$(k64y*@aVslxGIKG!z!xxoBN{MO%`R|{ zUhy`5@0Y)%(l_ZGx1yWIuN$kY$$lT5VrKGivqa6NQPG^W=jh(t9KPOojcAC6e@}e1 zMfiwb@?{06t<40)+R9*z;;{ z+`%5{3Z2^W)gv)5MDBhe+L_PDcwGm`5$*fb~EFiXY0b=bT(rO z&YzCzUPa$!lyv%&$f{-h>W!Gpk%2KaYzQlo@RS7rI6C^I0uHZt!KpI~52ise@4oDt z-_a@EXD8kAee;9s<(!8LV!*KHt;_8hd*Gl&kyp2=%avh3^Ls4WxwBHbQzNRvNYuSOF2!R~~iY_q` z3pG0FOEi&#ty(`_-#usT9qiBz+MS~-P!PZ931DYO3iS5%@cBqidPw^@0$Y8(`ccWh z!sxBk08huh{bNl@Vd7y$@NLH_J%Kzki2F#1bu2$fi%~} zlYHb8y1gYI64*=1yne2aJ;2Y}DBE}c82PCiSVQQ2$pt86iBXTMn~BDSMhxRe8mVs( zEgJh*r+x8KIe=nUu=`LNoh1=DVS`7W81G{sy?k51{s$tKH){QT_Z8qr;OEc1galTM$Y3AQ(*&66KPSbF{P6GA$$pcWgV)!{8 zB>O)0R={t?OOl{KJlZnQ(w|d4vO}IfjFOh(IdxdGk?kM1M!$#m87mHe96EbTOMQgt z22=YYdHj)O4&13V5X|Wuz7_eu2*}w4%WHfj)-#R}13zv3{JFtZh#u;*F*r!jCDMVt z>W!nJfnq|vig%JGCpR~9WW11Ma^E5ne9D~LoreOJQ0}}iQfA_AHW}l40_vqFkUsyo z3R(5?OKZ%H?@En*kYHZCbN55tT!OVal7~S|VIifY%1Oja@l2&UtqYx95sstMInXXE zahV+GPgN6v+~MCvx3uvWv+v2|sFNd?-z1lJAf`MnYKHC4RaAIN(~V6y<*v#!s6_^D zcb9Gv=6}r%{$g*OR_s`C#?t7SPvvU0)O3$s<9@eI>&?d^`=8oQQ_iRQ9i~h74-Yel zEt%XOwb=Gek-AcTW=Y0VPrq$!6YV=;A;*)f>^_n}NMkea$vWFWb~063hfrO;fAW+# z?^8ac5S4q=pP)dNskkya#~k!+j|s3<2$*S>aI^sY8<+AJGAE5~@gbnAe$e!J(=9|{ zmARWG%BDNAsp-#4s|LT{pB2qsCVovoJhQ(thAj9_VD|GQy9T4o@c*r#Z_5x@q>Ijq z*rZaKRM1O(A@h$lmnClk0IaKhOTo2`5}xM^5(`CRfExTYu@{tI9_SdU$|8TPF5MS@ zjb0Lg-0gYIo(8wZy^W^VL(CAWXeA0FJYnIOn0fb3wB0~wSGk3cp%EAoh%U_=G>?3v zqZ`I5P@F=&>LU!UJ`Y{WO|t&oZ*{bEiWn&A9>&_FCr} z&Gzdi(WkP>*3GcN{&E+P(R=%QY_r&H5_sTs6mf$QOf@vu|$!ywGnsiMQ<*36RZj*5<0>ktH^CpmQD%hTJbs#Uo6 z*{!!!RHk`TXQppF{e3EZvxqc2eCYtAij2ps1T0iNNRohrM-dL$@zdKKSZRKZBn$c) zZ~KG|1*Wp|dt~nB`rv+x+Y395!;viaOwj?cfHCSf*}D?P*E|?pSVnABAGZk zRhf%RuC9QEDyKW>)m84X)c$@hE>G$3ja`xJ7E>QOA~up6|Oi zE;g>z9W|$?FCFMms(Jf((1Tcru}b8XriMm$3=GAv>%@%YVD<_PNR#J|Ni_b4crp@a zjTpu1>aThg;UH4~T|J&~GY|V*Xmz!z7SZ<*%ttCC-h9C^x@_e;5#f@wyxl!f#ZC+sJ}kVh7x>*}#C`5YBh3|Caq^;70TNQ*!Kzk4j`t*X6a;_k**v3FZ}aQ# zPn@gyV}7d7r644za{4bo{%Uitm5Gc+c7ABcosgPDw{8yiw|hU`C&3}HdF^U^VJsh7#m9-4^DynlCyt%lboGL(O+XO4ku+s z_`RrgGt>OfzAccg6&u=-`U7qY$=SHYXw;r7$yZP5;Df6|Z5II9S4lY*mfA%6ZIJ-e zO4Ehxa?9iav;j}JK!;?oNg*D;xgh~ z;`a8hMHONE*4EBq2eU4#eV7Zts9a;9->V0I;9;%)JT!j_BC0(?#Elo~(7zu*AS`03 zzea!jU}cR2Oq8DQni>9j&aD248^ov6`iMolK9kzeGR8Ig$(t$a1bsUO32f!0%d1Dlh z42Pxbt|;<7mdd!?FSD4ri8oh$LV$SD5$u!i-nF&c3$yV3Mi}!GPC7T};ec^ z!LMb1oS-Qw*^B^_@5_rp5$^!8$`otfR(xCDC=sJ zJmifkmGJcBF~pLI_`&YPs*IMza|0!6AEAxagw+*?kx6t_2;9EhFuinfdA5YXtR0vx z=5n%`pT8Zzw&=u_>S|=w`zaisA$QzamP31I?5BgRx>PKPUM_IpbNBOn$I%1N=TP?r zAaKj}Qi~%^hJ<*`8RgBw!69p2P9PmXMrOnXsLhCyr2Nu+!XD3 zS~m6D`E-khmLsO~1buVWY_4dK0uR#|z*4OsjpE#Xzq+*rg;y>F-QJ{7SJL(maPv|r zEB+@OPFJ`+n`0tVtgz30ZykQo>XE_K@v!x2bvg57w7I$zgLPF1xQxV16_2HP&YHsU2^k-t7TUvd-A z57xN-qZ;i4CGE-5!GV}!q$$G$$7aR zbm)X)?x|^OYr4Ljga3Ox?a-1*J;q(4Hdo9ncq%~3BLvwZ#`bgfPH?aZdSVA@+k1ft z!g>grRDL1SG(UPBy<(dpJs)5Hr5*y;cGUT%`BYO!M;nKl6ZQ`Cy{Kx?vMA4~MGoT6 z>}d)ELl+yav;lyk-> zLykea+{6NYGN!>HeCR|eDQS-O=r-njfBscDfjF19n9UpiOn#q4ygCH(7M5L%z6wv0dFJ`wXz--*9dT<7Z_Ren9bP zWPOmKC0;?Uy(ygnw}b62Y^?sI?wtA29>klf=v3AiaLQH$H~uyQUqTb4@oQ<`CA-7J z`^xpUi%`bjzzFzvyZ+{&bgsQspEjGEj+$PtQfc0is6b$xJw5&Nmwl$K#M9s-_1oKK zqw)HOT7Ptp?E-7Q?I_%4h*em~C{{~61mV4DKqS}>8MQ2|OTy_)$9-9FK@6e{u(dG@ zOMS)ng(w(%nJZ^#sAz897w;@KXA?_d_49q9s++jg#ew_6q_fU5oe;#~IhyK`Hus;_ z0F!j^FA{eb5{z=%{{Aia(a&&vB+_5T3YQ`KLe!7qd#CU@s6#W6>TxiOc7e>7FIPP( zp+&y;zwA2&58VvllXD^H&FN*_v*r6dauXLT&H7=&sYe33ni@j`UIYVnQBj(B0AI?h z+1-wo?hG%lr+qBq&4kOo!Z}Ze6TD|*mL*&Z)D9o ze07<9*Et74fR2KkO(YD)nUc6z`h(+&ghZ1A9DcmP%fki2DVO*AQQy~&`!QUG0?7)B z&xzGFWP)&(o09Sl*~y`GOhKSRF2uF(QN5kMDYo_U-VY3C9v5Z*lNs&2X;;Gl_Aow6 z&9E*hO;5>hj(pEXv?ofKV$`2+e7?mt?mb~OyH$81ASHEcD^UG`CrdAFZMQPmWjhle z!Votw5GRu}v!+g5F@;{k>x@g>X}vZvH8n)}<1e&WmANwCm~O@;LW+7&M>7HCWqnR{CS0Hr9cJlC1l=aG)tllcVKI6xkal+e zXh1VGFDzGxjqmQ}Sj5!H{%mTh;SE+MSW?26LbmlL6nk0snw1p_qs!Vy!06xolH!MT z#G>i0MwMepYmuh$JAwXa+YUj+*gXLWKRN*9ESs-WrkQOO8HmcO zzNGbcMMz2c%Ooc2oAMATBB}}s!0du=JbguZW7^wL)lV^y5Jo9s4wZ5{EeSXA z9*mTT=#P_~FpEfSSw9Nc8@D~7TItZ;?G4zuaTQPF^Oc-YBdYDv^N^2v_m=Eo9GE>B zj|4<2rJVE@kb>Q9Q9xOuS`^XbLlXr0)U4xg)uQBV=>AF}EXWmhG)aRSj@cL_R-$4? zN$Pdv>h}V+1|R$cB&vL+d3h_1#>&T>L|y&ZZYYDSzf5{fZrpqgaS(y_^b##+nKL3HANgi+ zrfW7b#R+|txVZY~GEA)ly{pKp+Z(Kv-b&#XuXVu?DDwT)LADVIGuNIdmDvQ36sp7J9<2*bX+ode8Pg?h)hAm#9row?|B;#^^oIl{!;$@WNbN z%+wHNIV_JVR9p=VZ6-VWon;>V>aG|{dm6%O>*(1#-yA0vH?u~v+$TcVm^CgeJd3*Y z;|K{neJIK$ZCYqOAsR^?++>)z7{9fc6gpN2tbaEm~CGF~It^yp84B`!=3R`+!(? z4<_*BCy{PNDmV(L7!TaScc`_k#L&1+rwdhe$fPvqp?!nCm&SKs$8*C-KdklfvncBG z?G>aX;?7f9U|Fjq4x7G_o4!fBD=YJRy;aOkSFbw}UR3Hg`(eK_CL`tB19KE0M!lKp z@85qlfWlzl;^pL|VQqhLpOE6UtS1+@3Q{NMs|TLz_p{~9%y+!;ZmyJ-xl{cbH>T12 z=C}att28*g8lEdJCz{gE4A9JzcTKMrIJj6{3NtXN4am|+sqRXbW`X`u(qme0X_ z`%9al$>Gz!_Z2wLQ>?dlCmR)`{!HeRiWg<`#D;rV7&7pe#}hX8U(${ziSAMfa_`w& zC%7zX*T+ZXrkEw%OtUD!xFb?1i()j5*5xa$TmoN#` zO+ZoRhG9o!@PU9hm5S4I!X4J`^EHvuiL0`$cE#1Ux9?OAne#Y-z>ct^AIa8@6v9EB zGxk`GRB#^qSD~72)KIbBVhdc#Xk3OwJ>Q@}4M%?6=~a!Edl2(S`il?#et}mbGJ@}nIqi@|s(>NPUk$WyiyqNG}w9-Ivio4F}X0xt9s3jOYb$T0HixjJ!^}D?t z-w?Q!GL{~lt=*c+tKMyKkxAeUVXnW;lRG;{IMa|=4W+I2;aqGB8Cn-QTH!A8yC=u` zT+n^ELhy_Y9xdNY%M=Qj0*-n|feUgTKgR_*{&qrbU7ah-pYEomhFzhP*lw;((sC$t zP)m#Ljw3U>imtA9MS8@pxCB#|Y`Fg}5gzV@X|&N4FKgK52NK#QTR7%`ugL-9ebVCt zDKnr$z|;n0@3L2*bXv^6GXDBdrXcNC)4pL=WLjq6Oc_P4-ZSS>F*R7KF3IXF)=I|M z!|>rTfzpBYZh$b=2~rMo{c`UD0e>8TS^Dx_C(B{SCss{KScHmFML;3Q|N3HWEkmT$ zzw(>wQ<8inEvJER$*b8iWbGmQBD=@_>@T;2)A!}jzdu`jlLm10F(#ngJZ6oNdk=da zX@JbFt$%k{y*lopDk$izu|Hp8rY1;95u~%gB~O4*C_vnif{~FT-vIdW;}8s%0ytiG zL)r)eh>3T8z!fmrxR}L}6#{9VcP9)EevuUQ0T0hz;!nB06$^j>B&3X5WrMYvcUDwa zK_HeRXL~-)P`RL*8dq_vAA9FQsij<%5BHDKFzwi@ziZ?OKNYXT$=daleuOqlFjR)3Z1bDVb9=V^Hu`LH?1% zDzWhCgpL=9RKLgDI7)X>WeVDov6m!0J%Zt+WDX3rs5NR@T=ve)Ln|FuZV>stCOceI z>tc|1wf7Q(Psjg##{5grc|HqZ6H)EsC?GIxH?e!Ct(}Gh9A4{4O2SBHwLJ~TdP~2$ z7kWz3GdhYZB_g=KHfdB-!ITU7W&j!FAER`%yNl8wM}kWNEK zm6<8Oqi#F3Ggm4P4J)*<5eDM|va<4|{sKDHr${qG=Z3w}Vb?+d?siWO{wPHdVlMGY zbzrGe4iX_DVh@Wc^Gk_!4IkWH_wtPhSRN99G%W~Eb6j1d4~FQ_X&;@HjG8`reg5L* zp8{D#5~@l$7B_RV8V(tc6aBXi`3B1?EF^;34=pluH&4)|pomZBE${$oI9$X{-SB>K zn7scUgW3IJW&GbXHtX*|0|UgwirYxuk1&*{vohFwY$-&Z>$=)ffk&(%8Ug+OUCO67 z{`A~2)`1JO&Zz!KKlJmsPsC7bYmocwiQ@~z$KADmC1pDy!jvLZc+K+?8ts)%gOS9h z&0E}8D&9vM$;`43y{TATrmrs@4Qak_&y?>Oogc2o3|lYEMc;j36Yo=1wilxzv-8IM zWEN!P-IeT2HZ&wg&JUI#RTXPuBrxEq*KdkM43KWat4^yL!>0Lhe=i^nm(%@1DmU^T0$ zc^H`6_&f0UXRU~>O$uvf)D{_Y$cv-?8{fjN@5e!qJ~ z9>hz)%I|)8#HRY@exO#9%37WCHF=88&VjZ1$C;mIM&GNxZe1q3=ncA`9dawUao-0i z-t|(5WXs4H8w(Hj()svByALixR=SJgnGUTfe&hJ&0y;rDadL~@s(9AemX+;zyBtx5 zWF{Xl(`!E^ivIcfr&^R`Wd+OsU)-4)c}qBLKXo8a1)4$a74?;)hNcdpBZRr zv;D`Dx>M!&?nujN(PpU@4#%bE_rS=?XwP-ZVeX3NmM21)w)Ae7vkc?ClYKak8Hzu_ zrbtnAuHIiBHZ&%VX6&Ys)_dK&y}gGjxFJ|6;sf>j|Ewtc=>xL4<3g)qc5N%>SHKCL-C$YR`1H768dEb*@%s;uT}Vlecv>cCW;Vu= z7ha@~d+HeUMl4 zGETyug2fhaJ<_K-*pr!ld=UJe$pV%_)FL95gBo$4eZTKUUheDOUj1o`jvPOgo)=Fa zM$aPRr#tb6bM(Z`y(-mNal3#G1yu=VlNDa;@$Gr)MGqs-=hRYHe)g|Jym2 zMMW2_WBQ1Oi3g;d)2L1&a?p_M9sm+miz=rsp7$-Kfm7-(U{V2e<2md2lX-Xh*G5yD z;zZ>(gEN20)%R&=GeGnfkRi8SYmNpsns58RK^=Do-Pc=N>>9G)SIZPpA3m3F#}&d? zhK?M>KY4vy-gRp1czaA}Kw}rv9VmKQoV0HDV3Mm5ThP?t)m&d>QpjiNqSckwSISWp zneH>ku8(E}haTe3Z%@gFF>+{cyEl11T*D*|PWJiFT`*`;dtH~=)%wN|+q8o7b;(~F zX3VMVFf?Xz=hf`!M=HSRzGKn zPhfwR4l8d6TaE1cnvt!;0kQ4{3~t_%c;`@_F&)F>&yqi!)%$I*)jIiVSFw>xGz}Z4 z3a-@u&MvM5@o89hX2ML%747~MrCJ4E$Q@T!KwVC;p$Jl>iWEV5uZAj0FQJAe9Yi{z6F{1xAkuq4ArPwc4uT*xD4|G4 zdX&&Rf#dFGZsz9ZcINH3v%BBE{k8LE6Akp9QIWHd0{{T3=UVDUcNG24pdr5NNm6%?5&(!aD*DloB zBkw@wt!3eNN4x)ueGgUC-GIdZxvmDuHa-o(eT90|6wBQSuFus~OoHZibD?%%jk$qH zKWyiat}40P8%|XiXQzM`g~gY5r2PC^v?eqrplN)8Ur*>M5dJZwTK8ynz$rj7Fh~_f zqe`cv|2oxbTux${gADl>7Tw!kokNjQoVdJvrN{}6 zPP@|#$#Hn{RQ~(*3N%#7G|u={x!3Z+mcV%|AsS;j4Jxrk^4#XKV5H#anXY{jTD#rD znS@&*TKn)d>W3?~{ow1^6MgkJ4+vnWbC&#jpAGk)(d-vW5>FMf6r$=r1c@qC0EdZx z*;;RQ-Kyd*R4qbVSXY2P&u6;6QuA=qL^_8JC!^Q=36uuSt4m>qQ;GtNw~VwYxU+xK z7CyBBzo+xY`0W0!RC!3FV*WY*=CVNH64^Wk1j7g}U)Ja95aJNQxD)dYNM2` zN>uS^tft@v8?O0S`OgIQtNYhpOE0(j;!R!juqF^SjgI( zH<24FaxdF9zFNuQzfRZ`$!|v*r1SSYP&kdeRD6rboyAfMrBmvNQV;8ZsgB*9b8;vz zc_fU11h|Si)g}_3TrCDRxXRv;%h$LV7yih52b@+`Gb63ds)P#w&g@6RDg&iSv^ykR z5gr}8KiX4#TrPL~yZ6#-)YvfL)IATnq#MhRtm~3y{UtiZ9#v`~f{&$IUZZAns$uUS zf4=Q8dPqTH5AnAfb&!_8ki=H?YJBUQa(A)qZ#v*TSgT>dr(AeIA7%;xTwXYG?@TF~ zC`bH~q7A3eRl#atA7i7uKY#!DJzjM+OUwM;9!2QaQ^f`k`p=rSiyu?*WxWju3F)+G zXh47{7?sGslsPOWmfj9^p#=Shp(NW_E90mle75k+MmCKSyMfKds&to>c>LDpWzt<| zO2-c=uCqDCSNc9`$Pr!eDuUN6rmmOLczz1q=_i6dYl!#2`E3mm7_Jp4*2xN$&3%qu zuaDLLq4{&tiy6P4Nz5h*uO_1jYg1QrGDVyKUkMrz`@yvWLq$Q)zN_WvSosN2Ns5dK zM}z9gyl4S#aJQ@K;1$psLGBqtv_peKtr~3FD%3;~*c8&VX+s4{y0_Lpy9`&!oMIV> z#(LgIHWp=O@uB@EI+H{kZ6|WGR^5fyy#m@sQM)|2BdezO%Qd=918!aw)ZbhPn~p4m zE}Lv$D!950NYD~Qt_FiF++z-8!ry&5F)@uFWv+!@+AJ$URmSuFwb#qUWbr7|A4SCn zB`krISoU$FXg57yeDg7P(`c6J0#kieEnh92ZT18UR%7mID*NjFx2t>+D$gh#vtLBW zXdU%Rpc)X^m4&?ni9-A>01E$zmcL>p!q&1U+#+)Jni9yzDJ$d;4M9@kfxgTN*LHRBEB8zHU?dSWRsf-ecnK_)PY|q2x;3Xg`qZn|v zcwH5ciHT|XXbPbsi5Nt4p76g(NKt{iO_W;cyqNFo#y{W7;A4SZz>>BC_-9uZIBf~f zu0oP10VVG*$}PzC7DuKfcmnF%iX@@&^A%;q)-huT&GMt!wLW_aTRPW5Y*3(bQ^cNc9*E!VMZv#rxB zc{k>C*dU*fb-0xq2j@p8m0Og07(<}IHABLINBh24jhgFB?UX$<>aD4iiIah0MMVWE zoJ7?lE*vNp;raAKINLW4QV#Rpn|r(0S6hJkBly<5d7vSpSProY=;wW>ZQb&ak{KR)_q{X4K$c&c&!G}@8e)ES0LJ0bW6 z_to>(uTn5Sb5j{5(Kjx6A9nA0VQH+Dn6{AcxTyor*(Df#_>kVq?^(8wkHo+k-vE*WD8d9buB~!kW*YkEI+N8O>016-gsDFvgrYuEkM~ zKaDzwG&wqJmWo~}QL~l@IR1kBfvQ7P+3?S;NQ6tp7#`CU7_UP2|0_qKS{=B_Z3`@}5-{c=xK@uKL zKVLBX7&%!=P;^=b3Yt9{6{*O*8vZ7sUx+k#Da{0_aU1Iy2ke=I8HF`Bv^x2!VC%6; z)xW*n&|jJ|AHs|LRMb^iUDv+56muiEzxZ`d1jP)2WPa-U-`+r0JFpOvidT*l6!(ik zT{Wjj{S~7|?JmYL)y38mJ@geIY`^zx5dUk2z#ksb;xW|#GR2*N7fwV_4u`K{%x}wr zLK2@q;9i-3SFMSz?iU)XTmX~LKEj7c7LqHel_STa68J+` z_`Pq>1QLpfd_a!B>HqMi(@(I|D}8rG9H<_j=F~);dGC>(QaPb0ua=H#>M{iFI(VN| zu&q3oAsJ4*dlyyQ*_j$g`M}hoIa$=m&e=~-hgnzKY{JfFt|qsJrGwLtlG~M=yV%oe zg}2cw#VZ!OIj9d67^T{6zuRyd{X`AIY(_^s?jN@^>OF;>)aP(wreA49E7e=TfP4~$ z%Ih>>S|7))tziuT36Msk*nGC<<~_8j^FFCsy=&CdU#~0O)k(aor)sh!da}3N_&ES( z9H^sBD}$BYN}e*+4vAK?1E4u`>t6NoB(gg6Nz-q*rwc=n#jBVnHc`1<5sy!V&+6N2 zN~IKzD_vd~&VV|54LqFYn!`T-Df@l)EkkN8e@wZPNNR_v+_MM!~9yCrekYh@m@DCOA>tAH5o`LEnMY!aA*{dk;L-!H{Ry?>s48o%Dy zQ_rfRm$GI*cr}l2TbDnhem1$Yu{oMNs%+`^F1u57R{2+xiOyW2vZ9c#B2}gRNLBWS zF1#PzNH#V;n1yv5lBK9DXJjM1$X1+U!*N18=@Uj`f^E~cmArDWVc1jWz{CSs5msH2;itM@U7G6)a2Wsule>}H=W)0@|uzL$F;r1 ze$ZSk+p)@~Dwr??p8aT<#9HR5DGsDBMzAqh)a~^e{E1VXZ-VB*>cVM*NZG>thC%q5;Qzbx={@Cz(qydi@CP>UXdq+NmK=0cqgB z#&88o^N>zxCN8eSZXtX8@-Q>25xqtVbWq?b6`?Wk0phVhT5~Th!^ivszCJtx#0y&* zKiBD^9^eg+ZKXp?N?5#plLr|TYh-yf_2gdXUUX4&*qR~(o3_lZ5}V<#%VW0u2TSup zigGPgPBB>}InEy%bsF<#SYlX8hz={IE!w7@GnNVhO63J`dYS1N_24QmD64u-yQcM|@ILC<+Qco$^37Rl zabR-R_TrB6yYl&ETFH*PznA7i0LmU7SO*6!wCV0l{YB zn;Dr-y!38*_K-mP^`~~zv&fA};XR_D&iZ2OKB)A4^c8_*(Mm*_J#TN58H(LUI5us@ zuRRA9^97Vt%uoyeSNv;C++&BiqY^GahgSen(%OwXeOEI4V2P%57;MZ2nlcS1Be_ol zV^h!B{`R;f;_Ei-gi2O|udHp^!5~t1yQ8?#z#h45QqMv^(9USVPFy1?*A_&l&f2^u zZI;7)p!DuAyzPM$L{3xUr_7%{A1JF%Ak$piI2znF;t0KBo@Y7;!LntS$9j$~WLM=m z97k2G$;2Khup`XUS^Kp;>OiffESUVZ9f)P?O;@X9N%E!LDZM{=sD4E04c zsjLPui)(;}!`q?&ll0J05-#;LKPwWLQ`UxGBRSfvg2*;{*hSP9FzH>6v-5CYM1~Wb z5=|3{vsX^tm8WDOM6L0q-cA{7f`txtN^l+{LY_d0xz7G)CbsnVJtz)6Q}3A{!$-0E zy6L>V)NVS9X`S`wdu>pzk4iBav(jxw1kxAS3FjJf`>IuU0hC&N=gFt=_LsI(u4*2G z@{sOu@=G}Jg2Vk)s$ho~Pd33RHW&fAgpMJW6b&)_28)LuRY_a0;Ud;OfeYe>o+N8T z!D+lJEXXq%XfzV^RA{Yd16?zuw!&WC{3+2O75m$@n%E5?l!T|P=97MEfL`#^V2wwQ z1VjB;d;=M3xNIZpTsC5Tsu$FzUK}pEpewV9=9?i}BbToNke5)+CSkMZZi90><%SR7 z^I9z?q-AAMGmzR&ve%4|Jjtl-gKj?jVOY1yn-gtqxdocIry0NKpx0Iz5>g{!g6;3MV`@Zh#rgcUpVfB4}? z+RZ%Houm=Zcf#M1s7?kuJK9H@E9^{(>=t!#ZO64{0v1p91i>8%kihNnv17N}LPy+d$}{%B7kQ^vr4MLmUQw@nW+% z?=h}-Q&prLWjyy0!p=pO^~3T7%;2(?If>??PM+$*a2&d!ld=dVpd~*AH*wt;bd)9c ztdL0yt1O%f>p4jD$e06;KSB}8F=wBu^hQik{EfY;@tV$MHmlE3aAp?2&BdVJ6nq_P zt*SbEtgYd!q3jmp6<$0pY1-(HO^)V~^E;v%v* zxr#&w6b>HrLwjyt5kjW?+Ay>!#@&cixxNJCl?e|L8oH1BMd9;DzvISzFb?(yqAz$t z{BHt9w)LpC@T+Fx3!3`+P0_tic3qrXi2M7VKJbw+TH*KOEY3ju?iV(G`!T5lA=Kkh z9`!yVYpcID{v|tdzdb%G5GzJAk^rGyqCv=Cwg_CA&DnE+VrSQ zTwIGS2z1zP!CbIt;A|@#!w}`ZBq(*tBXxV8Q!RgQYf_@S%Zw)SNz9 zbh4bzeFV=Vz&_a*XeMKM5a5KCjGll(?gg+m0Y_@!`1U z|NHQhd5a>SpvE3$*+AKA9}%M6O42nOQrml?x*k*+1D3@(M6Dz5u8^iQffKiBJY?0c zMyfHVaia{edqce|(D5Z*B_I(a6EOZ>j zb^FlPCwr))~+(u79J&M%&rO(vy$fF7{!Te_jObQ1G2z+4)UowEknZI(>MOzFBt%~8flb0I(&EpU=0tS z|H{7kd(W0BY;ED?U+U{K*sTTrwM|3737fXh27wAkFzflwOp=Kz#bAQIMr~GubCN9o zz)z-r!)b4sgh#Y`TnT?4Mo)9TRrjN$$3QTl%wSum`0iDIAkBFGtU0oDz%#|}u>&B#)3m4{$p2I3OsR0xZ0qFlF_FyP!g}N>h_^Rv zoCU5nHptgJe#1iCI%5*3MEo`X7v#pyV>cgr#t!MQ^EAHR9@FN(;ZP#WuBYmmpuPKs z=keDt_jk1ScanGTb-DvUTvS|ANK{-%Oxi?LLS9^4UQ9|*R8(G6G&wS~=6?yEUXE_g x!T-N-^RVyUoxuM;8T{Qmo&5amJ-z?0BPI9WNid6Xs&@|HxrUy4wW?k8e*j=|COiND diff --git a/browser-extension/chrome/manifest-chromium.json b/browser-extension/chrome/manifest-chromium.json deleted file mode 100644 index d8dd2af..0000000 --- a/browser-extension/chrome/manifest-chromium.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Varia Integrator", - "description": "Route all downloads to Varia if it's running.", - "version": "1.0", - "icons": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - }, - "action": { - "default_popup": "popup.html" - }, - "permissions": ["downloads", "storage"], - "host_permissions": ["http://localhost:6801/jsonrpc"], - "manifest_version": 3, - "background": { - "service_worker": "background.js" - } -} - diff --git a/browser-extension/chrome/popup.html b/browser-extension/chrome/popup.html deleted file mode 100644 index c19fa34..0000000 --- a/browser-extension/chrome/popup.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -
-
-
- -         - -         - -
-
- -
-
- - - diff --git a/browser-extension/chrome/popup.js b/browser-extension/chrome/popup.js deleted file mode 100644 index 8abe2c4..0000000 --- a/browser-extension/chrome/popup.js +++ /dev/null @@ -1,10 +0,0 @@ -document.addEventListener('DOMContentLoaded', function () { - var toggleSwitch = document.getElementById('toggleSwitch'); - chrome.storage.sync.get('enabled', function(data) { - toggleSwitch.checked = data.enabled; - }); - toggleSwitch.addEventListener('change', function () { - var enabled = this.checked; - chrome.storage.sync.set({enabled: enabled}); - }); -}); diff --git a/io.github.giantpinkrobots.varia.json b/io.github.giantpinkrobots.varia.json index 4aca95a..41400a2 100644 --- a/io.github.giantpinkrobots.varia.json +++ b/io.github.giantpinkrobots.varia.json @@ -31,7 +31,6 @@ "buildsystem" : "autotools", "config-opts": [ "--without-libxml2", - "--without-libexpat", "--without-sqlite3", "--without-appletls", "--without-gnutls", @@ -42,13 +41,10 @@ "--without-cppunit", "--without-libz", "--without-libcunit", - "--without-libssl", - "--without-libuv", "--without-libcares", "--without-libaria2", "--disable-nls", "--with-openssl", - "--disable-bittorrent", "--disable-ftp" ], "sources" : [ diff --git a/src/download/actionrow.py b/src/download/actionrow.py index 78ba496..e43a61f 100644 --- a/src/download/actionrow.py +++ b/src/download/actionrow.py @@ -8,13 +8,12 @@ def on_download_clicked(button, self, entry, DownloadThread): entry.set_text("") if url: objectlist = create_actionrow(self, url) - download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], None) + download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None) self.downloads.append(download_thread) download_thread.start() self.filter_download_list("no", self.applied_filter) -def create_actionrow(self, url): - filename = url.split("/")[-1].split("?")[0] +def create_actionrow(self, filename): filename_shortened = filename[:40] if (filename != filename_shortened): filename_shortened = filename_shortened + "..." @@ -68,7 +67,7 @@ def create_actionrow(self, url): self.download_list.append(download_item) download_item.index = len(self.downloads)-1 - return [progress_bar, speed_label, self.pause_buttons[len(self.pause_buttons)-1], download_item] + return [progress_bar, speed_label, self.pause_buttons[len(self.pause_buttons)-1], download_item, filename_label] def on_pause_clicked(button, self, pause_button, download_item): self.all_paused = False diff --git a/src/download/listen.py b/src/download/listen.py index b7a4f25..341fcb8 100644 --- a/src/download/listen.py +++ b/src/download/listen.py @@ -13,10 +13,17 @@ def listen_to_aria2(self): if (len(aria2_total_downloads) > len(self.downloads)): downloads_in_frontend = [] for frontend_download_item in self.downloads: - downloads_in_frontend.append(frontend_download_item.download.gid) + if ( ((frontend_download_item.download.is_metadata) or (frontend_download_item.download.name.endswith(".torrent"))) and (frontend_download_item.download.is_complete)): + frontend_download_item.cancelled = True + frontend_download_item.stop(True) + self.download_list.remove(frontend_download_item.actionrow) + if (frontend_download_item in self.downloads): + self.downloads.remove(frontend_download_item) + else: + downloads_in_frontend.append(frontend_download_item.download.gid) for download_item_to_be_added in aria2_total_downloads: - if (download_item_to_be_added.gid not in downloads_in_frontend): + if ((download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_complete == False) and (download_item_to_be_added.is_metadata == False)): if (download_item_to_be_added.is_torrent == False): print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) GLib.idle_add(lambda: add_download_to_ui(self, download_item_to_be_added)) @@ -25,8 +32,16 @@ def listen_to_aria2(self): GLib.timeout_add(1000, lambda: listen_to_aria2(self)) def add_download_to_ui(self, download_item_to_be_added): - download_item_url = download_item_to_be_added.files[0].uris[0]["uri"].split("?")[0] + if ((download_item_to_be_added.is_metadata) or (download_item_to_be_added.is_complete)): + return + + if (download_item_to_be_added.is_torrent): + download_item_url = "magnet:?xt=urn:btih:" + download_item_to_be_added.info_hash + else: + download_item_url = download_item_to_be_added.files[0].uris[0]["uri"].split("?")[0] + objectlist = create_actionrow(self, download_item_url) - download_thread = DownloadThread(self, download_item_url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], download_item_to_be_added) + download_thread = DownloadThread(self, download_item_url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], download_item_to_be_added) self.downloads.append(download_thread) download_thread.start() + download_thread.pause_button.show() diff --git a/src/download/thread.py b/src/download/thread.py index 7ba42d7..b132424 100644 --- a/src/download/thread.py +++ b/src/download/thread.py @@ -11,7 +11,7 @@ import json class DownloadThread(threading.Thread): - def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, download): + def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, filename_label, download): threading.Thread.__init__(self) self.api = app.api self.downloaddir = app.appconf["download_directory"] @@ -25,6 +25,7 @@ def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, self.progress_bar = progress_bar self.pause_button = pause_button self.actionrow = actionrow + self.filename_label = filename_label self.app = app self.cancelled = False @@ -38,45 +39,54 @@ def is_valid_url(self): return False def run(self): - if (self.url == "sus"): - try: - GLib.idle_add(self.show_message("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠟⠉⠉⠉⠉⠉⠉⠉⠙⠻⢶⣄⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣷⡀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⠀⣠⣶⠛⠛⠛⠛⠛⠛⠳⣦⡀⠀⠘⣿⡄⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠁⠀⢹⣿⣦⣀⣀⣀⣀⣀⣠⣼⡇⠀⠀⠸⣷⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡏⠀⠀⠀⠉⠛⠿⠿⠿⠿⠛⠋⠁⠀⠀⠀⠀⣿⡄⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⣸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣧⠀\n⠀⠀⠀⠀⠀⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⠀\n⠀⠀⠀⠀⠀⠀⠀⣾⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⠀⠀⠀⠀⢰⣿⠀⠀⠀⠀⣠⡶⠶⠿⠿⠿⠿⢷⣦⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⣀⣀⣀⠀⣸⡇⠀⠀⠀⠀⣿⡀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⣿⠀\n⣠⡿⠛⠛⠛⠛⠻⠀⠀⠀⠀⠀⢸⣇⠀⠀⠀⠀⠀⠀⣿⠇⠀⠀⠀⠀⠀⠀⣿⠀\n⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⡟⠀⠀⢀⣤⣤⣴⣿⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠈⠙⢷⣶⣦⣤⣤⣤⣴⣶⣾⠿⠛⠁⢀⣶⡟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡟⠀\n⠀⠀⠀⠀⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠈⣿⣆⡀⠀⠀⠀⠀⠀⠀⢀⣠⣴⡾⠃⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⢿⣿⣾⣿⡿⠿⠟⠋⠁⠀⠀⠀")) - except: - pass - self.pause_button.hide() - self.progress_bar.hide() - return - else: - if not (self.is_valid_url()): - try: - GLib.idle_add(self.show_message(_("This is not a valid URL."))) - print("Error: Not a valid url.") - except: - print("Error: Couldn't display 'not a valid url' error, for some reason.") - return - response = requests.head(self.url) - if ((response.status_code == 401) and (self.auth == '1')): - if (self.url[0:7] == "http://"): - self.url = self.url[:7] + self.auth_username + ":" + self.auth_password + "@" + self.url[7:] - elif (self.url[0:8] == "https://"): - self.url = self.url[:8] + self.auth_username + ":" + self.auth_password + "@" + self.url[8:] - else: - self.url = self.auth_username + ":" + self.auth_password + "@" + self.url - print ("Authentication enabled.") - print(self.url) try: if (self.download == None): + if (self.url == "sus"): + try: + GLib.idle_add(self.show_message("⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠟⠉⠉⠉⠉⠉⠉⠉⠙⠻⢶⣄⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣷⡀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⠀⣠⣶⠛⠛⠛⠛⠛⠛⠳⣦⡀⠀⠘⣿⡄⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠁⠀⢹⣿⣦⣀⣀⣀⣀⣀⣠⣼⡇⠀⠀⠸⣷⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡏⠀⠀⠀⠉⠛⠿⠿⠿⠿⠛⠋⠁⠀⠀⠀⠀⣿⡄⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⣸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣧⠀\n⠀⠀⠀⠀⠀⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⠀\n⠀⠀⠀⠀⠀⠀⠀⣾⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⠀⠀⠀⠀⢰⣿⠀⠀⠀⠀⣠⡶⠶⠿⠿⠿⠿⢷⣦⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠀⠀⣀⣀⣀⠀⣸⡇⠀⠀⠀⠀⣿⡀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⣿⠀\n⣠⡿⠛⠛⠛⠛⠻⠀⠀⠀⠀⠀⢸⣇⠀⠀⠀⠀⠀⠀⣿⠇⠀⠀⠀⠀⠀⠀⣿⠀\n⢻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⡟⠀⠀⢀⣤⣤⣴⣿⠀⠀⠀⠀⠀⠀⠀⣿⠀\n⠈⠙⢷⣶⣦⣤⣤⣤⣴⣶⣾⠿⠛⠁⢀⣶⡟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡟⠀\n⠀⠀⠀⠀⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠈⣿⣆⡀⠀⠀⠀⠀⠀⠀⢀⣠⣴⡾⠃⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⢿⣿⣾⣿⡿⠿⠟⠋⠁⠀⠀⠀")) + except: + pass + self.pause_button.hide() + self.progress_bar.hide() + return + + if (self.url.startswith("magnet:") == False): + if not (self.is_valid_url()): + try: + GLib.idle_add(self.show_message(_("This is not a valid URL."))) + print("Error: Not a valid url.") + except: + print("Error: Couldn't display 'not a valid url' error, for some reason.") + return + response = requests.head(self.url) + if ((response.status_code == 401) and (self.auth == '1')): + if (self.url[0:7] == "http://"): + self.url = self.url[:7] + self.auth_username + ":" + self.auth_password + "@" + self.url[7:] + elif (self.url[0:8] == "https://"): + self.url = self.url[:8] + self.auth_username + ":" + self.auth_password + "@" + self.url[8:] + else: + self.url = self.auth_username + ":" + self.auth_password + "@" + self.url + print ("Authentication enabled.") + + try: self.download = self.api.add_uris([self.url]) + except: + pass + except: pass + downloadname = self.download.name - print("Download added.\n" + self.downloaddir + "\n" + self.url) + print("Download added. | " + self.download.gid + "\n" + self.downloaddir + "\n" + self.url) GLib.idle_add(self.update_header_pause_button) + GLib.idle_add(self.set_filename_label) while (self.cancelled == False): try: + self.pause_button.show() self.download.update() GLib.idle_add(self.update_labels_and_things) - if (self.download.is_complete): + if ((self.download.is_complete) and (self.download.is_metadata == False)): + print('Download complete: ' + self.download.gid) if os.path.exists(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))): os.remove(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))) break @@ -86,6 +96,9 @@ def run(self): return time.sleep(1) + def set_filename_label(self): + self.filename_label.set_text(self.download.name[:40]) + def update_header_pause_button(self): self.app.all_paused = False self.app.header_pause_content.set_icon_name("media-playback-pause-symbolic") @@ -181,9 +194,9 @@ def return_is_paused(self): return True @classmethod - def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow, download): + def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow, filename_label, download): with open(os.path.join(app.appconf["download_directory"], filename), 'r') as f: state = json.load(f) os.remove(os.path.join(app.appconf["download_directory"], filename)) - instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow, None) + instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow, filename_label, None) return instance diff --git a/src/variamain.py b/src/variamain.py index e1c2ae0..fcee196 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -70,7 +70,7 @@ def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, *args, **kwargs with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: state = json.load(f) objectlist = create_actionrow(self, state['url']) - download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], None) + download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None) self.downloads.append(download_thread) download_thread.start() @@ -315,9 +315,9 @@ def main(version, aria2cexec): if (appconf['remote'] == '0'): if (os.name == 'nt'): - aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"], shell=True) + aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801", "--follow-torrent=mem"], shell=True) else: - aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801"]) + aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801", "--follow-torrent=mem"]) arguments = sys.argv if (len(arguments) > 1): From 7f5507123f7b1c7868462e455ae08e62dda1abfa Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Tue, 20 Feb 2024 23:37:22 +0300 Subject: [PATCH 5/9] Bug fixes for torrenting and some translations --- po/tr.po | 15 +++++++++++++++ src/download/listen.py | 43 +++++++++++++++++------------------------- src/download/thread.py | 1 - src/variamain.py | 1 + 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/po/tr.po b/po/tr.po index e1157c8..c97f415 100644 --- a/po/tr.po +++ b/po/tr.po @@ -195,6 +195,21 @@ msgstr "Yetkilendirme başarısız oldu." msgid "Failed to open directory." msgstr "Dizin açılamadı." +msgid "Remote Mode" +msgstr "Uzaktan Kontrol Modu" + +msgid "Remote aria2 IP" +msgstr "Uzak aria2 IP'si" + +msgid "Remote aria2 Port" +msgstr "Uzak aria2 Port'u" + +msgid "Remote aria2 RPC Secret" +msgstr "Uzak aria2 RPC Şifresi" + +msgid "Remote Download Location" +msgstr "Uzak İndirme Dizini" + msgid "Exiting Varia..." msgstr "Varia'dan çıkılıyor..." diff --git a/src/download/listen.py b/src/download/listen.py index 341fcb8..6e29f30 100644 --- a/src/download/listen.py +++ b/src/download/listen.py @@ -8,40 +8,31 @@ import threading def listen_to_aria2(self): + for frontend_download_item in self.downloads.copy(): + if ( (frontend_download_item.download) and ( ((frontend_download_item.download.is_metadata) or (frontend_download_item.download.name.endswith(".torrent"))) and (frontend_download_item.download.is_complete) ) ): + frontend_download_item.cancelled = True + frontend_download_item.stop(True) + self.download_list.remove(frontend_download_item.actionrow) + self.downloads.remove(frontend_download_item) + aria2_total_downloads = self.api.get_downloads() - if (len(aria2_total_downloads) > len(self.downloads)): - downloads_in_frontend = [] - for frontend_download_item in self.downloads: - if ( ((frontend_download_item.download.is_metadata) or (frontend_download_item.download.name.endswith(".torrent"))) and (frontend_download_item.download.is_complete)): - frontend_download_item.cancelled = True - frontend_download_item.stop(True) - self.download_list.remove(frontend_download_item.actionrow) - if (frontend_download_item in self.downloads): - self.downloads.remove(frontend_download_item) - else: - downloads_in_frontend.append(frontend_download_item.download.gid) + downloads_in_frontend = set(download.download.gid for download in self.downloads) - for download_item_to_be_added in aria2_total_downloads: - if ((download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_complete == False) and (download_item_to_be_added.is_metadata == False)): - if (download_item_to_be_added.is_torrent == False): - print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) - GLib.idle_add(lambda: add_download_to_ui(self, download_item_to_be_added)) + for download_item_to_be_added in aria2_total_downloads: + if (download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_metadata == False) and (download_item_to_be_added.is_complete == False): + if not download_item_to_be_added.is_torrent: + print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) + add_download_to_ui(self, download_item_to_be_added) - if (self.terminating == False): - GLib.timeout_add(1000, lambda: listen_to_aria2(self)) + if not self.terminating: + GLib.timeout_add(2000, listen_to_aria2, self) def add_download_to_ui(self, download_item_to_be_added): - if ((download_item_to_be_added.is_metadata) or (download_item_to_be_added.is_complete)): - return - - if (download_item_to_be_added.is_torrent): - download_item_url = "magnet:?xt=urn:btih:" + download_item_to_be_added.info_hash - else: - download_item_url = download_item_to_be_added.files[0].uris[0]["uri"].split("?")[0] + download_item_url = "magnet:?xt=urn:btih:" + download_item_to_be_added.info_hash if download_item_to_be_added.is_torrent else download_item_to_be_added.files[0].uris[0]["uri"].split("?")[0] objectlist = create_actionrow(self, download_item_url) - download_thread = DownloadThread(self, download_item_url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], download_item_to_be_added) + download_thread = DownloadThread(self, download_item_url, *objectlist, download_item_to_be_added) self.downloads.append(download_thread) download_thread.start() download_thread.pause_button.show() diff --git a/src/download/thread.py b/src/download/thread.py index b132424..a4bc02d 100644 --- a/src/download/thread.py +++ b/src/download/thread.py @@ -82,7 +82,6 @@ def run(self): GLib.idle_add(self.set_filename_label) while (self.cancelled == False): try: - self.pause_button.show() self.download.update() GLib.idle_add(self.update_labels_and_things) if ((self.download.is_complete) and (self.download.is_metadata == False)): diff --git a/src/variamain.py b/src/variamain.py index fcee196..dacde31 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -313,6 +313,7 @@ def main(version, aria2cexec): with open(os.path.join(appdir, 'varia.conf'), 'w') as f: json.dump(appconf, f) + aria2c_subprocess = None if (appconf['remote'] == '0'): if (os.name == 'nt'): aria2c_subprocess = subprocess.Popen([aria2cexec, "--enable-rpc", "--rpc-listen-port=6801", "--follow-torrent=mem"], shell=True) From 4552f67f89f2b31cfd45cddf3a02433e05e2bbe1 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Thu, 22 Feb 2024 18:01:02 +0300 Subject: [PATCH 6/9] More work on torrenting and background download mode --- src/download/actionrow.py | 6 +- src/download/listen.py | 18 +++--- src/download/thread.py | 15 +++-- src/variamain.py | 127 ++++++++++++++++++++------------------ src/window/sidebar.py | 10 +++ 5 files changed, 99 insertions(+), 77 deletions(-) diff --git a/src/download/actionrow.py b/src/download/actionrow.py index e43a61f..e6903a7 100644 --- a/src/download/actionrow.py +++ b/src/download/actionrow.py @@ -8,7 +8,7 @@ def on_download_clicked(button, self, entry, DownloadThread): entry.set_text("") if url: objectlist = create_actionrow(self, url) - download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None) + download_thread = DownloadThread(self, url, objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None, None) self.downloads.append(download_thread) download_thread.start() self.filter_download_list("no", self.applied_filter) @@ -71,7 +71,7 @@ def create_actionrow(self, filename): def on_pause_clicked(button, self, pause_button, download_item): self.all_paused = False - download_thread = self.downloads[download_item.index+1] + download_thread = self.downloads[download_item.index] if download_thread.download.is_paused: download_thread.resume() image = Gtk.Image.new() @@ -86,7 +86,7 @@ def on_pause_clicked(button, self, pause_button, download_item): image = Gtk.Image.new() image.set_from_icon_name("media-playback-start-symbolic") pause_button.set_child(image) - download_thread.save_state() + download_thread.save_state() all_paused = True for download_thread in self.downloads: diff --git a/src/download/listen.py b/src/download/listen.py index 6e29f30..100ed04 100644 --- a/src/download/listen.py +++ b/src/download/listen.py @@ -17,13 +17,15 @@ def listen_to_aria2(self): aria2_total_downloads = self.api.get_downloads() - downloads_in_frontend = set(download.download.gid for download in self.downloads) - - for download_item_to_be_added in aria2_total_downloads: - if (download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_metadata == False) and (download_item_to_be_added.is_complete == False): - if not download_item_to_be_added.is_torrent: - print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) - add_download_to_ui(self, download_item_to_be_added) + try: + downloads_in_frontend = set(download.download.gid for download in self.downloads) + for download_item_to_be_added in aria2_total_downloads: + if (download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_metadata == False) and (download_item_to_be_added.is_complete == False): + if not download_item_to_be_added.is_torrent: + print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) + add_download_to_ui(self, download_item_to_be_added) + except: + pass if not self.terminating: GLib.timeout_add(2000, listen_to_aria2, self) @@ -32,7 +34,7 @@ def add_download_to_ui(self, download_item_to_be_added): download_item_url = "magnet:?xt=urn:btih:" + download_item_to_be_added.info_hash if download_item_to_be_added.is_torrent else download_item_to_be_added.files[0].uris[0]["uri"].split("?")[0] objectlist = create_actionrow(self, download_item_url) - download_thread = DownloadThread(self, download_item_url, *objectlist, download_item_to_be_added) + download_thread = DownloadThread(self, download_item_url, *objectlist, download_item_to_be_added, None) self.downloads.append(download_thread) download_thread.start() download_thread.pause_button.show() diff --git a/src/download/thread.py b/src/download/thread.py index a4bc02d..d77d8c7 100644 --- a/src/download/thread.py +++ b/src/download/thread.py @@ -11,7 +11,7 @@ import json class DownloadThread(threading.Thread): - def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, filename_label, download): + def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, filename_label, download, downloadname): threading.Thread.__init__(self) self.api = app.api self.downloaddir = app.appconf["download_directory"] @@ -28,6 +28,7 @@ def __init__(self, app, url, progress_bar, speed_label, pause_button, actionrow, self.filename_label = filename_label self.app = app self.cancelled = False + self.downloadname = downloadname def is_valid_url(self): try: @@ -68,8 +69,12 @@ def run(self): self.url = self.auth_username + ":" + self.auth_password + "@" + self.url print ("Authentication enabled.") + print(self.downloadname) try: - self.download = self.api.add_uris([self.url]) + if (self.downloadname == None): + self.download = self.api.add_uris([self.url]) + else: + self.download = self.api.add_uris([self.url], options={"out": self.downloadname}) except: pass @@ -176,7 +181,7 @@ def save_state(self): return state = { 'url': self.url, - 'downloaded': self.download.completed_length, + 'filename': self.download.name } with open(os.path.join(self.downloaddir, f'{self.download.gid}.varia.json'), 'w') as f: json.dump(state, f) @@ -193,9 +198,9 @@ def return_is_paused(self): return True @classmethod - def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow, filename_label, download): + def load_state(cls, app, filename, url, progress_bar, speed_label, pause_button, actionrow, filename_label, download, downloadname): with open(os.path.join(app.appconf["download_directory"], filename), 'r') as f: state = json.load(f) os.remove(os.path.join(app.appconf["download_directory"], filename)) - instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow, filename_label, None) + instance = cls(app, state['url'], progress_bar, speed_label, pause_button, actionrow, filename_label, None, downloadname) return instance diff --git a/src/variamain.py b/src/variamain.py index dacde31..b1221c5 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -1,4 +1,5 @@ variaVersion = "v2024.2.6" +is_running = False import gi import sys @@ -13,6 +14,9 @@ gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, GLib, Gio import requests +import signal +import dbus +import dbus.mainloop.glib from window.sidebar import window_create_sidebar from window.content import window_create_content @@ -23,56 +27,66 @@ from download.listen import listen_to_aria2 class MainWindow(Gtk.Window): - def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, *args, **kwargs): + def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, is_running, *args, **kwargs): super().__init__(*args, **kwargs) - self.appdir = appdir - self.appconf = appconf - self.aria2c_subprocess = aria2c_subprocess - self.set_hide_on_close(True) - self.connect("close-request", self.exitProgram, variaapp) + if (is_running == False): + self.set_hide_on_close(True) + self.connect('close-request', self.exitProgram, variaapp, False) - # Set up variables and all: - aria2_connection_successful = initiate(self) + self.appdir = appdir + self.appconf = appconf + self.aria2c_subprocess = aria2c_subprocess - if (aria2_connection_successful == -1): - return + # Set up variables and all: + aria2_connection_successful = initiate(self) + + if (aria2_connection_successful == -1): + return + + # Create window contents: + window_create_sidebar(self, variaapp, DownloadThread, variaVersion) + window_create_content(self, threading) + + # Check if the download path still exists: + if not (os.path.exists(self.appconf["download_directory"])): + self.appconf["download_directory"] = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD) + self.save_appconf() - # Create window contents: - window_create_sidebar(self, variaapp, DownloadThread, variaVersion) - window_create_content(self, threading) + # Set download speed limit from appconf: + if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): + set_speed_limit(self, self.appconf["download_speed_limit"]) - # Check if the download path still exists: - if not (os.path.exists(self.appconf["download_directory"])): - self.appconf["download_directory"] = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD) - self.save_appconf() + # Set download speed limit from appconf: + if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): + set_speed_limit(self, self.appconf["download_speed_limit"]) - # Set download speed limit from appconf: - if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): - set_speed_limit(self, self.appconf["download_speed_limit"]) + # Set download directory from appconf: + set_aria2c_download_directory(self) - # Set download speed limit from appconf: - if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): - set_speed_limit(self, self.appconf["download_speed_limit"]) + # Set the maximum simultaneous download amount from appconf: + set_aria2c_download_simultaneous_amount(self) - # Set download directory from appconf: - set_aria2c_download_directory(self) + # Listen to aria2c: + thread = threading.Thread(target=listen_to_aria2(self)) + thread.start() - # Set the maximum simultaneous download amount from appconf: - set_aria2c_download_simultaneous_amount(self) + # Load incomplete downloads: + default_state = {"url": None, "filename": None} - thread = threading.Thread(target=listen_to_aria2(self)) - thread.start() + for filename in os.listdir(self.appconf["download_directory"]): + if filename.endswith('.varia.json'): - # Load incomplete downloads: - for filename in os.listdir(self.appconf["download_directory"]): - if filename.endswith('.varia.json'): - with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: - state = json.load(f) - objectlist = create_actionrow(self, state['url']) - download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None) - self.downloads.append(download_thread) - download_thread.start() + with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: + loaded_state = json.load(f) + + state = {**default_state, **loaded_state} + objectlist = create_actionrow(self, state['url']) + download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None, state['filename']) + self.downloads.append(download_thread) + download_thread.start() + + is_running = True def filter_download_list(self, button, filter_mode): if (button != "no"): @@ -173,12 +187,13 @@ def pause_all(self, header_pause_content): if (self.all_paused == False): for download_thread in self.downloads: download_thread.pause() - download_thread.save_state() pause_button_images.append(Gtk.Image.new()) pause_button_images[i].set_from_icon_name("media-playback-start-symbolic") self.pause_buttons[i].set_child(pause_button_images[i]) + download_thread.save_state() + i += 1 if ((header_pause_content != "no") and (i > 0)): header_pause_content.set_icon_name("media-playback-start-symbolic") @@ -217,24 +232,15 @@ def save_appconf(self): json.dump(self.appconf, f) print("Config saved") - def exitProgram(self, app, variaapp): - self.terminating = True + def exitProgram(self, app, variaapp, background): + if (background == True): + self.hide() + notification = Gio.Notification.new(_("Varia")) + notification.set_body(_("Continuing the downloads in the background.\nDo not quit Varia directly through the Background Apps section.")) + variaapp.send_notification(None, notification) + return - if (len(self.downloads) > 0): - active_downloads = False - for download_thread in self.downloads: - try: - download_thread.download.update() - if ((download_thread.download.status == "active") or (download_thread.download.status == "waiting")): - active_downloads = True - except: - pass - if (active_downloads == True): - self.hide() - notification = Gio.Notification.new(_("Varia")) - notification.set_body(_("Continuing the downloads in the background.")) - variaapp.send_notification(None, notification) - return + self.terminating = True self.set_sensitive(False) self.all_paused = False @@ -265,7 +271,7 @@ def exitProgram(self, app, variaapp): def aria2c_exiting_check(self, app, counter): print(counter) - if ((counter < 15) and (self.aria2c_subprocess.poll() is None)): + if ((counter < 20) and (self.aria2c_subprocess.poll() is None)): counter += 1 GLib.timeout_add(250, self.aria2c_exiting_check, app, counter) else: @@ -275,12 +281,12 @@ def aria2c_exiting_check(self, app, counter): return class MyApp(Adw.Application): - def __init__(self, appdir, appconf, aria2c_subprocess, **kwargs): + def __init__(self, appdir, appconf, aria2c_subprocess, is_running, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate, appdir, appconf, aria2c_subprocess) def on_activate(self, app, appdir, appconf, aria2c_subprocess): - self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess) + self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess, is_running=is_running) self.win.present() def main(version, aria2cexec): @@ -324,7 +330,7 @@ def main(version, aria2cexec): if (len(arguments) > 1): arguments = arguments[:-1] - app = MyApp(appdir, appconf, aria2c_subprocess, application_id="io.github.giantpinkrobots.varia") + app = MyApp(appdir, appconf, aria2c_subprocess, is_running, application_id="io.github.giantpinkrobots.varia") try: app.run(arguments) except: @@ -332,4 +338,3 @@ def main(version, aria2cexec): if ((__name__ == '__main__') and (os.name == 'nt')): sys.exit(main(variaVersion, "aria2c")) - diff --git a/src/window/sidebar.py b/src/window/sidebar.py index fa4ddc7..0b17aea 100644 --- a/src/window/sidebar.py +++ b/src/window/sidebar.py @@ -24,6 +24,10 @@ def window_create_sidebar(self, variaapp, DownloadThread, variaVersion): hamburger_button.set_icon_name("open-menu-symbolic") hamburger_menu_model = Gio.Menu() + background_action = Gio.SimpleAction.new("background_mode", None) + background_action.connect("activate", background_mode, self, variaapp) + variaapp.add_action(background_action) + cancel_all_action = Gio.SimpleAction.new("cancel_all_downloads", None) cancel_all_action.connect("activate", self.stop_all) variaapp.add_action(cancel_all_action) @@ -36,6 +40,9 @@ def window_create_sidebar(self, variaapp, DownloadThread, variaVersion): downloads_folder_action.connect("activate", show_about, self, variaVersion) variaapp.add_action(downloads_folder_action) + hamburger_menu_item_background = Gio.MenuItem.new(_("Background Mode"), "app.background_mode") + #hamburger_menu_model.append_item(hamburger_menu_item_background) + hamburger_menu_item_cancel_all = Gio.MenuItem.new(_("Cancel All"), "app.cancel_all_downloads") hamburger_menu_model.append_item(hamburger_menu_item_cancel_all) @@ -128,6 +135,9 @@ def window_create_sidebar(self, variaapp, DownloadThread, variaVersion): self.overlay_split_view.set_sidebar(sidebar_box) +def background_mode(app, variaapp1, self, variaapp): + self.exitProgram(app=app, variaapp=variaapp, background=True) + def show_about(app, variaapp, self, variaVersion): dialog = Adw.AboutWindow(transient_for=self) dialog.set_application_name("Varia") From f97c12d982b498cfd268bb1796e5663f1d6d09df Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Sun, 25 Feb 2024 03:40:25 +0300 Subject: [PATCH 7/9] Background mode configured Enabled the background mode option. This allows the user to click on a hamburger menu item to close the Varia window but keep downloading the files in the background. --- src/download/actionrow.py | 5 +++- src/download/thread.py | 8 +++++ src/variamain.py | 63 +++++++++++++++++++++++++-------------- src/window/sidebar.py | 4 +-- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/download/actionrow.py b/src/download/actionrow.py index e6903a7..d110ae5 100644 --- a/src/download/actionrow.py +++ b/src/download/actionrow.py @@ -102,8 +102,11 @@ def on_pause_clicked(button, self, pause_button, download_item): def on_stop_clicked(button, self, download_item): index = download_item.index download_thread = self.downloads[index+1] + deletefiles = True + if ((download_thread.download.is_torrent) and (download_thread.download.seeder)): + deletefiles = False try: - download_thread.stop(True) + download_thread.stop(deletefiles) except: pass self.download_list.remove(download_item) diff --git a/src/download/thread.py b/src/download/thread.py index d77d8c7..cee587e 100644 --- a/src/download/thread.py +++ b/src/download/thread.py @@ -94,6 +94,9 @@ def run(self): if os.path.exists(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))): os.remove(os.path.join(self.downloaddir,(self.download.gid + ".varia.json"))) break + elif ((self.download.is_torrent) and (self.download.seeder)): + print('Torrent complete, seeding: ' + self.download.gid) + break elif (self.download.status == "error"): return except: @@ -114,6 +117,11 @@ def show_message(self, message): def update_labels_and_things(self): self.progress_bar.set_fraction(self.download.progress / 100) + + if ((self.download.is_torrent) and (self.download.seeder)): + GLib.idle_add(self.show_message(_("Seeding torrent"))) + return + download_speed_mb = (self.download.download_speed / 1024 / 1024) if int(str(download_speed_mb)[0]) == 0: download_speed_kb = (self.download.download_speed / 1024) diff --git a/src/variamain.py b/src/variamain.py index b1221c5..ed97fea 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -32,7 +32,7 @@ def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, is_running, *ar if (is_running == False): self.set_hide_on_close(True) - self.connect('close-request', self.exitProgram, variaapp, False) + self.connect('close-request', self.exitProgram, variaapp, False, True) self.appdir = appdir self.appconf = appconf @@ -221,7 +221,10 @@ def stop_all(self, app, variaapp): self.download_list.remove(child) child = next_child for download_thread in self.downloads: - download_thread.stop(True) + deletefiles = True + if ((download_thread.download.is_torrent) and (download_thread.download.seeder)): + deletefiles = False + download_thread.stop(deletefiles) self.downloads.remove(download_thread) self.header_pause_content.set_icon_name("media-playback-pause-symbolic") self.header_pause_content.set_label(_("Pause All")) @@ -232,11 +235,11 @@ def save_appconf(self): json.dump(self.appconf, f) print("Config saved") - def exitProgram(self, app, variaapp, background): + def exitProgram(self, app, variaapp, background, show_exit_window): if (background == True): self.hide() - notification = Gio.Notification.new(_("Varia")) - notification.set_body(_("Continuing the downloads in the background.\nDo not quit Varia directly through the Background Apps section.")) + notification = Gio.Notification.new(_("Background Mode")) + notification.set_body(_("Continuing the downloads in the background.")) variaapp.send_notification(None, notification) return @@ -249,46 +252,58 @@ def exitProgram(self, app, variaapp, background): self.pause_all("no") self.api.client.shutdown() - exiting_dialog = Adw.MessageDialog() - exiting_dialog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=25) - exiting_dialog.set_child(exiting_dialog_box) - exiting_dialog_box.set_margin_top(30) - exiting_dialog_box.set_margin_bottom(30) - exiting_dialog_spinner = Gtk.Spinner() - exiting_dialog_spinner.set_size_request(30, 30) - exiting_dialog_spinner.start() - exiting_dialog_box.append(exiting_dialog_spinner) - exiting_dialog_label = Gtk.Label(label=_("Exiting Varia...")) - exiting_dialog_label.get_style_context().add_class("title-1") - exiting_dialog_box.append(exiting_dialog_label) - exiting_dialog.set_transient_for(self) - GLib.idle_add(exiting_dialog.show) - - GLib.timeout_add(3000, self.aria2c_exiting_check, app, 0) + if (show_exit_window == True): + exiting_dialog = Adw.MessageDialog() + exiting_dialog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=25) + exiting_dialog.set_child(exiting_dialog_box) + exiting_dialog_box.set_margin_top(30) + exiting_dialog_box.set_margin_bottom(30) + exiting_dialog_spinner = Gtk.Spinner() + exiting_dialog_spinner.set_size_request(30, 30) + exiting_dialog_spinner.start() + exiting_dialog_box.append(exiting_dialog_spinner) + exiting_dialog_label = Gtk.Label(label=_("Exiting Varia...")) + exiting_dialog_label.get_style_context().add_class("title-1") + exiting_dialog_box.append(exiting_dialog_label) + exiting_dialog.set_transient_for(self) + GLib.idle_add(exiting_dialog.show) + + GLib.timeout_add(3000, self.aria2c_exiting_check, app, 0, variaapp) else: self.destroy() + variaapp.quit() - def aria2c_exiting_check(self, app, counter): + def aria2c_exiting_check(self, app, counter, variaapp): print(counter) if ((counter < 20) and (self.aria2c_subprocess.poll() is None)): counter += 1 - GLib.timeout_add(250, self.aria2c_exiting_check, app, counter) + GLib.timeout_add(250, self.aria2c_exiting_check, app, counter, variaapp) else: self.aria2c_subprocess.terminate() self.aria2c_subprocess.wait() self.destroy() + variaapp.quit() return + def quit_action_received(self, variaapp): + self.exitProgram(variaapp, variaapp, False, False) + class MyApp(Adw.Application): def __init__(self, appdir, appconf, aria2c_subprocess, is_running, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate, appdir, appconf, aria2c_subprocess) + quit_action = Gio.SimpleAction.new("quit", None) + quit_action.connect("activate", self.quit_action) + self.add_action(quit_action) def on_activate(self, app, appdir, appconf, aria2c_subprocess): self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess, is_running=is_running) self.win.present() + def quit_action(self, action, parameter): + self.win.quit_action_received(self) + def main(version, aria2cexec): if "FLATPAK_ID" in os.environ: appdir = os.path.join('/var', 'data') @@ -338,3 +353,5 @@ def main(version, aria2cexec): if ((__name__ == '__main__') and (os.name == 'nt')): sys.exit(main(variaVersion, "aria2c")) + +kill_now = False diff --git a/src/window/sidebar.py b/src/window/sidebar.py index 0b17aea..2c9eaf4 100644 --- a/src/window/sidebar.py +++ b/src/window/sidebar.py @@ -41,7 +41,7 @@ def window_create_sidebar(self, variaapp, DownloadThread, variaVersion): variaapp.add_action(downloads_folder_action) hamburger_menu_item_background = Gio.MenuItem.new(_("Background Mode"), "app.background_mode") - #hamburger_menu_model.append_item(hamburger_menu_item_background) + hamburger_menu_model.append_item(hamburger_menu_item_background) hamburger_menu_item_cancel_all = Gio.MenuItem.new(_("Cancel All"), "app.cancel_all_downloads") hamburger_menu_model.append_item(hamburger_menu_item_cancel_all) @@ -136,7 +136,7 @@ def window_create_sidebar(self, variaapp, DownloadThread, variaVersion): self.overlay_split_view.set_sidebar(sidebar_box) def background_mode(app, variaapp1, self, variaapp): - self.exitProgram(app=app, variaapp=variaapp, background=True) + self.exitProgram(app=app, variaapp=variaapp, background=True, show_exit_window=True) def show_about(app, variaapp, self, variaVersion): dialog = Adw.AboutWindow(transient_for=self) From 8fcc9fd1ece1b7325d9ccba3ce6aac9d4fd95af5 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Sun, 25 Feb 2024 20:16:31 +0300 Subject: [PATCH 8/9] Exit after background mode fix This commit fixes the app freezing if the user exits after hiding and showing the window. --- src/download/listen.py | 35 ++++----- src/initiate.py | 1 - src/variamain.py | 173 ++++++++++++++++++++--------------------- 3 files changed, 102 insertions(+), 107 deletions(-) diff --git a/src/download/listen.py b/src/download/listen.py index 100ed04..b610bb0 100644 --- a/src/download/listen.py +++ b/src/download/listen.py @@ -8,26 +8,25 @@ import threading def listen_to_aria2(self): - for frontend_download_item in self.downloads.copy(): - if ( (frontend_download_item.download) and ( ((frontend_download_item.download.is_metadata) or (frontend_download_item.download.name.endswith(".torrent"))) and (frontend_download_item.download.is_complete) ) ): - frontend_download_item.cancelled = True - frontend_download_item.stop(True) - self.download_list.remove(frontend_download_item.actionrow) - self.downloads.remove(frontend_download_item) - - aria2_total_downloads = self.api.get_downloads() + if not self.terminating: + for frontend_download_item in self.downloads.copy(): + if ( (frontend_download_item.download) and ( ((frontend_download_item.download.is_metadata) or (frontend_download_item.download.name.endswith(".torrent"))) and (frontend_download_item.download.is_complete) ) ): + frontend_download_item.cancelled = True + frontend_download_item.stop(True) + self.download_list.remove(frontend_download_item.actionrow) + self.downloads.remove(frontend_download_item) - try: - downloads_in_frontend = set(download.download.gid for download in self.downloads) - for download_item_to_be_added in aria2_total_downloads: - if (download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_metadata == False) and (download_item_to_be_added.is_complete == False): - if not download_item_to_be_added.is_torrent: - print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) - add_download_to_ui(self, download_item_to_be_added) - except: - pass + try: + aria2_total_downloads = self.api.get_downloads() + downloads_in_frontend = set(download.download.gid for download in self.downloads) + for download_item_to_be_added in aria2_total_downloads: + if (download_item_to_be_added.gid not in downloads_in_frontend) and (download_item_to_be_added.is_metadata == False) and (download_item_to_be_added.is_complete == False): + if not download_item_to_be_added.is_torrent: + print('Download added directly to aria2c, adding it to the UI: ' + download_item_to_be_added.files[0].uris[0]["uri"]) + add_download_to_ui(self, download_item_to_be_added) + except: + pass - if not self.terminating: GLib.timeout_add(2000, listen_to_aria2, self) def add_download_to_ui(self, download_item_to_be_added): diff --git a/src/initiate.py b/src/initiate.py index ad99be4..cda19f1 100644 --- a/src/initiate.py +++ b/src/initiate.py @@ -80,4 +80,3 @@ def initiate(self): def on_dialog_dismiss(dialog, response_id): dialog.destroy() - sys.exit() diff --git a/src/variamain.py b/src/variamain.py index ed97fea..06ee9c9 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -1,5 +1,4 @@ variaVersion = "v2024.2.6" -is_running = False import gi import sys @@ -14,9 +13,6 @@ gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, GLib, Gio import requests -import signal -import dbus -import dbus.mainloop.glib from window.sidebar import window_create_sidebar from window.content import window_create_content @@ -27,66 +23,62 @@ from download.listen import listen_to_aria2 class MainWindow(Gtk.Window): - def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, is_running, *args, **kwargs): + def __init__(self, variaapp, appdir, appconf, aria2c_subprocess, *args, **kwargs): super().__init__(*args, **kwargs) + self.set_hide_on_close(True) + self.connect('close-request', self.exitProgram, variaapp, False, True) - if (is_running == False): - self.set_hide_on_close(True) - self.connect('close-request', self.exitProgram, variaapp, False, True) + self.appdir = appdir + self.appconf = appconf + self.aria2c_subprocess = aria2c_subprocess - self.appdir = appdir - self.appconf = appconf - self.aria2c_subprocess = aria2c_subprocess + # Set up variables and all: + aria2_connection_successful = initiate(self) - # Set up variables and all: - aria2_connection_successful = initiate(self) - - if (aria2_connection_successful == -1): - return - - # Create window contents: - window_create_sidebar(self, variaapp, DownloadThread, variaVersion) - window_create_content(self, threading) + if (aria2_connection_successful == -1): + return - # Check if the download path still exists: - if not (os.path.exists(self.appconf["download_directory"])): - self.appconf["download_directory"] = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD) - self.save_appconf() + # Create window contents: + window_create_sidebar(self, variaapp, DownloadThread, variaVersion) + window_create_content(self, threading) - # Set download speed limit from appconf: - if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): - set_speed_limit(self, self.appconf["download_speed_limit"]) + # Check if the download path still exists: + if not (os.path.exists(self.appconf["download_directory"])): + self.appconf["download_directory"] = GLib.get_user_special_dir(GLib.USER_DIRECTORY_DOWNLOAD) + self.save_appconf() - # Set download speed limit from appconf: - if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): - set_speed_limit(self, self.appconf["download_speed_limit"]) + # Set download speed limit from appconf: + if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): + set_speed_limit(self, self.appconf["download_speed_limit"]) - # Set download directory from appconf: - set_aria2c_download_directory(self) + # Set download speed limit from appconf: + if ((self.appconf["download_speed_limit_enabled"] == "1") and (self.appconf["download_speed_limit"][:-1] != "0")): + set_speed_limit(self, self.appconf["download_speed_limit"]) - # Set the maximum simultaneous download amount from appconf: - set_aria2c_download_simultaneous_amount(self) + # Set download directory from appconf: + set_aria2c_download_directory(self) - # Listen to aria2c: - thread = threading.Thread(target=listen_to_aria2(self)) - thread.start() + # Set the maximum simultaneous download amount from appconf: + set_aria2c_download_simultaneous_amount(self) - # Load incomplete downloads: - default_state = {"url": None, "filename": None} + # Listen to aria2c: + thread = threading.Thread(target=listen_to_aria2(self)) + thread.start() - for filename in os.listdir(self.appconf["download_directory"]): - if filename.endswith('.varia.json'): + # Load incomplete downloads: + default_state = {"url": None, "filename": None} - with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: - loaded_state = json.load(f) + for filename in os.listdir(self.appconf["download_directory"]): + if filename.endswith('.varia.json'): - state = {**default_state, **loaded_state} - objectlist = create_actionrow(self, state['url']) - download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None, state['filename']) - self.downloads.append(download_thread) - download_thread.start() + with open(os.path.join(self.appconf["download_directory"], filename), 'r') as f: + loaded_state = json.load(f) - is_running = True + state = {**default_state, **loaded_state} + objectlist = create_actionrow(self, state['url']) + download_thread = DownloadThread.load_state(self, filename, state['url'], objectlist[0], objectlist[1], objectlist[2], objectlist[3], objectlist[4], None, state['filename']) + self.downloads.append(download_thread) + download_thread.start() def filter_download_list(self, button, filter_mode): if (button != "no"): @@ -241,56 +233,61 @@ def exitProgram(self, app, variaapp, background, show_exit_window): notification = Gio.Notification.new(_("Background Mode")) notification.set_body(_("Continuing the downloads in the background.")) variaapp.send_notification(None, notification) - return + else: + self.terminating = True - self.terminating = True - - self.set_sensitive(False) - self.all_paused = False - - if (self.appconf['remote'] == '0'): - self.pause_all("no") - self.api.client.shutdown() - - if (show_exit_window == True): - exiting_dialog = Adw.MessageDialog() - exiting_dialog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=25) - exiting_dialog.set_child(exiting_dialog_box) - exiting_dialog_box.set_margin_top(30) - exiting_dialog_box.set_margin_bottom(30) - exiting_dialog_spinner = Gtk.Spinner() - exiting_dialog_spinner.set_size_request(30, 30) - exiting_dialog_spinner.start() - exiting_dialog_box.append(exiting_dialog_spinner) - exiting_dialog_label = Gtk.Label(label=_("Exiting Varia...")) - exiting_dialog_label.get_style_context().add_class("title-1") - exiting_dialog_box.append(exiting_dialog_label) - exiting_dialog.set_transient_for(self) - GLib.idle_add(exiting_dialog.show) - - GLib.timeout_add(3000, self.aria2c_exiting_check, app, 0, variaapp) + self.set_sensitive(False) + self.all_paused = False - else: - self.destroy() - variaapp.quit() + if (self.appconf['remote'] == '0'): + self.pause_all("no") + self.api.client.shutdown() + + if (show_exit_window == True): + exiting_dialog = Adw.MessageDialog() + exiting_dialog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=25) + exiting_dialog.set_child(exiting_dialog_box) + exiting_dialog_box.set_margin_top(30) + exiting_dialog_box.set_margin_bottom(30) + exiting_dialog_spinner = Gtk.Spinner() + exiting_dialog_spinner.set_size_request(30, 30) + exiting_dialog_spinner.start() + exiting_dialog_box.append(exiting_dialog_spinner) + exiting_dialog_label = Gtk.Label(label=_("Exiting Varia...")) + exiting_dialog_label.get_style_context().add_class("title-1") + exiting_dialog_box.append(exiting_dialog_label) + exiting_dialog.set_transient_for(self) + GLib.idle_add(exiting_dialog.show) + else: + exiting_dialog = None + + GLib.timeout_add(3000, self.aria2c_exiting_check, app, 0, variaapp, exiting_dialog) + + else: + self.destroy() + variaapp.quit() - def aria2c_exiting_check(self, app, counter, variaapp): + def aria2c_exiting_check(self, app, counter, variaapp, exiting_dialog): print(counter) if ((counter < 20) and (self.aria2c_subprocess.poll() is None)): counter += 1 - GLib.timeout_add(250, self.aria2c_exiting_check, app, counter, variaapp) + GLib.timeout_add(250, self.aria2c_exiting_check, app, counter, variaapp, exiting_dialog) else: self.aria2c_subprocess.terminate() self.aria2c_subprocess.wait() + if (exiting_dialog is not None): + exiting_dialog.destroy() self.destroy() variaapp.quit() - return + for thread in threading.enumerate(): + print(thread.name) + return def quit_action_received(self, variaapp): self.exitProgram(variaapp, variaapp, False, False) class MyApp(Adw.Application): - def __init__(self, appdir, appconf, aria2c_subprocess, is_running, **kwargs): + def __init__(self, appdir, appconf, aria2c_subprocess, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate, appdir, appconf, aria2c_subprocess) quit_action = Gio.SimpleAction.new("quit", None) @@ -298,11 +295,13 @@ def __init__(self, appdir, appconf, aria2c_subprocess, is_running, **kwargs): self.add_action(quit_action) def on_activate(self, app, appdir, appconf, aria2c_subprocess): - self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess, is_running=is_running) + if not hasattr(self, 'win'): + self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess) self.win.present() def quit_action(self, action, parameter): - self.win.quit_action_received(self) + if (self.win.self.terminating == False): + self.win.quit_action_received(self) def main(version, aria2cexec): if "FLATPAK_ID" in os.environ: @@ -345,7 +344,7 @@ def main(version, aria2cexec): if (len(arguments) > 1): arguments = arguments[:-1] - app = MyApp(appdir, appconf, aria2c_subprocess, is_running, application_id="io.github.giantpinkrobots.varia") + app = MyApp(appdir, appconf, aria2c_subprocess, application_id="io.github.giantpinkrobots.varia") try: app.run(arguments) except: @@ -353,5 +352,3 @@ def main(version, aria2cexec): if ((__name__ == '__main__') and (os.name == 'nt')): sys.exit(main(variaVersion, "aria2c")) - -kill_now = False From f811086dc35ffcfdca3ff4f70030902d19c65aa9 Mon Sep 17 00:00:00 2001 From: Giant Pink Robots! Date: Tue, 27 Feb 2024 02:44:32 +0300 Subject: [PATCH 9/9] Tweaks - Potfile additions - Background mode tweaks - aria2p dependency update --- po/Varia.pot | 6 ++++++ po/tr.po | 6 ++++++ python3-aria2p.json | 25 ++++++++++--------------- src/variamain.py | 10 ++++++---- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/po/Varia.pot b/po/Varia.pot index a99a664..94fd232 100644 --- a/po/Varia.pot +++ b/po/Varia.pot @@ -209,6 +209,12 @@ msgstr "" msgid "Exiting Varia..." msgstr "" +msgid "Background Mode" +msgstr "" + +msgid "Continuing the downloads in the background." +msgstr "" + #: src/gtk/help-overlay.ui:11 msgctxt "shortcut window" msgid "General" diff --git a/po/tr.po b/po/tr.po index c97f415..16e191e 100644 --- a/po/tr.po +++ b/po/tr.po @@ -213,6 +213,12 @@ msgstr "Uzak İndirme Dizini" msgid "Exiting Varia..." msgstr "Varia'dan çıkılıyor..." +msgid "Background Mode" +msgstr "Arka Plan Modu" + +msgid "Continuing the downloads in the background." +msgstr "İndirmeler arka planda devam ediyor." + #: src/gtk/help-overlay.ui:11 msgctxt "shortcut window" msgid "General" diff --git a/python3-aria2p.json b/python3-aria2p.json index 9c2bf76..ed5cb50 100644 --- a/python3-aria2p.json +++ b/python3-aria2p.json @@ -12,13 +12,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/1b/2e/e1eba62ca72501efa07002f816e065849b4c26cc212f9c623d72e74993a0/aria2p-0.11.3-py3-none-any.whl", - "sha256": "bc0fbe71de360e01e3db4301c89052799e7c4f004ab62fdbaa6bc96d20f476da" + "url": "https://files.pythonhosted.org/packages/9a/56/a1ba3e0fe737b9b20a45c10266ab88a0f44e5779fa751a7484259d8845ea/aria2p-0.12.0-py3-none-any.whl", + "sha256": "476171257cdd8d74d888bcb983ecba5e9db19c2d8ecf9ef22547f88080b7bea8" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/64/62/428ef076be88fa93716b576e4a01f919d25968913e817077a386fcbe4f42/certifi-2023.11.17-py3-none-any.whl", - "sha256": "e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + "url": "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", + "sha256": "dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" }, { "type": "file", @@ -27,8 +27,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", - "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "url": "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", + "sha256": "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" }, { "type": "file", @@ -42,18 +42,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", - "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + "url": "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", + "sha256": "450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/96/94/c31f58c7a7f470d5665935262ebd7455c7e4c7782eb525658d3dbf4b9403/urllib3-2.1.0-py3-none-any.whl", - "sha256": "55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/c4/3c/1892ce394828c43d4f65248ebdee3854114266b75d1f5915cb211155ad7b/websocket_client-1.6.4-py3-none-any.whl", - "sha256": "084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24" + "url": "https://files.pythonhosted.org/packages/1e/70/1e88138a9afbed1d37093b85f0bebc3011623c4f47c166431599fe9d6c93/websocket_client-1.7.0-py3-none-any.whl", + "sha256": "f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588" } ] } \ No newline at end of file diff --git a/src/variamain.py b/src/variamain.py index 06ee9c9..91d509f 100644 --- a/src/variamain.py +++ b/src/variamain.py @@ -233,6 +233,7 @@ def exitProgram(self, app, variaapp, background, show_exit_window): notification = Gio.Notification.new(_("Background Mode")) notification.set_body(_("Continuing the downloads in the background.")) variaapp.send_notification(None, notification) + print('Background mode') else: self.terminating = True @@ -284,7 +285,8 @@ def aria2c_exiting_check(self, app, counter, variaapp, exiting_dialog): return def quit_action_received(self, variaapp): - self.exitProgram(variaapp, variaapp, False, False) + if (self.terminating == False): + self.exitProgram(variaapp, variaapp, False, False) class MyApp(Adw.Application): def __init__(self, appdir, appconf, aria2c_subprocess, **kwargs): @@ -297,11 +299,11 @@ def __init__(self, appdir, appconf, aria2c_subprocess, **kwargs): def on_activate(self, app, appdir, appconf, aria2c_subprocess): if not hasattr(self, 'win'): self.win = MainWindow(application=app, variaapp=self, appdir=appdir, appconf=appconf, aria2c_subprocess=aria2c_subprocess) - self.win.present() + if (self.win.terminating == False): + self.win.present() def quit_action(self, action, parameter): - if (self.win.self.terminating == False): - self.win.quit_action_received(self) + self.win.quit_action_received(self) def main(version, aria2cexec): if "FLATPAK_ID" in os.environ: