FileBackup 0.4.2__py3-none-any.whl → 0.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- FileBackup/Offsite.py +58 -36
- FileBackup/__init__.py +1 -1
- {filebackup-0.4.2.dist-info → filebackup-0.4.4.dist-info}/METADATA +1 -1
- {filebackup-0.4.2.dist-info → filebackup-0.4.4.dist-info}/RECORD +7 -7
- {filebackup-0.4.2.dist-info → filebackup-0.4.4.dist-info}/WHEEL +0 -0
- {filebackup-0.4.2.dist-info → filebackup-0.4.4.dist-info}/entry_points.txt +0 -0
- {filebackup-0.4.2.dist-info → filebackup-0.4.4.dist-info}/licenses/LICENSE +0 -0
FileBackup/Offsite.py
CHANGED
|
@@ -555,7 +555,7 @@ def Restore(
|
|
|
555
555
|
)
|
|
556
556
|
return
|
|
557
557
|
|
|
558
|
-
with _YieldTempDirectory("staging content") as staging_directory:
|
|
558
|
+
with _YieldTempDirectory(working_dir / "staging", "staging content") as staging_directory:
|
|
559
559
|
# ----------------------------------------------------------------------
|
|
560
560
|
@dataclass(frozen=True)
|
|
561
561
|
class Instruction:
|
|
@@ -709,15 +709,26 @@ def Restore(
|
|
|
709
709
|
def ExecuteTask(
|
|
710
710
|
status: ExecuteTasks.Status,
|
|
711
711
|
) -> Path:
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
712
|
+
# This function will create the following directory structure:
|
|
713
|
+
#
|
|
714
|
+
# <working_dir>
|
|
715
|
+
# └── <directory>
|
|
716
|
+
# └── transferred (temporary)
|
|
717
|
+
# └── decompressed (temporary)
|
|
718
|
+
# └── final
|
|
719
|
+
|
|
720
|
+
assert working_dir is not None
|
|
721
|
+
this_working_dir = working_dir / directory
|
|
722
|
+
final_dir = this_working_dir / "final"
|
|
723
|
+
|
|
724
|
+
if final_dir.is_dir():
|
|
715
725
|
# The destination already exists, no need to process it further
|
|
716
|
-
return
|
|
726
|
+
return final_dir
|
|
717
727
|
|
|
718
|
-
with
|
|
728
|
+
with _YieldTransferredArchive(
|
|
719
729
|
data_store, # type: ignore
|
|
720
730
|
directory,
|
|
731
|
+
this_working_dir / "transferred",
|
|
721
732
|
lambda bytes_transferred: cast(
|
|
722
733
|
None,
|
|
723
734
|
status.OnProgress(
|
|
@@ -725,10 +736,11 @@ def Restore(
|
|
|
725
736
|
bytes_transferred,
|
|
726
737
|
),
|
|
727
738
|
),
|
|
728
|
-
) as (
|
|
729
|
-
with
|
|
739
|
+
) as (transferred_dir, transferred_dir_is_temporary):
|
|
740
|
+
with _YieldDecompressedFiles(
|
|
741
|
+
transferred_dir,
|
|
742
|
+
this_working_dir / "decompressed",
|
|
730
743
|
directory,
|
|
731
|
-
archive_directory,
|
|
732
744
|
encryption_password,
|
|
733
745
|
lambda message: cast(
|
|
734
746
|
None,
|
|
@@ -737,11 +749,11 @@ def Restore(
|
|
|
737
749
|
message,
|
|
738
750
|
),
|
|
739
751
|
),
|
|
740
|
-
) as (
|
|
752
|
+
) as (decompressed_dir, decompressed_dir_is_temporary):
|
|
741
753
|
# Validate the contents
|
|
742
|
-
|
|
754
|
+
_VerifyFiles(
|
|
743
755
|
directory,
|
|
744
|
-
|
|
756
|
+
decompressed_dir,
|
|
745
757
|
lambda message: cast(
|
|
746
758
|
None,
|
|
747
759
|
status.OnProgress(
|
|
@@ -755,7 +767,7 @@ def Restore(
|
|
|
755
767
|
# directory structure and doesn't do anything to account for
|
|
756
768
|
# nested dirs. This assumption matches the current archive
|
|
757
769
|
# format.
|
|
758
|
-
if
|
|
770
|
+
if transferred_dir_is_temporary or decompressed_dir_is_temporary:
|
|
759
771
|
func = cast(Callable[[Path, Path], None], shutil.move)
|
|
760
772
|
else:
|
|
761
773
|
# ----------------------------------------------------------------------
|
|
@@ -770,14 +782,14 @@ def Restore(
|
|
|
770
782
|
|
|
771
783
|
func = CreateSymLink
|
|
772
784
|
|
|
773
|
-
temp_dest_dir =
|
|
785
|
+
temp_dest_dir = final_dir.parent / (final_dir.name + "__temp__")
|
|
774
786
|
|
|
775
787
|
shutil.rmtree(temp_dest_dir, ignore_errors=True)
|
|
776
788
|
temp_dest_dir.mkdir(parents=True)
|
|
777
789
|
|
|
778
790
|
items = [
|
|
779
791
|
item
|
|
780
|
-
for item in
|
|
792
|
+
for item in decompressed_dir.iterdir()
|
|
781
793
|
if item.name != INDEX_HASH_FILENAME
|
|
782
794
|
]
|
|
783
795
|
|
|
@@ -789,9 +801,9 @@ def Restore(
|
|
|
789
801
|
|
|
790
802
|
func(item, temp_dest_dir)
|
|
791
803
|
|
|
792
|
-
shutil.move(temp_dest_dir,
|
|
804
|
+
shutil.move(temp_dest_dir, final_dir)
|
|
793
805
|
|
|
794
|
-
return
|
|
806
|
+
return final_dir
|
|
795
807
|
|
|
796
808
|
# ----------------------------------------------------------------------
|
|
797
809
|
|
|
@@ -864,9 +876,9 @@ def Restore(
|
|
|
864
876
|
directory_working_dir
|
|
865
877
|
)
|
|
866
878
|
|
|
867
|
-
dest_filename.
|
|
868
|
-
|
|
869
|
-
|
|
879
|
+
if not dest_filename.is_file():
|
|
880
|
+
dest_filename.parent.mkdir(parents=True, exist_ok=True)
|
|
881
|
+
os.symlink(fullpath, dest_filename)
|
|
870
882
|
|
|
871
883
|
# Read the instructions
|
|
872
884
|
with (directory_working_dir / INDEX_FILENAME).open() as f:
|
|
@@ -960,7 +972,8 @@ def Restore(
|
|
|
960
972
|
with dm.Nested("\nProcessing instructions...") as all_instructions_dm:
|
|
961
973
|
all_instructions_dm.WriteLine("")
|
|
962
974
|
|
|
963
|
-
temp_directory =
|
|
975
|
+
temp_directory = working_dir / "instructions"
|
|
976
|
+
temp_directory.mkdir(parents=True, exist_ok=True)
|
|
964
977
|
|
|
965
978
|
with ExitStack(lambda: shutil.rmtree(temp_directory)):
|
|
966
979
|
commit_actions: list[Callable[[], None]] = []
|
|
@@ -1287,9 +1300,9 @@ def _CommitFileBasedDataStore(
|
|
|
1287
1300
|
# ----------------------------------------------------------------------
|
|
1288
1301
|
@contextmanager
|
|
1289
1302
|
def _YieldTempDirectory(
|
|
1303
|
+
temp_directory: Path,
|
|
1290
1304
|
desc: str,
|
|
1291
1305
|
) -> Iterator[Path]:
|
|
1292
|
-
temp_directory = PathEx.CreateTempDirectory()
|
|
1293
1306
|
should_delete = True
|
|
1294
1307
|
|
|
1295
1308
|
try:
|
|
@@ -1299,7 +1312,8 @@ def _YieldTempDirectory(
|
|
|
1299
1312
|
raise
|
|
1300
1313
|
finally:
|
|
1301
1314
|
if should_delete:
|
|
1302
|
-
|
|
1315
|
+
if temp_directory.is_dir():
|
|
1316
|
+
shutil.rmtree(temp_directory)
|
|
1303
1317
|
else:
|
|
1304
1318
|
sys.stderr.write(
|
|
1305
1319
|
f"**** The temporary directory '{temp_directory}' was preserved due to errors while {desc}.\n",
|
|
@@ -1308,9 +1322,10 @@ def _YieldTempDirectory(
|
|
|
1308
1322
|
|
|
1309
1323
|
# ----------------------------------------------------------------------
|
|
1310
1324
|
@contextmanager
|
|
1311
|
-
def
|
|
1325
|
+
def _YieldTransferredArchive(
|
|
1312
1326
|
data_store: FileBasedDataStore,
|
|
1313
1327
|
directory: str,
|
|
1328
|
+
working_dir: Path,
|
|
1314
1329
|
status_func: Callable[[str], None],
|
|
1315
1330
|
) -> Iterator[
|
|
1316
1331
|
tuple[
|
|
@@ -1318,6 +1333,8 @@ def _YieldRestoredArchive(
|
|
|
1318
1333
|
bool, # is temporary directory
|
|
1319
1334
|
],
|
|
1320
1335
|
]:
|
|
1336
|
+
"""Transfer content from the data store to the local filesystem."""
|
|
1337
|
+
|
|
1321
1338
|
if data_store.is_local_filesystem:
|
|
1322
1339
|
working_dir = data_store.GetWorkingDir() / directory
|
|
1323
1340
|
assert working_dir.is_dir(), working_dir
|
|
@@ -1327,7 +1344,7 @@ def _YieldRestoredArchive(
|
|
|
1327
1344
|
|
|
1328
1345
|
status_func("Calculating files to transfer...")
|
|
1329
1346
|
|
|
1330
|
-
with _YieldTempDirectory("transferring archive files") as
|
|
1347
|
+
with _YieldTempDirectory(working_dir, "transferring archive files") as working_dir:
|
|
1331
1348
|
# Map the remote filenames to local filenames
|
|
1332
1349
|
filename_map: dict[Path, Path] = {}
|
|
1333
1350
|
|
|
@@ -1339,7 +1356,7 @@ def _YieldRestoredArchive(
|
|
|
1339
1356
|
relative_root = root.relative_to(data_store_dir)
|
|
1340
1357
|
|
|
1341
1358
|
for filename in filenames:
|
|
1342
|
-
filename_map[root / filename] =
|
|
1359
|
+
filename_map[root / filename] = working_dir / relative_root / filename
|
|
1343
1360
|
|
|
1344
1361
|
if not filename_map:
|
|
1345
1362
|
raise Exception(f"The directory '{directory}' does not contain any files.")
|
|
@@ -1359,14 +1376,15 @@ def _YieldRestoredArchive(
|
|
|
1359
1376
|
),
|
|
1360
1377
|
)
|
|
1361
1378
|
|
|
1362
|
-
yield
|
|
1379
|
+
yield working_dir, True
|
|
1363
1380
|
|
|
1364
1381
|
|
|
1365
1382
|
# ----------------------------------------------------------------------
|
|
1366
1383
|
@contextmanager
|
|
1367
|
-
def
|
|
1384
|
+
def _YieldDecompressedFiles(
|
|
1385
|
+
transferred_dir: Path,
|
|
1386
|
+
decompressed_dir: Path,
|
|
1368
1387
|
directory_name: str,
|
|
1369
|
-
archive_dir: Path,
|
|
1370
1388
|
encryption_password: str | None,
|
|
1371
1389
|
status_func: Callable[[str], None],
|
|
1372
1390
|
) -> Iterator[
|
|
@@ -1375,8 +1393,10 @@ def _YieldRestoredFiles(
|
|
|
1375
1393
|
bool, # is temporary directory
|
|
1376
1394
|
],
|
|
1377
1395
|
]:
|
|
1378
|
-
|
|
1379
|
-
|
|
1396
|
+
"""Decompress the archive if necessary"""
|
|
1397
|
+
|
|
1398
|
+
if (transferred_dir / INDEX_FILENAME).is_file():
|
|
1399
|
+
yield transferred_dir, False
|
|
1380
1400
|
return
|
|
1381
1401
|
|
|
1382
1402
|
# By default, 7zip will prompt for a password with archives that were created
|
|
@@ -1394,7 +1414,7 @@ def _YieldRestoredFiles(
|
|
|
1394
1414
|
# Validate
|
|
1395
1415
|
status_func("Validating archive...")
|
|
1396
1416
|
|
|
1397
|
-
archive_filename =
|
|
1417
|
+
archive_filename = transferred_dir / (ARCHIVE_FILENAME + ".001")
|
|
1398
1418
|
|
|
1399
1419
|
if not archive_filename.is_file():
|
|
1400
1420
|
raise Exception(f"The archive file '{archive_filename.name}' was not found.")
|
|
@@ -1424,10 +1444,12 @@ def _YieldRestoredFiles(
|
|
|
1424
1444
|
# Extract
|
|
1425
1445
|
status_func("Extracting archive...")
|
|
1426
1446
|
|
|
1427
|
-
with _YieldTempDirectory("extracting the archive") as
|
|
1447
|
+
with _YieldTempDirectory(decompressed_dir, "extracting the archive") as decompressed_dir:
|
|
1448
|
+
decompressed_dir.mkdir(parents=True, exist_ok=True)
|
|
1449
|
+
|
|
1428
1450
|
result = SubprocessEx.Run(
|
|
1429
1451
|
f'{_GetZipBinary()} x "{archive_filename}" "-p{password}"',
|
|
1430
|
-
cwd=
|
|
1452
|
+
cwd=decompressed_dir,
|
|
1431
1453
|
)
|
|
1432
1454
|
|
|
1433
1455
|
if result.returncode != 0:
|
|
@@ -1450,11 +1472,11 @@ def _YieldRestoredFiles(
|
|
|
1450
1472
|
),
|
|
1451
1473
|
)
|
|
1452
1474
|
|
|
1453
|
-
yield
|
|
1475
|
+
yield decompressed_dir, True
|
|
1454
1476
|
|
|
1455
1477
|
|
|
1456
1478
|
# ----------------------------------------------------------------------
|
|
1457
|
-
def
|
|
1479
|
+
def _VerifyFiles(
|
|
1458
1480
|
directory_name: str,
|
|
1459
1481
|
contents_dir: Path,
|
|
1460
1482
|
status_func: Callable[[str], None],
|
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.4.
|
|
12
|
+
__version__ = "0.4.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FileBackup
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Tool for creating and restoring file system backups.
|
|
5
5
|
Project-URL: Homepage, https://github.com/davidbrownell/FileBackup
|
|
6
6
|
Project-URL: Documentation, https://github.com/davidbrownell/FileBackup
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
FileBackup/Mirror.py,sha256=GDgTWVNUYn5O7vlH5YVi0LppwQN4R99J61g1UCBwlfI,23544
|
|
2
|
-
FileBackup/Offsite.py,sha256=
|
|
2
|
+
FileBackup/Offsite.py,sha256=IFi6_bN5LIQdS47WqYphLvtx7d9mV3_zOsfVaWLNzUM,63054
|
|
3
3
|
FileBackup/Snapshot.py,sha256=Dj-XFjAFWkqNZIlMHqTOI1Ygdm1jB3orIwDxMQSrupQ,26970
|
|
4
|
-
FileBackup/__init__.py,sha256=
|
|
4
|
+
FileBackup/__init__.py,sha256=655mOBYNTfSBiePVm-85Y13RwifOu84XpRLXHL_Fx7U,597
|
|
5
5
|
FileBackup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
FileBackup/CommandLine/CommandLineArguments.py,sha256=7oLUf9tODd_OjBNzIjQiiyZ9ujy-KbW53Pg0dF0MQG8,2378
|
|
7
7
|
FileBackup/CommandLine/EntryPoint.py,sha256=Fn62wtKV6uiTJ1dQpolNFUyWWeUOKKgetOFSXPgoe-w,1813
|
|
@@ -19,8 +19,8 @@ FileBackup/DataStore/Interfaces/FileBasedDataStore.py,sha256=eMA_cQ-CED83z5uMDQZ
|
|
|
19
19
|
FileBackup/DataStore/Interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
FileBackup/Impl/Common.py,sha256=2WFMx5BdCnZmo-cuLYZlIx4Nr_ZBKmJWfMaFVUpsqKo,24935
|
|
21
21
|
FileBackup/Impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
filebackup-0.4.
|
|
23
|
-
filebackup-0.4.
|
|
24
|
-
filebackup-0.4.
|
|
25
|
-
filebackup-0.4.
|
|
26
|
-
filebackup-0.4.
|
|
22
|
+
filebackup-0.4.4.dist-info/METADATA,sha256=iynFleOqIop_GgtOoApRlRBPWZ2DwH7S2Y2TNkOeE3U,5762
|
|
23
|
+
filebackup-0.4.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
24
|
+
filebackup-0.4.4.dist-info/entry_points.txt,sha256=2SUVO3jHsi06MYfUEdqxaVwVvTQVjxzIWlHexRKWRgQ,121
|
|
25
|
+
filebackup-0.4.4.dist-info/licenses/LICENSE,sha256=2dPVskJuHRLwSF9fQiAfln1RVWya2DsKNPvgknRLTYw,1074
|
|
26
|
+
filebackup-0.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|