#!/usr/bin/env python
"""fpm2db - refresh a Frugalware database from a repository
Usage: python fpm2db [options]

Options:
	-D, --delete            The specified pkg can be deleted with this command.
	-F, --force             Update database entry regardless of status.
	-f ..., --fpm=...       The .fpm package. (optional when using -D)
	-m ..., --mainpkg=...   The name of the parent .fpm package. (required when using -D)
	-g ..., --maingrp=...   The group of the .fpm package. (optional)
	-h ..., --host=...      The host where the database is located. (optional)
	-u ..., --user=...      The user of the database. (optional)
	-p ..., --password=...  The password for the database. (optional)
	-d ..., --dbname=...    The name of the database. (optional)
"""

import getopt, re, os, hashlib, string, sys, time
import MySQLdb

class package:
	def __init__(self, fpm, parent=None, parentgrp=None):
		self.cursor=None
		self.id=None
		self.uploader_id=0
		self.parent=parent
		self.parentgrp=parentgrp
		self.parent_id=0
		self.updateable=True
		self.nobuild = False

		self.pkgname = re.sub("-[^-]+-[^-]+-[^-]+.fpm", "", fpm)
		if not self.parent:
			self.parent = self.pkgname
		self.pkgver = re.sub('.*-([^-]+-[^-]+)-[^-]+.fpm', r'\1', fpm)
		self.arch = os.getcwd().split('-')[-1]
		try:
			self.fwver = os.getcwd().split('/')[-2].split('-')[1]
		except IndexError:
			# this is some not frugalware-foo repo, just skip it
			sys.exit(0)
		if 'uploader' in os.environ.keys():
			self.uploader = os.environ['uploader']
		else:
			try:
				self.uploader = os.environ['SUDO_USER']
			except KeyError:
				self.uploader = os.environ['HOME'].split('/')[-1]

		try:
			os.stat(fpm)
		except OSError:
			# check if this is a nobuild fpm
			if self.getnobuild():
				self.nobuild = True
			else:
				self.updateable=False
				return

		self.files=[]
		if not self.nobuild:
			socket = os.popen("pacman-g2 -Ql -p %s" % fpm)
			while True:
				file = socket.readline().strip().replace("%s " % self.pkgname, "")
				if not file:
					break
				else:
					self.files.append(file)
			socket.close()

		self.abis=[]
		if not self.nobuild:
			socket = os.popen("../tools/dumpabi '%s'" % fpm)
			while True:
				abi = socket.readline().strip()
				if not abi:
					break
				else:
					self.abis.append(abi)
			socket.close()		

		self.conflicts=[]
		self.depends=[]
		self.groups=[]
		self.licenses=[]
		self.provides=[]
		self.desc=None
		self.url=None
		self.builddate=None
		self.usize=None
		if not self.nobuild:
			socket = os.popen("pacman-g2 -Qi -p %s" % fpm)
		else:
			command = """
source /usr/lib/frugalware/fwmakepkg
source %s

[ -z "$conflicts" ] && conflicts=('None')
[ -z "$depends" ] && depends=('None')
[ -z "$groups" ] && groups=('None')
[ -z "$license" ] && license=('None')
[ -z "$provides" ] && provides=('None')

cat << EOF
Conflicts With : ${conflicts[@]}
Depends On     : ${depends[@]}
Groups         : ${groups[@]}
License        : ${license[@]}
Provides       : ${provides[@]}
Description    : $pkgdesc
URL            : $url
Build Date     : $(date -u "+%%a %%b %%e %%H:%%M:%%S %%Y" --date="`stat -c "%%y" %s`") UTC
Size           : 0
EOF
			"""

			fb = "../source/%s/%s/FrugalBuild" % (self.parentgrp, self.parent)
			socket = os.popen(command % (fb, fb))
		buf = ''.join(socket.readlines()).replace('\n ', ' ')
		socket.close()
		for line in re.sub(' {2,}', ' ', buf).split('\n'):
			key = line.split(':')[0].strip()
			# a common list for depends/provides/etc, though it
			# will fail for build date and others which is normal
			try:
				values = line.strip().split(' : ')[1].split(' ')
			except IndexError:
				pass
			if key == "Conflicts With":
				self.conflicts = values
			elif key == "Depends On":
				self.depends = values
			elif key == "Groups":
				self.groups = values
			elif key == "License":
				try:
					self.licenses = values
				except IndexError:
					pass
			elif key == "Provides":
				self.provides = values
			elif key == "Description":
				# FIXME: we here hardcore the encoding of the FBs
				if not self.desc:
					self.desc = "".join(line.split(' : ')[1:]).decode('latin1')
			elif key == "URL":
				self.url = "".join(line.split(' : ')[1:])
			elif key == "Build Date":
				self.builddate = "".join(line.split(' : ')[1:])[:-4]
			elif key == "Size":
				self.usize = string.atoi(line.split(' : ')[1])
		if not self.parentgrp:
			self.parentgrp = self.groups[0]

		self.maintainer=None
		try:
			self.maintainer = self.getmaintainer()
		except IOError:
			# this happens when the group is changed but there was
			# no rebuild. we don't want to update the db in this
			# case
			self.uploader=None

		if not self.nobuild:
			ctx = hashlib.new("sha1")
			socket = open(fpm)
			while True:
				buf = socket.read(16384)
				if not buf:
					break
				ctx.update(buf)
			self.sha1sum = ctx.hexdigest()
			self.size=os.stat(fpm).st_size
		else:
			self.sha1sum = ""
			self.size = 0

	def getmaintainer(self):
		ret = ""
		socket = open("../source/%s/%s/FrugalBuild" % (self.parentgrp, self.parent))
		while True:
			line = socket.readline()
			if not line:
				break
			if line[:14] != "# Maintainer: ":
				continue
			# FIXME: we here hardcore the encoding of the FBs
			ret = line[14:].strip().decode('latin1')
			break
		socket.close()
		return ret

	def getnobuild(self):
		command = '    source /usr/lib/frugalware/fwmakepkg'
		command += ' ; source %s'
		command += ' ; [ -n "${nobuild}" ] && exit 1'
		command += ' ; echo ${options[@]} | grep -q nobuild && exit 1'
		command += ' ; exit 0'

		fb = "../source/%s/%s/FrugalBuild" % (self.parentgrp, self.parent)
		try:
			os.stat(fb)
		except OSError:
			return False
		return os.system(command % fb)

	def splitver(self, pkg):
		for i in ['>=', '<=', '=']:
			try:
				pos = pkg.index(i)
			except ValueError:
				continue
			return pkg[:pos], pkg[pos:]
		return pkg, None

	def insertlist(self, member):
		li = getattr(self, member)
		for i in li:
			if i == "None":
				return
			self.cursor.execute("""insert into %s (pkg_id, %s) values (%%s, %%s);""" % (member,
				member[:-1]), (self.id, i))

	def insertgroups(self):
		for i in self.groups:
			# get the group id or insert it if necessary
			self.cursor.execute("select id from groups where name = %s limit 1", [(i)])
			row = self.cursor.fetchone()
			if row:
				gid = row['id']
			else:
				self.cursor.execute("insert into groups (name) values (%s);", (i))
				self.cursor.execute("SELECT LAST_INSERT_ID()")
				row = self.cursor.fetchone()
				gid = row['LAST_INSERT_ID()']
			# now link the pkg with this group
			self.cursor.execute("""insert into ct_groups (pkg_id,
					group_id) values (%s, %s);""",
					(self.id, gid))

	def dellist(self, member):
		self.cursor.execute("delete from %s where pkg_id = %d" % (member, int(self.id)))

	def insertlistid(self, member):
		li = getattr(self, member)
		for i in li:
			if i == "None":
				return
			# slice the version if found
			if member == "depends":
				i, ver = self.splitver(i)
			else:
				ver=None
			# get the id of i
			self.cursor.execute("""select id from packages where pkgname = %s
				and arch = %s and fwver = %s limit 1""",
				(i, self.arch, self.fwver))
			row = self.cursor.fetchone()
			if row:
				id = row['id']
			else:
				# print "WARNING: can't find %s '%s'!" % (member, i)
				continue
			if not ver:
				self.cursor.execute("""insert into %s (pkg_id,
						%s_id) values (%%s, %%s);""" % (member, member[:-1]),
						(self.id, id))
			else:
				self.cursor.execute("""insert into %s (pkg_id,
				%s_id, version) values (%%s, %%s, %%s);""" % (member, member[:-1]),
				(self.id, id, ver))

	def insertlists(self):
		for i in ["depends", "conflicts", "provides"]:
			self.insertlistid(i)
		for i in ["files", "licenses", "abis"]:
			self.insertlist(i)
		self.insertgroups()

	def dellists(self):
		for i in ["depends", "conflicts", "provides", "files", "licenses", "ct_groups", "abis"]:
			self.dellist(i)

	def delete(self):
		# check if there is such a package & get the id
		self.cursor.execute("""select id from packages where pkgname =
				%s and arch = %s and fwver = %s""",
				(self.parent, self.arch, self.fwver))
		row = self.cursor.fetchone()
		if row:
			self.id = row['id']
		else:
			print 'WARNING: package %s not found, probably already deleted!' % (self.parent)
			return

		self.cursor.execute("delete from packages where id = %d" % self.id)
		self.dellists()

	def update(self, force):
		# do we really need this update?
		if not self.uploader:
			return
		if not self.updateable:
			print "Can't find the package!"
			return
		self.cursor.execute("""select id, pkgver from packages where pkgname = %s
			and arch = %s and fwver = %s limit 1""",
			(self.pkgname, self.arch, self.fwver))
		row = self.cursor.fetchone()
		if row and row['pkgver'] == self.pkgver and not force:
			return
		elif row:
			self.id = row['id']
		# uploader_id and parent_id
		self.cursor.execute("select id from uploaders where login = %s limit 1", [(self.uploader)])
		row = self.cursor.fetchone()
		if row:
			self.uploader_id = row['id']
		else:
			self.cursor.execute("insert into uploaders (login) values (%s);", (self.uploader,))
			self.cursor.execute("SELECT LAST_INSERT_ID()")
			row = self.cursor.fetchone()
			self.uploader_id = row['LAST_INSERT_ID()']

		if self.parent:
			self.cursor.execute("""select id from packages where pkgname = %s
				and arch = %s and fwver = %s limit 1""",
				(self.parent, self.arch, self.fwver))
			row = self.cursor.fetchone()
			if row:
				self.parent_id = row['id']

		# time to insert to the packages table
		if not self.id:
			self.cursor.execute('''
			insert into packages
			(pkgname, pkgver, `desc`, url, sha1sum, arch,
			size, usize, parent_id, maintainer, uploader_id, fwver, builddate)
			VALUES (%s, %s, %s, %s, %s, %s,
			%s, %s, %s, %s, %s,
			%s, %s);
			''', (self.pkgname, self.pkgver, self.desc, self.url, self.sha1sum, self.arch,
				self.size, self.usize, self.parent_id, self.maintainer, self.uploader_id,
				self.fwver, time.strftime("%Y-%m-%d %H:%M:%S", time.strptime(self.builddate))))
			self.cursor.execute("SELECT LAST_INSERT_ID()")
			row = self.cursor.fetchone()
			self.id = row['LAST_INSERT_ID()']
			self.insertlists()
		else:
			self.cursor.execute(''' update packages set pkgname = %s,
			pkgver = %s, `desc` = %s, url = %s, sha1sum =
			%s, arch = %s, size = %s, usize = %s, parent_id
			= %s, maintainer = %s, uploader_id = %s, fwver =
			%s, builddate = %s where id = %s ''',
			(self.pkgname, self.pkgver, self.desc, self.url,
			self.sha1sum, self.arch, self.size, self.usize,
			self.parent_id, self.maintainer, self.uploader_id,
			self.fwver, time.strftime("%Y-%m-%d %H:%M:%S",
				time.strptime(self.builddate)), self.id))
			self.dellists()
			self.insertlists()

def usage():
	print __doc__

def main(argv):
	# defaults
	update = True
	force = False
	fpm = ""
	mainpkg = None
	maingrp = None
	host = ""
	user = ""
	password = ""
	dbname = ""

	# option parsing
	try:
		opts, args = getopt.getopt(argv, "DFf:h:u:p:d:m:g:", ["delete",
			"force", "fpm=", "host=", "user=", "password=", "dbname=",
			"mainpkg=", "maingrp="])
	except getopt.GetoptError:
		usage()
		sys.exit(1)
	for opt, arg in opts:
		if opt in ("-D", "--delete"):
			update = False
		elif opt in ("-F", "--force"):
			force = True
		elif opt in ("-f", "--file"):
			fpm = arg
		elif opt in ("-m", "--mainpkg"):
			mainpkg = arg
		elif opt in ("-g", "--maingrp"):
			maingrp = arg
		elif opt in ("-h", "--host"):
			host = arg
		elif opt in ("-u", "--user"):
			user = arg
		elif opt in ("-p", "--password"):
			password = arg
		elif opt in ("-d", "--dbname"):
			dbname = arg

	# check for missing options
	if fpm != "" or (mainpkg != "" and not update):
		pass
	else:
		raise "missing -f package!"
	if host == "":
		host = "localhost"
	if user == "":
		user = "fpm2db"
	if password == "":
		password = "C6fO?o3qy"
	if dbname == "":
		dbname = "frugalware2"

	# autodetection
	os.unsetenv("LANG")
	os.unsetenv("LC_ALL")
	pkg = package(fpm, mainpkg, maingrp)

	conn = MySQLdb.connect(host=host, user=user, passwd=password,
			db=dbname)
	pkg.cursor = conn.cursor(MySQLdb.cursors.DictCursor)
	if update:
		pkg.update(force)
	else:
		pkg.delete()
	conn.close()

if __name__ == "__main__":
	main(sys.argv[1:])