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:
parent
4dea63843e
commit
2306807a73
3 changed files with 184 additions and 0 deletions
|
@ -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 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)
|
||||
|
|
14
README.md
14
README.md
|
@ -133,6 +133,20 @@ Commands:
|
|||
rm remove directory or file
|
||||
cp copy files into or out of flash file
|
||||
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
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <iomanip> // std::left, std::setw
|
||||
#include <typeinfo>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
@ -23,6 +24,9 @@
|
|||
#include "components/settings/Settings.h"
|
||||
#include "drivers/SpiNorFlash.h"
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "miniz.h"
|
||||
|
||||
/*********************
|
||||
* 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 << " cp copy files into or out of flash file" << 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)
|
||||
{
|
||||
|
@ -156,6 +161,14 @@ void print_help_settings(const std::string &program_name)
|
|||
std::cout << "Options:" << 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)
|
||||
{
|
||||
|
@ -602,6 +615,156 @@ int command_settings(const std::string &program_name, const std::vector<std::str
|
|||
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)
|
||||
{
|
||||
// parse arguments
|
||||
|
@ -649,6 +812,8 @@ int main(int argc, char **argv)
|
|||
return command_cp(argv[0], args, verbose);
|
||||
} else if (command == "settings") {
|
||||
return command_settings(argv[0], args, verbose);
|
||||
} else if (command == "res") {
|
||||
return command_res(argv[0], args, verbose);
|
||||
} else
|
||||
{
|
||||
std::cout << "unknown argument '" << command << "'" << std::endl;
|
||||
|
|
Loading…
Reference in a new issue