git » abk » commit b579c2f

Support multiple sources.

author Alberto Bertogli
2005-04-12 22:59:50 UTC
committer Alberto Bertogli
2005-04-12 22:59:50 UTC
parent c2d81ffc6d766ba158b9805b8b39b95adae5be9f

Support multiple sources.
This patch implements support for multiple sources, so you can now sync
multiple directories against a single one, very much like "cp".

abk +48 -39

diff --git a/abk b/abk
index 2dec45e..13c9bfa 100644
--- a/abk
+++ b/abk
@@ -29,7 +29,7 @@ VERSION = "0.03"
 
 def finfo_load(finfo):
 	"Loads data from the file."
-	s = os.lstat(finfo.name)
+	s = os.lstat(finfo.fullname)
 	finfo.stat = s
 	if S_ISREG(s.st_mode):
 		finfo.type = 'r'
@@ -43,7 +43,7 @@ def finfo_load(finfo):
 		finfo.rdev = s.st_rdev
 	elif S_ISFIFO(s.st_mode):
 		finfo.type = 'f'
-		finfo.linkto = os.readlink(finfo.name)
+		finfo.linkto = os.readlink(finfo.fullname)
 	elif S_ISDIR(s.st_mode):
 		finfo.type = 'd'
 	else:
@@ -82,7 +82,7 @@ def finfo_cmp_data(finfo, other):
 
 def finfo_copy_file_reg_raw(finfo, dst):
 	"Copy a regular file."
-	sfile = open(finfo.name, 'r')
+	sfile = open(finfo.fullname, 'r')
 	dfile = open(dst, 'w')
 
 	# the data
@@ -97,7 +97,7 @@ def finfo_copy_file_reg_raw(finfo, dst):
 def finfo_copy_file_reg_bzip2(finfo, dst):
 	"Copy a regular file, destination is bz2 compressed."
 	import bz2
-	sfile = open(finfo.name)
+	sfile = open(finfo.fullname)
 	dfile = open(dst, 'w')
 
 	bcomp = bz2.BZ2Compressor()
@@ -112,7 +112,7 @@ def finfo_copy_file_reg_bzip2(finfo, dst):
 def finfo_copy_file_reg_gzip(finfo, dst):
 	"Copy a regular file, destination is gzip compressed."
 	import gzip
-	sfile = open(finfo.name)
+	sfile = open(finfo.fullname)
 	dfile = gzip.open(dst, 'w')
 
 	data = sfile.read(PSIZE)
@@ -126,7 +126,7 @@ def finfo_copy_file_reg_gzip(finfo, dst):
 
 def finfo_copy_file_link(finfo, dst):
 	"Copy a symbolic link."
-	linkto = os.readlink(finfo.name)
+	linkto = os.readlink(finfo.fullname)
 	os.symlink(linkto, dst)
 
 
@@ -177,7 +177,7 @@ def finfo_hash_file_sha(finfo):
 	"Returns the sha1sum of a file."
 	import sha
 	hash = sha.new()
-	f = open(finfo.name)
+	f = open(finfo.fullname)
 	data = f.read(PSIZE)
 	while data:
 		hash.update(data)
@@ -189,7 +189,7 @@ def finfo_hash_file_md5(finfo):
 	"Returns the md5sum of a file."
 	import md5
 	hash = md5.new()
-	f = open(finfo.name)
+	f = open(finfo.fullname)
 	data = f.read(PSIZE)
 	while data:
 		hash.update(data)
@@ -204,8 +204,9 @@ def finfo_hash_file_none(finfo):
 
 class file_info:
 	"Represents a file"
-	def __init__(self, name):
+	def __init__(self, name, fullname):
 		self.name = name
+		self.fullname = fullname
 		self.mode = 0
 		self.uid = 0
 		self.gid = 0
@@ -250,6 +251,7 @@ class index_file:
 		self.name = name
 		self.db = {}
 		self.names = []
+		self.pathdb = {}
 
 	def load(self):
 		"Loads data from the file."
@@ -263,19 +265,22 @@ class index_file:
 
 	def save(self):
 		"Saves the index to the disk."
+		for f in self.db.keys():
+			self.db[f].fullname = ''
 		f = open(self.name, 'w')
 		cPickle.dump((self.db, self.names), f, cPickle.HIGHEST_PROTOCOL)
 		f.close()
 
-	def put_file(self, filename):
+	def put_file(self, filename, fullpath):
 		"Incorporates a file into the index."
-		self.db[filename] = file_info(filename)
+		self.db[filename] = file_info(filename, fullpath)
 		self.db[filename].load()
 		if self.db[filename].type == 'u':
 			# ignore files of unknown types, like unix sockets
-			del(self.db[filename].type)
+			del(self.db[filename])
 			return
 		self.names.append(filename)
+		self.pathdb[filename] = fullpath
 
 	def get_file(self, filename):
 		"Get the file_info object for the given filename."
@@ -283,17 +288,19 @@ class index_file:
 
 	def populate(self, root):
 		"Populate the index from a root path."
-		self.put_file(root)
+		root = os.path.abspath(root)
+		base, reduced = os.path.split(root)
+		self.put_file(reduced, root)
 		tree = os.walk(root, topdown = True)
 		for path, childs, files in tree:
 			for f in files:
-				#name = os.path.join(path, f)
-				name = path + '/' + f
-				self.put_file(name)
+				full = path + '/' + f
+				name = relative_path(base, full)
+				self.put_file(name, full)
 			for c in childs:
-				#name = os.path.join(path, c)
-				name = path + '/' + c
-				self.put_file(name)
+				full = path + '/' + c
+				name = relative_path(base, full)
+				self.put_file(name, full)
 
 
 def quiet_unlink(path):
@@ -326,30 +333,26 @@ def make_path(f):
 		# it can fail if already exist
 		pass
 
+def relative_path(base, path):
+	"""If base = '/x/x/b' and path = '/x/x/b/c/d', returns 'b/c/d'. Both
+	must be absolute for simplicity."""
+	res = path[len(base):]
+	while res[0] == '/':
+		res = res[1:]
+	return res
+
 
 #
 # main operations
 #
 
-def make_sync(src_path, srcidx_path, dst_path, dstidx_path):
+def make_sync(sources, srcidx_path, dst_path, dstidx_path):
 	"Sync two directories."
-	# reduce multiple '/' at the end to none (or only one if it's root)
-	while src_path[-1] == '/' and src_path != '/':
-		src_path = src_path[:-1]
-
 	# destination and indexes are always a complete path
 	srcidx_path = os.path.join(os.getcwd(), srcidx_path)
 	dst_path = os.path.join(os.getcwd(), dst_path)
 	dstidx_path = os.path.join(os.getcwd(), dstidx_path)
 
-	# move to the source's parent, and use the new source so we
-	# create only one level directories.
-	if src_path != '/':
-		parent, src = os.path.split(src_path)
-		if parent:
-			os.chdir(parent)
-			src_path = src
-
 	# load destination index
 	printv("* loading destination index")
 	dstidx = index_file(dstidx_path)
@@ -358,8 +361,9 @@ def make_sync(src_path, srcidx_path, dst_path, dstidx_path):
 	# create source index
 	printv("* building source index")
 	srcidx = index_file(srcidx_path)
-	srcidx.populate(src_path)
-	srcidx.save()
+	for src_path in sources:
+		printv("\t* " + src_path)
+		srcidx.populate(src_path)
 
 	printv("* sync")
 
@@ -406,6 +410,11 @@ def make_sync(src_path, srcidx_path, dst_path, dstidx_path):
 			printv('unlink\t', f, dst)
 			force_unlink(dst, dstidx.db[f].type)
 
+	# we save the index at last because it voids file_info.fullpath so we
+	# don't save unnecesary information
+	printv('* saving index')
+	srcidx.save()
+
 
 def show_idx(idx_path):
 	printv("* loading index")
@@ -472,8 +481,8 @@ commands:
     shows the given index file contents
   mkidx idx_file dir
     builds an index file for the given directory
-  sync idx src dst
-    synchronizes src with dst, using the given idx index file"""
+  sync idx src1 [src2 ... srcN] dst
+    synchronizes all sources with dst, using the given idx index file"""
 	parser = AbkOptionParser(usage=usage, description="A backup script - "
 		"Alberto Bertogli (albertogli@telpin.com.ar)",
 		version="%prog " + VERSION, prog='abk')
@@ -546,13 +555,13 @@ elif cmd == 'sync':
 	try:
 		old_idx_path = args[1]
 		new_idx_path = old_idx_path
-		src_path = args[2]
-		dst_path = args[3]
+		sources = args[2:-1]
+		dst_path = args[-1]
 	except:
 		parser.error("Missing parameter(s) for command sync.")
 	if opts.new_idx:
 		new_idx_path = opts.new_idx
-	make_sync(src_path, new_idx_path, dst_path, old_idx_path)
+	make_sync(sources, new_idx_path, dst_path, old_idx_path)
 else:
 	parser.error("Unknown command (%s)." % cmd)