FileBackup 0.3.6__py3-none-any.whl → 0.4.1__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.
@@ -29,9 +29,7 @@ def ToRegex(
29
29
  try:
30
30
  expressions.append(re.compile("^{}$".format(value)))
31
31
  except re.error as ex:
32
- raise typer.BadParameter(
33
- "The regular expression '{}' is not valid ({}).".format(value, ex)
34
- )
32
+ raise typer.BadParameter("The regular expression '{}' is not valid ({}).".format(value, ex))
35
33
 
36
34
  return expressions
37
35
 
@@ -71,18 +71,12 @@ def Execute(
71
71
  CommandLineArguments.input_filename_or_dirs_argument,
72
72
  ],
73
73
  ssd: Annotated[bool, CommandLineArguments.ssd_option] = CommandLineArguments.ssd_option_default,
74
- force: Annotated[
75
- bool, CommandLineArguments.force_option
76
- ] = CommandLineArguments.force_option_default,
74
+ force: Annotated[bool, CommandLineArguments.force_option] = CommandLineArguments.force_option_default,
77
75
  verbose: Annotated[
78
76
  bool, CommandLineArguments.verbose_option
79
77
  ] = CommandLineArguments.verbose_option_default,
80
- quiet: Annotated[
81
- bool, CommandLineArguments.quiet_option
82
- ] = CommandLineArguments.quiet_option_default,
83
- debug: Annotated[
84
- bool, CommandLineArguments.debug_option
85
- ] = CommandLineArguments.debug_option_default,
78
+ quiet: Annotated[bool, CommandLineArguments.quiet_option] = CommandLineArguments.quiet_option_default,
79
+ debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
86
80
  file_include_params: Annotated[
87
81
  list[str],
88
82
  CommandLineArguments.file_include_option,
@@ -146,12 +140,8 @@ def Validate(
146
140
  verbose: Annotated[
147
141
  bool, CommandLineArguments.verbose_option
148
142
  ] = CommandLineArguments.verbose_option_default,
149
- quiet: Annotated[
150
- bool, CommandLineArguments.quiet_option
151
- ] = CommandLineArguments.quiet_option_default,
152
- debug: Annotated[
153
- bool, CommandLineArguments.debug_option
154
- ] = CommandLineArguments.debug_option_default,
143
+ quiet: Annotated[bool, CommandLineArguments.quiet_option] = CommandLineArguments.quiet_option_default,
144
+ debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
155
145
  ) -> None:
156
146
  """Validates previously mirrored content in the backup data store."""
157
147
 
@@ -180,9 +170,7 @@ def Cleanup(
180
170
  verbose: Annotated[
181
171
  bool, CommandLineArguments.verbose_option
182
172
  ] = CommandLineArguments.verbose_option_default,
183
- debug: Annotated[
184
- bool, CommandLineArguments.debug_option
185
- ] = CommandLineArguments.debug_option_default,
173
+ debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
186
174
  ) -> None:
187
175
  """Cleans a backup data store after a mirror execution that was interrupted or failed."""
188
176
 
@@ -92,18 +92,12 @@ def Execute(
92
92
  ),
93
93
  ] = False,
94
94
  ssd: Annotated[bool, CommandLineArguments.ssd_option] = CommandLineArguments.ssd_option_default,
95
- force: Annotated[
96
- bool, CommandLineArguments.force_option
97
- ] = CommandLineArguments.force_option_default,
95
+ force: Annotated[bool, CommandLineArguments.force_option] = CommandLineArguments.force_option_default,
98
96
  verbose: Annotated[
99
97
  bool, CommandLineArguments.verbose_option
100
98
  ] = CommandLineArguments.verbose_option_default,
101
- quiet: Annotated[
102
- bool, CommandLineArguments.quiet_option
103
- ] = CommandLineArguments.quiet_option_default,
104
- debug: Annotated[
105
- bool, CommandLineArguments.debug_option
106
- ] = CommandLineArguments.debug_option_default,
99
+ quiet: Annotated[bool, CommandLineArguments.quiet_option] = CommandLineArguments.quiet_option_default,
100
+ debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
107
101
  working_dir: Annotated[
108
102
  Optional[Path],
109
103
  typer.Option(
@@ -123,9 +117,7 @@ def Execute(
123
117
  ] = Offsite.DEFAULT_ARCHIVE_VOLUME_SIZE,
124
118
  ignore_pending_snapshot: Annotated[
125
119
  bool,
126
- typer.Option(
127
- "--ignore-pending-snapshot", help="Disable the pending warning snapshot and continue."
128
- ),
120
+ typer.Option("--ignore-pending-snapshot", help="Disable the pending warning snapshot and continue."),
129
121
  ] = False,
130
122
  file_include_params: Annotated[
131
123
  list[str],
@@ -181,9 +173,7 @@ def Commit(
181
173
  verbose: Annotated[
182
174
  bool, CommandLineArguments.verbose_option
183
175
  ] = CommandLineArguments.verbose_option_default,
184
- debug: Annotated[
185
- bool, CommandLineArguments.debug_option
186
- ] = CommandLineArguments.debug_option_default,
176
+ debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
187
177
  ) -> None:
188
178
  """Commits a pending snapshot after the changes have been transferred to an offsite data store."""
189
179
 
@@ -240,12 +230,8 @@ def Restore( # pylint: disable=dangerous-default-value
240
230
  verbose: Annotated[
241
231
  bool, CommandLineArguments.verbose_option
242
232
  ] = CommandLineArguments.verbose_option_default,
243
- quiet: Annotated[
244
- bool, CommandLineArguments.quiet_option
245
- ] = CommandLineArguments.quiet_option_default,
246
- debug: Annotated[
247
- bool, CommandLineArguments.debug_option
248
- ] = CommandLineArguments.debug_option_default,
233
+ quiet: Annotated[bool, CommandLineArguments.quiet_option] = CommandLineArguments.quiet_option_default,
234
+ debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
249
235
  working_dir: Annotated[
250
236
  Optional[Path],
251
237
  typer.Option(
@@ -323,9 +309,7 @@ def _ResolveWorkingDir(
323
309
  else:
324
310
  assert False, dm.result # pragma: no cover
325
311
 
326
- dm.WriteInfo(
327
- f"The temporary directory '{working_dir}' was preserved due to {type_desc}.\n"
328
- )
312
+ dm.WriteInfo(f"The temporary directory '{working_dir}' was preserved due to {type_desc}.\n")
329
313
 
330
314
 
331
315
  # ----------------------------------------------------------------------
@@ -70,10 +70,12 @@ class FastGlacierDataStore(BulkStorageDataStore):
70
70
  )
71
71
  return
72
72
 
73
- with dm.Nested("Uploading to Fast Glacier...") as upload_dm:
74
- command_line = f'glacier-con upload "{self.account_name}" "{local_path / "*"}" "{self.aws_region}" "{self._glacier_dir.as_posix()}"'
73
+ self._validated_command_line = True
75
74
 
76
- upload_dm.WriteVerbose(f"Command Line: {command_line}\n\n")
75
+ with dm.Nested("Uploading to Fast Glacier...") as upload_dm:
76
+ command_line = f'glacier-con upload "{self.account_name}" "{local_path / "*"}" "{self.aws_region}" "{self._glacier_dir.as_posix()}"'
77
77
 
78
- with upload_dm.YieldStream() as stream:
79
- upload_dm.result = SubprocessEx.Stream(command_line, stream)
78
+ upload_dm.WriteVerbose(f"Command Line: {command_line}\n\n")
79
+
80
+ with upload_dm.YieldStream() as stream:
81
+ upload_dm.result = SubprocessEx.Stream(command_line, stream)
@@ -0,0 +1,81 @@
1
+ # ----------------------------------------------------------------------
2
+ # |
3
+ # | S3BrowserDataStore.py
4
+ # |
5
+ # | David Brownell <db@DavidBrownell.com>
6
+ # | 2025-02-23 14:08:12
7
+ # |
8
+ # ----------------------------------------------------------------------
9
+ # |
10
+ # | Copyright David Brownell 2025
11
+ # | Distributed under the MIT License.
12
+ # |
13
+ # ----------------------------------------------------------------------
14
+ """Contains the S3BrowserDataStore object"""
15
+
16
+ from pathlib import Path
17
+ from typing import Optional
18
+
19
+ from dbrownell_Common.Streams.DoneManager import DoneManager # type: ignore[import-untyped]
20
+ from dbrownell_Common import SubprocessEx # type: ignore[import-untyped]
21
+ from dbrownell_Common.Types import override # type: ignore[import-untyped]
22
+
23
+ from FileBackup.DataStore.Interfaces.BulkStorageDataStore import BulkStorageDataStore
24
+
25
+
26
+ # ----------------------------------------------------------------------
27
+ class S3BrowserDataStore(BulkStorageDataStore):
28
+ """Data store that uses the S3 Browser application (https://s3browser.com/)"""
29
+
30
+ # ----------------------------------------------------------------------
31
+ def __init__(
32
+ self,
33
+ account_name: str,
34
+ bucket_name: str,
35
+ s3_dir: Optional[Path],
36
+ ) -> None:
37
+ super(S3BrowserDataStore, self).__init__()
38
+
39
+ self.account_name = account_name
40
+ self.bucket_name = bucket_name
41
+
42
+ self._s3_dir = self.bucket_name / (s3_dir or Path())
43
+
44
+ self._validated_command_line = False
45
+
46
+ # ----------------------------------------------------------------------
47
+ @override
48
+ def ExecuteInParallel(self) -> bool:
49
+ return False
50
+
51
+ # ----------------------------------------------------------------------
52
+ @override
53
+ def Upload(
54
+ self,
55
+ dm: DoneManager,
56
+ local_path: Path,
57
+ ) -> None:
58
+ if self._validated_command_line is False:
59
+ with dm.Nested(
60
+ "Validating S3 Browser on the command line...",
61
+ suffix="\n",
62
+ ) as check_dm:
63
+ result = SubprocessEx.Run("s3browser-cli license show")
64
+
65
+ check_dm.WriteVerbose(result.output)
66
+
67
+ if result.returncode != 0:
68
+ check_dm.WriteError(
69
+ "S3 Browser is not available; please make sure it exists in the path and run the script again.\n"
70
+ )
71
+ return
72
+
73
+ self._validated_command_line = True
74
+
75
+ with dm.Nested("Uploading via S3 Browser...") as upload_dm:
76
+ command_line = f's3browser-cli file upload "{self.account_name}" "{local_path / "*"}" "{self._s3_dir.as_posix()}"'
77
+
78
+ upload_dm.WriteVerbose(f"Command Line: {command_line}\n\n")
79
+
80
+ with upload_dm.YieldStream() as stream:
81
+ upload_dm.result = SubprocessEx.Stream(command_line, stream)
FileBackup/Impl/Common.py CHANGED
@@ -22,7 +22,16 @@ from contextlib import contextmanager
22
22
  from dataclasses import dataclass, field
23
23
  from enum import auto, Enum
24
24
  from pathlib import Path
25
- from typing import Any, Callable, cast, Iterable, Iterator, Optional, Pattern, TYPE_CHECKING
25
+ from typing import (
26
+ Any,
27
+ Callable,
28
+ cast,
29
+ Iterable,
30
+ Iterator,
31
+ Optional,
32
+ Pattern,
33
+ TYPE_CHECKING,
34
+ )
26
35
  from urllib import parse as urlparse
27
36
 
28
37
  from dbrownell_Common import ExecuteTasks # type: ignore[import-untyped]
@@ -35,6 +44,7 @@ from FileBackup.DataStore.FastGlacierDataStore import FastGlacierDataStore
35
44
  from FileBackup.DataStore.FileSystemDataStore import FileSystemDataStore
36
45
  from FileBackup.DataStore.Interfaces.DataStore import DataStore, ItemType
37
46
  from FileBackup.DataStore.Interfaces.FileBasedDataStore import FileBasedDataStore
47
+ from FileBackup.DataStore.S3BrowserDataStore import S3BrowserDataStore
38
48
  from FileBackup.DataStore.SFTPDataStore import SFTPDataStore, SSH_PORT
39
49
 
40
50
  if TYPE_CHECKING:
@@ -79,6 +89,22 @@ FAST_GLACIER_TEMPLATE_REGEX = re.compile(
79
89
  [sep] )\/(?#
80
90
  Posix Working Dir )(?P<working_dir>.+)(?#
81
91
  Working Dir End ))?(?#
92
+ End )$(?#
93
+ )""",
94
+ )
95
+
96
+
97
+ S3_BROWSER_TEMPLATE_REGEX = re.compile(
98
+ r"""(?#
99
+ Start )^(?#
100
+ Prefix )s3_browser:\/\/(?#
101
+ Account Name )(?P<account_name>[^@]+)(?#
102
+ [sep] )@(?#
103
+ Bucket Name )(?P<bucket_name>[^\/]+)(?#
104
+ Working Dir Begin )(?:(?#
105
+ Posix Working Dir )(?P<working_dir>.+)(?#
106
+ Working Dir End ))?(?#
107
+ End )$(?#
82
108
  )""",
83
109
  )
84
110
 
@@ -129,11 +155,7 @@ class DiffResult:
129
155
  # ----------------------------------------------------------------------
130
156
  def __post_init__(self):
131
157
  assert (
132
- (
133
- self.operation == DiffOperation.add
134
- and self.this_hash is not None
135
- and self.other_hash is None
136
- )
158
+ (self.operation == DiffOperation.add and self.this_hash is not None and self.other_hash is None)
137
159
  or (
138
160
  self.operation == DiffOperation.modify
139
161
  and self.this_hash is not None
@@ -288,6 +310,17 @@ def GetDestinationHelp() -> str:
288
310
  fast_glacier://MyFastGlacierAccount@us-west-2
289
311
  fast_glacier://MyFastGlacierAccount@us-west-2/Glacier/Dir
290
312
 
313
+ S3 Browser
314
+ ----------
315
+ Write content using the S3 Browser application (https://s3browser.com/).
316
+
317
+ Format:
318
+ s3_browser://<s3_browser_account_name>@<bucket_name>[/<working_dir>]
319
+
320
+ Examples:
321
+ s3_browser://MyS3BrowserAccount@MyBucket
322
+ s3_browser://MyS3BrowserAccount@MyBucket/A/Working/Dir
323
+
291
324
  """,
292
325
  ).replace("\n", "\n\n")
293
326
 
@@ -337,6 +370,16 @@ def YieldDataStore(
337
370
  )
338
371
  return
339
372
 
373
+ # S3 Browser
374
+ s3_browser_match = S3_BROWSER_TEMPLATE_REGEX.match(destination)
375
+ if s3_browser_match:
376
+ yield S3BrowserDataStore(
377
+ s3_browser_match.group("account_name"),
378
+ s3_browser_match.group("bucket_name"),
379
+ Path(s3_browser_match.group("working_dir") or ""),
380
+ )
381
+ return
382
+
340
383
  # Create a FileSystemDataStore instance
341
384
  is_local_filesystem_override_value_for_testing: Optional[bool] = None
342
385
 
@@ -381,14 +424,10 @@ def CreateFilterFunc(
381
424
  ) -> bool:
382
425
  filename_str = filename.as_posix()
383
426
 
384
- if file_excludes is not None and any(
385
- exclude.search(filename_str) for exclude in file_excludes
386
- ):
427
+ if file_excludes is not None and any(exclude.search(filename_str) for exclude in file_excludes):
387
428
  return False
388
429
 
389
- if file_includes is not None and not any(
390
- include.search(filename_str) for include in file_includes
391
- ):
430
+ if file_includes is not None and not any(include.search(filename_str) for include in file_includes):
392
431
  return False
393
432
 
394
433
  return True
@@ -450,11 +489,7 @@ def CalculateDiffs(
450
489
  stream.write(
451
490
  " {}) [{}] {}\n".format(
452
491
  diff_index + 1,
453
- (
454
- "FILE"
455
- if diff.path.is_file()
456
- else "DIR " if diff.path.is_dir() else "????"
457
- ),
492
+ ("FILE" if diff.path.is_file() else "DIR " if diff.path.is_dir() else "????"),
458
493
  (
459
494
  diff.path
460
495
  if dm.capabilities.is_headless
@@ -519,9 +554,7 @@ def WriteFile(
519
554
  dest_filename: Path,
520
555
  status_func: Callable[[int], None],
521
556
  ) -> None:
522
- temp_dest_filename = (
523
- dest_filename.parent / f"{dest_filename.stem}.__temp__{dest_filename.suffix}"
524
- )
557
+ temp_dest_filename = dest_filename.parent / f"{dest_filename.stem}.__temp__{dest_filename.suffix}"
525
558
 
526
559
  with source_filename.open("rb") as source:
527
560
  data_store.MakeDirs(temp_dest_filename.parent)
@@ -620,9 +653,7 @@ def CopyLocalContent(
620
653
  destination_data_store,
621
654
  diff.path,
622
655
  dest_filename,
623
- lambda bytes_transferred: cast(
624
- None, status.OnProgress(bytes_transferred, None)
625
- ),
656
+ lambda bytes_transferred: cast(None, status.OnProgress(bytes_transferred, None)),
626
657
  )
627
658
  else:
628
659
  assert False, diff.path # pragma: no cover
FileBackup/Mirror.py CHANGED
@@ -118,9 +118,7 @@ def Backup(
118
118
  )
119
119
  else:
120
120
  with dm.Nested("\nReading the destination snapshot...") as destination_dm:
121
- destination_snapshot = Snapshot.LoadPersisted(
122
- destination_dm, destination_data_store
123
- )
121
+ destination_snapshot = Snapshot.LoadPersisted(destination_dm, destination_data_store)
124
122
 
125
123
  if destination_dm.result != 0:
126
124
  return
@@ -151,9 +149,7 @@ def Backup(
151
149
  # Persist all content
152
150
  with dm.Nested("\nPersisting content...") as persist_dm:
153
151
  # Transfer the snapshot
154
- pending_snapshot_filename = Path(
155
- Snapshot.PERSISTED_FILE_NAME + Common.PENDING_COMMIT_EXTENSION
156
- )
152
+ pending_snapshot_filename = Path(Snapshot.PERSISTED_FILE_NAME + Common.PENDING_COMMIT_EXTENSION)
157
153
 
158
154
  temp_directory = PathEx.CreateTempDirectory()
159
155
 
@@ -268,9 +264,7 @@ def Backup(
268
264
  ],
269
265
  Remove,
270
266
  quiet=quiet,
271
- max_num_threads=(
272
- None if destination_data_store.ExecuteInParallel() else 1
273
- ),
267
+ max_num_threads=(None if destination_data_store.ExecuteInParallel() else 1),
274
268
  refresh_per_second=Common.EXECUTE_TASKS_REFRESH_PER_SECOND,
275
269
  ),
276
270
  )
@@ -314,9 +308,7 @@ def Backup(
314
308
  lambda fullpath, item_type: (
315
309
  cast(FileBasedDataStore, destination_data_store).RemoveDir(fullpath)
316
310
  if item_type == Common.ItemType.Dir
317
- else cast(FileBasedDataStore, destination_data_store).RemoveFile(
318
- fullpath
319
- )
311
+ else cast(FileBasedDataStore, destination_data_store).RemoveFile(fullpath)
320
312
  ),
321
313
  ),
322
314
  ]:
@@ -349,9 +341,7 @@ def Backup(
349
341
  ],
350
342
  Commit,
351
343
  quiet=quiet,
352
- max_num_threads=(
353
- None if destination_data_store.ExecuteInParallel() else 1
354
- ),
344
+ max_num_threads=(None if destination_data_store.ExecuteInParallel() else 1),
355
345
  refresh_per_second=Common.EXECUTE_TASKS_REFRESH_PER_SECOND,
356
346
  )
357
347
 
@@ -431,14 +421,10 @@ def Validate(
431
421
  # the backup with the values of the current snapshot are based on what is on the file
432
422
  # system. Convert the data in the mirror snapshot so it matches the values in the
433
423
  # current snapshot before we compare the contents of each.
434
- new_root = Snapshot.Node(
435
- None, None, Common.DirHashPlaceholder(explicitly_added=False), None
436
- )
424
+ new_root = Snapshot.Node(None, None, Common.DirHashPlaceholder(explicitly_added=False), None)
437
425
 
438
426
  for node in mirrored_snapshot.node.Enum():
439
- destination_path = destination_data_store.SnapshotFilenameToDestinationName(
440
- node.fullpath
441
- )
427
+ destination_path = destination_data_store.SnapshotFilenameToDestinationName(node.fullpath)
442
428
 
443
429
  if node.is_dir:
444
430
  if not node.children:
FileBackup/Offsite.py CHANGED
@@ -82,8 +82,7 @@ class SnapshotFilenames:
82
82
  return cls(
83
83
  backup_name,
84
84
  snapshot_filename,
85
- snapshot_filename.parent
86
- / f"{snapshot_filename.stem}.__pending__{snapshot_filename.suffix}",
85
+ snapshot_filename.parent / f"{snapshot_filename.stem}.__pending__{snapshot_filename.suffix}",
87
86
  )
88
87
 
89
88
 
@@ -331,9 +330,7 @@ def Backup(
331
330
  file_content_data_store,
332
331
  diff.path,
333
332
  dest_filename,
334
- lambda bytes_written: cast(
335
- None, status.OnProgress(bytes_written, None)
336
- ),
333
+ lambda bytes_written: cast(None, status.OnProgress(bytes_written, None)),
337
334
  )
338
335
 
339
336
  return dest_filename
@@ -351,15 +348,10 @@ def Backup(
351
348
  ExecuteTasks.TransformTasksEx(
352
349
  preserve_dm,
353
350
  "Processing",
354
- [
355
- ExecuteTasks.TaskData(str(diff.path), diff)
356
- for diff in diffs_to_process
357
- ],
351
+ [ExecuteTasks.TaskData(str(diff.path), diff) for diff in diffs_to_process],
358
352
  PrepareTask,
359
353
  quiet=quiet,
360
- max_num_threads=(
361
- None if file_content_data_store.ExecuteInParallel() else 1
362
- ),
354
+ max_num_threads=(None if file_content_data_store.ExecuteInParallel() else 1),
363
355
  refresh_per_second=Common.EXECUTE_TASKS_REFRESH_PER_SECOND,
364
356
  )
365
357
 
@@ -436,12 +428,12 @@ def Backup(
436
428
  ) as validate_dm:
437
429
  assert zip_binary is not None
438
430
 
439
- command_line = f'{zip_binary} t "{file_content_root / ARCHIVE_FILENAME}.001"{encryption_arg}'
440
-
441
- validate_dm.WriteVerbose(
442
- f"Command Line: {_ScrubZipCommandLine(command_line)}\n\n"
431
+ command_line = (
432
+ f'{zip_binary} t "{file_content_root / ARCHIVE_FILENAME}.001"{encryption_arg}'
443
433
  )
444
434
 
435
+ validate_dm.WriteVerbose(f"Command Line: {_ScrubZipCommandLine(command_line)}\n\n")
436
+
445
437
  with validate_dm.YieldStream() as stream:
446
438
  validate_dm.result = SubprocessEx.Stream(command_line, stream)
447
439
 
@@ -639,9 +631,7 @@ def Restore(
639
631
  for directory in directories:
640
632
  match = dir_regex.match(directory)
641
633
  if not match:
642
- preprocess_dm.WriteError(
643
- f"'{directory}' is not a recognized directory name.\n"
644
- )
634
+ preprocess_dm.WriteError(f"'{directory}' is not a recognized directory name.\n")
645
635
  return
646
636
 
647
637
  offsite_directories.setdefault(directory, []).append(
@@ -774,17 +764,13 @@ def Restore(
774
764
  dest: Path,
775
765
  ) -> None:
776
766
  dest /= source.name
777
- os.symlink(
778
- source, dest, target_is_directory=source.is_dir()
779
- )
767
+ os.symlink(source, dest, target_is_directory=source.is_dir())
780
768
 
781
769
  # ----------------------------------------------------------------------
782
770
 
783
771
  func = CreateSymLink
784
772
 
785
- temp_dest_dir = destination_dir.parent / (
786
- destination_dir.name + "__temp__"
787
- )
773
+ temp_dest_dir = destination_dir.parent / (destination_dir.name + "__temp__")
788
774
 
789
775
  shutil.rmtree(temp_dest_dir, ignore_errors=True)
790
776
  temp_dest_dir.mkdir(parents=True)
@@ -813,27 +799,22 @@ def Restore(
813
799
 
814
800
  # ----------------------------------------------------------------------
815
801
 
816
- directory_working_dirs: list[Path | None | Exception] = (
817
- ExecuteTasks.TransformTasksEx(
818
- preprocess_dm,
819
- "Processing",
820
- [
821
- ExecuteTasks.TaskData(str(directory), directory)
822
- for directory in directories
823
- ],
824
- PrepareTask,
825
- quiet=quiet,
826
- max_num_threads=None if ssd and data_store.ExecuteInParallel() else 1,
827
- refresh_per_second=Common.EXECUTE_TASKS_REFRESH_PER_SECOND,
828
- )
802
+ directory_working_dirs: list[Path | None | Exception] = ExecuteTasks.TransformTasksEx(
803
+ preprocess_dm,
804
+ "Processing",
805
+ [ExecuteTasks.TaskData(str(directory), directory) for directory in directories],
806
+ PrepareTask,
807
+ quiet=quiet,
808
+ max_num_threads=None if ssd and data_store.ExecuteInParallel() else 1,
809
+ refresh_per_second=Common.EXECUTE_TASKS_REFRESH_PER_SECOND,
829
810
  )
830
811
 
831
812
  if preprocess_dm.result != 0:
832
813
  return
833
814
 
834
- assert all(
835
- isinstance(working_dir, Path) for working_dir in directory_working_dirs
836
- ), directory_working_dirs
815
+ assert all(isinstance(working_dir, Path) for working_dir in directory_working_dirs), (
816
+ directory_working_dirs
817
+ )
837
818
 
838
819
  with preprocess_dm.Nested("Staging working content...") as stage_dm:
839
820
  # ----------------------------------------------------------------------
@@ -864,9 +845,7 @@ def Restore(
864
845
 
865
846
  with stage_dm.Nested(
866
847
  f"Processing '{directory}' ({index + 1} of {len(directories)})...",
867
- lambda: "{} added".format(
868
- inflect.no("instruction", len(these_instructions))
869
- ),
848
+ lambda: "{} added".format(inflect.no("instruction", len(these_instructions))),
870
849
  ):
871
850
  # link the content
872
851
  for root_str, _, filenames in os.walk(
@@ -1077,9 +1056,7 @@ def Restore(
1077
1056
  Common.DiffOperation.remove: ("Removing", OnRemoveInstruction),
1078
1057
  }
1079
1058
 
1080
- for directory_index, (directory, these_instructions) in enumerate(
1081
- instructions.items()
1082
- ):
1059
+ for directory_index, (directory, these_instructions) in enumerate(instructions.items()):
1083
1060
  with all_instructions_dm.Nested(
1084
1061
  f"Processing '{directory}' ({directory_index + 1} of {len(instructions)})...",
1085
1062
  suffix="\n",
@@ -1117,9 +1094,7 @@ def Restore(
1117
1094
 
1118
1095
  if not dry_run:
1119
1096
  for instruction_index, instruction in enumerate(these_instructions):
1120
- prefix, on_instruction_func = operation_map[
1121
- instruction.operation
1122
- ]
1097
+ prefix, on_instruction_func = operation_map[instruction.operation]
1123
1098
 
1124
1099
  with instructions_dm.Nested(
1125
1100
  f"{prefix} the {'file' if instruction.file_content_path is not None else 'directory'} '{instruction.local_filename}' ({instruction_index + 1} of {len(these_instructions)})...",
@@ -1495,9 +1470,7 @@ def _VerifyRestoredFiles(
1495
1470
  for root_str, _, filenames in os.walk(contents_dir):
1496
1471
  root = Path(root_str)
1497
1472
 
1498
- all_filenames += [
1499
- root / filename for filename in filenames if filename != INDEX_HASH_FILENAME
1500
- ]
1473
+ all_filenames += [root / filename for filename in filenames if filename != INDEX_HASH_FILENAME]
1501
1474
 
1502
1475
  data_store = FileSystemDataStore()
1503
1476
 
FileBackup/Snapshot.py CHANGED
@@ -94,9 +94,7 @@ class Snapshot:
94
94
  )
95
95
 
96
96
  assert (isinstance(self.hash_value, DirHashPlaceholder) and self.file_size is None) or (
97
- isinstance(self.hash_value, str)
98
- and self.file_size is not None
99
- and self.file_size >= 0
97
+ isinstance(self.hash_value, str) and self.file_size is not None and self.file_size >= 0
100
98
  ), (self.hash_value, self.file_size)
101
99
 
102
100
  assert not self.children or self.is_dir
@@ -179,9 +177,7 @@ class Snapshot:
179
177
  result = cls(name, parent, hash_value, file_size)
180
178
 
181
179
  if result.is_dir:
182
- result.children = {
183
- k: cls.FromJson(k, result, v) for k, v in value["children"].items()
184
- }
180
+ result.children = {k: cls.FromJson(k, result, v) for k, v in value["children"].items()}
185
181
 
186
182
  return result
187
183
 
@@ -343,9 +339,7 @@ class Snapshot:
343
339
  new_node = node.children.get(part, None)
344
340
 
345
341
  if new_node is None:
346
- new_node = self.__class__(
347
- part, node, DirHashPlaceholder(explicitly_added=False), None
348
- )
342
+ new_node = self.__class__(part, node, DirHashPlaceholder(explicitly_added=False), None)
349
343
  node.children[part] = new_node
350
344
 
351
345
  node = new_node
@@ -427,9 +421,7 @@ class Snapshot:
427
421
  def OnNestedExit():
428
422
  # We need to do some extra work because inflect doesn't work when we need a word between
429
423
  # the number and plural where the singular form of the plural ends in 'y'.
430
- num_empty_dirs = sum(
431
- len(input_info.empty_dirs) for input_info in all_input_infos.values()
432
- )
424
+ num_empty_dirs = sum(len(input_info.empty_dirs) for input_info in all_input_infos.values())
433
425
 
434
426
  return "{} found, {} empty {} found".format(
435
427
  inflect.no(
@@ -460,9 +452,7 @@ class Snapshot:
460
452
  filenames.append(input_item)
461
453
 
462
454
  elif input_item_type == ItemType.Dir:
463
- for this_root, these_directories, these_filenames in data_store.Walk(
464
- input_item
465
- ):
455
+ for this_root, these_directories, these_filenames in data_store.Walk(input_item):
466
456
  if not these_directories and not these_filenames:
467
457
  empty_dirs.append(this_root)
468
458
  continue
@@ -471,9 +461,7 @@ class Snapshot:
471
461
  fullpath = this_root / this_filename
472
462
 
473
463
  if data_store.GetItemType(fullpath) != ItemType.File:
474
- status.OnInfo(
475
- f"The file '{fullpath}' is not a supported item type."
476
- )
464
+ status.OnInfo(f"The file '{fullpath}' is not a supported item type.")
477
465
  continue
478
466
 
479
467
  if not filter_filename_func(fullpath):
@@ -489,9 +477,7 @@ class Snapshot:
489
477
  else:
490
478
  # By default, FileSystemDataStore and SFTPDataStore will not get here, as
491
479
  # the will not traverse directory symlinks.
492
- raise Exception(
493
- f"'{input_item}' is not a supported item type."
494
- ) # pragma: no cover
480
+ raise Exception(f"'{input_item}' is not a supported item type.") # pragma: no cover
495
481
 
496
482
  short_desc = "{} found".format(inflect.no("file", len(filenames)))
497
483
 
@@ -529,8 +515,7 @@ class Snapshot:
529
515
  return cls(Snapshot.Node(None, None, DirHashPlaceholder(explicitly_added=False), None))
530
516
 
531
517
  with dm.Nested(
532
- "\n"
533
- + ("Calculating hashes..." if calculate_hashes else "Retrieving file information..."),
518
+ "\n" + ("Calculating hashes..." if calculate_hashes else "Retrieving file information..."),
534
519
  ) as hashes_dm:
535
520
  # ----------------------------------------------------------------------
536
521
  def PrepareTask(
@@ -651,9 +636,7 @@ class Snapshot:
651
636
  *Progress.get_default_columns(),
652
637
  TimeElapsedColumn(),
653
638
  "{task.fields[status]}",
654
- console=Capabilities.Get(stdout_context.stream).CreateRichConsole(
655
- stdout_context.stream
656
- ),
639
+ console=Capabilities.Get(stdout_context.stream).CreateRichConsole(stdout_context.stream),
657
640
  transient=True,
658
641
  ) as progress_bar:
659
642
  total_progress_id = progress_bar.add_task(
@@ -674,9 +657,7 @@ class Snapshot:
674
657
  progress_bar.update(total_progress_id, advance=len(chunk))
675
658
 
676
659
  try:
677
- return Snapshot(
678
- Snapshot.Node.FromJson(None, None, json.loads(content.decode("UTF-8")))
679
- )
660
+ return Snapshot(Snapshot.Node.FromJson(None, None, json.loads(content.decode("UTF-8"))))
680
661
  except KeyError as ex:
681
662
  raise Exception(f"The content at '{snapshot_filename}' is not valid.") from ex
682
663
 
@@ -704,8 +685,12 @@ class Snapshot:
704
685
  """Enumerates the differences between two Snapshots"""
705
686
 
706
687
  if compare_hashes:
707
- compare_func = lambda a, b: a.hash_value == b.hash_value
688
+
689
+ def CompareFunc(a, b) -> bool:
690
+ return a.hash_value == b.hash_value
708
691
  else:
709
- compare_func = lambda a, b: a.file_size == b.file_size
710
692
 
711
- yield from self.node.CreateDiffs(other.node, compare_func)[0]
693
+ def CompareFunc(a, b) -> bool:
694
+ return a.file_size == b.file_size
695
+
696
+ yield from self.node.CreateDiffs(other.node, CompareFunc)[0]
FileBackup/__init__.py CHANGED
@@ -9,4 +9,4 @@
9
9
  # Note that this value will be overwritten by calls to `python ../../Build.py update_version` based
10
10
  # on changes observed in the git repository. The default value below will be used until the value
11
11
  # here is explicitly updated by the Continuous Integration system as part of a commit.
12
- __version__ = "0.3.6"
12
+ __version__ = "0.4.1"
FileBackup/py.typed ADDED
File without changes
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: FileBackup
3
+ Version: 0.4.1
4
+ Summary: Tool for creating and restoring file system backups.
5
+ Project-URL: Homepage, https://github.com/davidbrownell/FileBackup
6
+ Project-URL: Documentation, https://github.com/davidbrownell/FileBackup
7
+ Project-URL: Repository, https://github.com/davidbrownell/FileBackup
8
+ Author-email: David Brownell <github@DavidBrownell.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: backup,mirror,offsite,restore
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Natural Language :: English
19
+ Classifier: Operating System :: MacOS
20
+ Classifier: Operating System :: Microsoft :: Windows
21
+ Classifier: Operating System :: POSIX :: Linux
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: System :: Archiving :: Backup
28
+ Classifier: Topic :: System :: Archiving :: Mirroring
29
+ Classifier: Topic :: System :: Systems Administration
30
+ Classifier: Topic :: Utilities
31
+ Requires-Python: >=3.10
32
+ Requires-Dist: dbrownell-common>=0.14.6
33
+ Requires-Dist: paramiko>=3.5.1
34
+ Requires-Dist: typer>=0.15.3
35
+ Description-Content-Type: text/markdown
36
+
37
+ **Project:**
38
+ [![License](https://img.shields.io/github/license/davidbrownell/FileBackup?color=dark-green)](https://github.com/davidbrownell/FileBackup/blob/master/LICENSE)
39
+
40
+ **Package:**
41
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/FileBackup?color=dark-green)](https://pypi.org/project/FileBackup/)
42
+ [![PyPI - Version](https://img.shields.io/pypi/v/FileBackup?color=dark-green)](https://pypi.org/project/FileBackup/)
43
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/FileBackup)](https://pypistats.org/packages/FileBackup)
44
+
45
+ **Development:**
46
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
47
+ [![CI](https://github.com/davidbrownell/FileBackup/actions/workflows/CICD.yml/badge.svg)](https://github.com/davidbrownell/FileBackup/actions/workflows/CICD.yml)
48
+ [![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/davidbrownell/f15146b1b8fdc0a5d45ac0eb786a84f7/raw/FileBackup_code_coverage.json)](https://github.com/davidbrownell/FileBackup/actions)
49
+ [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/davidbrownell/FileBackup?color=dark-green)](https://github.com/davidbrownell/FileBackup/commits/main/)
50
+
51
+ <!-- Content above this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
52
+
53
+ ## Contents
54
+ - [Overview](#overview)
55
+ - [Installation](#installation)
56
+ - [Development](#development)
57
+ - [Additional Information](#additional-information)
58
+ - [License](#license)
59
+
60
+ ## Overview
61
+ TODO: Complete this section
62
+
63
+ ### How to use FileBackup
64
+ TODO: Complete this section
65
+
66
+ <!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
67
+
68
+ ## Installation
69
+
70
+ | Installation Method | Command |
71
+ | --- | --- |
72
+ | Via [uv](https://github.com/astral-sh/uv) | `uv add FileBackup` |
73
+ | Via [pip](https://pip.pypa.io/en/stable/) | `pip install FileBackup` |
74
+
75
+ ### Verifying Signed Artifacts
76
+ Artifacts are signed and validated using [py-minisign](https://github.com/x13a/py-minisign) and the public key in the file `./minisign_key.pub`.
77
+
78
+ To verify that an artifact is valid, visit [the latest release](https://github.com/davidbrownell/FileBackup/releases/latest) and download the `.minisign` signature file that corresponds to the artifact, then run the following command, replacing `<filename>` with the name of the artifact to be verified:
79
+
80
+ `uv run --with py-minisign python -c "import minisign; minisign.PublicKey.from_file('minisign_key.pub').verify_file('<filename>')"`
81
+
82
+ ## Development
83
+ Please visit [Contributing](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) and [Development](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) for information on contributing to this project.
84
+
85
+ ## Additional Information
86
+ Additional information can be found at these locations.
87
+
88
+ | Title | Document | Description |
89
+ | --- | --- | --- |
90
+ | Code of Conduct | [CODE_OF_CONDUCT.md](https://github.com/davidbrownell/FileBackup/blob/main/CODE_OF_CONDUCT.md) | Information about the norms, rules, and responsibilities we adhere to when participating in this open source community. |
91
+ | Contributing | [CONTRIBUTING.md](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) | Information about contributing to this project. |
92
+ | Development | [DEVELOPMENT.md](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) | Information about development activities involved in making changes to this project. |
93
+ | Governance | [GOVERNANCE.md](https://github.com/davidbrownell/FileBackup/blob/main/GOVERNANCE.md) | Information about how this project is governed. |
94
+ | Maintainers | [MAINTAINERS.md](https://github.com/davidbrownell/FileBackup/blob/main/MAINTAINERS.md) | Information about individuals who maintain this project. |
95
+ | Security | [SECURITY.md](https://github.com/davidbrownell/FileBackup/blob/main/SECURITY.md) | Information about how to privately report security issues associated with this project. |
96
+
97
+ ## License
98
+ FileBackup is licensed under the <a href="https://choosealicense.com/licenses/MIT/" target="_blank">MIT</a> license.
@@ -0,0 +1,26 @@
1
+ FileBackup/Mirror.py,sha256=GDgTWVNUYn5O7vlH5YVi0LppwQN4R99J61g1UCBwlfI,23544
2
+ FileBackup/Offsite.py,sha256=GAEUvdQ2enAMfIyiCRQb5B23xHizNda2aRDdq76PIfQ,61947
3
+ FileBackup/Snapshot.py,sha256=Dj-XFjAFWkqNZIlMHqTOI1Ygdm1jB3orIwDxMQSrupQ,26970
4
+ FileBackup/__init__.py,sha256=oGJQp7XFkEmtI84E9DqGrdtVoA1X3TK2mwORIAotyfI,597
5
+ FileBackup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ FileBackup/CommandLine/CommandLineArguments.py,sha256=7oLUf9tODd_OjBNzIjQiiyZ9ujy-KbW53Pg0dF0MQG8,2378
7
+ FileBackup/CommandLine/EntryPoint.py,sha256=Fn62wtKV6uiTJ1dQpolNFUyWWeUOKKgetOFSXPgoe-w,1813
8
+ FileBackup/CommandLine/MirrorEntryPoint.py,sha256=WlGIz_AZ7OdE-hVL19lQExUXbEjQ8_4bbIuITWxdWO4,6704
9
+ FileBackup/CommandLine/OffsiteEntryPoint.py,sha256=ZpZBpIk4ZLC27oWjvDriiqqwtwqHLJ2tfMOFdhVyigU,12368
10
+ FileBackup/CommandLine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ FileBackup/DataStore/FastGlacierDataStore.py,sha256=5J4W9FqB92hnHoRoZLo-DfMQ6uyjaOAWWrnuc0pvnJo,3017
12
+ FileBackup/DataStore/FileSystemDataStore.py,sha256=XUvIQe2Yl-W6OPfvsJlPzDPsGclDOkKlozO0GJ-05bo,6791
13
+ FileBackup/DataStore/S3BrowserDataStore.py,sha256=3NHl-advSbyXuL1kDu1wswbcAZbhmAGDu8yzlOuY5Us,2954
14
+ FileBackup/DataStore/SFTPDataStore.py,sha256=TKz-hwwHg4avxKHDFRRz7FlKx3d40wT15ug1gCpu6VU,11374
15
+ FileBackup/DataStore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ FileBackup/DataStore/Interfaces/BulkStorageDataStore.py,sha256=qZo92nPDP_aVTxlohsc1ySnU9Skgxgo7Mj_tT0KxFZ8,1310
17
+ FileBackup/DataStore/Interfaces/DataStore.py,sha256=FpQmxoqBS5indJYRxibkSXHTgk7U6HFyViI0BDqCcVw,1169
18
+ FileBackup/DataStore/Interfaces/FileBasedDataStore.py,sha256=eMA_cQ-CED83z5uMDQZ18UFzBRvaXOwWz67kVu-2ftM,5307
19
+ FileBackup/DataStore/Interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ FileBackup/Impl/Common.py,sha256=2WFMx5BdCnZmo-cuLYZlIx4Nr_ZBKmJWfMaFVUpsqKo,24935
21
+ FileBackup/Impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ filebackup-0.4.1.dist-info/METADATA,sha256=RIPSLlTV_DHWAyA4w23ILziHhk022iKX50Xgy-DL04s,5802
23
+ filebackup-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ filebackup-0.4.1.dist-info/entry_points.txt,sha256=2SUVO3jHsi06MYfUEdqxaVwVvTQVjxzIWlHexRKWRgQ,121
25
+ filebackup-0.4.1.dist-info/licenses/LICENSE,sha256=2dPVskJuHRLwSF9fQiAfln1RVWya2DsKNPvgknRLTYw,1074
26
+ filebackup-0.4.1.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  FileBackup = FileBackup.CommandLine:EntryPoint.app
3
+ file_backup = FileBackup.CommandLine:EntryPoint.app
@@ -1,6 +1,6 @@
1
1
  MIT LICENSE
2
2
 
3
- Copyright (c) 2024 David Brownell
3
+ Copyright (c) 2024-25 David Brownell
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,73 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: FileBackup
3
- Version: 0.3.6
4
- Summary: Tool for creating and restoring file system backups.
5
- Author-email: David Brownell <github@DavidBrownell.com>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/davidbrownell/FileBackup
8
- Project-URL: Documentation, https://github.com/davidbrownell/FileBackup
9
- Project-URL: Repository, https://github.com/davidbrownell/FileBackup
10
- Keywords: backup,mirror,offsite,restore
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Environment :: Console
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Intended Audience :: End Users/Desktop
15
- Classifier: Intended Audience :: System Administrators
16
- Classifier: License :: OSI Approved :: MIT License
17
- Classifier: Natural Language :: English
18
- Classifier: Operating System :: MacOS
19
- Classifier: Operating System :: Microsoft :: Windows
20
- Classifier: Operating System :: POSIX :: Linux
21
- Classifier: Programming Language :: Python
22
- Classifier: Programming Language :: Python :: 3.10
23
- Classifier: Programming Language :: Python :: 3.11
24
- Classifier: Programming Language :: Python :: 3.12
25
- Classifier: Topic :: System :: Archiving :: Backup
26
- Classifier: Topic :: System :: Archiving :: Mirroring
27
- Classifier: Topic :: System :: Systems Administration
28
- Classifier: Topic :: Utilities
29
- Description-Content-Type: text/markdown
30
- License-File: LICENSE.txt
31
- Requires-Dist: dbrownell-Common
32
- Requires-Dist: paramiko ~=3.4
33
- Requires-Dist: typer ~=0.9
34
- Provides-Extra: dev
35
- Requires-Dist: dbrownell-DevTools ; extra == 'dev'
36
- Provides-Extra: package
37
- Requires-Dist: build ~=1.2 ; extra == 'package'
38
- Requires-Dist: cx-Freeze ~=6.15 ; extra == 'package'
39
- Requires-Dist: twine ~=5.1 ; extra == 'package'
40
-
41
- # FileBackup
42
-
43
-
44
-
45
- Tool for creating and restoring file system backups.
46
-
47
-
48
-
49
- ## Overview
50
- TODO: Complete this section
51
-
52
- ### How to use FileBackup
53
- TODO: Complete this section
54
-
55
-
56
-
57
- ## Additional Information
58
- Additional information can be found at these locations.
59
-
60
- <!-- [BEGIN] Additional Information -->
61
- | Title | Document | Description |
62
- | --- | --- | --- |
63
- | Code of Conduct | [CODE_OF_CONDUCT.md](https://github.com/davidbrownell/FileBackup/blob/main/CODE_OF_CONDUCT.md) | Information about the the norms, rules, and responsibilities we adhere to when participating in this open source community. |
64
- | Contributing | [CONTRIBUTING.md](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) | Information about contributing code changes to this project. |
65
- | Development | [DEVELOPMENT.md](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) | Information about development activities involved in making changes to this project. |
66
- | Governance | [GOVERNANCE.md](https://github.com/davidbrownell/FileBackup/blob/main/GOVERNANCE.md) | Information about how this project is governed. |
67
- | Maintainers | [MAINTAINERS.md](https://github.com/davidbrownell/FileBackup/blob/main/MAINTAINERS.md) | Information about individuals who maintain this project. |
68
- | Security | [SECURITY.md](https://github.com/davidbrownell/FileBackup/blob/main/SECURITY.md) | Information about how to privately report security issues associated with this project. |
69
- <!-- [END] Additional Information -->
70
-
71
- ## License
72
-
73
- FileBackup is licensed under the <a href="https://choosealicense.com/licenses/mit/" target="_blank">MIT</a> license.
@@ -1,25 +0,0 @@
1
- FileBackup/Mirror.py,sha256=FA9vfvPHOJzmN__Tx09ndWMeMTSWcED52BuVfeOBuKc,23904
2
- FileBackup/Offsite.py,sha256=2kK5ToJIHXIAPChtREcztp1e1L95ZJF6adIk-8d5V8g,62796
3
- FileBackup/Snapshot.py,sha256=r6l35Tl75WIQALtzN81r62XNkzJiTddzuihmlSb56Aw,27336
4
- FileBackup/__init__.py,sha256=lhHWzRFcuwSYms0q5XOm17GhlPgSqT4ESBJzRZP-5uo,597
5
- FileBackup/CommandLine/CommandLineArguments.py,sha256=79K4jVxtZ8xk1TfSJT8KIdkqZFjCEAspoIKP21GUXxE,2410
6
- FileBackup/CommandLine/EntryPoint.py,sha256=Fn62wtKV6uiTJ1dQpolNFUyWWeUOKKgetOFSXPgoe-w,1813
7
- FileBackup/CommandLine/MirrorEntryPoint.py,sha256=ZQ3mz0AgBwwn7n9uOuxN-lEQvm22u8SQbpWASkvRsAA,6800
8
- FileBackup/CommandLine/OffsiteEntryPoint.py,sha256=enbgeD_nhdR2T6UufySt33f4P2FnOVzsHiEktyVtLlo,12528
9
- FileBackup/CommandLine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- FileBackup/DataStore/FastGlacierDataStore.py,sha256=YxmYY0IVZ6HTFE_2zMTz4g09Z9jJcXM9sMu_Yp86DAg,2982
11
- FileBackup/DataStore/FileSystemDataStore.py,sha256=XUvIQe2Yl-W6OPfvsJlPzDPsGclDOkKlozO0GJ-05bo,6791
12
- FileBackup/DataStore/SFTPDataStore.py,sha256=TKz-hwwHg4avxKHDFRRz7FlKx3d40wT15ug1gCpu6VU,11374
13
- FileBackup/DataStore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- FileBackup/DataStore/Interfaces/BulkStorageDataStore.py,sha256=qZo92nPDP_aVTxlohsc1ySnU9Skgxgo7Mj_tT0KxFZ8,1310
15
- FileBackup/DataStore/Interfaces/DataStore.py,sha256=FpQmxoqBS5indJYRxibkSXHTgk7U6HFyViI0BDqCcVw,1169
16
- FileBackup/DataStore/Interfaces/FileBasedDataStore.py,sha256=eMA_cQ-CED83z5uMDQZ18UFzBRvaXOwWz67kVu-2ftM,5307
17
- FileBackup/DataStore/Interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- FileBackup/Impl/Common.py,sha256=8XV0ARuLLci8AGjvW2lfJyXbA2FnyRpUHel4AI-7ReM,23712
19
- FileBackup/Impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- FileBackup-0.3.6.dist-info/LICENSE.txt,sha256=6ycKI8SWG920Saey4PGi5YUVLX0C_8hfqJr7Q0sqGI8,1071
21
- FileBackup-0.3.6.dist-info/METADATA,sha256=VAmADMs2CGKydEooi87xZBiYv3EybFPp653_Lb7Xog0,3300
22
- FileBackup-0.3.6.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
23
- FileBackup-0.3.6.dist-info/entry_points.txt,sha256=OlHrvdhWJQGIp4vCUJAqOWxW7y0ZH59qQ-2ImWFUcPI,69
24
- FileBackup-0.3.6.dist-info/top_level.txt,sha256=mXd2b6GEOpQCnmzgYEb6c_rfwlgP3RgmMuVDkQasxKw,11
25
- FileBackup-0.3.6.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- FileBackup