diff --git a/cores/esp8266/FS.cpp b/cores/esp8266/FS.cpp index d7baa43069..fa841c64fa 100644 --- a/cores/esp8266/FS.cpp +++ b/cores/esp8266/FS.cpp @@ -180,6 +180,19 @@ String File::readString() return ret; } +time_t File::getLastWrite() { + if (!_p) + return 0; + + return _p->getLastWrite(); +} + +void File::setTimeCallback(time_t (*cb)(void)) { + if (!_p) + return; + _p->setTimeCallback(cb); +} + File Dir::openFile(const char* mode) { if (!_impl) { return File(); @@ -192,7 +205,9 @@ File Dir::openFile(const char* mode) { return File(); } - return File(_impl->openFile(om, am), _baseFS); + File f(_impl->openFile(om, am), _baseFS); + f.setTimeCallback(timeCallback); + return f; } String Dir::fileName() { @@ -203,6 +218,12 @@ String Dir::fileName() { return _impl->fileName(); } +time_t Dir::fileTime() { + if (!_impl) + return 0; + return _impl->fileTime(); +} + size_t Dir::fileSize() { if (!_impl) { return 0; @@ -241,6 +262,20 @@ bool Dir::rewind() { return _impl->rewind(); } +time_t Dir::getLastWrite() { + if (!_impl) + return 0; + + return _impl->getLastWrite(); +} + +void Dir::setTimeCallback(time_t (*cb)(void)) { + if (!_impl) + return; + _impl->setTimeCallback(cb); +} + + bool FS::setConfig(const FSConfig &cfg) { if (!_impl) { return false; @@ -315,7 +350,9 @@ File FS::open(const char* path, const char* mode) { DEBUGV("FS::open: invalid mode `%s`\r\n", mode); return File(); } - return File(_impl->open(path, om, am), this); + File f(_impl->open(path, om, am), this); + f.setTimeCallback(timeCallback); + return f; } bool FS::exists(const char* path) { @@ -334,7 +371,9 @@ Dir FS::openDir(const char* path) { return Dir(); } DirImplPtr p = _impl->openDir(path); - return Dir(p, this); + Dir d(p, this); + d.setTimeCallback(timeCallback); + return d; } Dir FS::openDir(const String& path) { @@ -385,6 +424,11 @@ bool FS::rename(const String& pathFrom, const String& pathTo) { return rename(pathFrom.c_str(), pathTo.c_str()); } +void FS::setTimeCallback(time_t (*cb)(void)) { + if (!_impl) + return; + _impl->setTimeCallback(cb); +} static bool sflags(const char* mode, OpenMode& om, AccessMode& am) { diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index 669287876d..39fd7c0f06 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -110,12 +110,16 @@ class File : public Stream String readString() override; + time_t getLastWrite(); + void setTimeCallback(time_t (*cb)(void)); + protected: FileImplPtr _p; // Arduino SD class emulation std::shared_ptr _fakeDir; FS *_baseFS; + time_t (*timeCallback)(void) = nullptr; }; class Dir { @@ -126,15 +130,21 @@ class Dir { String fileName(); size_t fileSize(); + time_t fileTime(); bool isFile() const; bool isDirectory() const; bool next(); bool rewind(); + time_t getLastWrite(); + void setTimeCallback(time_t (*cb)(void)); + protected: DirImplPtr _impl; FS *_baseFS; + time_t (*timeCallback)(void) = nullptr; + }; // Backwards compatible, <4GB filesystem usage @@ -161,12 +171,10 @@ struct FSInfo64 { class FSConfig { public: - FSConfig(bool autoFormat = true) { - _type = FSConfig::fsid::FSId; - _autoFormat = autoFormat; - } + static constexpr uint32_t FSId = 0x00000000; + + FSConfig(uint32_t type = FSId, bool autoFormat = true) : _type(type), _autoFormat(autoFormat) { } - enum fsid { FSId = 0x00000000 }; FSConfig setAutoFormat(bool val = true) { _autoFormat = val; return *this; @@ -179,17 +187,17 @@ class FSConfig class SPIFFSConfig : public FSConfig { public: - SPIFFSConfig(bool autoFormat = true) { - _type = SPIFFSConfig::fsid::FSId; - _autoFormat = autoFormat; - } - enum fsid { FSId = 0x53504946 }; + static constexpr uint32_t FSId = 0x53504946; + SPIFFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { } + + // Inherit _type and _autoFormat + // nothing yet, enableTime TBD when SPIFFS has metadate }; class FS { public: - FS(FSImplPtr impl) : _impl(impl) { } + FS(FSImplPtr impl) : _impl(impl) { timeCallback = _defaultTimeCB; } bool setConfig(const FSConfig &cfg); @@ -225,10 +233,14 @@ class FS bool gc(); bool check(); + void setTimeCallback(time_t (*cb)(void)); + friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits protected: FSImplPtr _impl; FSImplPtr getImpl() { return _impl; } + time_t (*timeCallback)(void); + static time_t _defaultTimeCB(void) { return time(NULL); } }; } // namespace fs diff --git a/cores/esp8266/FSImpl.h b/cores/esp8266/FSImpl.h index b7cf4a7a65..9715c65a8b 100644 --- a/cores/esp8266/FSImpl.h +++ b/cores/esp8266/FSImpl.h @@ -41,6 +41,19 @@ class FileImpl { virtual const char* fullName() const = 0; virtual bool isFile() const = 0; virtual bool isDirectory() const = 0; + + // Filesystems *may* support a timestamp per-file, so allow the user to override with + // their own callback for *this specific* file (as opposed to the FSImpl call of the + // same name. The default implementation simply returns time(&null) + virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; } + + // Return the last written time for a file. Undefined when called on a writable file + // as the FS is allowed to return either the time of the last write() operation or the + // time present in the filesystem metadata (often the last time the file was closed) + virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps + +protected: + time_t (*timeCallback)(void) = nullptr; }; enum OpenMode { @@ -62,10 +75,24 @@ class DirImpl { virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0; virtual const char* fileName() = 0; virtual size_t fileSize() = 0; + virtual time_t fileTime() { return 0; } // By default, FS doesn't report file times virtual bool isFile() const = 0; virtual bool isDirectory() const = 0; virtual bool next() = 0; virtual bool rewind() = 0; + + // Filesystems *may* support a timestamp per-file, so allow the user to override with + // their own callback for *this specific* file (as opposed to the FSImpl call of the + // same name. The default implementation simply returns time(&null) + virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; } + + // Return the last written time for a file. Undefined when called on a writable file + // as the FS is allowed to return either the time of the last write() operation or the + // time present in the filesystem metadata (often the last time the file was closed) + virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps + +protected: + time_t (*timeCallback)(void) = nullptr; }; class FSImpl { @@ -86,6 +113,14 @@ class FSImpl { virtual bool rmdir(const char* path) = 0; virtual bool gc() { return true; } // May not be implemented in all file systems. virtual bool check() { return true; } // May not be implemented in all file systems. + + // Filesystems *may* support a timestamp per-file, so allow the user to override with + // their own callback for all files on this FS. The default implementation simply + // returns the present time as reported by time(&null) + virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; } + +protected: + time_t (*timeCallback)(void) = nullptr; }; } // namespace fs diff --git a/cores/esp8266/spiffs_api.h b/cores/esp8266/spiffs_api.h index c19dbc2c4e..44f34cd1d4 100644 --- a/cores/esp8266/spiffs_api.h +++ b/cores/esp8266/spiffs_api.h @@ -162,7 +162,7 @@ class SPIFFSImpl : public FSImpl bool setConfig(const FSConfig &cfg) override { - if ((cfg._type != SPIFFSConfig::fsid::FSId) || (SPIFFS_mounted(&_fs) != 0)) { + if ((cfg._type != SPIFFSConfig::FSId) || (SPIFFS_mounted(&_fs) != 0)) { return false; } _cfg = *static_cast(&cfg); diff --git a/doc/filesystem.rst b/doc/filesystem.rst index d70655f2fe..1e7ae14227 100644 --- a/doc/filesystem.rst +++ b/doc/filesystem.rst @@ -92,6 +92,27 @@ and ``SPIFFS.open()`` to ``LittleFS.open()`` with the rest of the code remaining untouched. +SDFS and SD +----------- +FAT filesystems are supported on the ESP8266 using the old Arduino wrapper +"SD.h" which wraps the "SDFS.h" filesystem transparently. + +Any commands discussed below pertaining to SPIFFS or LittleFS are +applicable to SD/SDFS. + +For legacy applications, the classic SD filesystem may continue to be used, +but for new applications, directly accessing the SDFS filesystem is +recommended as it may expose additional functionality that the old Arduino +SD filesystem didn't have. + +Note that in earlier releases of the core, using SD and SPIFFS in the same +sketch was complicated and required the use of ``NO_FS_GLOBALS``. The +current design makes SD, SDFS, SPIFFS, and LittleFS fully source compatible +and so please remove any ``NO_FS_GLOBALS`` definitions in your projects +when updgrading core versions. + + + SPIFFS file system limitations ------------------------------ @@ -198,8 +219,8 @@ use esptool.py. - To upload a LittleFS filesystem use Tools > ESP8266 LittleFS Data Upload -File system object (SPIFFS/LittleFS) ------------------------------------- +File system object (SPIFFS/LittleFS/SD/SDFS) +-------------------------------------------- setConfig ~~~~~~~~~ @@ -369,6 +390,31 @@ rename Renames file from ``pathFrom`` to ``pathTo``. Paths must be absolute. Returns *true* if file was renamed successfully. +gc +~~ + +.. code:: cpp + + SPIFFS.gc() + +Only implemented in SPIFFS. Performs a quick garbage collection operation on SPIFFS, +possibly making writes perform faster/better in the future. On very full or very fragmented +filesystems, using this call can avoid or reduce issues where SPIFFS reports free space +but is unable to write additional data to a file. See `this discussion +` for more info. + +check +~~~~~ + +.. code:: cpp + + SPIFFS.begin(); + SPIFFS.check(); + +Only implemented in SPIFFS. Performs an in-depth check of the filesystem metadata and +correct what is repairable. Not normally needed, and not guaranteed to actually fix +anything should there be corruption. + info ~~~~ @@ -379,7 +425,7 @@ info or LittleFS.info(fs_info); Fills `FSInfo structure <#filesystem-information-structure>`__ with -information about the file system. Returns ``true`` is successful, +information about the file system. Returns ``true`` if successful, ``false`` otherwise. Filesystem information structure @@ -404,30 +450,45 @@ block size - ``pageSize`` — filesystem logical page size - ``maxOpenFiles`` ``maxPathLength`` — max file name length (including one byte for zero termination) -gc -~~ +info64 +~~~~~~ .. code:: cpp - SPIFFS.gc() + FSInfo64 fsinfo; + SD.info(fsinfo); + or LittleFS(fsinfo); -Only implemented in SPIFFS. Performs a quick garbage collection operation on SPIFFS, -possibly making writes perform faster/better in the future. On very full or very fragmented -filesystems, using this call can avoid or reduce issues where SPIFFS reports free space -but is unable to write additional data to a file. See `this discussion -` for more info. +Performs the same operation as ``info`` but allows for reporting greater than +4GB for filesystem size/used/etc. Should be used with the SD and SDFS +filesystems since most SD cards today are greater than 4GB in size. -check -~~~~~ +setTimeCallback(time_t (*cb)(void)) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: cpp - SPIFFS.begin(); - SPIFFS.check(); + time_t myTimeCallback() { + return 1455451200; // UNIX timestamp + } + void setup () { + LittleFS.setTimeCallback(myTimeCallback); + ... + // Any files will now be made with Pris' incept date + } -Only implemented in SPIFFS. Performs an in-depth check of the filesystem metadata and -correct what is repairable. Not normally needed, and not guaranteed to actually fix -anything should there be corruption. + +The SD, SDFS, and LittleFS filesystems support a file timestamp, updated when the file is +opened for writing. By default, the ESP8266 will use the internal time returned from +``time(NULL)`` (i.e. local time, not UTC, to conform to the existing FAT filesystem), but this +can be overridden to GMT or any other standard you'd like by using ``setTimeCallback()``. +If your app sets the system time using NTP before file operations, then +you should not need to use this function. However, if you need to set a specific time +for a file, or the system clock isn't correct and you need to read the time from an external +RTC or use a fixed time, this call allows you do to so. + +In general use, with a functioning ``time()`` call, user applications should not need +to use this function. Directory object (Dir) ---------------------- @@ -468,6 +529,12 @@ fileSize Returns the size of the current file pointed to by the internal iterator. +fileTime +~~~~~~~~ + +Returns the time_t write time of the current file pointed +to by the internal iterator. + isFile ~~~~~~ @@ -491,6 +558,13 @@ rewind Resets the internal pointer to the start of the directory. +setTimeCallback(time_t (*cb)(void)) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sets the time callback for any files accessed from this Dir object via openNextFile. +Note that the SD and SDFS filesystems only support a filesystem-wide callback and +calls to ``Dir::setTimeCallback`` may produce unexpected behavior. + File object ----------- @@ -562,6 +636,12 @@ fullName Returns the full path file name as a ``const char*``. +getLastWrite +~~~~~~~~~~~~ + +Returns the file last write time, and only valid for files opened in read-only +mode. If a file is opened for writing, the returned time may be indeterminate. + isFile ~~~~~~ @@ -616,3 +696,10 @@ rewindDirectory (compatibiity method, not recommended for new code) Resets the ``openNextFile`` pointer to the top of the directory. Only valid when ``File.isDirectory() == true``. + +setTimeCallback(time_t (*cb)(void)) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sets the time callback for this specific file. Note that the SD and +SDFS filesystems only support a filesystem-wide callback and calls to +``Dir::setTimeCallback`` may produce unexpected behavior. diff --git a/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino b/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino new file mode 100644 index 0000000000..4c6b487aef --- /dev/null +++ b/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino @@ -0,0 +1,181 @@ +/* Example showing timestamp support in LittleFS */ +/* Released into the public domain. */ +/* Earle F. Philhower, III */ + +#include +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +long timezone = 2; +byte daysavetime = 1; + + +bool getLocalTime(struct tm * info, uint32_t ms) { + uint32_t count = ms / 10; + time_t now; + + time(&now); + localtime_r(&now, info); + + if (info->tm_year > (2016 - 1900)) { + return true; + } + + while (count--) { + delay(10); + time(&now); + localtime_r(&now, info); + if (info->tm_year > (2016 - 1900)) { + return true; + } + } + return false; +} + + +void listDir(const char * dirname) { + Serial.printf("Listing directory: %s\n", dirname); + + Dir root = LittleFS.openDir(dirname); + + while (root.next()) { + File file = root.openFile("r"); + Serial.print(" FILE: "); + Serial.print(root.fileName()); + Serial.print(" SIZE: "); + Serial.print(file.size()); + time_t t = file.getLastWrite(); + struct tm * tmstruct = localtime(&t); + file.close(); + Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec); + } +} + + +void readFile(const char * path) { + Serial.printf("Reading file: %s\n", path); + + File file = LittleFS.open(path, "r"); + if (!file) { + Serial.println("Failed to open file for reading"); + return; + } + + Serial.print("Read from file: "); + while (file.available()) { + Serial.write(file.read()); + } + file.close(); +} + +void writeFile(const char * path, const char * message) { + Serial.printf("Writing file: %s\n", path); + + File file = LittleFS.open(path, "w"); + if (!file) { + Serial.println("Failed to open file for writing"); + return; + } + if (file.print(message)) { + Serial.println("File written"); + } else { + Serial.println("Write failed"); + } + file.close(); +} + +void appendFile(const char * path, const char * message) { + Serial.printf("Appending to file: %s\n", path); + + File file = LittleFS.open(path, "a"); + if (!file) { + Serial.println("Failed to open file for appending"); + return; + } + if (file.print(message)) { + Serial.println("Message appended"); + } else { + Serial.println("Append failed"); + } + file.close(); +} + +void renameFile(const char * path1, const char * path2) { + Serial.printf("Renaming file %s to %s\n", path1, path2); + if (LittleFS.rename(path1, path2)) { + Serial.println("File renamed"); + } else { + Serial.println("Rename failed"); + } +} + +void deleteFile(const char * path) { + Serial.printf("Deleting file: %s\n", path); + if (LittleFS.remove(path)) { + Serial.println("File deleted"); + } else { + Serial.println("Delete failed"); + } +} + +void setup() { + Serial.begin(115200); + // We start by connecting to a WiFi network + Serial.println(); + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println("Contacting Time Server"); + configTime(3600 * timezone, daysavetime * 3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + struct tm tmstruct ; + delay(2000); + tmstruct.tm_year = 0; + getLocalTime(&tmstruct, 5000); + Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec); + Serial.println(""); + + Serial.printf("Formatting LittleFS filesystem\n"); + LittleFS.format(); + listDir("/"); + deleteFile("/hello.txt"); + writeFile("/hello.txt", "Hello "); + appendFile("/hello.txt", "World!\n"); + listDir("/"); + + Serial.printf("The timestamp should be valid above\n"); + + Serial.printf("Now unmount and remount and perform the same operation.\n"); + Serial.printf("Timestamp should be valid, data should be good.\n"); + LittleFS.end(); + Serial.printf("Now mount it\n"); + if (!LittleFS.begin()) { + Serial.println("LittleFS mount failed"); + return; + } + readFile("/hello.txt"); + listDir("/"); + + +} + +void loop() { } + diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index 467255ca31..2a35869eda 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -47,11 +47,8 @@ class LittleFSDirImpl; class LittleFSConfig : public FSConfig { public: - LittleFSConfig(bool autoFormat = true) { - _type = LittleFSConfig::fsid::FSId; - _autoFormat = autoFormat; - } - enum fsid { FSId = 0x4c495454 }; + static constexpr uint32_t FSId = 0x4c495454; + LittleFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { } }; class LittleFSImpl : public FSImpl @@ -176,7 +173,7 @@ class LittleFSImpl : public FSImpl } bool setConfig(const FSConfig &cfg) override { - if ((cfg._type != LittleFSConfig::fsid::FSId) || _mounted) { + if ((cfg._type != LittleFSConfig::FSId) || _mounted) { return false; } _cfg = *static_cast(&cfg); @@ -422,7 +419,25 @@ class LittleFSFileImpl : public FileImpl lfs_file_close(_fs->getFS(), _getFD()); _opened = false; DEBUGV("lfs_file_close: fd=%p\n", _getFD()); + if (timeCallback) { + // Add metadata with last write time + time_t now = timeCallback(); + int rc = lfs_setattr(_fs->getFS(), _name.get(), 't', (const void *)&now, sizeof(now)); + if (rc < 0) { + DEBUGV("Unable to set time on '%s' to %d\n", _name.get(), now); + } + } + } + } + + time_t getLastWrite() override { + time_t ftime = 0; + if (_opened && _fd) { + int rc = lfs_getattr(_fs->getFS(), _name.get(), 't', (void *)&ftime, sizeof(ftime)); + if (rc != sizeof(ftime)) + ftime = 0; // Error, so clear read value } + return ftime; } const char* name() const override { @@ -520,6 +535,27 @@ class LittleFSDirImpl : public DirImpl return _dirent.size; } + time_t fileTime() override { + if (!_valid) { + return 0; + } + int nameLen = 3; // Slashes, terminator + nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0; + nameLen += strlen(_dirent.name); + char *tmpName = (char*)malloc(nameLen); + if (!tmpName) { + return 0; + } + snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name); + time_t ftime = 0; + int rc = lfs_getattr(_fs->getFS(), tmpName, 't', (void *)&ftime, sizeof(ftime)); + if (rc != sizeof(ftime)) + ftime = 0; // Error, so clear read value + free(tmpName); + return ftime; + } + + bool isFile() const override { return _valid && (_dirent.type == LFS_TYPE_REG); } diff --git a/libraries/SD/examples/listfiles/listfiles.ino b/libraries/SD/examples/listfiles/listfiles.ino index 2b5eaf1ab8..fd35d0c338 100644 --- a/libraries/SD/examples/listfiles/listfiles.ino +++ b/libraries/SD/examples/listfiles/listfiles.ino @@ -28,14 +28,14 @@ File root; void setup() { // Open serial communications and wait for port to open: - Serial.begin(9600); + Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } Serial.print("Initializing SD card..."); - if (!SD.begin(4)) { + if (!SD.begin(SS)) { Serial.println("initialization failed!"); return; } @@ -70,11 +70,13 @@ void printDirectory(File dir, int numTabs) { } else { // files have sizes, directories do not Serial.print("\t\t"); - Serial.println(entry.size(), DEC); + Serial.print(entry.size(), DEC); + Serial.print("\t\t"); + time_t ft = entry.getLastWrite(); + struct tm *tm = localtime(&ft); + // US format. Feel free to convert to your own locale... + Serial.printf("%02d-%02d-%02d %02d:%02d:%02d\n", tm->tm_mon + 1, tm->tm_mday, tm->tm_year % 100, tm->tm_hour, tm->tm_min, tm->tm_sec); } entry.close(); } } - - - diff --git a/libraries/SD/src/SD.cpp b/libraries/SD/src/SD.cpp index 6b40b0e494..a3f0431dd8 100644 --- a/libraries/SD/src/SD.cpp +++ b/libraries/SD/src/SD.cpp @@ -3,3 +3,5 @@ #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD) SDClass SD; #endif + +void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*) = nullptr; diff --git a/libraries/SD/src/SD.h b/libraries/SD/src/SD.h index bb1c696a58..d15d8f85b4 100644 --- a/libraries/SD/src/SD.h +++ b/libraries/SD/src/SD.h @@ -29,6 +29,7 @@ #undef FILE_WRITE #define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT | sdfat::O_APPEND) + class SDClass { public: boolean begin(uint8_t csPin, SPISettings cfg = SPI_HALF_SPEED) { @@ -137,6 +138,17 @@ class SDClass { return ((uint64_t)clusterSize() * (uint64_t)totalClusters()); } + void setTimeCallback(time_t (*cb)(void)) { + SDFS.setTimeCallback(cb); + } + + // Wrapper to allow obsolete datetimecallback use, silently convert to time_t in wrappertimecb + void dateTimeCallback(void (*cb)(uint16_t*, uint16_t*)) { + extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*); + __SD__userDateTimeCB = cb; + SDFS.setTimeCallback(wrapperTimeCB); + } + private: const char *getMode(uint8_t mode) { bool read = (mode & sdfat::O_READ) ? true : false; @@ -150,8 +162,46 @@ class SDClass { else { return "r"; } } + static time_t wrapperTimeCB(void) { + extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*); + if (__SD__userDateTimeCB) { + uint16_t d, t; + __SD__userDateTimeCB(&d, &t); + return sdfs::SDFSImpl::FatToTimeT(d, t); + } + return time(nullptr); + } + }; + +// Expose FatStructs.h helpers for MSDOS date/time for use with dateTimeCallback +static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) { + return (year - 1980) << 9 | month << 5 | day; +} +static inline uint16_t FAT_YEAR(uint16_t fatDate) { + return 1980 + (fatDate >> 9); +} +static inline uint8_t FAT_MONTH(uint16_t fatDate) { + return (fatDate >> 5) & 0XF; +} +static inline uint8_t FAT_DAY(uint16_t fatDate) { + return fatDate & 0X1F; +} +static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) { + return hour << 11 | minute << 5 | second >> 1; +} +static inline uint8_t FAT_HOUR(uint16_t fatTime) { + return fatTime >> 11; +} +static inline uint8_t FAT_MINUTE(uint16_t fatTime) { + return (fatTime >> 5) & 0X3F; +} +static inline uint8_t FAT_SECOND(uint16_t fatTime) { + return 2*(fatTime & 0X1F); +} + + #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD) extern SDClass SD; #endif diff --git a/libraries/SDFS/src/SDFS.h b/libraries/SDFS/src/SDFS.h index db55fe547a..c1c4f17ad8 100644 --- a/libraries/SDFS/src/SDFS.h +++ b/libraries/SDFS/src/SDFS.h @@ -45,22 +45,9 @@ class SDFSDirImpl; class SDFSConfig : public FSConfig { public: - SDFSConfig() { - _type = SDFSConfig::fsid::FSId; - _autoFormat = false; - _csPin = 4; - _spiSettings = SD_SCK_MHZ(10); - _part = 0; - } - SDFSConfig(uint8_t csPin, SPISettings spi) { - _type = SDFSConfig::fsid::FSId; - _autoFormat = false; - _csPin = csPin; - _spiSettings = spi; - _part = 0; - } + static constexpr uint32_t FSId = 0x53444653; - enum fsid { FSId = 0x53444653 }; + SDFSConfig(uint8_t csPin = 4, SPISettings spi = SD_SCK_MHZ(10)) : FSConfig(FSId, false), _csPin(csPin), _part(0), _spiSettings(spi) { } SDFSConfig setAutoFormat(bool val = true) { _autoFormat = val; @@ -152,7 +139,7 @@ class SDFSImpl : public FSImpl bool setConfig(const FSConfig &cfg) override { - if ((cfg._type != SDFSConfig::fsid::FSId) || _mounted) { + if ((cfg._type != SDFSConfig::FSId) || _mounted) { DEBUGV("SDFS::setConfig: invalid config or already mounted\n"); return false; } @@ -203,6 +190,20 @@ class SDFSImpl : public FSImpl return (clusterSize() * totalClusters()); } + // Helper function, takes FAT and makes standard time_t + static time_t FatToTimeT(uint16_t d, uint16_t t) { + struct tm tiempo; + memset(&tiempo, 0, sizeof(tiempo)); + tiempo.tm_sec = (((int)t) << 1) & 0x3e; + tiempo.tm_min = (((int)t) >> 5) & 0x3f; + tiempo.tm_hour = (((int)t) >> 11) & 0x1f; + tiempo.tm_mday = (int)(d & 0x1f); + tiempo.tm_mon = ((int)(d >> 5) & 0x0f) - 1; + tiempo.tm_year = ((int)(d >> 9) & 0x7f) + 80; + tiempo.tm_isdst = -1; + return mktime(&tiempo); + } + protected: friend class SDFileImpl; friend class SDFSDirImpl; @@ -212,6 +213,7 @@ class SDFSImpl : public FSImpl return &_fs; } + static uint8_t _getFlags(OpenMode openMode, AccessMode accessMode) { uint8_t mode = 0; if (openMode & OM_CREATE) { @@ -350,6 +352,17 @@ class SDFSFileImpl : public FileImpl return _opened ? _fd->isDirectory() : false; } + time_t getLastWrite() override { + time_t ftime = 0; + if (_opened && _fd) { + sdfat::dir_t tmp; + if (_fd.get()->dirEntry(&tmp)) { + ftime = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime); + } + } + return ftime; + } + protected: SDFSImpl* _fs; @@ -425,6 +438,12 @@ class SDFSDirImpl : public DirImpl _size = file.fileSize(); _isFile = file.isFile(); _isDirectory = file.isDirectory(); + sdfat::dir_t tmp; + if (file.dirEntry(&tmp)) { + _time = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime); + } else { + _time = 0; + } file.getName(_lfn, sizeof(_lfn)); file.close(); } else { @@ -447,6 +466,7 @@ class SDFSDirImpl : public DirImpl std::shared_ptr _dir; bool _valid; char _lfn[64]; + time_t _time; std::shared_ptr _dirPath; uint32_t _size; bool _isFile; diff --git a/package/package_esp8266com_index.template.json b/package/package_esp8266com_index.template.json index 709238253c..66b03a92f4 100644 --- a/package/package_esp8266com_index.template.json +++ b/package/package_esp8266com_index.template.json @@ -121,7 +121,7 @@ }, { "packager": "esp8266", - "version": "2.5.0-4-b40a506", + "version": "2.5.0-4-69bd9e6", "name": "mklittlefs" }, { @@ -302,61 +302,54 @@ ] }, { - "version": "2.5.0-4-b40a506", + "version": "2.5.0-4-69bd9e6", "name": "mklittlefs", "systems": [ { "host": "aarch64-linux-gnu", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/aarch64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz", - "archiveFileName": "aarch64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz", - "checksum": "SHA-256:25c4dcf818d175e19c3cc22bc0388c61fa3d9bdf82a1fad388323cef34caa169", - "size": "44059" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/aarch64-linux-gnu-mklittlefs-69bd9e6.tar.gz", + "archiveFileName": "aarch64-linux-gnu-mklittlefs-69bd9e6.tar.gz", + "checksum": "SHA-256:74d938f15a3fb8ac20aeb0f938ace2c6759f622451419c09446aa79866302e18", + "size": "44342" }, { "host": "arm-linux-gnueabihf", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/arm-linux-gnueabihf.mklittlefs-7f77f2b.1563313032.tar.gz", - "archiveFileName": "arm-linux-gnueabihf.mklittlefs-7f77f2b.1563313032.tar.gz", - "checksum": "SHA-256:75a284f4e8c54d302b1880df46dd48e18857f69c21baa0977b1e6efc404caf18", - "size": "36567" - }, - { - "host": "i686-pc-linux-gnu", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz", - "archiveFileName": "i686-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz", - "checksum": "SHA-256:022c96df4d110f957d43f6d23e9c5e8b699a66d8ab041056dd5da7411a8ade42", - "size": "47544" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/arm-linux-gnueabihf-mklittlefs-69bd9e6.tar.gz", + "archiveFileName": "arm-linux-gnueabihf-mklittlefs-69bd9e6.tar.gz", + "checksum": "SHA-256:926cca1c1f8f732a8ac79809ce0a52cabe283ab4137aa3237bca0fcca6bc2236", + "size": "36871" }, { "host": "i686-mingw32", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip", - "archiveFileName": "i686-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip", - "checksum": "SHA-256:7778209e9df8c8c5f5da82660ff9a95b866defee3c9eb5c22371e0fd84b1addc", - "size": "332057" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-w64-mingw32-mklittlefs-69bd9e6.zip", + "archiveFileName": "i686-w64-mingw32-mklittlefs-69bd9e6.zip", + "checksum": "SHA-256:da916c66f70e162f4aec22dbcb4542dd8b8187d12c35c915d563e2262cfe6fbd", + "size": "332325" }, { "host": "x86_64-apple-darwin", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-apple-darwin14.mklittlefs-7f77f2b.1563313032.tar.gz", - "archiveFileName": "x86_64-apple-darwin14.mklittlefs-7f77f2b.1563313032.tar.gz", - "checksum": "SHA-256:c465da766026c6c66d731442b741fb5a7f8b741e9473d181e6c5e588c541f588", - "size": "362014" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-apple-darwin14-mklittlefs-69bd9e6.tar.gz", + "archiveFileName": "x86_64-apple-darwin14-mklittlefs-69bd9e6.tar.gz", + "checksum": "SHA-256:35610be5f725121eaa9baea83c686693f340742e61739af6789d00feff4e90ba", + "size": "362366" }, { "host": "x86_64-pc-linux-gnu", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz", - "archiveFileName": "x86_64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz", - "checksum": "SHA-256:6a358716d4c780fa459b4c774723302431b3ad5e1ee3f7edae62be331541615c", - "size": "46164" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-linux-gnu-mklittlefs-69bd9e6.tar.gz", + "archiveFileName": "x86_64-linux-gnu-mklittlefs-69bd9e6.tar.gz", + "checksum": "SHA-256:e4ce7cc80eceab6a9a2e620f2badfb1ef09ee88f7af529f290c65b4b72f19358", + "size": "46518" }, { "host": "x86_64-mingw32", - "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip", - "archiveFileName": "x86_64-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip", - "checksum": "SHA-256:d5d44b5f21681a831318a23b31957bc9368c50f0766964ead409c3d2fe4747d2", - "size": "344578" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-w64-mingw32-mklittlefs-69bd9e6.zip", + "archiveFileName": "x86_64-w64-mingw32-mklittlefs-69bd9e6.zip", + "checksum": "SHA-256:c65ee1ee38f65ce67f664bb3118301ee6e93bec38a7a7efaf8e1d8455c6a4a18", + "size": "344780" } ] } ] } ] -} \ No newline at end of file +}