35#include <unordered_map>
39#if (defined(OCPN_GHC_FILESYSTEM) || \
40 (defined(__clang_major__) && (__clang_major__ < 15)))
42#include <ghc/filesystem.hpp>
43namespace fs = ghc::filesystem;
47namespace fs = std::filesystem;
56#include <wx/filename.h>
58#include <wx/tokenzr.h>
63#include <archive_entry.h>
64typedef __LA_INT64_T la_int64_t;
66#if defined(__MINGW32__) && defined(Yield)
72#include "model/base_platform.h"
75#include "model/config_vars.h"
77#include "model/downloader.h"
79#include "model/ocpn_utils.h"
80#include "model/plugin_cache.h"
81#include "model/plugin_handler.h"
82#include "model/plugin_loader.h"
83#include "model/plugin_paths.h"
86static std::string SEP(
"\\");
88static std::string SEP(
"/");
96static std::vector<std::string> split(
const std::string& s,
97 const std::string& delim) {
98 std::vector<std::string> result;
99 size_t pos = s.find(delim);
100 if (pos == std::string::npos) {
104 result.push_back(s.substr(0, pos));
105 result.push_back(s.substr(pos + delim.length()));
109inline std::string basename(
const std::string path) {
110 wxFileName wxFile(path);
111 return wxFile.GetFullName().ToStdString();
114bool isRegularFile(
const char* path) {
115 wxFileName wxFile(path);
116 return wxFile.FileExists() && !wxFile.IsDir();
119static void mkdir(
const std::string path) {
120#if defined(_WIN32) && !defined(__MINGW32__)
121 _mkdir(path.c_str());
122#elif defined(__MINGW32__)
125 mkdir(path.c_str(), 0755);
129static std::vector<std::string> glob_dir(
const std::string& dir_path,
130 const std::string& pattern) {
131 std::vector<std::string> found;
134 auto match = dir.GetFirst(&s, pattern);
136 static const std::string SEP =
137 wxString(wxFileName::GetPathSeparator()).ToStdString();
138 found.push_back(dir_path + SEP + s.ToStdString());
139 match = dir.GetNext(&s);
148static ssize_t PlugInIxByName(
const std::string& name,
149 const ArrayOfPlugIns* plugins) {
150 const auto lc_name = ocpn::tolower(name);
151 for (
unsigned i = 0; i < plugins->GetCount(); i += 1) {
152 if (lc_name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
159static std::string pluginsConfigDir() {
161 pluginDataDir += SEP +
"plugins";
162 if (!ocpn::exists(pluginDataDir)) {
163 mkdir(pluginDataDir);
165 pluginDataDir += SEP +
"install_data";
166 if (!ocpn::exists(pluginDataDir)) {
167 mkdir(pluginDataDir);
169 return pluginDataDir;
172static std::string importsDir() {
173 auto path = pluginsConfigDir();
174 path = path + SEP +
"imports";
175 if (!ocpn::exists(path)) {
181static std::string dirListPath(std::string name) {
182 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
183 return pluginsConfigDir() + SEP + name +
".dirs";
187 return pluginsConfigDir();
190static std::vector<std::string> LoadLinesFromFile(
const std::string& path) {
191 std::vector<std::string> lines;
192 std::ifstream src(path);
195 src.getline(line,
sizeof(line));
196 lines.push_back(line);
202static std::string tmpfile_path() {
205 if (tmpnam(fname) == NULL) {
206 MESSAGE_LOG <<
"Cannot create temporary file";
209 return std::string(fname);
213static std::string tmpfile_path() {
215 fs::path tmp_path = fs::temp_directory_path() /
"ocpn-tmpXXXXXX";
217 strncpy(buff, tmp_path.c_str(), PATH_MAX - 1);
218 int fd = mkstemp(buff);
220 MESSAGE_LOG <<
"Cannot create temporary file: " << strerror(errno);
223 assert(close(fd) == 0 &&
"Cannot close file?!");
224 return std::string(buff);
232 m_abi = metadata.target;
233 m_abi_version = metadata.target_version;
234 m_major_version = ocpn::split(m_abi_version.c_str(),
".")[0];
235 m_name = metadata.name;
236 DEBUG_LOG <<
"Plugin: setting up, name: " << m_name;
237 DEBUG_LOG <<
"Plugin: init: abi: " << m_abi
238 <<
", abi_version: " << m_abi_version
239 <<
", major ver: " << m_major_version;
241 const std::string& abi()
const {
return m_abi; }
242 const std::string& abi_version()
const {
return m_abi_version; }
243 const std::string& major_version()
const {
return m_major_version; }
244 const std::string& name()
const {
return m_name; }
248 std::string m_abi_version;
249 std::string m_major_version;
257 m_abi = compatOs->name();
258 m_abi_version = compatOs->version();
259 m_major_version = ocpn::split(m_abi_version.c_str(),
".")[0];
260 DEBUG_LOG <<
"Host: init: abi: " << m_abi
261 <<
", abi_version: " << m_abi_version
262 <<
", major ver: " << m_major_version;
265 bool is_version_compatible(
const Plugin& plugin)
const {
266 if (ocpn::startswith(plugin.abi(),
"ubuntu")) {
267 return plugin.abi_version() == m_abi_version;
269 return plugin.major_version() == m_major_version;
274 bool is_debian_plugin_compatible(
const Plugin& plugin)
const {
275 if (!ocpn::startswith(m_abi,
"ubuntu"))
return false;
276 static const std::vector<std::string> compat_versions = {
278 "debian-x86_64;11;ubuntu-gtk3-x86_64;20.04",
279 "debian-wx32-x86_64;11;ubuntu-wx32-x86_64;22.04",
280 "debian-x86_64;12;ubuntu-x86_64;23.04",
281 "debian-x86_64;12;ubuntu-x86_64;23.10",
282 "debian-x86_64;12;ubuntu-x86_64;24.04",
283 "debian-x86_64;sid;ubuntu-x86_64;24.04",
285 "debian-arm64;11;ubuntu-gtk3-arm64;20.04",
286 "debian-wx32-arm64;11;ubuntu-wx32-arm64;22.04",
287 "debian-arm64;12;ubuntu-arm64;23.04",
288 "debian-arm64;12;ubuntu-arm64;23.10",
289 "debian-arm64;12;ubuntu-arm64;24.04",
290 "debian-arm64;sid;ubuntu-arm64;24.04",
292 "debian-armhf;10;ubuntu-armhf;18.04",
293 "debian-gtk3-armhf;10;ubuntu-gtk3-armhf;18.04",
294 "debian-armhf;11;ubuntu-gtk3-armhf;20.04",
295 "debian-wx32-armhf;11;ubuntu-wx32-armhf;22.04",
296 "debian-armhf;12;ubuntu-armhf;23.04",
297 "debian-armhf;12;ubuntu-armhf;23.10",
298 "debian-armhf;12;ubuntu-armhf;24.04",
299 "debian-armhf;sid;ubuntu-armhf;24.04"};
301 if (ocpn::startswith(plugin.abi(),
"debian")) {
302 DEBUG_LOG <<
"Checking for debian plugin on a ubuntu host";
303 const std::string compat_version = plugin.abi() +
";" +
304 plugin.major_version() +
";" + m_abi +
306 for (
auto& cv : compat_versions) {
307 if (compat_version == cv) {
315 const std::string& abi()
const {
return m_abi; }
317 const std::string& abi_version()
const {
return m_abi_version; }
319 const std::string& major_version()
const {
return m_major_version; }
323 std::string m_abi_version;
324 std::string m_major_version;
328 static std::string last_global_os(
"");
331 if (!instance || last_global_os != g_compatOS) {
333 last_global_os = g_compatOS;
338CompatOs::CompatOs() : _name(PKG_TARGET), _version(PKG_TARGET_VERSION) {
344 std::string compatOS(_name);
345 std::string compatOsVersion(_version);
347 if (getenv(
"OPENCPN_COMPAT_TARGET") != 0) {
348 _name = getenv(
"OPENCPN_COMPAT_TARGET");
349 if (_name.find(
':') != std::string::npos) {
350 auto tokens = ocpn::split(_name.c_str(),
":");
352 _version = tokens[1];
354 }
else if (g_compatOS !=
"") {
357 if (g_compatOsVersion !=
"") {
358 _version = g_compatOsVersion;
360 }
else if (ocpn::startswith(_name,
"ubuntu") && (_version ==
"22.04")) {
361 int wxv = wxMAJOR_VERSION * 10 + wxMINOR_VERSION;
363 auto tokens = ocpn::split(_name.c_str(),
"-");
364 _name = std::string(tokens[0]) + std::string(
"-wx32");
365 if (tokens.size() > 1) _name = _name + std::string(
"-") + tokens[1];
369 _name = ocpn::tolower(_name);
370 _version = ocpn::tolower(_version);
373PluginHandler::PluginHandler() {}
376 const char* os_version) {
380 if (plugin_api.major == -1) {
381 DEBUG_LOG <<
"Cannot parse API version \"" << metadata.api_version <<
"\"";
384 if (plugin_api < kMinApi || plugin_api > kMaxApi) {
385 DEBUG_LOG <<
"Incompatible API version \"" << metadata.api_version <<
"\"";
389 static const std::vector<std::string> simple_abis = {
390 "msvc",
"msvc-wx32",
"android-armhf",
"android-arm64"};
393 if (plugin.abi() ==
"all") {
394 DEBUG_LOG <<
"Returning true for plugin abi \"all\"";
397 auto compatOS = CompatOs::getInstance();
400 auto found = std::find(simple_abis.begin(), simple_abis.end(), plugin.abi());
401 if (found != simple_abis.end()) {
402 bool ok = plugin.abi() == host.abi();
403 DEBUG_LOG <<
"Returning " << (ok ?
"ok" :
"fail") <<
" for " << host.abi();
407 if (host.abi() == plugin.abi() && host.is_version_compatible(plugin)) {
409 DEBUG_LOG <<
"Found matching abi version " << plugin.abi_version();
410 }
else if (host.is_debian_plugin_compatible(plugin)) {
412 DEBUG_LOG <<
"Found Debian version matching Ubuntu host";
416 if (host.abi() ==
"darwin-wx32" && plugin.abi() ==
"darwin-wx32") {
418 auto found = metadata.target_arch.find(detail->osd_arch);
419 if (found != std::string::npos) {
423 DEBUG_LOG <<
"Plugin compatibility check Final: "
424 << (rv ?
"ACCEPTED: " :
"REJECTED: ") << metadata.name;
429 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
430 return pluginsConfigDir() + SEP + name +
".files";
434 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
435 return pluginsConfigDir() + SEP + name +
".version";
440 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
441 return importsDir() + SEP + name +
".xml";
444typedef std::unordered_map<std::string, std::string> pathmap_t;
450static pathmap_t getInstallPaths() {
462static void saveFilelist(std::string filelist, std::string name) {
465 ofstream diskfiles(listpath);
466 if (!diskfiles.is_open()) {
467 MESSAGE_LOG <<
"Cannot create installed files list.";
470 diskfiles << filelist;
473static void saveDirlist(std::string name) {
475 string path = dirListPath(name);
477 if (!dirs.is_open()) {
478 MESSAGE_LOG <<
"Cannot create installed files list.";
481 pathmap_t pathmap = getInstallPaths();
482 unordered_map<string, string>::iterator it;
483 for (it = pathmap.begin(); it != pathmap.end(); it++) {
484 dirs << it->first <<
": " << it->second << endl;
488static void saveVersion(
const std::string& name,
const std::string& version) {
491 ofstream stream(path);
492 if (!stream.is_open()) {
493 MESSAGE_LOG <<
"Cannot create version file.";
496 stream << version << endl;
499static int copy_data(
struct archive* ar,
struct archive* aw) {
506 r = archive_read_data_block(ar, &buff, &size, &offset);
507 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
508 if (r < ARCHIVE_OK) {
509 std::string s(archive_error_string(ar));
512 r = archive_write_data_block(aw, buff, size, offset);
513 if (r < ARCHIVE_OK) {
514 std::string s(archive_error_string(aw));
515 MESSAGE_LOG <<
"Error copying install data: " << archive_error_string(aw);
521static bool win_entry_set_install_path(
struct archive_entry* entry,
522 pathmap_t installPaths) {
525 string path = archive_entry_pathname(entry);
528 int slashes = count(path.begin(), path.end(),
'/');
530 archive_entry_set_pathname(entry,
"");
533 if (ocpn::startswith(path,
"./")) {
534 path = path.substr(1);
538 int slashpos = path.find_first_of(
'/', 1);
540 archive_entry_set_pathname(entry,
"");
544 string prefix = path.substr(0, slashpos);
545 path = path.substr(prefix.size() + 1);
548 if (ocpn::endswith(path,
".dll") || ocpn::endswith(path,
".exe")) {
549 slashpos = path.find_first_of(
'/');
550 path = path.substr(slashpos + 1);
551 path = installPaths[
"bin"] +
"\\" + path;
552 }
else if (ocpn::startswith(path,
"share")) {
554 wxFileName fn(installPaths[
"share"].c_str(),
557 path = fn.GetFullPath().ToStdString() + path;
558 }
else if (ocpn::startswith(path,
"plugins")) {
559 slashpos = path.find_first_of(
'/');
561 path = path.substr(slashpos + 1);
562 path = installPaths[
"share"] +
"\\" + path;
564 }
else if (archive_entry_filetype(entry) == AE_IFREG) {
565 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
566 msg += wxString(path.c_str());
571 s.Replace(
"/",
"\\");
572 s.Replace(
"\\\\",
"\\");
573 archive_entry_set_pathname(entry, s.c_str());
577static bool flatpak_entry_set_install_path(
struct archive_entry* entry,
578 pathmap_t installPaths) {
581 string path = archive_entry_pathname(entry);
582 int slashes = count(path.begin(), path.end(),
'/');
584 archive_entry_set_pathname(entry,
"");
587 if (ocpn::startswith(path,
"./")) {
588 path = path.substr(2);
590 int slashpos = path.find_first_of(
'/', 1);
591 string prefix = path.substr(0, slashpos);
592 path = path.substr(prefix.size() + 1);
593 slashpos = path.find_first_of(
'/');
594 string location = path.substr(0, slashpos);
595 string suffix = path.substr(slashpos + 1);
596 if (installPaths.find(location) == installPaths.end() &&
597 archive_entry_filetype(entry) == AE_IFREG) {
598 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
599 msg += wxString(path.c_str());
603 string dest = installPaths[location] +
"/" + suffix;
604 archive_entry_set_pathname(entry, dest.c_str());
609static bool linux_entry_set_install_path(
struct archive_entry* entry,
610 pathmap_t installPaths) {
613 string path = archive_entry_pathname(entry);
614 int slashes = count(path.begin(), path.end(),
'/');
616 archive_entry_set_pathname(entry,
"");
620 int slashpos = path.find_first_of(
'/', 1);
621 if (ocpn::startswith(path,
"./"))
622 slashpos = path.find_first_of(
'/', 2);
624 string prefix = path.substr(0, slashpos);
625 path = path.substr(prefix.size() + 1);
626 if (ocpn::startswith(path,
"usr/")) {
627 path = path.substr(strlen(
"usr/"));
629 if (ocpn::startswith(path,
"local/")) {
630 path = path.substr(strlen(
"local/"));
632 slashpos = path.find_first_of(
'/');
633 string location = path.substr(0, slashpos);
634 string suffix = path.substr(slashpos + 1);
635 if (installPaths.find(location) == installPaths.end() &&
636 archive_entry_filetype(entry) == AE_IFREG) {
637 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
638 msg += wxString(path.c_str());
643 string dest = installPaths[location] +
"/" + suffix;
647 if (ocpn::startswith(location,
"share") &&
648 ocpn::startswith(suffix,
"opencpn/plugins/")) {
649 slashpos = suffix.find_first_of(
"opencpn/plugins/");
650 suffix = suffix.substr(16);
655 if (ocpn::startswith(location,
"lib") &&
656 ocpn::startswith(suffix,
"opencpn/")) {
657 suffix = suffix.substr(8);
660 "/plugins/lib/" + suffix;
664 archive_entry_set_pathname(entry, dest.c_str());
668static bool apple_entry_set_install_path(
struct archive_entry* entry,
669 pathmap_t installPaths) {
673 "/Library/Application Support/OpenCPN";
675 string path = archive_entry_pathname(entry);
676 if (ocpn::startswith(path,
"./")) path = path.substr(2);
679 size_t slashes = count(path.begin(), path.end(),
'/');
681 archive_entry_set_pathname(entry,
"");
684 auto parts = split(path,
"Contents/Resources");
685 if (parts.size() >= 2) {
686 dest = base +
"/Contents/Resources" + parts[1];
689 parts = split(path,
"Contents/SharedSupport");
690 if (parts.size() >= 2) {
691 dest = base +
"/Contents/SharedSupport" + parts[1];
695 parts = split(path,
"Contents/PlugIns");
696 if (parts.size() >= 2) {
697 dest = base +
"/Contents/PlugIns" + parts[1];
700 if (dest ==
"" && archive_entry_filetype(entry) == AE_IFREG) {
701 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
702 msg += wxString(path.c_str());
706 archive_entry_set_pathname(entry, dest.c_str());
710static bool android_entry_set_install_path(
struct archive_entry* entry,
711 pathmap_t installPaths) {
714 string path = archive_entry_pathname(entry);
715 int slashes = count(path.begin(), path.end(),
'/');
717 archive_entry_set_pathname(entry,
"");
722 int slashpos = path.find_first_of(
'/', 1);
723 if (ocpn::startswith(path,
"./"))
724 slashpos = path.find_first_of(
'/', 2);
726 string prefix = path.substr(0, slashpos);
727 path = path.substr(prefix.size() + 1);
728 if (ocpn::startswith(path,
"usr/")) {
729 path = path.substr(strlen(
"usr/"));
731 if (ocpn::startswith(path,
"local/")) {
732 path = path.substr(strlen(
"local/"));
734 slashpos = path.find_first_of(
'/');
735 string location = path.substr(0, slashpos);
736 string suffix = path.substr(slashpos + 1);
737 if (installPaths.find(location) == installPaths.end() &&
738 archive_entry_filetype(entry) == AE_IFREG) {
739 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
740 msg += wxString(path.c_str());
745 if ((location ==
"lib") && ocpn::startswith(suffix,
"opencpn")) {
746 auto parts = split(suffix,
"/");
747 if (parts.size() == 2) suffix = parts[1];
750 if ((location ==
"share") && ocpn::startswith(suffix,
"opencpn")) {
751 auto parts = split(suffix,
"opencpn/");
752 if (parts.size() == 2) suffix = parts[1];
756 string dest = installPaths[location] +
"/" + suffix;
758 archive_entry_set_pathname(entry, dest.c_str());
762static bool entry_set_install_path(
struct archive_entry* entry,
763 pathmap_t installPaths) {
764 const std::string src = archive_entry_pathname(entry);
766#ifdef __OCPN__ANDROID__
767 rv = android_entry_set_install_path(entry, installPaths);
769 const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
770 if (g_BasePlatform->isFlatpacked()) {
771 rv = flatpak_entry_set_install_path(entry, installPaths);
772 }
else if (osSystemId & wxOS_UNIX_LINUX) {
773 rv = linux_entry_set_install_path(entry, installPaths);
774 }
else if (osSystemId & wxOS_WINDOWS) {
775 rv = win_entry_set_install_path(entry, installPaths);
776 }
else if (osSystemId & wxOS_MAC) {
777 rv = apple_entry_set_install_path(entry, installPaths);
779 MESSAGE_LOG <<
"set_install_path() invoked, unsupported platform "
780 << wxPlatformInfo::Get().GetOperatingSystemDescription();
784 const std::string dest = archive_entry_pathname(entry);
787 DEBUG_LOG <<
"Installing " << src <<
" into " << dest << std::endl;
793bool PluginHandler::archive_check(
int r,
const char* msg,
struct archive* a) {
794 if (r < ARCHIVE_OK) {
797 if (archive_error_string(a)) s = s +
": " + archive_error_string(a);
801 return r >= ARCHIVE_WARN;
804bool PluginHandler::explodeTarball(
struct archive* src,
struct archive* dest,
805 std::string& filelist,
806 const std::string& metadata_path,
807 bool only_metadata) {
808 struct archive_entry* entry = 0;
809 pathmap_t pathmap = getInstallPaths();
810 bool is_metadata_ok =
false;
812 int r = archive_read_next_header(src, &entry);
813 if (r == ARCHIVE_EOF) {
814 if (!is_metadata_ok) {
815 MESSAGE_LOG <<
"Plugin tarball does not contain metadata.xml";
817 return is_metadata_ok;
819 if (!archive_check(r,
"archive read header error", src)) {
822 std::string path = archive_entry_pathname(entry);
823 bool is_metadata = std::string::npos != path.find(
"metadata.xml");
825 is_metadata_ok =
true;
826 if (metadata_path ==
"") {
829 archive_entry_set_pathname(entry, metadata_path.c_str());
830 DEBUG_LOG <<
"Extracted metadata.xml to " << metadata_path;
832 }
else if (!entry_set_install_path(entry, pathmap))
834 if (strlen(archive_entry_pathname(entry)) == 0) {
837 if (!is_metadata && only_metadata) {
841 filelist.append(std::string(archive_entry_pathname(entry)) +
"\n");
843 r = archive_write_header(dest, entry);
844 archive_check(r,
"archive write install header error", dest);
845 if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
846 r = copy_data(src, dest);
847 if (!archive_check(r,
"archive copy data error", dest)) {
851 r = archive_write_finish_entry(dest);
852 if (!archive_check(r,
"archive finish write error", dest)) {
888bool PluginHandler::extractTarball(
const std::string path,
889 std::string& filelist,
890 const std::string metadata_path,
891 bool only_metadata) {
892 struct archive* src = archive_read_new();
893 archive_read_support_filter_gzip(src);
894 archive_read_support_format_tar(src);
895 int r = archive_read_open_filename(src, path.c_str(), 10240);
896 if (r != ARCHIVE_OK) {
897 std::ostringstream os;
898 os <<
"Cannot read installation tarball: " << path;
899 MESSAGE_LOG << os.str();
900 last_error_msg = os.str();
903 struct archive* dest = archive_write_disk_new();
904 archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
905 bool ok = explodeTarball(src, dest, filelist, metadata_path, only_metadata);
906 archive_read_free(src);
907 archive_write_free(dest);
923 auto loader = PluginLoader::getInstance();
924 return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
927static std::string computeMetadataPath(
void) {
930 path +=
"ocpn-plugins.xml";
931 if (ocpn::exists(path)) {
945 path = g_BasePlatform->GetSharedDataDir();
947 path +=
"ocpn-plugins.xml";
948 if (!ocpn::exists(path)) {
949 MESSAGE_LOG <<
"Non-existing plugins file: " << path;
954static void parseMetadata(
const std::string path,
CatalogCtx& ctx) {
957 MESSAGE_LOG <<
"PluginHandler: using metadata path: " << path;
959 if (!ocpn::exists(path)) {
960 MESSAGE_LOG <<
"Non-existing plugins metadata file: " << path;
963 ifstream ifpath(path);
964 std::string xml((istreambuf_iterator<char>(ifpath)),
965 istreambuf_iterator<char>());
966 ParseCatalog(xml, &ctx);
969bool PluginHandler::InstallPlugin(
const std::string& path,
970 std::string& filelist,
971 const std::string metadata_path,
972 bool only_metadata) {
973 if (!extractTarball(path, filelist, metadata_path, only_metadata)) {
974 std::ostringstream os;
975 os <<
"Cannot unpack plugin tarball at : " << path;
976 MESSAGE_LOG << os.str();
977 if (filelist !=
"")
cleanup(filelist,
"unknown_name");
978 last_error_msg = os.str();
985 std::ifstream istream(metadata_path);
986 std::stringstream buff;
987 buff << istream.rdbuf();
989 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
990 ParseCatalog(xml, &ctx);
991 auto name = ctx.plugins[0].name;
992 auto version = ctx.plugins[0].version;
993 saveFilelist(filelist, name);
995 saveVersion(name, version);
1001 if (metadataPath.size() > 0) {
1002 return metadataPath;
1004 metadataPath = computeMetadataPath();
1005 DEBUG_LOG <<
"Using metadata path: " << metadataPath;
1006 return metadataPath;
1012 plugins.insert(plugins.end(), a.begin(), a.end());
1013 std::map<std::string, int> count_by_target;
1014 for (
const auto& p : plugins) {
1015 if (p.target ==
"") {
1018 auto key = p.target +
":" + p.target_version;
1019 if (count_by_target.find(key) == count_by_target.end()) {
1020 count_by_target[key] = 1;
1022 count_by_target[key] += 1;
1025 return count_by_target;
1029 return glob_dir(importsDir(),
"*.xml");
1032void PluginHandler::cleanupFiles(
const std::string& manifestFile,
1033 const std::string& plugname) {
1034 std::ifstream diskfiles(manifestFile);
1035 if (diskfiles.is_open()) {
1036 std::stringstream buffer;
1037 buffer << diskfiles.rdbuf();
1043static void PurgeEmptyDirs(
const std::string& root) {
1044 if (!wxFileName::IsDirWritable(root))
return;
1045 if (ocpn::tolower(root).find(
"opencpn") == std::string::npos)
return;
1046 wxDir rootdir(root);
1047 if (!rootdir.IsOpened())
return;
1049 bool cont = rootdir.GetFirst(&dirname,
"", wxDIR_DIRS);
1051 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1052 cont = rootdir.GetNext(&dirname);
1056 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1057 wxFileName::Rmdir(rootdir.GetName());
1062 const std::string& plugname) {
1063 MESSAGE_LOG <<
"Cleaning up failed install of " << plugname;
1065 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1066 for (
const auto& path : paths) {
1067 if (isRegularFile(path.c_str())) {
1068 int r = remove(path.c_str());
1070 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1074 for (
const auto& path : paths) PurgeEmptyDirs(path);
1077 if (ocpn::exists(path)) remove(path.c_str());
1080 remove(dirListPath(plugname).c_str());
1090 struct metadata_compare {
1093 return lhs.key() < rhs.key();
1097 std::vector<PluginMetadata> returnArray;
1099 std::set<PluginMetadata, metadata_compare> unique_plugins;
1101 unique_plugins.insert(plugin);
1103 for (
const auto& plugin : unique_plugins) {
1105 returnArray.push_back(plugin);
1112 using namespace std;
1115 auto catalogHandler = CatalogHandler::getInstance();
1117 ctx = catalogHandler->GetActiveCatalogContext();
1118 auto status = catalogHandler->GetCatalogStatus();
1120 if (status == CatalogHandler::ServerStatus::OK) {
1121 catalogData.undef =
false;
1122 catalogData.version = ctx->version;
1123 catalogData.date = ctx->date;
1125 return ctx->plugins;
1129 std::vector<std::string> names;
1131 for (
const auto& entry : fs::directory_iterator(dirpath)) {
1132 const std::string name(entry.path().filename().string());
1133 if (ocpn::endswith(name,
".files"))
1134 names.push_back(ocpn::split(name.c_str(),
".")[0]);
1140 using namespace std;
1141 vector<PluginMetadata> plugins;
1143 auto loader = PluginLoader::getInstance();
1144 for (
unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1150 std::stringstream ss;
1151 ss << p->m_version_major <<
"." << p->m_version_minor;
1152 plugin.version = ss.str();
1155 if (path !=
"" && wxFileName::IsFileReadable(path)) {
1156 std::ifstream stream;
1157 stream.open(path, ifstream::in);
1158 stream >> plugin.version;
1160 plugins.push_back(plugin);
1166 auto loader = PluginLoader::getInstance();
1167 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1168 if (ix == -1)
return;
1170 auto plugins = *loader->GetPlugInArray();
1171 plugins[ix]->m_managed_metadata = pm;
1175 std::string filelist;
1176 if (!extractTarball(path, filelist)) {
1177 std::ostringstream os;
1178 os <<
"Cannot unpack plugin: " << plugin.name <<
" at " << path;
1179 MESSAGE_LOG << os.str();
1180 last_error_msg = os.str();
1184 saveFilelist(filelist, plugin.name);
1185 saveDirlist(plugin.name);
1186 saveVersion(plugin.name, plugin.version);
1192 std::string path = tmpfile_path();
1194 MESSAGE_LOG <<
"Cannot create temporary file";
1198 std::ofstream stream;
1199 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1200 DEBUG_LOG <<
"Downloading: " << plugin.name << std::endl;
1201 auto downloader =
Downloader(plugin.tarball_url);
1202 downloader.download(&stream);
1210 MESSAGE_LOG <<
"Cannot extract metadata from tarball";
1218 std::string filelist;
1219 std::string temp_path = tmpfile_path();
1220 if (!extractTarball(path, filelist, temp_path,
true)) {
1221 std::ostringstream os;
1222 os <<
"Cannot unpack plugin " << metadata.name <<
" tarball at: " << path;
1223 MESSAGE_LOG << os.str();
1224 if (filelist !=
"")
cleanup(filelist,
"unknown_name");
1225 last_error_msg = os.str();
1228 if (!isRegularFile(temp_path.c_str())) {
1235 std::ifstream istream(temp_path);
1236 std::stringstream buff;
1237 buff << istream.rdbuf();
1238 int r = remove(temp_path.c_str());
1240 MESSAGE_LOG <<
"Cannot remove file " << temp_path <<
":" << strerror(r);
1242 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1243 ParseCatalog(xml, &ctx);
1244 metadata = ctx.plugins[0];
1245 if (metadata.name.empty()) {
1246 MESSAGE_LOG <<
"Plugin metadata is empty";
1248 return !metadata.name.empty();
1252 auto ix = PlugInIxByName(plugin_name,
1253 PluginLoader::getInstance()->GetPlugInArray());
1255 MESSAGE_LOG <<
"Attempt to remove installation data for loaded plugin";
1258 return DoClearInstallData(plugin_name);
1261bool PluginHandler::DoClearInstallData(
const std::string plugin_name) {
1263 if (!ocpn::exists(path)) {
1264 MESSAGE_LOG <<
"Cannot find installation data for " << plugin_name <<
" ("
1268 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1269 for (
const auto& p : plug_paths) {
1270 if (isRegularFile(p.c_str())) {
1271 int r = remove(p.c_str());
1273 MESSAGE_LOG <<
"Cannot remove file " << p <<
": " << strerror(r);
1277 for (
const auto& p : plug_paths) PurgeEmptyDirs(p);
1278 int r = remove(path.c_str());
1280 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1283 remove(dirListPath(plugin_name).c_str());
1290 using namespace std;
1292 auto loader = PluginLoader::getInstance();
1293 auto ix = PlugInIxByName(plugin_name, loader->GetPlugInArray());
1295 MESSAGE_LOG <<
"trying to uninstall non-existing plugin " << plugin_name;
1298 auto pic = loader->GetPlugInArray()->Item(ix);
1301 string libfile = pic->m_plugin_file.ToStdString();
1302 loader->UnLoadPlugIn(ix);
1304 bool ok = DoClearInstallData(plugin_name);
1309 if (isRegularFile(libfile.c_str())) {
1310 remove(libfile.c_str());
1316using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1323static std::string FindMatchingDataDir(std::regex name_re) {
1324 using namespace std;
1326 wxStringTokenizer tokens(data_dirs,
";");
1327 while (tokens.HasMoreTokens()) {
1328 auto token = tokens.GetNextToken();
1329 wxFileName path(token);
1330 wxDir dir(path.GetFullPath());
1331 if (dir.IsOpened()) {
1333 bool cont = dir.GetFirst(&filename,
"", wxDIR_DIRS);
1337 if (regex_search(s, sm, name_re)) {
1339 for (
auto c : sm) ss << c;
1342 cont = dir.GetNext(&filename);
1353static std::string FindMatchingLibFile(std::regex name_re) {
1354 using namespace std;
1355 for (
const auto& lib :
PluginPaths::getInstance()->Libdirs()) {
1358 bool cont = dir.GetFirst(&filename,
"", wxDIR_FILES);
1362 if (regex_search(s, sm, name_re)) {
1364 for (
auto c : sm) ss << c;
1367 cont = dir.GetNext(&filename);
1374static std::string PluginNameCase(
const std::string& name) {
1375 using namespace std;
1376 const string lc_name = ocpn::tolower(name);
1377 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1382 for (
const auto& plugin :
PluginHandler::getInstance()->getInstalled()) {
1383 if (ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1385 for (
const auto& plugin :
PluginHandler::getInstance()->getAvailable()) {
1386 if (ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1389 string match = FindMatchingDataDir(name_re);
1390 if (match !=
"")
return match;
1392 match = FindMatchingLibFile(name_re);
1393 return match !=
"" ? match : name;
1397static void LoadPluginMapFile(PluginMap& map,
const std::string& path) {
1401 MESSAGE_LOG <<
"Cannot open " << path <<
": " << strerror(errno);
1404 std::stringstream buf;
1406 auto filelist = ocpn::split(buf.str().c_str(),
"\n");
1407 for (
auto& file : filelist) {
1408 file = wxFileName(file).GetFullName().ToStdString();
1412 auto key = wxFileName(path).GetFullName().ToStdString();
1413 key = ocpn::split(key.c_str(),
".")[0];
1414 key = PluginNameCase(key);
1415 map[key] = filelist;
1419static void LoadPluginMap(PluginMap& map) {
1422 if (!root.IsOpened())
return;
1424 bool cont = root.GetFirst(&filename,
"*.files", wxDIR_FILES);
1426 auto path = root.GetNameWithSep() + filename;
1427 LoadPluginMapFile(map, path.ToStdString());
1428 cont = root.GetNext(&filename);
1433 auto basename = wxFileName(filename).GetFullName().ToStdString();
1434 if (files_by_plugin.size() == 0) LoadPluginMap(files_by_plugin);
1435 for (
const auto& it : files_by_plugin) {
1436 auto found = std::find(it.second.begin(), it.second.end(), basename);
1437 if (found != it.second.end())
return it.first;
1444 wxURI uri(wxString(plugin.tarball_url.c_str()));
1445 wxFileName fn(uri.GetPath());
1446 wxString tarballFile = fn.GetFullName();
1453 if (cacheFile ==
"") {
1455 wxFileName fn1(fn.GetFullName());
1456 if (fn1.GetExt().IsSameAs(
"tar")) {
1457 tarballFile = fn.GetFullName();
1463 if (cacheFile !=
"") {
1464 MESSAGE_LOG <<
"Installing " << tarballFile <<
" from local cache";
1467 evt_download_failed.
Notify(cacheFile);
1470 evt_download_ok.
Notify(plugin.name +
" " + plugin.version);
Plugin catalog management: Build the runtime catalog, handling downloads as required.
Datatypes and methods to parse ocpn-plugins.xml XML data, either complete catalog or a single plugin.
Handle downloading of files from remote urls.
const void Notify()
Notify all listeners, no data supplied.
Host ABI encapsulation and plugin compatibility checks.
Data for a loaded plugin, including dl-loaded library.
wxString m_common_name
A common name string for the plugin.
const std::vector< PluginMetadata > getInstalled()
Return list of all installed and loaded plugins.
static void cleanup(const std::string &filelist, const std::string &plugname)
Cleanup failed installation attempt using filelist for plugin.
static std::vector< std::string > GetImportPaths()
List of paths for imported plugins metadata.
bool installPluginFromCache(PluginMetadata plugin)
Install plugin tarball from local cache.
const std::map< std::string, int > getCountByTarget()
Map of available plugin targets -> number of occurences.
std::vector< PluginMetadata > getCompatiblePlugins()
Return list of available, unique and compatible plugins from configured XML catalog.
static std::string ImportedMetadataPath(std::string name)
Return path to imported metadata for given plugin.
std::string getPluginByLibrary(const std::string &filename)
Return plugin containing given filename or "" if not found.
bool isPluginWritable(std::string name)
Check if given plugin can be installed/updated.
static std::string pluginsInstallDataPath()
Return base directory for installation data.
std::vector< std::string > GetInstalldataPlugins()
Return list of installed plugins lower case names, not necessarily loaded.
static std::string fileListPath(std::string name)
Return path to installation manifest for given plugin.
static std::string versionPath(std::string name)
Return path to file containing version for given plugin.
bool uninstall(const std::string plugin)
Uninstall an installed and loaded plugin.
bool installPlugin(PluginMetadata plugin)
Download and install a new, not installed plugin.
bool ClearInstallData(const std::string plugin_name)
Remove installation data for not loaded plugin.
std::string getMetadataPath()
Return path to metadata XML file.
void SetInstalledMetadata(const PluginMetadata &pm)
Set metadata for an installed plugin.
bool ExtractMetadata(const std::string &path, PluginMetadata &metadata)
Extract metadata in given tarball path.
const std::vector< PluginMetadata > getAvailable()
Update catalog and return list of available, not installed plugins.
static bool isCompatible(const PluginMetadata &metadata, const char *os=PKG_TARGET, const char *os_version=PKG_TARGET_VERSION)
Return true if given plugin is loadable on given os/version.
std::string UserLibdir()
The single, user-writable directory for installing .dll files.
std::string Homedir() const
home directory, convenience stuff.
std::string UserDatadir()
The single, user-writable common parent for plugin data directories, typically ending in 'plugins'.
std::string UserBindir()
The single, user-writable directory for installing helper binaries.
static PluginPaths * getInstance()
Return the singleton instance.
Plugin ABI encapsulation.
Global variables reflecting command line options and arguments.
Enhanced logging interface on top of wx/log.h.
std::string lookup_tarball(const char *uri)
Get path to tarball in cache for given filename.
std::string lookup_metadata(const char *name)
Get metadata path for a given name defaulting to ocpn-plugins.xml)
The result from parsing the xml catalog i.
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.