/*
 *      net.c
 *      
 *      Copyright 2008 Giorgio "Dani" G. <dani@slacky.it>
 *      
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, 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
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

#include "slackyd.h"

/* read (and ignore) http header received from socket...
 * Return -1 if find 404 not found, else return size of object requested. */
int
read_http_header (int sd)
{
    char buffer[BUFFSIZE0] = { 0 }, *ptr;
    u_short i = 0, cnt = 0;

    /* receive data while occured "\r\n\r\n" */
    while (cnt != 4) {
	
	if (recv(sd, &buffer[i], 1, 0) <= 0)
	break;

	if (buffer[i] == '\r' || buffer[i] == '\n')
	 cnt++;
	else
	 cnt = 0;

	i++;
    }

    if (strstr(buffer, "404 Not Found"))
	return EXIT_FAILURE;

    if ((ptr = strstr(buffer, "Content-Length: ")))
	return atoi(ptr + strlen("Content-Length: "));

    return EXIT_SUCCESS;
}

/* If timeout is minor or equal to 0 use traditional connect()
 * over blocking socket, else set `sockfd' in non-blocking mode 
 * and try to connect socket.
 * If connect success return EXIT_SUCCESS.
 * If timeout finished return EXIT_FAILURE.
 * If unexpexted error occured, return -2.
 */
int
N_BLOCK_connect (int sockfd, void *serv_addr, socklen_t addrlen, u_short timeout)
{
    int rval = 0, u_sec = 0;
    const unsigned short pause = 2;

    if (timeout <= 0) return connect (sockfd, serv_addr, addrlen);

    slassert (fcntl(sockfd, F_SETFL, O_NONBLOCK) >= 0);
    
    while (1)
    {
    	rval = connect (sockfd, serv_addr, addrlen);
    	
    	if (!rval)
    	{
    		break;		/* success */
		}
		else
		if (rval < 0 && errno != EINPROGRESS && errno != EALREADY)
		{
			return -2;
		}
		else
		if ((u_sec / DEC_SEC (10)) >= timeout) /* timeout is in second */
		{
			return EXIT_FAILURE;
		}
		
		usleep (DEC_SEC (pause));
		u_sec += DEC_SEC (pause);
    }

    slassert (fcntl (sockfd, F_SETFL, 0) >= 0);

    return EXIT_SUCCESS;
}

/* receive data from socket `sd' and save it in `data'.
 * Timeout for receive data will be set to `sec' and `u_sec'.
 * 
 * If `line' is true and received data are "\r\n" terminated, return.
 */
int
N_BLOCK_recv (int sd, char **data, int sec, int u_sec, bool line)
{
    fd_set read_set;
    struct timeval timeout;
    int rval, received = 0, i, n;
    char buffer[BUFFSIZE] = { 0 };

    while (1) {
	FD_ZERO(&read_set);
	FD_SET(sd, &read_set);
	timeout.tv_sec = sec;
	timeout.tv_usec = u_sec;

	rval = select (sd + 1, &read_set, NULL, NULL, &timeout);

	if (rval < 0 || (!rval && !received))
	 return EXIT_FAILURE;
	else if (!rval && received)
	 break;
	else {
	    memset (buffer, 0, BUFFSIZE);

	    rval = recv(sd, buffer, BUFFSIZE, 0);
	    if (rval <= 0)
		return EXIT_FAILURE;

	    received += rval;

	    *data = xrealloc (*data, (received + 1) * sizeof (char));
	    /* copy new data to previous, if exist. */
	    for (n = 0, i = received - rval; i < received; i++)
		(*data)[i] = buffer[n++];

	    (*data)[received] = 0;

	    if (line == true && (*data)[received - 2] == '\r' && (*data)[received - 1] == '\n')
		return EXIT_SUCCESS;
	}
    }

    return EXIT_SUCCESS;
}

/* get_file_over{http,ftp}:
 +----------------------------------------------------------------------------------- 
 *  Print to stdout `msg'. Require to `hostname' file `request'. Save data in `dest'.
 * If `overwrite' is true and `dest' already exist, overwrite it, else return.
 * If `part' is true save data in `dest'.part and only at download finish rename `dest'.part to `dest'.
 * If `timestamp' is true and `dest' already exist, verify firts 1024 bytes of 
 * old `dest' and of new `dest'. If are equal stop download, remove `dest'.part and return.
 +-----------------------------------------------------------------------------------
 */

int
get_file_over_http
(const char *msg, const char *hostname, const char *request, const char *dest,
 bool overwrite, bool part, bool timestamp)
{

    struct hostent *phostent;
    struct sockaddr_in client;
    const u_short port = 80, stamp_size = 50;
    char buffer[HUGE0], stamp[BUFFSIZE0];
    char *part_path = NULL;
    int i, sd, rval;
    long received = 0, size;
    time_t savetime, difftime[2];
    unsigned savedspeed;
    FILE *fd;

    memset (buffer, 0, HUGE0);
    memset (stamp , 0, BUFFSIZE0);

    fprintf (stdout, "%s ", msg);
    fflush (stdout);

    /* sanity check:
     * If `part' is false, `overwite' is true and if `dest' was already download, we overwrite old file; else, if `part'
     * is true, we BEFORE save data in `dest'.part and AFTER, when download has been finished, replace old file.
     * 
     * If `timestamp' is true, we verify first `stamp_size' bytes of `dest' and of `dest'.part. If are identical
     * quitting and remove `dest'.part WITHOUT overwrite old file ! 
     * 
     * Then if `part' is false, timestamp CANNOT be true :-)
     */
    if (!part) slassert (!timestamp);
        
    
    if (!overwrite && (fd = fopen (dest, "rb"))) {
		clrline (stdout, strlen(msg) + 30);
		fprintf (stdout, "\r%s [Already downloaded]\n", msg);
		fclose (fd);
		return EXIT_FAILURE;
    }

    /* if `timestamp' is true and `dest' already exist, load first `stamp_size' bytes. */
    if (timestamp && (fd = fopen (dest, "rb")) != NULL) {
		i = fread (stamp, 1, stamp_size, fd);
		fclose (fd);
		/* if file is too short force skip of timestamp check */
		if (i < stamp_size)
	     timestamp = false;
    }

    /* resolve hosname */
    if ((phostent = gethostbyname(hostname)) == NULL) {
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [cannot resolve %s]\n", msg, hostname);
		return EXIT_FAILURE;
    }

    client.sin_family = phostent->h_addrtype;
    client.sin_port = htons((u_short) port);
    memcpy(&client.sin_addr.s_addr, phostent->h_addr_list[0], phostent->h_length);

    /* create a socket */
    sd = socket(client.sin_family, SOCK_STREAM, 0);
    if (sd < 0) {
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error, cannot create socket: %s]\n", msg, strerror(errno));
		return EXIT_FAILURE;
    }

    /* connect to host */
    rval = N_BLOCK_connect(sd, (struct sockaddr *)&client, sizeof client, opt.TIMEOUT);
    if (rval == -2) {
		close (sd);
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error: %s]\n", msg, strerror(errno));
		return EXIT_FAILURE;
    }
    else if (rval == EXIT_FAILURE) {
		close (sd);
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error: connection timeout]\n", msg);
		return EXIT_FAILURE;
    }

    /* create http request */
    snprintf (buffer, HUGE, HTTP_TEMPLATE, request, hostname);
    /* send request */
    rval = (unsigned) send (sd, buffer, strlen(buffer), 0);
    if ((unsigned) rval != strlen(buffer)) {
		close (sd);
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error, cannot send HTTP request: %s]\n", msg, strerror(errno));
		return EXIT_FAILURE;
    }

    /* receive http header and save to `size' length of file required */
    size = read_http_header(sd);
    if (size < 0) {
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [File Not Found]\n", msg);
		return EXIT_FAILURE;
    }

    /* if `part' is true save data in `dest'.part, else save directly in `dest'. */
    if (part) {
		part_path = xcalloc (strlen(dest) + 10, sizeof (char));
		snprintf (part_path, BUFFSIZE, "%s.part", dest);
		
		fd = fopen (part_path, "wb");
		if (!fd) {
	    	close (sd);
	    	clrline (stderr, strlen(msg) + 30);
	    	fprintf (stderr, "\r%s [error, cannot write %s: %s ]\n", msg, part_path, strerror(errno));
	    	free (part_path);
	   		return EXIT_FAILURE;
		}
    }
    else {
		fd = fopen (dest, "wb");
		if (!fd) {
	    	close (sd);
	    	clrline (stderr, strlen(msg) + 30);
	    	fprintf (stderr, "\r%s [error, cannot write %s: %s ]\n", msg, dest, strerror(errno));
	    	return EXIT_FAILURE;
		}
    }

    /* if `timestamp' is true receive firts bytes and compare with old file */
    if (timestamp) {
		rval = recv(sd, buffer, stamp_size, 0);
		if (rval <= 0) {
	    	remove (part_path);
	    	free (part_path);
	    	fclose (fd);
	    	close (sd);
	    	clrline (stderr, strlen(msg) + 30);
	    	fprintf (stderr, "\r%s [error: %s]\n", msg, strerror(errno));
	    	return EXIT_FAILURE;
		}

		if (!memcmp (buffer, stamp, stamp_size)) {
	    	remove (part_path);
	    	free (part_path);
	    	fclose (fd);
	    	close (sd);
	    	clrline (stderr, strlen(msg) + 30);
	    	fprintf (stdout, "\r%s [Already update]\n", msg);
	    	return FILE_ALREADY_UPDATE;
    }
	else {
	    fwrite (buffer, 1, rval, fd);
	    received += (u_long) rval;
		}
    }

    clrline (stdout, strlen(msg) + 30);

    /* receive all (remaining?) data... */
    memset (buffer, 0, HUGE);

    savetime    = time (NULL);
    difftime[0] = 1;
    savedspeed  = 0;
    while ((rval = recv (sd, buffer, HUGE, 0)) > 0) {

		received += rval;
		fwrite (buffer, 1, rval, fd);
		
		if (size > HUGE) {
		    fprintf (stdout, "\r%s [%u %% ", msg, PERCENTAGE(KBytes(received), KBytes(size)));
            difftime[1] = time (NULL) - savetime; /* total time elapsed since start download */
            /* we update download speed only if is spent one second or more
             * since last time calculation
             */
            if (difftime[1] > difftime[0]) { 
                savedspeed = NETSPEED  (received, difftime[1]);
                difftime[0] = difftime[1];
            }

            fprintf (stdout, "@ %u KB/s]", KBytes (savedspeed));
            
		}
	    else {
	        /* if required file is smaller of HUGE bytes we print only this: */
	        fprintf (stdout, "\r%s [Downloading %ld bytes]", msg, size);
        }

	    fflush (stdout);
	}
    
    clrline (stderr, strlen(msg) + 30);

    fprintf (stdout, "\r%s [100 %% @ %u KB/s]", msg,
    (savedspeed > 0) ? KBytes (savedspeed) : KBytes (received));

    fflush (stdout);

    close (sd);
    fclose (fd);

    if (part) {
		rename(part_path, dest);
		free (part_path);
    }

    putchar('\n');

    return FILE_DOWNLOADED;

}

/* send to socket `sd' ftp command `cmd'. Read data from server and check for `reply'.
 * If `data' isn't NULL, alloc BUFFSIZE bytes and store ftp reply here.
 * If `reply' is NULL only send command and return.
 */
static bool
ftp_act
(const char *msg, int sd, const char *cmd,
 const char *reply, char **data)
{
    
    int cmd_sz = 0, rval;
    char buffer[BUFFSIZE0] = { 0 };
    fd_set read_set;
    struct timeval timeout;

    FD_ZERO(&read_set);
    FD_SET(sd, &read_set);
    timeout.tv_sec = opt.TIMEOUT;
    timeout.tv_usec = 0;

    if (cmd != NULL && (cmd_sz = strlen(cmd)) > 0 && send(sd, cmd, cmd_sz, 0) != cmd_sz) {
		fprintf (stderr, "\r%s [cannot send ftp command: %s]\n", msg, strerror(errno));
		return true;
    }

    if (!reply)
	return false;

    switch (opt.TIMEOUT) {
    case 0:
	if ((rval = recv(sd, buffer, BUFFSIZE, 0)) <= 0) {
	    fprintf (stderr, "\r %s [error: %s]\n", msg, strerror(errno));
	    return true;
	}
	break;

    default:
	if ((rval = select(sd + 1, &read_set, NULL, NULL, &timeout)) <= 0) {
	    fprintf (stderr, "\r%s [%s]\n", msg,
	    (rval < 0) ? "Unexpected error on select() call !" :
		    		 "error: connection timeout");
	    return true;
	}
	else {
	    rval = recv(sd, buffer, BUFFSIZE, 0);
	    if (rval <= 0) {
			fprintf (stderr, "\r%s [error: %s]\n", msg, strerror(errno));
			return true;
	    }
	}
	break;
    }

    buffer[rval] = 0x00;
    rval = strncmp(buffer, reply, strlen(reply));

    if (rval) {
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s ", msg);
		if (atoi(buffer) != 550)
	    	fprintf (stderr, "[error: bad server reply (%s)]\n", buffer);
		else
	    	fprintf (stderr, "[File Not Found]\n");
		return true;
    }

    if (data)
	*data = xstrdup(buffer);

    return false;
}


int
get_file_over_ftp
(const char *msg, const char *hostname, const char *request, const char *dest,
 bool overwrite, bool part, bool timestamp)
{

    struct hostent *phostent;
    struct sockaddr_in client;
    const u_short port = 21, stamp_size = 50;
    long received = 0, size;
    int i, sd, rval;
    char buffer[HUGE0], stamp[BUFFSIZE0];
    char *data = NULL, *ptr = NULL, *part_path = NULL;
    unsigned savedspeed;
    time_t savetime, difftime[2];
    FILE *fd;

    char PASV_ip[20] = { 0 };
    int PASV_sd;
    u_short PASV_fold[6];	/* first 4 fold are IP, 5° and 6° folds is port (5° * 256 + 6°) */
    u_short PASV_port;
    struct sockaddr_in PASV_client;

    memset (buffer, 0, HUGE0);
    memset (stamp,  0, BUFFSIZE0);

    fprintf (stdout, "\r%s [Starting FTP transaction]", msg);
    fflush (stdout);

    /* sanity check:
     * If `part' is false, `overwite' is true and if `dest' was already download, we overwrite old file; else, if `part'
     * is true, we BEFORE save data in `dest'.part and AFTER, when download has been finished, replace old file.
     * 
     * If `timestamp' is true, we verify first `stamp_size' bytes of `dest' and of `dest'.part. If are identical
     * quitting and remove `dest'.part WITHOUT overwrite old file ! 
     * 
     * Then if `part' is false, timestamp CANNOT be true :-)
     */
    if (!part) slassert (!timestamp);
    
    if (!overwrite && (fd = fopen (dest, "rb"))) {
		fclose (fd);
		clrline (stdout, strlen(msg) + 30);
		fprintf (stdout, "\r%s [Already downloaded]\n", msg);
		return EXIT_FAILURE;
    }

    /* if `timestamp' is true and `dest' already exist, load first `stamp_size' bytes. */
    if (timestamp && (fd = fopen (dest, "rb")) != NULL) {
		i = fread (stamp, 1, stamp_size, fd);
		fclose (fd);
		
		/* if file is too short force skip of timestamp check */
		if (i != stamp_size)
	     timestamp = false;
    }

    /* resolve hostname */
    if ((phostent = gethostbyname(hostname)) == NULL) {
		fprintf (stderr, "\r%s [cannot resolve %s]\n", msg, hostname);
		return EXIT_FAILURE;
    }

    client.sin_family = phostent->h_addrtype;
    client.sin_port = htons((u_short) port);
    memcpy(&client.sin_addr.s_addr, phostent->h_addr_list[0], phostent->h_length);

    /* create a socket */
    if ((sd = socket(phostent->h_addrtype, SOCK_STREAM, 0)) < 0) {
		fprintf (stderr, "\r%s [error: cannot open socket]\n", msg);
		return EXIT_FAILURE;
    }

    /* connect to host */
    rval = N_BLOCK_connect(sd, (struct sockaddr *) &client, sizeof client, opt.TIMEOUT);
    if (rval == -2) {
		close (sd);
		fprintf (stderr, "\r%s [cannot connect to server: %s]\n", msg, strerror(errno));
		return EXIT_FAILURE;
    }
    else if (rval == EXIT_FAILURE) {
		close (sd);
		fprintf (stderr, "\r%s [error: connection timeout]\n", msg);
		return EXIT_FAILURE;
    }

    /* starting FTP transaction */

    /* read 'server ready' */
    if (N_BLOCK_recv(sd, &data, 0, 500000, false) != EXIT_SUCCESS) {
		close (sd);
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error: %s]", msg, strerror(errno));
		return EXIT_FAILURE;
    }

    if (strncmp(data, "220", 3)) {
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error, server said: %s]\n", msg, data);
		ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
		close (sd);
		sfree (data);
		return EXIT_FAILURE;
    }

    /* anonymous login */
    clrline (stderr, strlen(msg) + 30);
    fprintf (stdout, "\r%s [FTP anonymous login]", msg);
    fflush (stdout);

    /* sending username */
    if (ftp_act (msg, sd, "USER anonymous\r\n", "331", NULL)) {
		close (sd);
		return EXIT_FAILURE;
    }

    /* sending pass */
    if (ftp_act (msg, sd, "PASS slackyd@anonymous.org\r\n", NULL, NULL)) {
		close (sd);
		return EXIT_FAILURE;
    }

    /* read here response, maybe a welcome message too long :-? */
    if (N_BLOCK_recv(sd, &data, 0, 500000, false) != EXIT_SUCCESS) {
		close (sd);
		clrline (stderr, strlen(msg) + 30);
		fprintf (stderr, "\r%s [error: %s]", msg, strerror(errno));
		return EXIT_FAILURE;
    }
    sfree (data);

    clrline (stderr, strlen(msg) + 30);
    fprintf (stdout, "\r%s [Retrieving file]", msg);
    fflush (stdout);

    /* sending SYST command */
    if (ftp_act (msg, sd, "SYST\r\n", "215", NULL)) {
		close (sd);
		return EXIT_FAILURE;
    }
    /* sending TYPE command */
    if (ftp_act (msg, sd, "TYPE I\r\n", "200", NULL)) {
		close (sd);
		return EXIT_FAILURE;
    }
    /* entering in passive mode */
    if (ftp_act (msg, sd, "PASV\r\n", "227", &data)) {
		close (sd);
		return EXIT_FAILURE;
    }

    /* obtain ip and port on passive mode */
    ptr = strchr(data, '(');
    if (!ptr) {
		fprintf (stderr, "\r%s [error, server said: %s]\n", msg, data);
		close (sd);
		sfree (data);
		return EXIT_FAILURE;
    }

    sscanf(ptr, "(%hu,%hu,%hu,%hu,%hu,%hu)",
    &PASV_fold[0], &PASV_fold[1],	/* IP */
    &PASV_fold[2], &PASV_fold[3],	/* IP */
	&PASV_fold[4], &PASV_fold[5]);	/* port: PASV_fold[4] * 256 + PASV_fold[5] */

    snprintf (PASV_ip, sizeof PASV_ip,
    "%hu.%hu.%hu.%hu",
    PASV_fold[0], PASV_fold[1], PASV_fold[2], PASV_fold[3]);
    
    PASV_port = PASV_fold[4] * 256 + PASV_fold[5];

    sfree (data);

    PASV_sd = socket(AF_INET, SOCK_STREAM, 0);
    PASV_client.sin_family = AF_INET;
    PASV_client.sin_port = htons((u_short) PASV_port);
    rval = inet_pton(AF_INET, PASV_ip, &PASV_client.sin_addr);

    if (rval <= 0) {
		close (PASV_sd);
		ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
		close (sd);
		fprintf (stderr, "\r%s [cannot create network address: %s]\n",
         msg, strerror(errno));
		return EXIT_FAILURE;
    }

    if (N_BLOCK_connect (PASV_sd, (struct sockaddr *) &PASV_client,
    	sizeof PASV_client, opt.TIMEOUT) < 0) {
		close (PASV_sd);
		ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
		close (sd);
		fprintf (stderr, "\r%s [error, cannot connect to server in passive mode: %s]\n",
		 msg, strerror(errno));
		return EXIT_FAILURE;
    }

    /* read size of required file */
    snprintf (buffer, HUGE, "SIZE %s\r\n", request);
    if (ftp_act (msg, sd, buffer, "213", &data)) {
		close (PASV_sd);
		ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
		close (sd);
		return EXIT_FAILURE;
    }

    size = atoi(data + 4);	/* 4 = strlen ("213 ") */
    sfree (data);

    /* require file */
    snprintf (buffer, HUGE, "RETR %s\r\n", request);
    if (ftp_act (msg, sd, buffer, "150", NULL)) {
		close (PASV_sd);
		ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
		close (sd);
		return EXIT_FAILURE;
    }

    /* if `part' is true save data in `dest'.part, else save directly in `dest'. */
    if (part) {
		part_path = xcalloc (strlen(dest) + 10, sizeof (char));
		snprintf (part_path, BUFFSIZE, "%s.part", dest);

		fd = fopen (part_path, "wb");
		if (!fd) {
	    	sfree (part_path);
	    	close (PASV_sd);
	    	ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
	    	close (sd);
	    	fprintf (stderr, "\r%s [cannot write %s: %s]\n", msg, part_path, strerror(errno));
	    	return EXIT_FAILURE;
	}
    }
    else {
    	fd = fopen (dest, "wb");
		if (!fd) {
	     close (PASV_sd);
	     ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
	     close (sd);
	     fprintf (stderr, "\r%s [cannot write %s: %s]\n", msg, dest, strerror(errno));
	    return EXIT_FAILURE;
	}
    }

    /* if `timestamp' is true receive firts bytes and compare with old file */
    if (timestamp) {
		if (stamp_size > HUGE)
         _error (1, "\n\n"
         "Fatal error on ftp transaction: stamp size huge !?\n\n");

        rval = recv (PASV_sd, buffer, stamp_size, 0);
		if (rval <= 0) {
	    	free (part_path);
	    	fclose (fd);
	    	close (PASV_sd);
	    	ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
	    	close (sd);
	    	fprintf (stderr, "\r%s [error, cannot receive file: %s]\n", msg, strerror(errno));
		}
		if (!memcmp (buffer, stamp, stamp_size)) {
	    	remove (part_path);
	    	sfree (part_path);
	    	fclose (fd);
	    	/* close connection */
	    	close (PASV_sd);
	    	ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
	    	close (sd);
	    	clrline (stderr, strlen(msg) + 30);
	    	fprintf (stderr, "\r%s [Already update]\n", msg);
	    	return FILE_ALREADY_UPDATE;
		}
		else {
	    	fwrite (buffer, 1, rval, fd);
	    	received += (u_long) rval;
	}
    }

    clrline (stdout, strlen(msg) + 30);

    /* receive all (remaining ?) data ... */
    memset (buffer, 0, HUGE0);
    savetime    = time (NULL);
    difftime[0] = 1;
    savedspeed  = 0;

    while ((rval = recv(PASV_sd, buffer, HUGE, 0)) > 0) {
		received += rval;
		fwrite (buffer, 1, rval, fd);
		memset (buffer, 0, HUGE);

		if (size > HUGE) {
		    fprintf (stdout, "\r%s [%u %% ", msg, PERCENTAGE(KBytes(received), KBytes(size)));
            difftime[1] = time (NULL) - savetime; /* total time elapsed since start download */
            /* we update download speed only if is spent one second or more
             * since last time calculation
             */
            if (difftime[0] == 0 || difftime[1] > difftime[0]) { 
                savedspeed = NETSPEED  (received, difftime[1]);
                difftime[0] = difftime[1];
            }

            fprintf (stdout, "@ %u KB/s]", KBytes (savedspeed));
            
		}
	    else {
	        /* if required file is smaller of HUGE bytes we print only this: */
	        fprintf (stdout, "\r%s [Downloading %ld bytes]", msg, size);
        }

	    fflush (stdout);
	}

    clrline (stderr, strlen(msg) + 30);

    fprintf (stdout, "\r%s [100 %% @ %u KB/s]", msg,
    (savedspeed > 0) ? KBytes (savedspeed) : KBytes (received));
    
    fflush (stdout);

    /* close connection */
    close (PASV_sd);
    ftp_act (msg, sd, "QUIT\r\n", NULL, NULL);
    close (sd);

    fclose (fd);

    if (part) {
		rename(part_path, dest);
		free (part_path);
    }

    putchar('\n');

    return FILE_DOWNLOADED;
}

/* require `package' from N_REPO repository already load from config file */
int
get_pkg (pkg_t *package)
{
    int rval = 0;
    char msg[BUFFSIZE0] = { 0 };
    char request[BUFFSIZE0] = { 0 };
    char dest[BUFFSIZE0] = { 0 };

    if (!package)
	return EXIT_FAILURE;

    snprintf (msg, BUFFSIZE, "Downloading %s.", package->name);
    snprintf (request, BUFFSIZE, "%s/%s/%s", REPOS[package->N_REPO].path,
	     package->location, package->name);
    snprintf (dest, BUFFSIZE, "%s/%s", DATADIR, package->name);

    switch (REPOS[package->N_REPO].proto_t) {
    case http:
	 rval =
	 get_file_over_http(msg, REPOS[package->N_REPO].hostname, request, dest,
	 false, true, false);
	 break;
    
    case ftp:
	 rval =
	 get_file_over_ftp (msg, REPOS[package->N_REPO].hostname, request, dest,
	 false, true, false);
	 break;
    
    default:
	 slassert (NULL);
    }

    return rval;
}

/* retrieve package build sources. */
int
gepkgsrc_t (pkg_t * package, pkgsrc_t source)
{
    unsigned j;
    int rval = 0;

    char buffer  [BUFFSIZE0] = { 0 }, bdest[BUFFSIZE0] = { 0 },
    	 bsubdest[BUFFSIZE0] = { 0 }, dest [BUFFSIZE0] = { 0 },
    	 request [BUFFSIZE0] = { 0 }, msg  [BUFFSIZE0] = { 0 };

    pkg_struct pstruct;
    struct stat s_stat;

    if (!package)
	 return EXIT_FAILURE;

    if (split_pkg(package->name, &pstruct))
	 return EXIT_FAILURE;

    /* sanity check base destination */
    snprintf (bdest, BUFFSIZE, SRCDIR);
    if (stat(bdest, &s_stat) < 0) {
    	if (mkdir(bdest, 0777) < 0)
		_error (5, "\nError: cannot create ", bdest, ": ", strerror(errno), ".\n\n");
	}
	else
	if (!S_ISDIR (s_stat.st_mode))
	_error (3, "\nError: ", bdest, " exist and isn't directory !?\n\n");
	
    
    snprintf (bdest, BUFFSIZE, "%s/%s-%s_%s/",
    SRCDIR, pstruct.name, pstruct.version, REPOS[package->N_REPO].name);
    
    if (mkdir(bdest, 0777) < 0 && errno != EEXIST) {
		fprintf (stderr, "Error, cannot create %s: %s\n", bdest, strerror(errno));
		return EXIT_FAILURE;
    }

    /* fix sub directories */
	memset (bsubdest, 0, BUFFSIZE);
	for (j = 0; j < source.fsubpath.nlist; j++) {
	    strncat(bsubdest, "/", BUFFSIZE - strlen(bsubdest));
	    strncat(bsubdest, source.fsubpath.list[j], BUFFSIZE - strlen(bsubdest));
	    snprintf (buffer, BUFFSIZE, "/%s/%s/", bdest, bsubdest);

	    if (stat(buffer, &s_stat) < 0 && mkdir(buffer, 0777) < 0) {
			fprintf (stderr, "Error: cannot create %s: %s\n", buffer, strerror(errno));
			return EXIT_FAILURE;
	    }
	}

	snprintf (msg, BUFFSIZE, "  --> Retrieving %s", source.file);

	/* make repository file path */
	if (source.fsubpath.nlist > 0)
		snprintf (request, BUFFSIZE, "%s/%s/%s/%s", REPOS[package->N_REPO].path,
		source.rpath, bsubdest, source.file);
	else
		snprintf (request, BUFFSIZE, "%s/%s/%s", REPOS[package->N_REPO].path,
		source.rpath, source.file);

	/* make local file path dest */
	if (source.fsubpath.nlist > 0)
	    snprintf (dest, BUFFSIZE, "/%s/%s/%s", bdest, bsubdest, source.file);
	else
	    snprintf (dest, BUFFSIZE, "/%s/%s", bdest, source.file);

	switch (REPOS[package->N_REPO].proto_t) {
	case http:
	    rval =
		get_file_over_http(msg, REPOS[package->N_REPO].hostname, request, dest,
		false, true, false);
	    break;
	case ftp:
	    rval =
		get_file_over_ftp (msg, REPOS[package->N_REPO].hostname, request,
		dest, false, true, false);
	    break;
	default:
	    _error (1, "\nUnknow repository protocol.\n\n");
	    break;
	}
    
    return rval;
}


int
gepkgsrc_ts (pkg_t *package, pkgsrc_t *sources, unsigned n_sources)
{
	unsigned i;
	
	for (i = 0; i < n_sources; i++)
	 gepkgsrc_t (package, sources[i]);
	 
	 return EXIT_SUCCESS;
 } 

