diff options
Diffstat (limited to '')
-rw-r--r-- | FTPClient.cpp | 9 | ||||
-rw-r--r-- | FTPClient.h | 1 | ||||
-rw-r--r-- | FTPCommon.cpp | 16 | ||||
-rw-r--r-- | FTPCommon.h | 15 | ||||
-rw-r--r-- | FTPServer.cpp | 151 | ||||
-rw-r--r-- | esp32compat/PolledTimeout.h | 254 | ||||
-rw-r--r-- | examples/FTPServerSample/LittleFSSample/LittleFSSample.ino | 9 | ||||
-rw-r--r-- | examples/FTPServerSample/SPIFFSSample/SPIFFSSample.ino | 77 |
8 files changed, 379 insertions, 153 deletions
diff --git a/FTPClient.cpp b/FTPClient.cpp index ef4db15..d385c43 100644 --- a/FTPClient.cpp +++ b/FTPClient.cpp @@ -155,7 +155,12 @@ void FTPClient::handleFTP() millisBeginTrans = millis(); bytesTransfered = 0; ftpState = cTransfer; - allocateBuffer(TCP_MSS); + if (allocateBuffer() == 0) + { + _serverStatus.code = errorMemory; + _serverStatus.desc = F("No memory for transfer buffer"); + ftpState = cError; + } if (_direction & FTP_PUT_NONBLOCKING) { FTP_DEBUG_MSG(">>> STOR %s", _remoteFileName.c_str()); @@ -208,7 +213,7 @@ int8_t FTPClient::controlConnect() { FTP_DEBUG_MSG("Ignoring CA verification - FTP only"); } - control.connect(_server->servername, _server->port); + control.connect(_server->servername.c_str(), _server->port); FTP_DEBUG_MSG("Connection to %s:%d ... %S", _server->servername.c_str(), _server->port, control.connected() ? PSTR("OK") : PSTR("failed")); if (control.connected()) return 1; diff --git a/FTPClient.h b/FTPClient.h index ca5cd78..05a1bf3 100644 --- a/FTPClient.h +++ b/FTPClient.h @@ -51,6 +51,7 @@ public: static constexpr int16_t errorDataConnectionFailed = -5; static constexpr int16_t errorUninitialized = -6; static constexpr int16_t errorTimeout = -7; + static constexpr int16_t errorMemory = -8; typedef struct { diff --git a/FTPCommon.cpp b/FTPCommon.cpp index 4b8af4c..0651022 100644 --- a/FTPCommon.cpp +++ b/FTPCommon.cpp @@ -1,7 +1,6 @@ #include "FTPCommon.h" -FTPCommon::FTPCommon(FS &_FSImplementation) : - THEFS(_FSImplementation), sTimeOutMs(FTP_TIME_OUT*60*1000), aTimeout(FTP_TIME_OUT*60*1000) +FTPCommon::FTPCommon(FS &_FSImplementation) : THEFS(_FSImplementation), sTimeOutMs(FTP_TIME_OUT * 60 * 1000), aTimeout(FTP_TIME_OUT * 60 * 1000) { } @@ -23,14 +22,17 @@ void FTPCommon::setTimeout(uint32_t timeoutMs) sTimeOutMs = timeoutMs; } +// +// allocate a big buffer for file transfers +// uint16_t FTPCommon::allocateBuffer(uint16_t desiredBytes) { - // allocate a big buffer for file transfers +#if (defined ESP8266) uint16_t maxBlock = ESP.getMaxFreeBlockSize() / 2; if (desiredBytes > maxBlock) desiredBytes = maxBlock; - +#endif while (fileBuffer == NULL && desiredBytes > 0) { fileBuffer = (uint8_t *)malloc(desiredBytes); @@ -141,7 +143,7 @@ bool FTPCommon::doNetworkToFile() void FTPCommon::closeTransfer() { - freeBuffer(); - file.close(); - data.stop(); + freeBuffer(); + file.close(); + data.stop(); } diff --git a/FTPCommon.h b/FTPCommon.h index edb9d8b..a88dd8d 100644 --- a/FTPCommon.h +++ b/FTPCommon.h @@ -5,7 +5,18 @@ #include <FS.h> #include <WiFiClient.h> #include <WString.h> + +#ifdef ESP8266 #include <PolledTimeout.h> +using esp8266::polledTimeout::oneShotMs; // import the type to the local namespace +#define BUFFERSIZE TCP_MSS +#define PRINTu32 "lu" +#elif defined ESP32 +#include "esp32compat/PolledTimeout.h" +using esp32::polledTimeout::oneShotMs; +#define BUFFERSIZE CONFIG_TCP_MSS +#define PRINTu32 "u" +#endif #define FTP_SERVER_VERSION "0.9.7-20200529" @@ -84,8 +95,6 @@ #define FTP_CMD_LE_SYST 0x54535953 // "SYST" as uint32_t (little endian) #define FTP_CMD_BE_SYST 0x53595354 // "SYST" as uint32_t (big endian) -using esp8266::polledTimeout::oneShotMs; // import the type to the local namespace - class FTPCommon { public: @@ -124,7 +133,7 @@ protected: bool doNetworkToFile(); virtual void closeTransfer(); - uint16_t allocateBuffer(uint16_t desiredBytes); // allocate buffer for transfer + uint16_t allocateBuffer(uint16_t desiredBytes = BUFFERSIZE); // allocate buffer for transfer void freeBuffer(); uint8_t *fileBuffer = NULL; // pointer to buffer for file transfer (by allocateBuffer) uint16_t fileBufferSize; // size of buffer diff --git a/FTPServer.cpp b/FTPServer.cpp index c521691..bad2fb2 100644 --- a/FTPServer.cpp +++ b/FTPServer.cpp @@ -546,31 +546,37 @@ int8_t FTPServer::processCommand() path.remove(dashPos); } FTP_DEBUG_MSG("Listing content of '%s'", path.c_str()); +#if (defined ESP8266) Dir dir = THEFS.openDir(path); while (dir.next()) { - ++dirCount; - bool isDir = false; - String fn = dir.fileName(); - if (cwd == FPSTR(aSlash) && fn[0] == '/') - fn.remove(0, 1); - isDir = dir.isDirectory(); file = dir.openFile("r"); +#elif (defined ESP32) + File dir = THEFS.open(path); + file = dir.openNextFile(); + while (file) + { +#endif + bool isDir = file.isDirectory(); + String fn = file.name(); + uint32_t fs = file.size(); String fileTime = makeDateTimeStr(file.getLastWrite()); file.close(); + if (cwd == FPSTR(aSlash) && fn[0] == '/') + fn.remove(0, 1); if (FTP_CMD(LIST) == command) { // unixperms type userid groupid size time & date name // drwxrwsr-x 2 111 117 4096 Apr 01 12:45 aDirectory // -rw-rw-r-- 1 111 117 875315 Mar 23 17:29 aFile - data.printf_P(PSTR("%crw%cr-%cr-%c %c 0 0 %8lu %s %s\r\n"), + data.printf_P(PSTR("%crw%cr-%cr-%c %c 0 0 %8" PRINTu32 " %s %s\r\n"), isDir ? 'd' : '-', isDir ? 'x' : '-', isDir ? 'x' : '-', isDir ? 'x' : '-', isDir ? '2' : '1', - isDir ? 0 : (uint32_t)dir.fileSize(), + isDir ? 0 : fs, fileTime.c_str(), fn.c_str()); //data.printf_P(PSTR("+r,s%lu\r\n,\t%s\r\n"), (uint32_t)dir.fileSize(), fn.c_str()); @@ -586,7 +592,7 @@ int8_t FTPServer::processCommand() } else { - data.printf_P(PSTR("0644;size=%lu;type=file; "), (uint32_t)dir.fileSize()); + data.printf_P(PSTR("0644;size=%" PRINTu32 ";type=file; "), fs); } data.printf_P(PSTR("%s\r\n"), fn.c_str()); } @@ -594,124 +600,21 @@ int8_t FTPServer::processCommand() { data.println(fn); } + ++dirCount; +#if (defined ESP32) + file = dir.openNextFile(); +#endif } - + if (FTP_CMD(MLSD) == command) { control.println(F("226-options: -a -l\r\n")); } sendMessage_P(226, PSTR("%d matches total"), dirCount); } -#if defined ESP32 - File root = THEFS.open(cwd); - if (!root) - { - sendMessage_P(550, PSTR("Can't open directory \"%s\""), cwd.c_str()); - // return; - } - else - { - // if(!root.isDirectory()){ - // FTP_DEBUG_MSG("Not a directory: '%s'", cwd.c_str()); - // return; - // } - - File file = root.openNextFile(); - while (file) - { - if (file.isDirectory()) - { - data.println("+r,s <DIR> " + String(file.name())); - // Serial.print(" DIR : "); - // Serial.println(file.name()); - // if(levels){ - // listDir(fs, file.name(), levels -1); - // } - } - else - { - String fn, fs; - fn = file.name(); - // fn.remove(0, 1); - fs = String(file.size()); - data.println("+r,s" + fs); - data.println(",\t" + fn); - nm++; - } - file = root.openNextFile(); - } - sendMessage_P(226, PSTR("%d matches total"), nm); - } -#endif data.stop(); } -#if defined ESP32 - // - // FIXME MLSD ESP32 - // - else if (!strcmp(command, "MLSD")) - { - File root = THEFS.open(cwd); - // if(!root){ - // control.println( "550, "Can't open directory " + cwd ); - // // return; - // } else { - // if(!root.isDirectory()){ - // Serial.println("Not a directory"); - // return; - // } - - File file = root.openNextFile(); - while (file) - { - // if(file.isDirectory()){ - // data.println( "+r,s <DIR> " + String(file.name())); - // // Serial.print(" DIR : "); - // // Serial.println(file.name()); - // // if(levels){ - // // listDir(fs, file.name(), levels -1); - // // } - // } else { - String fn, fs; - fn = file.name(); - fn.remove(0, 1); - fs = String(file.size()); - data.println("Type=file;Size=" + fs + ";" + "modify=20000101160656;" + " " + fn); - nm++; - // } - file = root.openNextFile(); - } - sendMessage_P(226, PSTR("-options: -a -l")); - sendMessage_P(226, PSTR("%d matches total"), nm); - // } - data.stop(); - } - // - // NLST - // - else if (!strcmp(command, "NLST")) - { - File root = THEFS.open(cwd); - if (!root) - { - sendMessage_P(550, "Can't open directory %s\n"), cwd.c_str()); - } - else - { - File file = root.openNextFile(); - while (file) - { - data.println(file.name()); - nm++; - file = root.openNextFile(); - } - sendMessage_P(226, "%d matches total", nm); - } - data.stop(); - } -#endif - // // RETR - Retrieve // @@ -730,7 +633,7 @@ int8_t FTPServer::processCommand() { sendMessage_P(550, PSTR("File \"%s\" not found."), parameters.c_str()); } - else if (!file.isFile()) + else if (file.isDirectory()) { sendMessage_P(450, PSTR("Cannot open file \"%s\"."), parameters.c_str()); } @@ -748,7 +651,7 @@ int8_t FTPServer::processCommand() millisBeginTrans = millis(); bytesTransfered = 0; uint32_t fs = file.size(); - if (allocateBuffer(TCP_MSS)) + if (allocateBuffer()) { FTP_DEBUG_MSG("Sending file '%s' (%lu bytes)", path.c_str(), fs); sendMessage_P(150, PSTR("%lu bytes to download"), fs); @@ -795,7 +698,7 @@ int8_t FTPServer::processCommand() transferState = tStore; millisBeginTrans = millis(); bytesTransfered = 0; - if (allocateBuffer(TCP_MSS)) + if (allocateBuffer()) { FTP_DEBUG_MSG("Receiving file '%s' => %s", parameters.c_str(), path.c_str()); sendMessage_P(150, PSTR("Connected to port %d"), dataPort); @@ -839,9 +742,17 @@ int8_t FTPServer::processCommand() sendMessage_P(550, "Remove directory operation failed."); //not support on SPIFFS #else // check directory for files +#if (defined ESP8266) Dir dir = THEFS.openDir(path); if (dir.next()) { +#elif (defined ESP32) + File dir = THEFS.open(path); + file = dir.openNextFile(); + if (file) + { + file.close(); +#endif //only delete if dir is empty! sendMessage_P(550, PSTR("Remove directory operation failed, directory is not empty.")); } diff --git a/esp32compat/PolledTimeout.h b/esp32compat/PolledTimeout.h new file mode 100644 index 0000000..64677b7 --- /dev/null +++ b/esp32compat/PolledTimeout.h @@ -0,0 +1,254 @@ +#ifndef __POLLEDTIMING_H__ +#define __POLLEDTIMING_H__ + + +/* + PolledTimeout.h - Encapsulation of a polled Timeout + + Copyright (c) 2018 Daniel Salazar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <limits> + +#include <Arduino.h> + +namespace esp32 +{ + + +namespace polledTimeout +{ + +namespace YieldPolicy +{ + +struct DoNothing +{ + static void execute() {} +}; + +struct YieldOrSkip +{ + static void execute() {delay(0);} +}; + +template <unsigned long delayMs> +struct YieldAndDelayMs +{ + static void execute() {delay(delayMs);} +}; + +} //YieldPolicy + +namespace TimePolicy +{ + +struct TimeSourceMillis +{ + // time policy in milli-seconds based on millis() + + using timeType = decltype(millis()); + static timeType time() {return millis();} + static constexpr timeType ticksPerSecond = 1000; + static constexpr timeType ticksPerSecondMax = 1000; +}; + +template <typename TimeSourceType, unsigned long long second_th> + // "second_th" units of timeType for one second +struct TimeUnit +{ + using timeType = typename TimeSourceType::timeType; + +#if __GNUC__ < 5 + // gcc-4.8 cannot compile the constexpr-only version of this function + // using #defines instead luckily works + static constexpr timeType computeRangeCompensation () + { + #define number_of_secondTh_in_one_tick ((1.0 * second_th) / ticksPerSecond) + #define fractional (number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick) + + return ({ + fractional == 0? + 1: // no need for compensation + (number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division + }); + + #undef number_of_secondTh_in_one_tick + #undef fractional + } +#else + static constexpr timeType computeRangeCompensation () + { + return ({ + constexpr double number_of_secondTh_in_one_tick = (1.0 * second_th) / ticksPerSecond; + constexpr double fractional = number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick; + fractional == 0? + 1: // no need for compensation + (number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division + }); + } +#endif + + static constexpr timeType ticksPerSecond = TimeSourceType::ticksPerSecond; + static constexpr timeType ticksPerSecondMax = TimeSourceType::ticksPerSecondMax; + static constexpr timeType rangeCompensate = computeRangeCompensation(); + static constexpr timeType user2UnitMultiplierMax = (ticksPerSecondMax * rangeCompensate) / second_th; + static constexpr timeType user2UnitMultiplier = (ticksPerSecond * rangeCompensate) / second_th; + static constexpr timeType user2UnitDivider = rangeCompensate; + // std::numeric_limits<timeType>::max() is reserved + static constexpr timeType timeMax = (std::numeric_limits<timeType>::max() - 1) / user2UnitMultiplierMax; + + static timeType toTimeTypeUnit (const timeType userUnit) {return (userUnit * user2UnitMultiplier) / user2UnitDivider;} + static timeType toUserUnit (const timeType internalUnit) {return (internalUnit * user2UnitDivider) / user2UnitMultiplier;} + static timeType time () {return TimeSourceType::time();} +}; + +using TimeMillis = TimeUnit< TimeSourceMillis, 1000 >; + +} //TimePolicy + +template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing, typename TimePolicyT = TimePolicy::TimeMillis> +class timeoutTemplate +{ +public: + using timeType = typename TimePolicyT::timeType; + static_assert(std::is_unsigned<timeType>::value == true, "timeType must be unsigned"); + + static constexpr timeType alwaysExpired = 0; + static constexpr timeType neverExpires = std::numeric_limits<timeType>::max(); + static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug + + timeoutTemplate(const timeType userTimeout) + { + reset(userTimeout); + } + + IRAM_ATTR // fast + bool expired() + { + YieldPolicyT::execute(); //in case of DoNothing: gets optimized away + if(PeriodicT) //in case of false: gets optimized away + return expiredRetrigger(); + return expiredOneShot(); + } + + IRAM_ATTR // fast + operator bool() + { + return expired(); + } + + bool canExpire () const + { + return !_neverExpires; + } + + bool canWait () const + { + return _timeout != alwaysExpired; + } + + IRAM_ATTR // called from ISR + void reset(const timeType newUserTimeout) + { + reset(); + _timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout); + _neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax()); + } + + IRAM_ATTR // called from ISR + void reset() + { + _start = TimePolicyT::time(); + } + + void resetToNeverExpires () + { + _timeout = alwaysExpired + 1; // because canWait() has precedence + _neverExpires = true; + } + + timeType getTimeout() const + { + return TimePolicyT::toUserUnit(_timeout); + } + + static constexpr timeType timeMax() + { + return TimePolicyT::timeMax; + } + +private: + + IRAM_ATTR // fast + bool checkExpired(const timeType internalUnit) const + { + // canWait() is not checked here + // returns "can expire" and "time expired" + return (!_neverExpires) && ((internalUnit - _start) >= _timeout); + } + +protected: + + IRAM_ATTR // fast + bool expiredRetrigger() + { + if (!canWait()) + return true; + + timeType current = TimePolicyT::time(); + if(checkExpired(current)) + { + unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) + _start += n * _timeout; + return true; + } + return false; + } + + IRAM_ATTR // fast + bool expiredOneShot() const + { + // returns "always expired" or "has expired" + return !canWait() || checkExpired(TimePolicyT::time()); + } + + timeType _timeout; + timeType _start; + bool _neverExpires; +}; + +// standard versions (based on millis()) +// timeMax() is 49.7 days ((2^32)-2 ms) + +using oneShotMs = polledTimeout::timeoutTemplate<false>; +using periodicMs = polledTimeout::timeoutTemplate<true>; + + +} //polledTimeout + + +/* A 1-shot timeout that auto-yields when in CONT can be built as follows: + * using oneShotYieldMs = esp32::polledTimeout::timeoutTemplate<false, esp32::polledTimeout::YieldPolicy::YieldOrSkip>; + * + * Other policies can be implemented by the user, e.g.: simple yield that panics in SYS, and the polledTimeout types built as needed as shown above, without modifying this file. + */ + +}//esp32 + +#endif diff --git a/examples/FTPServerSample/LittleFSSample/LittleFSSample.ino b/examples/FTPServerSample/LittleFSSample/LittleFSSample.ino index 197c730..d30120b 100644 --- a/examples/FTPServerSample/LittleFSSample/LittleFSSample.ino +++ b/examples/FTPServerSample/LittleFSSample/LittleFSSample.ino @@ -15,12 +15,11 @@ Daniel Plasa <dplasa@gmail.com> */ -#ifdef ESP8266 -#include <ESP8266WiFi.h> -#elif defined ESP32 -#include <WiFi.h> +#if (defined ESP32) +#error "No LittlFS on the ESP32" #endif +#include <ESP8266WiFi.h> #include <LittleFS.h> #include <FTPServer.h> @@ -47,7 +46,7 @@ void setup(void) Serial.printf_P(PSTR("\nConnected to %S, IP address is %s\n"), ssid, WiFi.localIP().toString().c_str()); // setup the ftp server with username and password - // ports are defined in esFTP.h, default is + // ports are defined in FTPCommon.h, default is // 21 for the control connection // 50009 for the data connection (passive mode by default) ftpSrv.begin(F("ftp"), F("ftp")); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV) diff --git a/examples/FTPServerSample/SPIFFSSample/SPIFFSSample.ino b/examples/FTPServerSample/SPIFFSSample/SPIFFSSample.ino index 358404c..b15bd98 100644 --- a/examples/FTPServerSample/SPIFFSSample/SPIFFSSample.ino +++ b/examples/FTPServerSample/SPIFFSSample/SPIFFSSample.ino @@ -15,18 +15,18 @@ Daniel Plasa <dplasa@gmail.com> */ -#ifdef ESP8266 -#include <FS.h> -#include <ESP8266WiFi.h> -#elif defined ESP32 +#if (defined ESP32) #include <WiFi.h> #include <SPIFFS.h> +#elif (defined ESP8266) +#include <ESP8266WiFi.h> +#include <FS.h> #endif #include <FTPServer.h> -const char *ssid PROGMEM = "YOUR_SSID"; -const char *password PROGMEM = "YOUR_PASS"; +const char *ssid = "YOUR_SSID"; +const char *password = "YOUR_PASS"; // Since SPIFFS is becoming deprecated but might still be in // use in your Projects, tell the FtpServer to use SPIFFS @@ -38,7 +38,7 @@ void setup(void) WiFi.begin(ssid, password); bool fsok = SPIFFS.begin(); - Serial.printf_P(PSTR("FS init: %S\n"), fsok ? PSTR("ok") : PSTR("fail!")); + Serial.printf_P(PSTR("FS init: %s\n"), fsok ? PSTR("ok") : PSTR("fail!")); // Wait for connection while (WiFi.status() != WL_CONNECTED) @@ -46,10 +46,10 @@ void setup(void) delay(500); Serial.printf_P(PSTR(".")); } - Serial.printf_P(PSTR("\nConnected to %S, IP address is %s\n"), ssid, WiFi.localIP().toString().c_str()); + Serial.printf_P(PSTR("\nConnected to %s, IP address is %s\n"), ssid, WiFi.localIP().toString().c_str()); // setup the ftp server with username and password - // ports are defined in esFTP.h, default is + // ports are defined in FTPCommon.h, default is // 21 for the control connection // 50009 for the data connection (passive mode by default) ftpSrv.begin(F("ftp"), F("ftp")); @@ -102,14 +102,59 @@ void loop(void) else if (action == list) { Serial.printf_P(PSTR("Listing contents...\n")); - uint16_t dirCount = 0; - Dir dir = SPIFFS.openDir(F("/")); - while (dir.next()) - { - ++dirCount; - Serial.printf_P(PSTR("%6ld %s\n"), (uint32_t)dir.fileSize(), dir.fileName().c_str()); - } + uint16_t dirCount = ListDir("/"); Serial.printf_P(PSTR("%d files total\n"), dirCount); action = show; } } + +#if (defined ESP8266) +uint16_t ListDir(const char *path) +{ + uint16_t dirCount = 0; + Dir dir = SPIFFS.openDir(path); + while (dir.next()) + { + ++dirCount; + Serial.printf_P(PSTR("%6ld %s\n"), (uint32_t)dir.fileSize(), dir.fileName().c_str()); + } + return dirCount; +} +#elif (defined ESP32) +uint16_t ListDir(const char *path) +{ + uint16_t dirCount = 0; + File root = SPIFFS.open(path); + if (!root) + { + Serial.println(F("failed to open root")); + return 0; + } + if (!root.isDirectory()) + { + Serial.println(F("/ not a directory")); + return 0; + } + + File file = root.openNextFile(); + while (file) + { + ++dirCount; + if (file.isDirectory()) + { + Serial.print(F(" DIR : ")); + Serial.println(file.name()); + dirCount += ListDir(file.name()); + } + else + { + Serial.print(F(" FILE: ")); + Serial.print(file.name()); + Serial.print(F("\tSIZE: ")); + Serial.println(file.size()); + } + file = root.openNextFile(); + } + return dirCount; +} +#endif
\ No newline at end of file |