/**
 * @file ardrone_ftp.c
 * @author nicolas.brulez@parrot.com
 * @date 2011/04/06
 * Copyright Parrot SA. 2011
 */

#include <utils/ardrone_ftp.h>

#include <stdio.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>

#include <VP_Os/vp_os_thread.h>
#include <VP_Os/vp_os_malloc.h>
#include <VP_Os/vp_os_signal.h>

#include <errno.h>

#include <VP_Os/vp_os_print.h>

/* CONFIGURATION */

// All following macros MUST be defined !
// To activate, define to (1)
// To deactivate, define to (0)

#ifdef DEBUG // Debug options
#define _FTP_DEBUG (0) // Common debug informations
#define _FTP_VERBOSE (0) // Extended debug information (many outputs on ftpList)
#define _FTP_ERRORS_PRINT (1) // Display of error messages
#else // Release options
#define _FTP_DEBUG (0)
#define _FTP_VERBOSE (0)
#define _FTP_ERRORS_PRINT (0)
#endif

#define FTP_PREFIX "FTP : "

/* LOCAL PRINT MACROS */

#if _FTP_ERRORS_PRINT
#define FTP_ERROR(...)                                                  \
    do                                                                  \
    {                                                                   \
        PRINT ("Error in function %s at line %d : ", __FUNCTION__, __LINE__); \
        PRINT (__VA_ARGS__);                                            \
        char *errorBuffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);      \
        if (NULL != errorBuffer)                                        \
        {                                                               \
            snprintf (errorBuffer, COMMAND_BUFFER_SIZE-1, __VA_ARGS__); \
            FTPlastErrorMessageSize = strlen (errorBuffer) + 1;         \
            FTPlastErrorMessage = vp_os_realloc (FTPlastErrorMessage, FTPlastErrorMessageSize); \
            if (NULL != FTPlastErrorMessage)                            \
            {                                                           \
                strncpy (FTPlastErrorMessage, errorBuffer, FTPlastErrorMessageSize); \
            }                                                           \
            vp_os_free (errorBuffer);                                   \
        }                                                               \
    } while (0)
#else
#define FTP_ERROR(...)                                                  \
    do                                                                  \
    {                                                                   \
        char *errorBuffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);      \
        if (NULL != errorBuffer)                                        \
        {                                                               \
            snprintf (errorBuffer, COMMAND_BUFFER_SIZE-1, __VA_ARGS__); \
            FTPlastErrorMessageSize = strlen (errorBuffer) + 1;         \
            FTPlastErrorMessage = vp_os_realloc (FTPlastErrorMessage, FTPlastErrorMessageSize); \
            if (NULL != FTPlastErrorMessage)                            \
            {                                                           \
                strncpy (FTPlastErrorMessage, errorBuffer, FTPlastErrorMessageSize); \
            }                                                           \
            vp_os_free (errorBuffer);                                   \
        }                                                               \
    } while (0)
#endif

#if _FTP_DEBUG
#define FTP_DEBUG(...)                                                  \
    do                                                                  \
    {                                                                   \
        PRINT ("Debug from function %s at line %d : ", __FUNCTION__, __LINE__); \
        PRINT (__VA_ARGS__);                                            \
    } while (0)
#else
#define FTP_DEBUG(...)
#endif

#if _FTP_VERBOSE
#define FTP_PRINT(...)                          \
    do                                          \
    {                                           \
        PRINT (FTP_PREFIX);                     \
        PRINT (__VA_ARGS__);                    \
    } while (0)
#else
#define FTP_PRINT(...)
#endif

/* SIZE MACROS */

#ifndef MAX_SIZE_MSG
#define MAX_SIZE_MSG 32768
#endif
#ifndef COMMAND_BUFFER_SIZE
#define COMMAND_BUFFER_SIZE 512
#endif
#define IP_STRING_SIZE 16 // IP strings goes from 8 ("w.x.y.z\0") to 16 ("www.xxx.yyy.zzz\0") chars
#define LIST_BUFFER_BLOCKSIZE 1024
#define FILE_NAME_MAX_SIZE 512

/* TIMEOUT MACROS */

/* Total socket timeout : SOCK_TO_SEC + SOCK_TO_USEC */
#define SOCK_TO_SEC 3 // Socket timeout (seconds)
#define SOCK_TO_USEC 0 // Socket timeout (useconds)

/* GLOBAL ERROR MESSAGE STRING */
char *FTPlastErrorMessage = NULL;
int FTPlastErrorMessageSize = 0;

/* THREAD STRUCTURES */

typedef struct _ftp_list_param_s _ftp_list_param;
typedef struct _ftp_get_param_s _ftp_get_param;
typedef struct _ftp_put_param_s _ftp_put_param;

struct _ftp_put_param_s
{
    _ftp_t *ftp;
    char localName [FILE_NAME_MAX_SIZE];
    char remoteName [FILE_NAME_MAX_SIZE];
    int useResume;
    ftp_callback callback;
    char *fileList;
};

struct _ftp_get_param_s
{
    _ftp_t *ftp;
    char localName [FILE_NAME_MAX_SIZE];
    char remoteName [FILE_NAME_MAX_SIZE];
    int useResume;
    ftp_callback callback;
    char *fileList;
};

struct _ftp_list_param_s
{
    _ftp_t *ftp;
    char *fileList;
    int listSize;
    ftp_callback callback;
};

/* LOCAL FUNCTIONS PROTOTYPES */
int isLANIp (const char *ip);
void emptyCallback (_ftp_status status, void *arg, _ftp_t *callingFtp);
_ftp_status waitFor226Answer (_ftp_t *ftp);
int setSockTimeout (int socket, int timeoutSec, int timeoutUsec);
_ftp_status goToBinaryMode (_ftp_t *ftp);
void flushFtp (_ftp_t *ftp);
int getFileSize (_ftp_t *ftp, const char *distPath);
int getLocalFileSize (const char *localPath);
int getResponseCode (const char *response);
_ftp_status getPassiveIpAndPort (_ftp_t *ftp, const char *response, char *ip, int *port, int ipLen);
_ftp_status ftpTransfert (_ftp_t *ftp, const char *message, char *answer, int answSize);
_ftp_status ftpSend (_ftp_t *ftp, const char *message);
_ftp_status ftpRecv (_ftp_t *ftp, char *answer, int answSize);
DEFINE_THREAD_ROUTINE (ftpGet, param);
DEFINE_THREAD_ROUTINE (ftpPut, param);
DEFINE_THREAD_ROUTINE (ftpList, param);

/* FUNCTIONS IMPLEMENTATION */
int
isLANIp (const char *ip)
{
    int retVal = -1;
    uint32_t ip0, ip1, ip2, ip3;
    if (4 == sscanf (ip, "%u.%u.%u.%u", &ip0, &ip1, &ip2, &ip3))
    {
        /* LAN address classes :
         * 127.x.x.x
         * 10.x.x.x
         * 192.168.x.x
         * 169.254.x.x
         * 172.[16-31].x.x
         *
         * Test IP is :
         * ip0.ip1.ip2.ip3
         */
        if (((127 == ip0)) ||
            ((10  == ip0)) ||
            ((192 == ip0) && (168 == ip1)) ||
            ((169 == ip0) && (254 == ip1)) ||
            ((172 == ip0) && (16 <= ip1) && (31 >= ip1)))
        {
            retVal = 1;
        }
        else
        {
            retVal = 0;
        }
    }
    else
    {
        FTP_ERROR ("String %s does not represent a valid ipv4 address\n", ip);
    }
    return retVal;
}

void
emptyCallback (_ftp_status status, void *arg, _ftp_t *callingFtp)
{
    FTP_PRINT ("Called with status %d\n", status);
#if _FTP_VERBOSE
    if (FTP_PROGRESS == status)
    {
        FTP_PRINT ("Trying float arg : %f\n", (NULL != arg) ? *(float *)arg : -1.0);
    }
#endif
    if (NULL != callingFtp)
    {
        callingFtp->lastStatus = status;
        if(FTP_SUCCESS == status && NULL != arg)
        {
            callingFtp->lastFileList = (char *)arg;
        }
    }
}

#define FTP_MAX_NUM_RETRIES 10
_ftp_status
waitFor226Answer (_ftp_t *ftp)
{
    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == srvMsg)
    {
        return FTP_FAIL;
    }
    int repCode = 0;
    int numretries = FTP_MAX_NUM_RETRIES;

    _ftp_status ftp_result = FTP_SUCCESS;
    while (226 != repCode && 0 < numretries)
    {
        ftp_result = ftpRecv (ftp, srvMsg, MAX_SIZE_MSG-1);
        numretries--;
        repCode = getResponseCode (srvMsg);
    }
    vp_os_free (srvMsg);
    return ftp_result;
}

int
setSockTimeout (int socket, int timeoutSec, int timeoutUsec)
{
#ifdef _WIN32
    int winTO = (1000 * timeoutSec) + (timeoutUsec / 1000);
#else
    struct timeval posixTO;
    posixTO.tv_sec = timeoutSec;
    posixTO.tv_usec = timeoutUsec;
#endif

    if (0 > setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO,
#ifdef _WIN32
                        (const char *)&winTO, sizeof (winTO)
#else
                        (const char *)&posixTO, sizeof (posixTO)
#endif
            ))
    {
        FTP_ERROR ("Unable to set recv timeout\n");
        return -1;
    }

    if (0 > setsockopt (socket, SOL_SOCKET, SO_SNDTIMEO,
#ifdef _WIN32
                        (const char *)&winTO, sizeof (winTO)
#else
                        (const char *)&posixTO, sizeof (posixTO)
#endif
            ))
    {
        FTP_ERROR ("Unable to set send timeout\n");
        return -1;
    }
    return 0;
}

_ftp_status
goToBinaryMode (_ftp_t *ftp)
{
    char *ftpAnswer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    if (NULL == ftpAnswer)
    {
        return FTP_FAIL;
    }
    _ftp_status ftp_result = ftpTransfert (ftp, "TYPE I\r\n\0", ftpAnswer, COMMAND_BUFFER_SIZE-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Unable to go to binary mode\n");
    }
    vp_os_free (ftpAnswer);
    return ftp_result;
}

void
flushFtp (_ftp_t *ftp)
{
    FTP_DEBUG ("Starting flush\n");
    char c = 0;
    int bytes = 1;
    int flushedBytes = 0;
    VP_COM_SOCKET_BLOCKING_OPTIONS oldOptions = ftp->socket->block;
    ftp->socket->block = VP_COM_DONTWAIT;
    C_RESULT vp_result = ftp->readSock (ftp->socket, (uint8_t *)&c, &bytes);
    while (0 < bytes && VP_SUCCEEDED (vp_result))
    {
        flushedBytes++;
#if _FTP_DEBUG
        printf ("%c", c);
#endif
        vp_result = ftp->readSock (ftp->socket, (uint8_t *)&c, &bytes);
    }
    FTP_DEBUG ("Flushed %d bytes\n", flushedBytes);
    ftp->socket->block = oldOptions;
}

int
getFileSize (_ftp_t *ftp, const char *distPath)
{
    char *ftpCommand = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    char *ftpAnswer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    if (NULL == ftpCommand || NULL == ftpAnswer)
    {
        vp_os_free (ftpCommand);
        vp_os_free (ftpAnswer);
        return -1;
    }
    snprintf (ftpCommand, COMMAND_BUFFER_SIZE-1, "SIZE %s\r\n", distPath);
    _ftp_status ftp_result = ftpTransfert (ftp, ftpCommand, ftpAnswer, COMMAND_BUFFER_SIZE-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Unable to get file size\n");
        vp_os_free (ftpCommand);
        vp_os_free (ftpAnswer);
        return -1;
    }
    int size = -1;
    int repCode = 0;
    sscanf (ftpAnswer, "%d %d", &repCode, &size);
    vp_os_free (ftpCommand);
    vp_os_free (ftpAnswer);
    return size;
}

int
getLocalFileSize (const char *localPath)
{
    FILE *localFile = fopen (localPath, "r");
    if (NULL == localFile)
    {
        FTP_DEBUG ("File %s does not exist\n", localPath);
        return -1;
    }
    fseek (localFile, 0, SEEK_END);
    int size = (int)ftell (localFile);
    FTP_DEBUG ("Size of file %s : %d o\n", localPath, size);
    fclose (localFile);
    return size;
}

int
getResponseCode (const char *response)
{
    int retVal = -1;
    sscanf (response, "%d", &retVal);
    return retVal;
}

_ftp_status
getPassiveIpAndPort (_ftp_t *ftp, const char *response, char *ip, int *port, int ipLen)
{
    int ip1, ip2, ip3, ip4, port1, port2;
    int indexOfFirstIpField = 0;
    char atIndex = '\0';
    int maxIndex = strlen (response);
    while (indexOfFirstIpField < maxIndex && '(' != atIndex)
    {
        atIndex = response[indexOfFirstIpField++];
    }
    int numread = sscanf (&response[indexOfFirstIpField], "%d,%d,%d,%d,%d,%d)", &ip1, &ip2, &ip3, &ip4, &port1, &port2);
    _ftp_status result = FTP_SUCCESS;
    if (6 == numread)
    {
        snprintf (ip, ipLen-1, "%d.%d.%d.%d", ip1, ip2, ip3, ip4);
        *port = 256 * port1 + port2;
        FTP_DEBUG ("IP : %s | Port : %d\n", ip, *port);
    }
    else
    {
        result = FTP_FAIL;
    }
    int pasvIsLan = isLANIp (ip);
    int servIsLan = isLANIp (ftp->socket->serverHost);
    if (-1 == pasvIsLan || -1 == servIsLan)
    {
        // Error while parsing IP ...
        result = FTP_FAIL;
    }
    else if (1 == pasvIsLan && 0 == servIsLan)
    {
        FTP_DEBUG ("pasv ip is a LAN ip (not routable), but server ip is not a LAN ip. We'll use the server ip (%s) instead of the pasv ip (%s)\n", ftp->socket->serverHost, ip);
        strncpy (ip, ftp->socket->serverHost, ipLen);
    }
    // No else : if pasv addr is routable or if both address are LAN address, keep pasv ip.

    return result;
}

_ftp_status
ftpTransfert (_ftp_t *ftp, const char *message, char *answer, int answSize)
{
    flushFtp (ftp);
    _ftp_status ftp_result = ftpSend (ftp, message);
    if (FTP_FAILED (ftp_result))
    {
        return ftp_result;
    }
    return ftpRecv (ftp, answer, answSize);
}

_ftp_status
ftpSend (_ftp_t *ftp, const char *message)
{
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }
    FTP_DEBUG ("Sending %sto FTP at %s:%d\n", message, ftp->socket->serverHost, ftp->socket->port);

    int bytes = strlen (message);
    C_RESULT vp_result = ftp->writeSock (ftp->socket, (uint8_t *)message, &bytes);
    if (VP_FAILED (vp_result))
    {
        FTP_ERROR ("Error while sending data\n");
        return FTP_FAIL;
    }
    if (0 == bytes)
    {
        FTP_ERROR ("Unable to send data\n");
        return FTP_TIMEOUT;
    }
    return FTP_SUCCESS;
}

_ftp_status
ftpRecv (_ftp_t *ftp, char *answer, int answSize)
{
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    vp_os_memset (answer, 0x0, answSize);
    int index = 0;
    do
    {
        int bytes = 1;
        C_RESULT vp_result = ftp->readSock (ftp->socket, (uint8_t *)(&answer [index]), &bytes);
        if (VP_FAILED (vp_result))
        {
            FTP_ERROR ("Error while reading data\n");
            return FTP_FAIL;
        }
        if (0 == bytes)
        {
            FTP_ERROR ("Recv timeout\n");
            return FTP_TIMEOUT;
        }
    }
    while (index < answSize && '\n' != answer [index++]);

    FTP_DEBUG ("Answer:\n<---START--->\n%s\n<---END--->\n", answer);
    return FTP_SUCCESS;
}

_ftp_status
ftpClose (_ftp_t **ftp)
{
    FTP_DEBUG ("Closing ftp\n");
    _ftp_status retVal = FTP_FAIL;
    if (NULL != *ftp)
    {
        FTP_DEBUG ("Not null ftp\n");
        if (NULL != (*ftp)->socket)
        {
            FTP_DEBUG ("Not null socket\n");
            if (1 == (*ftp)->connected)
            {
                if (FTP_SUCCESS == ftpAbort ((*ftp))) // An operation was in progress, abort and let time to cleanup.
                {
                    usleep (100000); // 100ms
                }
                ftpSend ((*ftp), "QUIT\r\n\0");
                (*ftp)->connected = 0;
            }
            vp_com_close_socket ((*ftp)->socket);
            vp_os_free ((*ftp)->socket);
            (*ftp)->socket = NULL;
            retVal = FTP_SUCCESS;
        }
        vp_os_free (*ftp);
        *ftp = NULL;
    }
    return retVal;
}

_ftp_t *
ftpConnectFromName (const char *name, int port, const char *username, const char *password, _ftp_status *status)
{
    struct hostent *hostent = gethostbyname(name);
    return ftpConnect(inet_ntoa( *( struct in_addr*)( hostent->h_addr)), port, username, password, status);
}

_ftp_t *
ftpConnect (const char *ip, int port, const char *username, const char *password, _ftp_status *status)
{
    if (NULL == ip ||
        NULL == username ||
        NULL == password ||
        NULL == status)
    {
        FTP_ERROR ("Must not pass NULL pointers to ftpConnect\n");
        if (NULL != status) { *status = FTP_FAIL; }
        return NULL;
    }
    int isAnonymous = ((0 == strcmp (username, "anonymous")) || (0 == strcmp (username, ""))) ? 1 : 0;
    if (1 == isAnonymous)
    {
        FTP_DEBUG ("Connecting to %s:%d, anonymous\n", ip, port);
    }
    else
    {
        FTP_DEBUG ("Connecting to %s:%d, USER = %s, Password = %s\n", ip, port, username, password);
    }

    *status = FTP_FAIL;
    _ftp_t *retFtp = vp_os_malloc (sizeof (_ftp_t));
    if (NULL == retFtp)
    {
        FTP_ERROR ("Unable to allocate a ftp structure\n");
        return NULL;
    }
    retFtp->lastStatus = FTP_SUCCESS;
    retFtp->lastFileList = NULL;

    retFtp->socket = vp_os_malloc (sizeof (vp_com_socket_t));
    if (NULL == retFtp->socket)
    {
        FTP_ERROR ("Unable to allocate socket filed of the ftp structure\n");
        ftpClose (&retFtp);
        return NULL;
    }

    retFtp->connected = 0;

    retFtp->socket->type = VP_COM_CLIENT;
    retFtp->socket->protocol = VP_COM_TCP;
    retFtp->socket->port = port;
    strncpy (retFtp->socket->serverHost, ip, VP_COM_NAME_MAXSIZE-1);
    retFtp->socket->is_multicast = 0;
    retFtp->socket->block = VP_COM_DEFAULT;

    C_RESULT vp_result = vp_com_open_socket (retFtp->socket, &(retFtp->readSock), &(retFtp->writeSock));
    if (VP_FAILED (vp_result))
    {
        FTP_ERROR ("Unable to connect\n");
        ftpClose (&retFtp);
        return NULL;
    }

    int result = setSockTimeout ((int)retFtp->socket->priv, SOCK_TO_SEC, SOCK_TO_USEC);
    if (0 > result)
    {
        FTP_ERROR ("Unable to set socket timeout\n");
        ftpClose (&retFtp);
        return NULL;
    }

    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == srvMsg)
    {
        FTP_ERROR ("Unable to allocate buffer\n");
        ftpClose (&retFtp);
        return NULL;
    }
    if (FTP_FAILED (ftpRecv (retFtp, srvMsg, MAX_SIZE_MSG-1)))
    {
        FTP_ERROR ("Unable to recieve data from server\n");
        ftpClose (&retFtp);
        vp_os_free (srvMsg);
        return NULL;
    }

    int repCode = getResponseCode (srvMsg);
    if (220 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 220)\n", repCode);
        ftpClose (&retFtp);
        vp_os_free (srvMsg);
        return NULL;
    }

    char *buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    if (NULL == buffer)
    {
        FTP_ERROR ("Unable to allocate buffer\n");
        ftpClose (&retFtp);
        vp_os_free (srvMsg);
        return NULL;
    }
    if (1 == isAnonymous)
    {
        snprintf (buffer, COMMAND_BUFFER_SIZE-1, "USER anonymous\r\n");
    }
    else
    {
        snprintf (buffer, COMMAND_BUFFER_SIZE-1, "USER %s\r\n", username);
    }
    if (FTP_FAILED (ftpTransfert (retFtp, buffer, srvMsg, MAX_SIZE_MSG-1)))
    {
        FTP_ERROR ("Error while sending command\n");
        ftpClose (&retFtp);
        vp_os_free (srvMsg);
        vp_os_free (buffer);
        return NULL;
    }
    repCode = getResponseCode (srvMsg);
    int goodRepCode = 331;
    if (1 == isAnonymous)
    {
        goodRepCode = 230;
    }
    if (goodRepCode != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected %d)\n", repCode, goodRepCode);
        ftpClose (&retFtp);
        vp_os_free (srvMsg);
        vp_os_free (buffer);
        return NULL;
    }

    if (0 == isAnonymous)
    {
        vp_os_memset (buffer, 0x0, COMMAND_BUFFER_SIZE);
        snprintf (buffer, COMMAND_BUFFER_SIZE-1, "PASS %s\r\n", password);
        if (FTP_FAILED (ftpTransfert (retFtp, buffer, srvMsg, MAX_SIZE_MSG-1)))
        {
            FTP_ERROR ("Error while sending command\n");
            ftpClose (&retFtp);
            vp_os_free (srvMsg);
            vp_os_free (buffer);
            return NULL;
        }
        repCode = getResponseCode (srvMsg);
        if (230 != repCode)
        {
            FTP_ERROR ("Bad response from server (%d, expected 230)\n", repCode);
            ftpClose (&retFtp);
            vp_os_free (srvMsg);
            vp_os_free (buffer);
            return NULL;
        }
    }

    *status = FTP_SUCCESS;
    retFtp->connected = 1;
    retFtp->opInProgress = 0;
    retFtp->abortCurrentOp = 0;
    retFtp->tag = NULL;
    vp_os_free (srvMsg);
    vp_os_free (buffer);
    return retFtp;
}


#undef CLEAN_PARAMS
#define CLEAN_PARAMS(status) CLEAN_PARAMS_WITH_ARG(status,NULL)
#undef CLEAN_PARAMS_WITH_ARG
#define CLEAN_PARAMS_WITH_ARG(status,arg)       \
    do                                          \
    {                                           \
        if (NULL != params->ftp)                \
        {                                       \
            params->ftp->opInProgress = 0;      \
        }                                       \
        CLEAN_PARAMS_ABORT (status,arg);        \
    } while (0)
#undef CLEAN_PARAMS_ABORT
#define CLEAN_PARAMS_ABORT(status,arg)                                  \
    do                                                                  \
    {                                                                   \
        _ftp_status locStat = (status);                                 \
        if (NULL != localFile) fclose (localFile);                      \
        if (NULL != srvMsg) vp_os_free (srvMsg);                        \
        if (NULL != buffer) vp_os_free (buffer);                        \
        if (NULL != filePart) vp_os_free (filePart);                    \
        if (NULL != dataSocket)                                         \
        {                                                               \
            vp_com_close_socket (dataSocket);                           \
            vp_os_free (dataSocket);                                    \
            dataSocket = NULL;                                          \
        }                                                               \
        params->callback (locStat, arg, params->ftp);                   \
        if (FTP_SUCCESS != status && NULL != params->fileList)          \
        {                                                               \
            vp_os_free (params->fileList);                              \
            params->fileList = NULL;                                    \
        }                                                               \
        vp_os_free (param);                                             \
        FTP_DEBUG ("Returning from thread %s with status %d\n", __FUNCTION__, locStat); \
        THREAD_RETURN (locStat);                                        \
    }                                                                   \
    while (0)
#undef CHECK_ABORT
#define CHECK_ABORT                                                     \
    do                                                                  \
    {                                                                   \
        if (1 <= params->ftp->abortCurrentOp)                           \
        {                                                               \
            vp_os_memset (srvMsg, 0x0, MAX_SIZE_MSG);                   \
            if (NULL != dataSocket)                                     \
            {                                                           \
                vp_com_close_socket (dataSocket);                       \
                vp_os_free (dataSocket);                                \
                dataSocket = NULL;                                      \
            }                                                           \
            ftpTransfert (params->ftp, "ABOR\r\n\0", srvMsg, MAX_SIZE_MSG-1); \
            flushFtp (params->ftp);                                     \
            params->ftp->abortCurrentOp = 0;                            \
            params->ftp->opInProgress = 0;                              \
            CLEAN_PARAMS_ABORT (FTP_ABORT, NULL);                       \
        }                                                               \
    } while (0)

DEFINE_THREAD_ROUTINE (ftpList, param)
{
    char *srvMsg = NULL;
    char *buffer = NULL; // Compatibility with macros
    char *filePart = NULL; // Compatibility with macros
    FILE *localFile = NULL; // Compatibilty with macros
    vp_com_socket_t *dataSocket = NULL;
    Read dataRead = NULL;
    Write dataWrite = NULL;
    _ftp_list_param *params = (_ftp_list_param *)param;
    if (NULL == params->ftp)
    {
        FTP_ERROR ("FTP not open\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    flushFtp (params->ftp);

    srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == srvMsg)
    {
        FTP_ERROR ("Unable to allocate buffer\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    _ftp_status ftp_result = ftpTransfert (params->ftp, "PASV\r\n\0", srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending command\n");
        CLEAN_PARAMS (ftp_result);
    }
    int repCode = getResponseCode (srvMsg);
    if (227 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 227)\n", repCode);
        CLEAN_PARAMS (FTP_FAIL);
    }

    char dataIp [IP_STRING_SIZE] = {0};
    int dataPort = 0;
    getPassiveIpAndPort (params->ftp, srvMsg, dataIp, &dataPort, IP_STRING_SIZE);

    FTP_DEBUG ("Thread will connect to %s:%d\n", dataIp, dataPort);
    dataSocket = vp_os_malloc (sizeof (vp_com_socket_t));
    if (NULL == dataSocket)
    {
        FTP_ERROR ("Unable to allocate socket structure\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    dataSocket->type = VP_COM_CLIENT;
    dataSocket->protocol = VP_COM_TCP;
    dataSocket->port = dataPort;
    strncpy (dataSocket->serverHost, dataIp, VP_COM_NAME_MAXSIZE);
    dataSocket->is_multicast = 0;
    dataSocket->block = VP_COM_WAITALL;

    C_RESULT vp_result = vp_com_open_socket (dataSocket, &dataRead, &dataWrite);
    if (VP_FAILED (vp_result))
    {
        FTP_ERROR ("Unable to connect\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    int result = setSockTimeout ((int)dataSocket->priv, SOCK_TO_SEC, SOCK_TO_USEC);
    if (0 > result)
    {
        FTP_ERROR ("Unable to set data socket timeout\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    CHECK_ABORT;

    ftp_result = ftpSend (params->ftp, "LIST\r\n\0");
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending LIST command\n");
        CLEAN_PARAMS (ftp_result);
    }

    CHECK_ABORT;
    char ftpData [2] = {0};
    int bytes = 1, totalBytes;
    vp_os_memset (params->fileList, 0x0, params->listSize);
    vp_result = dataRead (dataSocket, (uint8_t *)ftpData, &bytes);
    if (VP_FAILED (vp_result))
    {
        FTP_ERROR ("Unable to receive data\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    if(0 == bytes)
    {
        FTP_DEBUG ("Empty folder\n");
        waitFor226Answer (params->ftp);
        flushFtp (params->ftp);
        CLEAN_PARAMS (FTP_SAMESIZE);
    }

    if (strlen (ftpData) > params->listSize - 1)
    {
        params->listSize += LIST_BUFFER_BLOCKSIZE;
        params->fileList = vp_os_realloc(params->fileList, params->listSize);
        if(NULL == params->fileList)
        {
            FTP_ERROR ("Not enough space in response string, can't reallocate buffer list\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
    }
    snprintf (params->fileList, params->listSize-1, "%s", ftpData);
    totalBytes = bytes;
    while (1) // Loop is killed by a return or a break statement
    {
        CHECK_ABORT;
        ftpData [0] = 0;
        bytes = 1;
        vp_result = dataRead (dataSocket, (uint8_t *)ftpData, &bytes);
        if (VP_FAILED (vp_result))
        {
            FTP_ERROR ("Unable to receive data\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        if (0 == bytes && '\n' == params->fileList [totalBytes-1])
        {
            FTP_DEBUG ("Got all listing !\n");
            // Timeouted. We got all the listing !
            // Timeout is voluntary, so still return FTP_SUCCESS
            break;
        }
        else if (0 == bytes)
        {
            // Timeout
            FTP_ERROR ("Recv timeout\n");
            CLEAN_PARAMS (FTP_TIMEOUT);
        }
        if (strlen (ftpData) > (params->listSize - 1 - strlen (params->fileList)))
        {
            params->listSize += LIST_BUFFER_BLOCKSIZE;
            params->fileList = vp_os_realloc(params->fileList, params->listSize);
            if(NULL == params->fileList)
            {
                FTP_ERROR ("Not enough space in response string, can't reallocate buffer list\n");
                CLEAN_PARAMS (FTP_FAIL);
            }
        }
        strcat (params->fileList, ftpData);
        totalBytes += bytes;
        FTP_PRINT ("Progress of listing : finished ? %d : (%d->%d bytes) %s\n", (params->response [totalBytes -1] == '\n') ? 1: 0, bytes, totalBytes, params->response);
    }

    ftp_result = ftpRecv (params->ftp, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while getting answer\n");
        CLEAN_PARAMS (ftp_result);
    }
    repCode = getResponseCode (srvMsg);
    if (150 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 150)\n", repCode);
        CLEAN_PARAMS (FTP_FAIL);
    }

    /* Cleaning FTP */
    if (NULL != dataSocket)
    {
        vp_com_close_socket (dataSocket);
        vp_os_free (dataSocket);
        dataSocket = NULL;
    }
    _ftp_status loc226Status = waitFor226Answer (params->ftp);
    flushFtp (params->ftp);


    CLEAN_PARAMS_WITH_ARG (loc226Status, (void *)params->fileList);
}

DEFINE_THREAD_ROUTINE (ftpGet, param)
{
    char *srvMsg = NULL;
    char *buffer = NULL;
    char *filePart = NULL;
    vp_com_socket_t *dataSocket = NULL;
    Read dataRead = NULL;
    Write dataWrite = NULL;
    FILE *localFile = NULL;
    _ftp_get_param *params = (_ftp_get_param *)param;
    FTP_DEBUG ("Downloading %s to %s [resume : %c]\n", params->remoteName, params->localName, params->useResume ? 'y' : 'n');
    if (NULL == params->ftp)
    {
        FTP_ERROR ("FTP not open\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    flushFtp (params->ftp);

    buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    if (NULL == buffer)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "PASV\r\n");
    _ftp_status ftp_result = ftpTransfert (params->ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while entering passive mode\n");
        CLEAN_PARAMS (ftp_result);
    }
    int repCode = getResponseCode (srvMsg);
    if (227 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 227)\n", repCode);
        CLEAN_PARAMS (FTP_FAIL);
    }

    char dataIp [IP_STRING_SIZE] = {0};
    int dataPort = 0;
    getPassiveIpAndPort (params->ftp, srvMsg, dataIp, &dataPort, IP_STRING_SIZE);

    ftp_result = goToBinaryMode (params->ftp);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Unable to go to binary mode\n");
        CLEAN_PARAMS (ftp_result);
    }

    int fileSize = getFileSize (params->ftp, params->remoteName);
    if (0 > fileSize)
    {
        FTP_ERROR ("File %s does not exist on server\n", params->remoteName);
        CLEAN_PARAMS (FTP_FAIL);
    }
    int sizeToGet = fileSize;
    int localFileSize = getLocalFileSize (params->localName);
    int appendToFile = params->useResume;
    int appendOffset = 0;
    if (-1 == localFileSize && 0 == fileSize)
    {
        // Zero byte file on server
        FTP_DEBUG ("Zero byte file on server, create empty file locally\n");
        FILE *fdesc = fopen (params->localName, "ab");
        if (NULL != fdesc)
        {
            fclose (fdesc);
        }
        CLEAN_PARAMS (FTP_SUCCESS);
    }

    if (-1 == localFileSize && 1 == params->useResume)
    {
        FTP_DEBUG ("File does not exist ... full download\n");
        appendToFile = 0;
    }
    if (1 == appendToFile)
    {
        if (localFileSize == fileSize)
        {
            FTP_DEBUG ("File already downloaded\n");
            CLEAN_PARAMS (FTP_SAMESIZE);
        }
        else if (localFileSize > fileSize)
        {
            FTP_ERROR ("Local file (%s) is greater than distant file (%s)\n", params->localName, params->remoteName);
            CLEAN_PARAMS (FTP_BADSIZE);
        }
        sizeToGet = fileSize - localFileSize;

        appendOffset = fileSize - sizeToGet;
        snprintf (buffer, COMMAND_BUFFER_SIZE-1, "REST %d\r\n", appendOffset);
        _ftp_status ftp_result = ftpTransfert (params->ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
        if (FTP_FAILED (ftp_result))
        {
            FTP_ERROR ("Unable to set server offset\n");
            CLEAN_PARAMS (ftp_result);
        }
    }

    FTP_DEBUG ("Thread will connect to %s:%d\n", dataIp, dataPort);
    dataSocket = vp_os_malloc (sizeof (vp_com_socket_t));
    if (NULL == dataSocket)
    {
        FTP_ERROR ("Unable to allocate socket structure\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    dataSocket->type = VP_COM_CLIENT;
    dataSocket->protocol = VP_COM_TCP;
    dataSocket->port = dataPort;
    strncpy (dataSocket->serverHost, dataIp, VP_COM_NAME_MAXSIZE);
    dataSocket->is_multicast = 0;
    dataSocket->block = VP_COM_WAITALL;

    C_RESULT vp_result = vp_com_open_socket (dataSocket, &dataRead, &dataWrite);
    if (VP_FAILED (vp_result))
    {
        FTP_ERROR ("Unable to connect\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    int result = setSockTimeout ((int)dataSocket->priv, SOCK_TO_SEC, SOCK_TO_USEC);
    if (0 > result)
    {
        FTP_ERROR ("Unable to set data socket timeout\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    CHECK_ABORT;

    vp_os_memset (buffer, 0x0, COMMAND_BUFFER_SIZE);
    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "RETR %s\r\n", params->remoteName);
    ftp_result = ftpSend (params->ftp, buffer);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending RETR command\n");
        CLEAN_PARAMS (ftp_result);
    }

    int sizeGot = appendOffset;
    float percentGot = (sizeGot * 100.0) / (fileSize *1.0);
    params->callback (FTP_PROGRESS, (void *)&percentGot, params->ftp);

    filePart = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == filePart)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    if (1 == appendToFile)
    {
        localFile = fopen (params->localName, "ab");
    }
    else
    {
        localFile = fopen (params->localName, "wb");
    }
    if (NULL == localFile)
    {
        FTP_ERROR ("Unable to open dest file %s\n", params->localName);
        CLEAN_PARAMS (FTP_FAIL);
    }
    while (sizeGot < fileSize)
    {
        CHECK_ABORT;
        int bytes = MAX_SIZE_MSG-1;
        C_RESULT vp_result = dataRead (dataSocket, (uint8_t *)filePart, &bytes);
        if (VP_FAILED (vp_result))
        {
            FTP_ERROR ("Error while receiving data\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        if (0 >= bytes)
        {
            FTP_ERROR ("Recv timeout\n");
            CLEAN_PARAMS (FTP_TIMEOUT);
        }
        if (0 > fwrite (filePart, 1, bytes, localFile))
        {
            FTP_ERROR ("Unable to write to file\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        sizeGot += bytes;
        percentGot = (sizeGot * 100.0) / (fileSize * 1.0);
        params->callback (FTP_PROGRESS, (void *)&percentGot, params->ftp);
    }
    ftp_result = ftpRecv (params->ftp, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while getting answer\n");
        CLEAN_PARAMS (ftp_result);
    }
    repCode = getResponseCode (srvMsg);
    if (150 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 150)\n", repCode);
        CLEAN_PARAMS (FTP_FAIL);
    }

    /* Cleaning FTP */
    if (NULL != dataSocket)
    {
        vp_com_close_socket (dataSocket);
        vp_os_free (dataSocket);
        dataSocket = NULL;
    }
    _ftp_status loc226Status = waitFor226Answer (params->ftp);
    flushFtp (params->ftp);

    CLEAN_PARAMS (loc226Status);
}

DEFINE_THREAD_ROUTINE (ftpPut, param)
{
    char *srvMsg = NULL;
    char *buffer = NULL;
    char *filePart = NULL;
    vp_com_socket_t *dataSocket = NULL;
    Read dataRead = NULL;
    Write dataWrite = NULL;
    FILE *localFile = NULL;
    _ftp_put_param *params = (_ftp_put_param *)param;
    FTP_DEBUG ("Uploading %s to %s [resume : %c]\n", params->localName, params->remoteName, params->useResume ? 'y' : 'n');
    if (NULL == params->ftp)
    {
        FTP_ERROR ("FTP not open\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    flushFtp (params->ftp);

    buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    if (NULL == buffer)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "PASV\r\n");
    _ftp_status ftp_result = ftpTransfert (params->ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while entering passive mode\n");
        CLEAN_PARAMS (ftp_result);
    }
    int repCode = getResponseCode (srvMsg);
    if (227 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 227)\n", repCode);
        CLEAN_PARAMS (FTP_FAIL);
    }

    char dataIp [IP_STRING_SIZE] = {0};
    int dataPort = 0;
    getPassiveIpAndPort (params->ftp, srvMsg, dataIp, &dataPort, IP_STRING_SIZE);

    ftp_result = goToBinaryMode (params->ftp);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Unable to go to binary mode\n");
        CLEAN_PARAMS (ftp_result);
    }

    int localFileSize = getLocalFileSize (params->localName);
    if (0 >= localFileSize)
    {
        FTP_ERROR ("File %s does not exist on filesystem\n", params->localName);
        CLEAN_PARAMS (FTP_FAIL);
    }
    int fileSize = getFileSize (params->ftp, params->remoteName);
    int sizeToPut = localFileSize;
    int appendToFile = params->useResume;
    int appendOffset = 0;
    if (-1 == fileSize && 1 == params->useResume)
    {
        FTP_DEBUG ("File does not exist on server ... full upload\n");
        appendToFile = 0;
    }
    if (1 == appendToFile)
    {
        if (localFileSize == fileSize)
        {
            FTP_DEBUG ("File already uploaded\n");
            CLEAN_PARAMS (FTP_SAMESIZE);
        }
        else if (localFileSize < fileSize)
        {
            FTP_ERROR ("Distant file (%s) is greather than local file (%s)\n", params->remoteName, params->localName);
            CLEAN_PARAMS (FTP_BADSIZE);
        }
        sizeToPut = localFileSize - fileSize;

        appendOffset = localFileSize - sizeToPut;
        snprintf (buffer, COMMAND_BUFFER_SIZE-1, "REST %d\r\n", appendOffset);
        ftp_result = ftpTransfert (params->ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
        if (FTP_FAILED (ftp_result))
        {
            FTP_ERROR ("Unable to set server offset\n");
            CLEAN_PARAMS (ftp_result);
        }
    }

    FTP_DEBUG ("Thread will connect to %s:%d\n", dataIp, dataPort);
    dataSocket = vp_os_malloc (sizeof (vp_com_socket_t));
    if (NULL == dataSocket)
    {
        FTP_ERROR ("Unable to allocate socket structure\n");
        CLEAN_PARAMS (FTP_FAIL);
    }
    dataSocket->type = VP_COM_CLIENT;
    dataSocket->protocol = VP_COM_TCP;
    dataSocket->port = dataPort;
    strncpy (dataSocket->serverHost, dataIp, VP_COM_NAME_MAXSIZE);
    dataSocket->is_multicast = 0;
    dataSocket->block = VP_COM_DEFAULT;

    C_RESULT vp_result = vp_com_open_socket (dataSocket, &dataRead, &dataWrite);
    if (VP_FAILED (vp_result))
    {
        FTP_ERROR ("Unable to connect\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    int result = setSockTimeout ((int)dataSocket->priv, SOCK_TO_SEC, SOCK_TO_USEC);
    if (0 > result)
    {
        FTP_ERROR ("Unable to set data socket timeout\n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    CHECK_ABORT;

    vp_os_memset (buffer, 0x0, COMMAND_BUFFER_SIZE);
    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "STOR %s\r\n", params->remoteName);
    ftp_result = ftpTransfert (params->ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending command\n");
        CLEAN_PARAMS (ftp_result);
    }
    repCode = getResponseCode (srvMsg);
    if (150 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 150)\n", repCode);
        CLEAN_PARAMS (FTP_FAIL);
    }

    int sizeSent = appendOffset;
    float percentSend = (sizeSent * 100.0) / (localFileSize *1.0);
    params->callback (FTP_PROGRESS, (void *)&percentSend, params->ftp);

    int numberOfFullSendNeeded = (sizeToPut / (MAX_SIZE_MSG-1));
    int partialSendNeeded = (sizeToPut % (MAX_SIZE_MSG-1)); // Zero if not needed, non-zero if needed
    filePart = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == filePart)
    {
        FTP_ERROR ("Unable to alloc buffer \n");
        CLEAN_PARAMS (FTP_FAIL);
    }

    localFile = fopen (params->localName, "rb");
    if (NULL == localFile)
    {
        FTP_ERROR ("Unable to open source file %s\n", params->localName);
        CLEAN_PARAMS (FTP_FAIL);
    }
    fseek (localFile, appendOffset, SEEK_SET);

    int numSend = 0;
    for (numSend = 0; numSend < numberOfFullSendNeeded; numSend++)
    {
        CHECK_ABORT;
        int bytes = fread (filePart, 1, MAX_SIZE_MSG-1, localFile);
        if (MAX_SIZE_MSG-1 != bytes)
        {
            FTP_ERROR ("Unable to read from file\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        C_RESULT vp_result = dataWrite (dataSocket, (uint8_t *)filePart, &bytes);
        if (VP_FAILED (vp_result))
        {
            FTP_ERROR ("Unable to send data\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        if (MAX_SIZE_MSG-1 > bytes)
        {
            FTP_ERROR ("Send timeout\n");
            CLEAN_PARAMS (FTP_TIMEOUT);
        }
        sizeSent += bytes;
        percentSend = (sizeSent * 100.0) / (localFileSize * 1.0);
        params->callback (FTP_PROGRESS, (void *)&percentSend, params->ftp);
    }

    if (0 != partialSendNeeded)
    {
        CHECK_ABORT;
        vp_os_memset (filePart, 0x0, MAX_SIZE_MSG);
        int bytes = fread (filePart, 1, MAX_SIZE_MSG-1, localFile);
        FTP_DEBUG ("Read %d bytes\n", bytes);
        if (0 > bytes)
        {
            FTP_ERROR ("Unable to read from file\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        int sendBytes = bytes;
        C_RESULT vp_result = dataWrite (dataSocket, (uint8_t *)filePart, &sendBytes);
        if (VP_FAILED (vp_result))
        {
            FTP_ERROR ("Unable to send data\n");
            CLEAN_PARAMS (FTP_FAIL);
        }
        if (bytes > sendBytes)
        {
            FTP_ERROR ("Send timeout\n");
            CLEAN_PARAMS (FTP_TIMEOUT);
        }
        sizeSent += bytes;
        percentSend = (sizeSent * 100.0) / (localFileSize * 1.0);
        params->callback (FTP_PROGRESS, (void *)&percentSend, params->ftp);
    }


    /* Cleaning FTP */
    if (NULL != dataSocket)
    {
        vp_com_close_socket (dataSocket);
        vp_os_free (dataSocket);
        dataSocket = NULL;
    }
    _ftp_status loc226Status = waitFor226Answer (params->ftp);
    flushFtp (params->ftp);

    CLEAN_PARAMS (loc226Status);
}


_ftp_status
ftpPut (_ftp_t *ftp, const char *localName, const char *remoteName, int useResume, ftp_callback callback)
{
    THREAD_HANDLE putThread;
    ftp_callback actualCallback = (NULL != callback) ? callback : emptyCallback;
    if (NULL == ftp)
    {
        actualCallback (FTP_FAIL, NULL, ftp);
        return FTP_FAIL;
    }
    if (1 == ftp->opInProgress)
    {
        actualCallback (FTP_BUSY, NULL, ftp);
        return FTP_BUSY;
    }
    ftp->opInProgress = 1;

    _ftp_put_param *param = vp_os_malloc (sizeof (_ftp_put_param));

    if (NULL == param)
    {
        FTP_ERROR ("Unable to allocate thread param\n");
        actualCallback (FTP_FAIL, NULL, ftp);
        ftp->opInProgress = 0;
        return FTP_FAIL;
    }

    param->ftp = ftp;
    strncpy (param->localName, localName, FILE_NAME_MAX_SIZE);
    param->localName [FILE_NAME_MAX_SIZE-1] = '\0';
    strncpy (param->remoteName, remoteName, FILE_NAME_MAX_SIZE);
    param->remoteName [FILE_NAME_MAX_SIZE-1] = '\0';
    param->useResume = useResume;
    param->callback = actualCallback;
    param->fileList = NULL;
    _ftp_status threadReturn = FTP_SUCCESS;

    vp_os_thread_create (thread_ftpPut, (THREAD_PARAMS)param, &putThread);

    if (NULL == callback)
    {
        vp_os_thread_join (putThread);
        threadReturn = ftp->lastStatus;
    }

    return threadReturn;
}

_ftp_status
ftpGet (_ftp_t *ftp, const char *remoteName, const char *localName, int useResume, ftp_callback callback)
{
    THREAD_HANDLE getThread;
    ftp_callback actualCallback = (NULL != callback) ? callback : emptyCallback;
    if (NULL == ftp)
    {
        actualCallback (FTP_FAIL, NULL, ftp);
        return FTP_FAIL;
    }
    if (1 == ftp->opInProgress)
    {
        actualCallback (FTP_BUSY, NULL, ftp);
        return FTP_BUSY;
    }
    ftp->opInProgress = 1;

    _ftp_get_param *param = vp_os_malloc (sizeof (_ftp_get_param));

    if (NULL == param)
    {
        FTP_ERROR ("Unable to allocate thread param\n");
        actualCallback (FTP_FAIL, NULL, ftp);
        ftp->opInProgress = 0;
        return FTP_FAIL;
    }

    param->ftp = ftp;
    strncpy (param->localName, localName, FILE_NAME_MAX_SIZE);
    param->localName [FILE_NAME_MAX_SIZE-1] = '\0';
    strncpy (param->remoteName, remoteName, FILE_NAME_MAX_SIZE);
    param->remoteName [FILE_NAME_MAX_SIZE-1] = '\0';
    param->useResume = useResume;
    param->callback = actualCallback;
    param->fileList = NULL;
    _ftp_status threadReturn = FTP_SUCCESS;

    vp_os_thread_create (thread_ftpGet, (THREAD_PARAMS)param, &getThread);

    if (NULL == callback)
    {
        vp_os_thread_join (getThread);
        threadReturn = ftp->lastStatus;
    }

    return threadReturn;
}

_ftp_status
ftpList (_ftp_t *ftp, char **fileList,  ftp_callback callback)
{
    if (NULL == fileList && NULL == callback)
    {
        FTP_ERROR ("file list and callback pointer must not be both NULL\n");
        return FTP_FAIL;
    }
    THREAD_HANDLE listThread;
    ftp_callback actualCallback = (NULL != callback) ? callback : emptyCallback;
    if (NULL == ftp)
    {
        actualCallback (FTP_FAIL, NULL, ftp);
        return FTP_FAIL;
    }
    if (1 == ftp->opInProgress)
    {
        actualCallback (FTP_BUSY, NULL, ftp);
        return FTP_BUSY;
    }
    ftp->opInProgress = 1;

    _ftp_list_param *param = vp_os_malloc (sizeof (_ftp_list_param));

    if (NULL == param)
    {
        FTP_ERROR ("Unable to allocate thread param\n");
        actualCallback (FTP_FAIL, NULL, ftp);
        ftp->opInProgress = 0;
        return FTP_FAIL;
    }

    param->fileList = vp_os_malloc (sizeof (char) * LIST_BUFFER_BLOCKSIZE);
    if (NULL == param->fileList)
    {
        FTP_ERROR ("Unable to allocate list buffer\n");
        actualCallback (FTP_FAIL, NULL, ftp);
        ftp->opInProgress = 0;
        vp_os_free (param);
        return FTP_FAIL;
    }

    param->ftp = ftp;
    param->listSize = LIST_BUFFER_BLOCKSIZE;
    param->callback = actualCallback;
    _ftp_status threadReturn = FTP_SUCCESS;

    vp_os_thread_create (thread_ftpList, (THREAD_PARAMS)param, &listThread);

    if (NULL == callback)
    {
        vp_os_thread_join (listThread);
        threadReturn = ftp->lastStatus;
        if (FTP_SUCCESS == threadReturn)
        {
            *fileList = ftp->lastFileList;
            ftp->lastFileList = NULL;
        }
    }

    return threadReturn;
}

_ftp_status
ftpRemove (_ftp_t *ftp, const char *remoteName)
{
    _ftp_status ftp_result = FTP_FAIL;

    if (NULL == remoteName)
    {
        FTP_ERROR ("remoteName must not be a NULL pointer\n");
        return FTP_FAIL;
    }
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    char *buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == buffer || NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return FTP_FAIL;
    }

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "DELE %s\r\n", remoteName);
    ftp_result = ftpTransfert (ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the delete command\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return ftp_result;
    }
    int repCode = getResponseCode (srvMsg);
    if (250 != repCode && 550 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 250 or 550)\n", repCode);
        ftp_result = FTP_FAIL;
    }
    vp_os_free (buffer);
    vp_os_free (srvMsg);
    return ftp_result;
}

_ftp_status
ftpRename (_ftp_t *ftp, const char *origin, const char *dest)
{
    _ftp_status ftp_result = FTP_FAIL;

    if (NULL == origin ||
        NULL == dest)
    {
        FTP_ERROR ("origin and dest pointers must not be NULL\n");
        return FTP_FAIL;
    }
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    char *buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == buffer || NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return FTP_FAIL;
    }

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "RNFR %s\r\n", origin);
    ftp_result = ftpTransfert (ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the RNFR command\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return ftp_result;
    }
    int repCode = getResponseCode (srvMsg);
    if (350 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 350)\n", repCode);
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        ftp_result = FTP_FAIL;
        return ftp_result;
    }

    vp_os_memset (buffer, 0x0, COMMAND_BUFFER_SIZE);
    vp_os_memset (srvMsg, 0x0, MAX_SIZE_MSG);

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "RNTO %s\r\n", dest);
    ftp_result = ftpTransfert (ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the RNTO command\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return ftp_result;
    }
    repCode = getResponseCode (srvMsg);
    if (250 == repCode) // Rename worked
    {
        ftp_result = FTP_SUCCESS;
    }
    else if (550 == repCode) // Source file don't exist
    {
        FTP_DEBUG ("File %s doest not exist on FTP\n", origin);
        ftp_result = FTP_SAMESIZE;
    }
    else
    {
        FTP_ERROR ("Bad response from server (%d, expected 250 or 550)\n", repCode);
        ftp_result = FTP_FAIL;
    }
    vp_os_free (buffer);
    vp_os_free (srvMsg);
    return ftp_result;
}

_ftp_status
ftpCd (_ftp_t *ftp, const char *nextDir)
{
    _ftp_status ftp_result = FTP_FAIL;

    if (NULL == nextDir)
    {
        FTP_ERROR ("nextDir must not be NULL\n");
        return FTP_FAIL;
    }
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    char *buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == buffer || NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return FTP_FAIL;
    }

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "CWD %s\r\n", nextDir);
    ftp_result = ftpTransfert (ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the CWD command\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return ftp_result;
    }
    int repCode = getResponseCode (srvMsg);
    if (250 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 250)\n", repCode);
        ftp_result = FTP_FAIL;
    }
    vp_os_free (buffer);
    vp_os_free (srvMsg);
    return ftp_result;
}

_ftp_status
ftpPwd (_ftp_t *ftp, char *workingDir, int wdLen)
{
    _ftp_status ftp_result = FTP_FAIL;

    if (NULL == workingDir)
    {
        FTP_ERROR ("workingDir must not be NULL\n");
        return FTP_FAIL;
    }
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        return FTP_FAIL;
    }

    ftp_result = ftpTransfert (ftp, "PWD\r\n\0", srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the CWD command\n");
        vp_os_free (srvMsg);
        return ftp_result;
    }
    int repCode = getResponseCode (srvMsg);
    if (257 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 257)\n", repCode);
        ftp_result = FTP_FAIL;
    }
    else
    {
        int pwdStartIndex = 0;
        int pwdEndIndex = 0;
        for (pwdStartIndex = 0; (pwdStartIndex < MAX_SIZE_MSG) && (srvMsg[pwdStartIndex] != '\"'); pwdStartIndex++);
        for (pwdEndIndex = pwdStartIndex+1; (pwdEndIndex < MAX_SIZE_MSG) && (srvMsg[pwdEndIndex] != '\"'); pwdEndIndex++);
        if (MAX_SIZE_MSG == pwdStartIndex ||
            MAX_SIZE_MSG == pwdEndIndex)
        {
            FTP_ERROR ("FTP Answer does not conains PWD\n");
            ftp_result = FTP_FAIL;
        }
        else
        {
            int srvLen = pwdEndIndex - (pwdStartIndex + 1);
            int totalLen = (srvLen < wdLen) ? srvLen : wdLen;
            strncpy (workingDir, &(srvMsg[pwdStartIndex+1]), totalLen);
            FTP_DEBUG ("PWD is %s\n", workingDir);
        }
    }
    vp_os_free (srvMsg);
    return ftp_result;
}

_ftp_status
ftpMkdir (_ftp_t *ftp, const char *dirName)
{
    _ftp_status ftp_result = FTP_FAIL;

    if (NULL == dirName)
    {
        FTP_ERROR ("dirName must not be NULL\n");
        return FTP_FAIL;
    }
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    char *buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == buffer || NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return FTP_FAIL;
    }

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "MKD %s\r\n", dirName);
    ftp_result = ftpTransfert (ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the MKD command\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return ftp_result;
    }
    int repCode = getResponseCode (srvMsg);
    if (257 != repCode)
    {
        FTP_ERROR ("Bad response from server (%d, expected 257)\n", repCode);
        ftp_result = FTP_FAIL;
    }
    vp_os_free (buffer);
    vp_os_free (srvMsg);
    return ftp_result;
}

_ftp_status
ftpRmdir (_ftp_t *ftp, const char *dirName)
{
    _ftp_status ftp_result = FTP_FAIL;

    if (NULL == dirName)
    {
        FTP_ERROR ("dirName must not be NULL\n");
        return FTP_FAIL;
    }
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
        return FTP_FAIL;
    }

    char *buffer = vp_os_calloc (COMMAND_BUFFER_SIZE, 1);
    char *srvMsg = vp_os_calloc (MAX_SIZE_MSG, 1);
    if (NULL == buffer || NULL == srvMsg)
    {
        FTP_ERROR ("Unable to alloc buffer\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return FTP_FAIL;
    }

    snprintf (buffer, COMMAND_BUFFER_SIZE-1, "RMD %s\r\n", dirName);
    ftp_result = ftpTransfert (ftp, buffer, srvMsg, MAX_SIZE_MSG-1);
    if (FTP_FAILED (ftp_result))
    {
        FTP_ERROR ("Error while sending the RMD command\n");
        vp_os_free (buffer);
        vp_os_free (srvMsg);
        return ftp_result;
    }
    int repCode = getResponseCode (srvMsg);
    if (250 == repCode) // Deleted
    {
        FTP_DEBUG ("Successfully deleted %s directory\n", dirName);
        ftp_result = FTP_SUCCESS;
    }
    else if (550 == repCode) // Didn't exist / not empty
    {
        FTP_DEBUG ("Did not delete directory %s : did not exist or was not empty\n", dirName);
        ftp_result = FTP_BADSIZE;
    }
    else
    {
        FTP_ERROR ("Bad response from server (%d, expected 250 or 550)\n", repCode);
        ftp_result = FTP_FAIL;
    }
    vp_os_free (buffer);
    vp_os_free (srvMsg);
    return ftp_result;
}

_ftp_status ftpAbort (_ftp_t *ftp)
{
    _ftp_status retVal = FTP_FAIL;
    if (NULL == ftp)
    {
        FTP_ERROR ("FTP not open\n");
    }
    else
    {
        if (1 == ftp->opInProgress)
        {
            ftp->abortCurrentOp = 1;
            retVal = FTP_SUCCESS;
        }
        else
        {
            retVal = FTP_SAMESIZE;
        }
    }
    return retVal;
}
