Skip to content

Commit adfcef9

Browse files
authored
Release 4.0 (#510)
<ul> <li>New: Include custom folders in backups (#505)</li> <li>Redesigned Flatpak apps selection: shows app names (not IDs) and backs up selected apps together with their user data</li> <li>New: Sync selected configuration items across PCs to keep archives consistent</li> <li>Updated archive structure with full backward compatibility for older backups</li> <li>Updated translations</li> </ul> Release date: February 22, 2026
2 parents 7347120 + f507692 commit adfcef9

17 files changed

Lines changed: 564 additions & 239 deletions

data/io.github.vikdevelop.SaveDesktop.gschema.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353
<default>[]</default>
5454
<summary>Select, what Flatpak applications data will be saved</summary>
5555
</key>
56+
<key name="custom-dirs" type="as">
57+
<default>[]</default>
58+
<summary>Selected custom directories</summary>
59+
</key>
5660
<key name="periodic-saving" type="s">
5761
<choices>
5862
<choice value="Never"/>

data/io.github.vikdevelop.SaveDesktop.metainfo.xml.in

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@
6666
</screenshots>
6767

6868
<releases>
69+
<release version="4.0" date="2026-02-22">
70+
<url>https://github.com/vikdevelop/SaveDesktop/releases/tag/4.0</url>
71+
<description>
72+
<ul>
73+
<li>New: Include custom folders in backups (#505)</li>
74+
<li>Redesigned Flatpak apps selection: shows app names (not IDs) and backs up selected apps together with their user data</li>
75+
<li>New: Sync selected configuration items across PCs to keep archives consistent</li>
76+
<li>Updated archive structure with full backward compatibility for older backups</li>
77+
<li>Updated translations</li>
78+
</ul>
79+
</description>
80+
</release>
6981
<release version="3.9" date="2026-01-18">
7082
<url>https://github.com/vikdevelop/SaveDesktop/releases/tag/3.9</url>
7183
<description>

data/io.github.vikdevelop.SaveDesktop.service.in

Lines changed: 0 additions & 3 deletions
This file was deleted.

data/meson.build

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,4 @@ test('Validate schema file',
3333
compile_schemas,
3434
args: ['--strict', '--dry-run', meson.current_source_dir()])
3535

36-
37-
service_conf = configuration_data()
38-
service_conf.set('bindir', get_option('prefix') / get_option('bindir'))
39-
configure_file(
40-
input: 'io.github.vikdevelop.SaveDesktop.service.in',
41-
output: 'io.github.vikdevelop.SaveDesktop.service',
42-
configuration: service_conf,
43-
install_dir: get_option('datadir') / 'dbus-1' / 'services'
44-
)
45-
4636
subdir('icons')

meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
project('savedesktop',
2-
version: '3.9',
2+
version: '4.0-rc',
33
meson_version: '>= 1.0.0',
44
default_options: [ 'warning_level=2', 'werror=false', ],
55
)

po/savedesktop.pot

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ msgstr ""
6767
msgid "File manager bookmarks"
6868
msgstr ""
6969

70+
#: src/gui/items_dialog.py
71+
#: src/gui/custom_dirs_dialog.py
72+
msgid "Custom folders"
73+
msgstr ""
74+
75+
#: src/gui/custom_dirs_dialog.py
76+
msgid "Select custom folders to include in the configuration archive."
77+
msgstr ""
78+
79+
#: src/gui/custom_dirs_dialog.py
80+
msgid "Add folder"
81+
msgstr ""
82+
83+
#: src/gui/custom_dirs_dialog.py
84+
msgid "Remove"
85+
msgstr ""
86+
87+
#: src/gui/custom_dirs_dialog.py
88+
msgid "<i>Since you are using Flatpak, pay attention to the path format. <b>If the selected path begins at /run/user/</b>, it would be necessary to grant access to the folder you want to select.</i>"
89+
msgstr ""
90+
7091
#: src/gui/items_dialog.py
7192
msgid "Flatpak apps"
7293
msgstr ""
@@ -80,7 +101,7 @@ msgid "User data of installed Flatpak apps"
80101
msgstr ""
81102

82103
#: src/gui/items_dialog.py
83-
msgid "Flatpak apps data selection"
104+
msgid "Select Flatpak apps"
84105
msgstr ""
85106

86107
#: src/gui/items_dialog.py
@@ -411,12 +432,7 @@ msgid "Bidirectional synchronization"
411432
msgstr ""
412433

413434
#: src/gui/synchronization_dialogs.py
414-
msgid ""
415-
"If enabled, and the sync interval and cloud drive folder are selected, the periodic saving information (interval, folder, and file name) from the other computer with synchronization set to synchronize is copied to this computer."
416-
msgstr ""
417-
418-
#: src/gui/window.py
419-
msgid "From now on, you can sync the config from the menu in the header bar"
435+
msgid "Enables full two-way synchronization, allowing this computer to use the same backup schedule, archive name, and selected configuration items as the other computer with synchronization set up."
420436
msgstr ""
421437

422438
#: src/gui/templates/shortcuts_window.ui:36

src/core/de_config.py

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,35 @@ def save_icons():
3737
legacy_path = f"{home}"
3838

3939
if os.path.isdir(f"{xdg_path}/icons"):
40-
subprocess.run(["tar", "-czf", "icon-themes.tgz", "-C", xdg_path, "icons"])
40+
subprocess.run(["tar", "-czf", "General/icon-themes.tgz", "-C", xdg_path, "icons"])
4141

4242
if os.path.isdir(f"{legacy_path}/.icons"):
43-
subprocess.run(["tar", "-czf", "icon-themes-legacy.tgz", "-C", legacy_path, ".icons"])
43+
subprocess.run(["tar", "-czf", "General/icon-themes-legacy.tgz", "-C", legacy_path, ".icons"])
4444

4545
print("[OK] Saving icons")
4646

4747
def import_icons():
48-
if os.path.exists("icon-themes.tgz"):
49-
subprocess.run(["tar", "-xzf", "icon-themes.tgz", "-C", f"{home}/.local/share"])
48+
if os.path.exists("General"):
49+
if os.path.exists("General/icon-themes.tgz"):
50+
subprocess.run(["tar", "-xzf", "General/icon-themes.tgz", "-C", f"{home}/.local/share"])
5051

51-
if os.path.exists("icon-themes-legacy.tgz"):
52-
subprocess.run(["tar", "-xzf", "icon-themes-legacy.tgz", "-C", home])
52+
if os.path.exists("General/icon-themes-legacy.tgz"):
53+
subprocess.run(["tar", "-xzf", "General/icon-themes-legacy.tgz", "-C", home])
5354

54-
print("[OK] Importing icons")
55+
print("[OK] Importing icons")
56+
else:
57+
if os.path.exists("icon-themes.tgz"):
58+
subprocess.run(["tar", "-xzf", "icon-themes.tgz", "-C", f"{home}/.local/share"])
59+
60+
if os.path.exists("icon-themes-legacy.tgz"):
61+
subprocess.run(["tar", "-xzf", "icon-themes-legacy.tgz", "-C", home])
62+
63+
print("[OK] Importing icons")
5564

5665
def save_flatpak_data():
5766
blacklist = settings["disabled-flatpak-apps-data"]
5867

59-
cmd = ["tar", "-czf", "flatpak-apps-data.tgz", "--exclude=*/cache"]
68+
cmd = ["tar", "-czf", "Flatpak_Apps/flatpak-apps-data.tgz", "--exclude=*/cache"]
6069

6170
for app in blacklist:
6271
cmd.append(f"--exclude={app}")
@@ -66,17 +75,25 @@ def save_flatpak_data():
6675
subprocess.run(cmd)
6776
print("[OK] Saving Flatpak app data")
6877

78+
def export_flatpaks(path, output_file, install_type):
79+
if os.path.exists(path):
80+
apps = [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))]
81+
with open(output_file, 'w') as f:
82+
for app in apps:
83+
if not app in settings["disabled-flatpak-apps-data"]:
84+
f.write(f"flatpak install --{install_type} {app} -y\n")
85+
6986
def create_flatpak_list():
70-
os.system("ls /var/lib/flatpak/app/ | awk '{print \"flatpak install --system \" $1 \" -y\"}' > installed_flatpaks.sh")
71-
os.system("ls ~/.local/share/flatpak/app | awk '{print \"flatpak install --user \" $1 \" -y\"}' > installed_user_flatpaks.sh")
87+
export_flatpaks('/var/lib/flatpak/app/', 'Flatpak_Apps/installed_flatpaks.sh', 'system')
88+
export_flatpaks(f'{home}/.local/share/flatpak/app', 'Flatpak_Apps/installed_user_flatpaks.sh', 'user')
7289
print("[OK] Saving Flatpak app list")
7390

7491
def create_flatpak_autostart():
7592
os.system(f"cp /app/share/savedesktop/savedesktop/core/flatpaks_installer.py {CACHE}/workspace")
7693

7794
# Create a JSON file with the user's preferences for Flatpak apps
7895
with open(f"{CACHE}/workspace/flatpak-prefs.json", "w") as fl:
79-
json.dump({"keep-flatpaks": settings["keep-flatpaks"], "install-flatpaks": settings["save-installed-flatpaks"], "copy-data": settings["save-flatpak-data"]}, fl)
96+
json.dump({"keep-flatpaks": settings["keep-flatpaks"], "disabled-flatpaks": settings["disabled-flatpak-apps-data"], "install-flatpaks": settings["save-installed-flatpaks"], "copy-data": settings["save-flatpak-data"]}, fl)
8097

8198
# Create an autostart file for post-login Flatpak installation
8299
os.makedirs(f"{home}/.config/autostart", exist_ok=True)
@@ -92,14 +109,19 @@ def create_flatpak_autostart():
92109
print("[OK] Created Flatpak autostart")
93110

94111
def save_bookmarks():
95-
safe_copytree(f"{home}/.config/gtk-4.0", "gtk-4.0")
112+
safe_copytree(f"{home}/.config/gtk-4.0", "General/gtk-4.0")
96113
if settings["save-bookmarks"]:
97-
safe_copy(f"{home}/.config/gtk-3.0/bookmarks", "gtk-3.0/bookmarks")
114+
safe_copy(f"{home}/.config/gtk-3.0/bookmarks", "General/gtk-3.0/bookmarks")
98115

99116
def import_bookmarks():
100-
safe_copytree("gtk-4.0", f"{home}/.config/gtk-4.0", )
101-
if settings["save-bookmarks"]:
102-
safe_copy("gtk-3.0/bookmarks", f"{home}/.config/gtk-3.0/bookmarks")
117+
if os.path.exists("General"):
118+
safe_copytree("General/gtk-4.0", f"{home}/.config/gtk-4.0", )
119+
if settings["save-bookmarks"]:
120+
safe_copy("General/gtk-3.0/bookmarks", f"{home}/.config/gtk-3.0/bookmarks")
121+
else:
122+
safe_copytree("gtk-4.0", f"{home}/.config/gtk-4.0", )
123+
if settings["save-bookmarks"]:
124+
safe_copy("gtk-3.0/bookmarks", f"{home}/.config/gtk-3.0/bookmarks")
103125

104126
# ------------------------
105127
# Desktop folder
@@ -109,20 +131,28 @@ def import_bookmarks():
109131

110132
def save_desktop_folder():
111133
subprocess.run([
112-
"tar", "-czf", "desktop-folder.tgz",
134+
"tar", "-czf", "General/desktop-folder.tgz",
113135
"-C", str(desktop_dir.parent),
114136
desktop_dir.name
115137
])
116138

117-
safe_copytree(f"{home}/.local/share/gvfs-metadata", "gvfs-metadata")
139+
safe_copytree(f"{home}/.local/share/gvfs-metadata", "General/gvfs-metadata")
118140
print("[OK] Saving Desktop folder")
119141

120142
def import_desktop_folder():
121-
if os.path.exists("desktop-folder.tgz"):
143+
if os.path.exists("General"):
144+
subprocess.run([
145+
"tar", "-xzf", "General/desktop-folder.tgz",
146+
"-C", str(desktop_dir.parent)
147+
])
148+
safe_copytree("General/gvfs-metadata", f"{home}/.local/share/gvfs-metadata")
149+
print("[OK] Restored Desktop folder")
150+
else:
122151
subprocess.run([
123152
"tar", "-xzf", "desktop-folder.tgz",
124153
"-C", str(desktop_dir.parent)
125154
])
155+
safe_copytree("gvfs-metadata", f"{home}/.local/share/gvfs-metadata")
126156
print("[OK] Restored Desktop folder")
127157

128158
# ------------------------
@@ -232,45 +262,51 @@ def import_desktop_folder():
232262
class Save:
233263

234264
def __init__(self):
265+
os.makedirs("General", exist_ok=True)
266+
os.makedirs("DE", exist_ok=True)
267+
os.makedirs("Flatpak_Apps", exist_ok=True)
268+
os.makedirs("Custom_Dirs", exist_ok=True)
235269

236270
# General dirs
237271
for item in GENERAL_ITEMS.values():
238-
239272
if settings[item["key"]]:
240-
241273
for src, dst in item["dirs"]:
242-
safe_copytree(src, dst)
274+
safe_copytree(src, os.path.join("General", dst))
243275

244276
# Special handlers
245277
for item in SPECIAL_ITEMS.values():
246-
247278
if settings[item["key"]]:
248279
item["save"]()
249280

281+
# Custom dirs
282+
for folder in settings["custom-dirs"]:
283+
short_folder = Path(folder).relative_to(home)
284+
safe_copytree(folder, f"Custom_Dirs/{short_folder}")
285+
250286
# DE configuration
251287
if environment:
252288
if environment["de_name"] == "KDE Plasma":
253289
print("Saving KDE Plasma configuration...")
254-
os.makedirs("xdg-config", exist_ok=True)
255-
os.makedirs("xdg-data", exist_ok=True)
256-
os.system(f"cp -R {home}/.config/[k]* ./xdg-config/")
257-
os.system(f"cp -R {home}/.local/share/[k]* ./xdg-data/")
290+
os.makedirs("DE/xdg-config", exist_ok=True)
291+
os.makedirs("DE/xdg-data", exist_ok=True)
292+
os.system(f"cp -R {home}/.config/[k]* ./DE/xdg-config/")
293+
os.system(f"cp -R {home}/.local/share/[k]* ./DE/xdg-data/")
258294
for src, dst in KDE_DIRS_SAVE:
259295
if os.path.isfile(src):
260-
safe_copy(src, dst)
296+
safe_copy(src, os.path.join("DE", dst))
261297
else:
262-
safe_copytree(src, dst)
298+
safe_copytree(src, os.path.join("DE", dst))
263299
else:
264300
print(f"Saving environment-specific config for: {environment['de_name']}")
265301
for src, dst in environment["dirs"]:
266-
safe_copytree(src, dst)
302+
safe_copytree(src, os.path.join("DE", dst))
267303
else:
268304
print(f"[WARN] Unknown DE: {environment_key}")
269305

270306
self.save_dconf()
271307

272308
def save_dconf(self):
273-
os.system("dconf dump / > dconf-settings.ini")
309+
os.system("dconf dump / > ./General/dconf-settings.ini")
274310
print("[OK] Saved dconf")
275311

276312
# ------------------------
@@ -283,38 +319,56 @@ def __init__(self):
283319

284320
# General dirs (reverse copy)
285321
for item in GENERAL_ITEMS.values():
286-
287322
if settings[item["key"]]:
288-
289323
for src, dst in item["dirs"]:
290-
safe_copytree(dst, src)
324+
if os.path.exists("General"):
325+
safe_copytree(os.path.join("General", dst), src)
326+
else:
327+
safe_copytree(dst, src)
291328

292329
# Special handlers
293330
for item in SPECIAL_ITEMS.values():
294-
295331
if settings[item["key"]]:
296332
item["import"]()
297333

334+
# Custom dirs
335+
for folder in settings["custom-dirs"]:
336+
short_folder = Path(folder).relative_to(home)
337+
safe_copytree(f"Custom_Dirs/{short_folder}", folder)
338+
298339
# DE configuration
299340
if environment:
300341
if environment["de_name"] == "KDE Plasma":
301342
print("Importing KDE Plasma configuration...")
302343
for src, dst in KDE_DIRS_IMPORT:
303-
safe_copytree(src, dst)
344+
if os.path.exists("DE"):
345+
safe_copytree(os.path.join("DE", src), dst)
346+
else:
347+
safe_copytree(src, dst)
304348
else:
305349
print(f"Importing environment-specific config for: {environment['de_name']}")
306350
for src, dst in environment["dirs"]:
307-
safe_copytree(dst, src)
351+
if os.path.exists("DE"):
352+
safe_copytree(os.path.join("DE", dst), src)
353+
else:
354+
safe_copytree(dst, src)
308355
else:
309356
print(f"[WARN] Unknown DE: {environment_key}")
310357

311358
self.import_dconf()
312359

313360
def import_dconf(self):
314361
if flatpak:
315-
os.system("dconf load / < dconf-settings.ini")
362+
if os.path.exists("General"):
363+
os.system("dconf load / < ./General/dconf-settings.ini")
364+
else:
365+
os.system("dconf load / < ./dconf-settings.ini")
316366
else:
317367
os.system("echo user-db:user > temporary-profile")
318-
os.system('DCONF_PROFILE="$(pwd)/temporary-profile" dconf load / < dconf-settings.ini')
368+
369+
if os.path.exists("General"):
370+
os.system('DCONF_PROFILE="$(pwd)/temporary-profile" dconf load / < ./General/dconf-settings.ini')
371+
else:
372+
os.system('DCONF_PROFILE="$(pwd)/temporary-profile" dconf load / < ./dconf-settings.ini')
319373

320374
print("[OK] Imported dconf")

0 commit comments

Comments
 (0)