mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-21 10:26:06 +03:00
Add time to filesystem API (#6544)
* Add time to filesystem API Support the ESP32 File::getLastWrite() call and setting the time on all filesystems automatically (assuming the system clock has been set properly and time(NULL) returns the proper time!). Adds Dir::fileTime() to get the time of a file being listed, similar to Dir::fileName() and Dir::fileSize(). Adds ::setTimeCallback(time_t (*cb)()) to File, Dir, and FS, allowing users to override the default timestamp on a per-file, directory, or filesystem basis. By default, a simple callback returning time(nullptr) is implemented. LittleFS uses the 't' attribute and should be backwards compatible. SD/SDFS work and include wrappers for obsolete SdFat timestamp callbacks using the MSDOS time. This PR does not update SPIFFS, due to compatability concerns and a possible massive rewrite which would make it possible to determine if an old-style ot metadata enabled FS is present at mount time. Includes an updated SD/listfiles and LittleFS_time example. Replaces #6315 * Add links to new mklittlefs w/timestamp support Include the update mklittlefs which generated 't' metadata on imported files. ../tools/sdk/lwip2/include/netif/lowpan6_opts.h * Add explicit note about timestamp being local time * Address review concerns Clean up some awkward object instantiations. Remove the _enableTime flag/setter from SPIFFS. Clean up the FSConfig constructors using C++ style init lists.
This commit is contained in:
parent
b4c28e74d6
commit
72dd589599
@ -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) {
|
||||
|
@ -110,12 +110,16 @@ public:
|
||||
|
||||
String readString() override;
|
||||
|
||||
time_t getLastWrite();
|
||||
void setTimeCallback(time_t (*cb)(void));
|
||||
|
||||
protected:
|
||||
FileImplPtr _p;
|
||||
|
||||
// Arduino SD class emulation
|
||||
std::shared_ptr<Dir> _fakeDir;
|
||||
FS *_baseFS;
|
||||
time_t (*timeCallback)(void) = nullptr;
|
||||
};
|
||||
|
||||
class Dir {
|
||||
@ -126,15 +130,21 @@ public:
|
||||
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
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
|
||||
|
@ -41,6 +41,19 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
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
|
||||
|
@ -162,7 +162,7 @@ public:
|
||||
|
||||
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<const SPIFFSConfig *>(&cfg);
|
||||
|
@ -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,41 +390,6 @@ rename
|
||||
Renames file from ``pathFrom`` to ``pathTo``. Paths must be absolute.
|
||||
Returns *true* if file was renamed successfully.
|
||||
|
||||
info
|
||||
~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
FSInfo fs_info;
|
||||
SPIFFS.info(fs_info);
|
||||
or LittleFS.info(fs_info);
|
||||
|
||||
Fills `FSInfo structure <#filesystem-information-structure>`__ with
|
||||
information about the file system. Returns ``true`` is successful,
|
||||
``false`` otherwise.
|
||||
|
||||
Filesystem information structure
|
||||
--------------------------------
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
struct FSInfo {
|
||||
size_t totalBytes;
|
||||
size_t usedBytes;
|
||||
size_t blockSize;
|
||||
size_t pageSize;
|
||||
size_t maxOpenFiles;
|
||||
size_t maxPathLength;
|
||||
};
|
||||
|
||||
This is the structure which may be filled using FS::info method. -
|
||||
``totalBytes`` — total size of useful data on the file system -
|
||||
``usedBytes`` — number of bytes used by files - ``blockSize`` — filesystem
|
||||
block size - ``pageSize`` — filesystem logical page size - ``maxOpenFiles``
|
||||
— max number of files which may be open simultaneously -
|
||||
``maxPathLength`` — max file name length (including one byte for zero
|
||||
termination)
|
||||
|
||||
gc
|
||||
~~
|
||||
|
||||
@ -429,6 +415,81 @@ Only implemented in SPIFFS. Performs an in-depth check of the filesystem metada
|
||||
correct what is repairable. Not normally needed, and not guaranteed to actually fix
|
||||
anything should there be corruption.
|
||||
|
||||
info
|
||||
~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
FSInfo fs_info;
|
||||
SPIFFS.info(fs_info);
|
||||
or LittleFS.info(fs_info);
|
||||
|
||||
Fills `FSInfo structure <#filesystem-information-structure>`__ with
|
||||
information about the file system. Returns ``true`` if successful,
|
||||
``false`` otherwise.
|
||||
|
||||
Filesystem information structure
|
||||
--------------------------------
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
struct FSInfo {
|
||||
size_t totalBytes;
|
||||
size_t usedBytes;
|
||||
size_t blockSize;
|
||||
size_t pageSize;
|
||||
size_t maxOpenFiles;
|
||||
size_t maxPathLength;
|
||||
};
|
||||
|
||||
This is the structure which may be filled using FS::info method. -
|
||||
``totalBytes`` — total size of useful data on the file system -
|
||||
``usedBytes`` — number of bytes used by files - ``blockSize`` — filesystem
|
||||
block size - ``pageSize`` — filesystem logical page size - ``maxOpenFiles``
|
||||
— max number of files which may be open simultaneously -
|
||||
``maxPathLength`` — max file name length (including one byte for zero
|
||||
termination)
|
||||
|
||||
info64
|
||||
~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
FSInfo64 fsinfo;
|
||||
SD.info(fsinfo);
|
||||
or LittleFS(fsinfo);
|
||||
|
||||
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.
|
||||
|
||||
setTimeCallback(time_t (*cb)(void))
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
time_t myTimeCallback() {
|
||||
return 1455451200; // UNIX timestamp
|
||||
}
|
||||
void setup () {
|
||||
LittleFS.setTimeCallback(myTimeCallback);
|
||||
...
|
||||
// Any files will now be made with Pris' incept date
|
||||
}
|
||||
|
||||
|
||||
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.
|
||||
|
@ -0,0 +1,181 @@
|
||||
/* Example showing timestamp support in LittleFS */
|
||||
/* Released into the public domain. */
|
||||
/* Earle F. Philhower, III <earlephilhower@yahoo.com> */
|
||||
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
#include <time.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
#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() { }
|
||||
|
@ -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 @@ public:
|
||||
}
|
||||
|
||||
bool setConfig(const FSConfig &cfg) override {
|
||||
if ((cfg._type != LittleFSConfig::fsid::FSId) || _mounted) {
|
||||
if ((cfg._type != LittleFSConfig::FSId) || _mounted) {
|
||||
return false;
|
||||
}
|
||||
_cfg = *static_cast<const LittleFSConfig *>(&cfg);
|
||||
@ -422,8 +419,26 @@ public:
|
||||
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 {
|
||||
if (!_opened) {
|
||||
@ -520,6 +535,27 @@ public:
|
||||
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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -3,3 +3,5 @@
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
|
||||
SDClass SD;
|
||||
#endif
|
||||
|
||||
void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*) = nullptr;
|
||||
|
@ -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 @@ public:
|
||||
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 @@ private:
|
||||
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
|
||||
|
@ -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 @@ public:
|
||||
|
||||
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 @@ public:
|
||||
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 @@ protected:
|
||||
return &_fs;
|
||||
}
|
||||
|
||||
|
||||
static uint8_t _getFlags(OpenMode openMode, AccessMode accessMode) {
|
||||
uint8_t mode = 0;
|
||||
if (openMode & OM_CREATE) {
|
||||
@ -350,6 +352,17 @@ public:
|
||||
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 @@ public:
|
||||
_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 @@ protected:
|
||||
std::shared_ptr<sdfat::File> _dir;
|
||||
bool _valid;
|
||||
char _lfn[64];
|
||||
time_t _time;
|
||||
std::shared_ptr<char> _dirPath;
|
||||
uint32_t _size;
|
||||
bool _isFile;
|
||||
|
@ -121,7 +121,7 @@
|
||||
},
|
||||
{
|
||||
"packager": "esp8266",
|
||||
"version": "2.5.0-4-b40a506",
|
||||
"version": "2.5.0-4-69bd9e6",
|
||||
"name": "mklittlefs"
|
||||
},
|
||||
{
|
||||
@ -302,57 +302,50 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user