# GARstow binary package operations
# Copyright 2010 Adam Sampson <ats@offog.org>

from config import *
from packages import *
from utils import *
import sys, os, re, subprocess, shlex

garball_header = "garball-version-1"

compression_modes = {
	"gzip": ["-z"],
	"bzip2": ["-j"],
	"xz": ["-J"],
	}

def get_garball_name(package):
	"""Return the filename (with leading directory components) for a
	garball."""

	# The arch is prefixed as a directory to make it easier to have partial
	# mirrors of the GARStow package archive.
	return ("%s/%s-%s.garball"
	        % (get_package_arch(package),
	           package,
	           get_package_hash(package)))

def is_garball_up_to_date(fn):
	"""Check whether a garball file is up-to-date.
	Return True if it is, False if it isn't, and None if we don't know (for
	example, if it's not for this architecture)."""

	m = re.match(r'.*/([^/]*)/([^/]*)-([^/-]*).garball$', fn)
	if m is None:
		die("Not a garball filename: ", fn)
	(arch, package, hash) = m.group(1, 2, 3)
	if package not in get_package_names():
		return False
	elif arch != get_package_arch(package):
		return None
	elif hash != get_package_hash(package):
		return False
	else:
		return True

def make_garball(package, overwrite = True):
	"""Build a garball from a package.
	By default, do this even if the garball already exists, and return the
	name of the file created.
	If overwrite is False and the garball already exists, returns None."""

	version = get_version(package)
	if get_installed_version(package) != version:
		die("%s: latest version is not installed" % package)

	output_fn = get_garball_dir() + "/" + get_garball_name(package)
	if os.access(output_fn, os.F_OK) and not overwrite:
		return None

	runtime_deps = get_dependencies(package, build=False)

	info = {
		"Arch": get_package_arch(package),
		"Compressor": "gzip",
		"Description": get_description(package),
		"Hash": get_package_hash(package),
		"Name": package,
		"Requires": " ".join(sorted(runtime_deps)),
		"Version": version,
		}

	tar_cmd = [
		"tar",
		"-c",
		"-f", "-",
		]
	tar_cmd += compression_modes[info["Compressor"]]
	tar_cmd += [
		"-C", in_root(get_packages_dir()),
		"%s-%s" % (package, version),
		]

	mkdir_p(os.path.dirname(output_fn))
	f = open(output_fn, "wb")
	f.write(("%s\n" % garball_header).encode("UTF-8"))
	for k, v in sorted(info.items()):
		f.write(("%s: %s\n" % (k, v)).encode("UTF-8"))
	f.write("\n")
	f.flush()
	rc = subprocess.call(tar_cmd, stdout=f)
	if rc != 0:
		die("tar failed while making garball for ", package)
	f.close()

	return output_fn

# FIXME: should throw exceptions
def read_garball_headers(f):
	"""Read the headers from a garball file.
	If successful, return a dict, and leave the file at the start of the
	payload. If unsuccessful, return None."""

	l = f.readline().decode("UTF-8")
	if l != garball_header + "\n":
		# Bad signature line.
		return None

	headers = {}
	while True:
		l = f.readline().decode("UTF-8")
		if l == "":
			# Unexpected EOF.
			return None
		l = l[:-1]
		if l == "":
			# End of headers.
			return headers

		ls = l.split(": ", 1)
		if len(ls) != 2:
			# Bad header line.
			return None
		headers[ls[0]] = ls[1]

# FIXME: this could actually return an open file
def get_garball(package):
	"""Find (downloading, if necessary) a garball file for a package, and
	return the filename. If not found, return None."""

	garball_name = get_garball_name(package)

	fn = get_garball_dir() + "/" + garball_name
	if os.access(fn, os.F_OK):
		return fn

	sites = get_config_portfile().variable("GARBALL_SITES").split()
	wget_opts = shlex.split(get_config_portfile().variable("WGET_OPTS"))
	for site in sites:
		url = site + garball_name

		cmd = [
			"wget",
			"-O", fn,
			] + wget_opts + [url]
		if subprocess.call(cmd) == 0:
			return fn

		try:
			os.unlink(fn)
		except OSError:
			pass

	return None

def install_garball(package):
	fn = get_garball(package)
	if fn is None:
		die("No garball found for ", package)

	status("Unpacking ", fn)
	f = open(fn, "rb")

	info = read_garball_headers(f)
	if info is None:
		die("Failed to read headers from ", fn)
	if info["Hash"] != get_package_hash(package):
		die("Package hash does not match in ", fn)
	version = info["Version"]

	packagedir = get_packages_dir() + "/" + package + "-" + version
	command(["rm", "-fr", in_root(packagedir)])

	tar_cmd = [
		"tar",
		"-x",
		"-f", "-",
		"-p",
		"-C", in_root(get_packages_dir()),
		]
	tar_cmd += compression_modes[info["Compressor"]]
	tar = subprocess.Popen(tar_cmd, stdin=subprocess.PIPE)

	while True:
		data = f.read(65536)
		if data == "":
			break
		tar.stdin.write(data)
	tar.stdin.close()
	if tar.wait() != 0:
		die("tar failed while extracting ", fn)

	f.close()

	install_version(package, version)
