Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 126 additions & 4 deletions usr/lib/webapp-manager/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
import threading
import traceback
from typing import Optional
import tarfile
import time
from pathlib import Path

# 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.
Expand Down Expand Up @@ -159,7 +162,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)
Expand Down Expand Up @@ -307,8 +310,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:
Expand Down Expand Up @@ -551,5 +554,124 @@ def download_favicon(url):
images = sorted(images, key = lambda x: x[1].height, reverse=True)
return images

@_async
def export_webapps(callback):
# The background export process
try:
result = "ok"
filename = "web-apps-" + time.strftime(f"%Y-%m-%d", time.localtime())
suffix = len(list(Path.home().glob(f"*{filename}*")))
suffix = f"({suffix})".replace("(0)", "")
export_path = str(Path(f"~/{filename}{suffix}.tar.gz").expanduser())

desktop_files = prepare_export()
# Write the .tar.gz file
with tarfile.open(export_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/")
except Exception as e:
print(e)
result = "error"

GLib.idle_add(callback, result, "export", export_path)

@_async
def import_webapps(callback, path):
# The background import process
try:
result = "ok"
with tarfile.open(path, "r:gz") as tar:
files = tar.getnames()
base_dir = os.path.dirname(ICE_DIR)
for file in files:
try:
if not os.path.exists(os.path.join(base_dir, file)): # Skip existing files.
tar.extract(file, base_dir)
if file.startswith("applications/"):
# Update the .desktop file. Check if the browser is installed and rewrite the paths
path = os.path.join(base_dir, file)
if update_imported_desktop(path) == "error":
result = "error"
except Exception as e: # Keep the import process going even if there are problems extracting a file
print(e)
result = "error"
except Exception as e:
print(e)
result = "error"

GLib.idle_add(callback, result, "import")


def prepare_export():
# Search all web apps and desktop files.
files = []
supported_browsers = WebAppManager.get_supported_browsers()
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})
try:
# In older versions, some custom icons will not be automatically stored in the icons directory
webapp = WebAppLauncher(full_path, get_codename(full_path))
icon = webapp.icon
if "/" in icon and not ICONS_DIR in icon:
filename = "".join(filter(str.isalpha, webapp.name)) + os.path.splitext(icon)[1]
new_path = os.path.join(ICONS_DIR, filename)
shutil.copyfile(icon, new_path)
icon = new_path
browser = next((browser for browser in supported_browsers if browser.name == webapp.web_browser), None)
WebAppManager.edit_webapp(WebAppManager,full_path, webapp.name, browser, webapp.url, icon, webapp.category,
webapp.custom_parameters, webapp.codename, webapp.isolate_profile, webapp.navbar, webapp.privatewindow)
except:
# Skip custom icon
pass
return files


def get_codename(path):
filename = os.path.basename(path)
codename = filename.replace(".desktop", "").replace("WebApp-", "").replace("webapp-", "")
return codename

def update_imported_desktop(path):
try:
webapp = WebAppLauncher(path, get_codename(path))
if "/" in webapp.icon:
# update icon path, important when the username differs.
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
if configured_browser != None:
browser_type = configured_browser.browser_type
browser_name = configured_browser.name.split(" ")[0].lower()
else:
# Could not found the browser in the supported browser list. Search for an alternative.
browser_type = ""
browser_name = webapp.web_browser
similar_browsers.sort(key=lambda browser: (browser.browser_type == browser_type, browser_name 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")
# Apply the browser changes, the new icon path and create a profile if there is no existing one
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])
60 changes: 56 additions & 4 deletions usr/lib/webapp-manager/webapp-manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
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 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")

Expand Down Expand Up @@ -124,6 +125,16 @@ 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: export_webapps(self.show_result))
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", self.import_select_location)
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"))
Expand Down Expand Up @@ -315,9 +326,9 @@ def on_ok_button(self, widget):
privatewindow = self.privatewindow_switch.get_active()
icon = self.icon_chooser.get_icon()
custom_parameters = self.customparameters_entry.get_text()
if "/tmp" in icon:
# If the icon path is in /tmp, move it.
filename = "".join(filter(str.isalpha, name)) + ".png"
# Always copy custom icons in the icon directory (important when exporting web-apps)
if "/" in icon and not ICONS_DIR in icon:
filename = "".join(filter(str.isalpha, name)) + os.path.splitext(icon)[1]
new_path = os.path.join(ICONS_DIR, filename)
shutil.copyfile(icon, new_path)
icon = new_path
Expand Down Expand Up @@ -537,6 +548,47 @@ def load_webapps(self):
self.stack.set_visible_child_name("main_page")
self.headerbar.set_subtitle(_("Run websites as if they were apps"))

def import_select_location(self, widget):
# Open the file chooser dialog
buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
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")
dialog.add_filter(filter)
response = dialog.run()
if response == Gtk.ResponseType.OK:
path = dialog.get_filename()
if path != "":
import_webapps(self.show_result, path)
dialog.destroy()

def show_result(self, result, task, path=""):
# Displays a success or failure message when the import / export process is complete.
self.load_webapps()
if result == "ok" and task == "export":
# 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.ResponseType.OK))
dialog.get_content_area().add(Gtk.Label(label=_("WebApps have been exported successfully.")+f"\n\n{path}\n"))
dialog.show_all()
result = dialog.run()
if result == 10:
# Open Containing Folder
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 not completed due to an error")
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()

if __name__ == "__main__":
application = MyApplication("org.x.webapp-manager", Gio.ApplicationFlags.FLAGS_NONE)
Expand Down
Loading