-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackup.py
98 lines (83 loc) · 4.32 KB
/
backup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import signal
import argparse
import logging as log
import os
from pathlib import Path
import errno
from alive_progress import alive_bar
from backupdef import BackupDef
from entries import FolderEntry
from diskspacereserver import DiskSpaceReserver
from util import sanitizeFilename
def backup(source: str, destination: str):
# Create current backup definition from source folder
print("Indexing current folder state...")
with alive_bar(monitor="{count} files", receipt=False) as bar:
folder = FolderEntry.fromFolder(source, bar)
folder.name = sanitizeFilename(folder.name)
new_backupdef = BackupDef(folder)
# Initialize old backup definition
backupdef_path = os.path.join(destination, f"{folder.name}.cbdef")
if Path(backupdef_path).is_file():
print("Loading old backup definition...")
current_backupdef = BackupDef.loadFromFile(backupdef_path)
else:
current_backupdef = BackupDef(FolderEntry(folder.name))
# Initialize delta backup definition
print("Creating delta backup definition...")
delta_backupdef = BackupDef.delta(new_backupdef, current_backupdef)
# Initialize disk space reservation
reserver_path = os.path.join(destination, f"{folder.name}.reserved")
reserver = DiskSpaceReserver(reserver_path, new_backupdef.fileSize * 3)
# Copy over files until the disk is filled up
print("Copying files...")
with alive_bar(delta_backupdef.folder.size,
monitor="{count:,} / {total:,} bytes [{percent:.2%}]",
stats="({rate:,.0f}b/s, eta: {eta}s)") as bar:
while delta_backupdef.folder.contents or delta_backupdef.folder.deleted:
try:
# Before starting to copy over files, reserve space for the eventual backupdef
reserver.reserve()
# Copy the files
delta_backupdef.processDelta(current_backupdef, source, destination, bar)
except KeyboardInterrupt:
# Script was ended by ctrl-c, save backupdef and exit
reserver.release()
current_backupdef.saveToFile(backupdef_path)
print("The copying was interrupted, the progress has been saved.")
exit()
except Exception as e:
if e.errno == errno.ENOSPC:
# Disk full, save backupdef of files copied up to this point and ask for new destination
with bar.pause():
reserver.release()
current_backupdef.saveToFile(backupdef_path)
dest_input = input(f"\aCartridge full, insert next one and enter new path ({destination}): ")
if dest_input != "":
destination = dest_input
backupdef_path = os.path.join(destination, f"{folder.name}.cbdef")
reserver.path = os.path.join(destination, f"{folder.name}.reserved")
else:
# Copying error, save backupdef, print exception message, continue copying next file
reserver.release()
current_backupdef.saveToFile(backupdef_path)
log.warning("The copying was interrupted by an error. "
"The progress has been saved, the details are below:")
log.warning(e)
# Save backupdef of (presumably all) files copied up to this point
reserver.release()
current_backupdef.saveToFile(backupdef_path)
if __name__ == '__main__':
signal.signal(signal.SIGINT, signal.default_int_handler)
parser = argparse.ArgumentParser(description="Perform an incremental backup to"
"multiple, smaller destination drives(cartridges).")
parser.add_argument("source", help="The source directory")
parser.add_argument("destination", help="The destination directory")
parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true")
args = parser.parse_args()
if args.verbose:
log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
else:
log.basicConfig(format="%(levelname)s: %(message)s")
log.info(f"Running with source {args.source} and destination {args.destination}")
backup(args.source, args.destination)