#!/usr/bin/env python
#
#   ConVirt   -  Copyright (c) 2008 Convirture Corp.
#   ======
#
# ConVirt is a Virtualization management tool with a graphical user
# interface that allows for performing the standard set of VM operations
# (start, stop, pause, kill, shutdown, reboot, snapshot, etc...). It
# also attempts to simplify various aspects of VM lifecycle management.
#
#
# This software is subject to the GNU General Public License, Version 2 (GPLv2)
# and for details, please consult it at:
#
#    http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
# 
#
#

import os
import convirt.core.utils.constants
from convirt.core.model.ImageStore import ImageStore
from convirt.core.utils.utils import XMConfig, PyConfig
import convirt.core.utils.utils

# for dom parsing
import xml.dom.minidom
from xml.dom.minidom import Node
from convirt.core.utils.utils import fetch_isp, mkdir2, populate_node, populate_attrs, getText
import urllib

utils = convirt.core.utils.utils
constants = convirt.core.utils.constants
##
# class to manage appliance sources/feeds 
#
##
class ApplianceStore:
    
    DEFAULT_APPLIANCE_STORE = "/var/cache/convirt/appliance_store"
    APPLIANCE_FEEDS = "appliance_feeds.conf"
    CATALOG_CONF = "catalog.conf"
    
    def __init__(self, local_node, config):
        self.local_node = local_node
        self.config = config
        
        self._feeds = None
        self.cache_dir = None
        self.catalogs = {}
        # List of appliances keyd by feed name
        self.appliance_list = {}
        
        self.appliance_store_dir = config.get(XMConfig.PATHS,
                                          constants.prop_appliance_store)
        if self.appliance_store_dir is None or self.appliance_store_dir is '':
            self.appliance_store_dir = self.DEFAULT_APPLIANCE_STORE
            
        self.catalog_conf_file = os.path.join(self.appliance_store_dir,
                                              self.CATALOG_CONF)
        self.catalog_conf = PyConfig(self.local_node,self.catalog_conf_file)
        
        catalogs = self.catalog_conf.get("catalogs")
        import pprint
        pprint.pprint(catalogs)
        if catalogs:
            self.catalogs = catalogs

        # create the cache dir if it does not exist
        mkdir2(self.local_node,self.get_cache_dir())

    def __getattr__(self, name):
            if name == 'feeds': 
                return self.__initialize()

    def __initialize(self):
        if not self._feeds:
            return self.__read_appliance_feeds()
        else:
            return self._feeds

    def __read_appliance_feeds(self):
        # fetch the feed confs from the catalog sources
        # currently catalog provides simple name space for placing
        # files not surfaced anywhere else in the model.
        for c, c_info in self.catalogs.iteritems():
            if isinstance(c_info, str):
                url =  c_info
            # the c_info can be tuple containing other rich info
            # like proxy class to download etc. TBD
            try:
                feed_conf = self.fetch_catalog(c, url)
            except Exception, ex:
                feed_conf = self.get_conf_name(c)
                print "Error getting catalog ", c, url, ex
                print "Will try to use ", feed_conf
                

            if not os.path.exists(feed_conf):
                print "Skipping : %s does not exist.", feed_conf
                continue
            
            if not self._feeds:
                self._feeds = PyConfig(self.local_node, feed_conf)
            else:
                # merge the current feed data
                feed = PyConfig(self.local_node, feed_conf)
                for k, v in feed.iteritems():
                    self._feeds[k]=v
                    
        return self._feeds

    def get_conf_name(self, catalog):
        feed_conf_dir = os.path.join(self.appliance_store_dir,catalog)
        feed_conf = os.path.join(feed_conf_dir, self.APPLIANCE_FEEDS)
        return feed_conf
    
    # simply fetch the appliance_feed.conf pointed by the url
    # use catalog as namespace to keep different conf files in
    # directory.
    def fetch_catalog(self, catalog, url):
        feed_conf = self.get_conf_name(catalog)
        feed_conf_dir = os.path.dirname(feed_conf)
        mkdir2(self.local_node,feed_conf_dir)
        fetch_isp(url, feed_conf, "text/plain")
        print "fetched ", url, feed_conf
        return feed_conf

    def get_appliance_feeds(self):
        return self.feeds.keys()
    
    def get_appliances_list(self, feed_name):
        if feed_name is None:
            return []
        
        if self.appliance_list.get(feed_name) is not None:
            return self.appliance_list[feed_name]

        # get the appliance feed
        a_list = self._get_appliance_details(feed_name)
        if a_list :
            self.appliance_list[feed_name] = a_list

        return a_list


    def get_cache_dir(self):
        if self.cache_dir is None:
            self.cache_dir = os.path.join(self.appliance_store_dir, "cache")
        return self.cache_dir

    def get_feed_cache_dir(self, feed_name):
        return os.path.join(self.get_cache_dir(), feed_name)

    def get_feed_file_name(self, feed_name):
        return "feed.xml"
    
    def get_provider_id(self, feed_name):
        feed = self.feeds.get(feed_name)
        if feed:
            return feed_name


    def get_provider(self, feed_name):
        feed = self.feeds.get(feed_name)
        if feed:
            return feed["provider"]

    def get_provider_url(self, feed_name):
        feed = self.feeds.get(feed_name)
        if feed:
            return feed["provider_url"]

    def get_logo_url(self, feed_name):
        feed = self.feeds.get(feed_name)
        if feed:
            return feed["logo_url"]

    def get_feed_url(self, feed_name):
        feed = self.feeds.get(feed_name)
        if feed:
            return feed["feed_url"]

    def get_feed_name(self, feed_name):
        feed = self.feeds.get(feed_name)
        if feed:
            return feed["feed_name"]

    # download the RSS/ATOM feed and make it available.
    def _get_appliance_details(self,feed_name):
        feed = self.feeds.get(feed_name)
        if feed is None: return None
        
        cache_dir = self.get_feed_cache_dir(feed_name)
        utils.mkdir2(self.local_node, cache_dir)
        
        cache_file= self.get_feed_file_name(feed_name)
        
        feed_dest = os.path.join(cache_dir, cache_file)

        url = self.get_feed_url(feed_name)
        try :
            fetch_isp(url, feed_dest, "/xml")
        except Exception, ex:
            print "Error downloading feed " , url, ex
            print "Will try to use cached copy if available."

        #TODO : keep track of the download time
        details = []
        # parse the feed and return details
        if self.local_node.node_proxy.file_exists(feed_dest):
            details =  self._make_details(feed_name, feed_dest)
        else:
            print "Skipping ", feed_dest, " not found."

        return details


    # get the feed content and return a list of details for appliance
    # parse the feed RSS/ATOM
    def _make_details(self, feed_name, feed_file):
        feed_dom = xml.dom.minidom.parse(feed_file)        
        a_list = []
        for entry in feed_dom.getElementsByTagName("entry"):
            info = {}
            info["provider_id"] = self.get_provider_id(feed_name)
            info["provider"] = self.get_provider(feed_name)
            info["provider_url"] = self.get_provider_url(feed_name)
            info["provider_logo_url"] = self.get_logo_url(feed_name)
            
            for text in ("title","id","popularity_score","description", "short_description"):
                info[text] = getText(entry, text)

                
            populate_node(info,entry,"link",
                          { "link" : "href"})

            download_nodes = entry.getElementsByTagName("download")
            for download_node in download_nodes:
                download_info = {}
                populate_attrs(download_info,download_node,
                               { "href" : "href",
                                 "type" : "type"
                                 })

                populate_node(download_info,download_node,"platform",
                               { "platform" : "name"})
                
                populate_node(download_info,download_node, "package",
                               { "filename" : "filename",
                                 "compressed" : "compressed",
                                 "archive" : "archive",
                                 "size" : "size",
                                 "installed_size" : "installed_size"})
                populate_node(download_info,download_node,"kernel",
                               { "PAE" : "PAE",
                                 "arch": "arch",
                                 "is_hvm": "is_hvm"})

                # normalize x86, x86_32 to x86
                if download_info.get("arch"):
                    if download_info["arch"].upper() == "X86_32":
                        download_info["arch"] = "x86"
                        
                for t in ("updated",):
                    download_info[t] = getText(download_node, t)

                # merge feed and download in to a feed entry.
                feed_info = {}
                for k in info.keys():
                    feed_info[k] = info[k]
                    
                for k in download_info.keys():
                    if feed_info.get(k) is None:
                        feed_info[k] = download_info[k]
                    else:
                        print "ERROR : collision in feed and download entry"
                        
                a_list.append(feed_info)

        return a_list

    # initialize all feed details
    def _init_all(self):
        for f in self.feeds.keys():
            l = self.get_appliances_list(f)

    def get_all_archs(self):
        self._init_all()
        all_archs = {}
        for l in self.appliance_list.itervalues():
            for a in l:
                arch = a.get("arch")
                if arch and not all_archs.get(arch):
                    all_archs[arch] = arch

        return all_archs.keys()
                

    def get_all_packages(self):
        self._init_all()
        all_packages = {}
        for l in self.appliance_list.itervalues():
            for a in l:
                package = a.get("type")
                if package and not all_packages.get(package):
                    all_packages[package] = package

        return all_packages.keys()
                




if __name__ == '__main__':
    from ManagedNode import ManagedNode
    import pprint

    local_node = ManagedNode(hostname=constants.LOCALHOST)
    appliance_store = ApplianceStore(local_node, local_node.config)
    feeds = appliance_store.get_appliance_feeds()
    for feed_name in feeds:
        a_list = appliance_store.get_appliances_list(feed_name)
        print "=================", feed_name
        for a in a_list:
            print "*** ", a["title"]
            pprint.pprint(a)

            




