#!/usr/bin/python

# 
# filesinsnapshots
# 
# Prints out how many times each LIN that occurs in snapshots
# occurs in all snapshots,
# sorted by most occurrences first.
# 
# Possible further work:
# - Take a list of snapids, or a range of snapids.
# - Take a date-time range of snapshot date-times.
# 

import collections
import operator
import optparse
import string
import subprocess

import isi.ui.shell.util as util

def runlist(cmdlist):
    """cmdlist is a list of strings: command, arg1, arg2, and so on.
    Execute the command with args in a separate process.
    Return (stdout, stderr) as a tuple of strings."""

    bufsize = -1;

    proc = subprocess.Popen(cmdlist, shell=False, bufsize=bufsize,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    universal_newlines=True)
    stdoutpipe = proc.stdout
    stderrpipe = proc.stderr

    [stdout, stderr] = proc.communicate()

    stdoutpipe.close()
    stderrpipe.close()
    if proc.wait() != 0:
        print "There were some errors running ", cmdlist
        raise IOError

    return (stdout, stderr)

def ishex(instring):
    """If all the characters in instring are hex digits return True,
    otherwise return False."""
    for char in instring:
        if not char in string.hexdigits:
            return False
    return True

def getsnapids():
    """Get a list of snapshots.
    Return a list of snapshots."""

    cmdlist = ["/usr/sbin/isi_stf_test", "listid"]

    (stdout, stderr) = runlist(cmdlist)

    # stderr should be empty
    if stderr:
        print "Stderr not empty when running ", cmdlist
        raise IOError

    lines = stdout.strip().splitlines()
    lines.remove("List of snapids:")
    snapids = []
    for line in lines:
        # Each line looks like "10008006d 660"
        # The first column is "file's file serial number (inode number)
        # in hexadecimal.  This is the canonical LIN format."
        # The second column is the snapid.
        fields = line.split()
        if ( (len(fields) != 2)
                or not ishex(fields[0])
                or not fields[1].isdigit() ):
            raise IOError
        snapid = fields[1]
        snapids.append(snapid)
    # print "snapids: ", snapids
    return snapids

def getlinsinsnap(snapid):
    """Given a snapshot ID, get the list of LINs that are in the snapshot.
    Return a list of LINs."""

    cmdlist = ["/usr/sbin/isi_stf_test", "scan", str(snapid)]

    (stdout, stderr) = runlist(cmdlist)

    # stderr should be empty
    if stderr:
        print "Stderr not empty when running ", cmdlist
        raise IOError

    line = stdout.strip()
    # There is one line of output, it looks like
    # 'Lins for "2": 10004000b 10004000d 10004000f ...'
    # The first number is the snapid, after the colon are LINs in hex.
    if not line.startswith('Lins for '):
        raise IOError

    # header gets 'Lins for "2"'
    # linstring gets ' 10004000b 10004000d 10004000f ...'
    header, linstring = line.split(":", 1)
    linstring.strip()

    # print linstring
    lins = linstring.split()
    # print lins
    for lin in lins:
        if not ishex(lin):
            raise IOError
    return lins

def getpath(lin):
    """Given a LIN get the LIN's file path."""

    # convertLIN takes a list of LINs and a bool,
    # so convert getpath's one LIN arg to a list of one element.
    # convertLIN returns a list of LINs and a list of paths.
    # The list of paths returned should contain one string or one None.
    # See also use of convertLIN in getcmd.py.
    linlist = [lin]
    linsnaps, paths = util.convertLIN(linlist, print_path=False)
    # print "getpath: ", "lin: ", lin, "linsnaps: ", linsnaps, "paths: ", paths

    if len(paths) != 1:
        print "more than one path for LIN ", lin, " : ", paths

    # print paths[0]
    return paths[0]

def printpaths(sortedlincounts, num_lins_to_print):
    # if num_lins_to_print is None then print all LINs that are in snapshots
    lins_printed = 0
    for lin, count in sortedlincounts:
        if ((num_lins_to_print is not None)
                and (lins_printed >= num_lins_to_print)):
            break
        path = getpath(lin)
        if path == None:
            # a path is one string so make this one string
            path = "NO-PATH"
        print '%10d: %10s: %s' % (count, lin, path)
        lins_printed += 1

def countfilesinsnaps(sortedlincounts):
    """Count the total number of file occurrences in snapshots."""
    total = 0;
    for lin, count in sortedlincounts:
        total += count;
    return total

def main(options):
    num_lins_to_print = options.num_lins_to_print

    snapids = getsnapids()
    # print "snapids: ", snapids
    lincounts = collections.defaultdict(int);
    for snapid in snapids:
        linsinsnap = getlinsinsnap(snapid)
        # print "lins in snapshot ", snapid, ": ", linsinsnap
        for lin in linsinsnap:
            lincounts[lin] += 1
    # print lincounts

    sortedlincounts = sorted(lincounts.items(),
                                key=operator.itemgetter(1), reverse=True)

    # output the results

    print "Number of snapshots:\t\t\t%7u" % len(snapids)
    print "Number of files in snapshots:\t\t%7u" % len(lincounts)
    print ("Total occurrences of files in snapshots:\t%7u"
                                        % countfilesinsnaps(sortedlincounts))

    print 
    print "How many times each file which occurs in snapshots "
    print "occurs in all snapshots, "
    print "sorted by most occurrences first."
    print 

    print "%10s: %10s: %s" % ("Occurances", "---LIN----", "File-Path")
    printpaths(sortedlincounts, num_lins_to_print)


# 
# Main program
# 
if __name__ == "__main__":
    usage = ("usage: %prog [options] \n"
                "\n"
                "Prints out how many times each LIN occurs "
                "in all snapshots, \n"
                "sorted by most occurrences first. \n"
                "\n"
                "Use \"%prog --help\" to list options.")
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("-n", "--linstoprint",
                        type="int", dest="num_lins_to_print",
                        default=None,
                        help='number of LINs to print, default all')
    (options, args) = parser.parse_args()
    main(options)

