// ==========================================================================
// Author:  Yee Hsu
// Date:    9/5/2008
// File:    FTP.cpp
//
// Desc:    FTP wrapper that does simple FTP API calls. Now we have an FTP
//          object we can call and use to make FTP connections, transfer
//          files, and download files using this simple API
// ==========================================================================

#include "stdafx.h"
#include "CFTP.h"

// ==========================================================================
// Identifier:  CFTP()
//
// Description: CFTP Constructor
// ==========================================================================

CFTP::CFTP()
{
    this->sFileContents         = "";
    this->hInternet             = NULL;
    this->hConnection           = NULL;
    this->hFileHandle           = NULL;
    this->dwFileSize            = NULL;

    this->lpThreadId            = 0L;
    this->lpThreadExitCode      = 0L;
    this->lpThreadIdleMSec      = 60000L;   // Default Timeout: 1 minute
    this->bFTPSuccess           = false;
}

// ==========================================================================
// Identifier:  CFTP()
//
// Description: CFTP Destructor
// ==========================================================================

CFTP::~CFTP()
{
    this->sFileContents         = "";
    this->hInternet             = NULL;
    this->hConnection           = NULL;
    this->hFileHandle           = NULL;
    this->dwFileSize            = NULL;
}

// ==========================================================================
// Identifier:  InitFTPInfo()
//
// Description: Initialize FTP info
// ==========================================================================

void CFTP::InitFTPInfo(const FTPInfo ftpInfo)
{
    this->ftpInfo = ftpInfo;
}

// ==========================================================================
// Identifier:  InternetOpen()
//
// Description: Open an FTP connection
// ==========================================================================

bool CFTP::InternetOpen(void)
{
    hInternet = ::InternetOpen("FTP Connection", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

    if( !hInternet )
    {
        InternetClose();
        return false;
    }
    return true;
}

// ==========================================================================
// Identifier:  InternetClose()
//
// Description: Close an FTP connection
// ==========================================================================

bool CFTP::InternetClose(void)
{
    if( hFileHandle )
        InternetCloseHandle( hFileHandle );

    if( hConnection )
        InternetCloseHandle( hConnection );

    if( hInternet )
        InternetCloseHandle( hInternet );

    return true;
}

// ==========================================================================
// Identifier:  FtpConnect()
//
// Description: Connection to an FTP session
// ==========================================================================

bool CFTP::FtpConnect(void)
{
    hConnection = ::InternetConnect(hInternet, ftpInfo.lpszServerName, INTERNET_DEFAULT_FTP_PORT, ftpInfo.lpszUserName, ftpInfo.lpszPassword, INTERNET_SERVICE_FTP, NULL, NULL);

    if( !hConnection )
    {
        InternetClose();
        return false;
    }
    return true;
}

// ==========================================================================
// Identifier:  FtpSetCurrentDirectory()
//
// Description: Sets the current directory for an FTP connection
// ==========================================================================

bool CFTP::FtpSetCurrentDirectory(void)
{
    if ( !::FtpSetCurrentDirectory( hConnection, ftpInfo.lpszDirectory) )
    {
        InternetClose();
        return false;
    }
    return true;
}

// ==========================================================================
// Identifier:  FtpFindFirstFile()
//
// Description: Finds the first file
// ==========================================================================

bool CFTP::FtpFindFirstFile(void)
{
    WIN32_FIND_DATA lpFindFileDate = {0};

    hFileHandle = ::FtpFindFirstFile(hConnection, ftpInfo.lpszRemoteFile, &lpFindFileDate, INTERNET_FLAG_RELOAD, NULL );

    if ( !hFileHandle )
    {
        InternetClose();
        return false;
    }

    if (!FileTimeToSystemTime( &lpFindFileDate.ftLastWriteTime, &stSysTime ))
    {
        InternetClose();
        return false;
    }
    return true; 
}

// ==========================================================================
// Identifier:  FtpOpenFile()
//
// Description: Opens the file
// ==========================================================================

bool CFTP::FtpOpenFile(void)
{
    hFileHandle = ::FtpOpenFile( hConnection, ftpInfo.lpszRemoteFile, GENERIC_READ, FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_RELOAD, NULL );

    if ( !hFileHandle )
    {
        InternetClose();
        return false;
    }
    return true;    
}

// ==========================================================================
// Identifier:  FtpReadFile()
//
// Description: Reads the file
// ==========================================================================

bool CFTP::FtpReadFile(void)
{
    bool  bRet;
    DWORD dwFileSizeHigh;

    char* szBuffer = NULL;
    szBuffer = new char[this->dwFileSize + 1];      // Extra character reserved for terminating NULL character
    memset(szBuffer, 0, this->dwFileSize + 1);      // init the array with \0
    this->sFileContents.clear();

    if ( InternetReadFile( hFileHandle, ( LPVOID ) szBuffer, this->dwFileSize, &dwFileSizeHigh ) == FALSE )
    {
        InternetClose();
        bRet = false;
    }
    else
    {
        this->sFileContents = szBuffer;
        bRet = true;
    }

    if (szBuffer)
        delete szBuffer;

    return bRet;    
}

// ==========================================================================
// Identifier:  FtpGetFileSize()
//
// Description: Gets the file size
// ==========================================================================

bool CFTP::FtpGetFileSize(void)
{
    DWORD dwFileSizeHigh;
    dwFileSize = ::FtpGetFileSize( hFileHandle, &dwFileSizeHigh );
    return true;
}

// ==========================================================================
// Identifier:  FtpPutFile()
//
// Description: Uploads a file
// ==========================================================================

bool CFTP::FtpPutFile(void)
{
    if( !::FtpPutFile( hConnection, ftpInfo.lpszLocalFile, ftpInfo.lpszRemoteFile, FTP_TRANSFER_TYPE_BINARY, NULL ) )
    {
        InternetClose();
        return false;
    }
    return true;    
}

// ==========================================================================
// Identifier:  FtpGetFile()
//
// Description: Downloads a file
// ==========================================================================

bool CFTP::FtpGetFile(void)
{
    if( !::FtpGetFile( hConnection, ftpInfo.lpszRemoteFile, ftpInfo.lpszLocalFile, FALSE, FILE_ATTRIBUTE_NORMAL, FTP_TRANSFER_TYPE_BINARY, NULL ) )
    {
        InternetClose();
        return false;
    }
    return true;    
}

// ==========================================================================
// Identifier:  UploadFile()
//
// Description: Uploads a file
// ==========================================================================

bool CFTP::UploadFile(const FTPInfo ftpInfo)
{
    this->InitFTPInfo(ftpInfo);

    if (!this->InternetOpen())
        return false;

    if (!this->InternetConnect())
        return false;

    if (!this->FtpSetCurrentDirectory())
        return false;

    if (!this->FtpPutFile())
        return false;

    this->InternetClose();
    return true;
}

// ==========================================================================
// Identifier:  DownloadFile()
//
// Description: Downloads a file
// ==========================================================================

bool CFTP::DownloadFile(const FTPInfo ftpInfo)
{
    this->InitFTPInfo(ftpInfo);

    if (!this->InternetOpen())
        return false;

    if (!this->InternetConnect())
        return false;

    if (!this->FtpSetCurrentDirectory())
        return false;

    if (!this->FtpGetFile())
        return false;

    this->InternetClose();
    return true;
}

// ==========================================================================
// Identifier:  DownloadFile()
//
// Description: Gets a file size
// ==========================================================================

bool CFTP::GetFileSize(const FTPInfo ftpInfo, int& nFileSize)
{
    this->InitFTPInfo(ftpInfo);

    if (!this->InternetOpen())
        return false;

    if (!this->InternetConnect())
        return false;

    if (!this->FtpSetCurrentDirectory())
        return false;

    if (!this->FtpOpenFile())
        return false;
    
    if (!this->FtpGetFileSize())
        return false;

    nFileSize = this->dwFileSize;
    this->InternetClose();

    return true;
}

// ==========================================================================
// Identifier:  GetFileDate()
//
// Description: Gets a file date
// ==========================================================================

bool CFTP::GetFileDate(const FTPInfo ftpInfo, SYSTEMTIME& stSysTime)
{
    this->InitFTPInfo(ftpInfo);

    if (!this->InternetOpen())
        return false;

    if (!this->InternetConnect())
        return false;

    if (!this->FtpSetCurrentDirectory())
        return false;

    if (!this->FtpFindFirstFile())
        return false;
    
    stSysTime = this->stSysTime;
    this->InternetClose();

    return true;
}

// ==========================================================================
// Identifier:  ReadFileContents()
//
// Description: Reads the contents of a file
// ==========================================================================

bool CFTP::ReadFileContents(const FTPInfo ftpInfo, std::string& sFileContents)
{
    this->InitFTPInfo(ftpInfo);

    if (!this->InternetOpen())
        return false;

    if (!this->InternetConnect())
        return false;

    if (!this->FtpSetCurrentDirectory())
        return false;

    if (!this->FtpOpenFile())
        return false;
    
    if (!this->FtpGetFileSize())
        return false;

    if (!this->FtpReadFile())
        return false;

    sFileContents = this->sFileContents;
    this->InternetClose();

    return true;
}

// ==========================================================================
// Identifier:  GetFileInfo()
//
// Description: Gets information about a file
// ==========================================================================

bool CFTP::GetFileInfo(const FTPInfo ftpInfo, std::string& sFileContents, SYSTEMTIME& stSysTime, int* nFileSize)
{
    this->InitFTPInfo(ftpInfo);

    if (!this->InternetOpen())
        return false;

    if (!this->InternetConnect())
        return false;

    if (!this->FtpSetCurrentDirectory())
        return false;

    if (!this->FtpFindFirstFile())
        return false;   

    if (!this->FtpOpenFile())
        return false;
    
    if (!this->FtpGetFileSize())
        return false;

    if (!this->FtpReadFile())
        return false;

    if (nFileSize)
        *nFileSize  = this->dwFileSize;

    stSysTime       = this->stSysTime;
    sFileContents   = this->sFileContents;

    this->InternetClose();

    return true;
}

/* ***************************************  Threaded FTP Codes Here ****************************************** */

// ==========================================================================
// Identifier:  InternetConnect()
//
// Description: Makes an FTP connection
// ==========================================================================

bool CFTP::InternetConnect(void)
{
    this->bFTPSuccess = false;

    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadStart, this, 0, &lpThreadId);
    
    if ( WaitForSingleObject(hThread, lpThreadIdleMSec * FTP_TIMEOUT_MINUTES) == WAIT_TIMEOUT)
    {
        this->InternetClose();
        WaitForSingleObject(hThread, lpThreadIdleMSec);
        this->bFTPSuccess = false;
    }
    CloseHandle(hThread);

    return this->bFTPSuccess;  // returns TRUE (in mult-threaded code) when FTP request success
}

// ==========================================================================
// Identifier:  ThreadFTPRequestTimeOut()
//
// Description: Threaded FTP connection
// ==========================================================================

bool WINAPI CFTP::ThreadFTPRequestTimeOut()
{
    bFTPSuccess = false;  

    hConnection = ::InternetConnect(hInternet, ftpInfo.lpszServerName, INTERNET_DEFAULT_FTP_PORT, ftpInfo.lpszUserName, ftpInfo.lpszPassword, INTERNET_SERVICE_FTP, NULL, NULL);

    if ( !hConnection )
        InternetClose();
    else
        this->bFTPSuccess = true;

    ExitThread(lpThreadExitCode);
    return bFTPSuccess;
}

// ==========================================================================
// Identifier:  ThreadStart()
//
// Description: Starts a FTP connection in its own thread
// ==========================================================================

DWORD CFTP::ThreadStart(CFTP* pFTP)
{
    pFTP->ThreadFTPRequestTimeOut();
    return 0;
}