#-------------------------------------------------------------------------------
# Name:          ArcGIS Server Services Sniffer
# Purpose:       To create a HTML table of all featureclasses and their
#                respective ArcGIS Server services and the MXD used to create
#                each service.
#
# Need:          Whenever I need to change the schema of a featureclass that is
#                locked by a map service I have to manually right click on each
#                service in ArcCatalog and choose service workspaces to view
#                the list of featureclasses. I kept telling myself to
#                compile a list in Excel to track it all but the amount of work
#                it would take to manually compile it all would be too much.
#                I thought there had to be a way to do it with Python.
#
#                Two stack overflow posts pointed a way for me to try and create something on my own.
#                http://gis.stackexchange.com/questions/101816/which-feature-classes-is-are-used-by-a-service?rq=1
#                http://gis.stackexchange.com/questions/122026/is-there-a-python-script-to-identify-which-database-features-are-in-map-feature?rq=1
#
# Overview:      The minidom library is used to parse the manifest xml file
#                A dictionary is created where the layer is the key and the
#                list of service names and their MXDs are the values. A tuple
#                is then created from the dictionary to preserve the 'key-value'
#                pairs and then sorted on the 'keys' so the feature class
#                will be sorted alphabetically in the HTML table.
#
#                The HTMl table has 3 columns: layer Name, list of service names
#                and a list of MXDs for each service name.
#
#
#
# Prerequisites: Change the source to the directory that contains the manifest files
#                Change the destination for the html file output
# 
#
# Author:        Mike Onzay, GIS Specialist, City of Rockville, MD
#
# Completed:     07/05/2015
# Updated:       07/07/2015 - added code in buildhtml to deal with services
#                             that do not have a service MXD (i.e. locators)

#-------------------------------------------------------------------------------

import os
from xml.dom import minidom
from datetime import datetime
import collections
import webbrowser

# source = of the ArcGIS Server folders containing the manifest files. Something like r'...arcgisserver\directories\arcgissystem\arcgisinput'
# destination = # output for the html file
asof = datetime.now().strftime('%m-%d-%Y %H:%M:%S')

manifestLst = []
ags_fc_dict = collections.defaultdict(list) # creates a new list if key is not found

print "walk the directories..."

# Get all manifest files and put path in list
for root, dirs, files in os.walk(source):
    for file in files:
        if file == 'manifest.xml':
            path_to_file = os.path.join(root,file)
            #print root
            manifestLst.append(path_to_file)

def get_name_only(mf,indx):
    """ Gets a part from a pathname.

    When getting the service name this works as long as the path generated by
    ArcGIS Server does not change from ..\<service name\extracted\mainfest.xml

    """
    path_list = mf.split(os.sep)
    return path_list[indx].encode('UTF8')

def get_mxd(itemList):
    """ Gets only the MXD.

    Every manifest file contains one mxd name and path. I couldn't figure out
    how to get it and use it correctly when looping through makeuniquelist

    """
    for item in itemList:
        for t in item.childNodes:
            if t.nodeType == t.TEXT_NODE:
                if t.nodeValue.endswith('.mxd'):
                    #print t.NodeValue + " at if"
                    mxd = t.nodeValue
                    return mxd.encode('UTF8')
                if t.nodeValue.ends:
                    #print t.nodeValue + " at else"
                    mxd = "No MXD"
                    return mxd.encode('UTF8')

# build the dictionary where the fc are the keys and the service name and mxd are the values
print "build the dictionary..."


for mf in manifestLst:
    service_name = get_name_only(mf,-3) # service name is always 3 up from end

    xmldoc = minidom.parse(mf) #http://stackoverflow.com/questions/317413/get-element-value-with-minidom-python
    itemList = xmldoc.getElementsByTagName('OnPremisePath')
    gmxd = get_mxd(itemList)
    """
    In the xml file OnPremisePath typically contains the featureclass names, the
    path to the mxd and the path to the ArcGIS Server connection.
    """
    for item in itemList:
        for t in item.childNodes: # for each tag in the childNode of the item
            #print t.nodeType, t.TEXT_NODE #http://www.javascriptkit.com/domref/nodetype.shtml
            if t.nodeType == t.TEXT_NODE: # if the tag's nodeType is equal to the tag's TEXT_NODE (3 == 3)
                if not t.nodeValue.endswith('(admin)'): # ignore the path containing where service is staged by AGS
                    if not t.nodeValue.endswith('.mxd'): #after this line all that is left in the list are featureclasses
                        fc = get_name_only(t.nodeValue,-1) # get only the layer name not the path
                        if fc not in ags_fc_dict:
                            ags_fc_dict[fc] = [(service_name,gmxd)] # add layer name to dict with associated service name in list
                        else:
                            ags_fc_dict[fc].append((service_name,gmxd)) # add service name to list for featureclass that already exists


print "build the HTML file..."

# Create a list of sorted keys so the html table will be sorted correctly
def buildOrderedTuple(ags_fc_dict):
    maketuple = ags_fc_dict.items()
    maketuple.sort()
    return maketuple

sList = buildOrderedTuple(ags_fc_dict)


css_style = '<style>'\
'table{border-collapse: collapse; empty-cells: "show"}'\
'table, td, th {border: 1px solid black;}'\
'caption {font-weight: bold;text-align: left;}'\
'td {padding: 7px;}'\
'th{background-color: #1E90FF;color: #ffffff;text-align: center;}'\
'tr.alt {color: #000000;background-color: #A9D0F5;}'\
'ul {list-style-type:square}'\
'</style>'

buildhtml =[]
sList = buildOrderedTuple(ags_fc_dict)

buildhtml.append('<!DOCTYPE html><html><head>'\
'<meta charset="utf-8"><title>Layers to Services</title>' + css_style + '</head><body>'\
'<table><caption>Layers to Services as of '+ asof + '</caption>'\
'<tr><th>Layer Name</th>')

buildhtml.append('<th>Service Names</th><th>Service MXD</th></tr>')

for idx, item in enumerate(sList, start=1):
    if idx % 2 == 1:
        buildhtml.append('<tr class="alt"><td>')
    else:
        buildhtml.append('<tr><td>')
    services = item[1]
    buildhtml.append(item[0])
    buildhtml.append('</td><td><ul>')
    for s in services:
        buildhtml.append('<li>')
##        buildhtml.append(s[0])
##        buildhtml.append('</li>')
        if not (s[0] is None):
            buildhtml.append(s[0])
            buildhtml.append('</li>')
        elif s[0]:
            buildhtml.append(s[0])
            buildhtml.append('</li>')
    buildhtml.append('</ul></td><td><ul>')
    for s in services:
        buildhtml.append('<li>')
##        buildhtml.append(s[1])
##        buildhtml.append('</li>')

        if not (s[1] is None):
            buildhtml.append(s[1])
            buildhtml.append('</li>')
        elif s[1]:
            buildhtml.append(s[1])
            buildhtml.append('</li>')
    buildhtml.append('</ul></td></tr>')

buildhtml.append('</table></body></html>')

with open(os.path.join(destination, "AGSServicesSniffer.htm"),"wb") as f:
    f.writelines(buildhtml)

# uncommet for testing
#new = 2 #open in a new tab if possible
#url = "file://C:/GIS/output/AGSServicesSniffer.htm"
#webbrowser.open(url,new=new)
