-
Notifications
You must be signed in to change notification settings - Fork 5
Added support for Meala remote log database #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| connection_url = "udp://:14551" | ||
| local_server = "http://127.0.0.1:5006" | ||
| upload_service = 0 | ||
| remote_server = "https://review.px4.io" | ||
| credentials_file = "" | ||
| email = "" | ||
| upload_enabled = false | ||
| public_logs = false |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |||
| #include <future> | ||||
| #include <regex> | ||||
| #include <fstream> | ||||
| #include "ServerInterface.hpp" | ||||
|
|
||||
| namespace fs = std::filesystem; | ||||
|
|
||||
|
|
@@ -27,6 +28,9 @@ LogLoader::LogLoader(const LogLoader::Settings& settings) | |||
| .db_path = _settings.application_directory + "local_server.db", | ||||
| .upload_enabled = true, // Always upload to local server | ||||
| .public_logs = true, // Public required true for searching using Web UI | ||||
| .upload_service = UploadService::FlightReview, // Local server is always FlightReview, | ||||
| .credentials_file = "" // FlightReview does not need credentials | ||||
|
|
||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
| }; | ||||
|
|
||||
| // Setup remote server interface | ||||
|
|
@@ -37,10 +41,21 @@ LogLoader::LogLoader(const LogLoader::Settings& settings) | |||
| .db_path = _settings.application_directory + "remote_server.db", | ||||
| .upload_enabled = settings.upload_enabled, | ||||
| .public_logs = settings.public_logs, | ||||
| .upload_service = settings.upload_service, | ||||
| .credentials_file = settings.credentials_file | ||||
| }; | ||||
|
|
||||
| _local_server = std::make_shared<ServerInterface>(local_server_settings); | ||||
| _remote_server = std::make_shared<ServerInterface>(remote_server_settings); | ||||
|
|
||||
| // _remote_server = std::make_shared<ServerInterface>(remote_server_settings); | ||||
| if (_settings.upload_service == UploadService::Meala) { | ||||
| MealaCredentials creds; | ||||
| creds.credentials_file = _settings.credentials_file; | ||||
| _remote_server = std::make_shared<MealaServerInterface>(remote_server_settings, creds); | ||||
|
|
||||
| } else { | ||||
| _remote_server = std::make_shared<ServerInterface>(remote_server_settings); | ||||
| } | ||||
|
|
||||
| std::cout << std::fixed << std::setprecision(8); | ||||
|
|
||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| #include "ServerInterface.hpp" | ||
| #include <fstream> | ||
| #include <filesystem> | ||
| #include <vector> | ||
| #include <sstream> | ||
| #define CPPHTTPLIB_OPENSSL_SUPPORT | ||
| #include <httplib.h> | ||
| #include <nlohmann/json.hpp> | ||
| #include "Log.hpp" | ||
|
|
||
| namespace fs = std::filesystem; | ||
| using json = nlohmann::json; | ||
|
|
||
| MealaServerInterface::MealaServerInterface(const Settings& settings, const MealaCredentials& creds) | ||
| : ServerInterface(settings), _creds(creds) | ||
| {} | ||
|
|
||
| bool MealaServerInterface::login() | ||
| { | ||
| std::string username, password, token; | ||
|
|
||
| if (!_creds.credentials_file.empty() && fs::exists(_creds.credentials_file)) { | ||
| std::ifstream f(_creds.credentials_file); | ||
| json creds_json; | ||
| f >> creds_json; | ||
| username = creds_json.value("username", ""); | ||
| token = creds_json.value("token", ""); | ||
|
|
||
| } else { | ||
| username = _creds.username; | ||
| password = _creds.password; | ||
| } | ||
|
|
||
| httplib::Params params; | ||
| params.emplace("username", username); | ||
|
|
||
| if (!token.empty()) { | ||
| params.emplace("token", token); | ||
|
|
||
| } else { | ||
| params.emplace("password", password); | ||
| } | ||
|
|
||
| std::string url_path = "/login"; | ||
| httplib::Result res; | ||
|
|
||
| if (_protocol == Protocol::Https) { | ||
| httplib::SSLClient cli(_settings.server_url); | ||
| res = cli.Post(url_path.c_str(), params); | ||
|
|
||
| } else { | ||
| httplib::Client cli(_settings.server_url); | ||
| res = cli.Post(url_path.c_str(), params); | ||
| } | ||
|
|
||
| if (res && res->status == 200) { | ||
| auto it = res->headers.find("Set-Cookie"); | ||
|
|
||
| if (it != res->headers.end()) { | ||
| _session_cookie = it->second; | ||
| _logged_in = true; | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| _logged_in = false; | ||
| return false; | ||
| } | ||
|
|
||
| ServerInterface::UploadResult MealaServerInterface::upload(const std::string& filepath) | ||
| { | ||
| if (!_logged_in && !login()) { | ||
| return {false, 401, "Login to Meala failed"}; | ||
| } | ||
|
|
||
| constexpr size_t chunk_size = 5 * 1024 * 1024; // 5 MB | ||
|
|
||
| if (!fs::exists(filepath)) { | ||
| return {false, 404, "Log file does not exist: " + filepath}; | ||
| } | ||
|
|
||
| size_t file_size = fs::file_size(filepath); | ||
| size_t total_chunks = (file_size / chunk_size) + (file_size % chunk_size ? 1 : 0); | ||
| std::string filename = fs::path(filepath).filename().string(); | ||
|
|
||
| std::ifstream file(filepath, std::ios::binary); | ||
|
|
||
| if (!file) { | ||
| return {false, 0, "Failed to open file"}; | ||
| } | ||
|
|
||
| for (size_t chunk_index = 0; chunk_index < total_chunks; ++chunk_index) { | ||
| size_t offset = chunk_index * chunk_size; | ||
| size_t this_chunk_size = std::min(chunk_size, file_size - offset); | ||
| std::vector<char> buffer(this_chunk_size); | ||
| file.read(buffer.data(), this_chunk_size); | ||
|
|
||
| httplib::MultipartFormDataItems items = { | ||
| {"comments", "Log uploaded from C++ API", "", ""}, | ||
| {"battery", "", "", ""}, | ||
| {"pic", "", "", ""}, | ||
| {"gso", "", "", ""}, | ||
| {"vehicle_id", "", "", ""}, | ||
| {"dzchunkbyteoffset", std::to_string(offset), "", ""}, | ||
| {"dzchunkindex", std::to_string(chunk_index), "", ""}, | ||
| {"dztotalchunkcount", std::to_string(total_chunks), "", ""}, | ||
| {"files", std::string(buffer.begin(), buffer.end()), filename, "application/octet-stream"} | ||
| }; | ||
|
|
||
| std::string url_path = "/upload/api"; | ||
| httplib::Headers headers; | ||
|
|
||
| if (!_session_cookie.empty()) { | ||
| headers.emplace("Cookie", _session_cookie); | ||
| } | ||
|
|
||
| LOG("Uploading " << filename << " to " << _settings.server_url); | ||
|
|
||
| httplib::Result res; | ||
|
|
||
| if (_protocol == Protocol::Https) { | ||
| httplib::SSLClient cli(_settings.server_url); | ||
| res = cli.Post(url_path.c_str(), headers, items); | ||
|
|
||
| } else { | ||
| httplib::Client cli(_settings.server_url); | ||
| res = cli.Post(url_path.c_str(), headers, items); | ||
| } | ||
|
|
||
| if (!res || res->status != 200) { | ||
| LOG("Upload failed on chunk " << (chunk_index + 1) << ": " | ||
| << (res ? std::to_string(res->status) : "No response")); | ||
| return {false, res ? res->status : 0, "No response from server"}; | ||
| } | ||
| } | ||
|
|
||
| return {true, 200, "Upload completed successfully."}; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| #include <functional> | ||
| #define CPPHTTPLIB_OPENSSL_SUPPORT | ||
| #include <httplib.h> | ||
| #include <vector> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unnecessary? |
||
|
|
||
| namespace fs = std::filesystem; | ||
|
|
||
|
|
@@ -597,4 +598,4 @@ ServerInterface::DatabaseEntry ServerInterface::row_to_db_entry(sqlite3_stmt* st | |
| entry.downloaded = sqlite3_column_int(stmt, 4) != 0; | ||
|
|
||
| return entry; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,12 @@ | |
| #include <vector> | ||
| #include <sqlite3.h> | ||
| #include <mavsdk/plugins/log_files/log_files.h> | ||
| #include <optional> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unnecessary? |
||
|
|
||
| enum class UploadService { | ||
| FlightReview = 0, | ||
| Meala = 1 | ||
| }; | ||
|
|
||
| class ServerInterface | ||
| { | ||
|
|
@@ -15,6 +21,8 @@ class ServerInterface | |
| std::string db_path; // Path to this server's database | ||
| bool upload_enabled {}; | ||
| bool public_logs {}; | ||
| UploadService upload_service; | ||
| std::string credentials_file; // For Meala credentials | ||
| }; | ||
|
|
||
| struct UploadResult { | ||
|
|
@@ -59,23 +67,45 @@ class ServerInterface | |
| void start(); | ||
| void stop(); | ||
|
|
||
| private: | ||
| protected: | ||
| enum class Protocol { | ||
| Http, | ||
| Https | ||
| }; | ||
| virtual UploadResult upload(const std::string& filepath); | ||
| Protocol _protocol {Protocol::Https}; | ||
| Settings _settings; | ||
|
|
||
| private: | ||
| void sanitize_url_and_determine_protocol(); | ||
| UploadResult upload(const std::string& filepath); | ||
| bool server_reachable(); | ||
|
|
||
| // Database operations | ||
| bool execute_query(const std::string& query); | ||
| bool add_to_blacklist(const std::string& uuid, const std::string& reason); | ||
| DatabaseEntry row_to_db_entry(sqlite3_stmt* stmt); | ||
|
|
||
| Settings _settings; | ||
| Protocol _protocol {Protocol::Https}; | ||
| bool _should_exit = false; | ||
| sqlite3* _db = nullptr; | ||
| }; | ||
|
|
||
| struct MealaCredentials { | ||
| std::string username; | ||
| std::string password; | ||
| std::string token; | ||
| std::string credentials_file; | ||
| }; | ||
|
|
||
| class MealaServerInterface : public ServerInterface | ||
| { | ||
| public: | ||
| MealaServerInterface(const Settings& settings, const MealaCredentials& creds); | ||
|
|
||
| bool login(); | ||
| UploadResult upload(const std::string& filepath) override; | ||
|
|
||
| private: | ||
| MealaCredentials _creds; | ||
| std::string _session_cookie; | ||
| bool _logged_in = false; | ||
| }; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please put this class declaration and MealaCredentials struct in a separate header |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already in the hpp