diff options
author | Gluzskiy Alexandr <sss@sss.chaoslab.ru> | 2015-09-15 12:49:00 +0300 |
---|---|---|
committer | Gluzskiy Alexandr <sss@sss.chaoslab.ru> | 2015-09-15 12:49:00 +0300 |
commit | f4750dc7ca9ce56c54352c03bb40affe1a64c315 (patch) | |
tree | e5ff58ae92b0c49f5624644611ea05312eebe1ed | |
parent | 1d6ced38a89547aaf2cc3745876360f0e5086474 (diff) |
client-qt:
implemented handler for SERVER_DOWNLOAD_INFO_REPLY
server:
api:
removed core_id field from download_internal_s as it always used for index in download map
some useful coments
core:
improoved signal handler a bit
fiexd bug in core_api metadata related functions (metadata_set, metadata_get, metadata_remove), now thay returning correct data
basic implementation of CLIENT_DOWNLOAD_ADD handler
basic implementation of module unloading
curl_downloader_module:
implemented metadata storing
some sanity checks
curl_download structure refactored a bit (simplification)
-rw-r--r-- | client-qt/udm-client-qt/udm_main.cpp | 17 | ||||
-rw-r--r-- | server/include/api_module_downloader.h | 4 | ||||
-rw-r--r-- | server/include/download_internal.h | 2 | ||||
-rw-r--r-- | server/include/server.h | 4 | ||||
-rw-r--r-- | server/modules/downloaders/curl/curl.cbp | 2 | ||||
-rw-r--r-- | server/modules/downloaders/curl/include/curl_download.h | 15 | ||||
-rw-r--r-- | server/modules/downloaders/curl/main.cpp | 145 | ||||
-rw-r--r-- | server/modules/downloaders/curl/main.h | 6 | ||||
-rw-r--r-- | server/modules/downloaders/curl/src/curl_download.cpp | 6 | ||||
-rw-r--r-- | server/modules/metadata/flat_files/flat_files.cbp | 2 | ||||
-rw-r--r-- | server/src/api_core.cpp | 16 | ||||
-rw-r--r-- | server/src/main.cpp | 33 | ||||
-rw-r--r-- | server/src/modules_handler.cpp | 2 | ||||
-rw-r--r-- | server/src/server_session.cpp | 57 | ||||
-rw-r--r-- | server/udm-server.cbp | 4 |
15 files changed, 232 insertions, 83 deletions
diff --git a/client-qt/udm-client-qt/udm_main.cpp b/client-qt/udm-client-qt/udm_main.cpp index e6ba87c..7ad52ee 100644 --- a/client-qt/udm-client-qt/udm_main.cpp +++ b/client-qt/udm-client-qt/udm_main.cpp @@ -224,6 +224,23 @@ void udm_main::server_message_received(server_msg msg) downloads.push_back(i.download()); } break; + case SERVER_MSG_TYPE::SERVER_DOWNLOAD_INFO_REPLY: + { + bool found = false; + for(auto i : downloads) + { + if(i.id() == msg.download().download().id()) + { + i = msg.download().download(); + found = true; + break; + } + } + if(!found) + downloads.push_back(msg.download().download()); + + } + break; default: break; } diff --git a/server/include/api_module_downloader.h b/server/include/api_module_downloader.h index 33a3fce..9e42715 100644 --- a/server/include/api_module_downloader.h +++ b/server/include/api_module_downloader.h @@ -83,14 +83,14 @@ struct download_s { int64_t size, downloaded, download_speed; //download size and size of downloaded data, also download speed all vars in bytes std::map<int, std::string> info_map; //any additional info provided by downloader module can be stored here std::list<download_content_entry_s> content; //download content can be set for download, structure described above - std::map<std::string, std::string> metadata; + std::map<std::string, std::string> metadata; //client defined metadata map }; class module_downloader : public module_base { public: //basic actions - virtual int add_download(std::map<int, std::string> params) = 0; //add download, this function must return unique for current session and current downloader download id + virtual int add_download(std::map<int, std::string> params) = 0; //add download, this function must return unique for current session and current downloader download id, -1 on error virtual bool stop_download(int download_id) = 0; //stop download by id received via add_download, return true on success, false otherwise virtual bool start_download(int download_id) = 0; //start download by id received via add_download, return true on success, false otherwise virtual bool delete_download(int download_id, bool delete_data = false) = 0; //delete download by id received via add_download, if "delete_data" is set, also remove all downloaded data from storage, return true on success, false otherwise diff --git a/server/include/download_internal.h b/server/include/download_internal.h index 3474071..60d9608 100644 --- a/server/include/download_internal.h +++ b/server/include/download_internal.h @@ -23,7 +23,7 @@ struct download_internal_s { - int core_id, module_id; + int module_id; std::string module_name; }; diff --git a/server/include/server.h b/server/include/server.h index 046628a..812406a 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -37,6 +37,10 @@ class server { public: server(boost::asio::io_service& io_service, runtime_config_s &config, std::map<std::string, client*> &clients, std::map<int, download_internal_s> &downloads, short port); + void terminate() + { + io_service_.stop(); + } private: void start_accept(); diff --git a/server/modules/downloaders/curl/curl.cbp b/server/modules/downloaders/curl/curl.cbp index cec0d49..a82aeb1 100644 --- a/server/modules/downloaders/curl/curl.cbp +++ b/server/modules/downloaders/curl/curl.cbp @@ -37,11 +37,13 @@ <Compiler> <Add option="-Wall" /> <Add option="-fexceptions -fPIC" /> + <Add option="-DBOOST_LOG_DYN_LINK" /> <Add directory="../../../include" /> </Compiler> <Linker> <Add library="boost_system" /> <Add library="curl" /> + <Add library="boost_log" /> </Linker> <Unit filename="include/curl_download.h" /> <Unit filename="main.cpp" /> diff --git a/server/modules/downloaders/curl/include/curl_download.h b/server/modules/downloaders/curl/include/curl_download.h index e542de9..6956ce4 100644 --- a/server/modules/downloaders/curl/include/curl_download.h +++ b/server/modules/downloaders/curl/include/curl_download.h @@ -4,9 +4,9 @@ #include <api_module_downloader.h> #include <curl/curl.h> -enum download_state {running, stopped}; +enum download_state {download_running, download_stopped}; -class curl_download +class curl_download : public download_s { public: curl_download(std::map<int, std::string> params, core_api *a); @@ -19,12 +19,15 @@ class curl_download { return cancel_transfer; } + const std::string& get_download_path() + { + return download_path; + } private: void perform_internal(); - download_s api_download; - CURL *easy_handle; - bool cancel_transfer; - download_state state; + CURL *easy_handle = nullptr; + bool cancel_transfer = false; + download_state state = download_stopped; std::string download_path; }; diff --git a/server/modules/downloaders/curl/main.cpp b/server/modules/downloaders/curl/main.cpp index f3da19e..1e98e14 100644 --- a/server/modules/downloaders/curl/main.cpp +++ b/server/modules/downloaders/curl/main.cpp @@ -21,6 +21,7 @@ #include "main.h" #include <boost/bind.hpp> +#include <boost/log/trivial.hpp> #include <curl/curl.h> @@ -36,15 +37,78 @@ module_base::~module_base() downloader::downloader() { if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) - ;//TODO: handle error + { + //TODO: handle error + } } downloader::~downloader() { + //TODO: stop downloads + //TODO: deinit curl + //save runtime state on exit + { + //we need to know id of last element in downloads map on loading, save it + if(!downloads.empty()) + { + char id_[32]; + snprintf(id_, 31, "%d", downloads.rbegin()->first); + std::string val = id_; + api->metadata_set(this, "id_of_last_download", std::vector<char>(val.begin(), val.end())); + } + } } +std::string compute_var_name(int download_id, std::string setting_name) +{ + std::string var_name = "download_"; + char id_s[64]; + snprintf(id_s, 63, "%d", download_id); + var_name += id_s; + var_name += "_"; + var_name += setting_name; + return var_name; +} + + void downloader::on_modules_loaded() { + //TODO: load downloads, metadata, e.t.c. + std::vector<char> val; + api->metadata_get(this, "id_of_last_download", val); + std::string s(val.begin(), val.end()); + int last = atoi(s.c_str()); + for(int i = 0; i <= last; i++) + { + //TODO: rework following, for now just sufficient code for testing + std::vector<char> tmp; + if(!api->metadata_get(this, compute_var_name(i, "name"), tmp)) //always required + continue; + if(tmp.empty()) + continue; + tmp.clear(); + if(!api->metadata_get(this, compute_var_name(i, "download_path"), tmp)) //always required + continue; + if(tmp.empty()) + continue; + tmp.clear(); + std::map<int, std::string> params; + for(int p = 0;; p++) + { + char num_[32]; + snprintf(num_, 31, "%d", p); + if(api->metadata_get(this, compute_var_name(i, std::string("param_") + num_), tmp)) + { + params[p] = std::string(tmp.begin(), tmp.end()); + tmp.clear(); + } + else + break; + } + add_download(params); //TODO: implement better approach, but this should be ok for testing + + } + } void downloader::load(core_api *a) @@ -76,29 +140,51 @@ void downloader::set_module_settings(const std::map<std::string, setting_s> &set this->settings = settings; } - int downloader::add_download(std::map<int, std::string> params) { + if(params.find(0) == params.end()) //missed required parameter + return -1; curl_download *d = new curl_download(params, api); - my_download md; std::string download_name; { auto p1 = params[0].rfind("/"); if(p1 != std::string::npos) - md.name = params[0].substr(p1); + d->name = params[0].substr(p1+1); + else + d->name = params[0]; } - md.curl_d = d; int id = 0; if(!downloads.empty()) { id = downloads.size(); - md.id = id; + d->id = id; } else - md.id = 0; + d->id = 0; - downloads[id] = md; + downloads[id] = d; + //store metadata + { + if(!api->metadata_set(this, compute_var_name(id, "name"), std::vector<char>(d->name.begin(), d->name.end()))) + { + //TODO: handle error + } + if(!api->metadata_set(this, compute_var_name(id, "download_path"), std::vector<char>(d->get_download_path().begin(), d->get_download_path().end()))) + { + //TODO: handle error + } + //store params + for(auto i : params) + { + char num_[32]; + snprintf(num_, 31, "%d", i.first); + if(!api->metadata_set(this, compute_var_name(id, std::string("param_") + num_), std::vector<char>(i.second.begin(), i.second.end()))) + { + //TODO: handle error + } + } + } //TODO: return id; } @@ -106,7 +192,7 @@ int downloader::add_download(std::map<int, std::string> params) bool downloader::stop_download(int download_id) { //TODO: - bool status = downloads[download_id].curl_d->stop(); + bool status = downloads[download_id]->stop(); if(!status) { //TODO: handle error @@ -118,7 +204,7 @@ bool downloader::stop_download(int download_id) bool downloader::start_download(int download_id) { //TODO: - bool status = downloads[download_id].curl_d->start(); + bool status = downloads[download_id]->start(); if(!status) { //TODO: handle error @@ -129,7 +215,7 @@ bool downloader::start_download(int download_id) bool downloader::delete_download(int download_id, bool delete_data) { - bool status = downloads[download_id].curl_d->delete_download(); + bool status = downloads[download_id]->delete_download(); if(!status) { //TODO: handle errror @@ -137,7 +223,8 @@ bool downloader::delete_download(int download_id, bool delete_data) } else { - downloads[download_id].curl_d = nullptr; + delete downloads[download_id]; + downloads.erase(download_id); } //TODO: return true; @@ -153,7 +240,7 @@ std::list<download_s> downloader::get_downloads(std::map<int, std::string> param { std::list<download_s> l; for(auto i : downloads) - l.push_back(i.second); + l.push_back(*(i.second)); //TODO: fill aditional data fields return l; @@ -161,26 +248,16 @@ std::list<download_s> downloader::get_downloads(std::map<int, std::string> param download_s downloader::get_download(int download_id, std::map<int, std::string> params) { - download_s d = downloads[download_id]; + download_s d = *downloads[download_id]; //TODO: fill additional data fields return d; } -std::string compute_var_name(int download_id, std::string setting_name) -{ - std::string var_name = "download_"; - char id_s[64]; - snprintf(id_s, 63, "%d", download_id); - var_name += id_s; - var_name += "_"; - var_name += setting_name; - return var_name; -} bool downloader::metadata_set(int download_id, std::map<std::string, std::string> metadata) { - downloads[download_id].metadata = metadata; - for(auto i : downloads[download_id].metadata) + downloads[download_id]->metadata = metadata; + for(auto i : downloads[download_id]->metadata) api->metadata_set(this, compute_var_name(download_id, i.first), std::vector<char>(i.second.begin(), i.second.end())); return true; } @@ -188,8 +265,8 @@ bool downloader::metadata_set(int download_id, std::map<std::string, std::string bool downloader::metadata_update(int download_id, std::map<std::string, std::string> metadata) { for(auto i : metadata) - downloads[download_id].metadata[i.first] = i.second; - for(auto i : downloads[download_id].metadata) + downloads[download_id]->metadata[i.first] = i.second; + for(auto i : downloads[download_id]->metadata) api->metadata_set(this, compute_var_name(download_id, i.first), std::vector<char>(i.second.begin(), i.second.end())); return true; } @@ -198,26 +275,26 @@ bool downloader::metadata_drop(int download_id, std::string var) { if(!var.empty()) { - downloads[download_id].metadata.erase(var); + downloads[download_id]->metadata.erase(var); api->metadata_remove(this, compute_var_name(download_id, var)); } else { - for(auto i : downloads[download_id].metadata) + for(auto i : downloads[download_id]->metadata) api->metadata_remove(this, compute_var_name(download_id, i.first)); - downloads[download_id].metadata.clear(); + downloads[download_id]->metadata.clear(); } return true; } std::map<std::string, std::string> downloader::metadata_get(int download_id) { - return downloads[download_id].metadata; + return downloads[download_id]->metadata; } std::string downloader::metadata_get(int download_id, std::string var) { - auto i = downloads[download_id].metadata.find(var); - if(i != downloads[download_id].metadata.end()) + auto i = downloads[download_id]->metadata.find(var); + if(i != downloads[download_id]->metadata.end()) return i->second; return std::string(); } diff --git a/server/modules/downloaders/curl/main.h b/server/modules/downloaders/curl/main.h index f5e589b..7040d8b 100644 --- a/server/modules/downloaders/curl/main.h +++ b/server/modules/downloaders/curl/main.h @@ -26,10 +26,6 @@ #include "curl_download.h" -struct my_download : public download_s -{ - curl_download *curl_d; -}; class downloader : public module_downloader { @@ -57,7 +53,7 @@ public: private: void on_modules_loaded(); - std::map<int, my_download> downloads; //map of id, download + std::map<int, curl_download*> downloads; //map of id, download }; diff --git a/server/modules/downloaders/curl/src/curl_download.cpp b/server/modules/downloaders/curl/src/curl_download.cpp index 16866b5..29bdc21 100644 --- a/server/modules/downloaders/curl/src/curl_download.cpp +++ b/server/modules/downloaders/curl/src/curl_download.cpp @@ -12,12 +12,13 @@ size_t curl_w_callback(char *ptr, size_t size, size_t nmemb, void *userdata) size_t size_ = size * nmemb; if(size_) { + //TODO: write data } return size_; } -curl_download::curl_download(std::map<int, std::string> params, core_api *a) : cancel_transfer(false), state(stopped) +curl_download::curl_download(std::map<int, std::string> params, core_api *a) { //for now we use single transfer connection for url //TODO: support multiple connections in parallel for multithreaded download @@ -40,13 +41,14 @@ curl_download::curl_download(std::map<int, std::string> params, core_api *a) : c bool curl_download::start() { boost::thread(boost::bind(&curl_download::perform_internal, this)); - state = running; + state = download_running; return true; //TODO: } bool curl_download::stop() { cancel_transfer = true; + state = download_stopped; return true; //TODO: } diff --git a/server/modules/metadata/flat_files/flat_files.cbp b/server/modules/metadata/flat_files/flat_files.cbp index 038dcf0..901dad4 100644 --- a/server/modules/metadata/flat_files/flat_files.cbp +++ b/server/modules/metadata/flat_files/flat_files.cbp @@ -35,12 +35,14 @@ <Compiler> <Add option="-Wall" /> <Add option="-fexceptions -fPIC" /> + <Add option="-DBOOST_LOG_DYN_LINK" /> <Add directory="../../../include" /> </Compiler> <Linker> <Add library="boost_filesystem" /> <Add library="boost_system" /> <Add library="boost_serialization" /> + <Add library="boost_log" /> </Linker> <Unit filename="main.cpp" /> <Unit filename="main.h" /> diff --git a/server/src/api_core.cpp b/server/src/api_core.cpp index f9fe772..edc0e0a 100644 --- a/server/src/api_core.cpp +++ b/server/src/api_core.cpp @@ -30,27 +30,17 @@ extern runtime_config_s runtime_config; bool core_api::metadata_set(module_base *m, const std::string &setting_name, const std::vector<char> &data) { - //print data -/* std::cout<<"printing data in core set api:\n"; - for(auto i = data.begin(), end = data.end(); i != end; ++i) - { - std::cout<<*i; - } - std::cout<<std::endl; */ - runtime_config.default_metadata_storage->set(m->get_module_info().name, setting_name, data); - return true; + return runtime_config.default_metadata_storage->set(m->get_module_info().name, setting_name, data); } bool core_api::metadata_get(module_base *m, const std::string &setting_name, std::vector<char> &data) { - runtime_config.default_metadata_storage->get(m->get_module_info().name, setting_name, data); - return true; + return runtime_config.default_metadata_storage->get(m->get_module_info().name, setting_name, data); } bool core_api::metadata_remove(module_base *m, const std::string &setting_name) { - runtime_config.default_metadata_storage->remove(m->get_module_info().name, setting_name); - return true; + return runtime_config.default_metadata_storage->remove(m->get_module_info().name, setting_name); } std::map<std::string, setting_s> core_api::get_module_settings(module_base *m) diff --git a/server/src/main.cpp b/server/src/main.cpp index 09b0b62..214b6b5 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -52,15 +52,42 @@ server *serv = nullptr; //TODO: "core" config section architecture, define base settings -void sigint_handler(int sig) +void on_exit() { + serv->terminate(); bpt::write_info(runtime_config.config_file_path, runtime_config.config_file); //save config on sigint - exit(0); + for(auto m : modules->get_downloader_modules()) + delete m; + for(auto m : modules->get_metadata_modules()) + delete m; +} + +void sig_handler(int sig) +{ + switch(sig) + { + default: + { + on_exit(); + exit(0); + } + break; + } } extern "C" int main(int argc, char *argv[]) { - signal(SIGINT, sigint_handler); + //handle signals + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + signal(SIGABRT, sig_handler); + + //this will not work + signal(SIGKILL, sig_handler); + signal(SIGSTOP, sig_handler); + + + bpo::options_description desc("Available commands and options"); desc.add_options() ("help,h", "this message") diff --git a/server/src/modules_handler.cpp b/server/src/modules_handler.cpp index eb08261..90328b1 100644 --- a/server/src/modules_handler.cpp +++ b/server/src/modules_handler.cpp @@ -125,6 +125,7 @@ std::string modules_handler::list_modules_single_type_internal(const std::list<m std::string buf; for(auto i : modules) { + //TODO: additional info for each module type buf += "\tName: "; buf += i->get_module_info().name; buf += "\n\tDescription: " + i->get_module_info().description; @@ -199,7 +200,6 @@ void modules_handler::sync_downloads(std::map<int, download_internal_s> &downloa for(auto it : d->get_downloads()) { downloads[id].module_name = d->get_module_info().name; - downloads[id].core_id = id; //is it needed ? downloads[id].module_id = it.id; id++; } diff --git a/server/src/server_session.cpp b/server/src/server_session.cpp index 1a3c08c..ff49408 100644 --- a/server/src/server_session.cpp +++ b/server/src/server_session.cpp @@ -346,11 +346,11 @@ bool server_session::handle_command(client_msg *msg) break; case CLIENT_MSG_TYPE::CLIENT_MODULES_REQUEST: { - server_msg msg; - msg.set_type(SERVER_MSG_TYPE::SERVER_MODULES_REPLY); + server_msg m; + m.set_type(SERVER_MSG_TYPE::SERVER_MODULES_REPLY); for(auto i : modules->get_downloader_modules()) { - module_info *mi = msg.add_server_modules_reply(); + module_info *mi = m.add_server_modules_reply(); mi->set_type(SERVER_MODULE_TYPE::SERVER_MODULE_DOWNLOADER); mi->set_name(i->get_module_info().name); mi->set_description(i->get_module_info().description); @@ -378,7 +378,7 @@ bool server_session::handle_command(client_msg *msg) } for(auto i : modules->get_metadata_modules()) { - module_info *mi = msg.add_server_modules_reply(); + module_info *mi = m.add_server_modules_reply(); mi->set_type(SERVER_MODULE_TYPE::SERVER_MODULE_METADATA_STORAGE); mi->set_name(i->get_module_info().name); mi->set_description(i->get_module_info().description); @@ -386,21 +386,21 @@ bool server_session::handle_command(client_msg *msg) for(auto ms : i->get_runtime_module_settings()) add_runtime_module_setting(ms, mi); } - send_message(&msg); + send_message(&m); } break; case CLIENT_MSG_TYPE::CLIENT_CORE_INFO_REQUEST: { - server_msg msg; - msg.set_type(SERVER_MSG_TYPE::SERVER_CORE_INFO_REPLY); - msg.mutable_server_core_info_reply()->set_version(1); + server_msg m; + m.set_type(SERVER_MSG_TYPE::SERVER_CORE_INFO_REPLY); + m.mutable_server_core_info_reply()->set_version(1); try{ for(auto it : runtime_config.config_file.get_child("server")) //load server node { std::string val = it.second.get_value<std::string>("empty_value"); //TODO: something better here. we need to avoid subtrees and empty vars if(/*val == "" ||*/ val == "empty_value") continue; - setting *i = msg.mutable_server_core_info_reply()->add_settings(); + setting *i = m.mutable_server_core_info_reply()->add_settings(); i->set_name(it.first); i->set_value(val); //TODO: is it possible to set something better than just list of core setting ? @@ -410,7 +410,7 @@ bool server_session::handle_command(client_msg *msg) { //TODO: } - send_message(&msg); + send_message(&m); } break; @@ -418,11 +418,11 @@ bool server_session::handle_command(client_msg *msg) { //TODO: thread safety //draft implementation, need a lot of optimizations - server_msg msg; - msg.set_type(SERVER_MSG_TYPE::SERVER_DOWNLOADS_LIST_REPLY); + server_msg m; + m.set_type(SERVER_MSG_TYPE::SERVER_DOWNLOADS_LIST_REPLY); for(auto i : downloads) { - server_download_reply *r = msg.add_downloads(); + server_download_reply *r = m.add_downloads(); download *d = r->mutable_download(); d->set_id(i.first); //set core_id for later access d->set_module_name(i.second.module_name); @@ -446,10 +446,39 @@ bool server_session::handle_command(client_msg *msg) d->set_size(ds.size); d->set_name(ds.name); } - send_message(&msg); + send_message(&m); return true; } break; + case CLIENT_MSG_TYPE::CLIENT_DOWNLOAD_ADD: + { + for(auto i : modules->get_downloader_modules()) + { + if(i->get_module_info().name == msg->download_add_request().module_name()) + { + std::map<int, std::string> params; + for(auto p : msg->download_add_request().params()) + params[p.id()] = p.value(); + auto dm = static_cast<module_downloader *>(i); + int download_id = dm->add_download(params); + int core_id = downloads.size(); + downloads[core_id].module_id = download_id; + downloads[core_id].module_name = msg->download_add_request().module_name(); + server_msg m; + m.set_type(SERVER_MSG_TYPE::SERVER_DOWNLOAD_INFO_REPLY); + download *d = m.mutable_download()->mutable_download(); + d->set_id(core_id); + auto dl = dm->get_download(download_id); + d->set_name(dl.name); + d->set_size(dl.size); + d->set_module_name(msg->download_add_request().module_name()); + d->set_downloaded(dl.downloaded); + send_message(&m); + + } + } + } + break; default: return false; break; diff --git a/server/udm-server.cbp b/server/udm-server.cbp index 7d2a776..458ef0f 100644 --- a/server/udm-server.cbp +++ b/server/udm-server.cbp @@ -51,8 +51,8 @@ <Add library="boost_random" /> </Linker> <ExtraCommands> - <Add before="[ -d ../protocol ] || mkdir ../protocol" /> - <Add before="protoc --cpp_out=../protocol --proto_path=../protocol ../protocol/udm.proto" /> + <Add before="#[ -d ../protocol ] || mkdir ../protocol" /> + <Add before="#protoc --cpp_out=../protocol --proto_path=../protocol ../protocol/udm.proto" /> </ExtraCommands> <Unit filename="../protocol/udm.pb.cc" /> <Unit filename="../protocol/udm.pb.h" /> |