/**
  \class CFTPClient
  \brief A low-level, asynchronous FTP client based on RFC 959. 

  This class implements an FTP client. It allows the usual things done
  through FTP (uploading, downloading, dir-listing, etc), but at a very
  basic level.
  
  It may be a bit difficult to use, because it is so low level.  It isn't as
  simple as "open ftp server, do dir-listing, upload file, close". All these
  functions are there, but they are asynchronuous. That is, they return
  immediately and don't wait for the end result. Nor can you queue commands:
  only one command can be executed at a time. This means you will have to do
  the high-level protocol stuff in your own class.  On the other hand, it
  allows you to accurately follow the progress of your commands. Think of it
  as a wrapper around the FTP protocol itself, with some helper functions
  for the data transfers.

  With this class you have to make a distinction between 'commands' and
  'state'. A command is just that: the user demands a certain action, and
  the class executes it. A string is sent to the FTP server to perform the
  command; if a data stream is needed that is opened as well.
  
  A command will cause various changes in the class that can be tracked
  \through the ref StateChange signal. For example, uploading a file will
  cause the following states: SendingPort, WaitData, Transfer, ClosingData,
  Idle (or Failed). It reflects the various steps necessary by the FTP
  protocol to perform an actual filetransfer. It is up to the calling
  class to interpret these responses (most aren't really interesting to the
  user anyway).
  
  All commands will end in either "Idle" (== success) or "Failed" state.
  "Failed" indicates something went wrong; the \b result code from
  \ret StateChange contains the numeric FTP response code that explains
  the error. "Idle", which also doubles as "Success", indicates completion
  of the command without error.
    
  Note that it is possible for a state to be set multiple times in a row.
  If, for example, Login was succesful and was followed by a "Change Dir"
  that also succeeds, StateChange will be emitted twice with state "Idle".
  This is okay; 2 commands were issued, so you get two StateChange(Idle) 
  (possibly intermingled with other StateChange()s).

  Most commands listed below show a short state transition diagram, so
  you know what StateChange()s to expect.

*/

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>


#include "FTPClient.h"

const char *CFTPClient::CommandStr[] =
{
   "Nop",
   "Login",
   "Logout",
   "SetType",
   "ChangeDir",
   "ListDir",
   "Upload",
   "Download",
   "Rename",
   
   "Passive",
   "Destroy",
};

const char *CFTPClient::StateStr[] = 
{
   "NOC",
   "Connecting",
   "Connected",
   "Login", 
   "Authenticate",

   "Idle",

   "SendingPort",
   "WaitData",
   "Transfer",
   "ClosingData",

   "Failed",
   "Unknown",
};


CFTPClient::CFTPClient()
{
   CtrlFD = ListenFD = DataFD = LocalFileFD = -1;
   pControlPipe = NULL;
   pListenPipe = NULL;
   pDataPipe = NULL;

   inputbuffer = new char[1024];
   linebuffer = new char[1024];
   responsebuffer = new char[1024];
   LineLen = 0;
   RespLen = 0;
   TBufLen = 32768;
   transferbuffer = new uchar[TBufLen];
   TransferMethod = -1;
   m_Passive = false;

   CurrentCommand = cmdNop;
   CurrentState = stNOC;
}

CFTPClient::~CFTPClient()
{
   // SendQuit();
   CloseAllFD();
   delete inputbuffer;
   delete linebuffer;
   delete responsebuffer;
   delete transferbuffer;
}

// private

void CFTPClient::InitBuffers()
{
   LastChar = '\0';
   LineLen = RespLen = 0;
   Response = 0;
   MultiLine = FALSE;
   TransferMethod = 0; // ASCII is default
   
   TBufTail = TBufHead = TBufUsed = 0;
}

/**
  \brief Closes all TCP connections immediately
*/

void CFTPClient::CloseAllFD()
{
qDebug("CFTPClient::CloseAllFD()");
   CloseData();
   CloseListen();
   CloseFile();
   CloseControl();
}

/**
  \brief Try to connect to the server's FTP control port.
*/
int CFTPClient::SetupControl()
{
   CtrlFD = socket(PF_INET, SOCK_STREAM, 0);
   if (CtrlFD < 0)
     return -errno;

   // True async operation requires a NO_DELAY on the socket, and some additional code
   // fcntl(CtrlFD, NO_DELAY);
   // CurrentState = Connecting

   if (::connect(CtrlFD, (struct sockaddr *)&ServerAddress, sizeof(ServerAddress)) < 0)
     return -errno;
qDebug("CFTPClient::SetupControl() Opened control fd [%d].", CtrlFD);
   pControlPipe = new QSocketNotifier(CtrlFD, QSocketNotifier::Read);
   connect(pControlPipe, SIGNAL(activated(int)), this, SLOT(ControlRead(int)));
   return 0;
}


/**
  \brief Shut down connection to FTP server control port. 
*/
void CFTPClient::CloseControl()
{
qDebug("CFTPClient::CloseControl() Shutting down control fd [%d].", CtrlFD);  
   delete pControlPipe;
   pControlPipe = NULL;
   if (CtrlFD >= 0) ::close(CtrlFD);
   CtrlFD = -1;
   emit ControlPortClosed();
}

/** 
  \brief Sets up our incoming port where we will listen 
*/
int CFTPClient::SetupListen()
{
   if (ListenFD < 0) {
     ListenFD = socket(PF_INET, SOCK_STREAM, 0);
     if (ListenFD < 0)
       return -errno;

     if (listen(ListenFD, 5) < 0) // Will get assigned a port number
       return -errno;

     pListenPipe = new QSocketNotifier(ListenFD, QSocketNotifier::Read);
     connect(pListenPipe, SIGNAL(activated(int)), this, SLOT(ListenRead(int)));
qDebug("CFTPClient::SetupListen() Opened listen fd [%d].", ListenFD);
   }
   return 0;
}

/**
  \brief Close our listen port
*/
void CFTPClient::CloseListen()
{
qDebug("CFTPClient::CloseListen() Shutting down listen fd [%d].", ListenFD);
   delete pListenPipe;
   pListenPipe = NULL;
   if (ListenFD >= 0) ::close(ListenFD);
   ListenFD = -1;
}

/**
  \brief Set up the local file for transfer 
*/
int CFTPClient::SetupLocal(const QString &filename, bool write)
{
   if (LocalFileFD >= 0) {
     qWarning("Error: local file fd still open [%d]!\n", LocalFileFD);
     ::close(LocalFileFD);
   }
   if (write)
     LocalFileFD = ::open((const char *)filename, O_WRONLY | O_CREAT);
   else
     LocalFileFD = ::open((const char *)filename, O_RDONLY);
qDebug("CFTPClient::SetupLocal() Opened local file fd [%d].", LocalFileFD);
   if (LocalFileFD < 0)
     return -errno;
   return 0;
}

/**
  \brief Close fd of file on disk
*/  
void CFTPClient::CloseFile()
{
qDebug("CFTPClient::CloseFile() Shutting down local file fd [%d].", LocalFileFD); 
   if (LocalFileFD >= 0) ::close(LocalFileFD);
   LocalFileFD = -1;
}


/** \brief Make a data pipe in active mode (that is, we listen)
*/
int CFTPClient::SetupDataActive()
{
   int ret;
   socklen_t DLen;
   struct sockaddr_in DAddr;

   ret = SetupListen();

   if (ret < 0) {
     return ret;
   }
   
   // Let's find out our IP address; use CtrlFD, not ListenFD, since ListenFD
   // isn't connected (and never will be)
   DLen = sizeof(MyAddr);
   if (getsockname(CtrlFD, (struct sockaddr *)&MyAddr, &DLen) < 0) {
     ret = -errno;
     CloseListen();
     return ret;
   }

   // Find the port we just bounded to. This does use ListenFD (confused yet?)
   DLen = sizeof(DAddr);
   if (getsockname(ListenFD, (struct sockaddr *)&DAddr, &DLen) < 0) {
     ret = -errno;
     CloseListen();
     return ret;
   }

qDebug("CFTPClient::SetupDataActive() getsockname: addr = %s, port = %d", inet_ntoa(MyAddr.sin_addr), ntohs(DAddr.sin_port));

   /* Tell the other side we have a connection ready */
   TotalTransfered = 0;
   SetState(stSendingPort);
   SendPort(MyAddr, ntohs(DAddr.sin_port));
   return 0;
}

#define DATA_ASYNC

int CFTPClient::SetupDataPassive()
{
   int ret;
   socklen_t DLen;
   struct sockaddr_in DAddr;

qDebug(">> CFTPClient::SetupDataPassive()");
   // In passive mode we connect to the other side; the address was given with the PASV response.
   DAddr.sin_family = AF_INET;
   DAddr.sin_addr.s_addr = htonl(m_PassiveAddress);
   DAddr.sin_port = htons(m_PassivePort);
   DLen = sizeof(DAddr);
   
   DataFD = socket(PF_INET, SOCK_STREAM, 0);
   if (DataFD < 0) {
     SetState(stFailed);
     return -errno;
   }
   
   TotalTransfered = 0;
   /* This is done asynchronuously */
#ifdef DATA_ASYNC   
   if (fcntl(DataFD, F_SETFL, O_NONBLOCK) < 0)
     qDebug("fcntl() failed: %d\n", errno);
#endif
     
   ret = ::connect(DataFD, (struct sockaddr *)&DAddr, DLen);
   if (ret < 0) {
     if (errno == EINPROGRESS)
       SetState(stWaitData);
     else {
       SetState(stFailed);
       return -errno;
     }
   }

#ifdef DATA_ASYNC
   pDataPipe = new QSocketNotifier(DataFD, QSocketNotifier::Write);
   connect(pDataPipe, SIGNAL(activated(int)), this, SLOT(DataConnect(int)));
#else
   HookData();
#endif
   return 0;
}


/**
  \brief Close data socket
*/  
void CFTPClient::CloseData()
{
qDebug("CFTPClient::CloseData() Shutting down data fd [%d].", DataFD);
   delete pDataPipe;
   pDataPipe = NULL;
   if (DataFD >= 0) ::close(DataFD);
   DataFD = -1;
}

void CFTPClient::HookData()
{
   delete pDataPipe;
   pDataPipe = NULL;

   if (Direction) {
     pDataPipe = new QSocketNotifier(DataFD, QSocketNotifier::Write);
     connect(pDataPipe, SIGNAL(activated(int)), this, SLOT(DataWrite(int)));
   }
   else {
     pDataPipe = new QSocketNotifier(DataFD, QSocketNotifier::Read);
     connect(pDataPipe, SIGNAL(activated(int)), this, SLOT(DataRead(int)));
   }
   if (CurrentState != stWaitData)
     pDataPipe->setEnabled(FALSE); // wait until 125/150 response
}


/**
  \brief Asynchronuous data connect 
*/
void CFTPClient::DataConnect(int fd)
{
   int ret, val;
   socklen_t vallen;

qDebug("CFTPClient::DataConnect(%d)", fd);
   if (fd != DataFD) {
     qWarning("Illegal connect.");
     CloseData();
     return;
   }
   vallen = sizeof(val);
   ret = getsockopt(DataFD, SOL_SOCKET, SO_ERROR, &val, &vallen);
   if (ret < 0) {
     qDebug("getsockopt failed.");
     CloseData();
     return;
   }
   if (vallen == sizeof(val) && val == 0) {
     // Connect succeeded
qDebug("Re-setting socketnotifier");
     HookData();
     SetState(stTransfer);
   }
   else {
     qDebug("Not connected: %d", val);
     CloseData();
     SetState(stFailed);
   }
}



/**
  \brief Set new state en emit signal
*/  
void CFTPClient::SetState(States new_op, int response)
{
   CurrentState = new_op;
   emit StateChange(CurrentCommand, new_op, response < 0 ? Response : response, QString(responsebuffer));
}

/**
  \brief Takes a line from the control port input buffer and unfolds it.
*/  
void CFTPClient::InterpretLine()
{
   int l, resp;
   
//printf("InterpretLine(): %s\n", linebuffer);
   l = strlen(linebuffer);
   if (l == 0) {
     printf("InterpretLine(): short line.\n");
     return;
   }
   
   // See if we have 3 digits at the beginning of the line
   resp = 0;
   if (l >= 3) {
     if (isdigit(linebuffer[0]) && isdigit(linebuffer[1]) && isdigit(linebuffer[2]))
       resp = 100 * (linebuffer[0] - '0') + 10 * (linebuffer[1] - '0') + (linebuffer[2] - '0');
   }
   if (resp)
     Response = resp;

   if (MultiLine) {
     if (resp) {
//qDebug("Multiline: found response %d.", Response);
       MultiLine = FALSE;
     }
     else {
//qDebug("Multiline: appending");
       if (RespLen < 1023)
         responsebuffer[RespLen++] = '\n';
       if (RespLen + l > 1023) // prevent overflows
         l = 1023 - RespLen;
       if (l) 
         strncpy(responsebuffer + RespLen, linebuffer, l);
       RespLen += l;
     } // ..if resp
   }
   else { // Not multiline
     if (resp == 0) 
       printf("InterpretLine(): line without code and not in multiline mode.\n");
     else {
// printf("Response code %d\n", resp);
       if (l > 3) {
         if (linebuffer[3] == '-')
           MultiLine = TRUE;
         strcpy(responsebuffer, linebuffer + 4); // skip code & space
         RespLen = l - 4;
       }
     }
   }
   if (!MultiLine) {
     responsebuffer[RespLen] = '\0'; // terminate properly
     InterpretResponse();
     RespLen = 0;
   }
}


/**
  \brief Takes a complete, unfolded line from the control port and interprets the response
*/  
void CFTPClient::InterpretResponse()
{
qDebug(">> CFTPClient::InterpretResponse(%d, \"%s\"), CurrentCmd = %d (\"%s\")", Response, responsebuffer, CurrentCommand, CommandStr[CurrentCommand]);
   switch(Response) {
     case 120: // Service ready in nnn minutes
       SetState(stFailed);
       CloseControl();
       SetState(stNOC);
       break;
     
     case 125: // port is open
       //SetState(stTransfer);
       //break;

     case 150: // Transfer is about to start
       /* There is a continuous race condition between the 150 response
          and the actual opening of the data connection. It could be
          the 150 arrives after the datapipe has been opened (happens
          often on a local LAN). 
        */
       if (CurrentCommand != cmdListDir && CurrentCommand != cmdUpload && CurrentCommand != cmdDownload)
         qWarning("Received 150 repose, but not expecting any data.");
       if (pDataPipe) {
         qDebug("CFTPClient::InterpretResponse() 150 Enabling data pipe.");
         pDataPipe->setEnabled(TRUE);
       }
       else {
         SetState(stWaitData);
         qDebug("CFTPClient::InterpretResponse() Got 150 response but no data pipe yet.\n");
       }
       break;
     
     case 200: // General 'okay' response
       switch(CurrentCommand) {
         case cmdSetType:
           // host accepted type
           CurrentCommand = cmdNop;
           SetState(stIdle);
           break;
           
         case cmdUpload:
           StartSending();
           break;

         case cmdDownload:
           StartReceiving();
           break;

         case cmdListDir:
           // Send the actual command 
           SendList();
           break;
           
         case cmdChangeDir:
           qWarning("Received 200 response for CWD command.");
           CurrentCommand = cmdNop;
           SetState(stIdle);
           break;
           
         default:
           qWarning("Unexpected 200 response");
           CurrentCommand = cmdNop;
           SetState(stUnknown);
           break;
       }    // ..switch CurrentState
       break;
   
     case 220: // Ready for new user
       if (CurrentCommand == cmdLogin) {
         SetState(stLogin);
         SendUser();
       }
       break;
       
     case 221: // Logged out
       if (CurrentCommand == cmdLogout) {
         CloseAllFD();
         SetState(stNOC);
       }
       else
         qWarning("Got 221 response, but I don't know of anything?");
       break;
     
     case 226: // Transfer completed
       if (CurrentState == stClosingData || CurrentState == stTransfer) {
         SetState(stIdle);
       }
       else
         qWarning("226 without transfer?\n");

       if (CurrentCommand == cmdDownload || CurrentCommand == cmdUpload || CurrentCommand == cmdListDir)
         CurrentCommand = cmdNop;
       else
         qWarning("226 Response, but not in data transfer mode? (upload/download/listdir)");
       break;
       
     case 227: // Transfering in passive mode
       {
         QString braces; // text between ()
         int left, right, i;
         bool addrOk = true;

         braces = responsebuffer;         
         left = braces.find('('); 
         right = braces.findRev(')', -1);
         if (left >= 0 && right > left) {
           braces = braces.mid(left + 1, right - left - 1);
           /* Hmpf. Just think of all the FTP clients that have to switch to IPv6...  */
           m_PassiveAddress = 0;
           for (i = 0; i < 4; i++) {
              left = braces.find(',');
              if (left > 0) {
                m_PassiveAddress = (m_PassiveAddress << 8) + braces.left(left).toInt();
                braces = braces.mid(left + 1);
              }
              else {
                addrOk = false;
                break;
              }
           }
//qDebug("Passive addr = 0x%x (%ud)", m_PassiveAddress, m_PassiveAddress);
           
           m_PassivePort = 0;
           left = braces.find(',');
           if (left > 0) {
             m_PassivePort = braces.left(left).toInt();
             braces = braces.mid(left + 1);
           }
            else
              addrOk = false;
           m_PassivePort = (m_PassivePort << 8) + braces.toInt();

//qDebug("Passive port = 0x%x (%d)", m_PassivePort, m_PassivePort);
           if (!addrOk)
             SetState(stFailed);
           else {
             switch(CurrentCommand) {
               case cmdDownload: StartReceiving(); break;
               case cmdUpload: StartSending(); break;
               case cmdListDir: SendList(); break;
               default: SetState(stFailed); break; // the command doesn't make sense
             }
             if (SetupDataPassive() < 0)
               SetState(stFailed);
           }
         }
         else {
           qWarning("Could not interpret 227 response properly");
           SetState(stFailed);
         }
       }
       break;

     case 230: // Login succeeded
       if (CurrentCommand == cmdLogin && (CurrentState == stAuthenticate || CurrentState == stLogin)) {
         CurrentCommand = cmdNop;
         SetState(stIdle);
         emit LoggedIn();
         // SendTimeOut ??
       }
       break;
       
     case 250: // CWD succeeded
       if (CurrentCommand == cmdChangeDir) {
         CurrentCommand = cmdNop;
         SetState(stIdle);
       }
       break;
       
     case 331: // Password required
       if (CurrentCommand == cmdLogin && CurrentState == stLogin) {
         SetState(stAuthenticate);
         SendPass();
       }
       break;
       
     case 421: // Service not available
       SetState(stFailed);
       CloseControl();
       SetState(stNOC);
       break;
       
     case 426: // Transfer aborted
       CloseData();
       SetState(stFailed);
       break;
       
     case 500: // Syntax error
       SetState(stFailed);
       break;
       
     case 501: // Syntax error in parameters
       SetState(stFailed);
       break;
       
     case 504: // Command not implemented for that parameter (Duh? 'parameter not valid for that command' sounds a lot more logical...)
       SetState(stFailed);
       break;
   
     case 530: // Login failed/not logged in
       SetState(stFailed);
       emit LoginFailed();
       //SetState(stConnected); // hmm, retries?
       break;
       
     case 550:
       if (CurrentCommand == cmdChangeDir) {
         // ChDir failed
         SetState(stFailed);
       }
       else if (CurrentCommand == cmdUpload || CurrentCommand == cmdDownload) {
         // Permission deniend
         SetState(stFailed);
       }
       else {
         qWarning("Recevied unexpected 550 response");
         SetState(stUnknown);
       }
       break;
       
     default:
       printf("Unknown code\n");
       SetState(stUnknown);
       break;
   }
   Response = 0;
qDebug("<< CFTPClient::InterpretResponse");
}


/**
  \brief Send content of outputbuffer
*/
void CFTPClient::Send()
{
  int l;
  
printf("CFTPClient::Send(): %s", (const char *)outputbuffer);  
  l = outputbuffer.length();
  if (l > 0 && CtrlFD >= 0)
    write(CtrlFD, outputbuffer.latin1(), l); // Should be non-blocking as well
}


void CFTPClient::SendUser()
{
   outputbuffer = "USER " + UserName + "\r\n";
   Send();
}

void CFTPClient::SendPass()
{
   outputbuffer = "PASS " + Password + "\r\n";
   Send();
}

void CFTPClient::SendList()
{
   outputbuffer = "LIST\r\n";
   Send();
}

void CFTPClient::SendPort(struct sockaddr_in &addr, int port)
{
   unsigned int l;
   
   l = (unsigned int)ntohl(addr.sin_addr.s_addr);
   outputbuffer.sprintf("PORT %d,%d,%d,%d,%d,%d\r\n",
                (l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff,
                (port >> 8) & 0xff, port & 0xff);
   Send();
}

void CFTPClient::SendStore(const QString &filename)
{
   outputbuffer = "STOR " + filename + "\r\n";
   Send();
}



void CFTPClient::StartSending()
{
   /* We are ready to go.. we have our local filedescriptor, the remote
    * side has our port, now send a STOR and see what happens 
    */
   SetState(stTransfer);
   SendStore(RemoteFileName);
}

void CFTPClient::StartReceiving()
{
   SetState(stTransfer);
   //SendRetrieve(RemoteFileName);
}

// private slots

void CFTPClient::ControlRead(int fd)
{
   int err, i;
   char c;

   /* Read data from control socket. Because FTP responses can consist of 
      multiple lines we need a 3-stage process: 
        inputbuffer contains the raw input data from the socket;
        linebuffer contains a whole line (possibly truncated);
        responsebuffer has the complete command.

      Linebuffer is delimited on the \r\n sequence; the \r\n sequence is
      removed and replaced by a '\0' to make it a proper string.  To prevent
      bad servers from trying to overflow our stack/memory, linebuffer will
      never contain more than 1023 characters; if we receive more than this
      before a \r\n sequence, the rest is ignored.
    */
   err = read(fd, inputbuffer, 1023);
   if (err < 0) {
     // Emit error?
     printf("CFTPClient::ControlRead() Error reading from control pipe: %d\n", err);
     return;
   }
   if (err == 0) { // EOF
     printf("CFTPClient::ControlRead() Control pipe closed by remote end.\n");
     //SetState(stClosing);
     CloseAllFD();
     SetState(stNOC);
     return;
   }
   
   inputbuffer[err] = '\0';
//qDebug("CFTPClient::ControlRead() Read from control socket: `%s'", inputbuffer);

   for (i = 0; i < err; i++) {
      c = inputbuffer[i];
      if (c == '\n') { // End-Of-Line
        // Hmm, We should check if lastchar = \r, but what the heck
        linebuffer[LineLen] = '\0'; // Terminate; LineLen is never larger than 1023
        InterpretLine();
        LineLen = 0;
      }
      else if (isprint(c)) { // Copy character
        if (LineLen < 1023)
          linebuffer[LineLen++] = c;
      }
      // Anything else is ignored
      
      LastChar = c;
   }

}

/**
  \brief Activated when our data port is contacted
  
  In active mode, we (the client) wait for the FTP server to contact us
  for the data-transfer; we have sent the address & port with a PORT
  command before. A connection on a listening socket is signalled by
  a 'read' on that socket.
*/  
void CFTPClient::ListenRead(int fd)
{
   int i;
   struct sockaddr_in DataAddr;
   socklen_t DLen;

qDebug("CFTPClient::ListenRead()");
   // We got a new connection, hurray
   if (fd != ListenFD)
     abort();

   // Now accept connection
   DLen = sizeof(DataAddr);
   i = accept(ListenFD, (struct sockaddr *)&DataAddr, &DLen);
   if (i < 0) {
     qWarning("Error: failed to accept connection: %d", -errno);
     //SetState(?);
     return;
   }
   
   if (ntohs(DataAddr.sin_port) != 20)
     qWarning("Got an incoming connection from an unprivilidged port (%d)\n", ntohs(DataAddr.sin_port));

   if (DataFD >= 0) {
     // Hey? That's odd...
     qWarning("Warning: got a second connection on data port from %s, port %d", inet_ntoa(DataAddr.sin_addr), DataAddr.sin_port);
     ::close(i); // drop it on the floor
     return;
   }
   else 
     DataFD = i;
     
   // The listen port has fulfilled its puprose, close it now
   CloseListen();
 
qDebug("CFTPClient::ListenRead() Opened data fd [%d] = %s:%d.", DataFD, inet_ntoa(DataAddr.sin_addr), ntohs(DataAddr.sin_port));
   HookData();
   SetState(stTransfer);
}


// Activated when data is coming in
void CFTPClient::DataRead(int fd)
{
   char buf[1024];
   int rd;
   
   rd = read(fd, buf, 1000);
qDebug("Read %d bytes in DataRead()", rd);
   buf[rd] = '\0';
   printf("%s", buf);   

   if (rd == 0) { // End-Of-File
     SetState(stClosingData);
     CloseData();     
   }
}

/// Activated when the output socket has space
void CFTPClient::DataWrite(int fd)
{
   int sp, wr, rd;

   sp = TBufLen - TBufUsed; // space in buffer;

//qDebug("CFTPClient::DataWrite(Head = %d, Tail = %d, Used = %d, sp = %d, fd = %d)", TBufHead, TBufTail, TBufUsed, sp, LocalFileFD);

   if (TBufUsed == 0 && LocalFileFD < 0) { // done!   
     qDebug("DataWrite done. Transfered %d bytes.", TotalTransfered);
     SetState(stClosingData);
     CloseData();
     return;
   }

   rd = 0;
   if (sp > 0 && LocalFileFD >= 0) { // there is space and we can read from file
//qDebug("Trying to read %d bytes from file; ", sp);
     if (TBufHead + sp > TBufLen) { // split across buffer end
       rd = read(LocalFileFD, transferbuffer + TBufHead, TBufLen - TBufHead);
       if (rd > 0) {
         sp = read(LocalFileFD, transferbuffer, sp - rd);
         if (sp > 0)
           rd += sp;
       }
     }
     else // single piece
       rd = read(LocalFileFD, transferbuffer + TBufHead, sp);
//qDebug("Got %d bytes.", rd);
   }

   if (rd <= 0) {
     if (rd < 0)
       qDebug("Error reading local file: %d.", -errno);
     CloseFile();
   }
   else {
     TBufHead = (TBufHead + rd) % TBufLen;

     TBufUsed += rd; // Adjust
     sp -= rd;
   }

   wr = 0;   
   if (TBufUsed) {
//qDebug("Trying to write %d bytes to datapipe; ", TBufUsed);
     // try to send data to pipe
     if (TBufTail + TBufUsed > TBufLen) { // split across buffer end
       wr = write(DataFD, transferbuffer + TBufTail, TBufLen - TBufTail);
       if (wr > 0) {
         sp = write(DataFD, transferbuffer, TBufUsed - wr);
         if (sp > 0)
           wr += sp;
       }
     } 
     else // single piece
       wr = write(DataFD, transferbuffer + TBufTail, TBufUsed);
//qDebug("CFTPClient::DataWrite() Put %d bytes in datapipe.", wr);
   }

   if (wr <= 0) {
     if (wr < 0)
       qWarning("CFTPClient::DataWrite() Erorr writing data pipe: %d", -errno);
     
   
   }
   else {
     TBufTail = (TBufTail + wr) % TBufLen;
     TotalTransfered += wr;
     
     emit Progress(TotalTransfered);

     TBufUsed -= wr;
     sp += wr;
   }
}


// public

int CFTPClient::GetCommand() const
{
   return CurrentCommand;
}

int CFTPClient::GetState() const
{
   return (int)CurrentState;
}

/** \brief Set transfer mode to passive

  Usually FTP transfers (both up- and downloads) are initiated by
  the \i server, after the command has been sent by the client. However,
  this does not always work (firewalls, braindead FTP implementations, etc)
  and the client has to do the work; this is called 'passive mode', and
  this function turns this mode on.
  
  Note: this function does not trigger a state change.
*/
void CFTPClient::SetPassive()
{
   m_Passive = true;
}

/** \brief Turns off passive mode; see \ref SetPassive */
void CFTPClient::SetActive()
{
   m_Passive = false;
}

/** 
  \brief Initiate connection to FTP server 
  \param user The username
  \param pass The password
  \param server The server name
  \param port Port number on the server
  
  This function initiates a connection to the server. This is the only
  function of CFTPClient that can block, since name resolving is done here,
  and that is a blocking call.
*/
void CFTPClient::Connect(const QString &user, const QString &pass, const QString &server, int port)
{
   struct hostent *pServerName;

   if (CtrlFD >= 0) {
     // SendAbort();
     CloseAllFD();
     SetState(stNOC);
   }

   CurrentCommand = cmdLogin;
   InitBuffers();

   /* Step 1: name resolv of server
      Unfortunately, this is a blocking system call. res_mkquery() is not funny.
    */
   pServerName = gethostbyname((const char *)server);
   if (pServerName == NULL) {
     SetState(stFailed, 810);
     return;
#if 0     
     switch(h_errno) { // This is tweaking the error codes somewhat :)
       case TRY_AGAIN:      return -EAGAIN; break;
       case HOST_NOT_FOUND: SetState(stFailed, 810); break;
       case NO_ADDRESS:     return -EFAULT; break;
       case NO_RECOVERY:    return -EINVAL; break;
       default:             return -EBUSY;  break;
     }
#endif     
   }

   if (pServerName->h_length != sizeof(struct in_addr))
     abort(); // Something fishy is going on!
   ServerAddress.sin_family = AF_INET;
   ServerAddress.sin_port = htons(port);
   memcpy(&ServerAddress.sin_addr, pServerName->h_addr_list[0], pServerName->h_length);
qDebug("Resolved server address: %s (%s)", pServerName->h_name, inet_ntoa(ServerAddress.sin_addr));

   UserName = user;
   Password = pass;
   CurrentCommand = cmdLogin;

   /* Set up our control connection; once this is established, 
      this automatically logs in. 
    */
   if (SetupControl() < 0) {
     SetState(stFailed, 811);
     CurrentCommand = cmdNop;
   }

   SetState(stConnected);
}


/** 
  \brief Upload file to remote site
  \param local_file The filename on the local machine
  \param remote_file The filename on the remote machine; if not present, the same as local_file
*/
void CFTPClient::Upload(const QString &local_file, const QString &remote_file)
{
   if (SetupLocal(local_file, false) < 0) {
     SetState(stFailed, 800);
     return;
   }

   if (remote_file.isNull())
     RemoteFileName = local_file;
   else
     RemoteFileName = remote_file;

   CurrentCommand = cmdUpload;
   Direction = TRUE;
   if (m_Passive) {
     outputbuffer = "PASV\r\n";
     Send(); // That's all. We await the 227 response
   }
   else {
     if (SetupDataActive() < 0) {
       qDebug("CFTPClient::Upload() SetupData() failed:.");
       SetState(stFailed);
       CurrentCommand = cmdNop;
     }
   }
}


void CFTPClient::SetTypeAscii()
{
   TransferMethod = 0;
   CurrentCommand = cmdSetType;
   outputbuffer = "TYPE A\r\n";
   Send();
}

void CFTPClient::SetTypeBinary()
{
   TransferMethod = 1;
   CurrentCommand = cmdSetType;
   outputbuffer = "TYPE I\r\n";
   Send();
}

/** \brief Change to remote directory on server
*/
void CFTPClient::ChangeDir(const QString &new_dir)
{
   CurrentCommand = cmdChangeDir;
   outputbuffer = "CWD " + new_dir + "\r\n";
   Send();
}


/** \brief List contents of remote directory.

   This command will retrieve the list of filenames of the directory 
   on the remote server. The found files are emitted one by one through
   \ref ListDirEntry(const QString &filename).
   
   Note: make sure you set the transfer type to ASCII first.
*/
void CFTPClient::ListDir()
{
   int ret;

   CurrentCommand = cmdListDir;
   Direction = FALSE;

   if (m_Passive) {
     outputbuffer = "PASV\r\n";
     Send(); // That's all. We await the 227 response
   }
   else {
     ret = SetupDataActive();
     if (ret < 0) {
       qDebug("CFTPClient::ListDir() SetupData() failed: %d.", ret);
       SetState(stFailed);
       CurrentCommand = cmdNop;
     }
   }
}


/** \brief Rename a file on the server

  This command will instruct the FTP server to rename a file on the
  remote disk.
*/  
void CFTPClient::Rename(const QString &from, const QString &to)
{
   CurrentCommand = cmdRename;
   outputbuffer = "RNFR " + from + "\r\n";
   outputbuffer += "RNTO " + to + "\r\n";
   Send();
}



void CFTPClient::Logout()
{
   CurrentCommand = cmdLogout;
   outputbuffer = "QUIT\r\n";
   Send();
}

QString CFTPClient::GetErrorString(int result) const
{
   QString res = tr("unknown");

   switch(result) {
     case 110: res = tr("Restart marker"); break;
     case 120: res = tr("Service momentarely unavailable."); break;
   };
   return res;
}

