diff --git a/cores/esp8266/FS.cpp b/cores/esp8266/FS.cpp index fa841c64fa..c0a13a7614 100644 --- a/cores/esp8266/FS.cpp +++ b/cores/esp8266/FS.cpp @@ -187,6 +187,13 @@ time_t File::getLastWrite() { return _p->getLastWrite(); } +time_t File::getCreation() { + if (!_p) + return 0; + + return _p->getCreation(); +} + void File::setTimeCallback(time_t (*cb)(void)) { if (!_p) return; @@ -224,6 +231,12 @@ time_t Dir::fileTime() { return _impl->fileTime(); } +time_t Dir::fileCreation() { + if (!_impl) + return 0; + return _impl->fileCreation(); +} + size_t Dir::fileSize() { if (!_impl) { return 0; @@ -262,17 +275,11 @@ 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); + timeCallback = cb; } @@ -289,6 +296,7 @@ bool FS::begin() { DEBUGV("#error: FS: no implementation"); return false; } + _impl->setTimeCallback(timeCallback); bool ret = _impl->begin(); DEBUGV("%s\n", ret? "": "#error: FS could not start"); return ret; diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index 23d05bad83..a652b7511e 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -112,6 +112,7 @@ class File : public Stream String readString() override; time_t getLastWrite(); + time_t getCreation(); void setTimeCallback(time_t (*cb)(void)); protected: @@ -120,7 +121,6 @@ class File : public Stream // Arduino SD class emulation std::shared_ptr _fakeDir; FS *_baseFS; - time_t (*timeCallback)(void) = nullptr; }; class Dir { @@ -132,20 +132,19 @@ class Dir { String fileName(); size_t fileSize(); time_t fileTime(); + time_t fileCreation(); 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 diff --git a/cores/esp8266/FSImpl.h b/cores/esp8266/FSImpl.h index 9715c65a8b..3dea6f9424 100644 --- a/cores/esp8266/FSImpl.h +++ b/cores/esp8266/FSImpl.h @@ -51,6 +51,8 @@ class FileImpl { // 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 + // Same for creation time. + virtual time_t getCreation() { return 0; } // Default is to not support timestamps protected: time_t (*timeCallback)(void) = nullptr; @@ -75,7 +77,11 @@ class DirImpl { virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0; virtual const char* fileName() = 0; virtual size_t fileSize() = 0; + // 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 fileTime() { return 0; } // By default, FS doesn't report file times + virtual time_t fileCreation() { return 0; } // By default, FS doesn't report file times virtual bool isFile() const = 0; virtual bool isDirectory() const = 0; virtual bool next() = 0; @@ -86,11 +92,6 @@ class DirImpl { // 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; }; diff --git a/doc/filesystem.rst b/doc/filesystem.rst index fecd840742..3c2fb72664 100644 --- a/doc/filesystem.rst +++ b/doc/filesystem.rst @@ -535,6 +535,11 @@ fileTime Returns the time_t write time of the current file pointed to by the internal iterator. +fileCreation +~~~~~~~~~~~~ +Returns the time_t creation time of the current file +pointed to by the internal iterator. + isFile ~~~~~~ @@ -642,6 +647,11 @@ 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. +getCreation +~~~~~~~~~~~ + +Returns the file creation time, if available. + isFile ~~~~~~ diff --git a/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino b/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino index ce85de0340..b76f5f320a 100644 --- a/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino +++ b/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino @@ -53,9 +53,12 @@ void listDir(const char * dirname) { Serial.print(root.fileName()); Serial.print(" SIZE: "); Serial.print(file.size()); - time_t t = file.getLastWrite(); - struct tm * tmstruct = localtime(&t); + time_t cr = file.getCreation(); + time_t lw = file.getLastWrite(); file.close(); + struct tm * tmstruct = localtime(&cr); + Serial.printf(" CREATION: %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); + tmstruct = localtime(&lw); 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); } } @@ -90,6 +93,7 @@ void writeFile(const char * path, const char * message) { } else { Serial.println("Write failed"); } + delay(2000); // Make sure the CREATE and LASTWRITE times are different file.close(); } diff --git a/libraries/LittleFS/src/LittleFS.cpp b/libraries/LittleFS/src/LittleFS.cpp index 518ef663de..b85075112e 100644 --- a/libraries/LittleFS/src/LittleFS.cpp +++ b/libraries/LittleFS/src/LittleFS.cpp @@ -52,7 +52,7 @@ FileImplPtr LittleFSImpl::open(const char* path, OpenMode openMode, AccessMode a int flags = _getFlags(openMode, accessMode); auto fd = std::make_shared(); - if ((openMode && OM_CREATE) && strchr(path, '/')) { + if ((openMode & OM_CREATE) && strchr(path, '/')) { // For file creation, silently make subdirs as needed. If any fail, // it will be caught by the real file open later on char *pathStr = strdup(path); @@ -68,13 +68,26 @@ FileImplPtr LittleFSImpl::open(const char* path, OpenMode openMode, AccessMode a } free(pathStr); } + + time_t creation = 0; + if (timeCallback && (openMode & OM_CREATE)) { + // O_CREATE means we *may* make the file, but not if it already exists. + // See if it exists, and only if not update the creation time + int rc = lfs_file_open(&_lfs, fd.get(), path, LFS_O_RDONLY); + if (rc == 0) { + lfs_file_close(&_lfs, fd.get()); // It exists, don't update create time + } else { + creation = timeCallback(); // File didn't exist or otherwise, so we're going to create this time + } + } + int rc = lfs_file_open(&_lfs, fd.get(), path, flags); if (rc == LFS_ERR_ISDIR) { // To support the SD.openNextFile, a null FD indicates to the LittleFSFile this is just // a directory whose name we are carrying around but which cannot be read or written - return std::make_shared(this, path, nullptr, flags); + return std::make_shared(this, path, nullptr, flags, creation); } else if (rc == 0) { - return std::make_shared(this, path, fd, flags); + return std::make_shared(this, path, fd, flags, creation); } else { DEBUGV("LittleFSDirImpl::openFile: rc=%d fd=%p path=`%s` openMode=%d accessMode=%d err=%d\n", rc, fd.get(), path, openMode, accessMode, rc); diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index e332fd9acb..556558ed7d 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -323,7 +323,7 @@ class LittleFSImpl : public FSImpl class LittleFSFileImpl : public FileImpl { public: - LittleFSFileImpl(LittleFSImpl* fs, const char *name, std::shared_ptr fd, int flags) : _fs(fs), _fd(fd), _opened(true), _flags(flags) { + LittleFSFileImpl(LittleFSImpl* fs, const char *name, std::shared_ptr fd, int flags, time_t creation) : _fs(fs), _fd(fd), _opened(true), _flags(flags), _creation(creation) { _name = std::shared_ptr(new char[strlen(name) + 1], std::default_delete()); strcpy(_name.get(), name); } @@ -420,12 +420,19 @@ class LittleFSFileImpl : public FileImpl _opened = false; DEBUGV("lfs_file_close: fd=%p\n", _getFD()); if (timeCallback && (_flags & LFS_O_WRONLY)) { + // If the file opened with O_CREAT, write the creation time attribute + if (_creation) { + int rc = lfs_setattr(_fs->getFS(), _name.get(), 'c', (const void *)&_creation, sizeof(_creation)); + if (rc < 0) { + DEBUGV("Unable to set creation time on '%s' to %d\n", _name.get(), _creation); + } + } // 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); - } + DEBUGV("Unable to set last write time on '%s' to %d\n", _name.get(), now); + } } } } @@ -440,6 +447,16 @@ class LittleFSFileImpl : public FileImpl return ftime; } + time_t getCreation() override { + time_t ftime = 0; + if (_opened && _fd) { + int rc = lfs_getattr(_fs->getFS(), _name.get(), 'c', (void *)&ftime, sizeof(ftime)); + if (rc != sizeof(ftime)) + ftime = 0; // Error, so clear read value + } + return ftime; + } + const char* name() const override { if (!_opened) { return nullptr; @@ -484,6 +501,7 @@ class LittleFSFileImpl : public FileImpl std::shared_ptr _name; bool _opened; int _flags; + time_t _creation; }; class LittleFSDirImpl : public DirImpl @@ -537,23 +555,11 @@ class LittleFSDirImpl : public DirImpl } 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; + return (time_t)_getAttr4('t'); + } + + time_t fileCreation() override { + return (time_t)_getAttr4('c'); } @@ -592,6 +598,26 @@ class LittleFSDirImpl : public DirImpl return _dir.get(); } + uint32_t _getAttr4(char attr) { + 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, attr, (void *)&ftime, sizeof(ftime)); + if (rc != sizeof(ftime)) + ftime = 0; // Error, so clear read value + free(tmpName); + return ftime; + } + String _pattern; LittleFSImpl *_fs; std::shared_ptr _dir; diff --git a/libraries/SDFS/src/SDFS.h b/libraries/SDFS/src/SDFS.h index ee772cd5cb..43c743a5cd 100644 --- a/libraries/SDFS/src/SDFS.h +++ b/libraries/SDFS/src/SDFS.h @@ -363,6 +363,18 @@ class SDFSFileImpl : public FileImpl return ftime; } + time_t getCreation() override { + time_t ftime = 0; + if (_opened && _fd) { + sdfat::dir_t tmp; + if (_fd.get()->dirEntry(&tmp)) { + ftime = SDFSImpl::FatToTimeT(tmp.creationDate, tmp.creationTime); + } + } + return ftime; + } + + protected: SDFSImpl* _fs; @@ -426,6 +438,14 @@ class SDFSDirImpl : public DirImpl return _time; } + time_t fileCreation() override + { + if (!_valid) { + return 0; + } + + return _creation; + } bool isFile() const override { @@ -451,8 +471,10 @@ class SDFSDirImpl : public DirImpl sdfat::dir_t tmp; if (file.dirEntry(&tmp)) { _time = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime); + _creation = SDFSImpl::FatToTimeT(tmp.creationDate, tmp.creationTime); } else { _time = 0; + _creation = 0; } file.getName(_lfn, sizeof(_lfn)); file.close(); @@ -477,6 +499,7 @@ class SDFSDirImpl : public DirImpl bool _valid; char _lfn[64]; time_t _time; + time_t _creation; 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 eb6dca7312..2f338a5bb2 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-69bd9e6", + "version": "2.5.0-4-fe5bb56", "name": "mklittlefs" }, { @@ -302,54 +302,61 @@ ] }, { - "version": "2.5.0-4-69bd9e6", + "version": "2.5.0-4-fe5bb56", "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-69bd9e6.tar.gz", - "archiveFileName": "aarch64-linux-gnu-mklittlefs-69bd9e6.tar.gz", - "checksum": "SHA-256:74d938f15a3fb8ac20aeb0f938ace2c6759f622451419c09446aa79866302e18", - "size": "44342" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/aarch64-linux-gnu.mklittlefs-fe5bb56.1578453304.tar.gz", + "archiveFileName": "aarch64-linux-gnu.mklittlefs-fe5bb56.1578453304.tar.gz", + "checksum": "SHA-256:ac50bae3b580053ba98a181ae3700fafd2b2f8a37ed9c16bc22a5d7c1659388e", + "size": "44433" }, { "host": "arm-linux-gnueabihf", - "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" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/arm-linux-gnueabihf.mklittlefs-fe5bb56.1578453304.tar.gz", + "archiveFileName": "arm-linux-gnueabihf.mklittlefs-fe5bb56.1578453304.tar.gz", + "checksum": "SHA-256:092555612e7e229fbe622df75db70560896c3aea8d0ac2e5fa16d92dc16857cf", + "size": "36917" + }, + { + "host": "i686-pc-linux-gnu", + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-linux-gnu.mklittlefs-fe5bb56.1578453304.tar.gz", + "archiveFileName": "i686-linux-gnu.mklittlefs-fe5bb56.1578453304.tar.gz", + "checksum": "SHA-256:060e2525223269d2a5d01055542ff36837f0b19598d78cb02d58563aeda441cd", + "size": "47833" }, { "host": "i686-mingw32", - "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" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-w64-mingw32.mklittlefs-fe5bb56.1578453304.zip", + "archiveFileName": "i686-w64-mingw32.mklittlefs-fe5bb56.1578453304.zip", + "checksum": "SHA-256:2e570bed4ec59a9ecc73290e16c31ed53ee15e3abd8c82cb038b2148596d112e", + "size": "332329" }, { "host": "x86_64-apple-darwin", - "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" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-apple-darwin14.mklittlefs-fe5bb56.1578453304.tar.gz", + "archiveFileName": "x86_64-apple-darwin14.mklittlefs-fe5bb56.1578453304.tar.gz", + "checksum": "SHA-256:fcb57ff58eceac79e988cc26a9e009a11ebda68d4ae97e44fed8e7c6d98a35b5", + "size": "362389" }, { "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-69bd9e6.tar.gz", - "archiveFileName": "x86_64-linux-gnu-mklittlefs-69bd9e6.tar.gz", - "checksum": "SHA-256:e4ce7cc80eceab6a9a2e620f2badfb1ef09ee88f7af529f290c65b4b72f19358", - "size": "46518" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-linux-gnu.mklittlefs-fe5bb56.1578453304.tar.gz", + "archiveFileName": "x86_64-linux-gnu.mklittlefs-fe5bb56.1578453304.tar.gz", + "checksum": "SHA-256:5ef79d76e8e76f8287dc70d10c33f020d4cf5320354571adf666665eeef2e2de", + "size": "46580" }, { "host": "x86_64-mingw32", - "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" + "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-w64-mingw32.mklittlefs-fe5bb56.1578453304.zip", + "archiveFileName": "x86_64-w64-mingw32.mklittlefs-fe5bb56.1578453304.zip", + "checksum": "SHA-256:a460f410a22a59e23d7f862b8d08d6b7dfbc93aa558f8161a3d640d4df2ab86f", + "size": "344792" } ] } ] } ] -} \ No newline at end of file +}