"""

Miscellaneous functions to manage unix processes

"""

from commands import getoutput
from string import split, atoi, ljust, strip, find, upper
from tools import cleanlist, TRUE, FALSE
from types import ListType, StringType, DictType

import copy
import sys
import log4py

# Define some necessary constants

FILTERON = 1
FILTEROFF = 0

FILTERMODEEXACT = 1
FILTERMODESUB = 2

# Operating system depended ps parameters

OSTYPEBSD = "BSD"
OSTYPESYSV = "System V"
OSTYPEUNKNOWN = "Unknown"

# The platform can be determined by executing 
#     python -c "import sys;print sys.platform"

osFamily = {}
osFamily[OSTYPEBSD] = ["freebsd4", "bsdos4"]
osFamily[OSTYPESYSV] = ["linux-i386", "linux2"]

osPsParam = {}
osPsParam[OSTYPEBSD] = "-j -p %s"
osPsParam[OSTYPESYSV] = "-f -p %s"
osPsParam[OSTYPEUNKNOWN] = "-f -p %s"

# Definition of the ProcessTable class

class ProcessTable:

    # ProcessTable default settings
    def __init__(self):

        self.processes = {}                                 # List of Dictionaries containing processes
        self.parameter = ""                                 # Parameters passed to read
        self.processmap = {}                                # Dictionary containing the various columns
        self.columnlength = {}                              # Dictionary containing the maximum length of each column
        self.filteredpids = []                              # List of filtered pids
        self.mode = FILTEROFF                               # Current mode
        self.ppidparam = ""
        self.log4py = log4py.Logger().get_instance(self)
        self.log4py.set_loglevel(log4py.LOGLEVEL_DEBUG)

    # Reads the current processes with optional parameters
    def read(self, parameter = "ax"):

        self.processes = {}
        self.parameter = parameter
        self.reread()

    # Re-read processes list (no parameters necessary)
    def reread(self):

        command = "ps %s" % self.parameter
        output = getoutput(command)

        splitted = split(output, "\n")
        header = cleanlist(split(splitted[0], " "))
        processes = splitted[1:]
    
        processentry = {}

        self.processmap = {}
        self.columnlength = {}

        maxheaderindex = 0
        for i in range(len(header)):
            processentry[header[i]] = None
            self.processmap[i] = header[i]
            self.columnlength[i] = len(header[i])

        maxheaderindex = len(header) - 1

        for i in range(len(processes)):
            pid = -1
            splitted = cleanlist(split(processes[i], " "))
            process = copy.copy(processentry)

            for j in range(len(splitted)):

                if (j <= maxheaderindex):
                    if self.processmap[j] == "PID":
                        pid = atoi(splitted[j])
                    process[self.processmap[j]] = splitted[j]
                    if (len(splitted[j]) > self.columnlength[j]):
                        self.columnlength[j] = len(splitted[j])
                else:
                    process[self.processmap[maxheaderindex]] = "%s %s" % (process[self.processmap[maxheaderindex]], splitted[j])
                    
                if (j < maxheaderindex) and (len(splitted[j]) > self.columnlength[j]):
                    self.columnlength[j] = len(splitted[j])

            self.processes[pid] = process

    # Filter for certain conditions in the ProcessTable
    # filters is a dictionary containing parameter:[valuelist]
    def filter(self, filters = None, filtermode = FILTERMODEEXACT):
        self.mode = FILTERON
        self.filteredpids = []

        for i in range(len(self.processes.keys())):

            processid = self.processes.keys()[i]
            process = self.processes[processid]

            processmatches = TRUE
            for j in range(len(filters.keys())):

                filtername = filters.keys()[j]
                filterlist = filters[filtername]
                if type(filterlist) != ListType:
                    filterlist = [filterlist]
                filtername = upper(filtername)

                # special case for different ps implementations
                if (filtername == "CMD") and (process.has_key("COMMAND")):
                    filtername = "COMMAND"

                parameter = process[upper(filtername)]

                for k in range(len(filterlist)):
                    singlefilter = filterlist[k]
                    if (filtermode == FILTERMODEEXACT) or (type(singlefilter) != StringType):
                        if (str(singlefilter) <> str(parameter)):
                            processmatches = FALSE
                    else:
                        if (find(parameter, singlefilter) < 0):
                            processmatches = FALSE

            if (processmatches == TRUE):
                self.filteredpids.append(atoi(process["PID"]))
    
    # Remove applied filter
    def removefilter(self):

        self.filteredpids = []
        self.mode = FILTEROFF

    # Checks, wether a process specified by the given condition(s) exists
    def exists(self, filters = None):

        tempfilter = self.filteredpids
        self.filter(filters)
        exists = (len(self.filteredpids) > 0)
        self.filteredpids = tempfilter

        return exists

    # Returns a dictionary with the specified process
    def process(self, pid):

        if self.processes.has_key(pid):
            return self.processes[pid]
        else:
            return None

    # Print the process table (optional just a list of given pids)
    def printtable(self, list = None):

        if (list == None):
            list = self.processes.keys()
        elif (type(list) == DictType):
            list = list.keys()
        list.sort()

        if len(list) == 0:
            return

        header = ""
        for i in range(len(self.processmap)):
            if i < (len(self.processmap) - 1):
                header = "%s%s " % (header, ljust(self.processmap[i], self.columnlength[i]))
            else:
                header = "%s%s " % (header, self.processmap[i])

        sys.stdout.write("%s\n" % strip(header))

        keys = self.processmap.keys()
        keys.sort()

        for i in range(len(list)):

            if self.processes.has_key(list[i]):
                process = self.processes[list[i]]

                line = ""
                for j in range(len(keys)):

                    key = keys[j]
                    if j < (len(self.processmap) - 1):
                        line = "%s%s " % (line, ljust(process[self.processmap[key]], self.columnlength[j]))
                    else:
                        line = "%s%s " % (line, process[self.processmap[key]])
                sys.stdout.write("%s\n" % strip(line))
            else:
                sys.stdout.write("No process with pid #%s in the internal process table.\n" % list[i])

    # Returns either all pids or all filtered pids
    def pids(self):

        if (self.mode == FILTERON):
            list = self.filteredpids
        else:
            list = self.processes.keys()
        list.sort()
        return list

    # Returns a dictionary of parent pids of given pids, filtered pids or all pids (depends on filter mode)
    def ppids(self, pids = None):
        if (pids == None):
            if (self.mode == FILTERON):
                pids = self.filteredpids
            else:
                pids = self.processes.keys()
        pids.sort()
        ppids = {}
        for i in range(len(pids)):
            pptab = ProcessTable()

            if (self.ppidparam == ""):
                for j in range(len(osPsParam.keys())):
                    opSysFamily = osPsParam.keys()[j]
                    if (osFamily.has_key(opSysFamily)):
                        if (sys.platform in osFamily[opSysFamily]):
                            self.ppidparam = osPsParam[opSysFamily]
                if (self.ppidparam == ""):
                    self.log4py.warn("Unknown operating system platform: %s" % sys.platform)
                    self.ppidparam = osPsParam[OSTYPEUNKNOWN]

            pptab.read(self.ppidparam % pids[i])
            ppid = pptab.process(pptab.pids()[0])["PPID"]
            if not ppids.has_key(ppid):
                ppids[ppid] = [pids[i]]
            else:
                ppids[ppid].append(pids[i])
        return ppids
