mirror of
https://github.com/arendst/Tasmota.git
synced 2026-02-20 00:32:32 +01:00
Berry 'path.listdir(file.tapp#)' to list directory inside '.tapp' archives (#24367)
This commit is contained in:
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [15.2.0.3]
|
||||
### Added
|
||||
- Berry `path.listdir("file.tapp#")` to list directory inside '.tapp' archives
|
||||
|
||||
### Breaking Changed
|
||||
|
||||
|
||||
@@ -433,6 +433,76 @@ bool ZipArchive::parse(void) {
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Iterate over all files in a ZIP archive
|
||||
** This is a lightweight iterator that doesn't store entries in memory
|
||||
********************************************************************/
|
||||
bool ZipArchiveIterator(File &zipfile, ZipIteratorCallback callback, void *user_data) {
|
||||
ZipHeader header;
|
||||
zipfile.seek(0); // start of file
|
||||
int32_t offset = 0;
|
||||
const size_t zip_header_size = sizeof(header) - sizeof(header.padding);
|
||||
|
||||
while (1) {
|
||||
zipfile.seek(offset);
|
||||
int32_t bytes_read = zipfile.read(sizeof(header.padding) + (uint8_t*) &header, zip_header_size);
|
||||
if (bytes_read != (int32_t)zip_header_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check ZIP local file header signature (0x04034B50 split as 0x4B50 and 0x0403)
|
||||
if (header.signature1 != 0x4B50) {
|
||||
break; // Invalid signature
|
||||
}
|
||||
if (header.signature2 != 0x0403) {
|
||||
break; // End of local file headers (could be central directory)
|
||||
}
|
||||
|
||||
// Check for unsupported features
|
||||
if (header.gen_purpose_flags != 0x0000) {
|
||||
break; // Unsupported flags
|
||||
}
|
||||
if (header.compression != 0x0000) {
|
||||
break; // Compressed files not supported
|
||||
}
|
||||
|
||||
// Check file name size
|
||||
if (header.filename_size > 256 || header.filename_size == 0) {
|
||||
break; // Filename too long or empty
|
||||
}
|
||||
|
||||
// Read filename
|
||||
char fname[header.filename_size + 1];
|
||||
if (zipfile.read((uint8_t*)fname, header.filename_size) != header.filename_size) {
|
||||
break;
|
||||
}
|
||||
fname[header.filename_size] = '\0';
|
||||
|
||||
// Extract just the filename suffix after the last '#' (same logic as ZipArchive::parse)
|
||||
char *fname_suffix;
|
||||
char *saveptr;
|
||||
fname_suffix = strtok_r(fname, "#", &saveptr);
|
||||
char *res = fname_suffix;
|
||||
while (res) {
|
||||
res = strtok_r(nullptr, "#", &saveptr);
|
||||
if (res) { fname_suffix = res; }
|
||||
}
|
||||
|
||||
// Call the callback with the filename
|
||||
if (fname_suffix && fname_suffix[0] != '\0') {
|
||||
if (!callback(fname_suffix, user_data)) {
|
||||
break; // Callback requested to stop iteration
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next entry
|
||||
offset += zip_header_size + header.filename_size + header.extra_field_size + header.size_uncompressed;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Encapsulation of FS and File to piggyback on Arduino
|
||||
**
|
||||
|
||||
@@ -13,6 +13,25 @@
|
||||
class ZipReadFSImpl;
|
||||
typedef std::shared_ptr<FSImpl> ZipReadFSImplPtr;
|
||||
|
||||
/********************************************************************
|
||||
** Callback type for iterating over ZIP archive entries
|
||||
** Parameters:
|
||||
** filename: the filename (after '#' separator processing)
|
||||
** user_data: user-provided context pointer
|
||||
** Return: true to continue iteration, false to stop
|
||||
********************************************************************/
|
||||
typedef bool (*ZipIteratorCallback)(const char *filename, void *user_data);
|
||||
|
||||
/********************************************************************
|
||||
** Iterate over all files in a ZIP archive
|
||||
** Parameters:
|
||||
** zipfile: an open File object pointing to the ZIP archive
|
||||
** callback: function to call for each file entry
|
||||
** user_data: user-provided context pointer passed to callback
|
||||
** Returns: true if archive was parsed successfully
|
||||
********************************************************************/
|
||||
bool ZipArchiveIterator(File &zipfile, ZipIteratorCallback callback, void *user_data);
|
||||
|
||||
|
||||
class ZipReadFSImpl : public FSImpl {
|
||||
public:
|
||||
|
||||
@@ -100,6 +100,57 @@ BERRY_API void be_writebuffer(const char *buffer, size_t length)
|
||||
|
||||
// provides MPATH_ constants
|
||||
#include "be_port.h"
|
||||
|
||||
#ifdef USE_UFILESYS
|
||||
// Callback context for listing archive files
|
||||
struct ZipListContext {
|
||||
bvm *vm;
|
||||
};
|
||||
|
||||
// Callback function for ZipArchiveIterator
|
||||
static bool _zip_list_callback(const char *filename, void *user_data) {
|
||||
ZipListContext *ctx = (ZipListContext *)user_data;
|
||||
be_pushstring(ctx->vm, filename);
|
||||
be_data_push(ctx->vm, -2);
|
||||
be_pop(ctx->vm, 1);
|
||||
return true; // continue iteration
|
||||
}
|
||||
|
||||
// Helper function to list files in a ZIP archive
|
||||
// Returns true if the path ends with '#' and archive listing was attempted
|
||||
// The list object should already be on the Berry stack
|
||||
static bool _be_list_archive_files(bvm *vm, const char *path) {
|
||||
size_t path_len = strlen(path);
|
||||
if (path_len == 0 || path[path_len - 1] != '#') {
|
||||
return false; // not an archive path
|
||||
}
|
||||
|
||||
// Extract the archive path (without the trailing '#')
|
||||
char archive_path[path_len + 2];
|
||||
if (path[0] == '/') {
|
||||
strncpy(archive_path, path, path_len - 1);
|
||||
archive_path[path_len - 1] = '\0';
|
||||
} else {
|
||||
archive_path[0] = '/';
|
||||
strncpy(archive_path + 1, path, path_len - 1);
|
||||
archive_path[path_len] = '\0';
|
||||
}
|
||||
|
||||
// Open the archive file
|
||||
File zipfile = ffsp->open(archive_path, "r");
|
||||
if (!zipfile) {
|
||||
return true; // path ends with '#' but archive not found, return empty list
|
||||
}
|
||||
|
||||
// Use ZipArchiveIterator from Zip-readonly-FS module
|
||||
ZipListContext ctx = { vm };
|
||||
ZipArchiveIterator(zipfile, _zip_list_callback, &ctx);
|
||||
|
||||
zipfile.close();
|
||||
return true;
|
||||
}
|
||||
#endif // USE_UFILESYS
|
||||
|
||||
extern "C" {
|
||||
// this combined action is called from be_path_tasmota_lib.c
|
||||
// by using a single function, we save >200 bytes of flash
|
||||
@@ -147,8 +198,14 @@ extern "C" {
|
||||
}
|
||||
break;
|
||||
case MPATH_LISTDIR:
|
||||
be_newobject(vm, "list"); // add our list object and fall through
|
||||
be_newobject(vm, "list"); // add our list object
|
||||
returnit = 1;
|
||||
// Check if path ends with '#' for archive listing
|
||||
if (_be_list_archive_files(vm, path)) {
|
||||
// Archive listing handled, skip normal directory listing
|
||||
break;
|
||||
}
|
||||
// Fall through to normal directory listing
|
||||
case MPATH_ISDIR:
|
||||
case MPATH_MODIFIED: {
|
||||
//isdir needs to open the file, listdir does not
|
||||
|
||||
Reference in New Issue
Block a user