"""Code for the B{0store} command-line interface."""

# Copyright (C) 2006, Thomas Leonard
# See the README file for details, or visit http://0install.net.

import sys, os
from zeroinstall.zerostore.manifest import generate_manifest, verify, get_algorithm, copy_tree_with_verify
from zeroinstall import zerostore, SafeException, support

stores = None

def init_stores():
	global stores
	assert stores is None
	if stores is None:
		stores = zerostore.Stores()

class UsageError(SafeException): pass

def do_manifest(args):
	"""manifest DIRECTORY [ALGORITHM]"""
	if len(args) < 1 or len(args) > 2: raise UsageError("Wrong number of arguments")
	if len(args) == 2:
		alg = get_algorithm(args[1])
	else:
		# If no algorithm was given, guess from the directory name
		name = os.path.basename(args[0])
		if '=' in name:
			alg = get_algorithm(name.split('=', 1)[0])
		else:
			alg = get_algorithm('sha1')
	digest = alg.new_digest()
	for line in alg.generate_manifest(args[0]):
		print line
		digest.update(line + '\n')
	print alg.getID(digest)
	sys.exit(0)

def do_find(args):
	"""find DIGEST"""
	if len(args) != 1: raise UsageError("Wrong number of arguments")
	try:
		print stores.lookup(args[0])
		sys.exit(0)
	except zerostore.BadDigest, ex:
		print >>sys.stderr, ex
	except zerostore.NotStored, ex:
		print >>sys.stderr, ex
	sys.exit(1)

def do_add(args):
	"""add DIGEST (DIRECTORY | (ARCHIVE [EXTRACT]))"""
	from zeroinstall.zerostore import unpack
	if len(args) < 2: raise UsageError("Missing arguments")
	digest = args[0]
	if os.path.isdir(args[1]):
		if len(args) > 2: raise UsageError("Too many arguments")
		stores.add_dir_to_cache(digest, args[1])
	elif os.path.isfile(args[1]):
		if len(args) > 3: raise UsageError("Too many arguments")
		if len(args) > 2:
			extract = args[2]
		else:
			extract = None

		type = unpack.type_from_url(args[1])
		if not type:
			raise SafeException("Unknown extension in '%s' - can't guess MIME type" % args[1])
		unpack.check_type_ok(type)

		stores.add_archive_to_cache(digest, file(args[1]), args[1], extract, type = type)
	else:
		try:
			os.stat(args[1])
		except OSError, ex:
			if ex.errno != 2:			# No such file or directory
				raise UsageError(str(ex))	# E.g. permission denied
		raise UsageError("No such file or directory '%s'" % args[1])

def do_optimise(args):
	"""optimise [ CACHE ]"""
	if len(args) == 1:
		cache_dir = args[0]
	else:
		cache_dir = stores.stores[0].dir
	
	cache_dir = os.path.realpath(cache_dir)

	import stat
	info = os.stat(cache_dir)
	if not stat.S_ISDIR(info.st_mode):
		raise UsageError("Not a directory: '%s'" % cache_dir)

	impl_name = os.path.basename(cache_dir)
	if impl_name != 'implementations':
		raise UsageError("Cache directory should be named 'implementations', not\n"
				"'%s' (in '%s')" % (impl_name, cache_dir))

	print "Optimising", cache_dir

	import optimise
	uniq_size, dup_size, already_linked, man_size = optimise.optimise(cache_dir)
	print "Original size  :", support.pretty_size(uniq_size + dup_size) + " (excluding the %s of manifests)" % support.pretty_size(man_size)
	print "Already saved  :", support.pretty_size(already_linked)
	if dup_size == 0:
		print "No duplicates found; no changes made."
	else:
		print "Optimised size :", support.pretty_size(uniq_size)
		perc = (100 * float(dup_size)) / (uniq_size + dup_size)
		print "Space freed up :", support.pretty_size(dup_size), "(%.2f%%)" % perc
	print "Optimisation complete."

def do_verify(args):
	"""verify (DIGEST | (DIRECTORY [DIGEST])"""
	if len(args) == 2:
		required_digest = args[1]
		root = args[0]
	elif len(args) == 1:
		root = get_stored(args[0])
		required_digest = None		# Get from name
	else:
	     raise UsageError("Missing DIGEST or DIRECTORY")

	print "Verifying", root
	try:
		verify(root, required_digest)
		print "OK"
	except zerostore.BadDigest, ex:
		print str(ex)
		if ex.detail:
			print
			print ex.detail
			sys.exit(1)

def show_changes(actual, saved):
	import difflib
	for line in difflib.unified_diff(saved, actual, 'Recorded', 'Actual'):
		print line,

def do_list(args):
	"""list"""
	if args: raise UsageError("List takes no arguments")
	print "User store (writable) : " + stores.stores[0].dir
	for s in stores.stores[1:]:
		print "System store          : " + s.dir
	if len(stores.stores) < 2:
		print "No system stores."

def get_stored(dir_or_digest):
	if os.path.isdir(dir_or_digest):
		return dir_or_digest
	else:
		try:
			return stores.lookup(dir_or_digest)
		except zerostore.NotStored, ex:
			print >>sys.stderr, ex
		sys.exit(1)

def do_copy(args):
	"""copy SOURCE [ TARGET ]"""
	if len(args) == 2:
		source, target = args
	elif len(args) == 1:
		source = args[0]
		target = stores.stores[0].dir
	else:
		raise UsageError("Wrong number of arguments.")

	if not os.path.isdir(source):
		raise UsageError("Source directory '%s' not found" % source)
	if not os.path.isdir(target):
		raise UsageError("Target directory '%s' not found" % target)
	manifest_path = os.path.join(source, '.manifest')
	if not os.path.isfile(manifest_path):
		raise UsageError("Source manifest '%s' not found" % manifest_path)
	required_digest = os.path.basename(source)
	manifest_data = file(manifest_path).read()

	copy_tree_with_verify(source, target, manifest_data, required_digest)

commands = [do_add, do_copy, do_find, do_list, do_manifest, do_optimise, do_verify]


syntax highlighted by Code2HTML, v. 0.9.1