dar-backup 1.0.0.1__py3-none-any.whl → 1.0.2__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/demo.py CHANGED
@@ -37,17 +37,22 @@ DAR_BACKUP_DIR = util.normalize_dir(util.expand_path("~/dar-backup"))
37
37
 
38
38
  def check_directories(args, vars_map: Dict[str,str]) -> bool:
39
39
  """
40
- Check if the directories exist and create them if they don't.
40
+ Check if target paths already exist and are directories.
41
41
 
42
42
  Returns:
43
- bool: True if the directories were created successfully, False otherwise.
43
+ bool: True if it is safe to proceed, False otherwise.
44
44
  """
45
45
  result = True
46
46
  for key in ("DAR_BACKUP_DIR","BACKUP_DIR","TEST_RESTORE_DIR","CONFIG_DIR","BACKUP_D_DIR"):
47
47
  path = Path(vars_map[key])
48
- if path.exists() and not args.override:
49
- print(f"Directory '{path}' already exists")
50
- result = False
48
+ if path.exists():
49
+ if not path.is_dir():
50
+ print(f"Error: '{path}' exists and is not a directory")
51
+ result = False
52
+ continue
53
+ if not args.override:
54
+ print(f"Directory '{path}' already exists")
55
+ result = False
51
56
  return result
52
57
 
53
58
 
@@ -72,12 +77,17 @@ def generate_file(args, template: str, file_path: Path, vars_map: Dict[str, str]
72
77
  if rendered is None:
73
78
  print(f"Error: Template '{template}' could not be rendered.")
74
79
  return False
75
- if os.path.exists(file_path) and not args.override:
76
- print(f"Error: File '{file_path}' already exists. Use --override to overwrite.")
77
- return False
80
+ if file_path.exists():
81
+ if file_path.is_dir():
82
+ print(f"Error: '{file_path}' is a directory, expected a file.")
83
+ return False
84
+ if not args.override:
85
+ print(f"Error: File '{file_path}' already exists. Use --override to overwrite.")
86
+ return False
78
87
  file_path.parent.mkdir(parents=True, exist_ok=True)
79
88
  file_path.write_text(rendered)
80
89
  print(f"File generated at '{file_path}'")
90
+ return True
81
91
 
82
92
 
83
93
 
@@ -152,7 +162,6 @@ def main():
152
162
  parser.error(
153
163
  "Options --root-dir, --dir-to-backup must all be specified together."
154
164
  )
155
- exit(1)
156
165
 
157
166
  args.root_dir = util.normalize_dir(util.expand_path(args.root_dir)) if args.root_dir else None
158
167
  args.backup_dir = util.normalize_dir(util.expand_path(args.backup_dir)) if args.backup_dir else None
dar_backup/installer.py CHANGED
@@ -43,6 +43,9 @@ def install_autocompletion():
43
43
 
44
44
  # ensure RC file and parent directory exist
45
45
  rc_file.parent.mkdir(parents=True, exist_ok=True)
46
+ if rc_file.exists() and rc_file.is_dir():
47
+ print(f"Error: RC path is a directory: {rc_file}")
48
+ return
46
49
  if not rc_file.exists():
47
50
  rc_file.touch()
48
51
 
@@ -76,11 +79,17 @@ def uninstall_autocompletion() -> str:
76
79
  if not rc_file.exists():
77
80
  print(f"❌ RC file not found: {rc_file}")
78
81
  return
82
+ if rc_file.is_dir():
83
+ print(f"Error: RC path is a directory: {rc_file}")
84
+ return
79
85
 
80
86
  content = rc_file.read_text()
81
87
  if marker not in content:
82
88
  print(f"No autocompletion block found in {rc_file}")
83
89
  return f"No autocompletion block found in {rc_file}" # for unit test
90
+ if end_marker not in content:
91
+ print(f"Error: Autocompletion end marker not found in {rc_file}")
92
+ return f"Autocompletion end marker not found in {rc_file}"
84
93
 
85
94
  lines = content.splitlines(keepends=True)
86
95
  new_lines = []
@@ -128,7 +137,11 @@ def run_installer(config_file: str, create_db_flag: bool):
128
137
  log_to_stdout=True,
129
138
  )
130
139
  command_logger = get_logger(command_output_logger=True)
131
- runner = CommandRunner(logger=logger, command_logger=command_logger)
140
+ runner = CommandRunner(
141
+ logger=logger,
142
+ command_logger=command_logger,
143
+ default_capture_limit_bytes=getattr(config_settings, "command_capture_max_bytes", None)
144
+ )
132
145
 
133
146
 
134
147
  # Create required directories
@@ -151,6 +164,10 @@ def run_installer(config_file: str, create_db_flag: bool):
151
164
  # Optionally create databases for all backup definitions
152
165
  if create_db_flag:
153
166
  for file in os.listdir(config_settings.backup_d_dir):
167
+ file_path = os.path.join(config_settings.backup_d_dir, file)
168
+ if not os.path.isfile(file_path):
169
+ logger.info(f"Skipping non-file backup definition: {file}")
170
+ continue
154
171
  backup_def = os.path.basename(file)
155
172
  print(f"Creating catalog for: {backup_def}")
156
173
  result = create_db(backup_def, config_settings, logger, runner)
dar_backup/manager.py CHANGED
@@ -27,6 +27,8 @@ import os
27
27
  import re
28
28
  import sys
29
29
  import subprocess
30
+ import threading
31
+ import shlex
30
32
 
31
33
  from inputimeout import inputimeout, TimeoutOccurred
32
34
 
@@ -35,6 +37,8 @@ from . import __about__ as about
35
37
  from dar_backup.config_settings import ConfigSettings
36
38
  from dar_backup.util import setup_logging
37
39
  from dar_backup.util import CommandResult
40
+ from dar_backup.util import get_config_file
41
+ from dar_backup.util import send_discord_message
38
42
  from dar_backup.util import get_logger
39
43
  from dar_backup.util import get_binary_info
40
44
  from dar_backup.util import show_version
@@ -47,6 +51,7 @@ from dar_backup.command_runner import CommandResult
47
51
  from dar_backup.util import backup_definition_completer, list_archive_completer, archive_content_completer, add_specific_archive_completer
48
52
 
49
53
  from datetime import datetime
54
+ from sys import stderr
50
55
  from time import time
51
56
  from typing import Dict, List, NamedTuple, Tuple
52
57
 
@@ -60,6 +65,25 @@ logger = None
60
65
  runner = None
61
66
 
62
67
 
68
+ def _open_command_log(command: List[str]):
69
+ command_logger = get_logger(command_output_logger=True)
70
+ log_path = None
71
+ for handler in getattr(command_logger, "handlers", []):
72
+ if hasattr(handler, "baseFilename"):
73
+ log_path = handler.baseFilename
74
+ break
75
+ if not log_path:
76
+ return None, None
77
+ log_file = open(log_path, "ab")
78
+ header = (
79
+ f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - COMMAND: "
80
+ f"{' '.join(map(shlex.quote, command))}\n"
81
+ ).encode("utf-8", errors="replace")
82
+ log_file.write(header)
83
+ log_file.flush()
84
+ return log_file, threading.Lock()
85
+
86
+
63
87
  def get_db_dir(config_settings: ConfigSettings) -> str:
64
88
  """
65
89
  Return the correct directory for storing catalog databases.
@@ -129,24 +153,106 @@ def list_catalogs(backup_def: str, config_settings: ConfigSettings, suppress_out
129
153
  return CommandResult(1, '', error_msg)
130
154
 
131
155
  command = ['dar_manager', '--base', database_path, '--list']
132
- process = runner.run(command)
133
- stdout, stderr = process.stdout, process.stderr
156
+ if runner is not None and not hasattr(runner, "default_capture_limit_bytes"):
157
+ process = runner.run(command, capture_output_limit_bytes=-1)
158
+ stdout, stderr = process.stdout, process.stderr
134
159
 
135
- if process.returncode != 0:
136
- logger.error(f'Error listing catalogs for: "{database_path}"')
137
- logger.error(f"stderr: {stderr}")
138
- logger.error(f"stdout: {stdout}")
139
- return process
140
-
141
- # Extract only archive basenames from stdout
142
- archive_names = []
143
- for line in stdout.splitlines():
144
- line = line.strip()
145
- if not line or "archive #" in line or "dar path" in line or "compression" in line:
146
- continue
147
- parts = line.split("\t")
148
- if len(parts) >= 3:
149
- archive_names.append(parts[2].strip())
160
+ if process.returncode != 0:
161
+ logger.error(f'Error listing catalogs for: "{database_path}"')
162
+ logger.error(f"stderr: {stderr}")
163
+ logger.error(f"stdout: {stdout}")
164
+ return process
165
+
166
+ # Extract only archive basenames from stdout
167
+ archive_names = []
168
+ archive_lines = []
169
+ for line in stdout.splitlines():
170
+ line = line.strip()
171
+ if not line or "archive #" in line or "dar path" in line or "compression" in line:
172
+ continue
173
+ parts = line.split("\t")
174
+ if len(parts) >= 3:
175
+ archive_names.append(parts[2].strip())
176
+ archive_lines.append(line)
177
+ else:
178
+ stderr_lines: List[str] = []
179
+ stderr_bytes = 0
180
+ cap = getattr(config_settings, "command_capture_max_bytes", None)
181
+ if not isinstance(cap, int):
182
+ cap = None
183
+ log_file, log_lock = _open_command_log(command)
184
+
185
+ process = subprocess.Popen(
186
+ command,
187
+ stdout=subprocess.PIPE,
188
+ stderr=subprocess.PIPE,
189
+ text=False,
190
+ bufsize=0
191
+ )
192
+
193
+ def read_stderr():
194
+ nonlocal stderr_bytes
195
+ if process.stderr is None:
196
+ return
197
+ while True:
198
+ chunk = process.stderr.read(1024)
199
+ if not chunk:
200
+ break
201
+ if log_file:
202
+ with log_lock:
203
+ log_file.write(chunk)
204
+ log_file.flush()
205
+ if cap is None:
206
+ stderr_lines.append(chunk)
207
+ elif cap > 0 and stderr_bytes < cap:
208
+ remaining = cap - stderr_bytes
209
+ if len(chunk) <= remaining:
210
+ stderr_lines.append(chunk)
211
+ stderr_bytes += len(chunk)
212
+ else:
213
+ stderr_lines.append(chunk[:remaining])
214
+ stderr_bytes = cap
215
+
216
+ stderr_thread = threading.Thread(target=read_stderr)
217
+ stderr_thread.start()
218
+
219
+ archive_names = []
220
+ archive_lines = []
221
+ if process.stdout is not None:
222
+ buffer = b""
223
+ while True:
224
+ chunk = process.stdout.read(1024)
225
+ if not chunk:
226
+ break
227
+ if log_file:
228
+ with log_lock:
229
+ log_file.write(chunk)
230
+ buffer += chunk
231
+ while b"\n" in buffer:
232
+ line, buffer = buffer.split(b"\n", 1)
233
+ stripped = line.strip()
234
+ if not stripped:
235
+ continue
236
+ decoded = stripped.decode("utf-8", errors="replace")
237
+ if "archive #" in decoded or "dar path" in decoded or "compression" in decoded:
238
+ continue
239
+ parts = decoded.split("\t")
240
+ if len(parts) >= 3:
241
+ archive_names.append(parts[2].strip())
242
+ archive_lines.append(decoded)
243
+ process.stdout.close()
244
+
245
+ process.wait()
246
+ stderr_thread.join()
247
+ if log_file:
248
+ log_file.close()
249
+
250
+ if process.returncode != 0:
251
+ logger.error(f'Error listing catalogs for: "{database_path}"')
252
+ stderr_text = "".join(stderr_lines)
253
+ if stderr_text:
254
+ logger.error(f"stderr: {stderr_text}")
255
+ return CommandResult(process.returncode, "", stderr_text)
150
256
 
151
257
  # Sort by prefix and date
152
258
  def extract_date(arch_name):
@@ -165,7 +271,7 @@ def list_catalogs(backup_def: str, config_settings: ConfigSettings, suppress_out
165
271
  for name in archive_names:
166
272
  print(name)
167
273
 
168
- return process
274
+ return CommandResult(0, "\n".join(archive_lines), "")
169
275
 
170
276
 
171
277
  def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
@@ -182,9 +288,7 @@ def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
182
288
  if process.returncode != 0:
183
289
  logger.error(f"Error listing catalogs for backup def: '{backup_def}'")
184
290
  return -1
185
- line_no = 1
186
291
  for line in process.stdout.splitlines():
187
- line_no += 1
188
292
  search = re.search(rf".*?(\d+)\s+.*?({archive}).*", line)
189
293
  if search:
190
294
  logger.info(f"Found archive: '{archive}', catalog #: '{search.group(1)}'")
@@ -213,26 +317,104 @@ def list_archive_contents(archive: str, config_settings: ConfigSettings) -> int:
213
317
 
214
318
 
215
319
  command = ['dar_manager', '--base', database_path, '-u', f"{cat_no}"]
216
- process = runner.run(command, timeout = 10)
320
+ if runner is not None and not hasattr(runner, "default_capture_limit_bytes"):
321
+ process = runner.run(command, timeout=10)
322
+ stdout = process.stdout or ""
323
+ stderr = process.stderr or ""
324
+ if process.returncode != 0:
325
+ logger.error(f'Error listing catalogs for: "{database_path}"')
326
+ logger.error(f"stderr: {stderr}")
327
+ logger.error(f"stdout: {stdout}")
328
+ return process.returncode
217
329
 
330
+ combined_lines = (stdout + "\n" + stderr).splitlines()
331
+ file_lines = [line for line in combined_lines if line.strip().startswith("[ Saved ]")]
218
332
 
219
- stdout = process.stdout or ""
220
- stderr = process.stderr or ""
333
+ if file_lines:
334
+ for line in file_lines:
335
+ print(line)
336
+ else:
337
+ print(f"[info] Archive '{archive}' is empty.")
338
+
339
+ return process.returncode
340
+
341
+ stderr_lines: List[str] = []
342
+ stderr_bytes = 0
343
+ cap = getattr(config_settings, "command_capture_max_bytes", None)
344
+ log_file, log_lock = _open_command_log(command)
345
+
346
+ process = subprocess.Popen(
347
+ command,
348
+ stdout=subprocess.PIPE,
349
+ stderr=subprocess.PIPE,
350
+ text=False,
351
+ bufsize=0
352
+ )
353
+
354
+ def read_stderr():
355
+ nonlocal stderr_bytes
356
+ if process.stderr is None:
357
+ return
358
+ while True:
359
+ chunk = process.stderr.read(1024)
360
+ if not chunk:
361
+ break
362
+ if log_file:
363
+ with log_lock:
364
+ log_file.write(chunk)
365
+ log_file.flush()
366
+ if cap is None:
367
+ stderr_lines.append(chunk)
368
+ elif cap > 0 and stderr_bytes < cap:
369
+ remaining = cap - stderr_bytes
370
+ if len(chunk) <= remaining:
371
+ stderr_lines.append(chunk)
372
+ stderr_bytes += len(chunk)
373
+ else:
374
+ stderr_lines.append(chunk[:remaining])
375
+ stderr_bytes = cap
376
+
377
+ stderr_thread = threading.Thread(target=read_stderr)
378
+ stderr_thread.start()
379
+
380
+ found = False
381
+ if process.stdout is not None:
382
+ buffer = b""
383
+ while True:
384
+ chunk = process.stdout.read(1024)
385
+ if not chunk:
386
+ break
387
+ if log_file:
388
+ with log_lock:
389
+ log_file.write(chunk)
390
+ buffer += chunk
391
+ while b"\n" in buffer:
392
+ line, buffer = buffer.split(b"\n", 1)
393
+ if line.strip().startswith(b"[ Saved ]"):
394
+ print(line.decode("utf-8", errors="replace"))
395
+ found = True
396
+ process.stdout.close()
221
397
 
398
+ try:
399
+ process.wait(timeout=10)
400
+ except subprocess.TimeoutExpired:
401
+ process.kill()
402
+ stderr_thread.join()
403
+ logger.error(f"Timeout listing contents of archive: '{archive}'")
404
+ return 1
405
+
406
+ stderr_thread.join()
407
+ if log_file:
408
+ log_file.close()
222
409
 
223
410
  if process.returncode != 0:
224
411
  logger.error(f'Error listing catalogs for: "{database_path}"')
225
- logger.error(f"stderr: {stderr}")
226
- logger.error(f"stdout: {stdout}")
227
-
228
-
229
- combined_lines = (stdout + "\n" + stderr).splitlines()
230
- file_lines = [line for line in combined_lines if line.strip().startswith("[ Saved ]")]
412
+ stderr_text = "".join(stderr_lines)
413
+ if stderr_text:
414
+ logger.error(f"stderr: {stderr_text}")
415
+ return process.returncode
231
416
 
232
- if file_lines:
233
- for line in file_lines:
234
- print(line)
235
- else:
417
+ if not found:
236
418
  print(f"[info] Archive '{archive}' is empty.")
237
419
 
238
420
  return process.returncode
@@ -250,7 +432,7 @@ def list_catalog_contents(catalog_number: int, backup_def: str, config_settings:
250
432
  logger.error(f'Catalog database not found: "{database_path}"')
251
433
  return 1
252
434
  command = ['dar_manager', '--base', database_path, '-u', f"{catalog_number}"]
253
- process = runner.run(command)
435
+ process = runner.run(command, capture_output_limit_bytes=-1)
254
436
  stdout, stderr = process.stdout, process.stderr
255
437
  if process.returncode != 0:
256
438
  logger.error(f'Error listing catalogs for: "{database_path}"')
@@ -271,7 +453,7 @@ def find_file(file, backup_def, config_settings):
271
453
  logger.error(f'Database not found: "{database_path}"')
272
454
  return 1
273
455
  command = ['dar_manager', '--base', database_path, '-f', f"{file}"]
274
- process = runner.run(command)
456
+ process = runner.run(command, capture_output_limit_bytes=-1)
275
457
  stdout, stderr = process.stdout, process.stderr
276
458
  if process.returncode != 0:
277
459
  logger.error(f'Error finding file: {file} in: "{database_path}"')
@@ -492,7 +674,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
492
674
 
493
675
  def build_arg_parser():
494
676
  parser = argparse.ArgumentParser(description="Creates/maintains `dar` database catalogs")
495
- parser.add_argument('-c', '--config-file', type=str, help="Path to 'dar-backup.conf'", default='~/.config/dar-backup/dar-backup.conf')
677
+ parser.add_argument('-c', '--config-file', type=str, help="Path to 'dar-backup.conf'", default=None)
496
678
  parser.add_argument('--create-db', action='store_true', help='Create missing databases for all backup definitions')
497
679
  parser.add_argument('--alternate-archive-dir', type=str, help='Use this directory instead of BACKUP_DIR in config file')
498
680
  parser.add_argument('--add-dir', type=str, help='Add all archive catalogs in this directory to databases')
@@ -536,8 +718,20 @@ def main():
536
718
  show_version()
537
719
  sys.exit(0)
538
720
 
539
- args.config_file = os.path.expanduser(os.path.expandvars(args.config_file))
540
- config_settings = ConfigSettings(args.config_file)
721
+ config_settings_path = get_config_file(args)
722
+ if not (os.path.isfile(config_settings_path) and os.access(config_settings_path, os.R_OK)):
723
+ print(f"Config file {config_settings_path} must exist and be readable.", file=stderr)
724
+ raise SystemExit(127)
725
+ args.config_file = config_settings_path
726
+
727
+ try:
728
+ config_settings = ConfigSettings(args.config_file)
729
+ except Exception as exc:
730
+ msg = f"Config error: {exc}"
731
+ print(msg, file=stderr)
732
+ ts = datetime.now().strftime("%Y-%m-%d_%H:%M")
733
+ send_discord_message(f"{ts} - manager: FAILURE - {msg}")
734
+ sys.exit(127)
541
735
 
542
736
  if not os.path.dirname(config_settings.logfile_location):
543
737
  print(f"Directory for log file '{config_settings.logfile_location}' does not exist, exiting")
@@ -545,16 +739,28 @@ def main():
545
739
  return
546
740
 
547
741
  command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
548
- logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout, logfile_max_bytes=config_settings.logfile_max_bytes, logfile_backup_count=config_settings.logfile_backup_count)
742
+ logger = setup_logging(
743
+ config_settings.logfile_location,
744
+ command_output_log,
745
+ args.log_level,
746
+ args.log_stdout,
747
+ logfile_max_bytes=config_settings.logfile_max_bytes,
748
+ logfile_backup_count=config_settings.logfile_backup_count,
749
+ trace_log_max_bytes=getattr(config_settings, "trace_log_max_bytes", 10485760),
750
+ trace_log_backup_count=getattr(config_settings, "trace_log_backup_count", 1)
751
+ )
549
752
  command_logger = get_logger(command_output_logger=True)
550
- runner = CommandRunner(logger=logger, command_logger=command_logger)
753
+ runner = CommandRunner(
754
+ logger=logger,
755
+ command_logger=command_logger,
756
+ default_capture_limit_bytes=getattr(config_settings, "command_capture_max_bytes", None)
757
+ )
551
758
 
552
759
  start_msgs: List[Tuple[str, str]] = []
553
760
 
554
761
  start_time = int(time())
555
762
 
556
763
  start_msgs.append((f"{show_scriptname()}:", about.__version__))
557
- logger.info(f"START TIME: {start_time}")
558
764
  logger.debug(f"Command line: {get_invocation_command_line()}")
559
765
  logger.debug(f"`args`:\n{args}")
560
766
  logger.debug(f"`config_settings`:\n{config_settings}")
@@ -620,63 +826,70 @@ def main():
620
826
  return
621
827
 
622
828
  # --- Modify settings ---
623
- if args.alternate_archive_dir:
624
- if not os.path.exists(args.alternate_archive_dir):
625
- logger.error(f"Alternate archive dir '{args.alternate_archive_dir}' does not exist, exiting")
626
- sys.exit(1)
829
+ try:
830
+ if args.alternate_archive_dir:
831
+ if not os.path.exists(args.alternate_archive_dir):
832
+ logger.error(f"Alternate archive dir '{args.alternate_archive_dir}' does not exist, exiting")
833
+ sys.exit(1)
834
+ return
835
+ config_settings.backup_dir = args.alternate_archive_dir
836
+
837
+ # --- Functional logic ---
838
+ if args.create_db:
839
+ if args.backup_def:
840
+ sys.exit(create_db(args.backup_def, config_settings, logger, runner))
841
+ return
842
+ else:
843
+ for root, dirs, files in os.walk(config_settings.backup_d_dir):
844
+ for file in files:
845
+ current_backupdef = os.path.basename(file)
846
+ logger.debug(f"Create catalog db for backup definition: '{current_backupdef}'")
847
+ result = create_db(current_backupdef, config_settings, logger, runner)
848
+ if result != 0:
849
+ sys.exit(result)
850
+ return
851
+
852
+ if args.add_specific_archive:
853
+ sys.exit(add_specific_archive(args.add_specific_archive, config_settings))
627
854
  return
628
- config_settings.backup_dir = args.alternate_archive_dir
629
855
 
630
- # --- Functional logic ---
631
- if args.create_db:
632
- if args.backup_def:
633
- sys.exit(create_db(args.backup_def, config_settings, logger, runner))
856
+ if args.add_dir:
857
+ sys.exit(add_directory(args, config_settings))
634
858
  return
635
- else:
636
- for root, dirs, files in os.walk(config_settings.backup_d_dir):
637
- for file in files:
638
- current_backupdef = os.path.basename(file)
639
- logger.debug(f"Create catalog db for backup definition: '{current_backupdef}'")
640
- result = create_db(current_backupdef, config_settings, logger, runner)
641
- if result != 0:
642
- sys.exit(result)
643
- return
644
-
645
- if args.add_specific_archive:
646
- sys.exit(add_specific_archive(args.add_specific_archive, config_settings))
647
- return
648
859
 
649
- if args.add_dir:
650
- sys.exit(add_directory(args, config_settings))
651
- return
652
-
653
- if args.remove_specific_archive:
654
- return remove_specific_archive(args.remove_specific_archive, config_settings)
655
-
656
- if args.list_catalogs:
657
- if args.backup_def:
658
- process = list_catalogs(args.backup_def, config_settings)
659
- result = process.returncode
660
- else:
661
- result = 0
662
- for root, dirs, files in os.walk(config_settings.backup_d_dir):
663
- for file in files:
664
- current_backupdef = os.path.basename(file)
665
- if list_catalogs(current_backupdef, config_settings).returncode != 0:
666
- result = 1
667
- sys.exit(result)
668
- return
860
+ if args.remove_specific_archive:
861
+ return remove_specific_archive(args.remove_specific_archive, config_settings)
862
+
863
+ if args.list_catalogs:
864
+ if args.backup_def:
865
+ process = list_catalogs(args.backup_def, config_settings)
866
+ result = process.returncode
867
+ else:
868
+ result = 0
869
+ for root, dirs, files in os.walk(config_settings.backup_d_dir):
870
+ for file in files:
871
+ current_backupdef = os.path.basename(file)
872
+ if list_catalogs(current_backupdef, config_settings).returncode != 0:
873
+ result = 1
874
+ sys.exit(result)
875
+ return
669
876
 
670
- if args.list_archive_contents:
671
- result = list_archive_contents(args.list_archive_contents, config_settings)
672
- sys.exit(result)
673
- return
877
+ if args.list_archive_contents:
878
+ result = list_archive_contents(args.list_archive_contents, config_settings)
879
+ sys.exit(result)
880
+ return
674
881
 
675
882
 
676
- if args.find_file:
677
- result = find_file(args.find_file, args.backup_def, config_settings)
678
- sys.exit(result)
679
- return
883
+ if args.find_file:
884
+ result = find_file(args.find_file, args.backup_def, config_settings)
885
+ sys.exit(result)
886
+ return
887
+ except Exception as e:
888
+ msg = f"Unexpected error during manager operation: {e}"
889
+ logger.error(msg, exc_info=True)
890
+ ts = datetime.now().strftime("%Y-%m-%d_%H:%M")
891
+ send_discord_message(f"{ts} - manager: FAILURE - {msg}", config_settings=config_settings)
892
+ sys.exit(1)
680
893
 
681
894
 
682
895
  if __name__ == "__main__":