#!/usr/bin/python
#
# Copyright (C) 2005-2007 Canonical (author: Michael Vogt)
# Copyright (C) 2007-2008 Julian Andres Klode <jak@jak-linux.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import re
import urllib2
import logging
import fnmatch
import glob
from files import FileManager

# these globals here suck
base = os.path.dirname(sys.argv[0]) or os.getcwd()
blacklist = base + "/data/blacklist.cfg"
blacklist_desktop = base +  "/data/blacklist_desktop.cfg"
renamecfg = base + "/data/rename.cfg"
annotatecfg = base + "/data/annotate.cfg"
codecs_foradditional = { }
iconre	= re.compile('Icon=(.*)')


# packages we have already seen
pkgs_seen = set()

# pkgs that shouldn't be written (e.g. gnome-about)
pkgs_blacklisted = set()

# desktop files that are not wanted
desktop_blacklist = set()

# a mapping to transform certain detections to a new name
# (e.g.abiword-plugins -> abiword-gnome)
pkgs_transform = {}

# a mapping from desktop file to a list of free annotations that will
# be added to the desktop file (useful for e.g. X-AppInstall-Replaces
desktop_annotate = {}

# a dictionary with "$arch" -> set() of packages mapping
# this makes it easy to analyse what packages are only
# available in certain arches
pkgs_per_arch = {}

# pkg to desktop file mapping (this assumes that for a packagename
# is uniq across architecutures)
pkgs_to_desktop = {}

# all supported arches
for i in [x.strip().split(":") for x in urllib2.urlopen("http://cdimage.debian.org/debian/dists/testing/Release").readlines()]:
	if i[0] == "Architectures":
		SUPPORTED_ARCHES = ' '.join(i[1:]).split()
SUPPORTED_ARCHES.remove('amd64')
SUPPORTED_ARCHES = ["amd64"] + SUPPORTED_ARCHES

def search_icon(iconName, outputdir, pkgname=""):
	'''search_icon(str, str, str) -> str() or None'''
	if not iconName:
		logging.warning("search_icon() called with no icon name")
	#if not iconName:
		return

	# a iconName can be a single name or a full path
	# if it is a single name, look into a icon-theme path (usr/share/icons/hicolor) and then into usr/share/pixmaps
	# if it is a full path just look for this

	# this is the "full-path" case
	# FIXME: there are (some) icons that are not full pathes like "/usr/.../"
	#				but "zapping/zapping.png"
	# FIXED: Only return if the extraction was successfull
	if '/' in iconName:
		newIconName = iconName.replace("/", "_")
	else:
		newIconName = None
	if iconName.startswith("/"):
		try:
			FILES.extract_file(iconName, os.path.join(outputdir,"icons",newIconName), pkgname)
		except KeyError:
			pass
		else:
			return newIconName

	# extensions (ordered by importance)
	pixmaps_ext = ["", ".png",".svg", ".xpm"]
	help = None
	baseIcon = os.path.basename(iconName)
	pats = [('*/' + baseIcon + ext) for ext in pixmaps_ext]
	name = None
	packages = FILES.filter(pats[0], FILES.icons, *pats[1:])
	pkgname_ = pkgname
	# Get the package from
	# 1. pkg
	# 2. pkg-common
	# 3. pkg-data
	# 4. gnome-icon-theme and kde(base|libs)-data
	# 5. hicolor-icon-theme
	# 6. The best file we can get.
	if pkgname.endswith('-data'):
		pkgname = pkgname[:-5]
	elif pkgname.endswith('-common'):
		pkgname = pkgname[:-7]
	# Ignore icons in icon themes
	if 'gnome-icon-theme' in packages or 'hicolor-icon-theme' in packages:
		print 'T:', '   %-20.20s %-20.20s %s' % (pkgname_, 'icon-theme', iconName)
		return
	elif pkgname in packages:
		name = packages[pkgname][0]
	elif pkgname + '-common' in packages:
		name = packages[pkgname + '-common'][0]
		pkgname = pkgname + '-common'
	elif pkgname + '-data' in packages:
		name = packages[pkgname + '-data'][0]
		pkgname = pkgname + '-data'
	if name:
		try:
			FILES.extract_file(name, os.path.join(outputdir,"icons", os.path.basename(name)), pkgname_)
		except KeyError:
			pass
		else:
			return

	for pat in pats:
		for name in fnmatch.filter(FILES.icons, pat):
			try:
				FILES.extract_file(name, os.path.join(outputdir,"icons", os.path.basename(name)), pkgname_)
			except KeyError:
				pass
			else:
				return

	print 'M:', '   %-20.20s %-20.20s %s' % (pkgname_, pkgname, iconName)
	logging.warning("%s misses icon %s" % (pkgname_, iconName))
	return

def writeMetaData(outfile, pkgname, section, codecs):
	pkgname = pkgs_transform.get(pkgname, pkgname)
	if pkgname.endswith('-common') and pkgname[:-7] in FILES.packages:
		pkgname = pkgname[:-7]
	if pkgname.endswith('-data') and pkgname[:-5] in FILES.packages:
		pkgname = pkgname[:-5]
	outfile.write("X-AppInstall-Package=%s\n" % pkgname)
	outfile.write("X-AppInstall-Section=%s\n" % section)
	if codecs:
		outfile.write("X-AppInstall-Codecs=%s\n" % ';'.join(codecs))
	outfile.write("\n")
	return True

def getFiles(pkg, section, outputdir):
	""" actually extract the files from a data.tar.gz of a package """
	pkgname = pkg
	try:
		os.makedirs(os.path.join(outputdir, "icons"))
	except OSError:
		pass


	desktopPaths = FILES.filter("*usr/share/applications/*.desktop").get(pkg, [])
	codecSupport = FILES.filter('*usr/share/gstreamer-*/plugin-info/*.supported').get(pkg, [])
	#if not (desktopPaths or codecSupport or	iconPaths):
	#	logging.warning("Skipping package: %s" % pkgname)


	# and look for any codec support list
	codecs = set()

	for filename in codecSupport:
		try:
			codecsfile = FILES.get_file(filename, pkg)
		except KeyError:
			continue
		logging.debug("reading codec list file %s" % filename)
		for line in codecsfile:
			line = line.strip().rstrip(';')
			if ';' in line:
				logging.warning("Codec string '%s' from '%s' in '%s' contains ';'" %
												(line, filename, FILES.packages[pkg]))
				#continue
			codecs.add(line)



	for path in desktopPaths:
		try:
			desktopfile = FILES.get_file(path, pkg)
		except KeyError:
			continue
		# new location for the desktopfile
		desktop = os.path.join(outputdir, os.path.basename(path))
		# extract the icon
		iconName = None
		newIconName = None

		if os.path.basename(path) in desktop_blacklist:
			logging.debug("Skipping desktop file '%s'" % (desktopfile.name))
			continue

		if os.path.exists(desktop):
			logging.warning("Not overwriting '%s' from %s", desktop, pkg)
			continue

		if not "non-free" in section:
			for iconName in set(iconre.findall(desktopfile.read())):
				logging.debug("Package '%s' needs icon '%s'" % (pkgname, iconName))
				newIconName = search_icon(iconName, outputdir, pkgname)

		# now check for supicious pkgnames (FIXME: make this not hardcoded)
		if "-common" in pkgname or "-data" in pkgname:
			#logging.warning("'%s' looks wrong, trying to correct" % pkgname)
			parentpkg = pkg.rsplit("-", 1)[0]
			if parentpkg in FILES.packages:
				logging.warning("'%s' looked wrong, corrected	to '%s'" % (pkg, parentpkg))
				pkg = parentpkg

		# now write out the file
		arch = FILES.arch
		logging.debug("Writing desktop file '%s' for arch '%s'" % (desktopfile.name, arch))
		outfile = open(desktop, "w")
		desktopfile.seek(0)
		written=False
		desktop_section = ""
		for line in desktopfile.readlines():
			if line.startswith('TryExec=') or line.startswith('NoDisplay='):
				continue
			if newIconName is not None and line.startswith("Icon="):
				# Using the file extension is not themable
				if os.path.splitext(newIconName)[1] != "":
					line = "Icon=%s\n" % os.path.splitext(newIconName)[0]
				else:
					line = "Icon=%s\n" % newIconName
			if not line.endswith("\n"):
				line += "\n"
			# if a new section starts, write the metadata now
			if desktop_section == "[Desktop Entry]" and not written:
				writeMetaData(outfile, pkgname, section, codecs)
				written=True
			outfile.write(line)
			if line.startswith("["):
				desktop_section = line.strip()
		if not written:
			writeMetaData(outfile, pkgname, section, codecs)

		desktopfile_name = os.path.basename(desktopfile.name)
		if desktop_annotate.has_key(desktopfile_name):
			logging.info("adding annontate '%s' for '%s'" % (desktop_annotate[desktopfile_name], desktopfile_name))
			for a in desktop_annotate[desktopfile_name]:
				outfile.write("%s\n" % a.strip())
		outfile.close()
		try:
			pkgs_to_desktop[pkgname].append(os.path.basename(outfile.name))
		except KeyError:
			pkgs_to_desktop[pkgname] = [os.path.basename(outfile.name)]

		# close the desktop file
		desktopfile.close()

	if codecs:
		try: codecs_foradditional[pkgname] &= codecs
		except KeyError: codecs_foradditional[pkgname] = codecs

def processDeb(pkg, section, outputdir=os.path.join(os.getcwd(), "menu-data")):
	""" extract the desktop file and the icons from a deb """
	logging.debug("processing: %s" % FILES.packages[pkg])
	getFiles(pkg, section, outputdir)

def inspectDeb(pkg):
	""" check if the deb is interessting for us (our arch etc) """
	# certain pkgs are blacklisted
	if pkg in pkgs_blacklisted:
		logging.warning("skipping blacklisted pkg: '%s'" % pkg)
		return

	if pkg in pkgs_seen:
		return

	if FILES.packages[pkgs_transform.get(pkg, pkg)].endswith('_all.deb'):
		pkgs_per_arch['all'].add(pkg)
	component = 'main'
	logging.debug("Found interessting deb '%s' in section '%s'" % (FILES.packages[pkg], component))
	# found somethat worth looking at
	processDeb(pkg, component)
	pkgs_seen.add(pkg)





def main(local, mirror='http://ftp2.de.debian.org/debian/'):
	global FILES
	if mirror is 'None':
		mirror = 'http://ftp2.de.debian.org/debian/'
	logging.basicConfig(level=logging.DEBUG, filename="menu-data-extract.log",
											format='%(asctime)s %(levelname)s %(message)s',
											filemode='w')

	FILES = FileManager(local, mirror, components=['main'], archs=SUPPORTED_ARCHES)


	# run this once for each arch, it will skip packages already seen
	pkgs_per_arch["all"] = set()

	if os.path.exists(blacklist):
		logging.info("using blacklist: '%s'" % blacklist)
		for line in open(blacklist).readlines():
			line = line.strip()
			if line != "" and not line.startswith("#"):
				logging.debug("blacklisting: '%s'" % line.strip())
				pkgs_blacklisted.add(line.strip())

	if os.path.exists(blacklist_desktop):
		logging.info("using blacklist desktop: '%s'" % blacklist_desktop)
		for line in open(blacklist_desktop).readlines():
			line = line.strip()
			if line != "" and not line.startswith("#"):
				logging.debug("blacklisting (desktop file): '%s'" % line.strip())
				desktop_blacklist.add(line.strip())

	if os.path.exists(renamecfg):
		logging.info("using rename: '%s'" % renamecfg)
		for line in open(renamecfg).readlines():
			line = line.strip()
			if line != "" and not line.startswith("#"):
				(oldname,newname) = line.strip().split()
				logging.debug("renaming: %s -> %s" % (oldname,newname))
				pkgs_transform[oldname] = newname

	if os.path.exists(annotatecfg):
		logging.info("using annotates: '%s'" % annotatecfg)
		for line in open(annotatecfg):
			line = line.strip()
			if line != "" and not line.startswith("#"):
				(desktopfile,annotations_str) = line.split(":")
				annotations = annotations_str.split(",")
				logging.debug("annotations: '%s': %s" % (desktopfile,annotations))
				desktop_annotate[desktopfile] = annotations

	for arch in FILES.archs:
		print 'I: Processing',  arch
		files = FILES.filter("*usr/share/applications/*.desktop").keys()
		files += FILES.filter('*usr/share/gstreamer-*/plugin-info/*.supported').keys()
		files =  sorted(files)
		if len(sys.argv) == 4:
			files = files[files.index(sys.argv[3]):]
		for pkg in files:
			inspectDeb(pkg)


	# now add the architecture information
	arch_specific = set()
	for pkg in pkgs_seen:
		arches = []
		if not pkg in pkgs_per_arch["all"]:
			for arch in FILES.archs:
				if not pkg in FILES.packages:
					arch_specific.add(pkg)
				else:
					arches.append(arch)

		if pkg in arch_specific:
			str_="X-AppInstall-Architectures=%s\n" % ",".join(arches)
			if not pkgs_to_desktop.has_key(pkg):
				logging.debug("Skipping arch-specific pkg '%s' with no desktop file" % pkg)
				continue
			for f in pkgs_to_desktop[pkg]:
				dfile = "menu-data/" + f
				if os.path.exists(dfile):
					logging.debug("adding arch-specifc information to %s (%s)" % (dfile,arches))
					open(dfile,"a").write(str_)
				else:
					logging.error("can't find desktopfile for: %s (%s)" % (pkg,dfile))

	# edit the codec lists into the entries in menu-data-additional
	logging.debug("adding codecs: '%s'", codecs_foradditional)
	seen = set()
	for filename in glob.glob('menu-data-additional/*.desktop'):
		lines = []
		file_ = open(filename)
		pkgname = None
		for line in file_:
			line = line.strip()
			if re.match("^X-AppInstall-Codecs\s*=", line):
				continue
			lines.append(line)
			m = re.match("^X-AppInstall-Package\s*=\s*(\S+)\s*$", line)
			if m:
				pkgname = m.group(1)

		file_.close()
		if not pkgname:
			logging.warning("file '%s' doesn't specify an X-AppInstall-Package" %
											filename)
			continue
		try:
			codecs = codecs_foradditional[pkgname]
		except KeyError:
			continue
		logging.debug("adding codec info to '%s' " % pkgname)
		lines.append("X-AppInstall-Codecs=%s" % ';'.join(codecs))
		seen.add(pkgname)
		new_file = open(filename+'.tmp', 'w')
		for line in lines:
			print >>new_file, line
		new_file.close()
		os.rename(filename+'.tmp', filename)

	missing = set(codecs_foradditional.keys())-seen
	if len(missing) > 0:
		logging.warning("Packages with codecs but no data in menu-data-additonal: %s" % missing)

if __name__ == "__main__":
	if len(sys.argv) < 2:
		sys.stdout = sys.stderr
		print 'Usage: %s local [mirror] [start-pkg]'
		print 'local is the location where the temporary mirror is created'
		print 'mirror is the location of the real mirror (http:// , ftp:// or file://)'
		print
	elif len(sys.argv) == 2:
		main(sys.argv[1])
	elif len(sys.argv) == 3 or len(sys.argv) == 4:
		main(sys.argv[1], sys.argv[2])
