littlefs-do: unzip in memory and copy listed resources to SPI raw file

Add a new command `littlefs-do res load resource.zip` which loads
resources from a zip file to the SPI raw file.

Below an example `resource.zip` is loaded:

```sh
$ ./littlefs-do res load infinitime-resources-1.10.0.zip --verbose
Calling FS::Init()
running 'res'
running 'res load'
loading resource file: "infinitime-resources-1.10.0.zip"
zip: num of files in zip: 8
copy file teko.bin                  from zip to SPI path '/teko.bin'
copy file lv_font_dots_40.bin       from zip to SPI path '/lv_font_dots_40.bin'
copy file 7segments_40.bin          from zip to SPI path '/7segments_40.bin'
copy file bebas.bin                 from zip to SPI path '/bebas.bin'
copy file 7segments_115.bin         from zip to SPI path '/7segments_115.bin'
copy file matrix.bin                from zip to SPI path '/matrix.bin'
copy file infineat-1.bin            from zip to SPI path '/infineat-1.bin'
finished: zip file fully loaded into SPI memory: infinitime-resources-1.10.0.zip
```

Afterwards the files are listed in the SPI raw file:

```sh
$ ./littlefs-do ls
type: DIR
name: /
type: DIR name: .
type: DIR name: ..
type: REG size: 4928 name: 7segments\_115.bin
type: REG size: 760 name: 7segments\_40.bin
type: REG size: 4420 name: bebas.bin
type: REG size: 1430 name: infineat-1.bin
type: REG size: 1840 name: lv\_font\_dots\_40.bin
type: REG size: 115204 name: matrix.bin
type: REG size: 440 name: teko.bin
```

Fixes: https://github.com/InfiniTimeOrg/InfiniSim/issues/55
This commit is contained in:
Reinhold Gschweicher 2022-08-22 22:23:07 +02:00
parent 4dea63843e
commit 2306807a73
3 changed files with 184 additions and 0 deletions

View File

@ -336,3 +336,8 @@ target_link_libraries(littlefs-do PUBLIC littlefs)
target_link_libraries(littlefs-do PRIVATE SDL2::SDL2) target_link_libraries(littlefs-do PRIVATE SDL2::SDL2)
target_link_libraries(littlefs-do PRIVATE infinitime_fonts) target_link_libraries(littlefs-do PRIVATE infinitime_fonts)
add_subdirectory(external/miniz)
add_subdirectory(external/nlohmann_json)
target_link_libraries(littlefs-do PRIVATE miniz)
target_link_libraries(littlefs-do PRIVATE nlohmann_json::nlohmann_json)

View File

@ -133,6 +133,20 @@ Commands:
rm remove directory or file rm remove directory or file
cp copy files into or out of flash file cp copy files into or out of flash file
settings list settings from 'settings.h' settings list settings from 'settings.h'
res resource.zip handling
```
### Resource loading
To load resource zip files into the SPI raw file for the simulator to use the `res load` command can be used.
```sh
$ ./littlefs-do res --help
Usage: ./littlefs-do res <action> [options]
actions:
load res.zip load zip file into SPI memory
Options:
-h, --help show this help message for the selected command and exit
``` ```
## Licenses ## Licenses

View File

@ -13,6 +13,7 @@
#include <array> #include <array>
#include <iostream> #include <iostream>
#include <iomanip> // std::left, std::setw
#include <typeinfo> #include <typeinfo>
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
@ -23,6 +24,9 @@
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include "drivers/SpiNorFlash.h" #include "drivers/SpiNorFlash.h"
#include "nlohmann/json.hpp"
#include "miniz.h"
/********************* /*********************
* DEFINES * DEFINES
*********************/ *********************/
@ -104,6 +108,7 @@ void print_help_generic(const std::string &program_name)
std::cout << " rm remove directory or file" << std::endl; std::cout << " rm remove directory or file" << std::endl;
std::cout << " cp copy files into or out of flash file" << std::endl; std::cout << " cp copy files into or out of flash file" << std::endl;
std::cout << " settings list settings from 'settings.h'" << std::endl; std::cout << " settings list settings from 'settings.h'" << std::endl;
std::cout << " res resource.zip handling" << std::endl;
} }
void print_help_stat(const std::string &program_name) void print_help_stat(const std::string &program_name)
{ {
@ -156,6 +161,14 @@ void print_help_settings(const std::string &program_name)
std::cout << "Options:" << std::endl; std::cout << "Options:" << std::endl;
std::cout << " -h, --help show this help message for the selected command and exit" << std::endl; std::cout << " -h, --help show this help message for the selected command and exit" << std::endl;
} }
void print_help_res(const std::string &program_name)
{
std::cout << "Usage: " << program_name << " res <action> [options]" << std::endl;
std::cout << "actions:" << std::endl;
std::cout << " load res.zip load zip file into SPI memory" << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " -h, --help show this help message for the selected command and exit" << std::endl;
}
int command_stat(const std::string &program_name, const std::vector<std::string> &args, bool verbose) int command_stat(const std::string &program_name, const std::vector<std::string> &args, bool verbose)
{ {
@ -602,6 +615,156 @@ int command_settings(const std::string &program_name, const std::vector<std::str
return 0; return 0;
} }
void mkdir_path(const std::filesystem::path &path) {
if (!path.is_absolute()) {
// for absolute paths parent path converges at '/', then parent_path == path
mkdir_path(std::filesystem::path{"/"} / path);
return;
}
std::filesystem::path parent = path.parent_path();
if (path == parent) {
return;
}
lfs_info info;
int ret = fs.Stat(path.generic_string().c_str(), &info);
if (ret == 0) {
// directory exists, nothing to do
return;
}
// try to create parent dir first
mkdir_path(parent);
// then create current dir
ret = fs.DirCreate(path.generic_string().c_str());
if (ret < 0) {
std::cout << "mkdir_path: fs.DirCreate returned error code: " << ret << " " << lfs_error_to_string(ret) << std::endl;
assert(false);
return;
}
}
int command_res(const std::string &program_name, const std::vector<std::string> &args, bool verbose)
{
if (verbose) {
std::cout << "running 'res'" << std::endl;
}
for (const std::string &arg : args)
{
if (arg == "-h" || arg == "--help")
{
print_help_res(program_name);
return 0;
}
}
if (args.size() < 1) {
std::cout << "error: no action specified" << std::endl;
print_help_res(program_name);
return 1;
}
if (args.at(0) == "load") {
if (verbose) {
std::cout << "running 'res load'" << std::endl;
}
if (args.size() < 2) {
std::cout << "error: res load needs at least one path to a ressource bundle to load" << std::endl;
print_help_res(program_name);
return 1;
}
for (size_t i=1; i<args.size(); i++) {
const std::filesystem::path path = args.at(i);
if (verbose) {
std::cout << "loading resource file: " << path << std::endl;
}
if (path.extension() == ".zip") {
const std::string &zip_filename = args.at(i);
const size_t f_size = std::filesystem::file_size(path);
std::vector<uint8_t> buffer_compressed(f_size);
std::ifstream ifs(path, std::ios::binary);
ifs.read((char*)(buffer_compressed.data()), f_size);
mz_zip_archive zip_archive {};
int mz_status = mz_zip_reader_init_file(&zip_archive, zip_filename.c_str(), 0);
if (!mz_status) {
std::cout << "error: mz_zip_reader_init_file() failed!" << std::endl;
return 1;
}
mz_uint zip_num_files = mz_zip_reader_get_num_files(&zip_archive);
if (verbose) {
std::cout << "zip: num of files in zip: " << zip_num_files << std::endl;
}
size_t uncomp_size = 0;
void *p = nullptr;
// extract resources.json file to heap, create a string and parse it
p = mz_zip_reader_extract_file_to_heap(&zip_archive, "resources.json", &uncomp_size, 0);
if (!p)
{
std::cout << "mz_zip_reader_extract_file_to_heap() failed to extract resources.json file" << std::endl;
mz_zip_reader_end(&zip_archive);
return 1;
}
std::string_view json_data(static_cast<const char *>(p), uncomp_size);
nlohmann::json doc = nlohmann::json::parse(json_data);
mz_free(p); // free json data, already converted into json document
if (!doc.contains("resources")) {
std::cout << "resources.json is missing 'resources' entry" << std::endl;
mz_zip_reader_end(&zip_archive);
return 1;
}
// copy all listed resources to SPI raw file
for (const auto &res : doc["resources"]) {
const auto filename = res["filename"].get<std::string>();
const auto dest_path = res["path"].get<std::string>();
if (verbose) {
std::cout << "copy file " << std::left << std::setw(25) << filename
<< " from zip to SPI path '" << dest_path << "'" << std::endl;
}
// make sure destination directory exists before copy
const std::filesystem::path dest_dir = std::filesystem::path{dest_path}.parent_path();
mkdir_path(dest_dir);
// extract from zip to heap to then copy to SPI raw file
void *p = mz_zip_reader_extract_file_to_heap(&zip_archive, filename.c_str(), &uncomp_size, 0);
if (!p) {
std::cout << "mz_zip_reader_extract_file_to_heap() failed to extract file: " << filename << std::endl;
mz_zip_reader_end(&zip_archive);
return 1;
}
lfs_file_t file_p;
int ret = fs.FileOpen(&file_p, dest_path.c_str(), LFS_O_WRONLY | LFS_O_CREAT);
if (ret) {
std::cout << "fs.FileOpen returned error code: " << ret << " " << lfs_error_to_string(ret) << std::endl;
return ret;
}
ret = fs.FileWrite(&file_p, static_cast<uint8_t *>(p), uncomp_size);
if (ret < 0) {
std::cout << "fs.FileWrite returned error code: " << ret << " " << lfs_error_to_string(ret) << std::endl;
fs.FileClose(&file_p);
return ret;
}
// file copy complete, close destination file and free data
fs.FileClose(&file_p);
mz_free(p);
}
// all done, close archive
mz_zip_reader_end(&zip_archive);
if (verbose) {
std::cout << "finished: zip file fully loaded into SPI memory: " << zip_filename << std::endl;
}
} else {
std::cout << "error: resource has unknown extension: " << args.at(i) << std::endl;
print_help_res(program_name);
return 1;
}
}
} else {
std::cout << "error: unknown res action '" << args.at(0) << "'" << std::endl;
print_help_res(program_name);
return 1;
}
return 0;
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
// parse arguments // parse arguments
@ -649,6 +812,8 @@ int main(int argc, char **argv)
return command_cp(argv[0], args, verbose); return command_cp(argv[0], args, verbose);
} else if (command == "settings") { } else if (command == "settings") {
return command_settings(argv[0], args, verbose); return command_settings(argv[0], args, verbose);
} else if (command == "res") {
return command_res(argv[0], args, verbose);
} else } else
{ {
std::cout << "unknown argument '" << command << "'" << std::endl; std::cout << "unknown argument '" << command << "'" << std::endl;