Skip to content
Merged
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
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project('savedesktop',
version: '3.8',
version: '3.8.1',
meson_version: '>= 1.0.0',
default_options: [ 'warning_level=2', 'werror=false', ],
)
Expand Down
2 changes: 1 addition & 1 deletion po/savedesktop.pot
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ msgstr ""

#: src/gui/window.py
msgid ""
"<big><b>{}</b></big>\nYou can log out of the system for the changes to take effect, or go back to the previous page and log out later.\n"
"<big><b>{}</b></big>\nYou can log out of the system for the changes to take effect, or go back to the previous page and log out later.\n\n<i>If your archive contains Flatpak apps, they will start installing after the next login.</i>"
msgstr ""

#: src/gui/window.py
Expand Down
29 changes: 21 additions & 8 deletions src/core/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ def start_saving(self):
else:
self._create_archive()

print("Moving the configuration archive to the user-defined directory")
shutil.copyfile('cfg.sd.zip', self.path_with_filename)

print("Configuration saved successfully.")
remove_temp_file()

Expand All @@ -65,7 +62,7 @@ def _copy_config_to_folder(self):

# Create a new ZIP archive with 7-Zip
def _create_archive(self):
cmd = ['7z', 'a', '-tzip', '-mx=3', '-x!*.zip', '-x!saving_status', 'cfg.sd.zip', '.']
cmd = ['7z', 'a', '-snL', '-mx=3', '-x!*.zip', '-x!saving_status', 'cfg.sd.zip', "."]
if settings["enable-encryption"] or os.path.exists(f"{CACHE}/pb"):
if password:
cmd.insert(4, "-mem=AES256")
Expand All @@ -80,6 +77,9 @@ def _create_archive(self):
else:
print("7z finished with warnings:", proc.stderr)

print("Moving the configuration archive to the user-defined directory")
shutil.copyfile('cfg.sd.zip', self.path_with_filename)

class Unpack:
def __init__(self, dir_path):
self.path_with_filename = dir_path
Expand Down Expand Up @@ -141,11 +141,24 @@ def _unpack_zip_archive(self):
raise ValueError(first_error or "Wrong password")
print("Checking password is completed.")

cmd = subprocess.run(
['7z', 'x', '-y', f'-p{password}', self.path_with_filename, f'-o{TEMP_CACHE}'],
capture_output=True, text=True, check=True
cmd = ['7z', 'x', '-y', '-snL']

if password:
cmd.append(f'-p{password}')

cmd.extend([
self.path_with_filename,
f'-o{TEMP_CACHE}'
])

proc = subprocess.run(
cmd,
capture_output=True,
text=True
)
print(cmd.stdout)

if proc.returncode >= 7:
raise OSError(proc.stderr)

def __handle_sync_error(self):
# If 7-Zip returns an error regarding an incorrect password and the {CACHE}/sync file
Expand Down
79 changes: 48 additions & 31 deletions src/core/de_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os, shutil, argparse
import os, shutil, argparse, subprocess
from gi.repository import GLib
from savedesktop.globals import *
from pathlib import Path

# Helping functions
def safe_copy(src: str, dst: str):
Expand All @@ -17,7 +18,7 @@ def safe_copy(src: str, dst: str):
def safe_copytree(src: str, dst: str, ignore=None):
if os.path.isdir(src):
try:
shutil.copytree(src, dst, dirs_exist_ok=True, ignore=ignore)
shutil.copytree(src, dst, dirs_exist_ok=True, symlinks=True, ignore=ignore)
print(f"[OK] Copied dir: {src} → {dst}")
except Exception as e:
print(f"[ERR] Dir copy failed {src} → {dst}: {e}")
Expand Down Expand Up @@ -45,6 +46,9 @@ def safe_copytree(src: str, dst: str, ignore=None):
("xdg-data", f"{home}/.local/share"),
]

# The ~/Desktop folder definition
desktop_dir = Path(GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP))

class Save:
def __init__(self):

Expand All @@ -71,24 +75,27 @@ def __init__(self):
safe_copytree(f"{home}/.local/share/backgrounds", "backgrounds")
safe_copytree(f"{home}/.local/share/wallpapers", "xdg-data/wallpapers")
if settings["save-icons"]:
safe_copytree(f"{home}/.icons", ".icons")
safe_copytree(f"{home}/.local/share/icons", "icons")
xdg_path = f"{home}/.local/share"
legacy_path = f"{home}"
if os.path.isdir(f"{xdg_path}/icons") and os.listdir(f"{xdg_path}/icons"):
subprocess.run(["tar", "-czf", "icon-themes.tgz", "-C", xdg_path, "icons"])
if os.path.isdir(f"{legacy_path}/.icons") and os.listdir(f"{legacy_path}/.icons"):
subprocess.run(["tar", "-czf", "icon-themes-legacy.tgz", "-C", legacy_path, ".icons"])
print("[OK] Saving icons")

# Desktop folder
if settings["save-desktop-folder"]:
desktop_dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP)
if desktop_dir:
safe_copytree(desktop_dir, "Desktop")
subprocess.run(["tar", "-czf", "desktop-folder.tgz", "-C", str(desktop_dir.parent), str(desktop_dir.name)])
safe_copytree(f"{home}/.local/share/gvfs-metadata", "gvfs-metadata")
print("[OK] Saving ~/Desktop folder")

# Flatpak apps and their data
if flatpak:
if settings["save-installed-flatpaks"]:
print("saving list of installed Flatpak apps")
os.system("ls /var/lib/flatpak/app/ | awk '{print \"flatpak install --system \" $1 \" -y\"}' > ./installed_flatpaks.sh")
os.system("ls ~/.local/share/flatpak/app | awk '{print \"flatpak install --user \" $1 \" -y\"}' > ./installed_user_flatpaks.sh")
print("[OK] Saving list of installed Flatpak apps")
if settings["save-flatpak-data"]:
print("saving user data of installed Flatpak apps")
self.save_flatpak_data()

# Environment specific
Expand Down Expand Up @@ -116,24 +123,15 @@ def save_flatpak_data(self):
gsettings = settings["disabled-flatpak-apps-data"]
black_list = gsettings # convert GSettings property to a list

os.makedirs(f"{CACHE}/workspace/app", exist_ok=True)
destdir = f"{CACHE}/workspace/app"

# copy Flatpak apps data
for item in os.listdir(f"{home}/.var/app"):
if item not in black_list and item != "cache":
source_path = os.path.join(f"{home}/.var/app", item)
destination_path = os.path.join(destdir, item)
if os.path.isdir(source_path):
try:
shutil.copytree(source_path, destination_path, ignore=shutil.ignore_patterns('cache'))
except Exception as e:
print(f"Error copying directory {source_path}: {e}")
else:
try:
shutil.copy2(source_path, destination_path)
except Exception as e:
print(f"Error copying file {source_path}: {e}")
cmd = ["tar", "-czf", "flatpak-apps-data.tgz", "--exclude=*/cache"]

for apps in black_list:
cmd.append(f"--exclude={apps}")

cmd.extend(["-C", f"{home}/.var", "app"])

subprocess.run(cmd)
print("[OK] Saving Flatpak apps' data")

class Import:
def __init__(self):
Expand All @@ -156,12 +154,11 @@ def __init__(self):
safe_copytree("gnome-shell", f"{home}/.local/share/gnome-shell")
safe_copytree("cinnamon", f"{home}/.local/share/cinnamon")
safe_copytree("plasma", f"{home}/.local/share/plasma")
safe_copytree("Desktop", GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP))
safe_copytree("gvfs-metadata", f"{home}/.local/share/gvfs-metadata")
safe_copytree("backgrounds", f"{home}/.local/share/backgrounds")
safe_copytree("wallpapers", f"{home}/.local/share/wallpapers")
safe_copytree("icons", f"{home}/.local/share/icons")
safe_copytree(".icons", f"{home}/.icons")
self.import_desktop_folder()
self.import_icons()

# Environment specific
if environment:
Expand All @@ -177,9 +174,29 @@ def __init__(self):
print(f"[WARN] Unknown DE: {environment_key}")

if flatpak:
if any(os.path.exists(path) for path in ["app", "installed_flatpaks.sh", "installed_user_flatpaks.sh"]):
if any(os.path.exists(path) for path in ["app", "flatpak-apps-data.tgz", "installed_flatpaks.sh", "installed_user_flatpaks.sh"]):
self.create_flatpak_autostart()

# Extract an archive with the Desktop folder
def import_desktop_folder(self):
if os.path.exists("desktop-folder.tgz"):
subprocess.run(["tar", "-xzvf", "desktop-folder.tgz", "-C", str(desktop_dir.parent)])
print("[OK] Extracting a Desktop archive")
else:
safe_copytree("Desktop", GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP))

# Extract an archive with icon themes
def import_icons(self):
if os.path.exists(f"icon-themes.tgz"):
subprocess.run(["tar", "-xzvf", "icon-themes.tgz", "-C", f"{home}/.local/share/"])
print("[OK] Extracting a XDG icons archive")
if os.path.exists(f"icon-themes-legacy.tgz"):
print(f"[OK] Extracting a legacy icons archive")
subprocess.run(["tar", "-xzvf", "icon-themes-legacy.tgz", "-C", f"{home}/"])
else:
safe_copytree("icons", f"{home}/.local/share/icons")
safe_copytree(".icons", f"{home}/.icons")

# Create an autostart file to install Flatpaks from a list after the next login
def create_flatpak_autostart(self):
os.system(f"cp /app/share/savedesktop/savedesktop/core/flatpaks_installer.py {CACHE}/workspace")
Expand Down
8 changes: 6 additions & 2 deletions src/core/flatpaks_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@
dest_dir = None

# If the destination directory variable is not 'None', continue in installing Flatpak apps
if not dest_dir == None:
if dest_dir:
# If the destination directory has a directory with installed Flatpak apps user data, install them
if os.path.exists(f"{dest_dir}/app"):
print("copying the Flatpak apps' user data to the ~/.var/app directory")
os.system(f"cp -au {dest_dir}/app/ ~/.var/")

# If the flatpak-apps-data.tgz archive exists, unpack it to the ~/.var/app directory
if os.path.exists(f"{dest_dir}/flatpak-apps-data.tgz"):
subprocess.run(["tar", "-xzvf", "flatpak-apps-data.tgz", "-C", f"{Path.home()}/.var"])

# If the Bash scripts for installing Flatpak apps to the system exist, install them
if os.path.exists(f"{dest_dir}/installed_flatpaks.sh") or os.path.exists(f"{dest_dir}/installed_user_flatpaks.sh"):
print("installing the Flatpak apps on the system")
Expand Down Expand Up @@ -78,4 +82,4 @@
os.remove(autostart_file)

# Remove the cache dir after finishing the operations
shutil.rmtree(f"{CACHE}/workspace")
shutil.rmtree(f"{CACHE_FLATPAK}/workspace")
38 changes: 24 additions & 14 deletions src/gui/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,18 @@ def __init__(self, *args, **kwargs):
self.headerbar.pack_end(child=self.menu_button)

# primary layout
self.scrolled = Gtk.ScrolledWindow()
self.scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scrolled.set_vexpand(True)
self.scrolled.set_hexpand(True)
self.toolbarview.set_content(self.scrolled)

self.headapp = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.headapp.set_valign(Gtk.Align.CENTER)
self.headapp.set_halign(Gtk.Align.CENTER)
self.toolbarview.set_content(self.headapp)
self.headapp.set_valign(Gtk.Align.FILL)
self.headapp.set_halign(Gtk.Align.FILL)
self.headapp.set_vexpand(True)
self.headapp.set_hexpand(True)
self.scrolled.set_child(self.headapp)

# A view container for the menu switcher
self.stack = Adw.ViewStack(vexpand=True)
Expand Down Expand Up @@ -512,7 +520,7 @@ def _checkDialog_closed(self, w, response):

# Save configuration
def save_config(self):
print("Saving the configuration is in progress…\nFull output will be available after finishing this operation.")
print("Saving the configuration is in progress…")
self.archive_mode = "--create"
self.archive_name = f"{self.folder}/{self.filename_text}"

Expand All @@ -535,15 +543,15 @@ def _set_status_desc_save(self):

# Import configuration
def import_config(self):
print("Importing the configuration is in progress…\nFull output will be available after finishing this operation.")
print("Importing the configuration is in progress…")
self.toast_overlay.dismiss_all()

self.archive_name = self.path_to_import
self.archive_mode = "--unpack"
self.status_title = _("<big><b>Importing configuration …</b></big>\nImporting configuration from:\n<i>{}</i>\n").split('</b>')[0].split('<b>')[-1]
self.status_desc = _("<big><b>Importing configuration …</b></big>\nImporting configuration from:\n<i>{}</i>\n").format(self.archive_name)
self.done_title = _("The configuration has been applied!")
self.done_desc = _("<big><b>{}</b></big>\nYou can log out of the system for the changes to take effect, or go back to the previous page and log out later.\n").format(_("The configuration has been applied!"))
self.done_desc = _("<big><b>{}</b></big>\nYou can log out of the system for the changes to take effect, or go back to the previous page and log out later.\n\n<i>If your archive contains Flatpak apps, they will start installing after the next login.</i>").format(_("The configuration has been applied!"))

self.please_wait()
import_thread = Thread(target=self._call_archive_command)
Expand All @@ -552,7 +560,7 @@ def import_config(self):
def _call_archive_command(self):
self.archive_proc = subprocess.Popen(
[sys.executable, "-m", "savedesktop.core.archive", self.archive_mode, self.archive_name],
stdout=subprocess.PIPE,
stdout=None,
stderr=subprocess.PIPE,
text=True,
env={**os.environ, "PYTHONPATH": f"{app_prefix}"}
Expand All @@ -576,10 +584,10 @@ def please_wait(self):

# Create box widget for this page
self.status_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.status_box.set_halign(Gtk.Align.CENTER)
self.status_box.set_valign(Gtk.Align.CENTER)
self.status_box.set_vexpand(True)
self.status_box.set_hexpand(True)
self.status_box.set_size_request(350, 100)
self.toolbarview.set_content(self.status_box)
self.scrolled.set_child(self.status_box)

# Set bold title
self.set_title(self.status_title)
Expand Down Expand Up @@ -684,7 +692,7 @@ def _back_to_main(self, w):
self.status_box.remove(widget)

def _set_default_widgets_state(self):
self.toolbarview.set_content(self.headapp)
self.scrolled.set_child(self.headapp)
self.headerbar.set_title_widget(self.switcher_title)
self.set_title("Save Desktop")
self.apply_handler = self.break_point.connect("apply", self.__on_break_point_apply)
Expand Down Expand Up @@ -741,6 +749,8 @@ def on_close(self, w):
# Remove this folder to cleanup unnecessary content created during saving,
# importing, or syncing the configuration only if these files are not present
def remove_cache(self):
files_to_check = [f"{CACHE}/pb", f"{CACHE}/sync", f"{CACHE}/workspace/flatpaks_installer.py"]
if not any(os.path.exists(path) for path in files_to_check):
shutil.rmtree(f"{CACHE}/workspace")
if os.path.exists(f"{CACHE}/workspace"):
files_to_check = [f"{CACHE}/pb", f"{CACHE}/sync", f"{CACHE}/workspace/flatpaks_installer.py"]
if not any(os.path.exists(path) for path in files_to_check):
shutil.rmtree(f"{CACHE}/workspace")