#!/usr/bin/python
#
#  Copyright (c) 1998-2001 Sean Reifschneider, tummy.com, ltd.  
#	All Rights Reserved.
#
#  Generate a report in the standard format from the database created by
#	raddetail.  
# 	This format includes a file for every user detailing their usage, and 
#	an index listing total usage for each user.  
#	This uses the "generator" and "gen*.py" modules to create multiple output 
#	formats.
#
#  http://www.tummy.com/radiusContext/
#  ftp://ftp.tummy.com/pub/tummy/radiusContext/

revision = "$Revision: 1.88 $"
rcsid = "$Id: stdreport,v 1.88 2002/12/11 05:08:16 jafo Exp $"

import sys, os
import string
import shelve
import generator
from radiussupp import *
import getopt
try:
	import gdbm
	usedbm = gdbm
except ImportError:
	import anydbm
	usedbm = anydbm

#  list of default generators and directories
genList = [ 'text', 'html', 'raw', 'csv' ]
genDefault = 1				#  1 if we are using defaults
reportAllDir = None		#  directories for specific generators
reportDir = {}				#  directories for *ALL* generators
reportUsers = None		#  list of users to restrict report to
splitNum = 1				#  split into second-level report dirs

databaseName = "SessionData"

shortArgs = "A:d:D:g:H:T:R:s:u:hv?"
longArgs = [
		"database=",
		"textreportdir=",
		"htmlreportdir=",
		"htmlascendreportdir=",
		"generate=",
		"rawreportdir=",
		"csvreportdir=",
		"reportdir=",
		"split-num=",
		"secsconv-limit=",
		"user=",
		"help",
		"version",
		]

#  process options, retaining program name and non-option arguments
optlist, sys.argv[1:] = getopt.getopt(sys.argv[1:], shortArgs, longArgs)
for arg in optlist:
	if arg[0] == "-d" or arg[0] == "--database":
		databaseName = arg[1]
	if arg[0] == "-g" or arg[0] == "--generator":
		if genDefault:
			genList = []
			genDefault = 0
		genList.append(arg[1])
	if arg[0] == "--secsconv-limit":
		convDict = { 'hour' : 3600, 'hours' : 3600,
				'day' : 86400, 'days' : 86400, 'minute' : 60, 'minutes' : 60,
				'second' : 1, 'seconds' : 1 }

		#  convert the string to a number
		count = convDict.get(arg[1])
		try:
			if count == None: count = int(arg[1])
		except ValueError:
			sys.stderr.write('Invalid --secsconv-limit value "%s"\n' % arg[1])
			sys.exit(1)

		limitSecsToStr(count)
	if arg[0] == "-H" or arg[0] == "--htmlreportdir":
		reportDir['HTML'] = arg[1]
	if arg[0] == "-A" or arg[0] == "--htmlascendreportdir":
		reportDir['HTML-Ascend'] = arg[1]
	if arg[0] == "-T" or arg[0] == "--textreportdir":
		reportDir['text'] = arg[1]
	if arg[0] == "-C" or arg[0] == "--csvreportdir":
		reportDir['csv'] = arg[1]
	if arg[0] == "-R" or arg[0] == "--rawreportdir":
		reportDir['raw'] = arg[1]
	if arg[0] == "-D" or arg[0] == "--reportdir":
		reportAllDir = arg[1]
	if arg[0] == "-h" or arg[0] == "-?" or arg[0] == "--help":
		print "usage: stdreport [<arguments>]"
		print
		print "\t[ -d | --database <file> ]       Specify database file name."
		print "\t[ -D | --reportdir <dir> ]       Specify report directory name."
		print "\t[ -g | --generate <type> ]       Select reports to generate."
		print "\t                                 (May be used several times)."
		print "\t[ -C | --csvreportdir <dir> ]    Specify CSV directory name."
		print "\t[ -H | --htmlreportdir <dir> ]   Specify HTML directory name."
		print "\t[ -A | --htmlascendreportdir <dir> ]"
		print "\t                                 Specify HTML-Ascend directory."
		print "\t[ -T | --textreportdir <dir> ]   Specify text directory name."
		print "\t[ -R | --rawreportdir <dir> ]    Specify RAW directory name."
		print "\t[ -s | --split-num <n> ]         Second-level report dirs are" \
				" the"
		print "\t                                 first 'n' chars of login.  " \
				"(use 0"
		print "\t                                 to not create subdirectories)"
		print "\t[ --secsconv-limit <s> ]         Limit the time conversion to " \
				"at most"
		print "\t                                 the specified limit.  This " \
				"can be a"
		print "\t                                 number of seconds, or a name " \
				"such as"
		print "\t                                 as 'hours', 'days', or " \
				"'minutes'."
		print "\t                                 'days', or 'minutes'."
		print "\t[ -u | --user  ] <user name>     Limit report to <user> (may be"
		print "\t                                 specified multiple times)"
		print "\t[ -v | --version  ]              Display program version."
		print "\t[ -h | -? | --help  ]            Display this usage message."
		sys.exit(0)
	if arg[0] == "-s" or arg[0] == "--split-num":
		splitNum = int(arg[1])
	if arg[0] == "-u" or arg[0] == "--user":
		if reportUsers == None: reportUsers = {}
		reportUsers[string.lower(arg[1])] = 1
	if arg[0] == "-v" or arg[0] == "--version":
		print "Version", string.split(revision)[1]
		sys.exit(0)

#  import the user-specified generators and set options
for gen in genList:
	try:
		exec('import gen%s' % gen)
	except Exception, e:
		if e[0][:19] == 'No module named gen':
			print 'ERROR: Unable to locate generator for type "%s".  Aborting...' \
					% gen
			sys.exit(1)
		raise
if reportAllDir:
	if not os.path.exists(reportAllDir):
		sys.stderr.write('%s: Error, directory "%s" does not exist.  Aborting...'
				% ( sys.argv[0], reportAllDir ))
		sys.exit(1)
	generator.reportDir(reportAllDir)
generator.setSplit(splitNum)
for gen in reportDir.keys():
	generator.reportDir(reportDir[gen], gen)

out = None
try: sessionData = shelve.Shelf(usedbm.open(databaseName, 'r'))
except usedbm.error, e:
	s = str(e)
	try: s = e[1]
	except: pass
	sys.stderr.write('Error to open file "%s": %s\n' % ( databaseName, s ))
	sys.exit(1)

keyList = sessionData.keys()
keyList.sort()


#################################################################
#  close user session with generator and add information to index

def userStop(userData, indexData):
	userData["bwIn"] = 0
	userData["bwOut"] = 0
	try:
		userData["bwIn"] = bytesToStr(userData["bytesIn"]
				/ userData["timeOn"])
		userData["bwOut"] = bytesToStr(userData["bytesOut"]
				/ userData["timeOn"])
	except:
		pass

	generator.Stop(userData)
	generator.IndexItem(indexData, userData)

	#  build index data
	indexData["numUsers"] = indexData.get("numUsers", 0) + 1
	indexData["sessionCount"] = indexData.get("sessionCount", 0)	\
			+ userData["sessionCount"]
	indexData["bytesIn"] = indexData.get("bytesIn", 0L) \
			+ userData["bytesIn"]
	indexData["bytesOut"] = indexData.get("bytesOut", 0L)	\
			+ userData["bytesOut"]
	indexData["timeOn"] = indexData.get("timeOn", 0L) + userData["timeOn"]
	indexData["numDays"] = indexData.get("numDays", 0) \
			+ userData["numDays"]

oldUser = None
indexData = {}
userData = {}
generator.IndexStart()

if len(keyList) < 1:
	print 'No records to process.  Exiting...'
	sys.exit(0)

#  convert '/' in user names to '_'
userTrans = string.maketrans('/', '_')

lastKey = [ None, 0, None ]
for key in keyList:
	#  skip duplicate entries
	foo = string.split(key, ":")
	foo[1] = int(foo[1])
	if abs(lastKey[1] - foo[1]) < 10 and foo[0] == lastKey[0] and \
			foo[2] == lastKey[2]:
		lastKey = foo
		continue
	lastKey = foo

	user = foo[0]
	if len(user) < 1: user = '00-EmptyUserName'
	if reportUsers and not reportUsers.has_key(string.lower(user)):
		continue
	data = sessionData[key]

	if oldUser != user:
		if oldUser != None:
			userStop(userData, indexData)

		oldUser = user

		#  reinitialize userData
		userData.clear()
		userData["sessionCount"] = 0
		userData["bytesIn"] = 0L
		userData["bytesOut"] = 0L
		userData["numDays"] = 0
		userData["timeOn"] = 0
		if user == 'index':
			userData["userName"] = 'index(user)'
		else:
			userData["userName"] = string.translate(user, userTrans)

		generator.Start(userData)
		oldDate = ""

	userData["sessionCount"] = userData["sessionCount"] + 1
	thisDate = string.split(data["Session-Start-Date"])[0]
	data["reportDate"] = data["Session-Start-Date"]
	if oldDate != thisDate:
		oldDate = thisDate
		userData["numDays"] = userData["numDays"] + 1
	else:
		data["reportDate"] = \
				"           %s" % ( string.split(data["Session-Start-Date"])[1] )

	data["currentIn"] = data.get("Acct-Input-Octets", 0)
	data["currentOut"] = data.get("Acct-Output-Octets", 0)
	data["currentTime"] = data.get("Acct-Session-Time", 0)
	userData["bytesIn"] = userData["bytesIn"] + data["currentIn"]
	userData["bytesOut"] = userData["bytesOut"] + data["currentOut"]
	userData["timeOn"] = userData["timeOn"] + data["currentTime"]

	data["bwIn"] = 0
	data["bwOut"] = 0
	try:
		data["bwIn"] = bytesToStr(data["currentIn"] / data["currentTime"])
		data["bwOut"] = bytesToStr(data["currentOut"] / data["currentTime"])
	except:
		pass

	generator.Item(userData, data)

if oldUser != None: userStop(userData, indexData)

#  Build some convenience aliases for the index footer data
indexData["timePerUser"] = tryDivide(indexData["timeOn"], indexData["numUsers"])
indexData["timePerSes"] = tryDivide(indexData["timeOn"],
		indexData["sessionCount"])
indexData["sesPerUser"] = tryDivide(indexData["sessionCount"],
		indexData["numUsers"])
indexData["dataInPerUser"] = tryDivide(indexData["bytesIn"],
		indexData["numUsers"])
indexData["dataOutPerUser"] = tryDivide(indexData["bytesOut"],
		indexData["numUsers"])
indexData["dataInPerSes"] = tryDivide(indexData["bytesIn"],
		indexData["sessionCount"])
indexData["dataOutPerSes"] = tryDivide(indexData["bytesOut"],
		indexData["sessionCount"])
indexData["bwIn"] = tryDivide(indexData["bytesIn"], indexData["timeOn"])
indexData["bwOut"] = tryDivide(indexData["bytesOut"], indexData["timeOn"])
indexData["bwInUser"] = tryDivide(indexData["bytesIn"],
		indexData["timePerUser"])
indexData["bwOutUser"] = tryDivide(indexData["bytesOut"],
		indexData["timePerUser"])

generator.IndexStop(indexData)
