mirror of
https://github.com/morgan9e/warehouse
synced 2026-04-14 00:04:08 +09:00
Implement abilty to create snapshots of flatpaks
This commit is contained in:
2
src/clock-alt-symbolic.svg
Normal file
2
src/clock-alt-symbolic.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><path d="m 8 0 c -4.40625 0 -8 3.59375 -8 8 s 3.59375 8 8 8 s 8 -3.59375 8 -8 s -3.59375 -8 -8 -8 z m 0 2 c 3.324219 0 6 2.675781 6 6 s -2.675781 6 -6 6 s -6 -2.675781 -6 -6 s 2.675781 -6 6 -6 z m 0 0" fill="#222222"/><path d="m 4.929688 4.953125 c -0.128907 0.003906 -0.257813 0.058594 -0.351563 0.152344 c -0.191406 0.195312 -0.1875 0.511719 0.007813 0.707031 l 3.113281 3.042969 c 0.105469 0.097656 0.246093 0.144531 0.386719 0.128906 h 2.914062 c 0.277344 0 0.5 -0.222656 0.5 -0.5 c 0 -0.273437 -0.222656 -0.5 -0.5 -0.5 h -2.761719 l -2.953125 -2.886719 c -0.09375 -0.09375 -0.222656 -0.144531 -0.355468 -0.144531 z m 0 0" fill="#222222"/><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -600 -380)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -600 -380)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -600 -380)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -15,11 +15,13 @@ class myUtils:
|
||||
|
||||
def trashFolder(self, path):
|
||||
if not os.path.exists(path):
|
||||
print("error in common.trashFolder: path does not exists. path =", path)
|
||||
return 1
|
||||
try:
|
||||
subprocess.run(["flatpak-spawn", "--host", "gio", "trash", path], capture_output=True, check=True, env=self.new_env)
|
||||
subprocess.run(["gio", "trash", path], capture_output=False, check=True, env=self.new_env)
|
||||
return 0
|
||||
except subprocess.CalledProcessError:
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("error in common.trashFolder: CalledProcessError:", e)
|
||||
return 2
|
||||
|
||||
def getSizeWithFormat(self, path):
|
||||
|
||||
@@ -17,37 +17,37 @@ template RemotesWindow : Adw.Window {
|
||||
}
|
||||
content:
|
||||
Adw.ToastOverlay toast_overlay {
|
||||
Stack stack {
|
||||
Overlay main_overlay {
|
||||
[overlay]
|
||||
ProgressBar progress_bar {
|
||||
visible: false;
|
||||
pulse-step: 0.7;
|
||||
can-target: false;
|
||||
styles ["osd"]
|
||||
}
|
||||
ScrolledWindow scroll {
|
||||
vexpand: true;
|
||||
Adw.Clamp{
|
||||
ListBox remotes_list {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
styles["boxed-list"]
|
||||
Stack stack {
|
||||
Overlay main_overlay {
|
||||
[overlay]
|
||||
ProgressBar progress_bar {
|
||||
visible: false;
|
||||
pulse-step: 0.7;
|
||||
can-target: false;
|
||||
styles ["osd"]
|
||||
}
|
||||
ScrolledWindow scroll {
|
||||
vexpand: true;
|
||||
Adw.Clamp{
|
||||
ListBox remotes_list {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
styles["boxed-list"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Adw.StatusPage no_remotes {
|
||||
icon-name: "error-symbolic";
|
||||
title: _("No Remotes");
|
||||
description: _("Warehouse cannot see the list of remotes or the system has no remotes added");
|
||||
}
|
||||
}
|
||||
Adw.StatusPage no_remotes {
|
||||
icon-name: "error-symbolic";
|
||||
title: _("No Remotes");
|
||||
description: _("Warehouse cannot see the list of remotes or the system has no remotes added");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,43 +3,58 @@ using Adw 1;
|
||||
|
||||
template SnapshotsWindow : Adw.Window {
|
||||
default-width: 500;
|
||||
default-height: 450;
|
||||
default-height: 455;
|
||||
modal: true;
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
HeaderBar header_bar {
|
||||
[start]
|
||||
Button new_button {
|
||||
Button new_snapshot {
|
||||
Adw.ButtonContent {
|
||||
label: _("New Snapshot");
|
||||
icon-name: "plus-large-symbolic";
|
||||
}
|
||||
}
|
||||
[end]
|
||||
Button oepn_folder_button {
|
||||
icon-name: "document-open-symbolic";
|
||||
tooltip-text: _("Open Snapshots Folder");
|
||||
}
|
||||
}
|
||||
content:
|
||||
Adw.ToastOverlay toast_overlay {
|
||||
Stack main_stack {
|
||||
Overlay main_overlay {
|
||||
[overlay]
|
||||
ProgressBar progress_bar {
|
||||
pulse-step: 0.7;
|
||||
can-target: false;
|
||||
styles["osd"]
|
||||
}
|
||||
|
||||
Adw.PreferencesPage outerbox {
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
Adw.SwitchRow mask_row {
|
||||
title: _("Disable Updates");
|
||||
active: true;
|
||||
Overlay main_overlay {
|
||||
[overlay]
|
||||
ProgressBar progress_bar {
|
||||
pulse-step: 0.7;
|
||||
can-target: false;
|
||||
visible: false;
|
||||
styles["osd"]
|
||||
}
|
||||
Stack main_stack {
|
||||
ScrolledWindow outerbox {
|
||||
Adw.Clamp {
|
||||
ListBox snapshots_group {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
styles["boxed-list"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Adw.StatusPage no_snapshots {
|
||||
title: _("No Snapshots");
|
||||
description: _("Snapshots are backups of the app's user data. They can be reapplied at any time.");
|
||||
icon-name: "clock-alt-symbolic";
|
||||
|
||||
Adw.PreferencesGroup versions_group {
|
||||
title: _("Select a Release");
|
||||
description: _("This will uninstall the current release and install the chosen one instead. Note that downgrading can cause issues.");
|
||||
Button new_snapshot_pill {
|
||||
label: _("New Snapshot");
|
||||
halign: center;
|
||||
styles["pill", "suggested-action"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshots.ui")
|
||||
class SnapshotsWindow(Adw.Window):
|
||||
@@ -10,8 +11,158 @@ class SnapshotsWindow(Adw.Window):
|
||||
|
||||
new_env = dict( os.environ )
|
||||
new_env['LC_ALL'] = 'C'
|
||||
host_home = str(pathlib.Path.home())
|
||||
user_data_path = host_home + "/.var/app/"
|
||||
snapshots_path = host_home + "/.var/app/io.github.flattool.Warehouse/data/Snapshots/"
|
||||
|
||||
snapshots_group = Gtk.Template.Child()
|
||||
main_stack = Gtk.Template.Child()
|
||||
no_snapshots = Gtk.Template.Child()
|
||||
new_snapshot = Gtk.Template.Child()
|
||||
new_snapshot_pill = Gtk.Template.Child()
|
||||
oepn_folder_button = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
outerbox = Gtk.Template.Child()
|
||||
progress_bar = Gtk.Template.Child()
|
||||
should_pulse = False
|
||||
|
||||
def pulser(self):
|
||||
self.progress_bar.pulse()
|
||||
GLib.timeout_add(500, self.pulser)
|
||||
|
||||
def generateList(self):
|
||||
if not os.path.exists(self.snapshots_of_app_path):
|
||||
# Show no snapshots page if the folder is not there
|
||||
self.main_stack.set_visible_child(self.no_snapshots)
|
||||
self.oepn_folder_button.set_sensitive(False)
|
||||
return
|
||||
|
||||
snapshot_files = os.listdir(self.snapshots_of_app_path)
|
||||
to_trash = []
|
||||
|
||||
for i in range(len(snapshot_files)):
|
||||
if not snapshot_files[i].endswith(".tar.zst"):
|
||||
# Find all files that aren't snapshots
|
||||
to_trash.append(snapshot_files[i])
|
||||
|
||||
for i in range(len(to_trash)):
|
||||
# Delete all files that aren't snapshots
|
||||
a = self.my_utils.trashFolder(f"{self.snapshots_of_app_path}{to_trash[i]}")
|
||||
if a == 0:
|
||||
snapshot_files.remove(to_trash[i])
|
||||
|
||||
if len(snapshot_files) == 0:
|
||||
self.main_stack.set_visible_child(self.no_snapshots)
|
||||
return
|
||||
|
||||
for i in range(len(snapshot_files)):
|
||||
self.create_row(snapshot_files[i])
|
||||
|
||||
def create_row(self, file):
|
||||
size = self.my_utils.getSizeWithFormat(self.snapshots_of_app_path + file)
|
||||
split_file = file.removesuffix(".tar.zst").split("_")
|
||||
time = GLib.DateTime.new_from_unix_local(int(split_file[0])).format("%x %X")
|
||||
row = Adw.ActionRow(title=time, subtitle=size)
|
||||
|
||||
label = Gtk.Label(label=_("Version {}").format(split_file[1]), hexpand=True, wrap=True, justify=Gtk.Justification.RIGHT)
|
||||
row.add_suffix(label)
|
||||
|
||||
apply = Gtk.Button(icon_name="check-plain-symbolic", valign=Gtk.Align.CENTER)
|
||||
apply.add_css_class("flat")
|
||||
row.add_suffix(apply)
|
||||
|
||||
trash = Gtk.Button(icon_name="user-trash-symbolic", valign=Gtk.Align.CENTER)
|
||||
trash.connect("clicked", self.trash_snapshot, file, row)
|
||||
trash.add_css_class("flat")
|
||||
row.add_suffix(trash)
|
||||
self.snapshots_group.insert(row, 0)
|
||||
self.main_stack.set_visible_child(self.outerbox)
|
||||
self.oepn_folder_button.set_sensitive(True)
|
||||
|
||||
def trash_snapshot(self, button, file, row):
|
||||
def on_response(dialog, response, func):
|
||||
if response == "cancel":
|
||||
return
|
||||
a = self.my_utils.trashFolder(self.snapshots_of_app_path + file)
|
||||
if a == 0:
|
||||
self.snapshots_group.remove(row)
|
||||
if not self.snapshots_group.get_row_at_index(0):
|
||||
self.main_stack.set_visible_child(self.no_snapshots)
|
||||
self.my_utils.trashFolder(self.snapshots_of_app_path)
|
||||
self.oepn_folder_button.set_sensitive(False)
|
||||
else:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not trash snapshot")))
|
||||
|
||||
dialog = Adw.MessageDialog.new(self, _("Trash Snapshot?"), _("This snapshot and its contents will be sent to the trash."))
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("continue", _("Trash Snapshot"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", on_response, dialog.choose_finish)
|
||||
dialog.present()
|
||||
|
||||
def createSnapshot(self):
|
||||
epoch = int(time.time())
|
||||
|
||||
def thread():
|
||||
subprocess.run(
|
||||
['tar', 'caf', f"{self.snapshots_of_app_path}{epoch}_{self.app_version}.tar.zst", "-C", f"{self.app_user_data}", "."],
|
||||
check=True, env=self.new_env
|
||||
)
|
||||
|
||||
# `tar -tf filepath` to see the contents of a tar file
|
||||
|
||||
def callback():
|
||||
self.create_row(f"{epoch}_{self.app_version}.tar.zst")
|
||||
self.new_snapshot.set_sensitive(True)
|
||||
self.new_snapshot_pill.set_sensitive(True)
|
||||
self.progress_bar.set_visible(False)
|
||||
|
||||
if not os.path.exists(self.snapshots_of_app_path):
|
||||
file = Gio.File.new_for_path(self.snapshots_of_app_path)
|
||||
file.make_directory()
|
||||
|
||||
self.new_snapshot.set_sensitive(False)
|
||||
self.new_snapshot_pill.set_sensitive(False)
|
||||
self.progress_bar.set_visible(True)
|
||||
|
||||
task = Gio.Task.new(None, None, lambda *_: callback())
|
||||
task.run_in_thread(lambda *_: thread())
|
||||
|
||||
def open_button_handler(self, widget, path):
|
||||
try:
|
||||
Gio.AppInfo.launch_default_for_uri(f"file://{path}", None)
|
||||
except GLib.GError:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
|
||||
|
||||
def __init__(self, parent_window, flatpak_row, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.present()
|
||||
# Variables
|
||||
self.my_utils = myUtils(self)
|
||||
self.app_name = flatpak_row[0]
|
||||
self.app_id = flatpak_row[2]
|
||||
self.app_version = flatpak_row[3]
|
||||
self.app_ref = flatpak_row[8]
|
||||
self.snapshots_of_app_path = self.snapshots_path + self.app_id + "/"
|
||||
self.app_user_data = self.user_data_path + self.app_id + "/"
|
||||
|
||||
if self.app_version == "" or self.app_version == "-" or self.app_version == None:
|
||||
self.app_version = 0.0
|
||||
|
||||
if not os.path.exists(self.snapshots_path):
|
||||
# Create snapshots folder if none exists
|
||||
file = Gio.File.new_for_path(self.snapshots_path)
|
||||
file.make_directory()
|
||||
|
||||
# Calls
|
||||
self.generateList()
|
||||
self.oepn_folder_button.connect("clicked", self.open_button_handler, self.snapshots_of_app_path)
|
||||
self.new_snapshot.connect("clicked", lambda *_: self.createSnapshot())
|
||||
self.new_snapshot_pill.connect("clicked", lambda *_: self.createSnapshot())
|
||||
self.pulser()
|
||||
|
||||
# Window stuffs
|
||||
self.set_title(_("{} Snapshots").format(self.app_name))
|
||||
self.set_transient_for(parent_window)
|
||||
self.set_size_request(0, 230)
|
||||
@@ -25,5 +25,6 @@
|
||||
<file preprocess="xml-stripblanks">funnel-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">right-large-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">view-more-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">clock-alt-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
@@ -381,7 +381,7 @@ class WarehouseWindow(Adw.ApplicationWindow):
|
||||
open_data_item.set_attribute_value("hidden-when", GLib.Variant.new_string("action-disabled"))
|
||||
data_menu_model.append_item(open_data_item)
|
||||
|
||||
self.create_action(("snapshot" + str(index)), lambda *_, row=self.flatpak_rows[index]: SnapshotsWindow(self, row))
|
||||
self.create_action(("snapshot" + str(index)), lambda *_, row=self.flatpak_rows[index][6]: SnapshotsWindow(self, row).present())
|
||||
snapshot_item = Gio.MenuItem.new(_("Manage Snapshots"), f"win.snapshot{index}")
|
||||
snapshot_item.set_attribute_value("hidden-when", GLib.Variant.new_string("action-dsiabled"))
|
||||
data_menu_model.append_item(snapshot_item)
|
||||
|
||||
Reference in New Issue
Block a user