dar-backup 0.6.10__py3-none-any.whl → 0.6.11__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/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.6.10"
1
+ __version__ = "0.6.11"
dar_backup/dar_backup.py CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
3
  import argparse
4
- import datetime
5
4
  import filecmp
6
5
 
7
6
  import os
@@ -81,30 +80,31 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
81
80
  raise BackupError(f"Unexpected error during backup: {e}") from e
82
81
 
83
82
 
84
- # Function to recursively find <File> tags and build their full paths
85
- def find_files_with_paths(element: ET, current_path=""):
83
+
84
+ def find_files_with_paths(xml_root: ET.Element):
86
85
  """
87
- Recursively finds files within a directory element and returns a list of file paths with their sizes.
86
+ Finds files within an XML element and returns a list of file paths with their sizes.
88
87
 
89
88
  Args:
90
- element (Element): The directory element to search within.
91
- current_path (str, optional): The current path of the directory element. Defaults to "".
89
+ xml_root (Element): The root element of the XML.
92
90
 
93
91
  Returns:
94
92
  list: A list of tuples containing file paths and their sizes.
95
93
  """
96
- logger.debug(f"Recursively generate list of tuples with file paths and sizes for File elements in dar xml output")
94
+ logger.debug("Generating list of tuples with file paths and sizes for File elements in dar xml output")
97
95
  files = []
98
- if element.tag == "Directory":
99
- current_path = f"{current_path}/{element.get('name')}"
100
- for child in element:
101
- if child.tag == "File":
102
- file_path = (f"{current_path}/{child.get('name')}", child.get('size')) # tuple (filepath, size)
96
+ current_path = []
97
+
98
+ for elem in xml_root.iter():
99
+ if elem.tag == "directory":
100
+ current_path.append(elem.get('name'))
101
+ elif elem.tag == "file":
102
+ file_path = ("/".join(current_path + [elem.get('name')]), elem.get('size'))
103
103
  files.append(file_path)
104
- elif child.tag == "Directory":
105
- files.extend(find_files_with_paths(child, current_path))
106
- return files
104
+ elif elem.tag == "directory" and elem.get('name') in current_path:
105
+ current_path.pop()
107
106
 
107
+ return files
108
108
 
109
109
 
110
110
  def find_files_between_min_and_max_size(backed_up_files: list[(str, str)], config_settings: ConfigSettings):
@@ -238,20 +238,20 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
238
238
  restore_dir (str): The directory where the backup should be restored to.
239
239
  selection (str, optional): A selection criteria to restore specific files or directories. Defaults to None.
240
240
  """
241
- backup_file = os.path.join(config_settings.backup_dir, backup_name)
242
- command = ['dar', '-x', backup_file, '-Q', '-D']
243
- if restore_dir:
244
- if not os.path.exists(restore_dir):
245
- os.makedirs(restore_dir)
246
- command.extend(['-R', restore_dir])
247
- else:
248
- raise RestoreError("Restore directory ('-R <dir>') not specified")
249
- if selection:
250
- selection_criteria = shlex.split(selection)
251
- command.extend(selection_criteria)
252
- command.extend(['-B', darrc, 'restore-options']) # the .darrc `restore-options` section
253
- logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
254
241
  try:
242
+ backup_file = os.path.join(config_settings.backup_dir, backup_name)
243
+ command = ['dar', '-x', backup_file, '-Q', '-D']
244
+ if restore_dir:
245
+ if not os.path.exists(restore_dir):
246
+ os.makedirs(restore_dir)
247
+ command.extend(['-R', restore_dir])
248
+ else:
249
+ raise RestoreError("Restore directory ('-R <dir>') not specified")
250
+ if selection:
251
+ selection_criteria = shlex.split(selection)
252
+ command.extend(selection_criteria)
253
+ command.extend(['-B', darrc, 'restore-options']) # the .darrc `restore-options` section
254
+ logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
255
255
  process = run_command(command, config_settings.command_timeout_secs)
256
256
  if process.returncode == 0:
257
257
  logger.info(f"Restore completed successfully to: '{restore_dir}'")
@@ -260,6 +260,9 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
260
260
  raise RestoreError(str(process))
261
261
  except subprocess.CalledProcessError as e:
262
262
  raise RestoreError(f"Restore command failed: {e}") from e
263
+ except OSError as e:
264
+ logger.error(f"Failed to create restore directory: {e}")
265
+ raise RestoreError("Could not create restore directory")
263
266
  except Exception as e:
264
267
  raise RestoreError(f"Unexpected error during restore: {e}") from e
265
268
 
@@ -285,7 +288,7 @@ def get_backed_up_files(backup_name: str, backup_dir: str):
285
288
  # Parse the XML data
286
289
  root = ET.fromstring(process.stdout)
287
290
  output = None # help gc
288
- # Extract full paths and file size for all <File> elements
291
+ # Extract full paths and file size for all <file> elements
289
292
  file_paths = find_files_with_paths(root)
290
293
  root = None # help gc
291
294
  logger.trace(str(process))
@@ -624,6 +627,14 @@ def main():
624
627
  else:
625
628
  logger.error(f"Supplied .darrc: '{args.darrc}' does not exist or is not a file")
626
629
 
630
+ if not args.config_file:
631
+ logger.error(f"Config file not specified, exiting")
632
+ sys.exit(1)
633
+
634
+ if not os.path.exists(args.config_file):
635
+ logger.error(f"Config file {args.config_file} does not exist.")
636
+ sys.exit(1)
637
+
627
638
 
628
639
  try:
629
640
  start_time=int(time())
dar_backup/util.py CHANGED
@@ -14,6 +14,7 @@ import os
14
14
  import re
15
15
  import subprocess
16
16
  import shlex
17
+ import shutil
17
18
  import sys
18
19
  import threading
19
20
  import traceback
@@ -123,10 +124,113 @@ def _stream_reader(pipe, log_func, output_accumulator: List[str]):
123
124
  log_func(stripped_line) # Log the output in real time
124
125
 
125
126
 
127
+
126
128
  def run_command(command: List[str], timeout: int = 30) -> CommandResult:
127
129
  """
128
130
  Executes a given command via subprocess, logs its output in real time, and returns the result.
129
131
 
132
+ Args:
133
+ command (list): The command to be executed, represented as a list of strings.
134
+ timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
135
+
136
+ Returns:
137
+ A CommandResult NamedTuple with the following properties:
138
+ - process: subprocess.CompletedProcess
139
+ - stdout: str: The full standard output of the command.
140
+ - stderr: str: The full standard error of the command.
141
+ - returncode: int: The return code of the command.
142
+ - timeout: int: The timeout value in seconds used to run the command.
143
+ - command: list[str]: The command executed.
144
+
145
+ Logs:
146
+ - Logs standard output (`stdout`) in real-time at the INFO log level.
147
+ - Logs standard error (`stderr`) in real-time at the ERROR log level.
148
+
149
+ Raises:
150
+ subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
151
+ Exception: If other exceptions occur during command execution.
152
+ FileNotFoundError: If the command is not found.
153
+
154
+ Notes:
155
+ - While the command runs, its `stdout` and `stderr` streams are logged in real-time.
156
+ - The returned `stdout` and `stderr` capture the complete output, even though the output is also logged.
157
+ - The command is forcibly terminated if it exceeds the specified timeout.
158
+ """
159
+ stdout_lines = [] # To accumulate stdout
160
+ stderr_lines = [] # To accumulate stderr
161
+ process = None # Track the process for cleanup
162
+ stdout_thread = None
163
+ stderr_thread = None
164
+
165
+ try:
166
+ # Check if the command exists before executing
167
+ if not shutil.which(command[0]):
168
+ raise FileNotFoundError(f"Command not found: {command[0]}")
169
+
170
+ logger.debug(f"Running command: {command}")
171
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
172
+
173
+ # Start threads to read and log stdout and stderr
174
+ stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, logger.info, stdout_lines))
175
+ stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, logger.error, stderr_lines))
176
+
177
+ stdout_thread.start()
178
+ stderr_thread.start()
179
+
180
+ # Wait for process to complete or timeout
181
+ process.wait(timeout=timeout)
182
+
183
+ except FileNotFoundError as e:
184
+ logger.error(f"Command not found: {command[0]}")
185
+ return CommandResult(
186
+ process=None,
187
+ stdout="",
188
+ stderr=str(e),
189
+ returncode=127,
190
+ timeout=timeout,
191
+ command=command
192
+ )
193
+ except subprocess.TimeoutExpired:
194
+ if process:
195
+ process.terminate()
196
+ logger.error(f"Command: '{command}' timed out and was terminated.")
197
+ raise
198
+ except Exception as e:
199
+ logger.error(f"Error running command: {command}", exc_info=True)
200
+ raise
201
+ finally:
202
+ # Ensure threads are joined to clean up (only if they were started)
203
+ if stdout_thread and stdout_thread.is_alive():
204
+ stdout_thread.join()
205
+ if stderr_thread and stderr_thread.is_alive():
206
+ stderr_thread.join()
207
+
208
+ # Ensure process streams are closed
209
+ if process and process.stdout:
210
+ process.stdout.close()
211
+ if process and process.stderr:
212
+ process.stderr.close()
213
+
214
+ # Combine captured stdout and stderr lines into single strings
215
+ stdout = "\n".join(stdout_lines)
216
+ stderr = "\n".join(stderr_lines)
217
+
218
+ # Build the result object
219
+ result = CommandResult(
220
+ process=process,
221
+ stdout=stdout,
222
+ stderr=stderr,
223
+ returncode=process.returncode,
224
+ timeout=timeout,
225
+ command=command
226
+ )
227
+ logger.debug(f"Command result: {result}")
228
+ return result
229
+
230
+ def run_command2(command: List[str], timeout: int = 30) -> CommandResult:
231
+ """
232
+ Executes a given command via subprocess, logs its output in real time, and returns the result.
233
+
130
234
  Args:
131
235
  command (list): The command to be executed, represented as a list of strings.
132
236
  timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
@@ -158,6 +262,10 @@ def run_command(command: List[str], timeout: int = 30) -> CommandResult:
158
262
  process = None # Track the process for cleanup
159
263
 
160
264
  try:
265
+ # Check if the command exists before executing
266
+ if not shutil.which(command[0]):
267
+ raise FileNotFoundError(f"Command not found: {command[0]}")
268
+
161
269
  logger.debug(f"Running command: {command}")
162
270
  process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
163
271
 
@@ -171,6 +279,16 @@ def run_command(command: List[str], timeout: int = 30) -> CommandResult:
171
279
  # Wait for process to complete or timeout
172
280
  process.wait(timeout=timeout)
173
281
 
282
+ except FileNotFoundError as e:
283
+ logger.error(f"Command not found: {command[0]}")
284
+ return CommandResult(
285
+ process=None,
286
+ stdout="",
287
+ stderr=str(e),
288
+ returncode=127,
289
+ timeout=timeout,
290
+ command=command
291
+ )
174
292
  except subprocess.TimeoutExpired:
175
293
  if process:
176
294
  process.terminate()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dar-backup
3
- Version: 0.6.10
3
+ Version: 0.6.11
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/tree/main/v2
6
6
  Project-URL: Changelog, https://github.com/per2jensen/dar-backup/blob/main/v2/Changelog.md
@@ -690,9 +690,9 @@ Classifier: Topic :: System :: Archiving :: Backup
690
690
  Requires-Python: >=3.9
691
691
  Description-Content-Type: text/markdown
692
692
 
693
- # Full, differential or incremental backups using 'dar'
693
+ # Full, differential or incremental backups using 'dar'
694
694
 
695
- The wonderful 'dar' [Disk Archiver] (https://github.com/Edrusb/DAR) is used for
695
+ The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
696
696
  the heavy lifting, together with the par2 suite in these scripts.
697
697
 
698
698
  ## Table of Contents
@@ -705,30 +705,37 @@ Description-Content-Type: text/markdown
705
705
  - [Homepage - Github](#homepage---github)
706
706
  - [Requirements](#requirements)
707
707
  - [Config file](#config-file)
708
- - [How to run](#how-to-run)
709
- - [1](#1)
710
- - [2](#2)
711
- - [3](#3)
712
- - [4](#4)
713
- - [5](#5)
714
- - [6](#6)
708
+ - [How to install & run](#how-to-run)
709
+ - [1 - config file](#1---config-file)
710
+ - [2 - backup definitions](#2---backup-definitions)
711
+ - [3 - installation](#3---installation)
712
+ - [4 - generate catalog databases](#4---generate-catalog-databases)
713
+ - [5 - do FULL backups](#5---do-full-backups)
714
+ - [6 - deactivate venv](#6---deactivate-venv)
715
715
  - [.darrc](#darrc)
716
716
  - [Systemctl examples](#systemctl-examples)
717
717
  - [Service: dar-back --incremental-backup](#service-dar-back---incremental-backup)
718
718
  - [Timer: dar-back --incremental-backup](#timer-dar-back---incremental-backup)
719
719
  - [List contents of an archive](#list-contents-of-an-archive)
720
- - [dar file selection examples](#dar-file-selection-examples)
720
+ - [dar file selection examples](#dar-file-selection-exmaples)
721
721
  - [Select a directory](#select-a-directory)
722
722
  - [Select file dates in the directory](#select-file-dates-in-the-directory)
723
723
  - [Exclude .xmp files from that date](#exclude-xmp-files-from-that-date)
724
724
  - [Restoring](#restoring)
725
725
  - [Default location for restores](#default-location-for-restores)
726
- - [--restore-dir option](#restore-dir-option)
726
+ - [--restore-dir option](#--restore-dir-option)
727
727
  - [A single file](#a-single-file)
728
728
  - [A directory](#a-directory)
729
729
  - [.NEF from a specific date](#nef-from-a-specific-date)
730
+ - [Restore test fails with exit code 4](#restore-test-fails-with-exit-code-4)
731
+ - [Restore test fails with exit code 5](#restore-test-fails-with-exit-code-5)
732
+ - [Par2](#par2)
733
+ - [Par2 to verify/repair](#par2-to-verifyrepair)
734
+ - [Par2 create redundancy files](#par2-create-redundancy-files)
730
735
  - [Points of interest](#points-of-interest)
736
+ - [Merge FULL with DIFF, creating new FULL](#merge-full-with-diff-creating-new-full)
731
737
  - [dar manager databases](#dar-manager-databases)
738
+ - [Performance tip due to par2](#performance-tip-due-to-par2)
732
739
  - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
733
740
  - [Reference](#reference)
734
741
  - [dar-backup.py](#dar-backuppy)
@@ -793,7 +800,7 @@ The default configuration is expected here: ~/.config/dar-backup/dar-backup.conf
793
800
 
794
801
  ## How to run
795
802
 
796
- ### 1
803
+ ### 1 - config file
797
804
 
798
805
  Config file default location is $HOME/.config/dar-backup/dar-backup.conf
799
806
 
@@ -838,7 +845,7 @@ ENABLED = True
838
845
 
839
846
  ````
840
847
 
841
- ### 2
848
+ ### 2 - backup definitions
842
849
 
843
850
  Put your backup definitions in the directory $BACKUP.D_DIR (defined in the config file)
844
851
 
@@ -889,7 +896,7 @@ Example of backup definition for a home directory
889
896
  --cache-directory-tagging
890
897
  ````
891
898
 
892
- ### 3
899
+ ### 3 - installation
893
900
 
894
901
  Installation is currently in a venv. These commands are installed in the venv:
895
902
 
@@ -925,7 +932,7 @@ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW,
925
932
  See section 15 and section 16 in the supplied "LICENSE" file.
926
933
  ````
927
934
 
928
- ### 4
935
+ ### 4 - generate catalog databases
929
936
 
930
937
  Generate the archive catalog database(s).
931
938
  `dar-backup` expects the catalog databases to be in place, it does not automatically create them (by design)
@@ -934,7 +941,7 @@ Generate the archive catalog database(s).
934
941
  manager --create-db
935
942
  ````
936
943
 
937
- ### 5
944
+ ### 5 - do FULL backups
938
945
 
939
946
  You are ready to do backups of all your backup definitions, if your backup definitions are
940
947
  in place in BACKUP.D_DIR (see config file)
@@ -953,9 +960,9 @@ If you want a backup of a single definition, use the `-d <backup definition>` op
953
960
  dar-backup --full-backup -d <your backup definition>
954
961
  ````
955
962
 
956
- ### 6
963
+ ### 6 - deactivate venv
957
964
 
958
- Deactivate the virtual environment
965
+ Deactivate the virtual environment (venv)
959
966
 
960
967
  ```` bash
961
968
  deactivate
@@ -1311,14 +1318,92 @@ dar-backup --restore <archive_name> --selection "-X '*.xmp' -I '*2024-06-16*' -
1311
1318
  deactivate
1312
1319
  ```
1313
1320
 
1321
+ ### restore test fails with exit code 4
1322
+
1323
+ "dar" in newer versions emits a question about file ownership, which is "answered" with a "no" via the "-Q" option. That in turn leads to an error code 4.
1324
+
1325
+ Thus the dar option "--comparison-field=ignore-owner" has been placed in the supplied .darrc file (located in the virtual environment where dar-backup is installed).
1326
+
1327
+ This causes dar to restore without an error.
1328
+
1329
+ It is a good option when using dar as a non-privileged user.
1330
+
1331
+ ### restore test fails with exit code 5
1332
+
1333
+ If exit code 5 is emitted on the restore test, FSA (File System specific Attributes) could be the cause.
1334
+
1335
+ That (might) occur if you backup a file stored on one type of filesystem, and restore it on another type.
1336
+ My home directory is on a btrfs filesystem, while /tmp (for the restore test) is on zfs.
1337
+
1338
+ The restore test can result in an exit code 5, due to the different filesystems used. In order to avoid the errors, the "option "--fsa-scope none" can be used. That will restult in FSA's not being restored.
1339
+
1340
+ If you need to use this option, un-comment it in the .darrc file (located in the virtual environment where dar-backup is installed)
1341
+
1342
+ ## Par2
1343
+
1344
+ ### Par2 to verify/repair
1345
+
1346
+ You can run a par2 verification on an archive like this:
1347
+
1348
+ ```` bash
1349
+ for file in <archive>*.dar.par2; do
1350
+ par2 verify "$file"
1351
+ done
1352
+ ````
1353
+
1354
+ if there are problems with a slice, try to repair it like this:
1355
+
1356
+ ```` bash
1357
+ par2 repair <archive>.<slice number>.dar.par2
1358
+ ````
1359
+
1360
+ ### Par2 create redundancy files
1361
+
1362
+ If you have merged archives, you will need to create the .par2 redundency files manually.
1363
+ Here is an example
1364
+
1365
+ ```` bash
1366
+ for file in <some-archive>_FULL_yyyy-mm-dd.*; do
1367
+ par2 c -r5 -n1 "$file"
1368
+ done
1369
+ ````
1370
+
1371
+ where "c" is create, -r5 is 5% redundency and -n1 is 1 redundency file
1372
+
1314
1373
  ## Points of interest
1315
1374
 
1375
+ ### Merge FULL with DIFF, creating new FULL
1376
+
1377
+ Over time, the DIFF archives become larger and larger. At some point one wishes to create a new FULL archive to do DIFF's on.
1378
+ One way to do that, is to let dar create a FULL archive from scratch, another is to merge a FULL archive with a DIFF, and from there do DIFF's until they once again gets too large for your taste.
1379
+
1380
+ I do backups of my homedir. Here it is shown how a FULL archive is merged with a DIFF, creating a new FULL archive.
1381
+
1382
+ ```` bash
1383
+ dar --merge pj-homedir_FULL_2021-09-12 -A pj-homedir_FULL_2021-06-06 -@pj-homedir_DIFF_2021-08-29 -s 12G
1384
+
1385
+ # test the new FULL archive
1386
+ dar -t pj-homedir_FULL_2021-09-12
1387
+
1388
+ # create Par2 redundancy files
1389
+ for file in pj-homedir_FULL_yyyy-mm-dd.*.dar; do
1390
+ par2 c -r5 -n1 "$file"
1391
+ done
1392
+
1393
+ ````
1394
+
1316
1395
  ### dar manager databases
1317
1396
 
1318
1397
  `dar-backup` now saves archive catalogs in dar catalog databases.
1319
1398
 
1320
1399
  This makes it easier to restore to a given date when having many FULL, DIFF and INCR archives.
1321
1400
 
1401
+ ### Performance tip due to par2
1402
+
1403
+ This [dar benchmark page](https://dar.sourceforge.io/doc/benchmark.html) has an interesting note on the slice size.
1404
+
1405
+ Slice size should be smaller than available RAM, apparently a large performance hit can be avoided keeping the the par2 data in memory.
1406
+
1322
1407
  ### .darrc sets -vd -vf (since v0.6.4)
1323
1408
 
1324
1409
  These .darrc settings make `dar` print the current directory being processed (-vd) and some stats after (-vf)
@@ -1,14 +1,14 @@
1
1
  dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
2
- dar_backup/__about__.py,sha256=G-zfU8sHQRTZnZK0te1cvbxqeLhT1z86XeAYFNUON6Q,22
2
+ dar_backup/__about__.py,sha256=WAW-bfIrNx18qYeBN4lihEQibyosiKElDt_p7DfZELo,22
3
3
  dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  dar_backup/clean_log.py,sha256=VXKA2BMyQmaC6R08Bq9a3wP3mczdFb_moy6HkL-mnF8,5176
5
5
  dar_backup/cleanup.py,sha256=9yEdRR84XPtEvBGc2QfwGBQl2tdTPttjetHeiSc_TsM,11419
6
6
  dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
7
- dar_backup/dar_backup.py,sha256=hDy7aXU-XiWOtW40Pxql441liNkSYKGU76eOwy8m7fU,32714
7
+ dar_backup/dar_backup.py,sha256=o4DvaVsqWHb1PxHIXfdr2qyqW6KRRGNcrR2_CRbPy1I,32942
8
8
  dar_backup/manager.py,sha256=HDa8eYF89QFhlBRR4EWRzzmswOW00S_w8ToZ5SARO_o,21359
9
- dar_backup/util.py,sha256=SSSJYM9lQZfubhTUBlX1xDGWmCpYEF3ePARmlY544xM,11283
10
- dar_backup-0.6.10.dist-info/METADATA,sha256=CEgbqp93sB_cPnEDWuLn7gphZjdrn4zP_5dShC2Buv8,67980
11
- dar_backup-0.6.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- dar_backup-0.6.10.dist-info/entry_points.txt,sha256=p6c4uQLjlTIVP1Od2iorGefrVUH0IWZdFRMl63mNaRg,164
13
- dar_backup-0.6.10.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
- dar_backup-0.6.10.dist-info/RECORD,,
9
+ dar_backup/util.py,sha256=E-sEBQZY1hmdeVx5xNE22zKQ0BXDee1eI9F1-w7Fq1Q,15756
10
+ dar_backup-0.6.11.dist-info/METADATA,sha256=N7ICBJQEVDC01tza9lRAkmOXCIAILoDrg-mrHG926f0,71626
11
+ dar_backup-0.6.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ dar_backup-0.6.11.dist-info/entry_points.txt,sha256=p6c4uQLjlTIVP1Od2iorGefrVUH0IWZdFRMl63mNaRg,164
13
+ dar_backup-0.6.11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
+ dar_backup-0.6.11.dist-info/RECORD,,