Skip to content

Instantly share code, notes, and snippets.

@PetrGlad
Last active August 29, 2015 14:20
Show Gist options
  • Save PetrGlad/448abdeb8710f6611fd8 to your computer and use it in GitHub Desktop.
Save PetrGlad/448abdeb8710f6611fd8 to your computer and use it in GitHub Desktop.
bak.py
DOC = """
This script creates/restores_from backup copy of given
file (pushes/pops backup file into/from stack of backups)
Revision 3
Caveat: This script may work incorrectly if there are pre-existing
files which look like backups i.e. files matching ".+\.#[a-zA-Z]*[0-9]+"
Examples:
make backup copy of a.txt
python bak.py a.txt
make backup copy of sub directory a
python bak.py a
restore file from latest backup file
python bak.py --restore a.txt
find out what files look like backups in current dir
python bak.py --list .
Copygight (C) 2004, Petr Gladkikh
You can redistribute and/or modify this program under the terms
of the Common Public Licence version 1.0 as published by
Open Source Initiative corporation. Full text of the
licence can be found at http://opensource.org/licenses/cpl.php
"""
import sys
import shutil
import os
from os import path
import re
import tarfile
import glob
SEPARATOR = ".#"
def make_copy(a, b):
# print "make_copy", a, b # debug
if os.path.isdir(a):
shutil.copytree(a, b)
else:
shutil.copyfile(a, b)
def tar_store(a, b):
# print "tar_store", a, b # debug
tar = tarfile.open(b, "w:bz2")
tar.add(a)
tar.close()
def tar_restore(a, b):
# print "tar_restore", a, b # debug
tar = tarfile.open(a, "r")
for tarinfo in tar:
tar.extract(tarinfo)
tar.close()
def match_last(regex, str):
"return last match of regex in given string"
mi = regex.finditer(str)
match = None
try:
while 1:
match = mi.next()
except StopIteration:
pass
return match
def parse_bak_name(filename):
""" Parse bak into (filename, index, method)
or None if it does not seem like a bak """
rex = re.compile(
"(?P<sfx>" + re.escape(SEPARATOR) + ")"
+ "(?P<mtd>[a-zA-Z]*)"
+ "(?P<idx>[0-9]+)$")
match = match_last(rex, filename)
if match != None:
fn = filename[0 : match.start('sfx')]
id = eval(match.group('idx'))
m = match.group('mtd')
return (fn, id, m) # original_filename, depth, method_id
else:
return None
def make_bak_name(filename, method, id):
"make bak filename with given depth"
return filename + SEPARATOR + method + `id`
def remove(a):
if os.path.exists(a):
if os.path.isdir(a):
shutil.rmtree(a)
else:
os.remove(a)
class Backup:
def push(self, filename, id):
print "Pushing", filename, "[" + `id` + "]"
bak_name = make_bak_name(filename, self.tag, id)
self.store(filename, bak_name)
def pop(self, filename, id):
print "Popping", filename, "[" + `id` + "]"
bak_name = make_bak_name(filename, self.tag, id)
remove(filename)
self.restore(bak_name, filename)
remove(bak_name)
class CopyBackup(Backup):
tag = ""
def store(self, from_name, to_name):
make_copy(from_name, to_name)
def restore(self, from_name, to_name):
make_copy(from_name, to_name)
class BzipBackup(Backup):
tag = "bz"
def store(self, from_name, to_name):
tar_store(from_name, to_name)
def restore(self, from_name, to_name):
tar_restore(from_name, to_name)
METHODS = [ CopyBackup(), BzipBackup() ]
def find_method(method_id):
method = None
for m in METHODS:
if m.tag == method_id:
return m
else:
return None
def list_baks(filename):
directory = os.path.dirname(filename)
fileonly = os.path.split(filename)[-1]
lst = {}
for fn in os.listdir(directory):
bak = parse_bak_name(fn)
if bak != None:
(fn, id, m) = bak
if fileonly == fn:
lst[id] = m
return lst
def get_top_bak(filename):
"find max suffix for given filename"
lst = list_baks(filename)
if len(lst) > 0:
id = max(lst)
return (id, lst[id])
else:
return None
def apply_file_fn(filename, function):
if parse_bak(filename) == None:
function(filename)
def apply_fn(filename, function):
if os.path.isdir(filename):
for fn in os.listdir(filename):
apply_file_fn(os.path.join(filename, fn), function)
else:
apply_file_fn(filename, function)
def push(filename, push_method):
"make backup for given file"
tb = get_top_bak(filename)
if tb == None:
id = 0
else:
id = tb[0] + 1
push_method.push(filename, id)
def pop(filename):
"restore file from backup"
bk = get_top_bak(filename)
if bk == None:
print "No backups for", filename
else:
(id, method_id) = bk
method = find_method(method_id)
if method == None:
print "No handler for method '%s'" % method_id
else:
method.pop(filename, id)
def inventory(filename):
"list backups (print versions) for given file or directroy"
ids = list_baks(filename)
if len(ids) > 0:
lst = []
for i in ids.iteritems():
s = `i[0]`
if len(i[1]) > 0:
s += "("+i[1]+")"
lst.append(s)
import string
print filename, ":", string.join(lst, ", ")
else:
print "No backups for", filename
def cleanup(filename):
"remove all backups for given file"
for i in list_baks(filename).iteritems():
fn = make_bak_name(filename, i[1], i[0])
print "Deleting", fn
os.remove(fn)
if __name__ == '__main__':
from optparse import OptionParser
usage = "usage: %prog [options] filename1 [filename2 ...]"
parser = OptionParser(usage=usage)
parser.add_option("-z", "--pack",
action="store_true", dest="use_tar", default=False,
help="Use tar+bzip2 to store backup versions")
parser.add_option("-r", "--restore",
action="store_true", dest="do_restore", default=False,
help="Restore file (or directory) from last stored version")
parser.add_option("-l", "--list",
action="store_true", dest="list_files", default=False,
help="List backups for given file")
parser.add_option("-c", "--clean",
action="store_true", dest="do_cleanup", default=False,
help="Delete all backups for given file (see also --list option)")
parser.add_option("--about",
action="store_true", dest="show_about", default=False,
help="Print additional information about this program (see also --help option)")
(options, args) = parser.parse_args()
if options.show_about:
if options.list_files or options.do_cleanup:
parser.show_help()
else:
print DOC
elif options.list_files:
if options.do_cleanup:
parser.show_help()
else:
inventory(args[0])
elif options.do_cleanup:
cleanup(args[0])
else:
# file operations
for fname in args:
if options.do_restore:
if options.use_tar:
print "Error: -r or --restore option autodetects decompressing options.\n\t-z or --pack should not be specified."
else:
pop(fname) # method is autodetected
else:
if options.use_tar:
method = BzipBackup()
else:
method = CopyBackup()
push(fname, method)
python C:\Programs\bin\bak.py %*
python C:\Programs\bin\bak.py -z %*
python C:\Programs\bin\bak.py -r %1 %2 %3 %4 %5 %6 %7 %8 %9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment