#!/usr/bin/python
'''
" Repodiff utility for finding differences between different repositories
"
" The tool downloads, unpacks and parses synthesis.hdlist.cz and 
" changelog.xml.lzma to genererate lists of newly added packages,
" removed from new repository packages and updated packages.
" The tool outputs data to standart output or to file.
" It can show if a removed packages is obsoleted by some package
" in new repositories. Also the tool can output data in format of
" HTML table.
" 
" REQUIREMENTS
" ============ 
"  - urpmi
"  - python-2.7
"  - lzma
"  - gzip
"  - libxml2 python library
"  - rpm python library
"
" Copyright (C) 2012 ROSA Laboratory.
" Written by Vladimir Testov <vladimir.testov@rosalab.ru>
"
" This program is free software: you can redistribute it and/or modify
" it under the terms of the GNU General Public License or the GNU Lesser
" General Public License as published by the Free Software Foundation,
" either version 2 of the Licenses, or (at your option) any later version.
"
" This program is distributed in the hope that it will be useful,
" but WITHOUT ANY WARRANTY; without even the implied warranty of
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
" GNU General Public License for more details.
"
" You should have received a copy of the GNU General Public License
" and the GNU Lesser General Public License along with this program.
" If not, see <http://www.gnu.org/licenses/>.
'''

import argparse
import urllib
import tempfile
import os
import subprocess
import re
import libxml2
import sys
from datetime import date
import rpm
import shutil
import urllib2
import urpmmisc

old_dir = "old"
new_dir = "new"
changelog = "changelog.xml"
synthesis = "synthesis.hdlist"
htmlname = "repodiff.html"
media = "media_info/"

synthtags = ["provides", "requires", "obsoletes", "conflicts", "suggests", 
            "summary", "info"]

minus_check = re.compile('-')
re_search_unver = re.compile("([^\[\]]+)[\[\]]")
re_search_verrel = re.compile("\[(== |> |< |>= |<= )([\{\}+=0-9a-zA-Z_\.]*:)?([[\{\}+=0-9a-zA-Z_\.]+)(-[[\{\}+=0-9a-zA-Z_\.]+)?([^\[\]]*)\]$")

files = ["changelog.xml.lzma", "synthesis.hdlist.cz"]
files_renamed = ["synthesis.hdlist.gz"]
#temp files to be deleted anyway
temp_dir = ''
temp_old = []
temp_new = []
file_output = sys.stdout
ifnotquiet = True

def ParseCommandLine():
    """Parse arguments.
    
    Parse arguments from command line.
    Return these arguments.
    """
    parser = argparse.ArgumentParser(
        description="Tool for comparing sets of repositories.")
    parser.add_argument("--old", "-o", action="store", nargs='+', required="True",
        metavar="OLD_REPO", help="URL or PATH to old repositories")
    parser.add_argument("--new", "-n", action="store", nargs='+', required="True",
        metavar="NEW_REPO", help="URL or PATH to new repositories")
    parser.add_argument("--size", "-s", action="store_true",
        help="Show differences in package sizes.")
    parser.add_argument("--simple", action="store_false",
        help="Simple output format.")
    parser.add_argument("--quiet", "-q", action="store_false",
        help="Hide service messages.")
    parser.add_argument("--html", action="store_true",
        help="Output in HTML format, if --output is not present" +\
             " HTML will be created in current directory." +\
             " --size and --simple options are ignored.")
    parser.add_argument("--output", "-out", action="store", nargs=1, default='',
        metavar="OUTPUT_FILE", help="Change standart output to \"OUTPUT_FILE\".")
  
    CommandLineArguments = parser.parse_args()
    return CommandLineArguments

def CheckURL(url):
    """URL check.
    
    Check that URL is gettable.
    """
    try:
        urllib2.urlopen(url)
    except:
        print "Error: URL to repository \"" + url + "\" is incorrect"
        exit_proc(0)

def CheckArgs(url):
    """Trivial checks.
    
    Some trivial checks, that url is correct.
    """
    if (url.startswith("http://") or url.startswith("ftp://")):
        if not url.endswith('/'):
            url = url + '/'
        tmp_url = url + media
        CheckURL(tmp_url)
    elif (os.path.isdir(url)) or url.startswith("file://"):
        if url.startswith("file://./"):
            url = url[7:]
        else:
            url = url[6:]
        if not url.endswith('/'):
            url = url + '/'
        tmp_url = url + media
        if not os.path.isdir(tmp_url):
            print "Error: directory " + tmp_url + " does not exist"
            exit_proc(0)
    else:
        (e1,e2,url) = urpmmisc.GetUrlFromRepoName(url)
        if (url):
            if not url.endswith('/'):
                url = url + '/'
            url = url + media
            CheckURL(url)
        else:
            print "input is not correct url, path or name of repository"
            exit_proc(0)
    return url
  
def CheckOutput(output_file):
    """Check output file.
    
    Check if the file can be created and redirect standart output to this file.
    """
    global file_output
    if(output_file != ''):
        if(os.path.isfile(output_file[0])):
            print "Error: File " + output_file[0] + " is already exists"
            exit_proc(0)
        else:
            dirname = os.path.dirname(output_file[0])
            if(dirname == '') or (os.path.exists(dirname)):
                try:
                    file_output = open(output_file[0], "w")
                except IOError:
                    print "Error: File " + output_file[0] + " cannot be created"
            else:
                print "Error: Path " + dirname + " does not exist."
                exit_proc(0)
  
def GetFiles(url, localpath):
    """Download.
    
    Download needed files from server.
    Parameters:
        url - list of url (from "old" repo XOR "new" repo)
        localpath - list of temporary paths where files should be
            downloaded to.
    """
    for filename in files:
        for i in range(len(url)):
            tmpdir = localpath[i]
            tmpname = tmpdir + filename
            if not os.path.isdir(tmpdir):
                os.makedirs(os.path.realpath(tmpdir))
            if ifnotquiet:
                print "getting file " + filename + " from \n  " + url[i] + filename
            if os.path.isdir(url[i]):
                try:
                    shutil.copyfile(url[i] + filename, tmpname)
                except:
                    print "Error: file " + filename + " was not copied"
                    exit_proc(0)
            else:
                try:
                    file_from = urllib2.urlopen(urllib2.Request(url[i] + filename))
                    file_to = open(tmpname, "w")
                    shutil.copyfileobj(file_from, file_to)
                except:
                    print "Error: file " + filename + " was not downloaded"
                    exit_proc(0)
                file_from.close()
                file_to.close()
  
def RenameCZ(files_dir):
    """Rename.
    
    Rename *.cz files to *.gz so gzip can understand file extensions.
    """
    filename = files[1]
    if not os.path.isfile(files_dir + filename):
        print "Error: file not found: " + files_dir + filename
        exit_proc(-1)
    try:
        os.rename(files_dir + filename, files_dir + files_renamed[0])
    except OSError:
        print "Error: cannot rename file " + filename + " to " + files_renamed[0]
        exit_proc(-1)
    if not os.path.isfile(files_dir + files_renamed[0]):
        print "Error: file " + files_dir + files_renamed[0] + " is missing."
        exit_proc(-1)
    else:
        if ifnotquiet:
            print "file " + filename + " was renamed to " + files_renamed[0]
    
def UnpackFiles(files_dir):
    """Unpack.
    
    Unpack needed files in selected directory.
    """
    filename = files[0]
    if ifnotquiet:
        print "unpacking file " + filename
    subprocess.call(["lzma", "-df", files_dir + filename])
    filename = files_renamed[0]
    if ifnotquiet:
        print "unpacking file " + filename
    subprocess.call(["gzip", "-df", files_dir + filename])

def ParseVersion(names_list):
    """Parse version info is present.
    
    Parse version information from the field. e.g. provided_name[>= 1.2.3-4.5.6]
      is parsed to (provided_name, sign, (epoch, version, release))
    """
    new_names_list = []
    for name in names_list:
        match = re_search_unver.match(name)
        if match:
            tmp_entry = match.group(1)
        else:
            tmp_entry = name
        match = re_search_verrel.search(name)
        if match:
            sign = match.group(1)[:-1]
            epoch = match.group(2)
            if epoch:
                epoch = epoch[:-1]
            else:
                epoch = ''
            version = match.group(3)
            release = match.group(4)
            if release:
                release = release[1:]
            else:
                release = ''
            verrel = (epoch, version, release)
        else:
            sign = ''
            verrel = ('','','')
        new_names_list.append((tmp_entry, sign, verrel))
    return new_names_list

def ParseSynthesis(synthfile, pkgdict):
    """Collect info about packages.
    
    Parse synthesis.hdlist file (or add new entries to pkgdict).
    
    pkgdict is a dictionary with format:
    pkgdict[name]=(verrel,(s0,s1,s2))
    where:
        name - is package name parsed from package filename
        verrel - is tuple (version, release, epoch)
        s0[] - is package info
        s1 - is package summary
        s2[] - is list of obsoleted packages
    """
    if not os.path.isfile(synthfile):
        print "Error: Synthesis file " + synthfile + " was not found."
        exit_proc(-1)
    if ifnotquiet:
        print "Parsing synthesis"
    try:
        synth = open(synthfile)
        tmp = ['', '', '']
        for synthline in synth:
            if synthline.endswith('\n'):
                synthline = synthline[:-1]
            tmpline = synthline.split('@')
            tag = tmpline[1]
            if tag == synthtags[2]:
                tmp[2] = tmpline[2:]
            elif tag == synthtags[5]:
                tmp[1] = '@'.join(tmpline[2:])
            elif tag == synthtags[6]:
                tmp[0] = tmpline[2:]
                disttagepoch = ChkTagEpoch(tmp[0])
                tmp[2] = ParseVersion(tmp[2])
                (name, version, release) = RPMNameFilter(tmp[0][0], disttagepoch) #disttag + distepoch
                verrel = (version, release, tmp[0][1])
                if(not name in pkgdict):
                    pkgdict[name]=(verrel, (tmp[0], tmp[1], tmp[2]))
                elif(compare_versions(pkgdict[name][0], verrel) == -1):
                    pkgdict[name]=(verrel, (tmp[0], tmp[1], tmp[2]))
                tmp = ['', '', '']
        synth.close()
    except IOError:
        print "Error: Failed to open synthesis file " + synthfile
        exit_proc(-1)

def ChkDist(disttag, distepoch):
    """No minus in tag and epoch.
    
    Trivial check that tag and epoch hasn't got '-' in their name
    """
    if minus_check.search(disttag) or minus_check.search(distepoch):
        print "REPODIFF-Warning: strange format of <disttag> or <distepoch>: " +\
              disttag + distepoch
  
def ChkTagEpoch(i):
    """No minus in tag and epoch.
    
    Trivial check that tag and epoch hasn't got '-' in their name
    """
    if len(i) == 4:
        return '-'
    elif len(i) == 5:
        disttag = i[4]
        distepoch = ''
        ChkDist(disttag, distepoch)
        return disttag + distepoch
    elif len(i) == 6:
        disttag = i[4]
        distepoch = i[5]
        ChkDist(disttag, distepoch)
        return disttag + distepoch
    else:
        print "REPODIFF-Warning: strange <info>: " + str(i)

def RPMNameFilter(rpmname, disttagepoch):
    """Parse name and verrel.
    
    Function that parses name, version and release of a package.
    """
    string = rpmname.split('-')
    lastpart = string.pop()
    tmp = lastpart.split('.')
    tmp.pop()
    lastpart = '.'.join(tmp)
#    if (lastpart[0].isdigit() and not lastpart.isdigit()) or\
#            (not lastpart.startswith(disttagepoch)):  #thanks to lib64modprobe0-3.3-pre11.40mdv2009.0.x86_64
    if (lastpart[0].isdigit() or (not lastpart.startswith(disttagepoch))) and\
            (not lastpart.isdigit()):
        name = '-'.join(string[:-1])
        ver = string[-1]
        rel = lastpart
    else:
        name = '-'.join(string[:-2])
        ver = string[-2]
        rel = string[-1]
    return (name, ver, rel)  
    
def compare_versions(first_entry, second_entry):
    """Compare two verrel tuples.
    
    dict_entry and comp_entry are verrel tuples
    verrel = (version, release, epoch).
    Return 1 if the first argument is higher.
           0 if they are equivalent.
          -1 if the second argument is higher.
    """
    (version1, release1, first_epoch) = first_entry
    (version2, release2, second_epoch) = second_entry
    return(rpm.labelCompare((first_epoch, version1, release1),
                            (second_epoch, version2, release2)))
    
def ParsePackage(directory_list):
    """Processing files, parsing synthesis, getting pkgdict.

    pkgdict is a dictionary with format:
    pkgdict[name]=(verrel,(s0,s1,s2))
    where:
        name - is package name parsed from package filename
        verrel - is tuple (version, release, epoch)
        s0[] - is package info
        s1 - is package summary
        s2[] - is list of obsoleted packages
    """
    pkgdict = {}
    for directory in directory_list:
        RenameCZ(directory)
        UnpackFiles(directory)
        ParseSynthesis(directory + synthesis, pkgdict)
    return pkgdict
    
def CreateDicts(dict_old, dict_new):
    """Creating dictionaries.
    
    Creating dictionaries for new, updated and removed(deleted) packages
    from two dictionaries: old and new, for old and new repositories.
    
    dict_old, dict_new are dictionaries with format:
    pkgdict[name]=(verrel,(s0,s1,s2))
    where:
        name - is package name parsed from package filename
        verrel - is tuple (version, release, epoch)
        s0[] - is package info
        s1 - is package summary
        s2[] - is list of obsoleted packages
        
    dict_new_packages and dict_del_packages have the same format.
    dict_upd_packages has format:
    dict_upd_packages[name]=((verrel_old,(so0,so1,so2)),
        (verrel_new,(sn0,sn1,sn2)),ifdowngraded)
    or
    dict_upd_packages[name]=(dict_old[name],dict_new[name],ifdowngraded)
    """
    dict_new_packages = {}
    dict_del_packages = {}
    dict_upd_packages = {}
    
    for name in dict_new:
        if(name in dict_old):  #updated or downgraded
            compare_result = compare_versions(dict_new[name][0],
                                              dict_old[name][0])
            if(compare_result > 0):  #updated
                dict_upd_packages[name] = (dict_old[name], dict_new[name], 0)
            elif(compare_result < 0):  #downgraded ?
                dict_upd_packages[name] = (dict_old[name], dict_new[name], 1)
        else:  #new
            dict_new_packages[name] = dict_new[name]
    for name in dict_old:
        if(not name in dict_new): #removed
            dict_del_packages[name] = dict_old[name]
    return (dict_new_packages, dict_del_packages, dict_upd_packages)

def ProcessNewPackages(dict_new_packages):
    """Processing newly added packages.
    
    dict_new_packages[name]=(verrel,(s0,s1,s2))
    where:
        name - is package name parsed from package filename
        verrel - is tuple (version, release, epoch)
        s0[] - is package info
        s1 - is package summary
        s2[] - is list of obsoleted packages
    """
    sorted_list = sorted(dict_new_packages)
    for name in sorted_list:
        file_output.write("New package: " + dict_new_packages[name][1][0][0] +\
                          "\n             " + dict_new_packages[name][1][1] + "\n\n")

def GenerateDictObsoleted(dict_new):
    """Generate Dictionary of obsoleted packages.
    
    pkgdict[name]=(verrel,(s0,s1,s2))
    where:
        name - is package name parsed from package filename
        verrel - is tuple (version, release, epoch)
        s0[] - package info
        s1 - package summary
        s2[] - list of packages obsoleted by current package
    """
    if ifnotquiet:
        print "Generating obsoleted list."
    obsoleted_by = {}
    for name in dict_new:
        for (obsolete, sign, verrel) in dict_new[name][1][2]:
            if(not obsolete in obsoleted_by):
                obsoleted_by[obsolete] = []
            obsoleted_by[obsolete].append((dict_new[name][1][0][0], sign, verrel))
    return obsoleted_by

def compare_verrel(verrel1, sign, verrel2):
    if (sign == ''):
        return 1
    (e1, v1, r1) = verrel1
    (e2, v2, r2) = verrel2
    # checks
    if (v2 == '') or (v1 == ''):
        return 1
    if (e1 == '') or (e2 == ''):
        e1 = '0'
        e2 = '0'
    if (r1 == '') or (r2 == ''):
        r1 = '0'
        r2 = '0'
    # compare
    compare = rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
    if (sign == "=="):
        if (compare == 0):
            return 1
    elif (sign == ">"):
        if (compare == 1):
            return 1
    elif (sign == "<"):
        if (compare == -1):
            return 1
    elif (sign == ">="):
        if (compare > -1):
            return 1
    elif (sign == "<="):
        if (compare < 1):
            return 1
    return 0

def ProcessDelPackages(dict_del_packages, dict_obsoleted):
    """Process deleted packages.
    
    Printing every deleted package. Show if package is obsoleted.
    pkgdict[name]=(verrel,(s0,s1,s2))
    where:
        name - is package name parsed from package filename
        verrel - is tuple (version, release, epoch)
        s0[] - is package info
        s1 - is package summary
        s2[] - is list of obsoleted packages
        
    dict_obsoleted is dictionary
    dict_obsoleted[name]=[obs1, ...]
    """
    sorted_list = sorted(dict_del_packages)
    for name in sorted_list:
        file_output.write("Removed package: " + dict_del_packages[name][1][0][0] + '\n')
        if (name in dict_obsoleted):
            tmp_list = []
            for (obsolete, sign, verrel) in dict_obsoleted[name]:
                if (compare_verrel(dict_del_packages[name][0], sign, verrel)):
                    tmp_list.append(obsolete)
            sorted_obsolete = sorted(tmp_list)
            for obs_package_name in sorted_obsolete:
                file_output.write("    Obsoleted by " + obs_package_name + '\n')

def ParseLogfile(dict_log, logfile, dict_upd_packages, mode):
    """Parse Changelog.
    
    mode == 0 - for old changelog: we search only for 1st entry in changelog
    mode == 1 - for new changelog: we collect entries from changelog untill
        we find remembered entry from changelog
    
    Parse changelog.xml to compare changes between updated packages.
    dict_log - is dictionary with format:
    dict_log[name] = 
        [(verrel, (time,name,text)), (verrel,[(time,name,text),...])]
    
    dict_upd_packages[name] = [old_pkg[name],new_pkg[name],ifdowngraded]
    or dict_upd_packages[name] = 
        [(verler,(s0,s1,s2)),(verrel,(s0,s1,s2)),ifdowngraded]
    """
    if ifnotquiet:
        print "Reading changelog"
    if not os.path.isfile(logfile):
        print "Error: Can't find changelog " + logfile
        exit_proc(-1)
    doc = libxml2.parseFile(logfile)
    if (not doc):
        print "Error: Can't read changelog " + logfile + "."
        exit_proc(-1)
    root = doc.children
    if root.name != "media_info":
        print "Error: Wrong changelog."
        doc.freeDoc()
        exit_proc(-1)
    tag_changelog = root.children
    while(tag_changelog):
        if(tag_changelog.name != "changelogs"):
            tag_changelog = tag_changelog.next
            continue
            
        tag_property = tag_changelog.properties
        pkgname = ''
        disttag = ''
        distepoch = ''
        while(tag_property):
            if (tag_property.name == "fn"):
                pkgname = tag_property.name
            elif (tag_property.name == "disttag"):
                disttag = tag_property.content
            elif (tag_property.name == "distepoch"):
                distepoch = tag_property.content
            tag_property = tag_property.next
        if (pkgname == ''):
            print "Error: Corrupted changelog"
            doc.freeDoc()
            exit_proc(-1)
        disttagepoch = disttag + distepoch
        if (disttagepoch == ''):
            disttagepoch = '-'
        (result_key, version, release) = RPMNameFilter(tag_property.content, disttagepoch)
        verrel = (version, release, "-1")
        # skip entry if it wasn't updated
        if result_key not in dict_upd_packages:
            tag_changelog = tag_changelog.next
            continue
        ifdowngraded = dict_upd_packages[result_key][2]
        # skip entry if it's name is not in dictionary
        if(dict_upd_packages[result_key][mode][1][0][0] != tag_property.content):
            tag_changelog = tag_changelog.next
            continue
        # skip entry if it has been found already with appropriate version
        if(result_key in dict_log) and (dict_log[result_key][mode]):
            tag_changelog = tag_changelog.next
            continue
        # if "old" repository do not have changelog of the package
        if(mode == 1) and (not result_key in dict_log):   
            dict_log[result_key] = []
            dict_log[result_key].append([])
            dict_log[result_key].append([])
            dict_log[result_key][0] = (verrel, [0])
        
        log_current = tag_changelog.children
        result_changelog = []
        while(log_current):
            if(log_current.name != "log"):
                log_current = log_current.next
                continue
                
            if(log_current.properties.name == "time"):
                entry_time = log_current.properties.content
            else:
                entry_time = 0
                
            if(mode == 1) and (not ifdowngraded) and\
                    (entry_time == dict_log[result_key][0][1][0]):
                break
            log_child = log_current.children
            while(log_child):
                if(log_child.name == "log_name"):
                    entry_name = log_child.content
                elif(log_child.name == "log_text"):
                    entry_text = log_child.content
                log_child = log_child.next
            result_changelog.append((entry_time, entry_name, entry_text))
            if(mode == ifdowngraded):
                break
            log_current = log_current.next
        if(mode == 0):
            dict_log[result_key] = []
            dict_log[result_key].append([])
            dict_log[result_key].append([])
            if not ifdowngraded:
                dict_log[result_key][0] = (verrel, result_changelog[0])
            else:
                dict_log[result_key][0] = (verrel, result_changelog)
        else:
            if not ifdowngraded:
                dict_log[result_key][1] = (verrel, result_changelog)
            else:  #special actions for downgraded packages
                new_result = []
                time_to_stop = result_changelog[0][0]
                tmp_change = dict_log[result_key][0][1]  #changelog list
                if tmp_change:  #changelog is not empty
                    i = 0
                    length = len(tmp_change)
                    while i < length:
                        if tmp_change[i][0] <= time_to_stop:
                            i = i + 1
                            break
                        new_result.append(tmp_change[i])
                        i = i + 1
                dict_log[result_key][1] = (verrel, new_result)
        tag_changelog = tag_changelog.next
    doc.freeDoc()
    
def GenerateLogfileDiff(dict_upd_packages):
    """Changelog difference list.
    
    Generate changelog difference list.
    dict_upd_packages[name] = [old_pkg[name],new_pkg[name],ifdowngraded]
    or dict_upd_packages[name] = [(verler,(s0,s1,s2)),(verrel,(s0,s1,s2)),ifdowngraded]
    """
    if ifnotquiet:
        print "Generating changes list."
    dict_logfile_diff = {}
    dict_log = {}
    
    for old_dir in temp_old:
        ParseLogfile(dict_log, old_dir + changelog, dict_upd_packages, 0)
    for new_dir in temp_new:
        ParseLogfile(dict_log, new_dir + changelog, dict_upd_packages, 1)
        
    for name in dict_upd_packages:
        if(name in dict_log):
            if dict_log[name][1]:
                entry = dict_log[name][1][1]
            else:
                print "REPODIFF-Warning: Package " +  name + " was not described in changelogs.xml"
                entry = [(0, '', "REPODIFF-Warning: Changelogs of a package are absent in \"new\" repository.")]
        else:
            print "REPODIFF-Warning: Package " +  name + " was not described in changelogs.xml"
            entry = [(0, '', "REPODIFF-Warning: Changelogs of a package are absent.")]
        dict_logfile_diff[name] = entry
        
    return dict_logfile_diff

def ChangelogPrint(changes_list):
    """Changelog difference.
    
    Output changes in changelog.
    changes_list is list with format:
    changes_list = [(time,author,text)]
    """
    for entry in changes_list:
        file_output.write("* " + str(date.fromtimestamp(float(entry[0]))) +\
                          " " + entry[1] + '\n' + entry[2] + '\n\n')
    
def PrintLogfileDiff(package_name, dict_logfile_diff):
    """Changelog difference.
    
    Output changes in changelog.
    dict_logfile_diff is dictionary with format:
    dict_logfile_diff[name] = [(time,author,text)]
    """
    if package_name in dict_logfile_diff:
        ChangelogPrint(dict_logfile_diff[package_name])
    else:
        file_output.write("Package " + package_name + " has no changelog info\n")

def ProcessUpdPackages(dict_upd_packages, dict_logfile_diff,
                       ifsizes, ifnotsimple):
    """Process updated packages.
    
    ifsizes - is indicator: should we (1) or should we not (0) print 
        difference in package sizes.
    ifnotsimple - is indicator: should we (0) or shoudl we not (1) print
        difference in changelogs.
    Process updated packages and output everything needed info.
    dict_upd_packages[name] = [old_pkg[name],new_pkg[name],ifdowngraded]
    or dict_upd_packages[name] = [(verler,(s0,s1,s2)),(verrel,(s0,s1,s2)),ifdowngraded]
    """
    file_output.write("\n\nUpdated packages:\n\n")
    sorted_list = sorted(dict_upd_packages)
    for name in sorted_list:
        package = dict_upd_packages[name][1][1][0][0]
        if ifnotsimple:
            file_output.write(package + '\n' + '-'*len(package) + '\n')
            if dict_upd_packages[name][2]:
                file_output.write("   ***DOWNGRADED***\n")
            PrintLogfileDiff(name, dict_logfile_diff)
        else:
            old_package = dict_upd_packages[name][0][1][0][0]
            file_output.write(name + ": " + old_package + " -> " + package + '\n')
        if(ifsizes):
            sizediff = int(dict_upd_packages[name][1][1][0][2]) - \
                       int(dict_upd_packages[name][0][1][0][2])
            file_output.write("Size Change: " + str(sizediff) + " bytes\n\n")

def PrintSummary(dict_new_packages, dict_del_packages, dict_upd_packages):
    """Output summary.
    
    Output summary: numbers of new/removew/updated packages at all.
    """
    file_output.write("Summary:\n")
    length = len(dict_new_packages)
    if length:
        file_output.write("    Total added packages: " + str(length) + '\n')
    length = len(dict_del_packages)
    if length:
        file_output.write("    Total removed packages: " + str(length) + '\n')
    length = 0
    length_d = 0
    for packagename in dict_upd_packages:
        if dict_upd_packages[packagename][2] == 0:
            length = length + 1
        else:
            length_d = length_d + 1
    if length:
        file_output.write("    Total updated packages: " + str(length) + '\n')
    if length_d:
        file_output.write("    Total downgraded packages: " + str(length_d) + '\n')

def exit_proc(exit_code):
    """
    Remove trash.
    """
    if os.path.isdir(temp_dir):
        shutil.rmtree(temp_dir)
    if file_output:
        file_output.close()
    exit(exit_code)

def HTML_ParsePackage(temp_new):
    """Parse hdlist.
    
    HTML-specific ParsePackage(). Calls for ParsePackage
    """
    html_dict_list = []
    for tmpdir in temp_new:
        tempdict = ParsePackage([tmpdir])
        html_dict_list.append(tempdict)
    return html_dict_list

def HTML_UniteOld(list_dict_old):
    """Union of dictionaries.
    
    HTML-specific.
    """
    dict_old = list_dict_old[0]
    i = 1
    while(i < len(list_dict_old)):
        for name in list_dict_old[i]:
            if name not in dict_old:
                dict_old[name] = list_dict_old[i][name]
            elif(compare_versions(dict_old[name][0], list_dict_old[i][name][0]) == -1):
                dict_old[name] = list_dict_old[i][name]
        i = i + 1
    return dict_old
    
def HTML_CreateDicts(dict_old, list_dict_new):
    """Create dictionary of packages.
    
    Dictionary of packages and types of changes.
    """
    dict_packages = {}
    i = 0
    for dict_new in list_dict_new:
        (tmp_new, tmp_del, tmp_upd) = CreateDicts(dict_old, dict_new)
        for packagename in tmp_new:
            if packagename not in dict_packages:
                dict_packages[packagename] = []
            dict_packages[packagename].append((tmp_new[packagename], i, 1))
        for packagename in tmp_del:
            if packagename not in dict_packages:
                dict_packages[packagename] = []
            dict_packages[packagename].append((tmp_del[packagename], i, 2))
        for packagename in tmp_upd:
            if packagename not in dict_packages:
                dict_packages[packagename] = []
            if tmp_upd[packagename][2] == 0:
                dict_packages[packagename].append((tmp_upd[packagename][1], i, 3))
            elif tmp_upd[packagename][2] == 1:
                dict_packages[packagename].append((tmp_upd[packagename][1], i, 4))
        i = i + 1
    return dict_packages

def CssOutput():
    """Output style.
    
    Output contents of style tag or .css file.
    """
    csscontent = '\nbody {\nfont-size: 1em;\nmargin: 1em;\ncolor: black;\nbackground-color: white;\n}\n' +\
                 'th {\nborder-bottom-style: double;\n}\n' +\
                 'h1 {\nfont-size: 1.6em;\n}\n' +\
                 'h2 {\nfont-size: 1.4em;\n}\n' +\
                 'ul {\nfont-size: 1.2em;\n}\n' +\
                 'li {\nfont-size: 1em; list-style: disc;\n}\n' +\
                 '.even {\nbackground-color: #CCCCCC;\n}\n' +\
                 '.odd {\nbackground-color: #FFFFFF;\n}\n' +\
                 '.new {\nbackground-color: #C6DEFF;\n}\n' +\
                 '.removed {\nbackground-color: #FFC3CE;\n}\n' +\
                 '.updated {\nbackground-color: #CCFFCC;\n}\n' +\
                 '.downgraded {\nbackground-color: #F4F4AF;\n}\n' +\
                 'p.bold {\n font-weight: bold\n}\n'
    return csscontent
    
def HTML_OutputHead():
    """Output beginning of the document.
    
    Outputs static text.
    """
    file_output.write('<!--?xml version="1.0" encoding="UTF-8"?-->\n' +\
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +\
        '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n' +
        '<head>\n' +\
        '<title>Differences between Mandriva / Rosa releases</title>\n' +\
        '<meta name="keywords" content="Mandriva,Rosa,RPM,changes"/>\n' +\
        '<meta name="description" content="List of changes between Mandriva / Rosa releases"/>\n' +\
        '<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>\n' +\
        '<style type="text/css">' +\
        CssOutput() +\
        '</style>' +\
        '</head>\n' +\
        '<body>\n\n')

def GetRepoInfo(dict_packages, packagename, lenold, lennew, list_dict_old, list_dict_new):
    """Generate package-specific information.
    
    Generates class and name to be displayed in the table.
    """
    result1 = []
    result2 = []
    flag = 0
    for i in range(lenold):
        if packagename in list_dict_old[i]:
            result1.append(list_dict_old[i][packagename][0][0] + '-' +\
                           list_dict_old[i][packagename][0][1])
        else:
            result1.append("N/A")
        result2.append('')
        
    tmplist = dict_packages[packagename]
    tmpdict = {}
    for (entry, reponum, entry_type) in dict_packages[packagename]:
        tmpdict[reponum] = (entry[0][0] + '-' + entry[0][1], entry_type)
        
    for i in range(lennew):
        if(i not in tmpdict):
            if(packagename not in list_dict_new[i]):
                result1.append("N/A")
                result2.append("")
            else:
                result1.append(list_dict_new[i][packagename][0][0] + '-' +\
                               list_dict_new[i][packagename][0][1])
                result2.append("")
        else:
            (name, entry_type) = tmpdict[i]
            if entry_type == 1:
                result1.append(name)
                result2.append('class = "new"')
            elif entry_type == 2:
                result1.append("Removed")
                result2.append('class = "removed"')
                flag = 1
            elif entry_type == 3:
                result1.append(name)
                result2.append('class = "updated"')
            elif entry_type == 4:
                result1.append(name)
                result2.append('class = "downgraded"')
                
    return (result1, result2, flag)

def HTML_OutputBody(dict_packages, old, new, list_dict_old, list_dict_new):
    """Output table.
    
    Outputs table in HTML format.
    """
    file_output.write('<h1>Difference between repositories.</h1>\n' +\
                      '<p class="bold">The use of colour coding in tables:</p>\n' +\
                      '<table>\n' +\
                      '<tbody><tr><td class="new">New</td>\n' +\
                      '<td class="updated">Updated</td></tr>\n' +\
                      '<tr><td class="downgraded">Downgraded</td>\n' +\
                      '<td class="removed">Removed</td></tr>\n' +\
                      '</tbody></table>\n\n')
    repo_list = []

    all_list = []
    all_list.extend(old)
    all_list.extend(new)
    lenold = len(old)
    lennew = len(new)
    length = lenold + lennew
    
    tmp_string = '<h2>Old repositories:</h2>\n<ul>\n'
    for i in range(lenold):
        tmp_string = tmp_string + '<li>Repository ' + str(i) + ' :  <a href="' +\
                     old[i] + '">' + old[i] + '</a></li>\n'
    tmp_string = tmp_string + '</ul>\n'
    file_output.write(tmp_string)

    tmp_string = '<h2>New repositories:</h2>\n<ul>\n'
    for k in range(lennew):
        i = i + 1
        tmp_string = tmp_string + '<li>Repository ' + str(i) + ' :  <a href="' +\
                      new[k] + '">' + new[k] + '</a></li>\n'
    tmp_string = tmp_string + '</ul>\n'
    file_output.write(tmp_string)

    tmp_string = '<h2>Difference between '
    i = 0
    while(i < length):
        if(i < length - 2):
            delimeter = " , "
        elif(i == length - 2):
            delimeter = " and "
        else:
            delimeter = ''
        temp = '<a href="' + all_list[i] + '">' + \
                     'Repository ' + str(i) + '</a>'
        repo_list.append(temp)
        tmp_string = tmp_string + temp + delimeter
        i = i + 1
    tmp_string = tmp_string + ".</h2>\n"
    file_output.write(tmp_string)
    
    tmp_string = '<table>\n<tbody>\n<tr><th>Package name</th>'
    for reponame in repo_list:
        tmp_string = tmp_string + '<th>' + reponame + '</th>'
    tmp_string = tmp_string + '</tr>\n'
    
    strnum = 1
    resrange = []
    for i in range(lennew):
        resrange.append(lenold + i)
    sorted_list = sorted(dict_packages)
    
    for packagename in sorted_list:
        if strnum % 2:
            strtype = "even"
        else:
            strtype = "odd"
        tmp_string = tmp_string + '<tr class="' + strtype + '">'
        tmp_string = tmp_string + '<td>' + packagename + '</td>'
        (repo_name, repo_class, flag) = GetRepoInfo(dict_packages, packagename, 
                lenold, lennew, list_dict_old, list_dict_new)
        if flag:
            if(repo_name[lenold] == "Removed"):
                res = 0
                for k in resrange:
                    if(repo_name[k] != "Removed"):
                        res = 1
                if res:
                    for k in resrange:
                        if(repo_name[k] == "Removed"):
                            repo_name[k] = "N/A"
                            repo_class[k] = ''
            else:
                for k in resrange:
                    if(repo_name[k] == "Removed"):
                        repo_name[k] = "N/A"
                        repo_class[k] = ''
            
        for i in range(length):
            tmp_string = tmp_string + '<td ' + repo_class[i] + '>' +\
            repo_name[i] + '</td>'
        tmp_string = tmp_string + '</tr>\n'
        strnum = strnum + 1
    tmp_string = tmp_string + '</tbody>\n</table>\n'
    
    file_output.write(tmp_string)
        
def HTML_OutputTail():
    """Output end of document.
    
    Outputs static text.
    """
    file_output.write('</body>\n</html>\n')
    
def HTML_Output(dict_packages, old, new, list_dict_old, list_dict_new):
    """Output HTML file.
    
    Generates HTML file.
    """
    if ifnotquiet:
        print "Creating HTML file."
    HTML_OutputHead()
    HTML_OutputBody(dict_packages, old, new, list_dict_old, list_dict_new)
    HTML_OutputTail()    

def main(args):
    global temp_dir 
    global temp_old
    global temp_new
    global ifnotquiet
    global file_output
    CommandLineArguments = ParseCommandLine()
    temp_dir = tempfile.mkdtemp() + '/'
    head_old = temp_dir + old_dir
    head_new = temp_dir + new_dir
    old = []
    new = []
    for i in range(len(CommandLineArguments.old)):
        old.append(CheckArgs(CommandLineArguments.old[i]))
        temp_old.append(head_old + str(i) + '/')
    for i in range(len(CommandLineArguments.new)):
        new.append(CheckArgs(CommandLineArguments.new[i]))
        temp_new.append(head_new + str(i) + '/')
    ifsizes = CommandLineArguments.size
    ifnotsimple = CommandLineArguments.simple
    output_file = CommandLineArguments.output
    ifnotquiet = CommandLineArguments.quiet
    ifhtml = CommandLineArguments.html
    CheckOutput(output_file)
    if(ifhtml) and (output_file == ''):
        try:
            file_output = open(htmlname, "w")
        except:
            print "Error: Cannot open " + htmlname + " for writing."
            exit_proc(0)
            
    GetFiles(old, temp_old)
    GetFiles(new, temp_new)

    if not ifhtml:
        dict_old = ParsePackage(temp_old)
        dict_new = ParsePackage(temp_new)
        
        (dict_new_packages, dict_del_packages, dict_upd_packages) = CreateDicts(
                dict_old, dict_new)
        
        dict_obsoleted = GenerateDictObsoleted(dict_new)
        if(dict_upd_packages) and (ifnotsimple):
            dict_logfile_diff = GenerateLogfileDiff(dict_upd_packages)
        if not ifnotsimple:
            dict_logfile_diff = {}
    
        ProcessNewPackages(dict_new_packages)
        ProcessDelPackages(dict_del_packages, dict_obsoleted)
        if dict_upd_packages:
            ProcessUpdPackages(dict_upd_packages, dict_logfile_diff,
                               ifsizes, ifnotsimple)
        PrintSummary(dict_new_packages, dict_del_packages, dict_upd_packages)
    else:
        list_dict_old = HTML_ParsePackage(temp_old)
        list_dict_new = HTML_ParsePackage(temp_new)
        dict_old = HTML_UniteOld(list_dict_old)
        dict_packages = HTML_CreateDicts(dict_old, list_dict_new)
        HTML_Output(dict_packages, old, new, list_dict_old, list_dict_new)
        
    exit_proc(0)

if __name__ == "__main__":
    main(sys.argv)
