/*
 * database.c: manage the packages and distribution small database
 *             stored in the config file.
 *
 * See Copyright for the status of this software.
 *
 * $Id: database.c,v 1.23 2000/09/17 14:57:05 veillard Exp $
 */

#include "config.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <dirent.h>
#include <errno.h>
#include <time.h>
#ifdef HAVE_REGEX_H
#include <regex.h>
#else
#ifdef HAVE_RXPOSIX_H
#include <rxposix.h>
#endif
#endif

#include "rpmfind.h"
#include "database.h"
#include "transport.h"
#include "hosts.h"
#include "rdf_api.h"

/*
 * limits
 */
#define MAX_MIRRORS		500
#define MAX_NO_UPGRADE		100
#define MAX_NO_DISTRIB		100
#define MAX_NO_DEPEND		100
#define MAX_DISTRIBUTIONS	1000

/*
 * Type carrying the informations about a distribution.
 */
typedef struct rpmDistrib {
    char *ID;		/* e.g. redhat/5.1/i386 */
    int   rating;	/* rating for this distribution */
    char *name;		/* the distribution name */
    char *origin;	/* the URI for the origin server */
    char *myMirror;	/* the URI for the user's preferred mirror */
    char *sources;	/* the URI for the sources */
    int   allow_sort;	/* allow mirror sorting ? */
    int   nb_mirrors;	/* how many registered mirrors */
    char *mirrors[MAX_MIRRORS];/* the mirrors list */
} rpmDistrib, *rpmDistribPtr;

/*
 * Variables for distributions, packages user's choices...
 */

int   nb_metadata_mirrors = 0;
int   max_metadata_mirrors = 0;
char **metadata_mirrors = NULL;

static int rpmDistribListLen = 0;
static rpmDistribPtr rpmDistribList[MAX_DISTRIBUTIONS];
static int rpmNoDistribListLen = 0;
static char *rpmNoDistribList[MAX_NO_DISTRIB];
#ifdef HAVE_REGEX_H
static regex_t rpmNoDistribPattern[MAX_NO_DISTRIB];
#endif
static int rpmNoUpgradeListLen = 0;
static char *rpmNoUpgradeList[MAX_NO_UPGRADE];
#ifdef HAVE_REGEX_H
static regex_t rpmNoUpgradePattern[MAX_NO_UPGRADE];
#endif
static int rpmNoAutoUpgradeListLen = 0;
static char *rpmNoAutoUpgradeList[MAX_NO_UPGRADE];
#ifdef HAVE_REGEX_H
static regex_t rpmNoAutoUpgradePattern[MAX_NO_UPGRADE];
#endif
static int rpmNoDependListLen = 0;
static char *rpmNoDependList[MAX_NO_DEPEND];
#ifdef HAVE_REGEX_H
static regex_t rpmNoDependPattern[MAX_NO_DEPEND];
#endif

static void rpmFindSortMirrors(rpmDistribPtr distrib);


static const char *rpmDistribName = NULL;
#ifdef HAVE_REGEX_H
static regex_t rpmDistribReg;
#endif

/*
 * Set distrib name
 */
void rpmfindSetDistrib(const char *distrib) {
    rpmDistribName = distrib;

#ifdef HAVE_REGEX_H
#ifdef REG_ICASE
    regcomp(&rpmDistribReg, distrib, REG_ICASE);
#else
    regcomp(&rpmDistribReg, distrib, 0);
#endif
#endif
}

/*
 * Check teh distrib name
 */
int rpmfindCheckDistrib(const char *distrib) {
    regmatch_t pmatch[1];

    if (rpmDistribName == NULL) return(1);
#ifdef HAVE_REGEX_H
#else
    return(!strcmp(distrib, rpmDistribName));
#endif
#ifdef HAVE_REGEX_H
    if (!regexec(&rpmDistribReg, distrib, 1, pmatch, 0))
#elif HAVE_STRSTR
    if (strstr(distrib, rpmDistribName))
#else
#error "Code currently relies on regex(3) or strstr(3) availability ..."
#endif
	return(1);
    return(0);
}

/*
 * Distrib lookup by ID.
 */
static int rpmDistribLookup(const char *ID) {
    int i;

    /*
     * First pass...
     */
    for (i = 0;i < rpmDistribListLen;i++)
        if (!strcmp(ID, rpmDistribList[i]->ID)) return(i);

    /*
     * Add the distrib...
     */
    if (rpmDistribListLen >= MAX_DISTRIBUTIONS) {
	fprintf(stderr, "Increase MAX_DISTRIBUTIONS=%d\n", MAX_DISTRIBUTIONS);
	return(-1);
    }
    i = rpmDistribListLen;
    rpmDistribList[i] = (rpmDistribPtr) malloc(sizeof(rpmDistrib));
    if (rpmDistribList[i] == NULL) {
        fprintf(stderr, "rpmDistribLookup : out of memory\n");
	return(-1);
    }
    rpmDistribList[i]->ID = strdup(ID);
    rpmDistribList[i]->rating = 0;
    rpmDistribList[i]->nb_mirrors = 0;
    rpmDistribList[i]->allow_sort = 1;
    rpmDistribList[i]->origin = NULL;
    rpmDistribList[i]->myMirror = NULL;
    rpmDistribList[i]->sources = NULL;
    rpmDistribList[i]->name = NULL;
    rpmDistribListLen++;
    configNeedUpdate++;

    return(i);
}

/*
 * Fetch a resource. Ensure the loadbalancing in case one of the metadata
 * server is down
 * Returns the new file name or NULL in case of error.
 */
char *rpmFetchResource(const char *name) {
    char *filename = NULL;
    char url[1000];
    int needSwitch = 0;

    snprintf(url, sizeof(url), "%s/resources/%s.rdf", myServer, name);
    url[sizeof(url) - 1] = 0;
    filename = fetchURL(url);

    if (filename == NULL) {
	if (strcmp(myServer, "rpmfind.net")) {
	    snprintf(url, sizeof(url), "%s/resources/%s.rdf",
		     "rpmfind.net", name);
	    url[sizeof(url) - 1] = 0;
	    filename = fetchURL(url);
	    if (filename != NULL) {
		needSwitch = 1;
	    }
	} else {
	    snprintf(url, sizeof(url), "%s/resources/%s.rdf",
		     "fr.rpmfind.net", name);
	    url[sizeof(url) - 1] = 0;
	    filename = fetchURL(url);
	    if (filename != NULL) {
		needSwitch = 1;
	    }
	}
    }

    if (needSwitch) {
	/*
	 * Try to swap to another database.
	 */
	rpmMetadataNextMirror();
    }
    return(filename);
}

/*
 * Distrib lookup by neededPackagePtr
 */
const char *rpmPackageFindDistrib(neededPackagePtr package) {
    int i;
    int len;
    char *subdir;

    if ((package->distribNr >= 0) && (package->distribNr < rpmDistribListLen))
        return(rpmDistribList[package->distribNr]->ID);

    subdir = package->subdir;

    /*
     * Search for the prefix in the list of known Distribs
     */
    for (i = 0;i < rpmDistribListLen;i++) {
	len = strlen(rpmDistribList[i]->ID);
        if (!strncmp(subdir, rpmDistribList[i]->ID, len)) {
	    package->distribNr = i;
	    return(rpmDistribList[i]->ID);
	}
    }

    /*
     * Not found, update the distribs list from the server.
     */
    rpmFetchDistribList();

    /*
     * Try again on the updated list.
     */
    for (i = 0;i < rpmDistribListLen;i++) {
	len = strlen(rpmDistribList[i]->ID);
        if (!strncmp(subdir, rpmDistribList[i]->ID, len)) {
	    package->distribNr = i;
	    return(rpmDistribList[i]->ID);
	}
    }
    /*
     * Strange the remote base reference a distrib which doesn't exist
     * anymore.
     */
    if (rpmfindVerbose)
	printf("Unable to find the distribution owning package %s\n",
	       package->URL);

    return(NULL);
}

/*
 * Set the rating for a given distribution.
 */
void rpmDistribSetRating(const char *ID, const char *rating) {
    int no = rpmDistribLookup(ID);
    int val, res;

    if (no < 0) return;
    res = sscanf(rating, "%d", &val);
    if (res == 1) {
        rpmDistribList[no]->rating = val;
	configNeedUpdate++;
    } else
        fprintf(stderr, "Distrib [%s] invalid rating %s\n", ID, rating);
}

/*
 * Get the rating for a given distribution.
 */
int rpmDistribGetRating(const char *ID) {
    int no;

    if (rpmNoDistribCheck(ID)) return(-1);
    no = rpmDistribLookup(ID);
    if (no < 0) return(0);
    if ((rpmDistribList[no]->name != NULL) &&
	(!rpmfindCheckDistrib(rpmDistribList[no]->name))) return(-1);
    if ((rpmDistribList[no]->name != NULL) &&
        (rpmNoDistribCheck(rpmDistribList[no]->name))) return(-1);
    return(rpmDistribList[no]->rating);
}

/*
 * Set the name for a given distribution.
 */
void rpmDistribSetName(const char *ID, const char *name) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return;
    if (rpmDistribList[no]->name != NULL)
        free(rpmDistribList[no]->name);
    if (name != NULL)
	rpmDistribList[no]->name = strdup(name);
    else
        rpmDistribList[no]->name = NULL;
    configNeedUpdate++;
}

/*
 * Get the name for a given distribution.
 */
const char *rpmDistribGetName(const char *ID) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return(NULL);
    return(rpmDistribList[no]->name);
}

/*
 * Set the origin server for a given distribution.
 */
void rpmDistribSetOrigin(const char *ID, const char *origin) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return;
    if (rpmDistribList[no]->origin != NULL)
        free(rpmDistribList[no]->origin);
    if (origin != NULL)
	rpmDistribList[no]->origin = strdup(origin);
    else
        rpmDistribList[no]->origin = NULL;
    configNeedUpdate++;
}

/*
 * Get the origin server for a given distribution.
 */
const char *rpmDistribGetOrigin(const char *ID) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return(NULL);
    return(rpmDistribList[no]->origin);
}

/*
 * Set the user's preferred server for a given distribution.
 */
void rpmDistribSetMyMirror(const char *ID, const char *myMirror) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return;
    if (rpmDistribList[no]->myMirror != NULL)
        free(rpmDistribList[no]->myMirror);
    if (myMirror != NULL)
	rpmDistribList[no]->myMirror = strdup(myMirror);
    else
        rpmDistribList[no]->myMirror = NULL;
    configNeedUpdate++;
}

/*
 * Get the user's preferred server for a given distribution.
 */
const char *rpmDistribMyMirror(const char *ID) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return(NULL);
    return(rpmDistribList[no]->myMirror);
}

/*
 * Set the sources location for a given distribution.
 */
void rpmDistribSetSources(const char *ID, const char *sources) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return;
    if (rpmDistribList[no]->sources != NULL) {
        free(rpmDistribList[no]->sources);
	configNeedUpdate++;
    } rpmDistribList[no]->sources = strdup(sources);
}

/*
 * Get the sources location for a given distribution.
 */
const char *rpmDistribGetSources(const char *ID) {
    int no = rpmDistribLookup(ID);

    if (no < 0) return(NULL);
    return(rpmDistribList[no]->sources);
}

/*
 * Add a mirror at the end of the list for this distribution.
 */
void rpmDistribAddMirror(const char *ID, const char *mirror) {
    int no = rpmDistribLookup(ID);
    int i;
    rpmDistribPtr dist;

    if (no < 0) return;

    dist = rpmDistribList[no];
    for (i = 0; i < dist->nb_mirrors; i++)
        if (!strcmp(mirror, dist->mirrors[i])) return;
    
    if (dist->nb_mirrors >= MAX_MIRRORS) {
        fprintf(stderr, "Increase MAX_MIRRORS=%d\n", MAX_MIRRORS);
	return;
    }
    dist->mirrors[dist->nb_mirrors] = strdup(mirror);
    if (dist->mirrors[dist->nb_mirrors] != NULL)
        dist->nb_mirrors++;
    configNeedUpdate++;
}

/*
 * A mirror has proven successfull raise it in the list order.
 */
void rpmDistribSelectMirror(const char *ID, const char *mirror) {
    /* TODO !!! */
}

/*
 * Add a pattern to the list of packages to not upgrade.
 */
void rpmNoUpgradeListAdd(const char *pattern) {
    int i;
    if (pattern == NULL) return;

    if (rpmNoUpgradeListLen >= MAX_NO_UPGRADE) {
	fprintf(stderr, "Increase MAX_NO_UPGRADE=%d\n", MAX_NO_UPGRADE);
	return;
    }
    for (i = 0;i < rpmNoUpgradeListLen;i++)
        if (!strcmp(pattern, rpmNoUpgradeList[i])) return;
    rpmNoUpgradeList[rpmNoUpgradeListLen] = strdup(pattern);
#ifdef HAVE_REGEX_H
#ifdef REG_ICASE
    regcomp(&rpmNoUpgradePattern[rpmNoUpgradeListLen], pattern, REG_ICASE);
#else
    regcomp(&rpmNoUpgradePattern[rpmNoUpgradeListLen], pattern, 0);
#endif
#endif
    rpmNoUpgradeListLen++;
    configNeedUpdate++;
}

/*
 * Add a pattern to the list of packages to not upgrade automatically.
 */
void rpmNoAutoUpgradeListAdd(const char *pattern) {
    int i;
    if (pattern == NULL) return;

    if (rpmNoAutoUpgradeListLen >= MAX_NO_UPGRADE) {
	fprintf(stderr, "Increase MAX_NO_UPGRADE=%d\n", MAX_NO_UPGRADE);
	return;
    }
    for (i = 0;i < rpmNoAutoUpgradeListLen;i++)
        if (!strcmp(pattern, rpmNoAutoUpgradeList[i])) return;
    rpmNoAutoUpgradeList[rpmNoAutoUpgradeListLen] = strdup(pattern);
#ifdef HAVE_REGEX_H
#ifdef REG_ICASE
    regcomp(&rpmNoAutoUpgradePattern[rpmNoAutoUpgradeListLen], pattern, REG_ICASE);
#else
    regcomp(&rpmNoAutoUpgradePattern[rpmNoAutoUpgradeListLen], pattern, 0);
#endif
#endif
    rpmNoAutoUpgradeListLen++;
    configNeedUpdate++;
}

/*
 * Add a pattern to the list of packages to not upgrade.
 */
void rpmNoDistribListAdd(const char *pattern) {
    int i;
    if (pattern == NULL) return;

    if (rpmNoDistribListLen >= MAX_NO_UPGRADE) {
	fprintf(stderr, "Increase MAX_NO_DISTRIB=%d\n", MAX_NO_UPGRADE);
	return;
    }
    for (i = 0;i < rpmNoDistribListLen;i++)
        if (!strcmp(pattern, rpmNoDistribList[i])) return;
    rpmNoDistribList[rpmNoDistribListLen] = strdup(pattern);
#ifdef HAVE_REGEX_H
#ifdef REG_ICASE
    regcomp(&rpmNoDistribPattern[rpmNoDistribListLen], pattern, REG_ICASE);
#else
    regcomp(&rpmNoDistribPattern[rpmNoDistribListLen], pattern, 0);
#endif
#endif
    rpmNoDistribListLen++;
}

/*
 * Check a distrib name against the list of distrib to not upgrade from.
 */
int rpmNoDistribCheck(const char *distrib) {
    int i;
    regmatch_t pmatch[1];

    for (i = 0;i < rpmNoDistribListLen;i++) {
#ifdef HAVE_REGEX_H
        if (!regexec(&rpmNoDistribPattern[i], distrib, 1, pmatch, 0))
#elif HAVE_STRSTR
	if (strstr(distrib, rpmNoDistribList[i]))
#else
#error "Code currently relies on regex(3) or strstr(3) availability ..."
#endif
            return(1);
    }
    return(0);
}

/*
 * Check a package name against the list of packages to not upgrade.
 */
int rpmNoUpgradeCheck(const char *package) {
    int i;
    regmatch_t pmatch[1];

    for (i = 0;i < rpmNoUpgradeListLen;i++) {
#ifdef HAVE_REGEX_H
        if (!regexec(&rpmNoUpgradePattern[i], package, 1, pmatch, 0))
#elif HAVE_STRSTR
	if (strstr(package, rpmNoUpgradeList[i]))
#else
#error "Code currently relies on regex(3) or strstr(3) availability ..."
#endif
            return(1);
    }
    return(0);
}

/*
 * Check a package name against the list of packages to not upgrade
 * automatically.
 */
int rpmNoAutoUpgradeCheck(const char *package) {
    int i;
    regmatch_t pmatch[1];

    for (i = 0;i < rpmNoAutoUpgradeListLen;i++) {
#ifdef HAVE_REGEX_H
        if (!regexec(&rpmNoAutoUpgradePattern[i], package, 1, pmatch, 0))
#elif HAVE_STRSTR
	if (strstr(package, rpmNoAutoUpgradeList[i]))
#else
#error "Code currently relies on regex(3) or strstr(3) availability ..."
#endif
            return(1);
    }
    return(0);
}

/*
 * Add a pattern to the list of dependencies to refuse.
 */
void rpmNoDependListAdd(const char *pattern) {
    int i;
    if (pattern == NULL) return;

    if (rpmNoDependListLen >= MAX_NO_DEPEND) {
	fprintf(stderr, "Increase MAX_NO_DEPEND=%d\n", MAX_NO_DEPEND);
	return;
    }
    for (i = 0;i < rpmNoDependListLen;i++)
        if (!strcmp(pattern, rpmNoDependList[i])) return;
    rpmNoDependList[rpmNoDependListLen] = strdup(pattern);
#ifdef HAVE_REGEX_H
#ifdef REG_ICASE
    regcomp(&rpmNoDependPattern[rpmNoDependListLen], pattern, REG_ICASE);
#else
    regcomp(&rpmNoDependPattern[rpmNoDependListLen], pattern, 0);
#endif
#endif
    rpmNoDependListLen++;
}

/*
 * Check a dependency name against the list of dependencies to refuse.
 */
int rpmNoDependCheck(const char *dep) {
    int i;
    regmatch_t pmatch[1];

    for (i = 0;i < rpmNoDependListLen;i++) {
#ifdef HAVE_REGEX_H
        if (!regexec(&rpmNoDependPattern[i], dep, 1, pmatch, 0))
#elif HAVE_STRSTR
	if (strstr(dep, rpmNoDependList[i]))
#else
#error "Code currently relies on regex(3) or strstr(3) availability ..."
#endif
            return(1);
    }
    return(0);
}

/*
 * Reload the Distribution list from the resources server.
 */
void rpmFetchDistribList(void) {
    rdfSchema rdf;
    rdfNamespace rpmNs;
    rdfNamespace rdfNs;
    rdfDescription desc;
    char *file;
    char URL[2000];
    char *origin;
    char *ID;
    
    /*
     * Get the list of metadata servers first.
     */
    rpmFetchMetadataList();

    if (rpmfindVerbose)
	printf("Updating the distribution list\n");

    /*
     * For cross pollinization, merge with the rpmfind.net database
     */
    snprintf(URL, sizeof(URL),
	    "http://rpmfind.net/linux/RDF/resources/distribs/list.rdf");
    file = fetchURL(URL);

    if (file == NULL) {
	goto fetch_local;
    }

    /*
     * Parse the RDF file, and update the distribution database.
     */
    rdf = rdfRead(file);
    if (rdf == NULL) goto fetch_local;

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    if (rdfNs == NULL) {
	rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
	if (rdfNs == NULL) {
	    if (rpmfindVerbose)
		printf("%s is not an RDF schema\n", file);
	    rdfDestroySchema(rdf);
	    goto fetch_local;
	}
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", file);
	rdfDestroySchema(rdf);
	goto fetch_local;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", file);
	rdfDestroySchema(rdf);
	goto fetch_local;
    }

    /*
     * We are pretty sure that it will be a valid schema,
     * Walk the tree and collect the distribution descriptions.
     */
    while (desc != NULL) {
        /*
	 * Get the resource URL ...
	 */
	origin = rdfGetDescriptionAbout(rdf, desc);
	if (origin == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : Desc without href\n",
		        file);
	    rdfDestroySchema(rdf);
	    goto fetch_local;
	}

	/*
	 * Now extract all the metadata informations from the RDF tree
	 */
	rdfGetValue(desc, "ID", rpmNs, &ID, NULL);
	if (ID == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no ID\n", file);
	    rdfDestroySchema(rdf);
	    goto fetch_local;
	}
        /*
	 * Update the distrib infos.
	 */
	rpmDistribSetOrigin(ID, origin);

	free(origin);
	free(ID);
        /*
	 * Skip to next Description in the RDf file.
	 */
	desc = rdfNextDescription(desc); 
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);

fetch_local:
    /*
     * Load the distrib datatbase from the server.
     */
    snprintf(URL, sizeof(URL), "%s/resources/distribs/list.rdf", myServer);
    file = fetchURL(URL);

    if (file == NULL) {
        fprintf(stderr, "rpmFetchDistribList: failed to grab distrib list\n");
        fprintf(stderr, "   URI: %s\n", URL);
	removeMetadataMirror(myServer);
	rpmMetadataNextMirror();
	return;
    }

    /*
     * Parse the RDF file, and update the distribution database.
     */
    rdf = rdfRead(file);
    if (rdf == NULL) return;

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    if (rdfNs == NULL) {
	rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
	if (rdfNs == NULL) {
	    if (rpmfindVerbose)
		printf("%s is not an RDF schema\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", file);
	rdfDestroySchema(rdf);
	return;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", file);
	rdfDestroySchema(rdf);
	return;
    }

    /*
     * We are pretty sure that it will be a valid schema,
     * Walk the tree and collect the distribution descriptions.
     */
    while (desc != NULL) {
        /*
	 * Get the resource URL ...
	 */
	origin = rdfGetDescriptionAbout(rdf, desc);
	if (origin == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : Desc without href\n",
		        file);
	    rdfDestroySchema(rdf);
	    return;
	}

	/*
	 * Now extract all the metadata informations from the RDF tree
	 */
	rdfGetValue(desc, "ID", rpmNs, &ID, NULL);
	if (ID == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no ID\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
        /*
	 * Update the distrib infos.
	 */
	rpmDistribSetOrigin(ID, origin);

	free(origin);
	free(ID);
        /*
	 * Skip to next Description in the RDf file.
	 */
	desc = rdfNextDescription(desc); 
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);
}

/*
 * Reload the informations about a distribution from the resources server.
 */
void rpmFetchDistribInfo(const char *name, int alternate) {
    rpmDistribPtr dist;
    rdfSchema rdf;
    rdfNamespace rpmNs;
    rdfNamespace rdfNs;
    rdfDescription desc;
    rdfBag mirrors;
    rdfElement mirror;
    char *file, *ptr;
    char URL[2000];
    char buf[100];
    char *value;
    int index;
    
    if (rpmfindMirrorUpgrade == 0) return;
    if (rpmfindUseOrigin != 0) return;
    if (rpmfindVerbose)
	printf("Updating the distribution info for [%s] using alternate %d\n",
	       name, alternate);

    /*
     * Load the distrib datatbase from the server.
     */
    snprintf(buf, sizeof(buf), "%s", name);
    for (ptr = &buf[0]; *ptr; ptr++) if (*ptr == '/') *ptr = '_';
    if (alternate) {
        /*
	 * Grab the distribution informations from alternate metadata
	 * server #alternate
	 */
	if ((nb_metadata_mirrors > alternate) &&
	    (metadata_mirrors[alternate] != NULL))
	    snprintf(URL, sizeof(URL), "%s/resources/distribs/%s.rdf",
	            metadata_mirrors[alternate], buf);
	else
	    snprintf(URL, sizeof(URL), "%s/resources/distribs/%s.rdf", myServer, buf);
    } else {
	snprintf(URL, sizeof(URL), "%s/resources/distribs/%s.rdf", myServer, buf);
    }
    file = fetchURL(URL);

    if (file == NULL) {
        fprintf(stderr, "rpmFetchDistribList: failed to grab distrib list\n");
	return;
    }

    /*
     * Parse the RDF file, and update the distribution database.
     */
    rdf = rdfRead(file);
    if (rdf == NULL) return;

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    if (rdfNs == NULL) {
	rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
	if (rdfNs == NULL) {
	    if (rpmfindVerbose)
		printf("%s is not an RDF schema\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", file);
	rdfDestroySchema(rdf);
	return;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", file);
	rdfDestroySchema(rdf);
	return;
    }

    /*
     * Now extract all the metadata informations from the RDF tree
     */
    rdfGetValue(desc, "ID", rpmNs, &value, NULL);
    if (value == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema invalid : no ID\n", file);
	rdfDestroySchema(rdf);
	return;
    }
    if (strcmp(name, value)) {
	if (rpmfindVerbose)
	    printf(
"rpmFetchDistribInfo : [%s] ID [%s] doesn't match distrib requested\n",
		   name, value);
        free(value);
	rdfDestroySchema(rdf);
	return;
    }
    free(value);

    /*
     * Update the distrib infos.
     */
    rdfGetValue(desc, "Name", rpmNs, &value, NULL);
    if (value != NULL) {
	rpmDistribSetName(name, value);
	free(value);
    }
    rdfGetValue(desc, "Origin", rpmNs, &value, NULL);
    if (value != NULL) {
	rpmDistribSetOrigin(name, value);
	free(value);
    }
    rdfGetValue(desc, "Sources", rpmNs, &value, NULL);
    if (value != NULL) {
	rpmDistribSetSources(name, value);
	free(value);
    }

    rdfGetValue(desc, "Mirrors", rpmNs, NULL, &mirrors);
    if (mirrors != NULL) {
        const char *mirrorname;

	mirror = rdfFirstChild(mirrors);
	while (mirror != NULL) {
	    /*
	     * Check that we are scanning an Mirror Resource.
	     */
	    mirrorname = rdfElemGetPropertyName(mirror);
	    if ((mirrorname != NULL) &&
	        (!strcmp(mirrorname, "Mirror")) &&
		(rdfElemGetNamespace(mirror) == rpmNs)) {
		value = rdfGetElementResource(rdf, mirror);
                rpmDistribAddMirror(name, value);
		if (value != NULL) free(value);
	    } else if (rpmfindVerbose > 1) {
		fprintf(stderr, "%s : malformed Mirrors bag !\n", file);
	    }
	    mirror = rdfNextElem(mirror);
	}
    } else if (rpmfindVerbose) {
	fprintf(stderr, "%s doesn't export any mirror\n", file);
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);

    /*
     * Update the Mirrors list order.
     */
    index = rpmDistribLookup(name);
    if (index < 0) return;
    dist = rpmDistribList[index];
    rpmFindSortMirrors(dist);
}

/*
 * Append the database to a config file.
 */
void rpmFindWriteDatabase(FILE *output) {
    int i, j;

    fprintf(output, "\n;\n; Packages rejection criteria\n");
    fprintf(output, "; those are regexp\n");
    fprintf(output, "; You can add multiple of each\n");
    fprintf(output, "; no_upgrade=xyz     : do not upgrade package xyz\n");
    fprintf(output, "; no_autoupgrade=xyz : do not auto upgrade package xyz\n");
    fprintf(output, "; no_depend=xyz      : no package depending on xyz\n");
    fprintf(output, "; no_distrib=xyz     : no package from distrib xyz\n\n");
    fprintf(output, "[packages]\n");
    for (i = 0;i < rpmNoUpgradeListLen;i++)
        fprintf(output, "no_upgrade=%s\n", rpmNoUpgradeList[i]);
    for (i = 0;i < rpmNoAutoUpgradeListLen;i++)
        fprintf(output, "no_autoupgrade=%s\n", rpmNoAutoUpgradeList[i]);
    for (i = 0;i < rpmNoDependListLen;i++)
        fprintf(output, "no_depend=%s\n", rpmNoDependList[i]);
    for (i = 0;i < rpmNoDistribListLen;i++)
        fprintf(output, "no_distrib=%s\n", rpmNoDistribList[i]);
    fprintf(output, "\n");

    if (nb_metadata_mirrors > 0) {
	fprintf(output, "\n;\n; Metadata databases\n;\n");
	fprintf(output, "[metadata]\n");
	for (i = 0;i < nb_metadata_mirrors;i++)
	    fprintf(output, "mirror=%s\n", metadata_mirrors[i]);
    }

    fprintf(output, "\n;\n; Distributions rating and informations\n;\n");
    fprintf(output, "; Rating: 0 normal (default) <0 disable >0 preference\n");
    fprintf(output, ";    a preference of 1000 is rather large\n;\n");
    fprintf(output, "; To define a prefered mirror per distribution add\n");
    fprintf(output, "; myMirror=ftp://ftp.mymirror.org/pub/distrib\n;\n");
    fprintf(output, "; If it's not in the default list, please send it to\n");
    fprintf(output, "; Daniel.Veillard@w3.org so I can add it, thanks.\n");
    fprintf(output, "; \n");
    for (i = 0;i < rpmDistribListLen;i++) {
	/*
	 * Do not dump "banned" distribs
	 */
        if ((rpmDistribList[i]->ID != NULL) &&
	    (rpmNoDistribCheck(rpmDistribList[i]->ID))) continue;
        if ((rpmDistribList[i]->name != NULL) &&
	    (rpmNoDistribCheck(rpmDistribList[i]->name))) continue;

        fprintf(output, "[%s]\n", rpmDistribList[i]->ID);
	if (rpmDistribList[i]->name != NULL)
	    fprintf(output, "name=%s\n", rpmDistribList[i]->name);
	fprintf(output, "rating=%d\n", rpmDistribList[i]->rating);
	if (rpmDistribList[i]->origin != NULL)
	    fprintf(output, "origin=%s\n", rpmDistribList[i]->origin);
	if (rpmDistribList[i]->myMirror != NULL)
	    fprintf(output, "myMirror=%s\n", rpmDistribList[i]->myMirror);
	if (rpmfindUseOrigin == 0) {
	    for (j = 0;j < rpmDistribList[i]->nb_mirrors;j++) {
		fprintf(output, "mirror=%s\n", rpmDistribList[i]->mirrors[j]);
	    }
	}
	fprintf(output, "\n");
    }
}

/*
 * Sort the mirror list.
 */
static void rpmFindSortMirrors(rpmDistribPtr distrib) {
    int i, j, tmp; 
    char *ptr;
    int mirrorsValues[MAX_MIRRORS];

    if (!distrib->allow_sort) return;
    for (i = 0;i < distrib->nb_mirrors;i++)
        mirrorsValues[i] = rpmFindUrlDistance(distrib->mirrors[i]);
    for (i = 0;i < distrib->nb_mirrors;i++)
        for (j = i + 1;j < distrib->nb_mirrors;j++)
	    if (mirrorsValues[j] > mirrorsValues[i]) {
	        tmp = mirrorsValues[j]; ptr = distrib->mirrors[j];
		mirrorsValues[j] = mirrorsValues[i];
		distrib->mirrors[j] = distrib->mirrors[i];
		mirrorsValues[i] = tmp; distrib->mirrors[i] = ptr;
	    }
    configNeedUpdate++;
}

/*
 * Get the next mirror for a package and update the URL accordingly.
 */
int rpmFindGetNextMirror(neededPackagePtr package) {
    int distrib;
    rpmDistribPtr dist;
    char newMirror[2000];
    static int rollback;

    if ((package->distribNr >= 0) && (package->distribNr < rpmDistribListLen)) {
        distrib = package->distribNr;
    } else {
        const char *ID;
	
	ID = rpmPackageFindDistrib(package);
	if (ID == NULL) return(-1);
        distrib = rpmDistribLookup(ID);
	if (distrib < 0) return(-1);
    }
    dist = rpmDistribList[distrib];

    /*
     * Compute the subdir for the package within the distribution.
     * the subdir field in the package gives the full "virtual" path
     * the ID field of the distribution gives the base "virtual" path
     * e.g.: "suse/5.2/snd1" and "suse/5.2" => dir="/snd1"
     */
    if (package->dir == NULL) {
        char *ptr = package->subdir;
	char *ptr2 = dist->ID;

        while ((*ptr) && (*ptr == *ptr2)) {
	    ptr++;
	    ptr2++;
	}
	if (*ptr2 != 0) {
	    fprintf(stderr,
	            "Incoherency : pkg->subdir = %s and dist->ID = %s\n",
	            package->subdir, dist->ID);
            package->dir = strdup("");
	} else
	    package->dir = strdup(ptr);
    }

    /* If use only the origin servers for paranoiacs */
    if (rpmfindUseOrigin != 0) {
	if (package->filename == NULL)
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s-%s-%s.%s.rpm",
		    dist->origin, package->dir, package->name,
		    package->version, package->release, package->arch);
	else
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s",
		    dist->origin, package->dir, package->filename);
	if (package->URL != NULL) free(package->URL);
	package->URL = strdup(newMirror);
	return(0);
    }
    /* If defined myMirror is the primary choice */
    if ((package->mirror == -1) && (dist->myMirror != NULL)) {
	if (package->filename == NULL)
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s-%s-%s.%s.rpm",
		    dist->myMirror, package->dir, package->name,
		    package->version, package->release, package->arch);
	else
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s",
		    dist->myMirror, package->dir, package->filename);
	if (package->URL != NULL) free(package->URL);
	package->URL = strdup(newMirror);
	package->mirror = 0;
	return(0);
    } else if (package->mirror == -1)
        package->mirror = 0;

    if (package->mirror >= dist->nb_mirrors ) {
        /*
	 * Refetch the mirrors list for this distribution.
	 */
	rpmFetchDistribInfo(dist->ID, rollback++);
	/* rpmSortDistribInfo(dist); */
	if (dist->myMirror != NULL)
	    package->mirror = -1;
	else
	    package->mirror = 0;
    }

    if ((package->mirror == -1) && (dist->myMirror != NULL)) {
	if (package->filename == NULL)
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s-%s-%s.%s.rpm",
		    dist->myMirror, package->dir, package->name,
		    package->version, package->release, package->arch);
	else
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s",
		    dist->myMirror, package->dir, package->filename);
	if (package->URL != NULL) free(package->URL);
	package->URL = strdup(newMirror);
	package->mirror = 0;
	return(0);
    } else if (package->mirror == -1)
        package->mirror = 0;

    if (package->mirror < dist->nb_mirrors ) {
	/*
	 * We could try to get the actual package name from package->URL
	 * but it's simpler now to rebuild it !!!!
	 */
	if (package->filename == NULL)
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s-%s-%s.%s.rpm",
		    dist->mirrors[package->mirror], package->dir, package->name,
		    package->version, package->release, package->arch);
	else
	    snprintf(newMirror, sizeof(newMirror), "%s%s/%s",
		    dist->mirrors[package->mirror], package->dir,
		    package->filename);
	if (package->URL != NULL) free(package->URL);
	package->URL = strdup(newMirror);
	package->mirror++;
	return(0);
    }
    return(-1);
}

/*
 * Add an RDF database mirror to the list.
 */

void rpmMetadataAddMirror(const char *mir) {
    int i;
    char *mirror = strdup(mir);
    

    if (mirror[strlen(mirror) - 1] == '/') {
        mirror[strlen(mirror) - 1] = 0; /* avoid the trailing / */
    }

    if ((metadata_mirrors == NULL) || (max_metadata_mirrors == 0)) {
	max_metadata_mirrors = 10;
	nb_metadata_mirrors = 0;
	metadata_mirrors = (char **)
		  malloc(max_metadata_mirrors * sizeof(char *));
	if (metadata_mirrors == NULL) {
	    fprintf(stderr, "rpmMetadataAddMirror : ran out of memory!\n");
	    exit(1);
	}
    }

    for (i = 0;i < nb_metadata_mirrors;i++)
        if (!strcmp(mirror, metadata_mirrors[i])) {
	    free(mirror);
	    return;
	}

    if (nb_metadata_mirrors >= max_metadata_mirrors) {
	max_metadata_mirrors *= 2;
	metadata_mirrors = (char **) realloc(metadata_mirrors,
			       max_metadata_mirrors * sizeof(char *));
	if (metadata_mirrors == NULL) {
	    fprintf(stderr, "rpmMetadataAddMirror : ran out of memory!\n");
	    exit(1);
	}
    }
    metadata_mirrors[nb_metadata_mirrors++] = strdup(mirror);
    configNeedUpdate++;
    free(mirror);
}

/*
 * Get the next RDF database mirror.
 */
const char *rpmMetadataNextMirror(void) {
    int i;

    /*
     * Add the current server to the list if not already in.
     * Find it on the list and get the next one.
     */
    rpmMetadataAddMirror(myServer);

    for (i = 0;i < nb_metadata_mirrors;i++)
        if (!strcmp(metadata_mirrors[i], myServer)) break;

    i++;
    if (i >= nb_metadata_mirrors) i = 0;
    myServer = metadata_mirrors[i];
    return(myServer);
}

/*
 * Removes an RDF database mirror
 */
void removeMetadataMirror(const char *server) {
    int i;

    if (server == NULL)
	return;

    /*
     * if the server is the current one use rpmfind in the
     * meantime.
     */
    if (!strcmp(server, myServer)) {
	free(myServer);
	myServer = strdup("http://rpmfind.net/linux/RDF");
    }

    for (i = 0;i < nb_metadata_mirrors;i++)
        if (!strcmp(metadata_mirrors[i], server)) {
	    free(metadata_mirrors[i]);
	    metadata_mirrors[i] = strdup("http://rpmfind.net/linux/RDF");
	}
}

/*
 * Sort the metadata servers list.
 */
static void rpmFindSortMetadata() {
    int i, j, tmp; 
    char *ptr;
    int *mirrorsValues;

    mirrorsValues = (int *) malloc(nb_metadata_mirrors * sizeof(int));
    if (mirrorsValues == NULL) {
        fprintf(stderr, "rpmFindSortMetadata: malloc() failed\n");
	return;
    }

    for (i = 0;i < nb_metadata_mirrors;i++)
        mirrorsValues[i] = rpmFindUrlDistance(metadata_mirrors[i]);
    for (i = 0;i < nb_metadata_mirrors;i++)
        for (j = i + 1;j < nb_metadata_mirrors;j++)
	    if (mirrorsValues[j] > mirrorsValues[i]) {
	        tmp = mirrorsValues[j]; ptr = metadata_mirrors[j];
		mirrorsValues[j] = mirrorsValues[i];
		metadata_mirrors[j] = metadata_mirrors[i];
		mirrorsValues[i] = tmp; metadata_mirrors[i] = ptr;
	    }
    if (metadata_mirrors[0] != NULL)
	myServer = strdup(metadata_mirrors[0]);
    configNeedUpdate++;
    free(mirrorsValues);
}

/*
 * Reload the Metadata databases list from the resources server.
 */
void rpmFetchMetadataList(void) {
    rdfSchema rdf;
    rdfNamespace rpmNs;
    rdfNamespace rdfNs;
    rdfDescription desc;
    char *file;
    char URL[2000];
    char *origin;
    char *URI;
    
    if (rpmfindVerbose)
	printf("Updating the metadata server list\n");

    /*
     * Cross pollinize with metadata from rpmfind
     */
    snprintf(URL, sizeof(URL), 
	    "http://rpmfind.net/linux/RDF/resources/distribs/metadata.rdf");
    file = fetchURL(URL);

    if (file == NULL) {
	goto fetch_local;
    }

    /*
     * Parse the RDF file, and update the distribution database.
     */
    rdf = rdfRead(file);
    if (rdf == NULL) goto fetch_local;

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    if (rdfNs == NULL) {
	rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
	if (rdfNs == NULL) {
	    if (rpmfindVerbose)
		printf("%s is not an RDF schema\n", file);
	    rdfDestroySchema(rdf);
	    goto fetch_local;
	}
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", file);
	rdfDestroySchema(rdf);
	goto fetch_local;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", file);
	rdfDestroySchema(rdf);
	goto fetch_local;
    }

    /*
     * We are pretty sure that it will be a valid schema,
     * Walk the tree and collect the distribution descriptions.
     */
    while (desc != NULL) {
        /*
	 * Get the resource URL ...
	 */
	origin = rdfGetDescriptionAbout(rdf, desc);
	if (origin == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : Desc without href\n",
		        file);
	    rdfDestroySchema(rdf);
	    goto fetch_local;
	}

	/*
	 * Now extract all the metadata informations from the RDF tree
	 */
	rdfGetValue(desc, "URI", rpmNs, &URI, NULL);
	if (URI == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no URI\n", file);
	    rdfDestroySchema(rdf);
	    goto fetch_local;
	}

        /*
	 * Update the distrib infos.
	 */
	rpmMetadataAddMirror(URI);
	free(origin);
	free(URI);

        /*
	 * Skip to next Description in the RDF file.
	 */
	desc = rdfNextDescription(desc); 
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);

fetch_local:
    /*
     * Load the distrib datatbase from the local server.
     */
    snprintf(URL, sizeof(URL), "%s/resources/distribs/metadata.rdf", myServer);
    file = fetchURL(URL);

    if (file == NULL) {
        fprintf(stderr,
	    "rpmFetchDistribList: failed to grab metadata server list\n");
        fprintf(stderr, "   URI: %s\n", URL);
	rpmMetadataNextMirror();
	return;
    }

    /*
     * Parse the RDF file, and update the distribution database.
     */
    rdf = rdfRead(file);
    if (rdf == NULL) return;

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    if (rdfNs == NULL) {
	rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
	if (rdfNs == NULL) {
	    if (rpmfindVerbose)
		printf("%s is not an RDF schema\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", file);
	rdfDestroySchema(rdf);
	return;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", file);
	rdfDestroySchema(rdf);
	return;
    }

    /*
     * We are pretty sure that it will be a valid schema,
     * Walk the tree and collect the distribution descriptions.
     */
    while (desc != NULL) {
        /*
	 * Get the resource URL ...
	 */
	origin = rdfGetDescriptionAbout(rdf, desc);
	if (origin == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : Desc without href\n",
		        file);
	    rdfDestroySchema(rdf);
	    return;
	}

	/*
	 * Now extract all the metadata informations from the RDF tree
	 */
	rdfGetValue(desc, "URI", rpmNs, &URI, NULL);
	if (URI == NULL) {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no URI\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}

        /*
	 * Update the distrib infos.
	 */
	rpmMetadataAddMirror(URI);
	free(origin);
	free(URI);

        /*
	 * Skip to next Description in the RDF file.
	 */
	desc = rdfNextDescription(desc); 
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);

    /*
     * Reorder the list based on the proximity.
     */
    rpmFindSortMetadata();
}

