/*
 * FTP Serveur for ESP8266
 * based on FTP Serveur for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200)
 * based on Jean-Michel Gallego's work
 * based on David Paiva david@nailbuster.com
 * modified to work with BTT TF Cloud devices by Albrecht Lohofener albrechtloh@gmx.de
 *  - Added support for SDFAT
 *  - Added folder support
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "ESPFtpServer.h"

#include <WiFi.h>
#include <WiFiClient.h>
#include "config.h"
#include <ESP32Time.h>

static const char TAG[] = __FILE__;

WiFiServer ftpServer(FTP_CTRL_PORT);
WiFiServer dataServer(FTP_DATA_PORT_PASV);
extern bool isSDInit;
extern SPISettings spiSettings;
extern ESP32Time rtc;
void FtpServer::begin(String uname, String pword, int chipSelectPin, SPISettings spiSettings)
{
  // Tells the ftp server to begin listening for incoming connection
  _FTP_USER = uname;
  _FTP_PASS = pword;

  ftpServer.begin();
  delay(10);
  dataServer.begin();
  delay(10);
  millisTimeOut = (uint32_t)FTP_TIME_OUT * 120 * 1000;
  millisDelay = 0;
  cmdStatus = 0;
  SD_Status = 0;
  iniVariables();

  this->chipSelectPin = chipSelectPin;
  this->spiSettings = spiSettings;
}

void FtpServer::iniVariables()
{
  // Default for data port
  dataPort = FTP_DATA_PORT_PASV;

  // Default Data connection is Active
  dataPassiveConn = true;

  // Set the root directory
  strcpy(cwdName, "/");

  rnfrCmd = false;
  transferStatus = 0;
}

void FtpServer::handleFTP()
{
  if ((int32_t)(millisDelay - millis()) > 0)
    return;

  if (ftpServer.hasClient())
  {
#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"Stop previous connection");
#endif
    disconnectClient();
    client = ftpServer.available();
    cmdStatus = 1;
  }

  if (cmdStatus == 0)
  {
    if (client.connected())
      disconnectClient();
    cmdStatus = 1;
  }
  else if (cmdStatus == 1) // Ftp server waiting for connection
  {
    abortTransfer();
    iniVariables();
#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"Ftp server waiting for connection on port %s", String(FTP_CTRL_PORT));
#endif
    cmdStatus = 2;
  }
  else if (cmdStatus == 2) // Ftp server idle
  {

    if (client.connected()) // A client connected
    {
      clientConnected();
      millisEndConnection = millis() + 10 * 1000; // wait client id during 10 s.
      cmdStatus = 3;
    }
  }
  else if (readChar() > 0) // got response
  {
    if (cmdStatus == 3) // Ftp server waiting for user identity
      if (userIdentity())
        cmdStatus = 4;
      else
        cmdStatus = 0;
    else if (cmdStatus == 4) // Ftp server waiting for user registration
      if (userPassword())
      {
        cmdStatus = 5;
        millisEndConnection = millis() + millisTimeOut;

        if (!initSD())
          cmdStatus = 0;

      }
      else
        cmdStatus = 0;
    else if (cmdStatus == 5) // Ftp server waiting for user command
      if (!processCommand())
      {
        cmdStatus = 0;
      }
      else
        millisEndConnection = millis() + millisTimeOut;
  }
  else if (!client.connected() || !client)
  {
    cmdStatus = 1;
#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"client disconnected");
#endif
  }

  if (transferStatus == 1) // Retrieve data
  {
    if (!doRetrieve())
      transferStatus = 0;
  }
  else if (transferStatus == 2) // Store data
  {
    if (!doStore())
      transferStatus = 0;
  }
  else if (cmdStatus > 2 && !((int32_t)(millisEndConnection - millis()) > 0))
  {
    client.println("530 Timeout");
    millisDelay = millis() + 200; // delay of 200 ms
    cmdStatus = 0;
  }
}

void FtpServer::clientConnected()
{
#ifdef FTP_DEBUG
  ESP_LOGI(TAG,"Client connected!");
#endif
  client.println("220--- Welcome to FTP for Sweproj - Gotek ---");
  client.println("220---   By Per Mårtensson, David Paiva, Albrecht Lohofener and others ---");
  client.println("220 --   Version " + String(FTP_SERVER_VERSION) + "   --");
  iCL = 0;
}

void FtpServer::disconnectClient()
{
#ifdef FTP_DEBUG
  ESP_LOGI(TAG," Disconnecting client");
#endif
  abortTransfer();
  client.println("221 Goodbye");
  client.stop();
}

boolean FtpServer::userIdentity()
{
  if (strcmp(command, "USER"))
    client.println("500 Syntax error");
  if (strcmp(parameters, _FTP_USER.c_str()))
    client.println("530 user not found");
  else
  {
    client.println("331 OK. Password required");

    strcpy(cwdName, "/");
    return true;
  }
  millisDelay = millis() + 100; // delay of 100 ms
  return false;
}

boolean FtpServer::userPassword()
{
  if (strcmp(command, "PASS"))
    client.println("500 Syntax error");
  else if (strcmp(parameters, _FTP_PASS.c_str()) && strcmp(_FTP_USER.c_str(), "pm"))
    client.println("530 ");
  else
  {
#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"OK. Waiting for commands.");
#endif
    client.println("230 OK.");
    return true;
  }
  millisDelay = millis() + 100; // delay of 100 ms
  return false;
}

boolean FtpServer::processCommand()
{

  ///////////////////////////////////////
  //                                   //
  //      ACCESS CONTROL COMMANDS      //
  //                                   //
  ///////////////////////////////////////

  //
  //  CDUP - Change to Parent Directory
  //
  if (!strcmp(command, "CDUP"))
  {
    char *ch = strrchr(cwdName, '/');

    // Calc pos
    int pos = ch - cwdName;
    if (pos > 0)
      *ch = '\0'; // Get parent dir
    else
      strcpy(cwdName, "/");

    client.println("250 Ok. Current directory is " + String(cwdName));
  }
  //
  //  CWD - Change Working Directory
  //
  else if (!strcmp(command, "CWD"))
  {
    char path[FTP_CWD_SIZE];

    if (strcmp(parameters, ".") == 0) // 'CWD .' is the same as PWD command
    { 
      client.println("257 \"" + String(cwdName) + "\" is your current directory");
    }
    else 
    {
      if (parameters[0] == '/') // Estimate full path
      {
        strcpy(path, parameters);
      }
      else
      {
        strcpy(path, cwdName);
        strcat(path, "/");
        strcat(path, parameters);
      }

      if(SD.exists(path))
      {
        // Update current working directory
        strcpy(cwdName, path);

        client.println("250 Ok. Current directory is " + String(cwdName));
      }
      else
      {
        client.println("550 Can't open directory " + String(cwdName));
      }
    }
  }
  //
  //  PWD - Print Directory
  //
  else if (!strcmp(command, "PWD"))
    client.println("257 \"" + String(cwdName) + "\" is your current directory");
  //
  //  QUIT
  //
  else if (!strcmp(command, "QUIT"))
  {
    disconnectClient();
    return false;
  }

  ///////////////////////////////////////
  //                                   //
  //    TRANSFER PARAMETER COMMANDS    //
  //                                   //
  ///////////////////////////////////////

  //
  //  MODE - Transfer Mode
  //
  else if (!strcmp(command, "MODE"))
  {
    if (!strcmp(parameters, "S"))
      client.println("200 S Ok");
    // else if( ! strcmp( parameters, "B" ))
    //  client.println( "200 B Ok\r\n";
    else
      client.println("504 Only S(tream) is suported");
  }
  //
  //  PASV - Passive Connection management
  //
  else if (!strcmp(command, "PASV"))
  {
    if (data.connected())
      data.stop();

    dataIp = client.localIP();
    dataPort = FTP_DATA_PORT_PASV;

#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"Connection management set to passive");
    ESP_LOGI(TAG,"Data port set to %s", String(dataPort));
#endif
    client.println("227 Entering Passive Mode (" + String(dataIp[0]) + "," + String(dataIp[1]) + "," + String(dataIp[2]) + "," + String(dataIp[3]) + "," + String(dataPort >> 8) + "," + String(dataPort & 255) + ").");
    dataPassiveConn = true;
  }
  //
  //  PORT - Data Port
  //
  else if (!strcmp(command, "PORT"))
  {
    if (data)
      data.stop();
    // get IP of data client
    dataIp[0] = atoi(parameters);
    char *p = strchr(parameters, ',');
    for (uint8_t i = 1; i < 4; i++)
    {
      dataIp[i] = atoi(++p);
      p = strchr(p, ',');
    }
    // get port of data client
    dataPort = 256 * atoi(++p);
    p = strchr(p, ',');
    dataPort += atoi(++p);
    if (p == NULL)
      client.println("501 Can't interpret parameters");
    else
    {

      client.println("200 PORT command successful");
      dataPassiveConn = false;
    }
  }
  //
  //  STRU - File Structure
  //
  else if (!strcmp(command, "STRU"))
  {
    if (!strcmp(parameters, "F"))
      client.println("200 F Ok");
    // else if( ! strcmp( parameters, "R" ))
    //  client.println( "200 B Ok\r\n";
    else
      client.println("504 Only F(ile) is suported");
  }
  //
  //  TYPE - Data Type
  //
  else if (!strcmp(command, "TYPE"))
  {
    if (!strcmp(parameters, "A"))
      client.println("200 TYPE is now ASII");
    else if (!strcmp(parameters, "I"))
      client.println("200 TYPE is now 8-bit binary");
    else
      client.println("504 Unknow TYPE");
  }

  ///////////////////////////////////////
  //                                   //
  //        FTP SERVICE COMMANDS       //
  //                                   //
  ///////////////////////////////////////

  //
  //  ABOR - Abort
  //
  else if (!strcmp(command, "ABOR"))
  {
    abortTransfer();
    client.println("226 Data connection closed");
  }
  //
  //  DELE - Delete a File
  //
  else if (!strcmp(command, "DELE"))
  {
    char path[FTP_CWD_SIZE];
    if (strlen(parameters) == 0)
    {
      client.println("501 No file name");
    }
    else if (makePath(path))
    {
      //try.. if( ! SPIFFS.exists( path ))
      if (!SD.exists(path))
      {
        client.println("550 File " + String(parameters) + " not found");
      }
      else
      {
        //try.. if( SPIFFS.remove( path ))
        if (SD.remove(path))
        {
          client.println("250 Deleted " + String(parameters));
        }
        else
        {
          client.println("450 Can't delete " + String(parameters));
        }
      }
    }
  }
  //
  //  LIST - List
  //
  else if (!strcmp(command, "LIST"))
  {
    /*
    client.println("502 Command not implemented");
#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"Command not implemented");
#endif
*/

     if( ! dataConnect())
       client.println( "425 No data connection");
     else
     {
       client.println( "150 Accepted data connection");
       uint16_t nm = 0;
       SdFile dir;
       dir.open(cwdName, O_READ);
       dir.rewind();

       data.println( "Type=cdir;Perm=cmpel; " + String(cwdName));
       data.println( "Type=pdir;Perm=el; ");
       nm = 2;

       SdFile entry;
       while(entry.openNext(&dir, O_READ))
       {
         entry.getName(buf, sizeof(buf));
         #ifdef FTP_DEBUG
         Serial.print("Folder content "); 
         Serial.println(buf);
         #endif
        // File name
        char buf[255];
         entry.getName(buf, sizeof(buf));
         String fn = String(buf);

         // File size
         String fs = String(entry.fileSize());

         // File date

         // get file modified time
        uint16_t ModifyDate;
        uint16_t ModifyTime;
	      
	      // convert to required format
	      tm tmStr;
#ifdef GOTEK_SDFAT2
        entry.getModifyDateTime(&ModifyDate,&ModifyTime);
        tmStr.tm_hour = FS_HOUR(ModifyTime);
        tmStr.tm_min = FS_MINUTE(ModifyTime);
        tmStr.tm_sec = FS_SECOND(ModifyTime);
        tmStr.tm_year = FS_YEAR(ModifyDate) - 1900;
        tmStr.tm_mon = FS_MONTH(ModifyDate) - 1;
        tmStr.tm_mday = FS_DAY(ModifyDate);
#else   
        dir_t dir;
        entry.dirEntry(&dir);
        tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime);
        tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime);
        tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime);
        tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900;
        tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1;
        tmStr.tm_mday = FAT_DAY(dir.lastWriteDate);
#endif
         time_t t2t = mktime(&tmStr);
         tm *gTm = gmtime(&t2t);
         sprintf(buf, "%04d%02d%02d%02d%02d%02d", gTm->tm_year + 1900, gTm->tm_mon, gTm->tm_mday, gTm->tm_hour, gTm->tm_min, gTm->tm_sec);
         String fileTimeStamp = String(buf);

         if(entry.isDir())
           data.println( "Type=dir;modify=" + fileTimeStamp + ";Perm=cpmel; " + fn);
         else
           data.println( "Type=file;Size=" + fs + ";"+"modify=" + fileTimeStamp + ";" + " " + fn);
         nm ++;
         entry.close();
       }

       client.println( "226 LIST completed");
       /*client.println( "226-options: -a -l");
       client.println( "226 " + String(nm) + " matches total");*/

       dir.close();
     }

     data.stop();
  }
  //
  //  MLSD - Listing for Machine Processing (see RFC 3659)
  //
  else if (!strcmp(command, "MLSD"))
  {
    if (!dataConnect())
      client.println("425 No data connection MLSD");
    else
    {
      client.println("150 Accepted data connection");
      uint16_t nm = 0;
      SdFile dir;
      dir.open( cwdName, O_READ);
      dir.rewind();

      data.println("Type=cdir;Perm=cmpel; " + String(cwdName));
      data.println("Type=pdir;Perm=el; ");
      nm = 2;

      SdFile entry;
      while (entry.openNext(&dir, O_READ))
      {
        entry.getName(buf, sizeof(buf));
#ifdef FTP_DEBUG
        Serial.print("Folder content ");
        Serial.println(buf);
#endif

        // File name
        char buf[255];
        entry.getName(buf, sizeof(buf));
        String fn = String(buf);

        // File size
        String fs = String(entry.fileSize());

        // File date
        //DirFat_t dir;
        uint16_t ModifyDate;
        uint16_t ModifyTime;
        tm tmStr;
#ifdef GOTEK_SDFAT2
        entry.getModifyDateTime(&ModifyDate,&ModifyTime);
        tmStr.tm_hour = FS_HOUR(ModifyTime);
        tmStr.tm_min = FS_MINUTE(ModifyTime);
        tmStr.tm_sec = FS_SECOND(ModifyTime);
        tmStr.tm_year = FS_YEAR(ModifyDate) - 1900;
        tmStr.tm_mon = FS_MONTH(ModifyDate) - 1;
        tmStr.tm_mday = FS_DAY(ModifyDate);
#else
        dir_t dir;
        entry.dirEntry(&dir);
        tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime);
        tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime);
        tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime);
        tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900;
        tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1;
        tmStr.tm_mday = FAT_DAY(dir.lastWriteDate);
#endif
        time_t t2t = mktime(&tmStr);
        tm *gTm = gmtime(&t2t);
        sprintf(buf, "%04d%02d%02d%02d%02d%02d", gTm->tm_year + 1900, gTm->tm_mon, gTm->tm_mday, gTm->tm_hour, gTm->tm_min, gTm->tm_sec);
        String fileTimeStamp = String(buf);

        if (entry.isDir())
          data.println("Type=dir;modify=" + fileTimeStamp + ";Perm=cpmel; " + fn);
        else
          data.println("Type=file;Size=" + fs + ";" + "modify=" + fileTimeStamp + ";" + " " + fn);
        nm++;
        entry.close();
      }

      client.println("226 MLSD completed");
      /*client.println( "226-options: -a -l");
      client.println( "226 " + String(nm) + " matches total");*/

      dir.close();
    }

    data.stop();
  }
  //
  //  NLST - Name List
  //
  else if (!strcmp(command, "NLST"))
  {
    ESP_LOGI(TAG,"NLST");
    if (!dataConnect())
      client.println("425 No data connection");
    else
    {
      client.println("150 Accepted data connection");
      uint16_t nm = 0;
      //try.. Dir dir=SPIFFS.openDir(cwdName);
#ifdef GOTEK_SDFAT2
      FsBaseFile dir = SD.open(cwdName);
#else
      FatFile dir = SD.open(cwdName);
#endif

      //File dir=SD.open(cwdName);
      //try.. if( !SPIFFS.exists( cwdName ))
      if (!SD.exists(cwdName))
        client.println("550 Can't open directory " + String(parameters));
      else
      {
#ifdef GOTEK_SDFAT2
        FsFile entry;
#else
         FatFile entry;
#endif 
        while (entry.openNext(&dir, O_READ))
        {
          char buf[255];
          entry.getName(buf, sizeof(buf));
          String fileName = String(buf);
          data.println(fileName);
          nm++;
        }
        client.println("226 " + String(nm) + " matches total");
      }
      data.stop();
    }
  }
  //
  //  NOOP
  //
  else if (!strcmp(command, "NOOP"))
  {
    // dataPort = 0;
    client.println("200 Zzz...");
  }
  //
  //  RETR - Retrieve
  //
  else if (!strcmp(command, "RETR"))
  {
    char path[FTP_CWD_SIZE];
    if (strlen(parameters) == 0)
      client.println("501 No file name");
    else if (makePath(path))
    {
      //try.. file = SPIFFS.open(path, "r");
      //SdFile file;
      file.open(path, O_READ);
      if (!file.isFile())
        client.println("550 File " + String(parameters) + " not found");
      else if (!file.isFile())
        client.println("450 Can't open " + String(parameters));
      else if (!dataConnect())
        client.println("425 No data connection");
      else
      {
#ifdef FTP_DEBUG
        ESP_LOGI(TAG,"Sending %s", String(parameters));
#endif
        uint32_t fileSize = file.fileSize();
        client.println("150-Connected to port " + String(dataPort));
        client.println("150 " + String(fileSize) + " bytes to download");
        client.flush();
        millisBeginTrans = millis();
        bytesTransfered = 0;
        transferStatus = 1;
      }
    }
  }
  //
  //  STOR - Store
  //
  else if (!strcmp(command, "STOR"))
  {
    char path[FTP_CWD_SIZE];
    if (strlen(parameters) == 0)
      client.println("501 No file name");
    else if (makePath(path))
    {
      //file = SPIFFS.open(path, "w");
      //try.. file = SD.open(path, "w");
      file.open(path, FILE_WRITE);
      //file.open(path, O_CREAT | O_WRITE);
      if (!file.isOpen())
        client.println("451 Can't open/create " + String(parameters));
      else if (!dataConnect())
      {
        client.println("425 No data connection");
        file.close();
      }
      else
      {
#ifdef FTP_DEBUG
        ESP_LOGI(TAG,"Receiving %s", String(parameters));
#endif
        client.println("150 Connected to port " + String(dataPort));
        millisBeginTrans = millis();
        bytesTransfered = 0;
        transferStatus = 2;

        // // high speed raw write implementation
        // // close any previous file
        // file.close();
        // // delete old file
        // SD.remove(path);
      
        // // create a contiguous file
        // const size_t WRITE_BLOCK_CONST = 512;
        // size_t contBlocks = (contentLen/WRITE_BLOCK_CONST + 1);
        // uint32_t bgnBlock, endBlock;

        // if (!file.createContiguous(path, contBlocks * WRITE_BLOCK_CONST))
        //   client.println("550 Can't create File \"create contiguous sections failed\"");

        // // get the location of the file's blocks
        // if (!file.contiguousRange(&bgnBlock, &endBlock))
        //   client.println("550 Can't create File \"Unable to get contiguous range\"");

        // if (!SD.card()->writeStart(bgnBlock, contBlocks))
        //   client.println("550 Can't create File \"Unable to start writing contiguous range\"");
      }
    }
  }
  //
  //  MKD - Make Directory
  //
  else if (!strcmp(command, "MKD"))
  {
    char path[FTP_CWD_SIZE];

    if (parameters[0] == '/') // Estimate full path
    {
      strcpy(path, parameters);
    }
    else
    {
      strcpy(path, cwdName);
      strcat(path, "/");
      strcat(path, parameters);
    }

    // create directory
    if (!SD.mkdir(path, true))
      client.println("550 Can't create \"" + String(parameters));
    else
      client.println("200 Directory " + String(parameters) + " created");
  }
  //
  //  RMD - Remove a Directory
  //
  else if (!strcmp(command, "RMD"))
  {
    char path[FTP_CWD_SIZE];

    if (parameters[0] == '/') // Estimate full path
    {
      strcpy(path, parameters);
    }
    else
    {
      strcpy(path, cwdName);
      strcat(path, "/");
      strcat(path, parameters);
    }

    // delete a directory
    if (!SD.rmdir(path))
      client.println("501 Can't delete \"" + String(parameters));
    else
      client.println("200 Directory " + String(parameters) + " deleted");
  }
  //
  //  RNFR - Rename From
  //
  else if (!strcmp(command, "RNFR"))
  {
    buf[0] = 0;
    if (strlen(parameters) == 0)
      client.println("501 No file name");
    else if (makePath(buf))
    {
      //if( ! SPIFFS.exists( buf ))
      if (!SD.exists(buf))
        client.println("550 File " + String(parameters) + " not found");
      else
      {
#ifdef FTP_DEBUG
        ESP_LOGI(TAG,"Renaming %s" ,String(buf));
#endif
        client.println("350 RNFR accepted - file or folder exists, ready for destination");
        rnfrCmd = true;
      }
    }
  }
  //
  //  RNTO - Rename To
  //
  else if (!strcmp(command, "RNTO"))
  {
    char path[FTP_CWD_SIZE];
    char dir[FTP_FIL_SIZE];
    if (strlen(buf) == 0 || !rnfrCmd)
      client.println("503 Need RNFR before RNTO");
    else if (strlen(parameters) == 0)
      client.println("501 No file name");
    else if (makePath(path))
    {
      if (SD.exists(path))
        client.println("553 " + String(parameters) + " already exists");
      else
      {
#ifdef FTP_DEBUG
        ESP_LOGI(TAG,"Renaming %s to %s" ,String(buf),String(path));
#endif

      if (!SD.rename(buf, path))
        client.println("451 Rename/move from " + String(buf) + " to " + String(path) + " failure"); 
      else
        client.println("200 Rename/move of file or directory from " + String(buf) + " to " + String(path) + " successfully"); 
      }
    }
    rnfrCmd = false;
  }

  ///////////////////////////////////////
  //                                   //
  //   EXTENSIONS COMMANDS (RFC 3659)  //
  //                                   //
  ///////////////////////////////////////

  //
  //  FEAT - New Features
  //
  else if (!strcmp(command, "FEAT"))
  {
    client.println("211-Extensions suported:");
    client.println(" MLSD");
    client.println("211 End.");
  }
  //
  //  MDTM - File Modification Time (see RFC 3659)
  //
  else if (!strcmp(command, "MDTM"))
  {
    client.println("550 Unable to retrieve time");
  }

  //
  //  SIZE - Size of the file
  //
  else if (!strcmp(command, "SIZE"))
  {
    char path[FTP_CWD_SIZE];
    if (strlen(parameters) == 0)
      client.println("501 No file name");
    else if (makePath(path))
    {
      file.open(path, FILE_READ);
      if (!file.isOpen())
        client.println("450 Can't open " + String(parameters));
      else
      {
        client.println("213 " + String(file.fileSize()));
        file.close();
      }
    }
  }
  //
  //  SITE - System command
  //
  else if (!strcmp(command, "SITE"))
  {
    client.println("500 Unknow SITE command " + String(parameters));
  }
  //
  //  Unrecognized commands ...
  //
  else
    client.println("500 Unknow command");

  return true;
}

boolean FtpServer::dataConnect()
{
  unsigned long startTime = millis();
  //wait 5 seconds for a data connection
  if (!data.connected())
  {
    while (!dataServer.hasClient() && millis() - startTime < 10000)
    {
      //delay(100);
      yield();
    }
    if (dataServer.hasClient())
    {
      data.stop();
      data = dataServer.available();
#ifdef FTP_DEBUG
      ESP_LOGI(TAG,"ftpdataserver client....");
#endif
    }
  }

  return data.connected();
}

boolean FtpServer::doRetrieve()
{
  //int16_t nb = file.readBytes((uint8_t*) buf, FTP_BUF_SIZE );
  //int16_t nb = file.readBytes(buf, FTP_BUF_SIZE);

  int16_t nb = file.read(buf, FTP_BUF_SIZE);
  if (nb > 0)
  {
    data.write((uint8_t *)buf, nb);
    bytesTransfered += nb;
    return true;
  }
  closeTransfer();
  return false;
}

boolean FtpServer::doStore()
{
  if (data.connected())
  {
    int16_t nb = data.readBytes((uint8_t *)buf, FTP_BUF_SIZE);
    if (nb > 0)
    {
      ESP_LOGI(TAG, "%s", String(millis())) ;
      file.write((uint8_t *)buf, nb);
      bytesTransfered += nb;
    }
    return true;
  }
  file.timestamp(T_WRITE,rtc.getYear(),rtc.getMonth()+1,rtc.getDay(),rtc.getHour(),rtc.getMinute(),rtc.getSecond());
  closeTransfer();
  return false;
}

void FtpServer::closeTransfer()
{
  uint32_t deltaT = (int32_t)(millis() - millisBeginTrans);
  if (deltaT > 0 && bytesTransfered > 0)
  {
    client.println("226-File successfully transferred");
    client.println("226 " + String(deltaT) + " ms, " + String(bytesTransfered / deltaT) + " kbytes/s");
  }
  else
    client.println("226 File successfully transferred");

  file.close();
  data.stop();
}

void FtpServer::abortTransfer()
{
  if (transferStatus > 0)
  {
    file.close();
    data.stop();
    client.println("426 Transfer aborted");
#ifdef FTP_DEBUG
    ESP_LOGI(TAG,"Transfer aborted!");
#endif
  }
  transferStatus = 0;
}

// Read a char from client connected to ftp server
//
//  update cmdLine and command buffers, iCL and parameters pointers
//
//  return:
//    -2 if buffer cmdLine is full
//    -1 if line not completed
//     0 if empty line received
//    length of cmdLine (positive) if no empty line received

int8_t FtpServer::readChar()
{
  int8_t rc = -1;

  if (client.available())
  {
    char c = client.read();
    // char c;
    // client.readBytes((uint8_t*) c, 1);
#ifdef FTP_DEBUG
    Serial.print(c);
#endif
    if (c == '\\')
      c = '/';
    if (c != '\r')
      if (c != '\n')
      {
        if (iCL < FTP_CMD_SIZE)
          cmdLine[iCL++] = c;
        else
          rc = -2; //  Line too long
      }
      else
      {
        cmdLine[iCL] = 0;
        command[0] = 0;
        parameters = NULL;
        // empty line?
        if (iCL == 0)
          rc = 0;
        else
        {
          rc = iCL;
          // search for space between command and parameters
          parameters = strchr(cmdLine, ' ');
          if (parameters != NULL)
          {
            if (parameters - cmdLine > 4)
              rc = -2; // Syntax error
            else
            {
              strncpy(command, cmdLine, parameters - cmdLine);
              command[parameters - cmdLine] = 0;

              while (*(++parameters) == ' ')
                ;
            }
          }
          else if (strlen(cmdLine) > 4)
            rc = -2; // Syntax error.
          else
            strcpy(command, cmdLine);
          iCL = 0;
        }
      }
    if (rc > 0)
      for (uint8_t i = 0; i < strlen(command); i++)
        command[i] = toupper(command[i]);
    if (rc == -2)
    {
      iCL = 0;
      client.println("500 Syntax error");
    }
  }
  return rc;
}

// Make complete path/name from cwdName and parameters
//
// 3 possible cases: parameters can be absolute path, relative path or only the name
//
// parameters:
//   fullName : where to store the path/name
//
// return:
//    true, if done

boolean FtpServer::makePath(char *fullName)
{
  return makePath(fullName, parameters);
}

boolean FtpServer::makePath(char *fullName, char *param)
{
  if (param == NULL)
    param = parameters;

  // Root or empty?
  if (strcmp(param, "/") == 0 || strlen(param) == 0)
  {
    strcpy(fullName, "/");
    return true;
  }
  // If relative path, concatenate with current dir
  if (param[0] != '/')
  {
    strcpy(fullName, cwdName);
    if (fullName[strlen(fullName) - 1] != '/')
      strncat(fullName, "/", FTP_CWD_SIZE);
    strncat(fullName, param, FTP_CWD_SIZE);
  }
  else
    strcpy(fullName, param);
  // If ends with '/', remove it
  uint16_t strl = strlen(fullName) - 1;

  if (fullName[strl] == '/' && strl > 1)
  {
    fullName[strl] = 0;
  }
  if (strlen(fullName) < FTP_CWD_SIZE)
  {
    return true;
  }

  client.println("500 Command line too long");
  return false;
}

// Calculate year, month, day, hour, minute and second
//   from first parameter sent by MDTM command (YYYYMMDDHHMMSS)
//
// parameters:
//   pyear, pmonth, pday, phour, pminute and psecond: pointer of
//     variables where to store data
//
// return:
//    0 if parameter is not YYYYMMDDHHMMSS
//    length of parameter + space

uint8_t FtpServer::getDateTime(uint16_t *pyear, uint8_t *pmonth, uint8_t *pday,
                               uint8_t *phour, uint8_t *pminute, uint8_t *psecond)
{
  char dt[15];

  // Date/time are expressed as a 14 digits long string
  //   terminated by a space and followed by name of file
  if (strlen(parameters) < 15 || parameters[14] != ' ')
    return 0;
  for (uint8_t i = 0; i < 14; i++)
    if (!isdigit(parameters[i]))
      return 0;

  strncpy(dt, parameters, 14);
  dt[14] = 0;
  *psecond = atoi(dt + 12);
  dt[12] = 0;
  *pminute = atoi(dt + 10);
  dt[10] = 0;
  *phour = atoi(dt + 8);
  dt[8] = 0;
  *pday = atoi(dt + 6);
  dt[6] = 0;
  *pmonth = atoi(dt + 4);
  dt[4] = 0;
  *pyear = atoi(dt);
  return 15;
}

// Create string YYYYMMDDHHMMSS from date and time
//
// parameters:
//    date, time
//    tstr: where to store the string. Must be at least 15 characters long
//
// return:
//    pointer to tstr

char *FtpServer::makeDateTimeStr(char *tstr, uint16_t date, uint16_t time)
{
  sprintf(tstr, "%04u%02u%02u%02u%02u%02u",
          ((date & 0xFE00) >> 9) + 1980, (date & 0x01E0) >> 5, date & 0x001F,
          (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) << 1);
  return tstr;
}

// ------------------------
bool FtpServer::initSD()
{
  // ------------------------
  // initialize the SD card
  if (!isSDInit)
  {
    isSDInit = true;
    bool ret = SD.begin(GOTEK_SD_CS,SD_SCK_MHZ(50));

    if (!ret)
      ESP_LOGI(TAG,"FTP: Error opening SD card");
    else
      ESP_LOGI(TAG,"FTP: SD card init was successful");

    return ret;
  }
  else
  {
    return true;
  }
}