Skip to content

Commit be7cdff

Browse files
committed
Unify saving/importing/syncing configuration
1 parent 35ace11 commit be7cdff

File tree

5 files changed

+49
-128
lines changed

5 files changed

+49
-128
lines changed

src/core/archive.py

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,52 @@
11
import os, sys, shutil, subprocess, argparse, re
22
from savedesktop.globals import *
3+
from savedesktop.core.password_store import PasswordStore
34

45
parser = argparse.ArgumentParser()
56
parser.add_argument("-c", "--create", help="Create archive", type=str)
67
parser.add_argument("-u", "--unpack", help="Unpack archive", type=str)
78
args = parser.parse_args()
89

10+
TEMP_CACHE = f"{CACHE}/workspace"
11+
12+
# Cleanup the cache dir before saving
13+
def cleanup_cache_dir():
14+
print("Cleaning up the cache directory")
15+
try:
16+
shutil.rmtree(TEMP_CACHE)
17+
except:
18+
pass
19+
os.makedirs(TEMP_CACHE, exist_ok=True)
20+
os.chdir(TEMP_CACHE)
21+
922
# Get password entered in the "Create a new password" dialog from the temporary file
1023
def get_password():
1124
temp_file = f"{CACHE}/temp_file"
1225
if os.path.exists(temp_file):
1326
with open(temp_file) as tmp:
1427
return tmp.read().strip()
1528
else:
16-
return None
29+
p = PasswordStore()
30+
return p.password
1731

1832
# Remove above temporary file
1933
def remove_temp_file():
20-
try:
34+
if os.path.exists(f"{CACHE}/temp_file"):
2135
os.remove(f"{CACHE}/temp_file")
22-
except FileNotFoundError:
23-
pass
2436

2537
class Create:
2638
def __init__(self):
2739
self.start_saving()
2840

2941
def start_saving(self):
30-
self._cleanup_cache_dir()
42+
cleanup_cache_dir()
3143
subprocess.run([sys.executable, "-m", "savedesktop.core.config", "--save"], check=True, env={**os.environ, "PYTHONPATH": f"{app_prefix}"})
3244

3345
print("Creating and moving the configuration archive or folder to the user-defined directory")
3446

35-
if settings["save-without-archive"]:
47+
# In the periodic saving mode, it's not allowed to save the
48+
# configuration without creating the archive
49+
if settings["save-without-archive"] and not args.create == settings["periodic-saving-folder"]:
3650
self._copy_config_to_folder()
3751
else:
3852
self._create_archive()
@@ -41,25 +55,14 @@ def start_saving(self):
4155
print("Configuration saved successfully.")
4256
remove_temp_file()
4357

44-
# Cleanup the cache dir before saving
45-
def _cleanup_cache_dir(self):
46-
print("Cleaning up the cache directory")
47-
save_cache_dir = f"{CACHE}/save_config"
48-
try:
49-
shutil.rmtree(save_cache_dir)
50-
except:
51-
pass
52-
os.makedirs(save_cache_dir, exist_ok=True)
53-
os.chdir(save_cache_dir)
54-
5558
# Copy the configuration folder to the user-defined directory
5659
def _copy_config_to_folder(self):
57-
open(f"{CACHE}/save_config/.folder.sd", "w").close()
60+
open(f".folder.sd", "w").close()
5861

5962
if os.path.exists(args.create):
6063
shutil.rmtree(args.create)
6164

62-
shutil.move(f"{CACHE}/save_config", f"{args.create}")
65+
shutil.move(TEMP_CACHE, f"{args.create}")
6366

6467
# Create a new ZIP archive with 7-Zip
6568
def _create_archive(self):
@@ -85,31 +88,15 @@ def start_importing(self):
8588
self.import_file = args.unpack
8689
self.import_folder = args.unpack
8790

88-
self._cleanup_cache_dir()
91+
cleanup_cache_dir()
8992
self._check_config_type()
9093

9194
self._replace_home_in_files(".", home)
9295
subprocess.run([sys.executable, "-m", "savedesktop.core.config", "--import_"], check=True, env={**os.environ, "PYTHONPATH": f"{app_prefix}"})
9396

94-
self._remove_status_file()
95-
9697
print("Configuration imported successfully.")
9798
remove_temp_file()
9899

99-
def _cleanup_cache_dir(self):
100-
# Cleanup the cache dir before importing
101-
print("Cleaning up the cache directory")
102-
imp_cache_dir = f"{CACHE}/import_config"
103-
try:
104-
shutil.rmtree(imp_cache_dir)
105-
except:
106-
pass
107-
os.makedirs(imp_cache_dir, exist_ok=True)
108-
os.chdir(imp_cache_dir)
109-
110-
# Create a txt file to prevent removing the cache's content after closing the app window
111-
open("import_status", "w").close()
112-
113100
# Check, if the input is archive or folder
114101
def _check_config_type(self):
115102
if self.import_file.endswith(".sd.zip") or self.import_file.endswith(".sd.tar.gz"):
@@ -130,7 +117,7 @@ def _check_config_type(self):
130117

131118
# Copy the user-defined folder to the cache directory
132119
def _copy_folder_to_cache(self):
133-
shutil.copytree(self.import_folder, f"{CACHE}/import_config", dirs_exist_ok=True, ignore_dangling_symlinks=True)
120+
shutil.copytree(self.import_folder, TEMP_CACHE, dirs_exist_ok=True, ignore_dangling_symlinks=True)
134121

135122
# Unpack the ZIP archive with 7-Zip
136123
def _unpack_zip_archive(self):
@@ -148,13 +135,13 @@ def _unpack_zip_archive(self):
148135
print("Checking password is completed.")
149136

150137
subprocess.run(
151-
['7z', 'x', '-y', f'-p{password}', self.import_file, f'-o{CACHE}/import_config'],
138+
['7z', 'x', '-y', f'-p{password}', self.import_file, f'-o{TEMP_CACHE}'],
152139
capture_output=False, text=True, check=True
153140
)
154141

155142
# Unpack a legacy archive with Tarball (for backward compatibility)
156143
def _unpack_tar_archive(self):
157-
subprocess.run(["tar", "-xzf", self.import_file, "-C", f"{CACHE}/import_config"],capture_output=True, text=True, check=True)
144+
subprocess.run(["tar", "-xzf", self.import_file, "-C", f"{TEMP_CACHE}"],capture_output=True, text=True, check=True)
158145

159146
# Replace original /home/$USER path with actual path in the dconf-settings.ini file and other XML files
160147
def _replace_home_in_files(self, root, home, patterns=(".xml", ".ini")):
@@ -171,15 +158,6 @@ def _replace_home_in_files(self, root, home, patterns=(".xml", ".ini")):
171158
f.write(new_text)
172159
print(f"Updated /home/$USER path in: {path}")
173160

174-
# Remove the "import_status" file if the condition is met
175-
def _remove_status_file(self):
176-
if all(not os.path.exists(p) for p in [
177-
f"{CACHE}/import_config/app",
178-
f"{CACHE}/import_config/installed_flatpaks.sh",
179-
f"{CACHE}/import_config/installed_user_flatpaks.sh"
180-
]):
181-
os.remove("import_status")
182-
183161
if args.create:
184162
Create()
185163
elif args.unpack:

src/core/config.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,8 @@ def save_flatpak_data(self):
147147
gsettings = settings["disabled-flatpak-apps-data"]
148148
black_list = gsettings # convert GSettings property to a list
149149

150-
# set destination dir
151-
if os.path.exists(f"{CACHE}/periodic_saving/saving_status"):
152-
os.makedirs(f"{CACHE}/periodic_saving/app", exist_ok=True)
153-
destdir = f"{CACHE}/periodic_saving/app"
154-
else:
155-
os.makedirs(f"{CACHE}/save_config/app", exist_ok=True)
156-
destdir = f"{CACHE}/save_config/app"
150+
os.makedirs(f"{CACHE}/workspace/app", exist_ok=True)
151+
destdir = f"{CACHE}/workspace/app"
157152

158153
# copy Flatpak apps data
159154
for item in os.listdir(f"{home}/.var/app"):

src/core/flatpaks_installer.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818
os.system("gsettings set org.gnome.shell disable-user-extensions false")
1919

2020
# Check if the required directories exist in the cache directory
21-
if os.path.exists(f"{CACHE_FLATPAK}/import_config"):
22-
dest_dir = f"{CACHE_FLATPAK}/import_config"
23-
elif os.path.exists(f"{CACHE_FLATPAK}/syncing"):
24-
dest_dir = f"{CACHE_FLATPAK}/syncing"
21+
if os.path.exists(f"{CACHE_FLATPAK}/workspace"):
22+
dest_dir = f"{CACHE_FLATPAK}/workspace"
2523
else:
2624
dest_dir = None
2725

src/core/periodic_saving.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -72,39 +72,25 @@ def backup(self):
7272
else:
7373
self.filename = settings["filename-format"]
7474

75-
os.makedirs(f"{CACHE}/periodic_saving", exist_ok=True)
76-
os.chdir(f"{CACHE}/periodic_saving")
77-
os.system("echo > saving_status")
78-
subprocess.run([sys.executable, "-m", "savedesktop.core.config", "--save"], check=True, env={**os.environ, "PYTHONPATH": f"{app_prefix}"})
79-
80-
print("creating the configuration archive")
81-
print("moving the configuration archive to the user-defined directory")
82-
self.get_password_from_file()
75+
print("MODE: Periodic saving")
76+
self.call_archive_command()
8377

8478
self.save_last_backup_date()
8579
self.config_saved()
8680

87-
# Get an encrypted password from the {DATA}/password file
88-
def get_password_from_file(self):
89-
try:
90-
ps = PasswordStore()
91-
self.password = ps.password
92-
except:
93-
self.password = None
94-
if self.password != None:
95-
subprocess.run(['7z', 'a', '-tzip', '-mx=6', f'-p{self.password}', '-mem=AES256', '-x!*.zip', '-x!saving_status', 'cfg.sd.zip', '.'], check=True)
96-
else:
97-
subprocess.run(['7z', 'a', '-tzip', '-mx=6', '-x!*.zip', '-x!saving_status', 'cfg.sd.zip', '.'], check=True)
98-
shutil.copyfile('cfg.sd.zip', f'{self.pbfolder}/{self.filename}.sd.zip')
81+
# Call the command for making the archive
82+
def call_archive_command(self):
83+
self.archive_mode = "--create"
84+
self.archive_name = f"{self.pbfolder}/{self.filename}"
85+
86+
subprocess.run([sys.executable, "-m", "savedesktop.core.archive", self.archive_mode, self.archive_name], env={**os.environ, "PYTHONPATH": f"{app_prefix}"})
9987

10088
# Save today's date to the {DATA}/periodic-saving.json file
10189
def save_last_backup_date(self):
10290
with open(f"{DATA}/periodic-saving.json", "w") as pb:
10391
json.dump({"last-saved": date.today().isoformat()}, pb)
10492

10593
def config_saved(self):
106-
os.chdir(CACHE)
107-
shutil.rmtree("periodic_saving")
10894
print("Configuration saved.")
10995

11096
if __name__ == "__main__":

src/core/synchronization.py

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,14 @@ def check_manually_sync_status(self):
6464

6565
# Download the configuration archive from the cloud drive folder
6666
def download_config(self):
67+
print("MODE: Synchronization")
6768
self._send_notification_at_startup()
6869
if not os.path.exists(f"{settings['file-for-syncing']}/SaveDesktop.json"):
6970
err_str = _("An error occurred")
70-
err = "SaveDesktop.json doesn't exist in the cloud drive folder!"
71+
err = "SaveDesktop.json: directory or file does not exist."
7172
os.system(f'notify-send "{err_str}" "{err}"')
7273
else:
7374
self.get_pb_info()
74-
os.makedirs(f"{CACHE}/syncing", exist_ok=True) # create the subfolder in the cache directory
75-
os.chdir(f"{CACHE}/syncing")
76-
os.system("echo > sync_status") # create a txt file to prevent removing the sync's folder content after closing the app window
77-
print("extracting the archive")
7875
self.get_zip_file_status()
7976

8077
# Send a notification about the started synchronization
@@ -114,7 +111,7 @@ def get_zip_file_status(self):
114111
self.get_pwd_from_file()
115112
else:
116113
self.password = None
117-
self.extract_archive()
114+
self.call_archive_command()
118115

119116
# Get a password from the {DATA}/password file
120117
def get_pwd_from_file(self):
@@ -147,56 +144,23 @@ def try_passwordstore():
147144
os.system(f'notify-send "{err_occurred}" "{msg}"')
148145
else:
149146
# #5 Continue in extraction
150-
self.extract_archive()
147+
self.call_archive_command()
151148

152149
# Extract the configuration archive
153-
def extract_archive(self):
154-
try:
155-
if os.path.exists(f"{settings['file-for-syncing']}/{self.file}.sd.zip"):
156-
try:
157-
result = subprocess.run(
158-
['7z', 'x', f'-p{self.password}', f"{settings['file-for-syncing']}/{self.file}.sd.zip", f'-o{CACHE}/syncing', '-y'],
159-
capture_output=True, text=True, check=True
160-
)
161-
print("Output:", result.stdout)
162-
except subprocess.CalledProcessError as e:
163-
print("Return code:", e.returncode)
164-
raise OSError(e.stderr)
165-
else:
166-
with tarfile.open(f"{settings['file-for-syncing']}/{self.file}.sd.tar.gz", 'r:gz') as tar:
167-
for member in tar.getmembers():
168-
try:
169-
tar.extract(member)
170-
except PermissionError as e:
171-
print(f"Permission denied for {member.name}: {e}")
172-
self.password = None
173-
if os.path.exists(f"{DATA}/entered-password.txt"):
174-
os.remove(f"{DATA}/entered-password.txt")
175-
except Exception as e:
176-
err_occurred = _("An error occurred")
177-
os.system(f"notify-send '{err_occurred}' '{e}' -i io.github.vikdevelop.SaveDesktop-symbolic")
178-
if os.path.exists(f"{DATA}/password"):
179-
os.remove(f"{DATA}/password")
180-
if os.path.exists("sync_status"):
181-
os.remove("sync_status")
182-
else:
183-
self.import_config()
184-
185-
# Start importing a configuration from the configuration archive
186-
def import_config(self):
187-
subprocess.run([sys.executable, "-m", "savedesktop.core.config", "--save"], check=True, env={**os.environ, "PYTHONPATH": f"{app_prefix}"})
150+
def call_archive_command(self):
151+
self.archive_mode = "--unpack"
152+
self.archive_name = f"{settings['file-for-syncing']}/{self.file}.sd.zip"
153+
154+
subprocess.run([sys.executable, "-m", "savedesktop.core.archive", self.archive_mode, self.archive_name], env={**os.environ, "PYTHONPATH": f"{app_prefix}"})
188155
self.done()
189156

190157
def done(self):
191158
if not settings["manually-sync"]:
192159
with open(f"{DATA}/sync-info.json", "w") as s:
193160
json.dump({"last-synced": date.today().isoformat()}, s)
194161

195-
# Remove the cache dir's content
196-
os.chdir(CACHE)
197-
if all(not os.path.exists(p) for p in ["app", "installed_flatpaks.sh", "installed_user_flatpaks.sh"]):
198-
if os.path.exists("syncing"):
199-
shutil.rmtree("syncing")
162+
if os.path.exists(f"{DATA}/entered-password.txt"):
163+
os.remove(f"{DATA}/entered-password.txt")
200164

201165
# Send a notification about finished synchronization
202166
os.system(f"notify-send 'Save Desktop Synchronization ({self.file})' '{_('The configuration has been applied!')} {_('Changes will only take effect after the next login')}' -i io.github.vikdevelop.SaveDesktop-symbolic")

0 commit comments

Comments
 (0)