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"
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::ArchiveCheck(
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 (!ArchiveCheck(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 ArchiveCheck(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 (!ArchiveCheck(r,
"archive copy data error", dest)) {
887 r = archive_write_finish_entry(dest);
888 if (!ArchiveCheck(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);
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 return path +
"ocpn-plugins.xml";
1054 plugins.insert(plugins.end(), a.begin(), a.end());
1055 std::map<std::string, int> count_by_target;
1056 for (
const auto& p : plugins) {
1057 if (p.target ==
"") {
1060 auto key = p.target +
":" + p.target_version;
1061 if (count_by_target.find(key) == count_by_target.end()) {
1062 count_by_target[key] = 1;
1064 count_by_target[key] += 1;
1067 return count_by_target;
1071 return glob_dir(importsDir(),
"*.xml");
1074void PluginHandler::CleanupFiles(
const std::string& manifestFile,
1075 const std::string& plugname) {
1076 std::ifstream diskfiles(manifestFile);
1077 if (diskfiles.is_open()) {
1078 std::stringstream buffer;
1079 buffer << diskfiles.rdbuf();
1085static void PurgeEmptyDirs(
const std::string& root) {
1086 if (!wxFileName::IsDirWritable(root))
return;
1087 if (
ocpn::tolower(root).find(
"opencpn") == std::string::npos)
return;
1088 wxDir rootdir(root);
1089 if (!rootdir.IsOpened())
return;
1091 bool cont = rootdir.GetFirst(&dirname,
"", wxDIR_DIRS);
1093 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1094 cont = rootdir.GetNext(&dirname);
1098 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1099 wxFileName::Rmdir(rootdir.GetName());
1104 const std::string& plugname) {
1105 MESSAGE_LOG <<
"Cleaning up failed install of " << plugname;
1107 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1108 for (
const auto& path : paths) {
1109 if (isRegularFile(path.c_str())) {
1110 int r = remove(path.c_str());
1112 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1116 for (
const auto& path : paths) PurgeEmptyDirs(path);
1122 remove(dirListPath(plugname).c_str());
1132 struct metadata_compare {
1135 return lhs.key() < rhs.key();
1139 std::vector<PluginMetadata> returnArray;
1141 std::set<PluginMetadata, metadata_compare> unique_plugins;
1143 unique_plugins.insert(plugin);
1145 for (
const auto& plugin : unique_plugins) {
1147 returnArray.push_back(plugin);
1154 using namespace std;
1157 auto catalogHandler = CatalogHandler::GetInstance();
1159 ctx = catalogHandler->GetActiveCatalogContext();
1160 auto status = catalogHandler->GetCatalogStatus();
1162 if (status == CatalogHandler::ServerStatus::OK) {
1163 catalogData.undef =
false;
1164 catalogData.version = ctx->version;
1165 catalogData.date = ctx->date;
1167 return ctx->plugins;
1171 std::vector<std::string> names;
1173 for (
const auto& entry : fs::directory_iterator(dirpath)) {
1174 const std::string name(entry.path().filename().string());
1176 names.push_back(
ocpn::split(name.c_str(),
".")[0]);
1182 using namespace std;
1183 vector<PluginMetadata> plugins;
1185 auto loader = PluginLoader::GetInstance();
1186 for (
unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1192 std::stringstream ss;
1193 ss << p->m_version_major <<
"." << p->m_version_minor;
1194 plugin.version = ss.str();
1197 if (path !=
"" && wxFileName::IsFileReadable(path)) {
1198 std::ifstream stream;
1199 stream.open(path, ifstream::in);
1200 stream >> plugin.version;
1202 plugins.push_back(plugin);
1208 auto loader = PluginLoader::GetInstance();
1209 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1210 if (ix == -1)
return;
1212 auto plugins = *loader->GetPlugInArray();
1213 plugins[ix]->m_managed_metadata = pm;
1217 std::string filelist;
1218 if (!ExtractTarball(path, filelist)) {
1219 std::ostringstream os;
1220 os <<
"Cannot unpack plugin: " << plugin.name <<
" at " << path;
1221 MESSAGE_LOG << os.str();
1222 last_error_msg = os.str();
1226 saveFilelist(filelist, plugin.name);
1227 saveDirlist(plugin.name);
1228 saveVersion(plugin.name, plugin.version);
1233 std::string path = tmpfile_path();
1235 MESSAGE_LOG <<
"Cannot create temporary file";
1239 std::ofstream stream;
1240 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1241 DEBUG_LOG <<
"Downloading: " << plugin.name << std::endl;
1242 auto downloader =
Downloader(plugin.tarball_url);
1243 downloader.download(&stream);
1251 MESSAGE_LOG <<
"Cannot extract metadata from tarball";
1259 std::string filelist;
1260 std::string temp_path = tmpfile_path();
1261 if (!ExtractTarball(path, filelist, temp_path,
true)) {
1262 std::ostringstream os;
1263 os <<
"Cannot unpack plugin " << metadata.name <<
" tarball at: " << path;
1264 MESSAGE_LOG << os.str();
1265 if (filelist !=
"")
Cleanup(filelist,
"unknown_name");
1266 last_error_msg = os.str();
1269 if (!isRegularFile(temp_path.c_str())) {
1276 std::ifstream istream(temp_path);
1277 std::stringstream buff;
1278 buff << istream.rdbuf();
1279 int r = remove(temp_path.c_str());
1281 MESSAGE_LOG <<
"Cannot remove file " << temp_path <<
":" << strerror(r);
1283 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1284 ParseCatalog(xml, &ctx);
1285 metadata = ctx.plugins[0];
1286 if (metadata.name.empty()) {
1287 MESSAGE_LOG <<
"Plugin metadata is empty";
1289 return !metadata.name.empty();
1293 auto ix = PlugInIxByName(plugin_name,
1294 PluginLoader::GetInstance()->GetPlugInArray());
1296 MESSAGE_LOG <<
"Attempt to remove installation data for loaded plugin";
1299 return DoClearInstallData(plugin_name);
1302bool PluginHandler::DoClearInstallData(
const std::string plugin_name) {
1305 MESSAGE_LOG <<
"Cannot find installation data for " << plugin_name <<
" ("
1309 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1310 for (
const auto& p : plug_paths) {
1311 if (isRegularFile(p.c_str())) {
1312 int r = remove(p.c_str());
1314 MESSAGE_LOG <<
"Cannot remove file " << p <<
": " << strerror(r);
1318 for (
const auto& p : plug_paths) PurgeEmptyDirs(p);
1319 int r = remove(path.c_str());
1321 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1324 remove(dirListPath(plugin_name).c_str());
1331 using namespace std;
1333 auto loader = PluginLoader::GetInstance();
1334 auto ix = PlugInIxByName(plugin, loader->GetPlugInArray());
1336 MESSAGE_LOG <<
"trying to Uninstall non-existing plugin " << plugin;
1339 auto pic = loader->GetPlugInArray()->Item(ix);
1342 string libfile = pic->m_plugin_file.ToStdString();
1343 loader->UnLoadPlugIn(ix);
1345 bool ok = DoClearInstallData(plugin);
1350 if (isRegularFile(libfile.c_str())) {
1351 remove(libfile.c_str());
1357using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1364static std::string FindMatchingDataDir(std::regex name_re) {
1365 using namespace std;
1367 wxStringTokenizer tokens(data_dirs,
";");
1368 while (tokens.HasMoreTokens()) {
1369 auto token = tokens.GetNextToken();
1370 wxFileName path(token);
1371 wxDir dir(path.GetFullPath());
1372 if (dir.IsOpened()) {
1374 bool cont = dir.GetFirst(&filename,
"", wxDIR_DIRS);
1378 if (regex_search(s, sm, name_re)) {
1380 for (
auto c : sm) ss << c;
1383 cont = dir.GetNext(&filename);
1394static std::string FindMatchingLibFile(std::regex name_re) {
1395 using namespace std;
1396 for (
const auto& lib :
PluginPaths::GetInstance()->Libdirs()) {
1399 bool cont = dir.GetFirst(&filename,
"", wxDIR_FILES);
1403 if (regex_search(s, sm, name_re)) {
1405 for (
auto c : sm) ss << c;
1408 cont = dir.GetNext(&filename);
1415static std::string PluginNameCase(
const std::string& name) {
1416 using namespace std;
1418 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1423 for (
const auto& plugin :
PluginHandler::GetInstance()->GetInstalled()) {
1424 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1426 for (
const auto& plugin :
PluginHandler::GetInstance()->GetAvailable()) {
1427 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1430 string match = FindMatchingDataDir(name_re);
1431 if (match !=
"")
return match;
1433 match = FindMatchingLibFile(name_re);
1434 return match !=
"" ? match : name;
1438static void LoadPluginMapFile(PluginMap& map,
const std::string& path) {
1442 MESSAGE_LOG <<
"Cannot open " << path <<
": " << strerror(errno);
1445 std::stringstream buf;
1447 auto filelist =
ocpn::split(buf.str().c_str(),
"\n");
1448 for (
auto& file : filelist) {
1449 file = wxFileName(file).GetFullName().ToStdString();
1453 auto key = wxFileName(path).GetFullName().ToStdString();
1455 key = PluginNameCase(key);
1456 map[key] = filelist;
1460static void LoadPluginMap(PluginMap& map) {
1463 if (!root.IsOpened())
return;
1465 bool cont = root.GetFirst(&filename,
"*.files", wxDIR_FILES);
1467 auto path = root.GetNameWithSep() + filename;
1468 LoadPluginMapFile(map, path.ToStdString());
1469 cont = root.GetNext(&filename);
1474 auto basename = wxFileName(filename).GetFullName().ToStdString();
1475 if (FilesByPlugin.size() == 0) LoadPluginMap(FilesByPlugin);
1476 for (
const auto& it : FilesByPlugin) {
1477 auto found = std::find(it.second.begin(), it.second.end(), basename);
1478 if (found != it.second.end())
return it.first;
1485 wxURI uri(wxString(plugin.tarball_url.c_str()));
1486 wxFileName fn(uri.GetPath());
1487 wxString tarballFile = fn.GetFullName();
1494 if (cacheFile ==
"") {
1496 wxFileName fn1(fn.GetFullName());
1497 if (fn1.GetExt().IsSameAs(
"tar")) {
1498 tarballFile = fn.GetFullName();
1504 if (cacheFile !=
"") {
1505 MESSAGE_LOG <<
"Installing " << tarballFile <<
" from local cache";
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.
Internal helper wrapping host OS and version.
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.
Handle plugin install from remote repositories and local operations to Uninstall and list plugins.
static std::vector< std::string > GetImportPaths()
List of paths for imported plugins metadata.
bool Uninstall(const std::string plugin)
Uninstall an installed and loaded plugin.
const std::vector< PluginMetadata > GetInstalled()
Return list of all installed and loaded plugins.
const std::map< std::string, int > GetCountByTarget()
Map of available plugin targets -> number of occurences.
static void Cleanup(const std::string &filelist, const std::string &plugname)
Cleanup failed installation attempt using filelist for plugin.
bool IsPluginWritable(std::string name)
Check if given plugin can be installed/updated.
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.
static std::string VersionPath(std::string name)
Return path to file containing version for given plugin.
std::string GetMetadataPath()
Return path to metadata XML file.
std::vector< std::string > GetInstalldataPlugins()
Return list of installed plugins lower case names, not necessarily loaded.
bool InstallPlugin(PluginMetadata plugin)
Download and install a new, not installed plugin.
const std::vector< PluginMetadata > GetAvailable()
Update catalog and return list of available, not installed plugins.
bool ClearInstallData(const std::string plugin_name)
Remove installation data for not loaded plugin.
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 GetUserMetadataPath()
Return path to user, writable metadata XML file.
static std::string FileListPath(std::string name)
Return path to installation manifest for given plugin.
EventVar evt_download_failed
Notified with plugin name after failed download attempt.
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.
bool InstallPluginFromCache(PluginMetadata plugin)
Install plugin tarball from local cache.
std::string GetPluginByLibrary(const std::string &filename)
Return plugin containing given filename or "" if not found.
static PluginHandler * GetInstance()
Singleton factory.
static std::string PluginsInstallDataPath()
Return base directory for installation data.
EventVar evt_download_ok
Notified with plugin name + version string after successful download from repository.
static void MarkAsLoadable(const std::string &library_path)
Mark a library file (complete path) as loadable i.
Accessors for various paths to install plugins and their data.
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'.
static PluginPaths * GetInstance()
Return the singleton instance.
std::string UserBindir()
The single, user-writable directory for installing helper binaries.
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.
PLugin remote repositories installation and Uninstall/list operations.
Low level code to load plugins from disk, notably the PluginLoader class.
Plugin installation and data paths support.
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.