# vim: set nolist noet ts=4:
# Copyright 2002 2005 Ben Escoto
#
# This file is part of rdiff-backup.
#
# rdiff-backup is free software; you can redistribute it and/or modify
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# rdiff-backup is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with rdiff-backup; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

"""Invoke rdiff utility to make signatures, deltas, or patch"""

import os, librsync
import connection, Globals, log, static, TempFile, rpath, hash


def get_signature(rp, blocksize = None):
	"""Take signature of rpin file and return in file object"""
	if not blocksize: blocksize = find_blocksize(rp.getsize())
	log.Log("Getting signature of %s with blocksize %s" %
			(rp.get_indexpath(), blocksize), 7)
	return librsync.SigFile(rp.open("rb"), blocksize)

def get_signature_vf(rorp):
	size = os.path.getsize(rorp.path)
	file2 = open(rorp.path, 'rb')
	signature_fp = librsync.SigFile(file2, find_blocksize(size))
	return signature_fp

def find_blocksize(file_len):
	"""Return a reasonable block size to use on files of length file_len

	If the block size is too big, deltas will be bigger than is
	necessary.  If the block size is too small, making deltas and
	patching can take a really long time.

	"""
	if file_len < 4096: return 64 # set minimum of 64 bytes
	else: # Use square root, rounding to nearest 16
		return long(pow(file_len, 0.5)/16)*16

def get_delta_sigfileobj(sig_fileobj, rp_new):
	"""Like get_delta but signature is in a file object"""
	log.Log("Getting delta of %s with signature stream" % (rp_new.path,), 7)
	return librsync.DeltaFile(sig_fileobj, rp_new.open("rb"))

def get_delta_sigrp(rp_signature, rp_new):
	"""Take signature rp and new rp, return delta file object"""
	log.Log("Getting delta of %s with signature %s" %
			(rp_new.path, rp_signature.get_indexpath()), 7)
	return librsync.DeltaFile(rp_signature.open("rb"), rp_new.open("rb"))

def get_delta_sigrp_hash(rp_signature, rp_new):
	"""Like above but also calculate hash of new as close() value"""
	log.Log("Getting delta (with hash) of %s with signature %s" %
			(rp_new.path, rp_signature.get_indexpath()), 7)
	return librsync.DeltaFile(rp_signature.open("rb"),
							  hash.FileWrapper(rp_new.open("rb")))
	

def write_delta(basis, new, delta, compress = None):
	"""Write rdiff delta which brings basis to new"""
	log.Log("Writing delta %s from %s -> %s" %
			(basis.path, new.path, delta.path), 7)
	deltafile = librsync.DeltaFile(get_signature(basis), new.open("rb"))
	delta.write_from_fileobj(deltafile, compress)

def write_patched_fp(basis_fp, delta_fp, out_fp):
	"""Write patched file to out_fp given input fps.  Closes input files"""
	rpath.copyfileobj(librsync.PatchedFile(basis_fp, delta_fp), out_fp)
	assert not basis_fp.close() and not delta_fp.close()

def write_via_tempfile(fp, rp):
	"""Write fileobj fp to rp by writing to tempfile and renaming"""
	tf = TempFile.new(rp)
	retval = tf.write_from_fileobj(fp)
	rpath.rename(tf, rp)
	return retval

def patch_local(rp_basis, rp_delta, outrp = None, delta_compressed = None):
	"""Patch routine that must be run locally, writes to outrp

	This should be run local to rp_basis because it needs to be a real
	file (librsync may need to seek around in it).  If outrp is None,
	patch rp_basis instead.

	The return value is the close value of the delta, so it can be
	used to produce hashes.

	"""
	assert rp_basis.conn is Globals.local_connection
	if delta_compressed: deltafile = rp_delta.open("rb", 1)
	else: deltafile = rp_delta.open("rb")
	patchfile = librsync.PatchedFile(rp_basis.open("rb"), deltafile)
	if outrp: return outrp.write_from_fileobj(patchfile)
	else: return write_via_tempfile(patchfile, rp_basis)

def copy_local(rpin, rpout, rpnew = None):
	"""Write rpnew == rpin using rpout as basis.  rpout and rpnew local"""
	assert rpout.conn is Globals.local_connection
	deltafile = rpin.conn.librsync.DeltaFile(get_signature(rpout),
											 rpin.open("rb"))
	patched_file = librsync.PatchedFile(rpout.open("rb"), deltafile)

	if rpnew: rpnew.write_from_fileobj(patched_file)
	else: write_via_tempfile(patched_file, rpout)
	


