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 CHANGED
@@ -14,13 +14,13 @@ verbose:
14
14
  # -vs
15
15
 
16
16
  # shows diretory currently being processed
17
- # -vd
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
- # -vf
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.3"
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"\s+(\d+)\s+.*?({archive}).*", line)
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 > 0:
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
- sys.exit(remove_specific_archive(args.remove_specific_archive, config_settings))
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}', Stdout: '{self.stdout}', Stderr: '{self.stderr}', Timeout: '{self.timeout}', Command: '{self.command}']"
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
- def run_command(command: list[str], timeout: int=30) -> typing.NamedTuple:
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 captures its output.
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
- a typing.NamedTuple of class dar-backup.util.CommandResult with the following properties:
117
- - process: of type subprocess.CompletedProcess: The result of the command execution.
118
- - stdout: of type str: The standard output of the command.
119
- - stderr: of type str: The standard error of the command.
120
- - returncode: of type int: The return code of the command.
121
- - timeout: of type int: The timeout value in seconds used to run the command.
122
- - command: of type list[str): The command executed.
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: if the command execution times out (see `timeout` parameter).
126
- Exception: raise exceptions during command runs.
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
- stdout = None
129
- stderr = None
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
- stdout, stderr = process.communicate(timeout) # Wait with timeout
134
- result = CommandResult(process=process, stdout=stdout, stderr=stderr, returncode=process.returncode, timeout=timeout, command=command)
135
- logger.debug(f"Command result: {str(result)}")
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.terminate()
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
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,,