Skip to content

Commit 9014862

Browse files
authored
Tar support and Compression to .tar.gz (#81)
Tested on ESP32, RP2040 and ESP8266
1 parent 02d46e0 commit 9014862

28 files changed

+4769
-870
lines changed

Doxyfile

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,29 @@
11
EXTRACT_STATIC = YES
22
EXTRACT_ALL = YES
33

4-
54
INPUT = "src"
65
INPUT_ENCODING = UTF-8
76
FILE_PATTERNS = *.c \
87
*.cc \
98
*.cxx \
109
*.cpp \
1110
*.c++ \
12-
*.java \
13-
*.ii \
14-
*.ixx \
15-
*.ipp \
16-
*.i++ \
17-
*.inl \
18-
*.idl \
19-
*.ddl \
20-
*.odl \
2111
*.h \
2212
*.hh \
2313
*.hxx \
2414
*.hpp \
25-
*.h++ \
26-
*.cs \
27-
*.d \
28-
*.php \
29-
*.php4 \
30-
*.php5 \
31-
*.phtml \
32-
*.inc \
33-
*.m \
34-
*.markdown \
35-
*.md \
36-
*.mm \
37-
*.dox \
38-
*.py \
39-
*.f90 \
40-
*.f \
41-
*.for \
42-
*.tcl \
43-
*.vhd \
44-
*.vhdl \
45-
*.ucf \
46-
*.qsf \
47-
*.as \
48-
*.js
15+
*.h++
4916
RECURSIVE = YES
50-
EXCLUDE =
17+
EXCLUDE =
5118
EXCLUDE_SYMLINKS = NO
52-
EXCLUDE_PATTERNS =
53-
EXCLUDE_SYMBOLS =
54-
EXAMPLE_PATH =
19+
EXCLUDE_PATTERNS =
20+
EXCLUDE_SYMBOLS =
21+
EXAMPLE_PATH =
5522
EXAMPLE_PATTERNS = *
5623
EXAMPLE_RECURSIVE = NO
57-
IMAGE_PATH =
58-
INPUT_FILTER =
59-
FILTER_PATTERNS =
24+
IMAGE_PATH =
25+
INPUT_FILTER =
26+
FILTER_PATTERNS =
6027
FILTER_SOURCE_FILES = NO
61-
FILTER_SOURCE_PATTERNS =
62-
USE_MDFILE_AS_MAINPAGE =
28+
FILTER_SOURCE_PATTERNS =
29+
USE_MDFILE_AS_MAINPAGE =

README.md

Lines changed: 128 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,29 @@
1616
- uzlib https://github.com/pfalcon/uzlib
1717
- TinyUntar https://github.com/dsoprea/TinyUntar
1818

19-
ESP32-targz enables the channeling of gz :arrow_right: tar :arrow_right: filesystem data ~~without using an intermediate file~~ (bug: see [#4](https://github.com/tobozo/ESP32-targz/issues/4)).
19+
ESP32-targz enables the channeling of gz :arrow_left::arrow_right: tar :arrow_left::arrow_right: filesystem data in both directions.
2020

21-
In order to reach this goal, TinyUntar was heavily modified to allow data streaming, uzlib is also customized.
21+
Parental advisory: this project was made under the influence of hyperfocus and its code may contain comments that are unfit for children.
2222

2323

24+
Scope
25+
-----
26+
27+
- Compressing to `.tar.gz`
28+
- Decompressing from `tar.gz`
29+
- Compressing to `gz`
30+
- Decompressing from `gz`
31+
- Packing files/folders to `tar`
32+
- Unpacking `tar`
33+
- Supports any fs::FS filesystem (SD, SD_MMC, FFat, LittleFS) and Stream (HTTP, HTTPS, UDP, CAN, Ethernet)
34+
- This is experimental, expect bugs!
35+
- Contributions and feedback are more than welcome :-)
36+
37+
2438
Tradeoffs
2539
---------
2640

27-
When the output is the filesystem (e.g. NOT when streaming to TAR), gzip can work without the dictionary.
41+
When decompressing to the filesystem (e.g. NOT when streaming to TAR), gzip can work without the dictionary.
2842
Disabling the dictionary can cause huge slowdowns but saves ~36KB of ram.
2943

3044
TinyUntar requires 512bytes only so its memory footprint is negligible.
@@ -33,22 +47,10 @@ TinyUntar requires 512bytes only so its memory footprint is negligible.
3347
Limitations
3448
-----------
3549

36-
ESP32-TarGz can only have one **output** filesystem (see *Support Matrix*), and it must be set at compilation time (see *Usage*).
50+
- ESP32-targz decompression can only have one **output** filesystem (see *Support Matrix*), and it must be set at compilation time (see *Usage*).
3751
This limitation does not apply to the **input** filesystem/stream.
3852

3953

40-
Scope
41-
-----
42-
43-
- Compressing to `gz` (deflate/lz77)
44-
- Decompressing `gz`
45-
- Expanding `tar`
46-
- Decompressing and expanding `tar.gz`
47-
- Supports any fs::FS filesystem (SD, SD_MMC, FFat, LittleFS) and streams (HTTP, HTTPS, UDP, CAN, Ethernet)
48-
- This is experimental, expect bugs!
49-
- Contributions and feedback are more than welcome :-)
50-
51-
5254

5355
Support Matrix
5456
--------------
@@ -68,7 +70,7 @@ Usage
6870
-----
6971

7072

71-
:warning: Important note: setting the `#define` **before** including `<ESP32-targz.h>` is recommended to prevent the library from defaulting to SPIFFS.
73+
:warning: Optional: setting the `#define` **before** including `<ESP32-targz.h>` will alias a default flash filesystem to `tarGzFS`.
7274

7375

7476
```C
@@ -296,8 +298,24 @@ ESP32 Only: Direct Update (no intermediate file) from `.tar.gz.` stream
296298
```
297299
298300
299-
300-
301+
LZPacker::compress() signatures:
302+
-------------------------------
303+
```cpp
304+
// buffer to stream (best compression)
305+
size_t compress( uint8_t* srcBuf, size_t srcBufLen, Stream* dstStream );
306+
// buffer to buffer (best compression)
307+
size_t compress( uint8_t* srcBuf, size_t srcBufLen, uint8_t** dstBufPtr );
308+
// stream to buffer
309+
size_t compress( Stream* srcStream, size_t srcLen, uint8_t** dstBufPtr );
310+
// stream to stream
311+
size_t compress( Stream* srcStream, size_t srcLen, Stream* dstStream );
312+
// stream to file
313+
size_t compress( Stream* srcStream, size_t srcLen, fs::FS*dstFS, const char* dstFilename );
314+
// file to file
315+
size_t compress( fs::FS *srcFS, const char* srcFilename, fs::FS*dstFS, const char* dstFilename );
316+
// file to stream
317+
size_t compress( fs::FS *srcFS, const char* srcFilename, Stream* dstStream );
318+
```
301319

302320
Compress to `.gz` (buffer to stream)
303321
-------------------------------
@@ -344,11 +362,94 @@ Compress to `.gz` (stream to stream)
344362
```
345363

346364

365+
TarPacker::pack_files() signatures:
366+
-------------------------------
367+
```cpp
368+
int pack_files(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, Stream* dstStream, const char* tar_prefix=nullptr);
369+
int pack_files(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, fs::FS *dstFS, const char*tar_output_file_path, const char* tar_prefix=nullptr);
370+
```
371+
347372
373+
Pack to `.tar` (entities to File)
374+
-------------------------------
375+
```C
376+
std::vector<TAR::dir_entity_t> dirEntities;
377+
TarPacker::collectDirEntities(&dirEntities, &LittleFS, "/folder/to/pack");
378+
auto packedSize = TarPacker::pack_files(&LittleFS, dirEntities, &LittleFS, "/my.archive.tar");
379+
```
380+
381+
Pack to `.tar` (entities to Stream)
382+
-------------------------------
383+
```C
384+
std::vector<TAR::dir_entity_t> dirEntities;
385+
TarPacker::collectDirEntities(&dirEntities, &LittleFS, "/folder/to/pack");
386+
File tarOutfile = LittleFS.open("/my.archive.tar", "w");
387+
size_t packedSize = TarPacker::pack_files(&LittleFS, dirEntities, &tarOutfile);
388+
tarOutfile.close();
389+
```
390+
391+
392+
TarGzPacker::compress() signatures:
393+
-------------------------------
394+
395+
```cpp
396+
int compress(fs::FS *srcFS, const char* srcDir, Stream* dstStream, const char* tar_prefix=nullptr);
397+
int compress(fs::FS *srcFS, const char* srcDir, fs::FS *dstFS, const char* tgz_name, const char* tar_prefix=nullptr);
398+
399+
int compress(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, Stream* dstStream, const char* tar_prefix=nullptr);
400+
int compress(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, fs::FS *dstFS, const char* tgz_name, const char* tar_prefix=nullptr);
401+
```
402+
403+
404+
Pack & compress to `.tar.gz` file/stream (no filtering on source files/folders list, recursion applies)
405+
-------------------------------
406+
```C
407+
File TarGzOutFile = LittleFS.open("/my.archive.tar.gz", "w");
408+
size_t compressedSize = TarGzPacker::compress(&LittleFS/*source*/, "/folder/to/compress", &TarGzOutFile);
409+
TarGzOutFile.close();
410+
```
411+
412+
Pack & compress to `.tar.gz` file/stream
413+
-------------------------------
414+
415+
```C
416+
std::vector<TAR::dir_entity_t> dirEntities;
417+
TarPacker::collectDirEntities(&dirEntities, &LittleFS/*source*/, "/folder/to/compress");
418+
// eventually filter content from dirEntities
419+
File TarGzOutFile = LittleFS.open("/my.archive.tar.gz", "w");
420+
size_t compressedSize = TarGzPacker::compress(&LittleFS/*source*/, dirEntities, &TarGzOutFile);
421+
TarGzOutFile.close();
422+
```
423+
424+
Pack & compress to `.tar.gz` file (no filtering on source files/folders list, recursion applies)
425+
-------------------------------
426+
```C
427+
File TarGzOutFile = LittleFS.open("/my.archive.tar.gz", "w");
428+
size_t compressedSize = TarGzPacker::compress(&LittleFS/*source*/, "/folder/to/compress", &LittleFS/*destination*/, "/my.archive.tar.gz");
429+
TarGzOutFile.close();
430+
```
431+
348432

433+
Pack & compress to `.tar.gz` file
434+
-------------------------------
435+
436+
```C
437+
std::vector<TAR::dir_entity_t> dirEntities;
438+
TarPacker::collectDirEntities(&dirEntities, &LittleFS/*source*/, "/folder/to/compress");
439+
// eventually filter content from dirEntities
440+
File TarGzOutFile = LittleFS.open("/my.archive.tar.gz", "w");
441+
size_t compressedSize = TarGzPacker::compress(&LittleFS/*source*/, dirEntities, &LittleFS/*destination*/, "/my.archive.tar.gz");
442+
TarGzOutFile.close();
443+
```
444+
349445
350446
351-
Callbacks
447+
448+
449+
450+
451+
452+
TarGzUnpacker/GzUnpacker/TarUnpacker Callbacks
352453
---------
353454
354455
```C
@@ -402,7 +503,7 @@ Callbacks
402503
403504
```
404505

405-
Return Codes
506+
TarGzUnpacker/GzUnpacker/TarUnpacker Return Codes
406507
------------
407508

408509
`*Unpacker->tarGzGetError()` returns a value when a problem occured:
@@ -470,9 +571,9 @@ Test Suite
470571
Known bugs
471572
----------
472573

473-
- tarGzStreamExpander hates SPIFFS
474-
- tarGzExpander/tarExpander: some formats aren't supported with SPIFFS (e.g contains symlinks or long filename/path)
475-
- tarGzExpander without intermediate file hates situations with low heap
574+
- SPIFFS is deprecated, migrate to LittleFS!
575+
- tarGzExpander/tarExpander: symlinks or long filename/path not supported, path limit is 100 chars
576+
- tarGzExpander without intermediate file uses a lot of heap
476577
- tarGzExpander/gzExpander on ESP8266 : while the provided examples will work, the 32Kb dynamic allocation for gzip dictionary is unlikely to work in real world scenarios (e.g. with a webserver) and would probably require static allocation
477578

478579

@@ -482,13 +583,14 @@ Debugging:
482583

483584
- ESP32: use all of the "Debug level" values from the boards menu
484585
- ESP8266: Warning/Error when "Debug Port:Serial" is used, and Debug/Verbose when "Debug Level:Core" is selected from the boards menu
586+
- RP2040: only "Debug port: Serial" and "Debug Level: Core" enable logging
485587

486588

487589
Resources
488590
-----------
489-
- [LittleFS for ESP32](https://github.com/lorol/LITTLEFS)
490591
- [ESP8266 Sketch Data Upload tool for LittleFS](https://github.com/earlephilhower/arduino-esp8266littlefs-plugin)
491592
- [ESP32 Sketch Data Upload tool for FFat/LittleFS/SPIFFS](https://github.com/lorol/arduino-esp32fs-plugin/releases)
593+
- [Pico LittlsFS Data Upload tool](https://github.com/earlephilhower/arduino-pico-littlefs-plugin)
492594

493595
![image](https://user-images.githubusercontent.com/1893754/99714053-635de380-2aa5-11eb-98e3-631a94836742.png)
494596

@@ -501,7 +603,6 @@ Alternate links
501603

502604
Credits:
503605
--------
504-
505606
- [pfalcon](https://github.com/pfalcon/uzlib) (uzlib maintainer)
506607
- [dsoprea](https://github.com/dsoprea/TinyUntar) (TinyUntar maintainer)
507608
- [lorol](https://github.com/lorol) (LittleFS-ESP32 + fs plugin)
@@ -511,5 +612,6 @@ Credits:
511612
- [scubachristopher](https://github.com/scubachristopher) (contribution and support)
512613
- [infrafast](https://github.com/infrafast) (feedback fueler)
513614
- [vortigont](https://github.com/vortigont/) (inspiration and support)
615+
- [hitecSmartHome](https://github.com/hitecSmartHome) (feedback fueler)
514616

515617

examples/ESP32/WebServer_mod_gzip/GzipStaticHandler.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,9 @@ bool GzCacheMiddleware::run(WebServer &server, Middleware::Callback next) {
281281
return next();
282282

283283
assert(gzHandler);
284-
return gzHandler->handle( server, server.method(), server.uri() );
284+
if( gzHandler->handle( server, server.method(), server.uri() ) )
285+
return true;
286+
return next();
285287
}
286288

287289

@@ -445,5 +447,3 @@ GzStaticRequestHandler &GzStaticRequestHandler::setFilter(WebServer::FilterFunct
445447
_filter = filter;
446448
return *this;
447449
}
448-
449-

examples/ESP32/WebServer_mod_gzip/WebServer_mod_gzip.ino

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,49 @@ void setup()
203203
// mod_gzip.enableCache(); // cache gz files
204204
mod_gzip.disableCache(); // ignore existing gz files, compress on the fly
205205

206+
207+
server.on("/json", []() { // send gz compressed JSON
208+
// building HTTP response without "Content-Length" header isn't 100% standard, so we have to do this
209+
int responseCode = 200;
210+
const char* myJsonData = "{\"ceci\":\"cela\",\"couci\":\"couça\",\"patati\":\"patata\"}";
211+
server.sendHeader(String(F("Content-Type")), String(F("application/json")), true);
212+
server.sendHeader(String(F("Content-Encoding")), String(F("gzip")));
213+
server.sendHeader(String(F("Connection")), String(F("close")));
214+
String HTTPResponse = String(F("HTTP/1.1"))+' '+String(responseCode)+' '+server.responseCodeToString(responseCode)+"\r\n";
215+
size_t headersCount = server.responseHeaders();
216+
for(size_t i=0;i<headersCount;i++)
217+
HTTPResponse.concat(server.responseHeaderName(i) + F(": ") + server.responseHeader(i) + F("\r\n"));
218+
HTTPResponse.concat(F("\r\n"));
219+
// sent HTTP response
220+
server.client().write(HTTPResponse.c_str(), HTTPResponse.length());
221+
222+
// stream compressed json
223+
size_t compressed_size = LZPacker::compress( (uint8_t*)myJsonData, strlen(myJsonData), &server.client() );
224+
log_i("Sent %d compressed bytes", compressed_size);
225+
});
226+
227+
228+
server.on("/spiffs.tar.gz", []() { // compress all filesystem files/folders on the fly
229+
// building HTTP response without "Content-Length" header isn't 100% standard, so we have to do this
230+
int responseCode = 200;
231+
server.sendHeader(String(F("Content-Type")), String(F("application/tar+gzip")), true);
232+
server.sendHeader(String(F("Connection")), String(F("close")));
233+
String HTTPResponse = String(F("HTTP/1.1"))+' '+String(responseCode)+' '+server.responseCodeToString(responseCode)+"\r\n";
234+
size_t headersCount = server.responseHeaders();
235+
for(size_t i=0;i<headersCount;i++)
236+
HTTPResponse.concat(server.responseHeaderName(i) + F(": ") + server.responseHeader(i) + F("\r\n"));
237+
HTTPResponse.concat(F("\r\n"));
238+
// sent HTTP response
239+
server.client().write(HTTPResponse.c_str(), HTTPResponse.length());
240+
241+
// stream tar.gz data
242+
std::vector<TAR::dir_entity_t> dirEntities; // storage for scanned dir entities
243+
TarPacker::collectDirEntities(&dirEntities, &tarGzFS, "/"); // collect dir and files
244+
size_t compressed_size = TarGzPacker::compress(&tarGzFS, dirEntities, &server.client());
245+
log_i("Sent %d compressed bytes", compressed_size);
246+
});
247+
248+
206249
server.addMiddleware( &mod_gzip );
207250

208251
server.begin();

0 commit comments

Comments
 (0)