Skip to content

Commit bf005bf

Browse files
committed
Better paths support (config and logs paths). Extended logging features. Code refactor and improvements. Version updated.
1 parent 78bce77 commit bf005bf

File tree

8 files changed

+126
-37
lines changed

8 files changed

+126
-37
lines changed

Src/Config/ConfigManager.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,18 @@ def __init__(self):
2727
"""
2828
self.config_list = []
2929
self.config_match_regex = re.compile(".*[.]" + ConfigVersion.config_files_extension + "$")
30-
self.config_directory = ConfigVersion.config_files_directory
30+
self.config_directory = self.get_config_directory()
3131

3232
if not os.path.isdir(self.config_directory):
33-
raise Exception("Not a directory! Wrong config directory: " + self.config_directory)
33+
raise SystemExit("Wrong config directory! Not a directory: " + self.config_directory)
34+
35+
@classmethod
36+
def get_config_directory(cls):
37+
"""
38+
Returns the config directory. Can be called before the object is created.
39+
:return: str: config directory path.
40+
"""
41+
return ConfigVersion.config_files_directory
3442

3543
def search_config_entries(self):
3644
"""

Src/EESync.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,57 @@
33
from Src.Common.BackupAction import BackupAction
44
from Src.Config.ConfigEntry import ConfigEntry
55
from Src.Config.ConfigManager import ConfigManager
6+
from Src.IO.FileSystem import FileSystem
67
from Src.IO.Logger import Logger
78
from Src.IO.UserInputConsole import UserInputConsole
89
from Src.Service.CryptProvider import CryptProvider
910
from Src.Service.SyncProvider import SyncProvider
1011

1112

1213
class EESync:
13-
__version = '0.9.1'
14+
__version = '0.9.2'
1415

1516
sync_service = SyncProvider()
1617
crypt_service = CryptProvider()
18+
cm_object = None # postpone initialization
1719

1820
def start(self):
1921
"""
2022
Starts the main process.
2123
"""
2224
Logger.init()
25+
self.init_file_system()
26+
self.cm_object = ConfigManager()
27+
28+
Logger.log(f"EESync {self.__version} started.")
2329

2430
print(f"~~ EESync {self.__version} ~~")
2531
self.sync_service.version_check()
2632
self.crypt_service.version_check()
2733
print()
2834

2935
self.interactive_user_menu()
36+
37+
Logger.log("EESync finished.")
3038
print("EESync finished.")
3139

40+
@classmethod
41+
def init_file_system(cls):
42+
"""
43+
Inits and creates (if needed) directories required by the application (logs dir, config dir, etc).
44+
"""
45+
FileSystem.require_created_directory(Logger.get_log_path_dir())
46+
FileSystem.require_created_directory(ConfigManager.get_config_directory())
47+
3248
def interactive_user_menu(self):
3349
"""
3450
Handles the interactive user menu - main menu.
3551
"""
36-
cm_object = ConfigManager()
37-
cm_object.search_config_entries()
52+
self.cm_object.search_config_entries()
3853

39-
if len(cm_object.config_list):
54+
if len(self.cm_object.config_list):
4055
entry_list_string = ''
41-
for config_entry in cm_object.config_list:
56+
for config_entry in self.cm_object.config_list:
4257
entry_list_string += str(
4358
f" {config_entry['config_number']} - {config_entry['general_name']}\n"
4459
)
@@ -54,13 +69,13 @@ def interactive_user_menu(self):
5469
match user_input:
5570
case number if user_input.isdigit():
5671
print("Selected entry no. " + number + " ...")
57-
selected_config = cm_object.config_list[int(number) - 1]
72+
selected_config = self.cm_object.config_list[int(number) - 1]
5873
self.process_entry(selected_config['config_object'])
5974
pass
6075
case 'n':
6176
print("Creating new config.")
62-
new_config_entry = cm_object.new_entry_from_user()
63-
cm_object.save_config_entry(new_config_entry)
77+
new_config_entry = self.cm_object.new_entry_from_user()
78+
self.cm_object.save_config_entry(new_config_entry)
6479
self.interactive_user_menu()
6580
case _:
6681
print("Wrong command!")

Src/IO/FileSystem.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
3+
from Src.IO.Logger import Logger
4+
from Src.IO.UserInputConsole import UserInputConsole
5+
6+
7+
class FileSystem:
8+
"""
9+
File System (OS) level methods.
10+
"""
11+
12+
@classmethod
13+
def require_created_directory(cls, directory_path: str, interactive: bool = True):
14+
"""
15+
Checks if the directory exists.\n
16+
If it does - the method does not do anything.\n
17+
Otherwise - create_dir_interactive() or create_dir() is triggered (depending on `interactive` param value).
18+
:param interactive: Boolean, True by default. Controls if the directory should be created in interactive mode.
19+
:param directory_path: The path that should be created.
20+
"""
21+
if not os.path.isdir(directory_path):
22+
if interactive:
23+
cls.create_dir_interactive(directory_path)
24+
else:
25+
cls.create_dir(directory_path)
26+
27+
@classmethod
28+
def create_dir_interactive(cls, directory_path: str):
29+
"""
30+
Creates a new directory (only if the user confirms).
31+
:param directory_path: The path that should be created.
32+
"""
33+
print("About to create a new directory: \n"
34+
+ directory_path
35+
+ "\nPlease confirm. [y/n] ")
36+
37+
create_dir_confirmed = UserInputConsole.read_true_or_false()
38+
if create_dir_confirmed:
39+
cls.create_dir(directory_path)
40+
41+
@classmethod
42+
def create_dir(cls, directory_path: str):
43+
"""
44+
Creates a new directory (without user confirmation).
45+
:param directory_path: The path that should be created.
46+
"""
47+
if os.path.exists(directory_path):
48+
raise Exception("Given path already exists! The path: \n" + directory_path)
49+
else:
50+
os.makedirs(directory_path)
51+
Logger.log("Created a new directory at: \n" + directory_path)

Src/IO/Logger.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def init(cls):
2929
cls.__log_dir = default_logs_directory
3030

3131
@classmethod
32-
def get_log_path(cls):
32+
def get_log_path_dir(cls):
3333
"""
3434
Returns the application log directory.
3535
"""
@@ -94,7 +94,7 @@ def __handle_file(cls, text):
9494

9595
# file validation
9696
if not isdir(cls.__log_dir):
97-
raise SystemExit(f"Error! Wrong logs directory (not a directory): f{cls.__log_dir}")
97+
raise SystemExit(f"Error! Wrong logs directory (not a directory): {cls.__log_dir}")
9898
if not isfile(save_path):
9999
raise SystemExit(f"Error! Tried to log to a file, but the file is gone: {save_path}")
100100

@@ -113,7 +113,7 @@ def __init_log_file(cls):
113113

114114
# file validation
115115
if not isdir(cls.__log_dir):
116-
raise SystemExit(f"Error! Wrong logs directory (not a directory): f{cls.__log_dir}")
116+
raise SystemExit(f"Error! Wrong logs directory (not a directory): {cls.__log_dir}")
117117
if isfile(save_path):
118118
raise SystemExit(f"Error! Tried to log to a new file, but the file already exists: {save_path}")
119119

Src/IO/UserInputConsole.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33

44
from Src.Common.BackupAction import BackupAction
5+
from Src.IO.Logger import Logger
56

67

78
class UserInputConsole:
@@ -22,6 +23,7 @@ def general_input_int(cls):
2223
print("Wrong value! Only numbers are allowed.")
2324
return UserInputConsole.general_input_int()
2425
except (KeyboardInterrupt, EOFError):
26+
Logger.log("Input interrupted. Terminating.")
2527
raise SystemExit("\nInput interrupted. Terminating.")
2628
return user_input
2729

@@ -33,6 +35,7 @@ def general_input(cls) -> str:
3335
try:
3436
user_input = input(cls.input_caret)
3537
except (KeyboardInterrupt, EOFError):
38+
Logger.log("Input interrupted. Terminating.")
3639
raise SystemExit("\nInput interrupted. Terminating.")
3740
return user_input
3841

Src/Service/CommandRunner.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import GeneralSettings
77
from Src.IO.UserInputConsole import UserInputConsole
8+
from Src.IO.Logger import Logger
89

910

1011
class CommandRunner:
@@ -32,14 +33,15 @@ def __init__(self):
3233
atexit.register(self.cleanup)
3334

3435
@staticmethod
35-
def os_exec(command: list, confirmation_required: bool = False, silent: bool = False, capture_output: bool = True
36-
) -> subprocess.CompletedProcess:
36+
def os_exec(command: list, confirmation_required: bool = False, silent: bool = False, capture_output: bool = True,
37+
logging_enabled: bool = True) -> subprocess.CompletedProcess:
3738
"""
3839
A runner method. Throws exception when returncode is not 0.
3940
:param command: Command and the parameters in the form of a list.
4041
:param confirmation_required: bool value, False by default. Decide if we should ask user for the confirmation.
4142
:param silent: bool value, False by default. Allows to suppress printing the binary name that gets executed.
4243
:param capture_output: bool value, True by default. Allows to control whether the command output is captured.
44+
:param logging_enabled: bool value, True by default. Allows to control whether the logging feature is enabled.
4345
:return: CompletedProcess object.
4446
"""
4547
process_env = dict(environ)
@@ -54,6 +56,8 @@ def os_exec(command: list, confirmation_required: bool = False, silent: bool = F
5456
user_confirmed = True
5557

5658
if user_confirmed:
59+
if logging_enabled:
60+
Logger.log("Executing command: \n" + ' '.join(command))
5761
if not silent:
5862
print(f"( Running: {command[0]} ... )")
5963
command_result = subprocess.run(
@@ -63,26 +67,40 @@ def os_exec(command: list, confirmation_required: bool = False, silent: bool = F
6367
env=process_env
6468
)
6569
else:
70+
if logging_enabled:
71+
Logger.log("Skipped command / execution aborted: \n" + ' '.join(command))
6672
raise SystemExit("Aborted.")
6773

6874
if command_result.returncode != 0:
69-
raise SystemExit(f"Error: Failed to run: {command} "
75+
failed_msg = str(f"Error: Failed to run: {command} "
7076
+ f"\nDetails: \nSTDOUT: {command_result.stdout}\nSTDERR: {command_result.stderr}\n")
77+
if logging_enabled:
78+
Logger.log(failed_msg)
79+
raise SystemExit(failed_msg)
80+
7181
return command_result
7282

7383
@staticmethod
74-
def gen_run_report(exec_command, stdout, stderr) -> str:
84+
def gen_run_report(exec_command, stdout, stderr, logging_enabled: bool = True) -> str:
7585
"""
76-
Generates formatted string from command output.
86+
Generates formatted string from command output. Useful only for the reports.
87+
:param exec_command: Command that you executed.
88+
:param stdout: Standard output from the command.
89+
:param stderr: Standard error output from the command.
90+
:param logging_enabled: Allows to pass the output (returned value) to the logger.
91+
:return: formatted string.
7792
"""
7893
if stdout is None:
7994
stdout = str(f"{stdout} - no output or output disabled.\n")
8095
if stderr is None:
8196
stderr = str(f"{stderr} - no output or output disabled.\n")
82-
return str(f"--- STDOUT: {exec_command}: ---\n"
83-
+ str(stdout)
84-
+ f"--- STDERR: {exec_command}: ---\n"
85-
+ str(stderr))
97+
formatted_string = str(f"--- STDOUT: {exec_command}: ---\n"
98+
+ str(stdout)
99+
+ f"--- STDERR: {exec_command}: ---\n"
100+
+ str(stderr))
101+
if logging_enabled:
102+
Logger.log(formatted_string)
103+
return formatted_string
86104

87105
def version_check(self):
88106
"""

Src/Service/CryptProvider.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from Src.Common.BackupAction import BackupAction
88
from Src.Config.ConfigEntry import ConfigEntry
9-
from Src.IO.Logger import Logger
109
from Src.Service.CommandRunner import CommandRunner
1110

1211

@@ -41,11 +40,11 @@ class CryptProvider(CommandRunner):
4140

4241
def version_detect(self):
4342
# detect binary path
44-
which_result = self.os_exec(["which", self.binary_name], silent=True)
43+
which_result = self.os_exec(["which", self.binary_name], silent=True, logging_enabled=False)
4544
self.binary_path = str(which_result.stdout).strip()
4645

4746
# detect/parse the version
48-
version_check_result = self.os_exec([self.binary_path, "--version"], silent=True)
47+
version_check_result = self.os_exec([self.binary_path, "--version"], silent=True, logging_enabled=False)
4948
result_string = str(version_check_result.stderr).strip()
5049
matched_string = re.search(r"^encfs\s+version\s+(\d.*\.\d)\s*$", result_string)
5150
if not matched_string:
@@ -73,15 +72,13 @@ def __unmount_encfs(self):
7372
# TODO: check if target is busy, get rid of sleep()
7473
time.sleep(10.0)
7574

76-
which_result = self.os_exec(["which", "umount"], silent=True)
75+
which_result = self.os_exec(["which", "umount"], silent=True, logging_enabled=False)
7776
umount_binary = str(which_result.stdout).strip()
7877
exec_command: list = [umount_binary, self.config.encfs_decryption_dir]
7978

8079
umount_result = self.os_exec(exec_command, confirmation_required=True)
8180
self.__resource_mounted = False
82-
83-
run_report = self.gen_run_report(exec_command, umount_result.stdout, umount_result.stderr)
84-
Logger.log(run_report)
81+
self.gen_run_report(exec_command, umount_result.stdout, umount_result.stderr)
8582

8683
@validate_config
8784
def __mount_encfs(self):
@@ -96,9 +93,7 @@ def __mount_encfs(self):
9693
exec_command: list = [self.binary_path, self.config.encfs_encryption_dir, self.config.encfs_decryption_dir]
9794
mount_result = self.os_exec(exec_command, confirmation_required=True, capture_output=False)
9895
self.__resource_mounted = True
99-
100-
run_report = self.gen_run_report(exec_command, mount_result.stdout, mount_result.stderr)
101-
Logger.log(run_report)
96+
self.gen_run_report(exec_command, mount_result.stdout, mount_result.stderr)
10297

10398
def set_config(self, config: ConfigEntry):
10499
"""

Src/Service/SyncProvider.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ class SyncProvider(CommandRunner):
2323

2424
def version_detect(self):
2525
# detect binary path
26-
which_result = self.os_exec(["which", self.binary_name], silent=True)
26+
which_result = self.os_exec(["which", self.binary_name], silent=True, logging_enabled=False)
2727
self.binary_path = str(which_result.stdout).strip()
2828

2929
# detect/parse the version
30-
version_check_result = self.os_exec([self.binary_path, "--version"], silent=True)
30+
version_check_result = self.os_exec([self.binary_path, "--version"], silent=True, logging_enabled=False)
3131
result_string = str(version_check_result.stdout).strip()
3232
matched_string = re.search(r"^rsync\s+version\s+(\d.*\.\d)\s+", result_string)
3333
if not matched_string:
@@ -93,7 +93,7 @@ def __exec_rsync(self, source_dir: str, target_dir: str, dry_run: bool):
9393
if rsync_settings["rsync_logging_enabled"]:
9494
# rsync logging - prepare the path
9595
current_date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
96-
log_dir = Logger.get_log_path()
96+
log_dir = Logger.get_log_path_dir()
9797
rsync_log_path = log_dir + f"/rsync_{current_date}.log"
9898
if not os.path.isdir(log_dir) or os.path.isfile(rsync_log_path):
9999
raise SystemExit('Error! Rsync logging paths misconfigured!')
@@ -110,7 +110,6 @@ def __exec_rsync(self, source_dir: str, target_dir: str, dry_run: bool):
110110
rsync_result = self.os_exec(exec_command, confirmation_required=True, capture_output=False)
111111

112112
# save the report
113-
run_report = self.gen_run_report(exec_command, rsync_result.stdout, rsync_result.stderr)
114-
Logger.log(run_report)
113+
self.gen_run_report(exec_command, rsync_result.stdout, rsync_result.stderr)
115114

116115
print("Sync done!")

0 commit comments

Comments
 (0)