dar-backup 0.6.20__py3-none-any.whl → 0.6.21__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/Changelog.md +22 -0
- dar_backup/README.md +413 -228
- dar_backup/__about__.py +6 -1
- dar_backup/cleanup.py +2 -0
- dar_backup/dar_backup.py +24 -5
- dar_backup/demo.py +2 -5
- dar_backup/installer.py +41 -16
- dar_backup/manager.py +3 -2
- dar_backup/util.py +98 -15
- {dar_backup-0.6.20.dist-info → dar_backup-0.6.21.dist-info}/METADATA +414 -229
- dar_backup-0.6.21.dist-info/RECORD +23 -0
- {dar_backup-0.6.20.dist-info → dar_backup-0.6.21.dist-info}/entry_points.txt +1 -0
- dar_backup-0.6.20.dist-info/RECORD +0 -23
- {dar_backup-0.6.20.dist-info → dar_backup-0.6.21.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.20.dist-info → dar_backup-0.6.21.dist-info}/licenses/LICENSE +0 -0
dar_backup/__about__.py
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.21"
|
|
2
|
+
|
|
3
|
+
__license__ = '''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
|
|
4
|
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
5
|
+
See section 15 and section 16 in the supplied "LICENSE" file.'''
|
|
6
|
+
|
dar_backup/cleanup.py
CHANGED
|
@@ -37,6 +37,7 @@ from dar_backup.util import setup_logging
|
|
|
37
37
|
from dar_backup.util import get_logger
|
|
38
38
|
from dar_backup.util import requirements
|
|
39
39
|
from dar_backup.util import show_version
|
|
40
|
+
from dar_backup.util import get_invocation_command_line
|
|
40
41
|
from dar_backup.util import print_aligned_settings
|
|
41
42
|
from dar_backup.util import backup_definition_completer, list_archive_completer
|
|
42
43
|
|
|
@@ -222,6 +223,7 @@ def main():
|
|
|
222
223
|
start_msgs.append(("cleanup.py:", about.__version__))
|
|
223
224
|
|
|
224
225
|
logger.info(f"START TIME: {start_time}")
|
|
226
|
+
logger.debug(f"Command line: {get_invocation_command_line()}")
|
|
225
227
|
logger.debug(f"`args`:\n{args}")
|
|
226
228
|
logger.debug(f"`config_settings`:\n{config_settings}")
|
|
227
229
|
|
dar_backup/dar_backup.py
CHANGED
|
@@ -51,6 +51,7 @@ from dar_backup.util import BackupError
|
|
|
51
51
|
from dar_backup.util import RestoreError
|
|
52
52
|
from dar_backup.util import requirements
|
|
53
53
|
from dar_backup.util import show_version
|
|
54
|
+
from dar_backup.util import get_invocation_command_line
|
|
54
55
|
from dar_backup.util import get_binary_info
|
|
55
56
|
from dar_backup.util import print_aligned_settings
|
|
56
57
|
from dar_backup.util import backup_definition_completer, list_archive_completer
|
|
@@ -683,12 +684,29 @@ INCR back of a single backup definition in backup.d
|
|
|
683
684
|
|
|
684
685
|
|
|
685
686
|
--selection
|
|
686
|
-
--selection takes dar selection parameters between a pair of `"`.
|
|
687
687
|
|
|
688
|
-
|
|
689
|
-
|
|
688
|
+
--selection takes dar file selection options inside a quoted string.
|
|
689
|
+
|
|
690
|
+
💡 Shell quoting matters! Always wrap the entire selection string in double quotes to avoid shell splitting.
|
|
691
|
+
|
|
692
|
+
✅ Use: --selection="-I '*.NEF'"
|
|
693
|
+
❌ Avoid: --selection "-I '*.NEF'" → may break due to how your shell parses it.
|
|
694
|
+
|
|
695
|
+
Examples:
|
|
696
|
+
1)
|
|
697
|
+
select file names with "Z50_" in file names:
|
|
698
|
+
python3 dar-backup.py --restore <name of dar archive> --selection="-I '*Z50_*'"
|
|
699
|
+
2)
|
|
700
|
+
Filter out *.xmp files:
|
|
701
|
+
python3 dar-backup.py --restore <name of dar archive> --selection="-X '*.xmp'"
|
|
702
|
+
|
|
703
|
+
3)
|
|
704
|
+
Include all files in a directory:
|
|
705
|
+
python3 dar-backup.py --restore <name of dar archive> --selection="-g 'path/to/a/dir'"
|
|
690
706
|
|
|
691
|
-
|
|
707
|
+
4)
|
|
708
|
+
Exclude a directory:
|
|
709
|
+
python3 dar-backup.py --restore <name of dar archive> --selection="-P 'path/to/a/dir'"
|
|
692
710
|
|
|
693
711
|
See dar documentation on file selection: http://dar.linux.free.fr/doc/man/dar.html#COMMANDS%20AND%20OPTIONS
|
|
694
712
|
"""
|
|
@@ -764,7 +782,7 @@ def main():
|
|
|
764
782
|
parser.add_argument('--darrc', type=str, help='Optional path to .darrc')
|
|
765
783
|
parser.add_argument('-l', '--list', action='store_true', help="List available archives.").completer = list_archive_completer
|
|
766
784
|
parser.add_argument('--list-contents', help="List the contents of the specified archive.").completer = list_archive_completer
|
|
767
|
-
parser.add_argument('--selection', help="
|
|
785
|
+
parser.add_argument('--selection', type=str, help="Selection string to pass to 'dar', e.g. --selection=\"-I '*.NEF'\"")
|
|
768
786
|
# parser.add_argument('-r', '--restore', nargs=1, type=str, help="Restore specified archive.")
|
|
769
787
|
parser.add_argument('-r', '--restore', type=str, help="Restore specified archive.").completer = list_archive_completer
|
|
770
788
|
parser.add_argument('--restore-dir', type=str, help="Directory to restore files to.")
|
|
@@ -847,6 +865,7 @@ def main():
|
|
|
847
865
|
start_time=int(time())
|
|
848
866
|
start_msgs.append(('dar-backup.py:', about.__version__))
|
|
849
867
|
logger.info(f"START TIME: {start_time}")
|
|
868
|
+
logger.debug(f"Command line: {get_invocation_command_line()}")
|
|
850
869
|
logger.debug(f"{'`Args`:\n'}{args}")
|
|
851
870
|
logger.debug(f"{'`Config_settings`:\n'}{config_settings}")
|
|
852
871
|
dar_properties = get_binary_info(command='dar')
|
dar_backup/demo.py
CHANGED
|
@@ -19,11 +19,8 @@ import shutil
|
|
|
19
19
|
import sys
|
|
20
20
|
|
|
21
21
|
from . import __about__ as about
|
|
22
|
-
from pathlib import Path
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
26
|
-
See section 15 and section 16 in the supplied "LICENSE" file.'''
|
|
23
|
+
from pathlib import Path
|
|
27
24
|
|
|
28
25
|
CONFIG_DIR = os.path.expanduser("~/.config/dar-backup")
|
|
29
26
|
DAR_BACKUP_DIR = os.path.expanduser("~/dar-backup/")
|
|
@@ -76,7 +73,7 @@ def main():
|
|
|
76
73
|
parser.add_argument(
|
|
77
74
|
"-v", "--version",
|
|
78
75
|
action="version",
|
|
79
|
-
version=f"%(prog)s version {about.__version__}, {
|
|
76
|
+
version=f"%(prog)s version {about.__version__}, {about.__license__}"
|
|
80
77
|
)
|
|
81
78
|
|
|
82
79
|
args = parser.parse_args()
|
dar_backup/installer.py
CHANGED
|
@@ -1,58 +1,83 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import os
|
|
3
|
+
from . import __about__ as about
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from dar_backup.config_settings import ConfigSettings
|
|
5
6
|
from dar_backup.util import setup_logging, get_logger
|
|
6
7
|
from dar_backup.command_runner import CommandRunner
|
|
7
8
|
from dar_backup.manager import create_db
|
|
9
|
+
# Always expand manager DB dir correctly, using helper function
|
|
10
|
+
from dar_backup.manager import get_db_dir
|
|
11
|
+
from dar_backup.util import expand_path
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
def run_installer(config_file: str, create_db_flag: bool):
|
|
16
|
+
"""
|
|
17
|
+
Run the installation process for dar-backup using the given config file.
|
|
18
|
+
|
|
19
|
+
This includes:
|
|
20
|
+
- Expanding and parsing the config file
|
|
21
|
+
- Setting up logging
|
|
22
|
+
- Creating required backup directories
|
|
23
|
+
- Optionally initializing catalog databases for all backup definitions
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
config_file (str): Path to the configuration file (may include ~ or env vars).
|
|
27
|
+
create_db_flag (bool): If True, databases are initialized for each backup definition.
|
|
28
|
+
"""
|
|
11
29
|
config_file = os.path.expanduser(os.path.expandvars(config_file))
|
|
12
30
|
config_settings = ConfigSettings(config_file)
|
|
13
31
|
|
|
14
|
-
# Set up logging
|
|
32
|
+
# Set up logging
|
|
15
33
|
command_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
|
|
16
34
|
logger = setup_logging(
|
|
17
35
|
config_settings.logfile_location,
|
|
18
36
|
command_log,
|
|
19
37
|
log_level="info",
|
|
20
|
-
|
|
38
|
+
log_to_stdout=True,
|
|
21
39
|
)
|
|
22
40
|
command_logger = get_logger(command_output_logger=True)
|
|
23
41
|
runner = CommandRunner(logger=logger, command_logger=command_logger)
|
|
24
42
|
|
|
25
|
-
# Create directories listed in config
|
|
26
|
-
for attr in ["backup_dir", "test_restore_dir", "backup_d_dir", "manager_db_dir"]:
|
|
27
|
-
path = getattr(config_settings, attr, None)
|
|
28
|
-
if path:
|
|
29
|
-
dir_path = Path(path).expanduser()
|
|
30
|
-
if not dir_path.exists():
|
|
31
|
-
dir_path.mkdir(parents=True, exist_ok=True)
|
|
32
|
-
print(f"Created directory: {dir_path}")
|
|
33
|
-
else:
|
|
34
|
-
print(f"Directory already exists: {dir_path}")
|
|
35
43
|
|
|
36
|
-
#
|
|
44
|
+
# Create required directories
|
|
45
|
+
required_dirs = {
|
|
46
|
+
"backup_dir": config_settings.backup_dir,
|
|
47
|
+
"test_restore_dir": config_settings.test_restore_dir,
|
|
48
|
+
"backup_d_dir": config_settings.backup_d_dir,
|
|
49
|
+
"manager_db_dir": get_db_dir(config_settings),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for name, dir_path in required_dirs.items():
|
|
53
|
+
expanded = Path(expand_path(dir_path))
|
|
54
|
+
if not expanded.exists():
|
|
55
|
+
logger.info(f"Creating directory: {expanded} ({name})")
|
|
56
|
+
expanded.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
# Optionally create databases for all backup definitions
|
|
37
59
|
if create_db_flag:
|
|
38
60
|
for file in os.listdir(config_settings.backup_d_dir):
|
|
39
61
|
backup_def = os.path.basename(file)
|
|
40
62
|
print(f"Creating catalog for: {backup_def}")
|
|
41
|
-
result = create_db(backup_def, config_settings, logger)
|
|
63
|
+
result = create_db(backup_def, config_settings, logger, runner)
|
|
42
64
|
if result == 0:
|
|
43
65
|
print(f"✔️ Catalog created (or already existed): {backup_def}")
|
|
44
66
|
else:
|
|
45
67
|
print(f"❌ Failed to create catalog: {backup_def}")
|
|
46
68
|
|
|
47
69
|
|
|
48
|
-
def
|
|
70
|
+
def main():
|
|
49
71
|
parser = argparse.ArgumentParser(description="dar-backup installer")
|
|
50
72
|
parser.add_argument("--config", required=True, help="Path to config file")
|
|
51
73
|
parser.add_argument("--create-db", action="store_true", help="Create catalog databases")
|
|
74
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s version {about.__version__}, {about.__license__}"
|
|
75
|
+
)
|
|
76
|
+
|
|
52
77
|
args = parser.parse_args()
|
|
53
78
|
|
|
54
79
|
run_installer(args.config, args.create_db)
|
|
55
80
|
|
|
56
81
|
|
|
57
82
|
if __name__ == "__main__":
|
|
58
|
-
|
|
83
|
+
main()
|
dar_backup/manager.py
CHANGED
|
@@ -37,6 +37,7 @@ from dar_backup.util import CommandResult
|
|
|
37
37
|
from dar_backup.util import get_logger
|
|
38
38
|
from dar_backup.util import get_binary_info
|
|
39
39
|
from dar_backup.util import show_version
|
|
40
|
+
from dar_backup.util import get_invocation_command_line
|
|
40
41
|
from dar_backup.util import print_aligned_settings
|
|
41
42
|
|
|
42
43
|
from dar_backup.command_runner import CommandRunner
|
|
@@ -503,7 +504,7 @@ def build_arg_parser():
|
|
|
503
504
|
parser.add_argument('--log-level', type=str, help="`debug` or `trace`, default is `info`", default="info")
|
|
504
505
|
parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
|
|
505
506
|
parser.add_argument('--more-help', action='store_true', help='Show extended help message')
|
|
506
|
-
parser.add_argument('--version', action='store_true', help='Show version & license')
|
|
507
|
+
parser.add_argument('-v', '--version', action='store_true', help='Show version & license')
|
|
507
508
|
|
|
508
509
|
return parser
|
|
509
510
|
|
|
@@ -535,7 +536,6 @@ def main():
|
|
|
535
536
|
|
|
536
537
|
args.config_file = os.path.expanduser(os.path.expandvars(args.config_file))
|
|
537
538
|
config_settings = ConfigSettings(args.config_file)
|
|
538
|
-
print(f"Config settings: {config_settings}")
|
|
539
539
|
|
|
540
540
|
if not os.path.dirname(config_settings.logfile_location):
|
|
541
541
|
print(f"Directory for log file '{config_settings.logfile_location}' does not exist, exiting")
|
|
@@ -552,6 +552,7 @@ def main():
|
|
|
552
552
|
start_time = int(time())
|
|
553
553
|
start_msgs.append((f"{SCRIPTNAME}:", about.__version__))
|
|
554
554
|
logger.info(f"START TIME: {start_time}")
|
|
555
|
+
logger.debug(f"Command line: {get_invocation_command_line()}")
|
|
555
556
|
logger.debug(f"`args`:\n{args}")
|
|
556
557
|
logger.debug(f"`config_settings`:\n{config_settings}")
|
|
557
558
|
start_msgs.append(("Config file:", args.config_file))
|
dar_backup/util.py
CHANGED
|
@@ -102,13 +102,50 @@ def get_logger(command_output_logger: bool = False) -> logging.Logger:
|
|
|
102
102
|
|
|
103
103
|
return secondary_logger if command_output_logger else logger
|
|
104
104
|
|
|
105
|
+
|
|
106
|
+
# Setup completer logger only once
|
|
107
|
+
def _setup_completer_logger(logfile="/tmp/dar_backup_completer.log"):
|
|
108
|
+
logger = logging.getLogger("completer")
|
|
109
|
+
if not logger.handlers:
|
|
110
|
+
handler = logging.FileHandler(logfile)
|
|
111
|
+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
|
112
|
+
handler.setFormatter(formatter)
|
|
113
|
+
logger.addHandler(handler)
|
|
114
|
+
logger.setLevel(logging.DEBUG)
|
|
115
|
+
return logger
|
|
116
|
+
|
|
117
|
+
# Singleton logger for completer debugging
|
|
118
|
+
completer_logger = _setup_completer_logger()
|
|
119
|
+
completer_logger.debug("Completer logger initialized.")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_invocation_command_line() -> str:
|
|
123
|
+
"""
|
|
124
|
+
Safely retrieves the exact command line used to invoke the current Python process.
|
|
125
|
+
|
|
126
|
+
On Unix-like systems, this reads from /proc/[pid]/cmdline to reconstruct the
|
|
127
|
+
command with interpreter and arguments. If any error occurs (e.g., file not found,
|
|
128
|
+
permission denied, non-Unix platform), it returns a descriptive error message.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
str: The full command line string, or an error description if it cannot be retrieved.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
cmdline_path = f"/proc/{os.getpid()}/cmdline"
|
|
135
|
+
with open(cmdline_path, "rb") as f:
|
|
136
|
+
content = f.read()
|
|
137
|
+
if not content:
|
|
138
|
+
return "[error: /proc/cmdline is empty]"
|
|
139
|
+
return content.replace(b'\x00', b' ').decode().strip()
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return f"[error: could not read /proc/[pid]/cmdline: {e}]"
|
|
142
|
+
|
|
143
|
+
|
|
105
144
|
def show_version():
|
|
106
145
|
script_name = os.path.basename(sys.argv[0])
|
|
107
146
|
print(f"{script_name} {about.__version__}")
|
|
108
147
|
print(f"{script_name} source code is here: https://github.com/per2jensen/dar-backup")
|
|
109
|
-
print(
|
|
110
|
-
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
111
|
-
See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
148
|
+
print(about.__license__)
|
|
112
149
|
|
|
113
150
|
def extract_version(output):
|
|
114
151
|
match = re.search(r'(\d+\.\d+(\.\d+)?)', output)
|
|
@@ -245,6 +282,28 @@ class CommandResult(NamedTuple):
|
|
|
245
282
|
|
|
246
283
|
def list_backups(backup_dir, backup_definition=None):
|
|
247
284
|
"""
|
|
285
|
+
Lists the available backup files in the specified directory along with their total sizes in megabytes.
|
|
286
|
+
The function filters and processes `.dar` files, grouping them by their base names and ensuring proper
|
|
287
|
+
alignment of the displayed sizes.
|
|
288
|
+
Args:
|
|
289
|
+
backup_dir (str): The directory containing the backup files.
|
|
290
|
+
backup_definition (str, optional): A prefix to filter backups by their base name. Only backups
|
|
291
|
+
starting with this prefix will be included. Defaults to None.
|
|
292
|
+
Raises:
|
|
293
|
+
locale.Error: If setting the locale fails and the fallback to the 'C' locale is unsuccessful.
|
|
294
|
+
Behavior:
|
|
295
|
+
- Attempts to set the locale based on the environment for proper formatting of numbers.
|
|
296
|
+
- Filters `.dar` files in the specified directory based on the following criteria:
|
|
297
|
+
- The file name must contain one of the substrings: "_FULL_", "_DIFF_", or "_INCR_".
|
|
298
|
+
- The file name must include a date in the format "_YYYY-MM-DD".
|
|
299
|
+
- Groups files by their base name (excluding slice numbers and extensions) and calculates
|
|
300
|
+
the total size for each group in megabytes.
|
|
301
|
+
- Sorts the backups by their base name and date (if included in the name).
|
|
302
|
+
- Prints the backup names and their sizes in a formatted and aligned manner.
|
|
303
|
+
Returns:
|
|
304
|
+
None: The function prints the results directly to the console. If no backups are found,
|
|
305
|
+
it prints "No backups available.".
|
|
306
|
+
|
|
248
307
|
List the available backups in the specified directory and their sizes in megabytes, with aligned sizes.
|
|
249
308
|
"""
|
|
250
309
|
# Attempt to set locale from the environment or fall back to the default locale
|
|
@@ -346,6 +405,7 @@ def extract_backup_definition_fallback() -> str:
|
|
|
346
405
|
|
|
347
406
|
|
|
348
407
|
|
|
408
|
+
|
|
349
409
|
def list_archive_completer(prefix, parsed_args, **kwargs):
|
|
350
410
|
import os
|
|
351
411
|
import configparser
|
|
@@ -369,12 +429,39 @@ def list_archive_completer(prefix, parsed_args, **kwargs):
|
|
|
369
429
|
files = os.listdir(backup_dir)
|
|
370
430
|
archive_re = re.compile(rf"^{re.escape(backup_def)}_.+_\d{{4}}-\d{{2}}-\d{{2}}\.1\.dar$") if backup_def else re.compile(r".+_\d{4}-\d{2}-\d{2}\.1\.dar$")
|
|
371
431
|
|
|
372
|
-
|
|
432
|
+
completions = [
|
|
373
433
|
f.rsplit(".1.dar", 1)[0]
|
|
374
434
|
for f in files
|
|
375
435
|
if archive_re.match(f)
|
|
376
436
|
]
|
|
377
437
|
|
|
438
|
+
completions = sorted(set(completions), key=sort_key)
|
|
439
|
+
return completions or ["[no matching archives]"]
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def sort_key(archive_name: str):
|
|
444
|
+
"""
|
|
445
|
+
Sort by backup definition and then by date extracted from the archive name.
|
|
446
|
+
Handles formats like: <def>_<TYPE>_<YYYY-MM-DD>.<N>.dar
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
base = archive_name.split('.')[0] # remove .1.dar
|
|
450
|
+
parts = base.split('_')
|
|
451
|
+
if len(parts) < 3:
|
|
452
|
+
return (archive_name, datetime.min) # fallback for non-matching formats
|
|
453
|
+
|
|
454
|
+
# Correct assumption: last two parts are TYPE and DATE
|
|
455
|
+
def_name = '_'.join(parts[:-2]) # everything before _TYPE_DATE
|
|
456
|
+
date_str = parts[-1]
|
|
457
|
+
date = datetime.strptime(date_str, "%Y-%m-%d")
|
|
458
|
+
completer_logger.debug(f"Archive: {archive_name}, Def: {def_name}, Date: {date}")
|
|
459
|
+
return (def_name, date)
|
|
460
|
+
except Exception:
|
|
461
|
+
return (archive_name, datetime.min)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
378
465
|
|
|
379
466
|
def archive_content_completer(prefix, parsed_args, **kwargs):
|
|
380
467
|
"""
|
|
@@ -392,14 +479,15 @@ def archive_content_completer(prefix, parsed_args, **kwargs):
|
|
|
392
479
|
# Expand config path
|
|
393
480
|
config_file = expand_path(getattr(parsed_args, "config_file", "~/.config/dar-backup/dar-backup.conf"))
|
|
394
481
|
config = ConfigSettings(config_file=config_file)
|
|
395
|
-
|
|
482
|
+
#db_dir = expand_path((getattr(config, 'manager_db_dir', config.backup_dir))) # use manager_db_dir if set, else backup_dir
|
|
483
|
+
db_dir = expand_path(getattr(config, 'manager_db_dir', None) or config.backup_dir)
|
|
396
484
|
|
|
397
485
|
# Which db files to inspect?
|
|
398
486
|
backup_def = getattr(parsed_args, "backup_def", None)
|
|
399
487
|
db_files = (
|
|
400
|
-
[os.path.join(
|
|
488
|
+
[os.path.join( db_dir, f"{backup_def}.db")]
|
|
401
489
|
if backup_def
|
|
402
|
-
else [os.path.join(
|
|
490
|
+
else [os.path.join( db_dir, f) for f in os.listdir( db_dir) if f.endswith(".db")]
|
|
403
491
|
)
|
|
404
492
|
|
|
405
493
|
completions = []
|
|
@@ -426,13 +514,6 @@ def archive_content_completer(prefix, parsed_args, **kwargs):
|
|
|
426
514
|
if archive.startswith(prefix):
|
|
427
515
|
completions.append(archive)
|
|
428
516
|
|
|
429
|
-
# Sort: first by name (before first '_'), then by date (YYYY-MM-DD)
|
|
430
|
-
def sort_key(archive):
|
|
431
|
-
name_part = archive.split("_")[0]
|
|
432
|
-
date_match = re.search(r"\d{4}-\d{2}-\d{2}", archive)
|
|
433
|
-
date = datetime.strptime(date_match.group(0), "%Y-%m-%d") if date_match else datetime.min
|
|
434
|
-
return (name_part, date)
|
|
435
|
-
|
|
436
517
|
completions = sorted(set(completions), key=sort_key)
|
|
437
518
|
return completions or ["[no matching archives]"]
|
|
438
519
|
|
|
@@ -452,6 +533,8 @@ def add_specific_archive_completer(prefix, parsed_args, **kwargs):
|
|
|
452
533
|
|
|
453
534
|
config_file = expand_path(getattr(parsed_args, "config_file", "~/.config/dar-backup/dar-backup.conf"))
|
|
454
535
|
config = ConfigSettings(config_file=config_file)
|
|
536
|
+
#db_dir = expand_path((getattr(config, 'manager_db_dir', config.backup_dir))) # use manager_db_dir if set, else backup_dir
|
|
537
|
+
db_dir = expand_path(getattr(config, 'manager_db_dir') or config.backup_dir)
|
|
455
538
|
backup_dir = config.backup_dir
|
|
456
539
|
backup_def = getattr(parsed_args, "backup_def", None)
|
|
457
540
|
|
|
@@ -469,7 +552,7 @@ def add_specific_archive_completer(prefix, parsed_args, **kwargs):
|
|
|
469
552
|
all_archives.add(base)
|
|
470
553
|
|
|
471
554
|
# Step 2: exclude ones already present in the .db
|
|
472
|
-
db_path = os.path.join(
|
|
555
|
+
db_path = os.path.join(db_dir, f"{backup_def}.db") if backup_def else None
|
|
473
556
|
existing = set()
|
|
474
557
|
|
|
475
558
|
if db_path and os.path.exists(db_path):
|