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"
80#include "model/plugin_cache.h"
81#include "model/plugin_handler.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) {
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";
163 mkdir(pluginDataDir);
165 pluginDataDir += SEP +
"install_data";
167 mkdir(pluginDataDir);
169 return pluginDataDir;
172static std::string importsDir() {
173 auto path = pluginsConfigDir();
174 path = path + SEP +
"imports";
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 {
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 {
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"};
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) {
352 _version = tokens[1];
354 }
else if (g_compatOS !=
"") {
357 if (g_compatOsVersion !=
"") {
358 _version = g_compatOsVersion;
361 int wxv = wxMAJOR_VERSION * 10 + wxMINOR_VERSION;
364 _name = std::string(tokens[0]) + std::string(
"-wx32");
365 if (tokens.size() > 1) _name = _name + std::string(
"-") + tokens[1];
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);
526 bool is_library =
false;
529 int slashes = count(path.begin(), path.end(),
'/');
531 archive_entry_set_pathname(entry,
"");
535 path = path.substr(1);
539 int slashpos = path.find_first_of(
'/', 1);
541 archive_entry_set_pathname(entry,
"");
545 string prefix = path.substr(0, slashpos);
546 path = path.substr(prefix.size() + 1);
550 slashpos = path.find_first_of(
'/');
551 path = path.substr(slashpos + 1);
552 path = installPaths[
"bin"] +
"\\" + path;
556 wxFileName fn(installPaths[
"share"].c_str(),
559 path = fn.GetFullPath().ToStdString() + path;
561 slashpos = path.find_first_of(
'/');
563 path = path.substr(slashpos + 1);
564 path = installPaths[
"share"] +
"\\" + path;
566 }
else if (archive_entry_filetype(entry) == AE_IFREG) {
567 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
568 msg += wxString(path.c_str());
577 s.Replace(
"/",
"\\");
578 s.Replace(
"\\\\",
"\\");
579 archive_entry_set_pathname(entry, s.c_str());
583static bool flatpak_entry_set_install_path(
struct archive_entry* entry,
584 pathmap_t installPaths) {
587 string path = archive_entry_pathname(entry);
588 int slashes = count(path.begin(), path.end(),
'/');
590 archive_entry_set_pathname(entry,
"");
594 path = path.substr(2);
596 int slashpos = path.find_first_of(
'/', 1);
597 string prefix = path.substr(0, slashpos);
598 path = path.substr(prefix.size() + 1);
599 slashpos = path.find_first_of(
'/');
600 string location = path.substr(0, slashpos);
601 string suffix = path.substr(slashpos + 1);
602 if (installPaths.find(location) == installPaths.end() &&
603 archive_entry_filetype(entry) == AE_IFREG) {
604 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
605 msg += wxString(path.c_str());
609 string dest = installPaths[location] +
"/" + suffix;
610 archive_entry_set_pathname(entry, dest.c_str());
613 if (dest.find(paths->
UserLibdir()) != std::string::npos) {
621static bool linux_entry_set_install_path(
struct archive_entry* entry,
622 pathmap_t installPaths) {
625 string path = archive_entry_pathname(entry);
626 int slashes = count(path.begin(), path.end(),
'/');
628 archive_entry_set_pathname(entry,
"");
632 int slashpos = path.find_first_of(
'/', 1);
634 slashpos = path.find_first_of(
'/', 2);
636 string prefix = path.substr(0, slashpos);
637 path = path.substr(prefix.size() + 1);
639 path = path.substr(strlen(
"usr/"));
642 path = path.substr(strlen(
"local/"));
644 slashpos = path.find_first_of(
'/');
645 string location = path.substr(0, slashpos);
646 string suffix = path.substr(slashpos + 1);
647 if (installPaths.find(location) == installPaths.end() &&
648 archive_entry_filetype(entry) == AE_IFREG) {
649 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
650 msg += wxString(path.c_str());
655 bool is_library =
false;
656 string dest = installPaths[location] +
"/" + suffix;
662 slashpos = suffix.find_first_of(
"opencpn/plugins/");
663 suffix = suffix.substr(16);
670 suffix = suffix.substr(8);
672 "/plugins/lib/" + suffix;
683 wxFileName nm(suffix);
687 archive_entry_set_pathname(entry, dest.c_str());
691static bool apple_entry_set_install_path(
struct archive_entry* entry,
692 pathmap_t installPaths) {
696 "/Library/Application Support/OpenCPN";
698 string path = archive_entry_pathname(entry);
700 bool is_library =
false;
703 size_t slashes = count(path.begin(), path.end(),
'/');
705 archive_entry_set_pathname(entry,
"");
708 auto parts =
split(path,
"Contents/Resources");
709 if (parts.size() >= 2) {
710 dest = base +
"/Contents/Resources" + parts[1];
713 parts =
split(path,
"Contents/SharedSupport");
714 if (parts.size() >= 2) {
715 dest = base +
"/Contents/SharedSupport" + parts[1];
719 parts =
split(path,
"Contents/PlugIns");
720 if (parts.size() >= 2) {
721 dest = base +
"/Contents/PlugIns" + parts[1];
725 if (dest ==
"" && archive_entry_filetype(entry) == AE_IFREG) {
726 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
727 msg += wxString(path.c_str());
731 archive_entry_set_pathname(entry, dest.c_str());
740static bool android_entry_set_install_path(
struct archive_entry* entry,
741 pathmap_t installPaths) {
744 bool is_library =
false;
745 string path = archive_entry_pathname(entry);
746 int slashes = count(path.begin(), path.end(),
'/');
748 archive_entry_set_pathname(entry,
"");
753 int slashpos = path.find_first_of(
'/', 1);
755 slashpos = path.find_first_of(
'/', 2);
757 string prefix = path.substr(0, slashpos);
758 path = path.substr(prefix.size() + 1);
760 path = path.substr(strlen(
"usr/"));
763 path = path.substr(strlen(
"local/"));
765 slashpos = path.find_first_of(
'/');
766 string location = path.substr(0, slashpos);
767 string suffix = path.substr(slashpos + 1);
768 if (installPaths.find(location) == installPaths.end() &&
769 archive_entry_filetype(entry) == AE_IFREG) {
770 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
771 msg += wxString(path.c_str());
777 auto parts =
split(suffix,
"/");
778 if (parts.size() == 2) suffix = parts[1];
783 auto parts =
split(suffix,
"opencpn/");
784 if (parts.size() == 2) suffix = parts[1];
788 string dest = installPaths[location] +
"/" + suffix;
790 archive_entry_set_pathname(entry, dest.c_str());
792 wxFileName nm(suffix);
798static bool entry_set_install_path(
struct archive_entry* entry,
799 pathmap_t installPaths) {
800 const std::string src = archive_entry_pathname(entry);
802#ifdef __OCPN__ANDROID__
803 rv = android_entry_set_install_path(entry, installPaths);
805 const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
806 if (g_BasePlatform->isFlatpacked()) {
807 rv = flatpak_entry_set_install_path(entry, installPaths);
808 }
else if (osSystemId & wxOS_UNIX_LINUX) {
809 rv = linux_entry_set_install_path(entry, installPaths);
810 }
else if (osSystemId & wxOS_WINDOWS) {
811 rv = win_entry_set_install_path(entry, installPaths);
812 }
else if (osSystemId & wxOS_MAC) {
813 rv = apple_entry_set_install_path(entry, installPaths);
815 MESSAGE_LOG <<
"set_install_path() invoked, unsupported platform "
816 << wxPlatformInfo::Get().GetOperatingSystemDescription();
820 const std::string dest = archive_entry_pathname(entry);
823 DEBUG_LOG <<
"Installing " << src <<
" into " << dest << std::endl;
829bool PluginHandler::archive_check(
int r,
const char* msg,
struct archive* a) {
830 if (r < ARCHIVE_OK) {
833 if (archive_error_string(a)) s = s +
": " + archive_error_string(a);
837 return r >= ARCHIVE_WARN;
840bool PluginHandler::explodeTarball(
struct archive* src,
struct archive* dest,
841 std::string& filelist,
842 const std::string& metadata_path,
843 bool only_metadata) {
844 struct archive_entry* entry = 0;
845 pathmap_t pathmap = getInstallPaths();
846 bool is_metadata_ok =
false;
848 int r = archive_read_next_header(src, &entry);
849 if (r == ARCHIVE_EOF) {
850 if (!is_metadata_ok) {
851 MESSAGE_LOG <<
"Plugin tarball does not contain metadata.xml";
853 return is_metadata_ok;
855 if (!archive_check(r,
"archive read header error", src)) {
858 std::string path = archive_entry_pathname(entry);
859 bool is_metadata = std::string::npos != path.find(
"metadata.xml");
861 is_metadata_ok =
true;
862 if (metadata_path ==
"") {
865 archive_entry_set_pathname(entry, metadata_path.c_str());
866 DEBUG_LOG <<
"Extracted metadata.xml to " << metadata_path;
868 }
else if (!entry_set_install_path(entry, pathmap))
870 if (strlen(archive_entry_pathname(entry)) == 0) {
873 if (!is_metadata && only_metadata) {
877 filelist.append(std::string(archive_entry_pathname(entry)) +
"\n");
879 r = archive_write_header(dest, entry);
880 archive_check(r,
"archive write install header error", dest);
881 if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
882 r = copy_data(src, dest);
883 if (!archive_check(r,
"archive copy data error", dest)) {
887 r = archive_write_finish_entry(dest);
888 if (!archive_check(r,
"archive finish write error", dest)) {
924bool PluginHandler::extractTarball(
const std::string path,
925 std::string& filelist,
926 const std::string metadata_path,
927 bool only_metadata) {
928 struct archive* src = archive_read_new();
929 archive_read_support_filter_gzip(src);
930 archive_read_support_format_tar(src);
931 int r = archive_read_open_filename(src, path.c_str(), 10240);
932 if (r != ARCHIVE_OK) {
933 std::ostringstream os;
934 os <<
"Cannot read installation tarball: " << path;
935 MESSAGE_LOG << os.str();
936 last_error_msg = os.str();
939 struct archive* dest = archive_write_disk_new();
940 archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
941 bool ok = explodeTarball(src, dest, filelist, metadata_path, only_metadata);
942 archive_read_free(src);
943 archive_write_free(dest);
959 auto loader = PluginLoader::GetInstance();
960 return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
963static std::string computeMetadataPath(
void) {
966 path +=
"ocpn-plugins.xml";
981 path = g_BasePlatform->GetSharedDataDir();
983 path +=
"ocpn-plugins.xml";
985 MESSAGE_LOG <<
"Non-existing plugins file: " << path;
990static void parseMetadata(
const std::string path,
CatalogCtx& ctx) {
993 MESSAGE_LOG <<
"PluginHandler: using metadata path: " << path;
996 MESSAGE_LOG <<
"Non-existing plugins metadata file: " << path;
999 ifstream ifpath(path);
1000 std::string xml((istreambuf_iterator<char>(ifpath)),
1001 istreambuf_iterator<char>());
1002 ParseCatalog(xml, &ctx);
1005bool PluginHandler::InstallPlugin(
const std::string& path,
1006 std::string& filelist,
1007 const std::string metadata_path,
1008 bool only_metadata) {
1009 if (!extractTarball(path, filelist, metadata_path, only_metadata)) {
1010 std::ostringstream os;
1011 os <<
"Cannot unpack plugin tarball at : " << path;
1012 MESSAGE_LOG << os.str();
1013 if (filelist !=
"")
cleanup(filelist,
"unknown_name");
1014 last_error_msg = os.str();
1017 if (only_metadata) {
1021 std::ifstream istream(metadata_path);
1022 std::stringstream buff;
1023 buff << istream.rdbuf();
1025 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1026 ParseCatalog(xml, &ctx);
1027 auto name = ctx.plugins[0].name;
1028 auto version = ctx.plugins[0].version;
1029 saveFilelist(filelist, name);
1031 saveVersion(name, version);
1037 if (metadataPath.size() > 0) {
1038 return metadataPath;
1040 metadataPath = computeMetadataPath();
1041 DEBUG_LOG <<
"Using metadata path: " << metadataPath;
1042 return metadataPath;
1048 plugins.insert(plugins.end(), a.begin(), a.end());
1049 std::map<std::string, int> count_by_target;
1050 for (
const auto& p : plugins) {
1051 if (p.target ==
"") {
1054 auto key = p.target +
":" + p.target_version;
1055 if (count_by_target.find(key) == count_by_target.end()) {
1056 count_by_target[key] = 1;
1058 count_by_target[key] += 1;
1061 return count_by_target;
1065 return glob_dir(importsDir(),
"*.xml");
1068void PluginHandler::cleanupFiles(
const std::string& manifestFile,
1069 const std::string& plugname) {
1070 std::ifstream diskfiles(manifestFile);
1071 if (diskfiles.is_open()) {
1072 std::stringstream buffer;
1073 buffer << diskfiles.rdbuf();
1079static void PurgeEmptyDirs(
const std::string& root) {
1080 if (!wxFileName::IsDirWritable(root))
return;
1081 if (
ocpn::tolower(root).find(
"opencpn") == std::string::npos)
return;
1082 wxDir rootdir(root);
1083 if (!rootdir.IsOpened())
return;
1085 bool cont = rootdir.GetFirst(&dirname,
"", wxDIR_DIRS);
1087 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1088 cont = rootdir.GetNext(&dirname);
1092 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1093 wxFileName::Rmdir(rootdir.GetName());
1098 const std::string& plugname) {
1099 MESSAGE_LOG <<
"Cleaning up failed install of " << plugname;
1101 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1102 for (
const auto& path : paths) {
1103 if (isRegularFile(path.c_str())) {
1104 int r = remove(path.c_str());
1106 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1110 for (
const auto& path : paths) PurgeEmptyDirs(path);
1116 remove(dirListPath(plugname).c_str());
1126 struct metadata_compare {
1129 return lhs.key() < rhs.key();
1133 std::vector<PluginMetadata> returnArray;
1135 std::set<PluginMetadata, metadata_compare> unique_plugins;
1137 unique_plugins.insert(plugin);
1139 for (
const auto& plugin : unique_plugins) {
1141 returnArray.push_back(plugin);
1148 using namespace std;
1151 auto catalogHandler = CatalogHandler::getInstance();
1153 ctx = catalogHandler->GetActiveCatalogContext();
1154 auto status = catalogHandler->GetCatalogStatus();
1156 if (status == CatalogHandler::ServerStatus::OK) {
1157 catalogData.undef =
false;
1158 catalogData.version = ctx->version;
1159 catalogData.date = ctx->date;
1161 return ctx->plugins;
1165 std::vector<std::string> names;
1167 for (
const auto& entry : fs::directory_iterator(dirpath)) {
1168 const std::string name(entry.path().filename().string());
1170 names.push_back(
ocpn::split(name.c_str(),
".")[0]);
1176 using namespace std;
1177 vector<PluginMetadata> plugins;
1179 auto loader = PluginLoader::GetInstance();
1180 for (
unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1186 std::stringstream ss;
1187 ss << p->m_version_major <<
"." << p->m_version_minor;
1188 plugin.version = ss.str();
1191 if (path !=
"" && wxFileName::IsFileReadable(path)) {
1192 std::ifstream stream;
1193 stream.open(path, ifstream::in);
1194 stream >> plugin.version;
1196 plugins.push_back(plugin);
1202 auto loader = PluginLoader::GetInstance();
1203 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1204 if (ix == -1)
return;
1206 auto plugins = *loader->GetPlugInArray();
1207 plugins[ix]->m_managed_metadata = pm;
1211 std::string filelist;
1212 if (!extractTarball(path, filelist)) {
1213 std::ostringstream os;
1214 os <<
"Cannot unpack plugin: " << plugin.name <<
" at " << path;
1215 MESSAGE_LOG << os.str();
1216 last_error_msg = os.str();
1220 saveFilelist(filelist, plugin.name);
1221 saveDirlist(plugin.name);
1222 saveVersion(plugin.name, plugin.version);
1227 std::string path = tmpfile_path();
1229 MESSAGE_LOG <<
"Cannot create temporary file";
1233 std::ofstream stream;
1234 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1235 DEBUG_LOG <<
"Downloading: " << plugin.name << std::endl;
1236 auto downloader =
Downloader(plugin.tarball_url);
1237 downloader.download(&stream);
1245 MESSAGE_LOG <<
"Cannot extract metadata from tarball";
1253 std::string filelist;
1254 std::string temp_path = tmpfile_path();
1255 if (!extractTarball(path, filelist, temp_path,
true)) {
1256 std::ostringstream os;
1257 os <<
"Cannot unpack plugin " << metadata.name <<
" tarball at: " << path;
1258 MESSAGE_LOG << os.str();
1259 if (filelist !=
"")
cleanup(filelist,
"unknown_name");
1260 last_error_msg = os.str();
1263 if (!isRegularFile(temp_path.c_str())) {
1270 std::ifstream istream(temp_path);
1271 std::stringstream buff;
1272 buff << istream.rdbuf();
1273 int r = remove(temp_path.c_str());
1275 MESSAGE_LOG <<
"Cannot remove file " << temp_path <<
":" << strerror(r);
1277 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1278 ParseCatalog(xml, &ctx);
1279 metadata = ctx.plugins[0];
1280 if (metadata.name.empty()) {
1281 MESSAGE_LOG <<
"Plugin metadata is empty";
1283 return !metadata.name.empty();
1287 auto ix = PlugInIxByName(plugin_name,
1288 PluginLoader::GetInstance()->GetPlugInArray());
1290 MESSAGE_LOG <<
"Attempt to remove installation data for loaded plugin";
1293 return DoClearInstallData(plugin_name);
1296bool PluginHandler::DoClearInstallData(
const std::string plugin_name) {
1299 MESSAGE_LOG <<
"Cannot find installation data for " << plugin_name <<
" ("
1303 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1304 for (
const auto& p : plug_paths) {
1305 if (isRegularFile(p.c_str())) {
1306 int r = remove(p.c_str());
1308 MESSAGE_LOG <<
"Cannot remove file " << p <<
": " << strerror(r);
1312 for (
const auto& p : plug_paths) PurgeEmptyDirs(p);
1313 int r = remove(path.c_str());
1315 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1318 remove(dirListPath(plugin_name).c_str());
1325 using namespace std;
1327 auto loader = PluginLoader::GetInstance();
1328 auto ix = PlugInIxByName(plugin_name, loader->GetPlugInArray());
1330 MESSAGE_LOG <<
"trying to uninstall non-existing plugin " << plugin_name;
1333 auto pic = loader->GetPlugInArray()->Item(ix);
1336 string libfile = pic->m_plugin_file.ToStdString();
1337 loader->UnLoadPlugIn(ix);
1339 bool ok = DoClearInstallData(plugin_name);
1344 if (isRegularFile(libfile.c_str())) {
1345 remove(libfile.c_str());
1351using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1358static std::string FindMatchingDataDir(std::regex name_re) {
1359 using namespace std;
1361 wxStringTokenizer tokens(data_dirs,
";");
1362 while (tokens.HasMoreTokens()) {
1363 auto token = tokens.GetNextToken();
1364 wxFileName path(token);
1365 wxDir dir(path.GetFullPath());
1366 if (dir.IsOpened()) {
1368 bool cont = dir.GetFirst(&filename,
"", wxDIR_DIRS);
1372 if (regex_search(s, sm, name_re)) {
1374 for (
auto c : sm) ss << c;
1377 cont = dir.GetNext(&filename);
1388static std::string FindMatchingLibFile(std::regex name_re) {
1389 using namespace std;
1390 for (
const auto& lib :
PluginPaths::getInstance()->Libdirs()) {
1393 bool cont = dir.GetFirst(&filename,
"", wxDIR_FILES);
1397 if (regex_search(s, sm, name_re)) {
1399 for (
auto c : sm) ss << c;
1402 cont = dir.GetNext(&filename);
1409static std::string PluginNameCase(
const std::string& name) {
1410 using namespace std;
1412 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1417 for (
const auto& plugin :
PluginHandler::getInstance()->getInstalled()) {
1418 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1420 for (
const auto& plugin :
PluginHandler::getInstance()->getAvailable()) {
1421 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1424 string match = FindMatchingDataDir(name_re);
1425 if (match !=
"")
return match;
1427 match = FindMatchingLibFile(name_re);
1428 return match !=
"" ? match : name;
1432static void LoadPluginMapFile(PluginMap& map,
const std::string& path) {
1436 MESSAGE_LOG <<
"Cannot open " << path <<
": " << strerror(errno);
1439 std::stringstream buf;
1441 auto filelist =
ocpn::split(buf.str().c_str(),
"\n");
1442 for (
auto& file : filelist) {
1443 file = wxFileName(file).GetFullName().ToStdString();
1447 auto key = wxFileName(path).GetFullName().ToStdString();
1449 key = PluginNameCase(key);
1450 map[key] = filelist;
1454static void LoadPluginMap(PluginMap& map) {
1457 if (!root.IsOpened())
return;
1459 bool cont = root.GetFirst(&filename,
"*.files", wxDIR_FILES);
1461 auto path = root.GetNameWithSep() + filename;
1462 LoadPluginMapFile(map, path.ToStdString());
1463 cont = root.GetNext(&filename);
1468 auto basename = wxFileName(filename).GetFullName().ToStdString();
1469 if (files_by_plugin.size() == 0) LoadPluginMap(files_by_plugin);
1470 for (
const auto& it : files_by_plugin) {
1471 auto found = std::find(it.second.begin(), it.second.end(), basename);
1472 if (found != it.second.end())
return it.first;
1479 wxURI uri(wxString(plugin.tarball_url.c_str()));
1480 wxFileName fn(uri.GetPath());
1481 wxString tarballFile = fn.GetFullName();
1488 if (cacheFile ==
"") {
1490 wxFileName fn1(fn.GetFullName());
1491 if (fn1.GetExt().IsSameAs(
"tar")) {
1492 tarballFile = fn.GetFullName();
1498 if (cacheFile !=
"") {
1499 MESSAGE_LOG <<
"Installing " << tarballFile <<
" from local cache";
1502 evt_download_failed.
Notify(cacheFile);
1505 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.
static void MarkAsLoadable(const std::string &library_path)
Mark a library file (complete path) as loadable i.
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)
bool startswith(const std::string &str, const std::string &prefix)
Return true if s starts with given prefix.
std::string tolower(const std::string &input)
Return copy of s with all characters converted to lower case.
std::vector< std::string > split(const char *token_string, const std::string &delimiter)
Return vector of items in s separated by delimiter.
bool endswith(const std::string &str, const std::string &suffix)
Return true if s ends with given suffix.
bool exists(const std::string &name)
void mkdir(const std::string path)
Miscellaneous utilities, many of which string related.
Low level code to load plugins from disk, notably the PluginLoader class.
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.