dar-backup 0.6.3__py3-none-any.whl → 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dar_backup/.darrc +2 -2
- dar_backup/__about__.py +1 -1
- dar_backup/manager.py +9 -5
- dar_backup/util.py +87 -21
- {dar_backup-0.6.3.dist-info → dar_backup-0.6.4.dist-info}/METADATA +1 -1
- dar_backup-0.6.4.dist-info/RECORD +13 -0
- dar_backup-0.6.3.dist-info/RECORD +0 -13
- {dar_backup-0.6.3.dist-info → dar_backup-0.6.4.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.3.dist-info → dar_backup-0.6.4.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.3.dist-info → dar_backup-0.6.4.dist-info}/licenses/LICENSE +0 -0
dar_backup/.darrc
CHANGED
|
@@ -14,13 +14,13 @@ verbose:
|
|
|
14
14
|
# -vs
|
|
15
15
|
|
|
16
16
|
# shows diretory currently being processed
|
|
17
|
-
|
|
17
|
+
-vd
|
|
18
18
|
|
|
19
19
|
# shows detailed messages, not related to files and directories
|
|
20
20
|
# -vm
|
|
21
21
|
|
|
22
22
|
# shows summary of each treated directory, including average compression
|
|
23
|
-
|
|
23
|
+
-vf
|
|
24
24
|
|
|
25
25
|
# equivalent to "-vm -vs -vt"
|
|
26
26
|
# -va
|
dar_backup/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.4"
|
dar_backup/manager.py
CHANGED
|
@@ -119,11 +119,11 @@ def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
|
|
|
119
119
|
return -1
|
|
120
120
|
line_no = 1
|
|
121
121
|
for line in process.stdout.splitlines():
|
|
122
|
-
#print(f"{line_no}: {line}")
|
|
122
|
+
#print(f"{line_no}: '{line}'")
|
|
123
123
|
line_no += 1
|
|
124
|
-
search = re.search(f"
|
|
124
|
+
search = re.search(f".*?(\d+)\s+.*?({archive}).*", line)
|
|
125
125
|
if search:
|
|
126
|
-
#print("FOUND")
|
|
126
|
+
#print(f"FOUND: archive: {search.group(2)}, catalog #: '{search.group(1)}'")
|
|
127
127
|
logger.info(f"Found archive: '{archive}', catalog #: '{search.group(1)}'")
|
|
128
128
|
return int(search.group(1))
|
|
129
129
|
return -1
|
|
@@ -288,7 +288,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
|
|
|
288
288
|
backup_def = backup_def_from_archive(archive)
|
|
289
289
|
database_path = os.path.join(config_settings.backup_dir, f"{backup_def}{DB_SUFFIX}")
|
|
290
290
|
cat_no = cat_no_for_name(archive, config_settings)
|
|
291
|
-
if cat_no
|
|
291
|
+
if cat_no >= 0:
|
|
292
292
|
command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
|
|
293
293
|
process = run_command(command)
|
|
294
294
|
else:
|
|
@@ -430,7 +430,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
430
430
|
|
|
431
431
|
|
|
432
432
|
if args.remove_specific_archive:
|
|
433
|
-
|
|
433
|
+
|
|
434
|
+
if remove_specific_archive(args.remove_specific_archive, config_settings) == 0:
|
|
435
|
+
sys.exit(0)
|
|
436
|
+
else:
|
|
437
|
+
sys.exit(1)
|
|
434
438
|
|
|
435
439
|
|
|
436
440
|
|
dar_backup/util.py
CHANGED
|
@@ -15,10 +15,11 @@ import re
|
|
|
15
15
|
import subprocess
|
|
16
16
|
import shlex
|
|
17
17
|
import sys
|
|
18
|
+
import threading
|
|
18
19
|
import traceback
|
|
19
20
|
from datetime import datetime
|
|
20
21
|
|
|
21
|
-
from typing import NamedTuple
|
|
22
|
+
from typing import NamedTuple, List
|
|
22
23
|
|
|
23
24
|
logger=None
|
|
24
25
|
|
|
@@ -101,45 +102,110 @@ class CommandResult(NamedTuple):
|
|
|
101
102
|
command: list[str]
|
|
102
103
|
|
|
103
104
|
def __str__(self):
|
|
104
|
-
return f"CommandResult: [Return Code: '{self.returncode}',
|
|
105
|
+
#return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}', \nStdout:\n'{self.stdout}', \nStderr:\n'{self.stderr}', \nTimeout: '{self.timeout}']"
|
|
106
|
+
return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}']"
|
|
105
107
|
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
|
|
110
|
+
def _stream_reader(pipe, log_func, output_accumulator: List[str]):
|
|
111
|
+
"""
|
|
112
|
+
Reads lines from the subprocess pipe, logs them, and accumulates them.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
pipe: The pipe to read from (stdout or stderr).
|
|
116
|
+
log_func: The logging function to use (e.g., logger.info, logger.error).
|
|
117
|
+
output_accumulator: A list to store the lines read from the pipe.
|
|
118
|
+
"""
|
|
119
|
+
with pipe:
|
|
120
|
+
for line in iter(pipe.readline, ''):
|
|
121
|
+
stripped_line = line.strip()
|
|
122
|
+
output_accumulator.append(stripped_line) # Accumulate the output
|
|
123
|
+
log_func(stripped_line) # Log the output in real time
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def run_command(command: List[str], timeout: int = 30) -> CommandResult:
|
|
108
127
|
"""
|
|
109
|
-
Executes a given command via subprocess and
|
|
128
|
+
Executes a given command via subprocess, logs its output in real time, and returns the result.
|
|
110
129
|
|
|
111
130
|
Args:
|
|
112
131
|
command (list): The command to be executed, represented as a list of strings.
|
|
113
|
-
timeout (int): The maximum time in seconds to wait for the command to complete.Defaults to 30 seconds.
|
|
132
|
+
timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
|
|
114
133
|
|
|
115
134
|
Returns:
|
|
116
|
-
|
|
117
|
-
- process:
|
|
118
|
-
- stdout:
|
|
119
|
-
- stderr:
|
|
120
|
-
- returncode:
|
|
121
|
-
- timeout:
|
|
122
|
-
- command:
|
|
123
|
-
|
|
135
|
+
A CommandResult NamedTuple with the following properties:
|
|
136
|
+
- process: subprocess.CompletedProcess
|
|
137
|
+
- stdout: str: The full standard output of the command.
|
|
138
|
+
- stderr: str: The full standard error of the command.
|
|
139
|
+
- returncode: int: The return code of the command.
|
|
140
|
+
- timeout: int: The timeout value in seconds used to run the command.
|
|
141
|
+
- command: list[str]: The command executed.
|
|
142
|
+
|
|
143
|
+
Logs:
|
|
144
|
+
- Logs standard output (`stdout`) in real-time at the INFO log level.
|
|
145
|
+
- Logs standard error (`stderr`) in real-time at the ERROR log level.
|
|
146
|
+
|
|
124
147
|
Raises:
|
|
125
|
-
subprocess.TimeoutExpired:
|
|
126
|
-
Exception:
|
|
148
|
+
subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
|
|
149
|
+
Exception: If other exceptions occur during command execution.
|
|
150
|
+
|
|
151
|
+
Notes:
|
|
152
|
+
- While the command runs, its `stdout` and `stderr` streams are logged in real-time.
|
|
153
|
+
- The returned `stdout` and `stderr` capture the complete output, even though the output is also logged.
|
|
154
|
+
- The command is forcibly terminated if it exceeds the specified timeout.
|
|
127
155
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
156
|
+
stdout_lines = [] # To accumulate stdout
|
|
157
|
+
stderr_lines = [] # To accumulate stderr
|
|
158
|
+
process = None # Track the process for cleanup
|
|
159
|
+
|
|
130
160
|
try:
|
|
131
161
|
logger.debug(f"Running command: {command}")
|
|
132
162
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
163
|
+
|
|
164
|
+
# Start threads to read and log stdout and stderr
|
|
165
|
+
stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, logger.info, stdout_lines))
|
|
166
|
+
stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, logger.error, stderr_lines))
|
|
167
|
+
|
|
168
|
+
stdout_thread.start()
|
|
169
|
+
stderr_thread.start()
|
|
170
|
+
|
|
171
|
+
# Wait for process to complete or timeout
|
|
172
|
+
process.wait(timeout=timeout)
|
|
173
|
+
|
|
136
174
|
except subprocess.TimeoutExpired:
|
|
137
|
-
process
|
|
175
|
+
if process:
|
|
176
|
+
process.terminate()
|
|
138
177
|
logger.error(f"Command: '{command}' timed out and was terminated.")
|
|
139
178
|
raise
|
|
140
179
|
except Exception as e:
|
|
141
180
|
logger.error(f"Error running command: {command}", exc_info=True)
|
|
142
181
|
raise
|
|
182
|
+
finally:
|
|
183
|
+
# Ensure threads are joined to clean up
|
|
184
|
+
if stdout_thread.is_alive():
|
|
185
|
+
stdout_thread.join()
|
|
186
|
+
if stderr_thread.is_alive():
|
|
187
|
+
stderr_thread.join()
|
|
188
|
+
|
|
189
|
+
# Ensure process streams are closed
|
|
190
|
+
if process and process.stdout:
|
|
191
|
+
process.stdout.close()
|
|
192
|
+
if process and process.stderr:
|
|
193
|
+
process.stderr.close()
|
|
194
|
+
|
|
195
|
+
# Combine captured stdout and stderr lines into single strings
|
|
196
|
+
stdout = "\n".join(stdout_lines)
|
|
197
|
+
stderr = "\n".join(stderr_lines)
|
|
198
|
+
|
|
199
|
+
# Build the result object
|
|
200
|
+
result = CommandResult(
|
|
201
|
+
process=process,
|
|
202
|
+
stdout=stdout,
|
|
203
|
+
stderr=stderr,
|
|
204
|
+
returncode=process.returncode,
|
|
205
|
+
timeout=timeout,
|
|
206
|
+
command=command
|
|
207
|
+
)
|
|
208
|
+
logger.debug(f"Command result: {result}")
|
|
143
209
|
return result
|
|
144
210
|
|
|
145
211
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dar-backup
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: A script to do full, differential and incremental backups using dar. Some files are restored from the backups during verification, after which par2 redundancy files are created. The script also has a cleanup feature to remove old backups and par2 files.
|
|
5
5
|
Project-URL: Homepage, https://github.com/per2jensen/dar-backup
|
|
6
6
|
Project-URL: Issues, https://github.com/per2jensen/dar-backup/issues
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
dar_backup/.darrc,sha256=3d9opAnnZGU9XLyQpTDsLtgo6hqsvZ3JU-yMLz-7_f0,2110
|
|
2
|
+
dar_backup/__about__.py,sha256=ZbrGjsYU2ekVSe1-DcOOl7YsjrwB1bbJTUQlEi8pMjU,21
|
|
3
|
+
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
|
|
5
|
+
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
6
|
+
dar_backup/dar_backup.py,sha256=YZoVu9NJX3_WIkQIG8EMLSK3-VWdslI0c2XKrM2Un38,32214
|
|
7
|
+
dar_backup/manager.py,sha256=2qTcn1HiDrejc6S7dLpkylURGzMQ0QqTaSl2KQQ58Uw,19198
|
|
8
|
+
dar_backup/util.py,sha256=SSSJYM9lQZfubhTUBlX1xDGWmCpYEF3ePARmlY544xM,11283
|
|
9
|
+
dar_backup-0.6.4.dist-info/METADATA,sha256=9AKTUu-uyxNQqKoxtYdAWbAyMgHsmLCblvchEvT_B94,22496
|
|
10
|
+
dar_backup-0.6.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
dar_backup-0.6.4.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
|
|
12
|
+
dar_backup-0.6.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
+
dar_backup-0.6.4.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
dar_backup/.darrc,sha256=ggex9N6eETOS6u003_QRRJMeWbveQfkT1lDBt0XpU-I,2112
|
|
2
|
-
dar_backup/__about__.py,sha256=_SsQ0ZcyZbUqlFFT370nQxs8UER9D0oW_EmCr4Q-hx4,21
|
|
3
|
-
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
|
|
5
|
-
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
6
|
-
dar_backup/dar_backup.py,sha256=YZoVu9NJX3_WIkQIG8EMLSK3-VWdslI0c2XKrM2Un38,32214
|
|
7
|
-
dar_backup/manager.py,sha256=Y1dQ7CRGQx5sGoGrjAO62QfJcuW_0Vod-ZGaQmHInFU,19062
|
|
8
|
-
dar_backup/util.py,sha256=6lPCFHr3MDdaLWAW9EDMZ4jdL7pt8rki-5dOXcesmP8,8955
|
|
9
|
-
dar_backup-0.6.3.dist-info/METADATA,sha256=6120hSt2mUxhn3u8PfbJweoPY4ToWFlAPHAgLozaK7Q,22496
|
|
10
|
-
dar_backup-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
dar_backup-0.6.3.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
|
|
12
|
-
dar_backup-0.6.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
-
dar_backup-0.6.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|