console.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. /******************************************************************************
  2. *
  3. * Project: OpenCPN
  4. *
  5. * Purpose: Simple CLI application.
  6. *
  7. ***************************************************************************
  8. * Copyright (C) 2022 Alec Leamas *
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. * This program is distributed in the hope that it will be useful, *
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  18. * GNU General Public License for more details. *
  19. * *
  20. * You should have received a copy of the GNU General Public License *
  21. * along with this program; if not, write to the *
  22. * Free Software Foundation, Inc., *
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
  24. ***************************************************************************
  25. */
  26. #include "wx/wxprec.h"
  27. #ifndef WX_PRECOMP
  28. #include "wx/wx.h"
  29. #endif
  30. #include "config.h"
  31. #ifdef _WIN32
  32. #include <windows.h>
  33. #endif
  34. #include <algorithm>
  35. #include <chrono>
  36. #include <ctime>
  37. #include <fstream>
  38. #include <iomanip>
  39. #include <iostream>
  40. #include <memory>
  41. #include "wx/wxprec.h"
  42. #ifndef WX_PRECOMP
  43. #include "wx/wx.h"
  44. #endif
  45. #include <wx/app.h>
  46. #include <wx/bitmap.h>
  47. #include <wx/cmdline.h>
  48. #include <wx/dynlib.h>
  49. #include <wx/fileconf.h>
  50. #include <wx/init.h>
  51. #include <wx/string.h>
  52. #include <wx/utils.h>
  53. #include "model/ais_state_vars.h"
  54. #include "model/catalog_handler.h"
  55. #include "model/cli_platform.h"
  56. #include "model/comm_appmsg_bus.h"
  57. #include "model/comm_driver.h"
  58. #include "model/comm_navmsg_bus.h"
  59. #include "model/config_vars.h"
  60. #include "model/downloader.h"
  61. #include "model/multiplexer.h"
  62. #include "model/nmea_log.h"
  63. #include "model/ocpn_utils.h"
  64. #include "model/pincode.h"
  65. #include "model/plugin_handler.h"
  66. #include "model/plugin_loader.h"
  67. #include "model/routeman.h"
  68. #include "model/select.h"
  69. #include "model/track.h"
  70. #include "observable_evtvar.h"
  71. void* g_pi_manager = reinterpret_cast<void*>(1L);
  72. class NmeaLogDummy: public NmeaLog {
  73. bool Active() const { return false; }
  74. void Add(const wxString& s) {};
  75. };
  76. static void InitRouteman() {
  77. struct RoutePropDlgCtx ctx;
  78. auto RouteMgrDlgUpdateListCtrl = [&]() {};
  79. static NmeaLogDummy dummy_log;
  80. g_pRouteMan = new Routeman(ctx, RoutemanDlgCtx(), dummy_log);
  81. }
  82. static const char* USAGE = R"""(
  83. Usage: opencpn-cli [options] <command>
  84. Options:
  85. -v, --verbose:
  86. Verbose logging.
  87. -a, --abi <abi-spec>
  88. Use non-default ABI. <abi-spec> is a string platform:version for
  89. example 'ubuntu-gtk3-x86_64:20.04'.
  90. Commands:
  91. load-plugin <plugin library file>:
  92. Try to load a given library file, report possible errors.
  93. install-plugin <plugin name>:
  94. Download and install given plugin
  95. uninstall-plugin <plugin name>:
  96. Uninstall given plugin
  97. list-plugins:
  98. List name and version for all installed plugins
  99. list-available:
  100. List name and version for available plugins in catalog.
  101. import-plugin <tarball file>
  102. Import a given tarball file
  103. print-abi:
  104. print default ABI spec platform:version
  105. update-catalog:
  106. Download latest master catalog.
  107. plugin-by-file <filename>
  108. Print name of a plugin containing file or "not found"
  109. generate-key <hostname>
  110. Generate and store a key for given hostname, print key on stdout.
  111. store-key <key> <hostname>
  112. store a key obtained using generate-key on remote host
  113. print-hostname:
  114. Print official hostname for generate-key and store-key.
  115. )""";
  116. static const char* const DOWNLOAD_REPO_PROTO =
  117. "https://raw.githubusercontent.com/OpenCPN/plugins/@branch@/"
  118. "ocpn-plugins.xml";
  119. wxDEFINE_EVENT(EVT_FOO, wxCommandEvent);
  120. wxDEFINE_EVENT(EVT_BAR, wxCommandEvent);
  121. using namespace std;
  122. class CliApp : public wxAppConsole {
  123. public:
  124. CliApp() : wxAppConsole() {
  125. CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, "program");
  126. SetAppName("opencpn");
  127. }
  128. void OnInitCmdLine(wxCmdLineParser& parser) override {
  129. parser.AddOption("a", "abi", "abi:version e. g., \"ubuntu-x86_64:20.04\"");
  130. parser.AddSwitch("v", "verbose", "Verbose logging");
  131. parser.AddSwitch("h", "help", "Print help");
  132. parser.AddParam("<command>", wxCMD_LINE_VAL_STRING,
  133. wxCMD_LINE_PARAM_OPTIONAL);
  134. parser.AddParam("[arg]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
  135. parser.AddParam("[arg]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
  136. wxLog::SetActiveTarget(new wxLogStderr);
  137. wxLog::SetTimestamp("");
  138. wxLog::SetLogLevel(wxLOG_Warning);
  139. g_BasePlatform = new BasePlatform();
  140. auto config_file = g_BasePlatform->GetConfigFileName();
  141. InitBaseConfig(new wxFileConfig("", "", config_file));
  142. pSelect = new Select();
  143. pRouteList = new RouteList;
  144. InitRouteman();
  145. auto colour_func = [] (wxString c) { return *wxBLACK; };
  146. pWayPointMan = new WayPointman(colour_func);
  147. }
  148. void list_plugins() {
  149. using namespace std;
  150. wxImage::AddHandler(new wxPNGHandler());
  151. g_BasePlatform->GetSharedDataDir(); // See #2619
  152. PluginLoader::getInstance()->LoadAllPlugIns(false);
  153. auto plugins = PluginHandler::getInstance()->getInstalled();
  154. for (const auto& p : plugins) {
  155. if (p.version == "0.0") continue;
  156. auto path = PluginHandler::ImportedMetadataPath(p.name);
  157. std::string suffix(ocpn::exists(path) ? "[imported]" : "");
  158. cout << left << setw(25) << p.name << p.version << suffix << "\n";
  159. }
  160. }
  161. void list_available() {
  162. using namespace std;
  163. auto handler = PluginHandler::getInstance();
  164. auto plugins = handler->getAvailable();
  165. for (const auto& p : plugins) {
  166. if (handler->isCompatible(p)) {
  167. cout << left << setw(25) << p.name << p.version << "\n";
  168. }
  169. }
  170. }
  171. void uninstall_plugin(const std::string& plugin) {
  172. using namespace std;
  173. g_BasePlatform->GetSharedDataDir(); // See #2619
  174. PluginLoader::getInstance()->LoadAllPlugIns(false);
  175. auto plugins = PluginHandler::getInstance()->getInstalled();
  176. vector<PluginMetadata> found;
  177. copy_if(plugins.begin(), plugins.end(), back_inserter(found),
  178. [plugin](const PluginMetadata& m) { return m.name == plugin; });
  179. if (found.size() == 0) {
  180. cerr << "No such plugin installed\n";
  181. exit(2);
  182. }
  183. PluginHandler::getInstance()->uninstall(found[0].name);
  184. }
  185. void import_plugin(const std::string& tarball_path) {
  186. auto handler = PluginHandler::getInstance();
  187. PluginMetadata metadata;
  188. bool ok = handler->ExtractMetadata(tarball_path, metadata);
  189. if (!ok) {
  190. std::cerr << "Cannot extract metadata (malformed tarball?)\n";
  191. exit(2);
  192. }
  193. if (!PluginHandler::isCompatible(metadata)) {
  194. std::cerr << "Incompatible plugin detected\n";
  195. exit(2);
  196. }
  197. ok = handler->installPlugin(metadata, tarball_path);
  198. if (!ok) {
  199. std::cerr << "Error extracting import plugin tarball.\n";
  200. exit(2);
  201. }
  202. metadata.is_imported = true;
  203. auto metadata_path = PluginHandler::ImportedMetadataPath(metadata.name);
  204. std::ofstream file(metadata_path);
  205. file << metadata.to_string();
  206. if (!file.good()) {
  207. std::cerr << "Error saving metadata file: " << metadata_path
  208. << " for imported plugin: " << metadata.name;
  209. exit(2);
  210. }
  211. exit(0);
  212. }
  213. void install_plugin(const std::string& plugin) {
  214. using namespace std;
  215. g_BasePlatform->GetSharedDataDir(); // See #2619
  216. wxImage::AddHandler(new wxPNGHandler());
  217. auto handler = PluginHandler::getInstance();
  218. auto plugins = handler->getAvailable();
  219. vector<PluginMetadata> found;
  220. copy_if(plugins.begin(), plugins.end(), back_inserter(found),
  221. [plugin, handler](const PluginMetadata& m) {
  222. return m.name == plugin && handler->isCompatible(m);
  223. });
  224. if (found.size() == 0) {
  225. cerr << "No such plugin available\n";
  226. exit(2);
  227. }
  228. Downloader downloader(found[0].tarball_url);
  229. string path;
  230. bool ok = downloader.download(path);
  231. if (!ok) {
  232. cerr << "Cannot download data from " << found[0].tarball_url << "\n";
  233. exit(1);
  234. }
  235. PluginHandler::getInstance()->installPlugin(path);
  236. remove(path.c_str());
  237. exit(0);
  238. }
  239. void plugin_by_file(const std::string& filename) {
  240. auto plugin = PluginHandler::getInstance()->getPluginByLibrary(filename);
  241. std::cout << (plugin != "" ? plugin : "Not found") << "\n";
  242. }
  243. bool load_plugin(const std::string& plugin) {
  244. auto loader = PluginLoader::getInstance();
  245. wxImage::AddHandler(new wxPNGHandler());
  246. g_BasePlatform->GetSharedDataDir(); // See #2619
  247. wxDEFINE_EVENT(EVT_FILE_NOTFOUND, wxCommandEvent);
  248. ObservableListener file_notfound_listener;
  249. file_notfound_listener.Listen(loader->evt_unreadable_plugin, this,
  250. EVT_FILE_NOTFOUND);
  251. Bind(EVT_FILE_NOTFOUND, [&](wxCommandEvent ev) {
  252. std::cerr << "Cannot open file: " << ev.GetString() << "\n";
  253. });
  254. wxDEFINE_EVENT(EVT_BAD_VERSION, wxCommandEvent);
  255. ObservableListener bad_version_listener;
  256. bad_version_listener.Listen(loader->evt_version_incompatible_plugin, this,
  257. EVT_BAD_VERSION);
  258. Bind(EVT_BAD_VERSION, [&](wxCommandEvent ev) {
  259. std::cerr << "Incompatible plugin version " << ev.GetString() << "\n";
  260. });
  261. auto container = loader->LoadPlugIn(plugin.c_str());
  262. ProcessPendingEvents();
  263. std::cout << (container ? "File loaded OK\n" : "Load error\n");
  264. return container ? true : false;
  265. }
  266. class Apikeys : public std::unordered_map<std::string, std::string> {
  267. public:
  268. static Apikeys Parse(const std::string& s) {
  269. Apikeys apikeys;
  270. auto ip_keys = ocpn::split(s.c_str(), ";");
  271. for (const auto& ip_key : ip_keys) {
  272. auto words = ocpn::split(ip_key.c_str(), ":");
  273. if (words.size() != 2) continue;
  274. if (apikeys.find(words[0]) == apikeys.end()) {
  275. apikeys[words[0]] = words[1];
  276. }
  277. }
  278. return apikeys;
  279. }
  280. std::string ToString() const {
  281. std::stringstream ss;
  282. for (const auto& it : *this) ss << it.first << ":" << it.second << ";";
  283. return ss.str();
  284. }
  285. };
  286. void generate_key(const std::string& hostname) {
  287. TheBaseConfig()->SetPath("/Settings/RestServer");
  288. wxString key_string;
  289. TheBaseConfig()->Read("ServerKeys", &key_string);
  290. Apikeys key_map = Apikeys::Parse(key_string.ToStdString());
  291. Pincode pincode = Pincode::Create();
  292. key_map[hostname] = pincode.Hash();
  293. TheBaseConfig()->Write("ServerKeys", wxString(key_map.ToString()));
  294. TheBaseConfig()->Flush();
  295. std::cout << pincode.Get() << "\n";
  296. }
  297. void store_key(const std::string& hostname, const std::string& key) {
  298. if (key == "") {
  299. std::cerr << USAGE << "\n";
  300. exit(1);
  301. }
  302. TheBaseConfig()->SetPath("/Settings/RESTClient");
  303. wxString key_string;
  304. TheBaseConfig()->Read("ServerKeys", &key_string);
  305. Apikeys key_map = Apikeys::Parse(key_string.ToStdString());
  306. int numkey;
  307. try {
  308. numkey = std::stoi(key);
  309. } catch(...) {
  310. std::cerr << "Cannot parse key:" << key << "\n";
  311. exit(1);
  312. }
  313. Pincode pincode(numkey);
  314. key_map[hostname] = pincode.Hash();
  315. TheBaseConfig()->Write("ServerKeys", wxString(key_map.ToString()));
  316. TheBaseConfig()->Flush();
  317. }
  318. void print_hostname() {
  319. wxInitializer initializer;
  320. auto hostname = ::wxGetHostName().ToStdString();
  321. if (hostname.empty()) hostname = wxGetUserName().ToStdString();
  322. // A Portable need a unique mDNS data hostname to share routes.
  323. // FIXME (leamas) portable handling here...
  324. // FIXME (leamas) copy-pasted from ocpn_app.cpp. Refactor.
  325. // if (g_bportable) hostname = std::string("Portable-") + hostname;
  326. std::cout << hostname << "\n";
  327. }
  328. void check_param_count(const wxCmdLineParser& parser, size_t count) {
  329. if (parser.GetParamCount() < count) {
  330. std::cerr << USAGE << "\n";
  331. exit(1);
  332. }
  333. }
  334. void update_catalog() {
  335. std::string catalog(g_catalog_channel == "" ? "master" : g_catalog_channel);
  336. std::string url(g_catalog_custom_url);
  337. if (catalog != "custom") {
  338. url = std::string(DOWNLOAD_REPO_PROTO);
  339. ocpn::replace(url, "@branch@", catalog);
  340. }
  341. auto path = PluginHandler::getInstance()->getMetadataPath();
  342. auto cat_handler = CatalogHandler::getInstance();
  343. auto status = cat_handler->DownloadCatalog(path, url);
  344. if (status != CatalogHandler::ServerStatus::OK) {
  345. std::cout << "Cannot update catalog\n";
  346. exit(1);
  347. }
  348. }
  349. bool OnCmdLineParsed(wxCmdLineParser& parser) override {
  350. wxInitializer initializer;
  351. if (!initializer) {
  352. std::cerr << "Failed to initialize the wxWidgets library, aborting.";
  353. exit(1);
  354. }
  355. wxAppConsole::OnCmdLineParsed(parser);
  356. if (argc == 1) {
  357. std::cout << "OpenCPN CLI application. Use -h for help\n";
  358. exit(0);
  359. }
  360. wxString option_val;
  361. if (parser.Found("help")) {
  362. std::cout << USAGE << "\n";
  363. exit(0);
  364. }
  365. if (parser.Found("verbose")) wxLog::SetLogLevel(wxLOG_Debug);
  366. if (parser.Found("abi", &option_val)) {
  367. auto abi_vers = ocpn::split(option_val, ":");
  368. g_compatOS = abi_vers[0];
  369. if (abi_vers.size() > 1) g_compatOsVersion = abi_vers[1];
  370. }
  371. if (parser.GetParamCount() < 1) {
  372. std::cerr << USAGE << "\n";
  373. exit(1);
  374. }
  375. std::string command(parser.GetParam(0));
  376. if (command == "load-plugin") {
  377. check_param_count(parser, 2);
  378. load_plugin(parser.GetParam(1).ToStdString());
  379. } else if (command == "uninstall-plugin") {
  380. check_param_count(parser, 2);
  381. uninstall_plugin(parser.GetParam(1).ToStdString());
  382. } else if (command == "import-plugin") {
  383. check_param_count(parser, 2);
  384. import_plugin(parser.GetParam(1).ToStdString());
  385. } else if (command == "install-plugin") {
  386. check_param_count(parser, 2);
  387. install_plugin(parser.GetParam(1).ToStdString());
  388. } else if (command == "list-plugins") {
  389. check_param_count(parser, 1);
  390. list_plugins();
  391. } else if (command == "list-available") {
  392. check_param_count(parser, 1);
  393. list_available();
  394. } else if (command == "print-abi") {
  395. check_param_count(parser, 1);
  396. std::cout << PKG_TARGET << ":" << PKG_TARGET_VERSION "\n";
  397. } else if (command == "update-catalog") {
  398. check_param_count(parser, 1);
  399. update_catalog();
  400. } else if (command == "plugin-by-file") {
  401. check_param_count(parser, 2);
  402. plugin_by_file(parser.GetParam(1).ToStdString());
  403. } else if (command == "generate-key") {
  404. check_param_count(parser, 1);
  405. generate_key(parser.GetParam(1).ToStdString());
  406. } else if (command == "store-key") {
  407. check_param_count(parser, 3);
  408. store_key(parser.GetParam(1).ToStdString(),
  409. parser.GetParam(2).ToStdString());
  410. } else if (command == "print-hostname") {
  411. check_param_count(parser, 0);
  412. print_hostname();
  413. } else {
  414. std::cerr << USAGE << "\n";
  415. exit(2);
  416. }
  417. exit(0);
  418. }
  419. };
  420. wxIMPLEMENT_APP_CONSOLE(CliApp);