Skip to content

Export and import feature for WebApps #348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
101 changes: 97 additions & 4 deletions usr/lib/webapp-manager/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -159,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)
Expand Down Expand Up @@ -307,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:
Expand Down Expand Up @@ -551,5 +552,97 @@ def download_favicon(url):
images = sorted(images, key = lambda x: x[1].height, reverse=True)
return images

@_async
def export_webapps(callback, path):
# The background export process
try:
desktop_files = get_all_desktop_files()
# Write the .tar.gz file
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)
result = "error"

GLib.idle_add(callback, result, "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:
tar.extract(file, base_dir)
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)
result = update_imported_desktop(path)
if result == "error":
tar.close()
break
except Exception as e:
print(e)
result = "error"

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


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_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
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])
81 changes: 74 additions & 7 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 All @@ -37,6 +38,9 @@
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"

class MyApplication(Gtk.Application):
# Main initialization routine
Expand Down Expand Up @@ -67,10 +71,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")
Expand Down Expand Up @@ -124,6 +127,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.ei_select_location("export"))
key, mod = Gtk.accelerator_parse("<Control><Shift>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.ei_select_location("import"))
key, mod = Gtk.accelerator_parse("<Control><Shift>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"))
Expand Down Expand Up @@ -223,10 +240,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()
Expand Down Expand Up @@ -537,8 +553,59 @@ def load_webapps(self):
self.stack.set_visible_child_name("main_page")
self.headerbar.set_subtitle(_("Run websites as if they were apps"))

# "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)
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)
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 != "":
if task == "export":
path += ".tar.gz"
export_webapps(self.show_ei_result, path)
else:
import_webapps(self.show_ei_result, path)
dialog.destroy()

def show_ei_result(self, result, task, path):
# Displays a success or failure message when the 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.")))
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 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()

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

application.run()
14 changes: 14 additions & 0 deletions usr/share/webapp-manager/shortcuts.ui
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@
<property name="title" translatable="yes">Go Back</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;E</property>
<property name="title" translatable="yes">Export</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;ctrl&gt;&lt;shift&gt;I</property>
<property name="title" translatable="yes">Import</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
Expand Down
Loading