From 760bcf30c470d99c080f6e6f7c1a9743f68aa7fc Mon Sep 17 00:00:00 2001 From: JW-CodeArt Date: Sun, 1 Jun 2025 20:53:54 +0200 Subject: [PATCH 1/6] feature: "import/export" (1. iteration) - import and export menu entry - new shortcuts for the import and the export tool - a new tool window (ei_tool.ui) - import and export handler in common.py --- usr/lib/webapp-manager/common.py | 187 ++++++++++++++++++++++- usr/lib/webapp-manager/webapp-manager.py | 136 ++++++++++++++++- usr/share/webapp-manager/ei_tool.ui | 172 +++++++++++++++++++++ usr/share/webapp-manager/shortcuts.ui | 14 ++ 4 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 usr/share/webapp-manager/ei_tool.ui diff --git a/usr/lib/webapp-manager/common.py b/usr/lib/webapp-manager/common.py index e9ec1aa..8e7b398 100644 --- a/usr/lib/webapp-manager/common.py +++ b/usr/lib/webapp-manager/common.py @@ -17,9 +17,10 @@ import threading import traceback from typing import Optional +import tarfile # 2. Related third party imports. -from gi.repository import GObject +from gi.repository import GObject, GLib import PIL.Image import requests # Note: BeautifulSoup is an optional import supporting another way of getting a website's favicons. @@ -64,6 +65,19 @@ def wrapper(*args): ICONS_DIR = os.path.join(ICE_DIR, "icons") BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, BROWSER_TYPE_LIBREWOLF_FLATPAK, BROWSER_TYPE_WATERFOX_FLATPAK, BROWSER_TYPE_FLOORP_FLATPAK, BROWSER_TYPE_CHROMIUM, BROWSER_TYPE_EPIPHANY, BROWSER_TYPE_FALKON, BROWSER_TYPE_ZEN_FLATPAK = range(10) +class ei_task: + def __init__(self, result_callback, update_callback, builder, webAppLauncherSelf, window, stop_event, task): + self.result_callback = result_callback + self.update_callback = update_callback + self.builder = builder + self.webAppLauncherSelf = webAppLauncherSelf + self.path = "" + self.window = window + self.stop_event = stop_event + self.task = task + self.include_browserdata = False + self.result = "error" + class Browser: def __init__(self, browser_type, name, exec_path, test_path): @@ -551,5 +565,176 @@ def download_favicon(url): images = sorted(images, key = lambda x: x[1].height, reverse=True) return images +def export_config(ei_task_info: ei_task): + # The export process in the background. + try: + # Collect all files + webapps = get_all_desktop_files() + if ei_task_info.include_browserdata: + ice_files = get_all_files(ICE_DIR) + else: + ice_files = get_all_files(ICONS_DIR) + files = webapps + ice_files + total = len(files) + update_interval = 1 if int(total / 100) < 1 else int(total / 100) + + # Write the .tar.gz file + with tarfile.open(ei_task_info.path, "w:gz") as tar: + counter = 0 + for file in files: + tar.add(file["full_path"], arcname=file["arcname"]) + if counter % update_interval == 0: + progress = round(counter / total, 2) + GLib.idle_add(ei_task_info.update_callback, ei_task_info, progress) + + if ei_task_info.stop_event.is_set(): + # The user aborts the process. + tar.close() + clean_up_export(ei_task_info) + return "cancelled" + counter += 1 + + ei_task_info.result = "ok" + except Exception as e: + print(e) + ei_task_info.result = "error" + + GLib.idle_add(ei_task_info.result_callback, ei_task_info) + +def clean_up_export(ei_task_info: ei_task): + # Remove the rest of the exported file when the user aborts the process. + if os.path.exists(ei_task_info.path): + os.remove(ei_task_info.path) + GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) + +def import_config(ei_task_info: ei_task): + # The import process in the background. + try: + # Make a list of the files beforehand so that the file structure can be restored + # if the user aborts the import process. + files_before = get_files_dirs(ICE_DIR) + get_files_dirs(APPS_DIR) + + with tarfile.open(ei_task_info.path, "r:gz") as tar: + files = tar.getnames() + total = len(files) + base_dir = os.path.dirname(ICE_DIR) + update_interval = 1 if int(total / 100) < 1 else int(total / 100) + counter = 0 + for file in files: + # Exclude the file if it belongs to the browser data. + no_browserdata = ei_task_info.include_browserdata == False + is_ice_dir = file.startswith("ice/") + is_no_icon = not file.startswith("ice/icons") + if not(no_browserdata and is_ice_dir and is_no_icon): + tar.extract(file, base_dir) + + if file.startswith("applications/"): + # Redefine the "Exec" section. This is necessary if the username or browser path differs. + path = os.path.join(base_dir, file) + update_exec_path(path) + + if counter % update_interval == 0: + progress = round(counter / total, 2) + GLib.idle_add(ei_task_info.update_callback, ei_task_info, progress) + + if ei_task_info.stop_event.is_set(): + tar.close() + clean_up_import(ei_task_info, files_before) + return "cancelled" + counter += 1 + ei_task_info.result = "ok" + except Exception as e: + print(e) + ei_task_info.result = "error" + + GLib.idle_add(ei_task_info.result_callback, ei_task_info) + +def clean_up_import(ei_task_info: ei_task, files_before): + # Delete all imported files if the import process is aborted. + try: + # Search all new files + files_now = get_files_dirs(ICE_DIR) + get_files_dirs(APPS_DIR) + new_files = list(set(files_now) - set(files_before)) + for file in new_files: + if os.path.exists(file): + if os.path.isdir(file): + shutil.rmtree(file) + else: + os.remove(file) + + GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) + except Exception as e: + print(e) + +def check_browser_directories_tar(path): + # Check if the archive contains browser data. + try: + with tarfile.open(path, "r:gz") as tar: + for member in tar: + parts = member.name.strip("/").split("/") + if parts[0] == "ice" and parts[1] != "icons": + tar.close() + return True + tar.close() + return False + except: + return False + +def get_all_desktop_files(): + # Search all web apps and desktop files. + files = [] + for filename in os.listdir(APPS_DIR): + if filename.lower().startswith("webapp-") and filename.endswith(".desktop"): + full_path = os.path.join(APPS_DIR, filename) + arcname = os.path.relpath(full_path, os.path.dirname(APPS_DIR)) + files.append({"full_path":full_path, "arcname":arcname}) + return files + +def get_all_files(base_dir): + # List all the files in a directory. + files = [] + for root, dirs, filenames in os.walk(base_dir): + for filename in filenames: + full_path = os.path.join(root, filename) + arcname = "" + if base_dir == ICONS_DIR: + arcname += "ice/" + arcname += os.path.relpath(full_path, os.path.dirname(base_dir)) + files.append({"full_path":full_path, "arcname":arcname}) + return files + +def get_files_dirs(base_dir): + # List all the files and subdirectories within a directory. + paths = [] + for dirpath, dirnames, filenames in os.walk(base_dir): + paths.append(dirpath) + for name in filenames: + paths.append(os.path.join(dirpath, name)) + return paths + +def update_exec_path(path): + # This updates the 'exec' section of an imported web application or creates the browser directory for it. + config = configparser.RawConfigParser() + config.optionxform = str + config.read(path) + codename = os.path.basename(path) + codename = codename.replace(".desktop", "") + codename = codename.replace("WebApp-", "") + codename = codename.replace("webapp-", "") + webapp = WebAppLauncher(path, codename) + browsers = WebAppManager.get_supported_browsers() + if "/" in webapp.icon: + # Update Icon Path + iconpath = ICONS_DIR + "/" + os.path.basename(webapp.icon) + config.set("Desktop Entry", "Icon", iconpath) + else: + iconpath = webapp.icon + + browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) + new_exec_line = WebAppManager.get_exec_string(None, browser, webapp.codename, webapp.custom_parameters, iconpath, webapp.isolate_profile, webapp.navbar, webapp.privatewindow, webapp.url) + config.set("Desktop Entry", "Exec", new_exec_line) + with open(path, 'w') as configfile: + config.write(configfile, space_around_delimiters=False) + if __name__ == "__main__": download_favicon(sys.argv[1]) diff --git a/usr/lib/webapp-manager/webapp-manager.py b/usr/lib/webapp-manager/webapp-manager.py index dd77cae..08a20ba 100755 --- a/usr/lib/webapp-manager/webapp-manager.py +++ b/usr/lib/webapp-manager/webapp-manager.py @@ -7,6 +7,7 @@ import shutil import subprocess import warnings +import threading # 2. Related third party imports. import gi @@ -21,7 +22,7 @@ from gi.repository import Gtk, Gdk, Gio, XApp, GdkPixbuf # 3. Local application/library specific imports. -from common import _async, idle, WebAppManager, download_favicon, ICONS_DIR, BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP +from common import _async, idle, WebAppManager, download_favicon, BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, export_config, import_config, ei_task, check_browser_directories_tar setproctitle.setproctitle("webapp-manager") @@ -124,6 +125,20 @@ def __init__(self, application): self.window.add_accel_group(accel_group) menu = self.builder.get_object("main_menu") item = Gtk.ImageMenuItem() + item.set_image(Gtk.Image.new_from_icon_name("document-send-symbolic", Gtk.IconSize.MENU)) + item.set_label(_("Export")) + item.connect("activate", lambda widget: self.open_ei_tool("export")) + key, mod = Gtk.accelerator_parse("E") + item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE) + menu.append(item) + item = Gtk.ImageMenuItem() + item.set_image(Gtk.Image.new_from_icon_name("document-open-symbolic", Gtk.IconSize.MENU)) + item.set_label(_("Import")) + item.connect("activate", lambda widget: self.open_ei_tool("import")) + key, mod = Gtk.accelerator_parse("I") + item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE) + menu.append(item) + item = Gtk.ImageMenuItem() item.set_image( Gtk.Image.new_from_icon_name("preferences-desktop-keyboard-shortcuts-symbolic", Gtk.IconSize.MENU)) item.set_label(_("Keyboard Shortcuts")) @@ -537,6 +552,125 @@ def load_webapps(self): self.stack.set_visible_child_name("main_page") self.headerbar.set_subtitle(_("Run websites as if they were apps")) + # Export and Import feature "ei" + def open_ei_tool(self, action): + # Open the import / export window + gladefile = "/usr/share/webapp-manager/ei_tool.ui" + builder = Gtk.Builder() + builder.set_translation_domain(APP) + builder.add_from_file(gladefile) + window = builder.get_object("window") + # Translate text and prepare widgets + if action == "export": + window.set_title(_("Export Tool")) + else: + window.set_title(_("Import Tool")) + builder.get_object("choose_location_text").set_text(_("Choose a location")) + builder.get_object("include_browserdata").set_label(_("BETA: Include Browser data (Config, Cache, Extensions...)\nIt requires the same browser version on the destination computer\nIt might take some time.")) + builder.get_object("no_browser_data").set_text(_("Browser data import not available because \nit is not included in the importet file.")) + builder.get_object("no_browser_data").set_visible(False) + builder.get_object("start_button").set_label(_("Start")) + builder.get_object("start_button").connect("clicked", lambda button: self.ei_start_process(button, ei_task_info)) + builder.get_object("cancel_button").set_visible(False) + builder.get_object("select_location_button").connect("clicked", lambda widget: self.select_location(ei_task_info)) + + # Prepare ei_task_info which stores all the values for the import / export + stop_event = threading.Event() + ei_task_info = ei_task(self.show_ei_result, self.update_ei_progress, builder, self, window, stop_event, action) + window.show() + + def ei_start_process(self, button, ei_task_info: ei_task): + # Start the import / export process + buffer = ei_task_info.builder.get_object("file_path").get_buffer() + path = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True) + if path != "": + ei_task_info.path = path + ei_task_info.include_browserdata = ei_task_info.builder.get_object("include_browserdata").get_active() + button.set_sensitive(False) + if ei_task_info.task == "export": + thread = threading.Thread(target=export_config, args=(ei_task_info,)) + else: + thread = threading.Thread(target=import_config, args=(ei_task_info,)) + thread.start() + ei_task_info.builder.get_object("cancel_button").set_visible(True) + ei_task_info.builder.get_object("cancel_button").connect("clicked", lambda button: self.abort_ei(button, ei_task_info, thread)) + + + def select_location(self, ei_task_info: ei_task): + # Open the file chooser window + if ei_task_info.task == "export": + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) + dialog = Gtk.FileChooserDialog(_("Export Configuration - Please choose a file location"), self.window, Gtk.FileChooserAction.SAVE, buttons) + else: + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) + dialog = Gtk.FileChooserDialog(_("Import Configuration - Please select the file"), self.window, Gtk.FileChooserAction.OPEN, buttons) + + filter = Gtk.FileFilter() + filter.set_name(".tar.gz") + filter.add_pattern("*.tar.gz") + dialog.add_filter(filter) + response = dialog.run() + if response == Gtk.ResponseType.OK: + path = dialog.get_filename() + if ei_task_info.task == "export": + path += ".tar.gz" + ei_task_info.builder.get_object("file_path").get_buffer().set_text(path) + + # Check if include browser data is available + include_browser_available = True + if ei_task_info.task == "import": + if not check_browser_directories_tar(path): + include_browser_available = False + + ei_task_info.builder.get_object("include_browserdata").set_sensitive(include_browser_available) + ei_task_info.builder.get_object("no_browser_data").set_visible(not include_browser_available) + ei_task_info.builder.get_object("include_browserdata").set_active(include_browser_available) + dialog.destroy() + + + def abort_ei(self, button, ei_task_info:ei_task, thread): + # Abort the export / import process + button.set_sensitive(False) + self.update_ei_progress(ei_task_info, 0) + # The backend function will automatically clean up after the stop flag is triggered. + ei_task_info.stop_event.set() + thread.join() + + def update_ei_progress(self, ei_task_info:ei_task, progress): + # Update the progress bar or close the tool window by 100%. + try: + ei_task_info.builder.get_object("progress").set_fraction(progress) + if progress == 1: + ei_task_info.window.destroy() + except: + # The user closed the progress window + pass + + + def show_ei_result(self, ei_task_info:ei_task): + # Displays a success or failure message when the process is complete. + ei_task_info.window.destroy() + if ei_task_info.result == "ok": + message = _(ei_task_info.task.capitalize() + " completed!") + else: + message = _(ei_task_info.task.capitalize() + " failed!") + + if ei_task_info.result == "ok" and ei_task_info.task == "export": + # This dialog box gives users the option to open the containing directory. + dialog = Gtk.Dialog(message, ei_task_info.webAppLauncherSelf.window, None, (_("Open Containing Folder"), 10, Gtk.STOCK_OK, Gtk.ButtonsType.OK)) + dialog.get_content_area().add(Gtk.Label(label=_("Configuration has been exported successfully. This is the file location:")+"\n"+ei_task_info.path)) + dialog.show_all() + result = dialog.run() + if result == 10: + # Open Containing Folder + print("open folder") + os.system("xdg-open " + os.path.dirname(ei_task_info.path)) + else: + dialog = Gtk.MessageDialog(text=message, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK) + dialog.run() + + dialog.destroy() + ei_task_info.webAppLauncherSelf.load_webapps() if __name__ == "__main__": application = MyApplication("org.x.webapp-manager", Gio.ApplicationFlags.FLAGS_NONE) diff --git a/usr/share/webapp-manager/ei_tool.ui b/usr/share/webapp-manager/ei_tool.ui new file mode 100644 index 0000000..fdaa37a --- /dev/null +++ b/usr/share/webapp-manager/ei_tool.ui @@ -0,0 +1,172 @@ + + + + + + False + 500 + 200 + dialog + + + False + 5 + 5 + 5 + 5 + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + + + True + True + 0 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + start + Choose a location + + + False + True + 0 + + + + + True + False + + + True + True + center + 15 + True + False + + + True + True + 0 + + + + + gtk-open + True + True + True + True + + + False + True + 1 + + + + + False + True + 1 + + + + + Include Browser data (Config, Cache, Extensions...) +It might take some time. + True + True + False + True + + + False + True + 2 + + + + + True + False + start + 18 + Browser data import not available because +it is not included in the importet file. + True + + + + + + False + True + 3 + + + + + Start + True + True + True + 93 + + + False + True + 5 + + + + + True + False + True + + + False + True + 6 + + + + + True + True + 1 + + + + + + diff --git a/usr/share/webapp-manager/shortcuts.ui b/usr/share/webapp-manager/shortcuts.ui index 31f5389..ae69779 100644 --- a/usr/share/webapp-manager/shortcuts.ui +++ b/usr/share/webapp-manager/shortcuts.ui @@ -53,6 +53,20 @@ Go Back + + + 1 + <ctrl><shift>E + Export + + + + + 1 + <ctrl><shift>I + Import + + 1 From cbfaee3cfab09bfb7ae7b6c414e82def90be83ac Mon Sep 17 00:00:00 2001 From: JW-CodeArt Date: Fri, 6 Jun 2025 22:38:41 +0200 Subject: [PATCH 2/6] feature "import/export" (2nd iteration) - remove unnecessary code and simplify it - remove the unstable profile directory export and import functionality - remove the abort function and button, as the process is so quick that they are completely unnecessary. - define gladefiles constants at the beginning of webapp-manager.py --- usr/lib/webapp-manager/common.py | 113 +++---------- usr/lib/webapp-manager/webapp-manager.py | 78 ++++----- usr/share/webapp-manager/ei_tool.ui | 195 +++++++++-------------- 3 files changed, 123 insertions(+), 263 deletions(-) diff --git a/usr/lib/webapp-manager/common.py b/usr/lib/webapp-manager/common.py index 8e7b398..f24b873 100644 --- a/usr/lib/webapp-manager/common.py +++ b/usr/lib/webapp-manager/common.py @@ -66,16 +66,14 @@ def wrapper(*args): BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, BROWSER_TYPE_LIBREWOLF_FLATPAK, BROWSER_TYPE_WATERFOX_FLATPAK, BROWSER_TYPE_FLOORP_FLATPAK, BROWSER_TYPE_CHROMIUM, BROWSER_TYPE_EPIPHANY, BROWSER_TYPE_FALKON, BROWSER_TYPE_ZEN_FLATPAK = range(10) class ei_task: - def __init__(self, result_callback, update_callback, builder, webAppLauncherSelf, window, stop_event, task): + def __init__(self, result_callback, update_callback, builder, webAppLauncherSelf, window, task): self.result_callback = result_callback self.update_callback = update_callback self.builder = builder self.webAppLauncherSelf = webAppLauncherSelf self.path = "" self.window = window - self.stop_event = stop_event self.task = task - self.include_browserdata = False self.result = "error" class Browser: @@ -565,16 +563,12 @@ def download_favicon(url): images = sorted(images, key = lambda x: x[1].height, reverse=True) return images +@_async def export_config(ei_task_info: ei_task): # The export process in the background. try: - # Collect all files - webapps = get_all_desktop_files() - if ei_task_info.include_browserdata: - ice_files = get_all_files(ICE_DIR) - else: - ice_files = get_all_files(ICONS_DIR) - files = webapps + ice_files + # Search all files + files = get_all_desktop_files() + get_all_icons() total = len(files) update_interval = 1 if int(total / 100) < 1 else int(total / 100) @@ -586,14 +580,10 @@ def export_config(ei_task_info: ei_task): if counter % update_interval == 0: progress = round(counter / total, 2) GLib.idle_add(ei_task_info.update_callback, ei_task_info, progress) - - if ei_task_info.stop_event.is_set(): - # The user aborts the process. - tar.close() - clean_up_export(ei_task_info) - return "cancelled" + counter += 1 - + + GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) ei_task_info.result = "ok" except Exception as e: print(e) @@ -601,19 +591,10 @@ def export_config(ei_task_info: ei_task): GLib.idle_add(ei_task_info.result_callback, ei_task_info) -def clean_up_export(ei_task_info: ei_task): - # Remove the rest of the exported file when the user aborts the process. - if os.path.exists(ei_task_info.path): - os.remove(ei_task_info.path) - GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) - +@_async def import_config(ei_task_info: ei_task): # The import process in the background. try: - # Make a list of the files beforehand so that the file structure can be restored - # if the user aborts the import process. - files_before = get_files_dirs(ICE_DIR) + get_files_dirs(APPS_DIR) - with tarfile.open(ei_task_info.path, "r:gz") as tar: files = tar.getnames() total = len(files) @@ -621,27 +602,17 @@ def import_config(ei_task_info: ei_task): update_interval = 1 if int(total / 100) < 1 else int(total / 100) counter = 0 for file in files: - # Exclude the file if it belongs to the browser data. - no_browserdata = ei_task_info.include_browserdata == False - is_ice_dir = file.startswith("ice/") - is_no_icon = not file.startswith("ice/icons") - if not(no_browserdata and is_ice_dir and is_no_icon): - tar.extract(file, base_dir) - + tar.extract(file, base_dir) if file.startswith("applications/"): - # Redefine the "Exec" section. This is necessary if the username or browser path differs. + # Rewrite the "Exec" section. This is necessary if the username differs. path = os.path.join(base_dir, file) update_exec_path(path) if counter % update_interval == 0: progress = round(counter / total, 2) GLib.idle_add(ei_task_info.update_callback, ei_task_info, progress) - - if ei_task_info.stop_event.is_set(): - tar.close() - clean_up_import(ei_task_info, files_before) - return "cancelled" counter += 1 + GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) ei_task_info.result = "ok" except Exception as e: print(e) @@ -649,36 +620,6 @@ def import_config(ei_task_info: ei_task): GLib.idle_add(ei_task_info.result_callback, ei_task_info) -def clean_up_import(ei_task_info: ei_task, files_before): - # Delete all imported files if the import process is aborted. - try: - # Search all new files - files_now = get_files_dirs(ICE_DIR) + get_files_dirs(APPS_DIR) - new_files = list(set(files_now) - set(files_before)) - for file in new_files: - if os.path.exists(file): - if os.path.isdir(file): - shutil.rmtree(file) - else: - os.remove(file) - - GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) - except Exception as e: - print(e) - -def check_browser_directories_tar(path): - # Check if the archive contains browser data. - try: - with tarfile.open(path, "r:gz") as tar: - for member in tar: - parts = member.name.strip("/").split("/") - if parts[0] == "ice" and parts[1] != "icons": - tar.close() - return True - tar.close() - return False - except: - return False def get_all_desktop_files(): # Search all web apps and desktop files. @@ -690,37 +631,31 @@ def get_all_desktop_files(): files.append({"full_path":full_path, "arcname":arcname}) return files -def get_all_files(base_dir): +def get_all_icons(): # List all the files in a directory. files = [] - for root, dirs, filenames in os.walk(base_dir): + for root, dirs, filenames in os.walk(ICONS_DIR): for filename in filenames: full_path = os.path.join(root, filename) arcname = "" - if base_dir == ICONS_DIR: - arcname += "ice/" - arcname += os.path.relpath(full_path, os.path.dirname(base_dir)) + arcname += os.path.relpath(full_path, os.path.dirname(ICE_DIR)) files.append({"full_path":full_path, "arcname":arcname}) return files -def get_files_dirs(base_dir): - # List all the files and subdirectories within a directory. - paths = [] - for dirpath, dirnames, filenames in os.walk(base_dir): - paths.append(dirpath) - for name in filenames: - paths.append(os.path.join(dirpath, name)) - return paths -def update_exec_path(path): - # This updates the 'exec' section of an imported web application or creates the browser directory for it. - config = configparser.RawConfigParser() - config.optionxform = str - config.read(path) +def get_codename(path): codename = os.path.basename(path) codename = codename.replace(".desktop", "") codename = codename.replace("WebApp-", "") codename = codename.replace("webapp-", "") + return codename + +def update_exec_path(path): + # This updates the 'exec' section of an imported web application or creates the profile directory for it. + config = configparser.RawConfigParser() + config.optionxform = str + config.read(path) + codename = get_codename(path) webapp = WebAppLauncher(path, codename) browsers = WebAppManager.get_supported_browsers() if "/" in webapp.icon: @@ -731,7 +666,7 @@ def update_exec_path(path): iconpath = webapp.icon browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) - new_exec_line = WebAppManager.get_exec_string(None, browser, webapp.codename, webapp.custom_parameters, iconpath, webapp.isolate_profile, webapp.navbar, webapp.privatewindow, webapp.url) + new_exec_line = WebAppManager.get_exec_string(None, browser, codename, webapp.custom_parameters, iconpath, webapp.isolate_profile, webapp.navbar, webapp.privatewindow, webapp.url) config.set("Desktop Entry", "Exec", new_exec_line) with open(path, 'w') as configfile: config.write(configfile, space_around_delimiters=False) diff --git a/usr/lib/webapp-manager/webapp-manager.py b/usr/lib/webapp-manager/webapp-manager.py index 08a20ba..42d8539 100755 --- a/usr/lib/webapp-manager/webapp-manager.py +++ b/usr/lib/webapp-manager/webapp-manager.py @@ -22,7 +22,7 @@ from gi.repository import Gtk, Gdk, Gio, XApp, GdkPixbuf # 3. Local application/library specific imports. -from common import _async, idle, WebAppManager, download_favicon, BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, export_config, import_config, ei_task, check_browser_directories_tar +from common import _async, idle, WebAppManager, download_favicon, BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, export_config, import_config, ei_task, ICONS_DIR setproctitle.setproctitle("webapp-manager") @@ -38,6 +38,10 @@ CATEGORY_ID, CATEGORY_NAME = range(2) BROWSER_OBJ, BROWSER_NAME = range(2) +# Gladefiles +MAIN_WINDOW_GLADEFILE = "/usr/share/webapp-manager/webapp-manager.ui" +SHORTCUTS_GLADEFILE = "/usr/share/webapp-manager/shortcuts.ui" +EI_TOOL_GLADEFILE = "/usr/share/webapp-manager/ei_tool.ui" class MyApplication(Gtk.Application): # Main initialization routine @@ -68,10 +72,9 @@ def __init__(self, application): self.icon_theme = Gtk.IconTheme.get_default() # Set the Glade file - gladefile = "/usr/share/webapp-manager/webapp-manager.ui" self.builder = Gtk.Builder() self.builder.set_translation_domain(APP) - self.builder.add_from_file(gladefile) + self.builder.add_from_file(MAIN_WINDOW_GLADEFILE) self.window = self.builder.get_object("main_window") self.window.set_title(_("Web Apps")) self.window.set_icon_name("webapp-manager") @@ -238,10 +241,9 @@ def data_func_surface(self, column, cell, model, iter_, *args): cell.set_property("surface", surface) def open_keyboard_shortcuts(self, widget): - gladefile = "/usr/share/webapp-manager/shortcuts.ui" builder = Gtk.Builder() builder.set_translation_domain(APP) - builder.add_from_file(gladefile) + builder.add_from_file(SHORTCUTS_GLADEFILE) window = builder.get_object("shortcuts-webappmanager") window.set_title(_("Web Apps")) window.show() @@ -555,28 +557,23 @@ def load_webapps(self): # Export and Import feature "ei" def open_ei_tool(self, action): # Open the import / export window - gladefile = "/usr/share/webapp-manager/ei_tool.ui" builder = Gtk.Builder() builder.set_translation_domain(APP) - builder.add_from_file(gladefile) + builder.add_from_file(EI_TOOL_GLADEFILE) window = builder.get_object("window") + # Translate text and prepare widgets if action == "export": window.set_title(_("Export Tool")) else: window.set_title(_("Import Tool")) builder.get_object("choose_location_text").set_text(_("Choose a location")) - builder.get_object("include_browserdata").set_label(_("BETA: Include Browser data (Config, Cache, Extensions...)\nIt requires the same browser version on the destination computer\nIt might take some time.")) - builder.get_object("no_browser_data").set_text(_("Browser data import not available because \nit is not included in the importet file.")) - builder.get_object("no_browser_data").set_visible(False) builder.get_object("start_button").set_label(_("Start")) builder.get_object("start_button").connect("clicked", lambda button: self.ei_start_process(button, ei_task_info)) - builder.get_object("cancel_button").set_visible(False) - builder.get_object("select_location_button").connect("clicked", lambda widget: self.select_location(ei_task_info)) + builder.get_object("select_location_button").connect("clicked", lambda widget: self.ei_select_location(ei_task_info)) # Prepare ei_task_info which stores all the values for the import / export - stop_event = threading.Event() - ei_task_info = ei_task(self.show_ei_result, self.update_ei_progress, builder, self, window, stop_event, action) + ei_task_info = ei_task(self.show_ei_result, self.update_ei_progress, builder, self, window, action) window.show() def ei_start_process(self, button, ei_task_info: ei_task): @@ -585,25 +582,24 @@ def ei_start_process(self, button, ei_task_info: ei_task): path = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True) if path != "": ei_task_info.path = path - ei_task_info.include_browserdata = ei_task_info.builder.get_object("include_browserdata").get_active() button.set_sensitive(False) if ei_task_info.task == "export": - thread = threading.Thread(target=export_config, args=(ei_task_info,)) + export_config(ei_task_info) + #thread = threading.Thread(target=export_config, args=(ei_task_info,)) else: - thread = threading.Thread(target=import_config, args=(ei_task_info,)) - thread.start() - ei_task_info.builder.get_object("cancel_button").set_visible(True) - ei_task_info.builder.get_object("cancel_button").connect("clicked", lambda button: self.abort_ei(button, ei_task_info, thread)) + import_config(ei_task_info) + #thread = threading.Thread(target=import_config, args=(ei_task_info,)) + #thread.start() - def select_location(self, ei_task_info: ei_task): + def ei_select_location(self, ei_task_info: ei_task): # Open the file chooser window if ei_task_info.task == "export": buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) dialog = Gtk.FileChooserDialog(_("Export Configuration - Please choose a file location"), self.window, Gtk.FileChooserAction.SAVE, buttons) else: buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) - dialog = Gtk.FileChooserDialog(_("Import Configuration - Please select the file"), self.window, Gtk.FileChooserAction.OPEN, buttons) + dialog = Gtk.FileChooserDialog(_("Import Configuration - Please select the archive"), self.window, Gtk.FileChooserAction.OPEN, buttons) filter = Gtk.FileFilter() filter.set_name(".tar.gz") @@ -615,33 +611,12 @@ def select_location(self, ei_task_info: ei_task): if ei_task_info.task == "export": path += ".tar.gz" ei_task_info.builder.get_object("file_path").get_buffer().set_text(path) - - # Check if include browser data is available - include_browser_available = True - if ei_task_info.task == "import": - if not check_browser_directories_tar(path): - include_browser_available = False - - ei_task_info.builder.get_object("include_browserdata").set_sensitive(include_browser_available) - ei_task_info.builder.get_object("no_browser_data").set_visible(not include_browser_available) - ei_task_info.builder.get_object("include_browserdata").set_active(include_browser_available) dialog.destroy() - - def abort_ei(self, button, ei_task_info:ei_task, thread): - # Abort the export / import process - button.set_sensitive(False) - self.update_ei_progress(ei_task_info, 0) - # The backend function will automatically clean up after the stop flag is triggered. - ei_task_info.stop_event.set() - thread.join() - def update_ei_progress(self, ei_task_info:ei_task, progress): - # Update the progress bar or close the tool window by 100%. + # Update the progress bar try: - ei_task_info.builder.get_object("progress").set_fraction(progress) - if progress == 1: - ei_task_info.window.destroy() + ei_task_info.builder.get_object("progress").set_fraction(progress) except: # The user closed the progress window pass @@ -649,7 +624,7 @@ def update_ei_progress(self, ei_task_info:ei_task, progress): def show_ei_result(self, ei_task_info:ei_task): # Displays a success or failure message when the process is complete. - ei_task_info.window.destroy() + ei_task_info.webAppLauncherSelf.load_webapps() if ei_task_info.result == "ok": message = _(ei_task_info.task.capitalize() + " completed!") else: @@ -663,16 +638,19 @@ def show_ei_result(self, ei_task_info:ei_task): result = dialog.run() if result == 10: # Open Containing Folder - print("open folder") os.system("xdg-open " + os.path.dirname(ei_task_info.path)) else: dialog = Gtk.MessageDialog(text=message, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() - ei_task_info.webAppLauncherSelf.load_webapps() + try: + ei_task_info.window.destroy() + except: + # User closed the window manually + pass + if __name__ == "__main__": application = MyApplication("org.x.webapp-manager", Gio.ApplicationFlags.FLAGS_NONE) - application.run() - + application.run() \ No newline at end of file diff --git a/usr/share/webapp-manager/ei_tool.ui b/usr/share/webapp-manager/ei_tool.ui index fdaa37a..a2e12df 100644 --- a/usr/share/webapp-manager/ei_tool.ui +++ b/usr/share/webapp-manager/ei_tool.ui @@ -2,42 +2,33 @@ - + + True False + True 500 - 200 - dialog - + 100 + True + True + + True False - 5 - 5 - 5 - 5 + 10 + 10 + 10 + 10 vertical - 2 - - + + + True False - end - - - gtk-cancel - True - True - True - True - - - True - True - 0 - - + start + Choose a location False - False + True 0 @@ -45,127 +36,83 @@ True False - vertical - - True - False - start - Choose a location - - - False - True - 0 - - - - - True - False - - - True - True - center - 15 - True - False - - - True - True - 0 - - - - - gtk-open - True - True - True - True - - - False - True - 1 - - - - - False - True - 1 - - - - - Include Browser data (Config, Cache, Extensions...) -It might take some time. + True True - False - True - - - False - True - 2 - - - - - True - False - start - 18 - Browser data import not available because -it is not included in the importet file. - True - - - + center + 15 + 15 + True + False - False + True True - 3 + 0 - - Start + + gtk-open True True True - 93 - - - False - True - 5 - - - - - True - False - True + True False True - 6 + 1 - True + False True 1 + + + True + False + 20 + 20 + + + False + True + 2 + + + + + Start + True + True + True + + + False + True + 5 + + + + + True + False + 10 + 20 + True + + + False + True + 6 + + From e9dd83ca39b5b5c46f79b83279e2d8e0e8b86b29 Mon Sep 17 00:00:00 2001 From: JW-CodeArt Date: Sat, 7 Jun 2025 17:47:50 +0200 Subject: [PATCH 3/6] feature "import/export" (3rd iteration) - update text and function names - removed the ei_task class as it is no longer needed - created a get_codename() function as this is used twice in common.py - removed the tool window and the progress tracking because they are not needed - removed the browser profile export feature because it is unstable. It takes extremely long and does not work with different browser versions - implemented a function which checks during the import process whether the browser is installed on the device. If not, it searches an alternative browser on the device. - declared gladefiles paths as constants at the beginning of webapp-manager.py - removed all trailing spaces - applied the maximum line length --- usr/lib/webapp-manager/common.py | 132 +++++++++-------------- usr/lib/webapp-manager/webapp-manager.py | 112 ++++++------------- usr/share/webapp-manager/ei_tool.ui | 119 -------------------- webapp-manager.pot | 116 +++++++++++++------- 4 files changed, 162 insertions(+), 317 deletions(-) delete mode 100644 usr/share/webapp-manager/ei_tool.ui diff --git a/usr/lib/webapp-manager/common.py b/usr/lib/webapp-manager/common.py index f24b873..75bac73 100644 --- a/usr/lib/webapp-manager/common.py +++ b/usr/lib/webapp-manager/common.py @@ -65,17 +65,6 @@ def wrapper(*args): ICONS_DIR = os.path.join(ICE_DIR, "icons") BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, BROWSER_TYPE_LIBREWOLF_FLATPAK, BROWSER_TYPE_WATERFOX_FLATPAK, BROWSER_TYPE_FLOORP_FLATPAK, BROWSER_TYPE_CHROMIUM, BROWSER_TYPE_EPIPHANY, BROWSER_TYPE_FALKON, BROWSER_TYPE_ZEN_FLATPAK = range(10) -class ei_task: - def __init__(self, result_callback, update_callback, builder, webAppLauncherSelf, window, task): - self.result_callback = result_callback - self.update_callback = update_callback - self.builder = builder - self.webAppLauncherSelf = webAppLauncherSelf - self.path = "" - self.window = window - self.task = task - self.result = "error" - class Browser: def __init__(self, browser_type, name, exec_path, test_path): @@ -171,7 +160,7 @@ def get_webapps(self): for filename in os.listdir(APPS_DIR): if filename.lower().startswith("webapp-") and filename.endswith(".desktop"): path = os.path.join(APPS_DIR, filename) - codename = filename.replace("webapp-", "").replace("WebApp-", "").replace(".desktop", "") + codename = get_codename(path) if not os.path.isdir(path): try: webapp = WebAppLauncher(path, codename) @@ -319,8 +308,8 @@ def create_webapp(self, name, url, icon, category, browser, custom_parameters, i falkon_orig_prof_dir = os.path.join(os.path.expanduser("~/.config/falkon/profiles"), codename) os.symlink(falkon_profile_path, falkon_orig_prof_dir) - - def get_exec_string(self, browser, codename, custom_parameters, icon, isolate_profile, navbar, privatewindow, url): + @staticmethod + def get_exec_string( browser, codename, custom_parameters, icon, isolate_profile, navbar, privatewindow, url): if browser.browser_type in [BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, BROWSER_TYPE_ZEN_FLATPAK]: # Firefox based if browser.browser_type == BROWSER_TYPE_FIREFOX: @@ -564,61 +553,41 @@ def download_favicon(url): return images @_async -def export_config(ei_task_info: ei_task): - # The export process in the background. +def export_webapps(callback, path): + # The background export process try: - # Search all files - files = get_all_desktop_files() + get_all_icons() - total = len(files) - update_interval = 1 if int(total / 100) < 1 else int(total / 100) - + desktop_files = get_all_desktop_files() # Write the .tar.gz file - with tarfile.open(ei_task_info.path, "w:gz") as tar: - counter = 0 - for file in files: - tar.add(file["full_path"], arcname=file["arcname"]) - if counter % update_interval == 0: - progress = round(counter / total, 2) - GLib.idle_add(ei_task_info.update_callback, ei_task_info, progress) - - counter += 1 - - GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) - ei_task_info.result = "ok" + with tarfile.open(path, "w:gz") as tar: + for desktop_file in desktop_files: + tar.add(desktop_file["full_path"], arcname=desktop_file["arcname"]) + tar.add(ICONS_DIR, "ice/icons/") + result = "ok" except Exception as e: print(e) - ei_task_info.result = "error" - - GLib.idle_add(ei_task_info.result_callback, ei_task_info) + result = "error" + + callback(result, "export", path) @_async -def import_config(ei_task_info: ei_task): - # The import process in the background. +def import_webapps(callback, path): + # The background import process try: - with tarfile.open(ei_task_info.path, "r:gz") as tar: + result = "ok" + with tarfile.open(path, "r:gz") as tar: files = tar.getnames() - total = len(files) base_dir = os.path.dirname(ICE_DIR) - update_interval = 1 if int(total / 100) < 1 else int(total / 100) - counter = 0 for file in files: tar.extract(file, base_dir) if file.startswith("applications/"): - # Rewrite the "Exec" section. This is necessary if the username differs. + # Rewrite the "Exec" section. It will apply the new paths and will search for browsers path = os.path.join(base_dir, file) - update_exec_path(path) - - if counter % update_interval == 0: - progress = round(counter / total, 2) - GLib.idle_add(ei_task_info.update_callback, ei_task_info, progress) - counter += 1 - GLib.idle_add(ei_task_info.update_callback, ei_task_info, 1) - ei_task_info.result = "ok" + update_imported_desktop(path) except Exception as e: print(e) - ei_task_info.result = "error" + result = "error" - GLib.idle_add(ei_task_info.result_callback, ei_task_info) + callback(result, "import", path) def get_all_desktop_files(): @@ -631,45 +600,42 @@ def get_all_desktop_files(): files.append({"full_path":full_path, "arcname":arcname}) return files -def get_all_icons(): - # List all the files in a directory. - files = [] - for root, dirs, filenames in os.walk(ICONS_DIR): - for filename in filenames: - full_path = os.path.join(root, filename) - arcname = "" - arcname += os.path.relpath(full_path, os.path.dirname(ICE_DIR)) - files.append({"full_path":full_path, "arcname":arcname}) - return files - def get_codename(path): - codename = os.path.basename(path) - codename = codename.replace(".desktop", "") - codename = codename.replace("WebApp-", "") - codename = codename.replace("webapp-", "") + filename = os.path.basename(path) + codename = filename.replace(".desktop", "").replace("WebApp-", "").replace("webapp-", "") return codename -def update_exec_path(path): - # This updates the 'exec' section of an imported web application or creates the profile directory for it. - config = configparser.RawConfigParser() - config.optionxform = str - config.read(path) - codename = get_codename(path) - webapp = WebAppLauncher(path, codename) - browsers = WebAppManager.get_supported_browsers() +def update_imported_desktop(path): + webapp = WebAppLauncher(path, get_codename(path)) if "/" in webapp.icon: # Update Icon Path - iconpath = ICONS_DIR + "/" + os.path.basename(webapp.icon) - config.set("Desktop Entry", "Icon", iconpath) + iconpath = os.path.join(ICONS_DIR, os.path.basename(webapp.icon)) else: iconpath = webapp.icon - browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) - new_exec_line = WebAppManager.get_exec_string(None, browser, codename, webapp.custom_parameters, iconpath, webapp.isolate_profile, webapp.navbar, webapp.privatewindow, webapp.url) - config.set("Desktop Entry", "Exec", new_exec_line) - with open(path, 'w') as configfile: - config.write(configfile, space_around_delimiters=False) + # Check if the browser is installed + browsers = WebAppManager.get_supported_browsers() + configured_browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) + if os.path.exists(configured_browser.test_path) == False: + # If the browser is not installed, search another browser. + # 1. Sort browsers by same browser type + # 2. Sort the browsers by similarity of the name of the missing browser + similar_browsers = browsers + similar_browsers.sort(key=lambda browser: ( + browser.browser_type == configured_browser.browser_type, + configured_browser.name.split(" ")[0].lower() not in browser.name.lower() + )) + configured_browser = None + for browser in similar_browsers: + if os.path.exists(browser.test_path): + configured_browser = browser + break + + print(webapp.web_browser, "-Browser not installed") + + WebAppManager.edit_webapp(WebAppManager, path, webapp.name, configured_browser, webapp.url, iconpath, webapp.category, + webapp.custom_parameters, webapp.codename, webapp.isolate_profile, webapp.navbar, webapp.privatewindow) if __name__ == "__main__": download_favicon(sys.argv[1]) diff --git a/usr/lib/webapp-manager/webapp-manager.py b/usr/lib/webapp-manager/webapp-manager.py index 42d8539..b3b305d 100755 --- a/usr/lib/webapp-manager/webapp-manager.py +++ b/usr/lib/webapp-manager/webapp-manager.py @@ -22,7 +22,8 @@ from gi.repository import Gtk, Gdk, Gio, XApp, GdkPixbuf # 3. Local application/library specific imports. -from common import _async, idle, WebAppManager, download_favicon, BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, export_config, import_config, ei_task, ICONS_DIR +from common import BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, ICONS_DIR +from common import _async, idle, WebAppManager, download_favicon, export_webapps, import_webapps setproctitle.setproctitle("webapp-manager") @@ -41,7 +42,6 @@ # Gladefiles MAIN_WINDOW_GLADEFILE = "/usr/share/webapp-manager/webapp-manager.ui" SHORTCUTS_GLADEFILE = "/usr/share/webapp-manager/shortcuts.ui" -EI_TOOL_GLADEFILE = "/usr/share/webapp-manager/ei_tool.ui" class MyApplication(Gtk.Application): # Main initialization routine @@ -130,14 +130,14 @@ def __init__(self, application): item = Gtk.ImageMenuItem() item.set_image(Gtk.Image.new_from_icon_name("document-send-symbolic", Gtk.IconSize.MENU)) item.set_label(_("Export")) - item.connect("activate", lambda widget: self.open_ei_tool("export")) + item.connect("activate", lambda widget: self.ei_select_location("export")) key, mod = Gtk.accelerator_parse("E") item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE) menu.append(item) item = Gtk.ImageMenuItem() item.set_image(Gtk.Image.new_from_icon_name("document-open-symbolic", Gtk.IconSize.MENU)) item.set_label(_("Import")) - item.connect("activate", lambda widget: self.open_ei_tool("import")) + item.connect("activate", lambda widget: self.ei_select_location("import")) key, mod = Gtk.accelerator_parse("I") item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE) menu.append(item) @@ -554,53 +554,18 @@ def load_webapps(self): self.stack.set_visible_child_name("main_page") self.headerbar.set_subtitle(_("Run websites as if they were apps")) - # Export and Import feature "ei" - def open_ei_tool(self, action): - # Open the import / export window - builder = Gtk.Builder() - builder.set_translation_domain(APP) - builder.add_from_file(EI_TOOL_GLADEFILE) - window = builder.get_object("window") - - # Translate text and prepare widgets - if action == "export": - window.set_title(_("Export Tool")) - else: - window.set_title(_("Import Tool")) - builder.get_object("choose_location_text").set_text(_("Choose a location")) - builder.get_object("start_button").set_label(_("Start")) - builder.get_object("start_button").connect("clicked", lambda button: self.ei_start_process(button, ei_task_info)) - builder.get_object("select_location_button").connect("clicked", lambda widget: self.ei_select_location(ei_task_info)) - - # Prepare ei_task_info which stores all the values for the import / export - ei_task_info = ei_task(self.show_ei_result, self.update_ei_progress, builder, self, window, action) - window.show() - - def ei_start_process(self, button, ei_task_info: ei_task): - # Start the import / export process - buffer = ei_task_info.builder.get_object("file_path").get_buffer() - path = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True) - if path != "": - ei_task_info.path = path - button.set_sensitive(False) - if ei_task_info.task == "export": - export_config(ei_task_info) - #thread = threading.Thread(target=export_config, args=(ei_task_info,)) - else: - import_config(ei_task_info) - #thread = threading.Thread(target=import_config, args=(ei_task_info,)) - #thread.start() - - - def ei_select_location(self, ei_task_info: ei_task): - # Open the file chooser window - if ei_task_info.task == "export": + # "ei" means export and import feature + def ei_select_location(self, task): + # Open the file chooser dialog + if task == "export": buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) - dialog = Gtk.FileChooserDialog(_("Export Configuration - Please choose a file location"), self.window, Gtk.FileChooserAction.SAVE, buttons) + title = _("Export WebApps - Select file location") + dialog = Gtk.FileChooserDialog(title, self.window, Gtk.FileChooserAction.SAVE, buttons) else: buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) - dialog = Gtk.FileChooserDialog(_("Import Configuration - Please select the archive"), self.window, Gtk.FileChooserAction.OPEN, buttons) - + title = _("Import WebApps - Select the archive") + dialog = Gtk.FileChooserDialog(title, self.window, Gtk.FileChooserAction.OPEN, buttons) + filter = Gtk.FileFilter() filter.set_name(".tar.gz") filter.add_pattern("*.tar.gz") @@ -608,48 +573,39 @@ def ei_select_location(self, ei_task_info: ei_task): response = dialog.run() if response == Gtk.ResponseType.OK: path = dialog.get_filename() - if ei_task_info.task == "export": - path += ".tar.gz" - ei_task_info.builder.get_object("file_path").get_buffer().set_text(path) + if path != "": + if task == "export": + path += ".tar.gz" + export_webapps(self.show_ei_result, path) + else: + import_webapps(self.show_ei_result, path) dialog.destroy() - def update_ei_progress(self, ei_task_info:ei_task, progress): - # Update the progress bar - try: - ei_task_info.builder.get_object("progress").set_fraction(progress) - except: - # The user closed the progress window - pass - - - def show_ei_result(self, ei_task_info:ei_task): + def show_ei_result(self, result, task, path): # Displays a success or failure message when the process is complete. - ei_task_info.webAppLauncherSelf.load_webapps() - if ei_task_info.result == "ok": - message = _(ei_task_info.task.capitalize() + " completed!") - else: - message = _(ei_task_info.task.capitalize() + " failed!") - - if ei_task_info.result == "ok" and ei_task_info.task == "export": + self.load_webapps() + if result == "ok" and task == "export": # This dialog box gives users the option to open the containing directory. - dialog = Gtk.Dialog(message, ei_task_info.webAppLauncherSelf.window, None, (_("Open Containing Folder"), 10, Gtk.STOCK_OK, Gtk.ButtonsType.OK)) - dialog.get_content_area().add(Gtk.Label(label=_("Configuration has been exported successfully. This is the file location:")+"\n"+ei_task_info.path)) + title = _("Export completed!") + button_text = _("Open Containing Folder") + dialog = Gtk.Dialog(title, self.window, None, (button_text, 10, Gtk.STOCK_OK, Gtk.ButtonsType.OK)) + dialog.get_content_area().add(Gtk.Label(label=_("WebApps have been exported successfully."))) dialog.show_all() result = dialog.run() if result == 10: # Open Containing Folder - os.system("xdg-open " + os.path.dirname(ei_task_info.path)) + os.system("xdg-open " + os.path.dirname(path)) else: + if result == "ok" and task == "import": + message = _("Import completed!") + elif result != "ok" and task == "import": + message = _("Import failed!") + elif result != "ok" and task == "export": + message = _("Export failed!") + dialog = Gtk.MessageDialog(text=message, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK) dialog.run() - dialog.destroy() - try: - ei_task_info.window.destroy() - except: - # User closed the window manually - pass - if __name__ == "__main__": application = MyApplication("org.x.webapp-manager", Gio.ApplicationFlags.FLAGS_NONE) diff --git a/usr/share/webapp-manager/ei_tool.ui b/usr/share/webapp-manager/ei_tool.ui deleted file mode 100644 index a2e12df..0000000 --- a/usr/share/webapp-manager/ei_tool.ui +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - True - False - True - 500 - 100 - True - True - - - True - False - 10 - 10 - 10 - 10 - vertical - - - True - False - start - Choose a location - - - False - True - 0 - - - - - True - False - - - True - True - center - 15 - 15 - True - False - - - True - True - 0 - - - - - gtk-open - True - True - True - True - - - False - True - 1 - - - - - False - True - 1 - - - - - True - False - 20 - 20 - - - False - True - 2 - - - - - Start - True - True - True - - - False - True - 5 - - - - - True - False - 10 - 20 - True - - - False - True - 6 - - - - - - diff --git a/webapp-manager.pot b/webapp-manager.pot index 679fb64..806da78 100644 --- a/webapp-manager.pot +++ b/webapp-manager.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-21 10:58+0200\n" +"POT-Creation-Date: 2025-06-07 17:19+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,119 +17,161 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: usr/lib/webapp-manager/common.py:208 usr/lib/webapp-manager/common.py:319 +#: usr/lib/webapp-manager/common.py:264 usr/lib/webapp-manager/common.py:431 msgid "Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:73 -#: usr/lib/webapp-manager/webapp-manager.py:231 -#: usr/lib/webapp-manager/webapp-manager.py:238 generate_desktop_files:25 +#: usr/lib/webapp-manager/webapp-manager.py:79 +#: usr/lib/webapp-manager/webapp-manager.py:248 +#: usr/lib/webapp-manager/webapp-manager.py:255 generate_desktop_files:25 #: generate_desktop_files:39 usr/share/webapp-manager/webapp-manager.ui.h:1 #: usr/share/webapp-manager/shortcuts.ui.h:1 msgid "Web Apps" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:129 +#: usr/lib/webapp-manager/webapp-manager.py:132 +#: usr/share/webapp-manager/shortcuts.ui.h:8 +msgid "Export" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:139 +#: usr/share/webapp-manager/shortcuts.ui.h:9 +msgid "Import" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:147 msgid "Keyboard Shortcuts" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:136 -#: usr/lib/webapp-manager/webapp-manager.py:237 -#: usr/share/webapp-manager/shortcuts.ui.h:8 +#: usr/lib/webapp-manager/webapp-manager.py:154 +#: usr/lib/webapp-manager/webapp-manager.py:254 +#: usr/share/webapp-manager/shortcuts.ui.h:10 msgid "About" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:141 -#: usr/share/webapp-manager/shortcuts.ui.h:10 +#: usr/lib/webapp-manager/webapp-manager.py:159 +#: usr/share/webapp-manager/shortcuts.ui.h:12 msgid "Quit" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:156 +#: usr/lib/webapp-manager/webapp-manager.py:174 msgid "Icon" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:160 +#: usr/lib/webapp-manager/webapp-manager.py:178 msgid "Name" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:165 +#: usr/lib/webapp-manager/webapp-manager.py:183 msgid "Browser" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:179 -msgid "Internet" +#: usr/lib/webapp-manager/webapp-manager.py:197 generate_desktop_files:48 +msgid "Web" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:180 generate_desktop_files:48 -msgid "Web" +#: usr/lib/webapp-manager/webapp-manager.py:198 +msgid "Internet" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:181 +#: usr/lib/webapp-manager/webapp-manager.py:199 msgid "Accessories" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:182 +#: usr/lib/webapp-manager/webapp-manager.py:200 msgid "Games" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:183 +#: usr/lib/webapp-manager/webapp-manager.py:201 msgid "Graphics" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:184 +#: usr/lib/webapp-manager/webapp-manager.py:202 msgid "Office" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:185 +#: usr/lib/webapp-manager/webapp-manager.py:203 msgid "Sound & Video" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:186 +#: usr/lib/webapp-manager/webapp-manager.py:204 msgid "Programming" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:187 +#: usr/lib/webapp-manager/webapp-manager.py:205 msgid "Education" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:209 +#: usr/lib/webapp-manager/webapp-manager.py:227 msgid "No supported browsers were detected." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:239 -#: usr/lib/webapp-manager/webapp-manager.py:528 generate_desktop_files:25 +#: usr/lib/webapp-manager/webapp-manager.py:256 +#: usr/lib/webapp-manager/webapp-manager.py:555 generate_desktop_files:25 #: generate_desktop_files:39 msgid "Run websites as if they were apps" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:292 +#: usr/lib/webapp-manager/webapp-manager.py:307 #, python-format msgid "Delete '%s'" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:293 +#: usr/lib/webapp-manager/webapp-manager.py:308 #, python-format msgid "Are you sure you want to delete '%s'?" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:294 +#: usr/lib/webapp-manager/webapp-manager.py:309 msgid "This Web App will be permanently lost." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:347 -#: usr/lib/webapp-manager/webapp-manager.py:379 -#: usr/lib/webapp-manager/webapp-manager.py:435 +#: usr/lib/webapp-manager/webapp-manager.py:363 +#: usr/lib/webapp-manager/webapp-manager.py:405 +#: usr/lib/webapp-manager/webapp-manager.py:461 msgid "Add a New Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:369 +#: usr/lib/webapp-manager/webapp-manager.py:395 msgid "Edit Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:411 +#: usr/lib/webapp-manager/webapp-manager.py:437 msgid "Choose an icon" msgstr "" +#: usr/lib/webapp-manager/webapp-manager.py:562 +msgid "Export WebApps - Select file location" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:566 +msgid "Import WebApps - Select the archive" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:589 +msgid "Export completed!" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:590 +msgid "Open Containing Folder" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:592 +msgid "WebApps have been exported successfully." +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:600 +msgid "Import completed!" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:602 +msgid "Import failed!" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:604 +msgid "Export failed!" +msgstr "" + #: usr/share/webapp-manager/webapp-manager.ui.h:2 msgid "Manage your Web Apps" msgstr "" @@ -227,6 +269,6 @@ msgstr "" msgid "Go Back" msgstr "" -#: usr/share/webapp-manager/shortcuts.ui.h:9 +#: usr/share/webapp-manager/shortcuts.ui.h:11 msgid "Shortcuts" msgstr "" From 1f1684555b78b1ea00569bc40a635ce7ffa0ba8b Mon Sep 17 00:00:00 2001 From: JW-CodeArt Date: Sat, 7 Jun 2025 18:43:09 +0200 Subject: [PATCH 4/6] feature "import/export" (4th iteration) - bug fix: application crash It now uses GLib.idle_add() to prevent the "Segmentation fault (core dumped)" error in GTK3 - try catch in update_imported_desktop() --- usr/lib/webapp-manager/common.py | 75 +++++++++++++----------- usr/lib/webapp-manager/webapp-manager.py | 2 +- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/usr/lib/webapp-manager/common.py b/usr/lib/webapp-manager/common.py index 75bac73..b49be36 100644 --- a/usr/lib/webapp-manager/common.py +++ b/usr/lib/webapp-manager/common.py @@ -50,8 +50,8 @@ def wrapper(*args): _ = gettext.gettext # Constants -ICE_DIR = os.path.expanduser("~/.local/share/ice") -APPS_DIR = os.path.expanduser("~/.local/share/applications") +ICE_DIR = os.path.expanduser("/home/jannik/Programmieren/Python/LinuxMint/Local_Files/ice") +APPS_DIR = os.path.expanduser("/home/jannik/Programmieren/Python/LinuxMint/Local_Files/applications") PROFILES_DIR = os.path.join(ICE_DIR, "profiles") FIREFOX_PROFILES_DIR = os.path.join(ICE_DIR, "firefox") FIREFOX_FLATPAK_PROFILES_DIR = os.path.expanduser("~/.var/app/org.mozilla.firefox/data/ice/firefox") @@ -567,7 +567,7 @@ def export_webapps(callback, path): print(e) result = "error" - callback(result, "export", path) + GLib.idle_add(callback, result, "export", path) @_async def import_webapps(callback, path): @@ -582,12 +582,15 @@ def import_webapps(callback, path): if file.startswith("applications/"): # Rewrite the "Exec" section. It will apply the new paths and will search for browsers path = os.path.join(base_dir, file) - update_imported_desktop(path) + result = update_imported_desktop(path) + if result == "error": + tar.close() + break except Exception as e: print(e) result = "error" - callback(result, "import", path) + GLib.idle_add(callback, result, "import", path) def get_all_desktop_files(): @@ -607,35 +610,39 @@ def get_codename(path): return codename def update_imported_desktop(path): - webapp = WebAppLauncher(path, get_codename(path)) - if "/" in webapp.icon: - # Update Icon Path - iconpath = os.path.join(ICONS_DIR, os.path.basename(webapp.icon)) - else: - iconpath = webapp.icon - - # Check if the browser is installed - browsers = WebAppManager.get_supported_browsers() - configured_browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) - if os.path.exists(configured_browser.test_path) == False: - # If the browser is not installed, search another browser. - # 1. Sort browsers by same browser type - # 2. Sort the browsers by similarity of the name of the missing browser - similar_browsers = browsers - similar_browsers.sort(key=lambda browser: ( - browser.browser_type == configured_browser.browser_type, - configured_browser.name.split(" ")[0].lower() not in browser.name.lower() - )) - configured_browser = None - for browser in similar_browsers: - if os.path.exists(browser.test_path): - configured_browser = browser - break - - print(webapp.web_browser, "-Browser not installed") - - WebAppManager.edit_webapp(WebAppManager, path, webapp.name, configured_browser, webapp.url, iconpath, webapp.category, - webapp.custom_parameters, webapp.codename, webapp.isolate_profile, webapp.navbar, webapp.privatewindow) + try: + webapp = WebAppLauncher(path, get_codename(path)) + if "/" in webapp.icon: + # Update Icon Path + iconpath = os.path.join(ICONS_DIR, os.path.basename(webapp.icon)) + else: + iconpath = webapp.icon + + # Check if the browser is installed + browsers = WebAppManager.get_supported_browsers() + configured_browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) + if os.path.exists(configured_browser.test_path) == False: + # If the browser is not installed, search another browser. + # 1. Sort browsers by same browser type + # 2. Sort the browsers by similarity of the name of the missing browser + similar_browsers = browsers + similar_browsers.sort(key=lambda browser: ( + browser.browser_type == configured_browser.browser_type, + configured_browser.name.split(" ")[0].lower() not in browser.name.lower() + )) + configured_browser = None + for browser in similar_browsers: + if os.path.exists(browser.test_path): + configured_browser = browser + break + + print(webapp.web_browser, "-Browser not installed") + + WebAppManager.edit_webapp(WebAppManager, path, webapp.name, configured_browser, webapp.url, iconpath, webapp.category, + webapp.custom_parameters, webapp.codename, webapp.isolate_profile, webapp.navbar, webapp.privatewindow) + return "ok" + except: + return "error" if __name__ == "__main__": download_favicon(sys.argv[1]) diff --git a/usr/lib/webapp-manager/webapp-manager.py b/usr/lib/webapp-manager/webapp-manager.py index b3b305d..8ef4326 100755 --- a/usr/lib/webapp-manager/webapp-manager.py +++ b/usr/lib/webapp-manager/webapp-manager.py @@ -588,7 +588,7 @@ def show_ei_result(self, result, task, path): # This dialog box gives users the option to open the containing directory. title = _("Export completed!") button_text = _("Open Containing Folder") - dialog = Gtk.Dialog(title, self.window, None, (button_text, 10, Gtk.STOCK_OK, Gtk.ButtonsType.OK)) + dialog = Gtk.Dialog(title, self.window, None, (button_text, 10, Gtk.STOCK_OK, Gtk.ResponseType.OK)) dialog.get_content_area().add(Gtk.Label(label=_("WebApps have been exported successfully."))) dialog.show_all() result = dialog.run() From 712c4cf6942015d924392ca5ff5a24a114a7be81 Mon Sep 17 00:00:00 2001 From: JW-CodeArt Date: Sat, 7 Jun 2025 19:12:15 +0200 Subject: [PATCH 5/6] feature "import/export" (5th iteration) - cleanup --- usr/lib/webapp-manager/common.py | 4 +- usr/lib/webapp-manager/webapp-manager.py | 1 - webapp-manager.pot | 82 ++++++++++++------------ 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/usr/lib/webapp-manager/common.py b/usr/lib/webapp-manager/common.py index b49be36..e2f7603 100644 --- a/usr/lib/webapp-manager/common.py +++ b/usr/lib/webapp-manager/common.py @@ -50,8 +50,8 @@ def wrapper(*args): _ = gettext.gettext # Constants -ICE_DIR = os.path.expanduser("/home/jannik/Programmieren/Python/LinuxMint/Local_Files/ice") -APPS_DIR = os.path.expanduser("/home/jannik/Programmieren/Python/LinuxMint/Local_Files/applications") +ICE_DIR = os.path.expanduser("~/.local/share/ice") +APPS_DIR = os.path.expanduser("~/.local/share/applications") PROFILES_DIR = os.path.join(ICE_DIR, "profiles") FIREFOX_PROFILES_DIR = os.path.join(ICE_DIR, "firefox") FIREFOX_FLATPAK_PROFILES_DIR = os.path.expanduser("~/.var/app/org.mozilla.firefox/data/ice/firefox") diff --git a/usr/lib/webapp-manager/webapp-manager.py b/usr/lib/webapp-manager/webapp-manager.py index 8ef4326..02a094a 100755 --- a/usr/lib/webapp-manager/webapp-manager.py +++ b/usr/lib/webapp-manager/webapp-manager.py @@ -7,7 +7,6 @@ import shutil import subprocess import warnings -import threading # 2. Related third party imports. import gi diff --git a/webapp-manager.pot b/webapp-manager.pot index 806da78..5e3530f 100644 --- a/webapp-manager.pot +++ b/webapp-manager.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-07 17:19+0200\n" +"POT-Creation-Date: 2025-06-07 19:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,154 +21,154 @@ msgstr "" msgid "Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:79 -#: usr/lib/webapp-manager/webapp-manager.py:248 -#: usr/lib/webapp-manager/webapp-manager.py:255 generate_desktop_files:25 +#: usr/lib/webapp-manager/webapp-manager.py:78 +#: usr/lib/webapp-manager/webapp-manager.py:247 +#: usr/lib/webapp-manager/webapp-manager.py:254 generate_desktop_files:25 #: generate_desktop_files:39 usr/share/webapp-manager/webapp-manager.ui.h:1 #: usr/share/webapp-manager/shortcuts.ui.h:1 msgid "Web Apps" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:132 +#: usr/lib/webapp-manager/webapp-manager.py:131 #: usr/share/webapp-manager/shortcuts.ui.h:8 msgid "Export" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:139 +#: usr/lib/webapp-manager/webapp-manager.py:138 #: usr/share/webapp-manager/shortcuts.ui.h:9 msgid "Import" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:147 +#: usr/lib/webapp-manager/webapp-manager.py:146 msgid "Keyboard Shortcuts" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:154 -#: usr/lib/webapp-manager/webapp-manager.py:254 +#: usr/lib/webapp-manager/webapp-manager.py:153 +#: usr/lib/webapp-manager/webapp-manager.py:253 #: usr/share/webapp-manager/shortcuts.ui.h:10 msgid "About" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:159 +#: usr/lib/webapp-manager/webapp-manager.py:158 #: usr/share/webapp-manager/shortcuts.ui.h:12 msgid "Quit" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:174 +#: usr/lib/webapp-manager/webapp-manager.py:173 msgid "Icon" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:178 +#: usr/lib/webapp-manager/webapp-manager.py:177 msgid "Name" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:183 +#: usr/lib/webapp-manager/webapp-manager.py:182 msgid "Browser" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:197 generate_desktop_files:48 +#: usr/lib/webapp-manager/webapp-manager.py:196 generate_desktop_files:48 msgid "Web" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:198 +#: usr/lib/webapp-manager/webapp-manager.py:197 msgid "Internet" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:199 +#: usr/lib/webapp-manager/webapp-manager.py:198 msgid "Accessories" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:200 +#: usr/lib/webapp-manager/webapp-manager.py:199 msgid "Games" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:201 +#: usr/lib/webapp-manager/webapp-manager.py:200 msgid "Graphics" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:202 +#: usr/lib/webapp-manager/webapp-manager.py:201 msgid "Office" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:203 +#: usr/lib/webapp-manager/webapp-manager.py:202 msgid "Sound & Video" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:204 +#: usr/lib/webapp-manager/webapp-manager.py:203 msgid "Programming" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:205 +#: usr/lib/webapp-manager/webapp-manager.py:204 msgid "Education" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:227 +#: usr/lib/webapp-manager/webapp-manager.py:226 msgid "No supported browsers were detected." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:256 -#: usr/lib/webapp-manager/webapp-manager.py:555 generate_desktop_files:25 +#: usr/lib/webapp-manager/webapp-manager.py:255 +#: usr/lib/webapp-manager/webapp-manager.py:554 generate_desktop_files:25 #: generate_desktop_files:39 msgid "Run websites as if they were apps" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:307 +#: usr/lib/webapp-manager/webapp-manager.py:306 #, python-format msgid "Delete '%s'" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:308 +#: usr/lib/webapp-manager/webapp-manager.py:307 #, python-format msgid "Are you sure you want to delete '%s'?" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:309 +#: usr/lib/webapp-manager/webapp-manager.py:308 msgid "This Web App will be permanently lost." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:363 -#: usr/lib/webapp-manager/webapp-manager.py:405 -#: usr/lib/webapp-manager/webapp-manager.py:461 +#: usr/lib/webapp-manager/webapp-manager.py:362 +#: usr/lib/webapp-manager/webapp-manager.py:404 +#: usr/lib/webapp-manager/webapp-manager.py:460 msgid "Add a New Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:395 +#: usr/lib/webapp-manager/webapp-manager.py:394 msgid "Edit Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:437 +#: usr/lib/webapp-manager/webapp-manager.py:436 msgid "Choose an icon" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:562 +#: usr/lib/webapp-manager/webapp-manager.py:561 msgid "Export WebApps - Select file location" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:566 +#: usr/lib/webapp-manager/webapp-manager.py:565 msgid "Import WebApps - Select the archive" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:589 +#: usr/lib/webapp-manager/webapp-manager.py:588 msgid "Export completed!" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:590 +#: usr/lib/webapp-manager/webapp-manager.py:589 msgid "Open Containing Folder" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:592 +#: usr/lib/webapp-manager/webapp-manager.py:591 msgid "WebApps have been exported successfully." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:600 +#: usr/lib/webapp-manager/webapp-manager.py:599 msgid "Import completed!" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:602 +#: usr/lib/webapp-manager/webapp-manager.py:601 msgid "Import failed!" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:604 +#: usr/lib/webapp-manager/webapp-manager.py:603 msgid "Export failed!" msgstr "" From 8f38735e9d1872cbbd3371be257cdfbf8a74bf24 Mon Sep 17 00:00:00 2001 From: JW-CodeArt Date: Fri, 13 Jun 2025 22:44:30 +0200 Subject: [PATCH 6/6] merge conflict resolved --- webapp-manager.pot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp-manager.pot b/webapp-manager.pot index 5e3530f..875679d 100644 --- a/webapp-manager.pot +++ b/webapp-manager.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-07 19:10+0200\n" +"POT-Creation-Date: 2025-06-13 22:42+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: usr/lib/webapp-manager/common.py:264 usr/lib/webapp-manager/common.py:431 +#: usr/lib/webapp-manager/common.py:268 usr/lib/webapp-manager/common.py:437 msgid "Web App" msgstr ""