|
13 | 13 |
|
14 | 14 | #include <array> |
15 | 15 | #include <iostream> |
| 16 | +#include <iomanip> // std::left, std::setw |
16 | 17 | #include <typeinfo> |
17 | 18 | #include <algorithm> |
18 | 19 | #include <filesystem> |
|
23 | 24 | #include "components/settings/Settings.h" |
24 | 25 | #include "drivers/SpiNorFlash.h" |
25 | 26 |
|
| 27 | +#include "nlohmann/json.hpp" |
| 28 | +#include "miniz.h" |
| 29 | + |
26 | 30 | /********************* |
27 | 31 | * DEFINES |
28 | 32 | *********************/ |
@@ -104,6 +108,7 @@ void print_help_generic(const std::string &program_name) |
104 | 108 | std::cout << " rm remove directory or file" << std::endl; |
105 | 109 | std::cout << " cp copy files into or out of flash file" << std::endl; |
106 | 110 | std::cout << " settings list settings from 'settings.h'" << std::endl; |
| 111 | + std::cout << " res resource.zip handling" << std::endl; |
107 | 112 | } |
108 | 113 | void print_help_stat(const std::string &program_name) |
109 | 114 | { |
@@ -156,6 +161,14 @@ void print_help_settings(const std::string &program_name) |
156 | 161 | std::cout << "Options:" << std::endl; |
157 | 162 | std::cout << " -h, --help show this help message for the selected command and exit" << std::endl; |
158 | 163 | } |
| 164 | +void print_help_res(const std::string &program_name) |
| 165 | +{ |
| 166 | + std::cout << "Usage: " << program_name << " res <action> [options]" << std::endl; |
| 167 | + std::cout << "actions:" << std::endl; |
| 168 | + std::cout << " load res.zip load zip file into SPI memory" << std::endl; |
| 169 | + std::cout << "Options:" << std::endl; |
| 170 | + std::cout << " -h, --help show this help message for the selected command and exit" << std::endl; |
| 171 | +} |
159 | 172 |
|
160 | 173 | int command_stat(const std::string &program_name, const std::vector<std::string> &args, bool verbose) |
161 | 174 | { |
@@ -602,6 +615,156 @@ int command_settings(const std::string &program_name, const std::vector<std::str |
602 | 615 | return 0; |
603 | 616 | } |
604 | 617 |
|
| 618 | +void mkdir_path(const std::filesystem::path &path) { |
| 619 | + if (!path.is_absolute()) { |
| 620 | + // for absolute paths parent path converges at '/', then parent_path == path |
| 621 | + mkdir_path(std::filesystem::path{"/"} / path); |
| 622 | + return; |
| 623 | + } |
| 624 | + std::filesystem::path parent = path.parent_path(); |
| 625 | + if (path == parent) { |
| 626 | + return; |
| 627 | + } |
| 628 | + lfs_info info; |
| 629 | + int ret = fs.Stat(path.generic_string().c_str(), &info); |
| 630 | + if (ret == 0) { |
| 631 | + // directory exists, nothing to do |
| 632 | + return; |
| 633 | + } |
| 634 | + // try to create parent dir first |
| 635 | + mkdir_path(parent); |
| 636 | + // then create current dir |
| 637 | + ret = fs.DirCreate(path.generic_string().c_str()); |
| 638 | + if (ret < 0) { |
| 639 | + std::cout << "mkdir_path: fs.DirCreate returned error code: " << ret << " " << lfs_error_to_string(ret) << std::endl; |
| 640 | + assert(false); |
| 641 | + return; |
| 642 | + } |
| 643 | +} |
| 644 | +int command_res(const std::string &program_name, const std::vector<std::string> &args, bool verbose) |
| 645 | +{ |
| 646 | + if (verbose) { |
| 647 | + std::cout << "running 'res'" << std::endl; |
| 648 | + } |
| 649 | + for (const std::string &arg : args) |
| 650 | + { |
| 651 | + if (arg == "-h" || arg == "--help") |
| 652 | + { |
| 653 | + print_help_res(program_name); |
| 654 | + return 0; |
| 655 | + } |
| 656 | + } |
| 657 | + if (args.size() < 1) { |
| 658 | + std::cout << "error: no action specified" << std::endl; |
| 659 | + print_help_res(program_name); |
| 660 | + return 1; |
| 661 | + } |
| 662 | + if (args.at(0) == "load") { |
| 663 | + if (verbose) { |
| 664 | + std::cout << "running 'res load'" << std::endl; |
| 665 | + } |
| 666 | + if (args.size() < 2) { |
| 667 | + std::cout << "error: res load needs at least one path to a ressource bundle to load" << std::endl; |
| 668 | + print_help_res(program_name); |
| 669 | + return 1; |
| 670 | + } |
| 671 | + for (size_t i=1; i<args.size(); i++) { |
| 672 | + const std::filesystem::path path = args.at(i); |
| 673 | + if (verbose) { |
| 674 | + std::cout << "loading resource file: " << path << std::endl; |
| 675 | + } |
| 676 | + if (path.extension() == ".zip") { |
| 677 | + const std::string &zip_filename = args.at(i); |
| 678 | + const size_t f_size = std::filesystem::file_size(path); |
| 679 | + std::vector<uint8_t> buffer_compressed(f_size); |
| 680 | + std::ifstream ifs(path, std::ios::binary); |
| 681 | + ifs.read((char*)(buffer_compressed.data()), f_size); |
| 682 | + |
| 683 | + mz_zip_archive zip_archive {}; |
| 684 | + int mz_status = mz_zip_reader_init_file(&zip_archive, zip_filename.c_str(), 0); |
| 685 | + if (!mz_status) { |
| 686 | + std::cout << "error: mz_zip_reader_init_file() failed!" << std::endl; |
| 687 | + return 1; |
| 688 | + } |
| 689 | + |
| 690 | + mz_uint zip_num_files = mz_zip_reader_get_num_files(&zip_archive); |
| 691 | + if (verbose) { |
| 692 | + std::cout << "zip: num of files in zip: " << zip_num_files << std::endl; |
| 693 | + } |
| 694 | + |
| 695 | + size_t uncomp_size = 0; |
| 696 | + void *p = nullptr; |
| 697 | + // extract resources.json file to heap, create a string and parse it |
| 698 | + p = mz_zip_reader_extract_file_to_heap(&zip_archive, "resources.json", &uncomp_size, 0); |
| 699 | + if (!p) |
| 700 | + { |
| 701 | + std::cout << "mz_zip_reader_extract_file_to_heap() failed to extract resources.json file" << std::endl; |
| 702 | + mz_zip_reader_end(&zip_archive); |
| 703 | + return 1; |
| 704 | + } |
| 705 | + std::string_view json_data(static_cast<const char *>(p), uncomp_size); |
| 706 | + nlohmann::json doc = nlohmann::json::parse(json_data); |
| 707 | + mz_free(p); // free json data, already converted into json document |
| 708 | + if (!doc.contains("resources")) { |
| 709 | + std::cout << "resources.json is missing 'resources' entry" << std::endl; |
| 710 | + mz_zip_reader_end(&zip_archive); |
| 711 | + return 1; |
| 712 | + } |
| 713 | + // copy all listed resources to SPI raw file |
| 714 | + for (const auto &res : doc["resources"]) { |
| 715 | + const auto filename = res["filename"].get<std::string>(); |
| 716 | + const auto dest_path = res["path"].get<std::string>(); |
| 717 | + if (verbose) { |
| 718 | + std::cout << "copy file " << std::left << std::setw(25) << filename |
| 719 | + << " from zip to SPI path '" << dest_path << "'" << std::endl; |
| 720 | + } |
| 721 | + // make sure destination directory exists before copy |
| 722 | + const std::filesystem::path dest_dir = std::filesystem::path{dest_path}.parent_path(); |
| 723 | + mkdir_path(dest_dir); |
| 724 | + // extract from zip to heap to then copy to SPI raw file |
| 725 | + void *p = mz_zip_reader_extract_file_to_heap(&zip_archive, filename.c_str(), &uncomp_size, 0); |
| 726 | + if (!p) { |
| 727 | + std::cout << "mz_zip_reader_extract_file_to_heap() failed to extract file: " << filename << std::endl; |
| 728 | + mz_zip_reader_end(&zip_archive); |
| 729 | + return 1; |
| 730 | + } |
| 731 | + lfs_file_t file_p; |
| 732 | + int ret = fs.FileOpen(&file_p, dest_path.c_str(), LFS_O_WRONLY | LFS_O_CREAT); |
| 733 | + if (ret) { |
| 734 | + std::cout << "fs.FileOpen returned error code: " << ret << " " << lfs_error_to_string(ret) << std::endl; |
| 735 | + return ret; |
| 736 | + } |
| 737 | + ret = fs.FileWrite(&file_p, static_cast<uint8_t *>(p), uncomp_size); |
| 738 | + if (ret < 0) { |
| 739 | + std::cout << "fs.FileWrite returned error code: " << ret << " " << lfs_error_to_string(ret) << std::endl; |
| 740 | + fs.FileClose(&file_p); |
| 741 | + return ret; |
| 742 | + } |
| 743 | + // file copy complete, close destination file and free data |
| 744 | + fs.FileClose(&file_p); |
| 745 | + mz_free(p); |
| 746 | + } |
| 747 | + // all done, close archive |
| 748 | + mz_zip_reader_end(&zip_archive); |
| 749 | + if (verbose) { |
| 750 | + std::cout << "finished: zip file fully loaded into SPI memory: " << zip_filename << std::endl; |
| 751 | + } |
| 752 | + |
| 753 | + } else { |
| 754 | + std::cout << "error: resource has unknown extension: " << args.at(i) << std::endl; |
| 755 | + print_help_res(program_name); |
| 756 | + return 1; |
| 757 | + } |
| 758 | + } |
| 759 | + } else { |
| 760 | + std::cout << "error: unknown res action '" << args.at(0) << "'" << std::endl; |
| 761 | + print_help_res(program_name); |
| 762 | + return 1; |
| 763 | + } |
| 764 | + return 0; |
| 765 | +} |
| 766 | + |
| 767 | + |
605 | 768 | int main(int argc, char **argv) |
606 | 769 | { |
607 | 770 | // parse arguments |
@@ -649,6 +812,8 @@ int main(int argc, char **argv) |
649 | 812 | return command_cp(argv[0], args, verbose); |
650 | 813 | } else if (command == "settings") { |
651 | 814 | return command_settings(argv[0], args, verbose); |
| 815 | + } else if (command == "res") { |
| 816 | + return command_res(argv[0], args, verbose); |
652 | 817 | } else |
653 | 818 | { |
654 | 819 | std::cout << "unknown argument '" << command << "'" << std::endl; |
|
0 commit comments