#!/usr/bin/python

# Copyright (c) 2009 Benjamin Drung <bdrung@ubuntu.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import csv
import optparse
import os
import stat
import subprocess
import sys
import zipfile
import shutil

import RDF

# error codes
COMMAND_LINE_SYNTAX_ERROR = 1
XPI_FILE_DOES_NOT_EXISTS = 2

def get_query_field_id_as_list(rdf_content, rdf_path, query_string):
	ret = []
	model = RDF.Model()
	parser = RDF.Parser(name="rdfxml")
	stream = parser.parse_string_into_model(model, rdf_content, "file:" + rdf_path)
	query = RDF.Query("PREFIX em: <http://www.mozilla.org/2004/em-rdf#> " + query_string, query_language="sparql")
	results = query.execute(model)
	for result in results:
		ret.append(str(result["id"]))
	return ret
		

def get_target_applications(script_name, xpi_obj, verbose=False):
	install_rdf_content = xpi_obj.read("install.rdf")
	target_applications = get_query_field_id_as_list(install_rdf_content, os.path.join(xpi_obj.filename, "install.rdf"),
		"SELECT ?id WHERE { ?x1 em:targetApplication ?x2 . ?x2 em:id ?id }")
	return target_applications

def get_extension_id(xpi_obj):
	install_rdf_content = xpi_obj.read("install.rdf")
	extension_ids = set(get_query_field_id_as_list(install_rdf_content, os.path.join(xpi_obj.filename, "install.rdf"),
		"SELECT ?id WHERE {?x1 em:targetApplication ?x2 . ?x1 em:id ?id }"))
	return extension_ids.pop()

def get_arch(package):
	lines = open("debian/control").readlines()
	package_lines = filter(lambda x: x.find("Package:") >= 0, lines)
	packages = map(lambda x: x[x.find(":")+1:].strip(), package_lines)
	architecture_lines = filter(lambda x: x.find("Architecture:") >= 0, lines)
	architectures = map(lambda x: x[x.find(":")+1:].strip(), architecture_lines)
	(p, arch) = filter(lambda (x, y): x == package, zip(packages, architectures))[0]
	return arch

def get_mode(filename):
	st = os.stat(filename)
	mode = st[stat.ST_MODE]
	return mode & 0777

def get_xul_apps():
	csvfile = open("/usr/share/mozilla-devscripts/xul-app-data.csv")
	csv_reader = csv.DictReader(csvfile)
	rows = []
	for row in csv_reader:
		rows.append(row)
	return rows

def incompatible_apps(target_applications):
	"""There are still some application that do not scan
	"/usr/{lib,share}/mozilla/extensions/$target_application" for extensions."""
	xul_apps = filter(lambda x: x["id"] in target_applications, get_xul_apps())
	app_list = reduce(lambda l, x: l + [x["package"]], xul_apps, list())
	incompatible_apps = ["icedove", "thunderbird"]
	incompatible_apps = filter(lambda x: x in app_list, incompatible_apps)
	return incompatible_apps

def install_xpi(script_name, package, xpi_file, exclude, install_dir, links,
		correct_permissions, remove_licenses, system_prefs,
		xpi_dest, install_xpi_only, verbose=False):
	# get xpi file content list
	if not os.path.isfile(xpi_file):
		print >> sys.stderr, "%s: Error: xpi file %s does not exist." % (script_name, xpi_file)
		sys.exit(XPI_FILE_DOES_NOT_EXISTS)
	zfobj = zipfile.ZipFile(xpi_file)
	xpi_content = zfobj.namelist()
	install_rdf_content = zfobj.read("install.rdf")

	extension_id = get_extension_id(zfobj)

	if xpi_dest:
		copy_dest = os.path.join("debian", package, xpi_dest.strip('/'))
		if not os.path.isdir(copy_dest):
			os.makedirs(copy_dest)
		shutil.copy(xpi_file, os.path.join(copy_dest, extension_id + ".xpi"))

	if install_xpi_only:
		return

	# determine installation directory
	if get_arch(package) == "all":
		lib_share_dir = "share"
	else:
		lib_share_dir = "lib"
	if install_dir is None:
		install_dir = os.path.join("usr", lib_share_dir, "xul-ext",
				package.replace("xul-ext-", ""))

	copy_dir = os.path.join("debian", package, install_dir.strip("/"))

	if verbose:
		print "%s: install directory: %s" % (script_name, install_dir)

	# remove documented license files
	if remove_licenses:
		pattern_list = ("copying", "gpl.txt", "licence", "license", "licence.txt", "license.txt")
		for name in filter(lambda x: not x.endswith('/'), xpi_content):
			basename = os.path.basename(name).lower()
			if basename in pattern_list:
				exclude.append(name)
				print "%s: exclude license file %s" % (script_name, name)

	# create directory and extract xpi file
	if not os.path.isdir(copy_dir):
		os.makedirs(copy_dir)
	command = ["unzip", "-o", "-d", copy_dir, xpi_file]
	if len(exclude) > 0:
		command.append("-x")
		command.extend(exclude)
	print " ".join(command)
	subprocess.call(command)

	# correct permissons of files to 644 and directories to 755
	if correct_permissions:
		for name in xpi_content:
			filename = os.path.join(copy_dir, name)
			if os.path.exists(filename):
				mode = get_mode(filename)
				if os.path.isdir(filename) and mode != 0755:
					print "%s: correct permission from %s to %s of %s" % (script_name, oct(mode), oct(0755), name)
					os.chmod(filename, 0755)
				elif os.path.isfile(filename):
					header = open(filename, "r").read(2)
					if header != "#!" and mode != 0644:
						# file without shebang
						print "%s: correct permission from %s to %s of %s" % (script_name, oct(mode), oct(0644), name)
						os.chmod(filename, 0644)
					elif header == "#!" and mode != 0755:
						# file with shebang
						print "%s: correct permission from %s to %s of %s" % (script_name, oct(mode), oct(0755), name)
						os.chmod(filename, 0755)

	# create a system preference file in /etc
	if system_prefs:
		# search for preference files
		preferences = filter(lambda f: os.path.dirname(f) == os.path.join("defaults", "preferences") and f.endswith(".js"), xpi_content)
		if len(preferences) > 0:
			prefdir = os.path.join("etc", "xul-ext")
			full_prefdir = os.path.join("debian", package, prefdir)
			if not os.path.exists(full_prefdir):
				os.makedirs(full_prefdir)
			prefname = package.replace("xul-ext-", "") + ".js"
			# create system preference file
			f = open(os.path.join(full_prefdir, prefname), "w")
			if os.path.isfile(os.path.join("debian", package + ".js")):
				# use debian/package.js as configuration file if it exists
				content = open(os.path.join("debian", package + ".js")).read()
				# replace @INSTALLDIR@ by the actual installation directory
				content = content.replace("@INSTALLDIR@", os.path.join("/", install_dir))
				f.write(content)
			else:
				f.write("// Place your preferences for " + package + " in this file.\n")
				f.write("// You can override here the preferences specified in\n")
				map(lambda x: f.write("// " + os.path.join("/", install_dir, x) + "\n"), preferences)
			f.close()
			link_source = os.path.join(prefdir, prefname)
			link_target = os.path.join(install_dir, "defaults", "preferences", "000system.js")
			command = ["dh_link", "-p" + package, link_source, link_target]
			if verbose:
				print " ".join(command)
			subprocess.call(command)

	# get symlinks list
	target_applications = get_target_applications(script_name, zfobj)
	for target_application in target_applications:
		destination = os.path.join("/usr", lib_share_dir, "mozilla/extensions",
				target_application, extension_id)
		links.add(destination)

	# backwards compatibility: create symlinks for applications that do not
	# scan /usr/{lib,share}/mozilla/extensions/target_application for extensions
	for app in incompatible_apps(target_applications):
		links.add(os.path.join("/usr/lib", app, "extensions", extension_id))

	# create symlinks
	for link in links:
		command = ["dh_link", "-p" + package, install_dir, link]
		print " ".join(command)
		subprocess.call(command)

def get_first_package():
	lines = open("debian/control").readlines()
	package_lines = filter(lambda x: x.find("Package:") >= 0, lines)
	packages = map(lambda x: x[x.find(":")+1:].strip(), package_lines)
	return packages[0]

if __name__ == "__main__":
	usage = "%s [options] <xpi-file>" % (os.path.basename(sys.argv[0]))
	epilog = "See %s(1) for more info." % (os.path.basename(sys.argv[0]))
	parser = optparse.OptionParser(usage=usage, epilog=epilog)

	parser.add_option("--disable-system-prefs",
			help="do not create a system preference file in /etc",
			dest="system_prefs", action="store_false", default=True)
	parser.add_option("-x", "--exclude", metavar="FILE",
			help="do not install specified FILE",
			dest="exclude", action="append", default=list())
	parser.add_option("-i", "--install-dir", metavar="DIRECTORY",
			help="install extension into the specified DIRECTORY",
			dest="install_dir")
	parser.add_option("-l", "--link", metavar="DIRECTORY",
			help="link from DIRECTORY to extension directory",
			dest="links", action="append", default=list())
	parser.add_option("-p", "--package",  metavar="PACKAGE",
			help="install the extension into specified PACKAGE",
			dest="package", default=get_first_package())
	parser.add_option("--preserve-permissions",
			help="do not adjust the file permissions",
			dest="correct_permissions", action="store_false", default=True)
	parser.add_option("-r", "--remove-license-files",
			help="do not install license files",
			dest="remove_licenses", action="store_true", default=False)
	parser.add_option("-v", "--verbose", help="print more information",
			dest="verbose", action="store_true", default=False)
	parser.add_option("--xpi-install-dir",
			help="Specify a location to save a copy of the xpi",
			dest="xpi_dest")
	parser.add_option("--install-xpi-only",
			help="Just install the xpi without adding symlinks or anything else",
			dest="install_xpi_only", action="store_true", default=False)

	(options, args) = parser.parse_args()

	script_name = os.path.basename(sys.argv[0])

	if len(args) == 0:
		print >> sys.stderr, "%s: Error: No xpi file specified." % (script_name)
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)
	elif len(args) > 1:
		print >> sys.stderr, script_name + ": Error: Multiple xpi files specified: " + ", ".join(args)
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)

	if options.verbose:
		print script_name + ": Install %s into package %s." % (args[0], options.package)

	if options.install_xpi_only and options.xpi_dest == None:
		print >> sys.stderr, script_name + ": Error: Need to specify --xpi-install-dir with --install-xpi-only"
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)

	if options.install_dir != None and options.install_xpi_only:
		print >> sys.stderr, script_name + ": Error: Can't specify --install-dir with --install-xpi-only"
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)

	if len(options.exclude) > 0 and options.install_xpi_only:
		print >> sys.stderr, script_name + ": Error: Can't specify --exclude with --install-xpi-only"
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)

	if len(options.links) > 0 and options.install_xpi_only:
		print >> sys.stderr, script_name + ": Error: Can't specify --link with --install-xpi-only"
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)

	if options.remove_licenses and options.install_xpi_only:
		print >> sys.stderr, script_name + ": Error: Can't specify --remove-license-files with --install-xpi-only"
		sys.exit(COMMAND_LINE_SYNTAX_ERROR)

	install_xpi(script_name, options.package, args[0], options.exclude,
			options.install_dir, set(options.links), options.correct_permissions,
			options.remove_licenses, options.system_prefs,
			options.xpi_dest, options.install_xpi_only, options.verbose)
