self.epoch = epoch
self.mode = mode
+ @staticmethod
+ def fromDirName(dirname):
+ [strdate, strtime, epoch, mode] = dirname.split("-")
+
+ if not epoch in Epoch.keys():
+ raise ValueError("Invalid epoch: " + epoch)
+
+ if not mode in Mode:
+ raise ValueError("Invalid mode: " + mode)
+
+ date = datetime.datetime(int(strdate[0:4]),
+ int(strdate[4:6]), int(strdate[6:8]),\
+ int(strtime[0:2]), int(strtime[2:4]))
+
+ return Backup(date, epoch, mode)
+
def __str__(self):
return "[date: " + self.date.ctime() + \
", epoch: " + self.epoch + \
", mode: " + self.mode + "]"
+ def colAlignedString(self):
+ return "%16s %8s %4s" % ( \
+ self.date.strftime("%Y-%m-%d %H:%M"), self.epoch, self.mode)
+
@staticmethod
def getDirName(date, epoch, mode):
"""Get directory name of backup by given properties."""
class BackupManager:
"""List and create backups"""
- def __init__(self, conffn):
+ def __init__(self, conffn, alwaysyes):
self.conf = Config()
+ self.alwaysyes = alwaysyes
self.conf.read(conffn)
backups = []
for entry in [ b for b in self.listAllDirs() if Backup.isBackupDir(b) ]:
- [strdate, strtime, epoch, mode] = entry.split("-")
-
- if not epoch in Epoch.keys():
- raise ValueError("Invalid epoch: " + epoch)
-
- if not mode in Mode:
- raise ValueError("Invalid mode: " + mode)
-
- date = datetime.datetime(int(strdate[0:4]),
- int(strdate[4:6]), int(strdate[6:8]),\
- int(strtime[0:2]), int(strtime[2:4]))
- backups += [ Backup(date, epoch, mode) ]
+ backups += [ Backup.fromDirName(entry) ]
return backups
- def backupFileSet(self, fileset, targetdir, since=None):
+ def backupFileSet(self, fileset, targetdir, log, since=None):
"""Create an archive for given fileset at given target directory."""
print("Running file set: " + fileset.name)
tarargs = [tarpath] + taropts + ["-f", fsfn] + fileset.dirs
#print("tarargs: ", tarargs)
- tarp = subprocess.Popen( tarargs )
+ print("tar call: " + " ".join(tarargs), file=log)
+ tarp = subprocess.Popen( tarargs, stderr=subprocess.PIPE )
rett = tarp.wait()
if rett != 0:
- print(tarpath + " returned with exit status " + str(rett) + ":")
+ sys.stderr.write( tarp.stderr.read() )
+ msg = tarpath + " returned with exit status " + str(rett) + "."
+ print(msg)
+ print(msg, log)
def backup(self, epoch=None, mode=None):
if mode != "full":
print("** Warning: full backup recommended!")
+
+ # If we have a full backup, we backup everything
+ since = None
+ if mode == "diff":
+ since = sorted(oldfullbackups, key=lambda b: b.date)[-1].date
+ elif mode == "incr":
+ since = sorted(oldbackups, key=lambda b: b.date)[-1].date
+
+ if since != None:
+ print("Making backup relative to ", since.ctime())
+
+ yesno = self.ask_user_yesno("Proceed? [Y, n] ")
+ if yesno == "n":
+ return
+
# Create new target directory
basedir = self.conf.directory
dirname = Backup.getDirName(now, epoch, mode)
targetdir = os.path.join(basedir, tmpdirname)
os.mkdir( targetdir )
- # If we have a full backup, we backup everything
- since = None
- # Get latest full backup time
- if mode == "diff":
- since = sorted(oldfullbackups, key=lambda b: b.date)[-1].date
- # Get latest backup time
- elif mode == "incr":
- since = sorted(oldbackups, key=lambda b: b.date)[-1].date
+ log = open(os.path.join(targetdir, "log.log"), 'w')
+ print("Started: " + now.ctime(), file=log)
# Backup all file sets
for s in self.conf.sets:
- self.backupFileSet(s, targetdir, since)
+ self.backupFileSet(s, targetdir, log, since)
+
+ print("Stopped: " + datetime.datetime.now().ctime(), file=log)
+ log.close()
# Rename backup directory to final name
os.rename( targetdir, os.path.join(basedir, dirname) )
f.close()
+
def prune(self):
"""Prune old backup files"""
+ allDirs = self.listAllDirs()
# Collect all directories not matching backup name
- dirs = [ d for d in self.listAllDirs() if not Backup.isBackupDir(d) ]
+ removeDirs = [ d for d in allDirs if not Backup.isBackupDir(d) ]
- # Get all directories which are outdated
+ # Get all directories which are kept
backups = self.listOldBackups()
+ keepdirs = []
byepoch = { e : list(sorted( [ b for b in backups if b.epoch == e ], \
key=lambda b : b.date, reverse=True)) for e in RealEpoch }
for e in byepoch:
keep = self.conf.epochkeeps[e]
old = byepoch[e][keep:]
- dirs += [ Backup.getDirName(b.date, b.epoch, b.mode) for b in old]
+ removeDirs += [ Backup.getDirName(b.date, b.epoch, b.mode) for b in old]
- if len(dirs) == 0:
- print("No stale/outdated entries to remove.")
- return
print("List of stale/outdated entries:")
- for d in dirs:
- print(" " + d)
+ for d in allDirs:
+ if d in removeDirs:
+ print("[*] ", end="")
+ else:
+ print("[ ] ", end="")
+
+ if Backup.isBackupDir(d):
+ print( Backup.fromDirName(d).colAlignedString())
+ else:
+ print(d)
+
+ # Check that dirs to be removed is in list of all dirs
+ for d in removeDirs:
+ assert( d in allDirs )
+
+ if len(removeDirs) == 0:
+ print("No stale/outdated entries to remove.")
+ return
basedir = self.conf.directory
- yesno = input("Remove listed entries? [y, N] ")
+ yesno = self.ask_user_yesno("Remove entries marked by '*'? [y, N] ")
if yesno == "y":
- for d in dirs:
+ for d in removeDirs:
shutil.rmtree(os.path.join(basedir, d))
+ def ask_user_yesno(self, question):
+ if self.alwaysyes:
+ print(question + " y")
+ return "y"
+ else:
+ return input(question)
+
def printUsage():
"""Print --help text"""
print(" -e, --epoch <epoch> force to create backup for given epoch:")
print(" year, month, week, day, hour, sporadic")
print(" -m, --mode <mode> override mode: full, diff, or incr")
+ print(" -y, --yes always assume 'yes' when user is asked")
if __name__ == "__main__":
cmd = "list"
mode = None
epoch = None
+ yes = False
i = 0
while i < len(sys.argv)-1:
i += 1
conffn = sys.argv[i]
+ elif opt in ["-y", "--yes"]:
+ yes = True
+
elif opt in ["-m", "--mode"]:
i += 1
mode = sys.argv[i]
exit(1)
try:
- man = BackupManager(conffn)
+ man = BackupManager(conffn, yes)
if cmd == "backup":
man.backup(epoch, mode)
if cmd == "list":
for b in sorted(man.listOldBackups(), key=lambda b: b.date):
- print(b.date.strftime("%Y-%m-%d %H:%M") + \
- "\t" + b.epoch + "\t" + b.mode)
+ print(b.colAlignedString())
if cmd == "prune":
man.prune()