25#define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
33#ifdef DLDR_USE_LIBARCHIVE
35#include <archive_entry.h>
36#ifdef CHARTDLDR_RAR_UNARR
51#include <wx/filename.h>
53#include <wx/listctrl.h>
54#include <wx/progdlg.h>
56#include <wx/sstream.h>
59#include <wx/wfstream.h>
61#include <wxWTranslateCatalog.h>
62#include <wx/zipstrm.h>
68#include <QtAndroidExtras/QAndroidJniObject>
71#include "android_jvm.h"
76#define CATALOGS_NAME_WIDTH 300
77#define CATALOGS_DATE_WIDTH 120
78#define CATALOGS_PATH_WIDTH 100
79#define CHARTS_NAME_WIDTH 300
80#define CHARTS_STATUS_WIDTH 100
81#define CHARTS_DATE_WIDTH 120
85#define CATALOGS_NAME_WIDTH 350
86#define CATALOGS_DATE_WIDTH 500
87#define CATALOGS_PATH_WIDTH 1000
88#define CHARTS_NAME_WIDTH 520
89#define CHARTS_STATUS_WIDTH 150
90#define CHARTS_DATE_WIDTH 200
94#define CATALOGS_NAME_WIDTH 200
95#define CATALOGS_DATE_WIDTH 130
96#define CATALOGS_PATH_WIDTH 250
97#define CHARTS_NAME_WIDTH 320
98#define CHARTS_STATUS_WIDTH 150
99#define CHARTS_DATE_WIDTH 130
104#define CHART_DIR "Charts"
110extern "C" DECL_EXP
void destroy_pi(
opencpn_plugin *p) {
delete p; }
116int g_Android_SDK_Version;
120bool getDisplayMetrics();
125static bool IsPathInsideDir(
const wxString &targetDir,
126 const wxString &entryName, wxString &outFullPath) {
128 wxString combinedPath = targetDir;
129 if (!combinedPath.EndsWith(wxFileName::GetPathSeparator())) {
130 combinedPath += wxFileName::GetPathSeparator();
132 combinedPath += entryName;
135 wxFileName fn(combinedPath);
136 fn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG);
137 outFullPath = fn.GetFullPath();
140 wxFileName targetFn(targetDir);
141 targetFn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE |
143 wxString normalizedTarget = targetFn.GetFullPath();
146 if (!normalizedTarget.EndsWith(wxFileName::GetPathSeparator())) {
147 normalizedTarget += wxFileName::GetPathSeparator();
153 if (outFullPath.StartsWith(normalizedTarget)) {
158 if (outFullPath == targetFn.GetFullPath()) {
165static wxString FormatBytes(
double bytes) {
166 if (bytes <= 0)
return "?";
167 return wxString::Format(
"%.1fMB", bytes / 1024 / 1024);
170static wxString FormatBytes(
long bytes) {
171 return FormatBytes(
static_cast<double>(bytes));
174static bool IsDLDirWritable(
const wxFileName &fn) {
176 return fn.IsDirWritable();
178 if (g_Android_SDK_Version >= 30) {
180 return (fn.GetFullPath().Contains(
"org.opencpn.opencpn"));
182 return fn.IsDirWritable();
186static void SetBackColor(wxWindow *ctrl,
const wxColour &col) {
187 static int depth = 0;
190 ctrl->SetBackgroundColour(col);
193 wxWindowList kids = ctrl->GetChildren();
194 for (
unsigned int i = 0; i < kids.GetCount(); i++) {
195 wxWindowListNode *node = kids.Item(i);
196 wxWindow *win = node->GetData();
198 if (
dynamic_cast<wxListBox *
>(win))
199 dynamic_cast<wxListBox *
>(win)->SetBackgroundColour(col);
201 else if (
dynamic_cast<wxTextCtrl *
>(win))
202 dynamic_cast<wxTextCtrl *
>(win)->SetBackgroundColour(col);
207 else if (
dynamic_cast<wxChoice *
>(win))
208 dynamic_cast<wxChoice *
>(win)->SetBackgroundColour(col);
210 else if (
dynamic_cast<wxComboBox *
>(win))
211 dynamic_cast<wxComboBox *
>(win)->SetBackgroundColour(col);
213 else if (
dynamic_cast<wxRadioButton *
>(win))
214 dynamic_cast<wxRadioButton *
>(win)->SetBackgroundColour(col);
216 else if (
dynamic_cast<wxScrolledWindow *
>(win)) {
217 dynamic_cast<wxScrolledWindow *
>(win)->SetBackgroundColour(col);
220 else if (
dynamic_cast<wxButton *
>(win)) {
221 dynamic_cast<wxButton *
>(win)->SetBackgroundColour(col);
228 if (win->GetChildren().GetCount() > 0) {
231 SetBackColor(w, col);
253 m_parent_window =
nullptr;
254 m_chart_source =
nullptr;
256 m_reselect_new =
false;
257 m_reselect_updated =
false;
258 m_allow_bulk_update =
false;
259 m_options_page =
nullptr;
260 m_selected_source = -1;
261 m_dldrpanel =
nullptr;
262 m_schartdldr_sources =
"";
276 m_options_page =
nullptr;
278 m_chart_source =
nullptr;
281 androidGetSDKVersion();
289 wxStringTokenizer st(m_schartdldr_sources,
"|", wxTOKEN_DEFAULT);
290 while (st.HasMoreTokens()) {
291 wxString s1 = st.GetNextToken();
292 wxString s2 = st.GetNextToken();
293 wxString s3 = st.GetNextToken();
295 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
301 wxLogMessage(
"chartdldr_pi: DeInit");
303 m_ChartSources.clear();
311 if (m_options_page) {
331 return _(
"Chart Downloader PlugIn for OpenCPN");
336 "Chart Downloader PlugIn for OpenCPN\n\
337Manages chart downloads and updates from sources supporting\n\
338NOAA Chart Catalog format");
344 if (!m_options_page) {
345 wxLogMessage(
"Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!");
348 auto *sizer =
new wxBoxSizer(wxVERTICAL);
349 m_options_page->SetSizer(sizer);
353 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
355 m_options_page->InvalidateBestSize();
356 sizer->Add(m_dldrpanel, 1, wxALL | wxEXPAND);
357 m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
358 m_dldrpanel->FitInside();
363 m_dldrpanel->CancelDownload();
368 m_selected_source = m_dldrpanel->GetSelectedCatalog();
372bool chartdldr_pi::LoadConfig() {
373 auto *pConf = (wxFileConfig *)m_config;
376 pConf->SetPath(
"/Settings/ChartDnldr");
377 pConf->Read(
"ChartSources", &m_schartdldr_sources,
"");
378 pConf->Read(
"Source", &m_selected_source, -1);
381 fn.AppendDir(CHART_DIR);
383 pConf->Read(
"BaseChartDir", &m_base_chart_dir, fn.GetPath());
384 wxLogMessage(
"chartdldr_pi:m_base_chart_dir: " + m_base_chart_dir);
387 wxFileName testFN(m_base_chart_dir);
388 if (!IsDLDirWritable(testFN)) {
390 "Cannot write to m_base_chart_dir, override to "
391 "GetWritableDocumentsDir()");
392 m_base_chart_dir = fn.GetPath();
393 wxLogMessage(
"chartdldr_pi: Corrected: " + m_base_chart_dir);
396 pConf->Read(
"PreselectNew", &m_reselect_new,
true);
397 pConf->Read(
"PreselectUpdated", &m_reselect_updated,
true);
398 pConf->Read(
"AllowBulkUpdate", &m_allow_bulk_update,
false);
404bool chartdldr_pi::SaveConfig() {
405 auto *pConf = (wxFileConfig *)m_config;
407 m_schartdldr_sources.Clear();
409 for (
const std::unique_ptr<ChartSource> &cs : m_ChartSources) {
410 m_schartdldr_sources.Append(
411 wxString::Format(
"%s|%s|%s|", cs->GetName().c_str(),
412 cs->GetUrl().c_str(), cs->GetDir().c_str()));
416 pConf->SetPath(
"/Settings/ChartDnldr");
417 pConf->Write(
"ChartSources", m_schartdldr_sources);
418 pConf->Write(
"Source", m_selected_source);
419 pConf->Write(
"BaseChartDir", m_base_chart_dir);
420 pConf->Write(
"PreselectNew", m_reselect_new);
421 pConf->Write(
"PreselectUpdated", m_reselect_updated);
422 pConf->Write(
"AllowBulkUpdate", m_allow_bulk_update);
436 if (m_parent_window) {
437 int xmax = m_parent_window->GetSize().GetWidth();
438 int ymax = m_parent_window->GetParent()
441 dialog->SetSize(xmax, ymax);
447 wxColour cl = wxColour(214, 218, 222);
448 SetBackColor(dialog, cl);
451 dialog->SetPath(m_base_chart_dir);
452 dialog->SetPreferences(m_reselect_new, m_reselect_updated,
453 m_allow_bulk_update);
460 m_base_chart_dir = dialog->GetPath();
461 dialog->GetPreferences(m_reselect_new, m_reselect_updated,
462 m_allow_bulk_update);
464 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
467bool getDisplayMetrics() {
473 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
474 "org/qtproject/qt5/android/QtNative",
"activity",
475 "()Landroid/app/Activity;");
477 if (!activity.isValid()) {
482 QAndroidJniObject data =
483 activity.callObjectMethod(
"getDisplayMetrics",
"()Ljava/lang/String;");
485 wxString return_string;
486 jstring s = data.object<jstring>();
490 if (java_vm->GetEnv((
void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
493 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
494 return_string = wxString(ret_string, wxConvUTF8);
499 return_string.Replace(
",",
".");
507 double density = 1.0;
508 wxStringTokenizer tk(return_string,
";");
509 if (tk.HasMoreTokens()) {
510 wxString token = tk.GetNextToken();
511 token = tk.GetNextToken();
513 long b = ::wxGetDisplaySize().y;
514 token.ToDouble(&density);
516 token = tk.GetNextToken();
518 token = tk.GetNextToken();
519 token = tk.GetNextToken();
520 token = tk.GetNextToken();
521 token = tk.GetNextToken();
522 token = tk.GetNextToken();
523 token = tk.GetNextToken();
525 token = tk.GetNextToken();
533 double ldpi = 160. * density;
553ChartSource::ChartSource(
const wxString &name,
const wxString &url,
554 const wxString &localdir) {
558 m_update_data.clear();
561ChartSource::~ChartSource() { m_update_data.clear(); }
563#define ID_MNU_SELALL 2001
564#define ID_MNU_DELALL 2002
565#define ID_MNU_INVSEL 2003
566#define ID_MNU_SELUPD 2004
567#define ID_MNU_SELNEW 2005
569enum { ThreadId = wxID_HIGHEST + 1 };
575 switch (evt.GetId()) {
577 CheckAllCharts(
true);
580 CheckAllCharts(
false);
583 InvertCheckAllCharts();
586 CheckUpdatedCharts(
true);
589 CheckNewCharts(
true);
592 assert(
false &&
"Illegal popup menu id");
597void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
600 wxPoint mouseScreen = wxGetMousePosition();
601 wxPoint mouseClient = ScreenToClient(mouseScreen);
607 wxMenuItem *item1 =
new wxMenuItem(&menu, ID_MNU_SELALL, _(
"Select all"));
611 wxMenuItem *item2 =
new wxMenuItem(&menu, ID_MNU_DELALL, _(
"Deselect all"));
616 new wxMenuItem(&menu, ID_MNU_INVSEL, _(
"Invert selection"));
620 wxMenuItem *item4 =
new wxMenuItem(&menu, ID_MNU_SELUPD, _(
"Select updated"));
624 wxMenuItem *item5 =
new wxMenuItem(&menu, ID_MNU_SELNEW, _(
"Select new"));
630 menu.Append(ID_MNU_SELALL, _(
"Select all"),
"");
631 menu.Append(ID_MNU_DELALL, _(
"Deselect all"),
"");
632 menu.Append(ID_MNU_INVSEL, _(
"Invert selection"),
"");
633 menu.Append(ID_MNU_SELUPD, _(
"Select updated"),
"");
634 menu.Append(ID_MNU_SELNEW, _(
"Select new"),
"");
638 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
639 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick,
642 PopupMenu(&menu, mouseClient.x, mouseClient.y);
645void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
646 if (!m_plugin->m_chart_source)
return;
648 wxExecute(wxString::Format(
"xdg-open %s",
649 m_plugin->m_chart_source->GetDir().c_str()));
653 wxString::Format(
"open %s", m_plugin->m_chart_source->GetDir().c_str()));
656 wxExecute(wxString::Format(
"explorer %s",
657 m_plugin->m_chart_source->GetDir().c_str()));
661void ChartDldrPanelImpl::SetSource(
int id) {
662 m_plugin->SetSourceId(
id);
664 m_bDeleteSource->Enable(
id >= 0);
665 m_bUpdateChartList->Enable(
id >= 0);
666 m_bEditSource->Enable(
id >= 0);
671 if (
id >= 0 &&
id < (
int)m_plugin->m_ChartSources.size()) {
672 ::wxBeginBusyCursor();
674 std::unique_ptr<ChartSource> &cs = m_plugin->m_ChartSources.at(
id);
675 cs->LoadUpdateData();
676 cs->UpdateLocalFiles();
677 m_plugin->m_chart_source = cs.get();
678 FillFromFile(cs->GetUrl(), cs->GetDir(), m_plugin->m_reselect_new,
679 m_plugin->m_reselect_updated);
680 wxURI url(cs->GetUrl());
681 m_chartsLabel->SetLabel(wxString::Format(
683 (cs->GetName() + _(
" from ") + url.BuildURI() +
" -> " + cs->GetDir())
685 if (::wxIsBusy()) ::wxEndBusyCursor();
687 m_plugin->m_chart_source =
nullptr;
688 m_chartsLabel->SetLabel(_(
"Charts"));
692void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
693 int i = GetSelectedCatalog();
694 if (i >= 0) SetSource(i);
698void ChartDldrPanelImpl::SetBulkUpdate(
bool bulk_update) {
699 m_bUpdateAllCharts->Enable(bulk_update);
700 m_bUpdateAllCharts->Show(bulk_update);
705void ChartDldrPanelImpl::CleanForm() {
706#if defined(CHART_LIST)
709 m_scrollWinChartList->ClearBackground();
714void ChartDldrPanelImpl::FillFromFile(
const wxString &url,
const wxString &dir,
715 bool selnew,
bool selupd) {
717 wxStringTokenizer tk(url,
"/");
720 file = tk.GetNextToken();
721 }
while (tk.HasMoreTokens());
723 fn.SetFullName(file);
725 wxString path = fn.GetFullPath();
726 if (wxFileExists(path)) {
727 m_plugin->m_chart_catalog.LoadFromFile(path);
731 m_updated_charts = 0;
734#if !defined(CHART_LIST)
736 m_panelArray.clear();
737 m_scrollWinChartList->ClearBackground();
740 for (
size_t i = 0; i < m_plugin->m_chart_catalog.charts.size(); i++) {
745 m_plugin->m_chart_catalog.charts.at(i)->GetChartFilename(
true);
746 if (!m_plugin->m_chart_source->ExistsLocally(
747 m_plugin->m_chart_catalog.charts.at(i)->number, file_)) {
750 if (selnew) bcheck =
true;
752 if (m_plugin->m_chart_source->IsNewerThanLocal(
753 m_plugin->m_chart_catalog.charts.at(i)->number, file_,
754 m_plugin->m_chart_catalog.charts.at(i)->GetUpdateDatetime())) {
756 status = _(
"Out of date");
757 if (selupd) bcheck =
true;
759 status = _(
"Up to date");
763 m_plugin->m_chart_catalog.charts.at(i)->GetUpdateDatetime().Format(
766#if defined(CHART_LIST)
767 wxVector<wxVariant> data;
768 data.push_back(wxVariant(bcheck));
769 data.push_back(wxVariant(status));
770 data.push_back(wxVariant(latest));
772 wxVariant(m_plugin->m_chart_catalog.charts.at(i)->GetChartTitle()));
773 getChartList()->AppendItem(data);
775 auto pC = std::make_unique<ChartPanel>(
776 m_scrollWinChartList, wxID_ANY, wxDefaultPosition, wxSize(-1, -1),
777 m_plugin->m_chart_catalog.charts.at(i)->GetChartTitle(), status,
778 latest,
this, bcheck);
779 pC->Connect(wxEVT_RIGHT_DOWN,
780 wxMouseEventHandler(ChartDldrPanel::OnContextMenu),
nullptr,
783 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
784 m_panelArray.push_back(std::move(pC));
788#if !defined(CHART_LIST)
789 m_scrollWinChartList->ClearBackground();
790 m_scrollWinChartList->FitInside();
791 m_scrollWinChartList->GetSizer()->Layout();
793 m_scrollWinChartList->ClearBackground();
794 SetChartInfo(wxString::Format(_(
"%lu charts total, %lu updated, %lu new"),
795 m_plugin->m_chart_catalog.charts.size(),
796 m_updated_charts, m_new_charts));
798 SetChartInfo(wxString::Format(
799 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
800 m_plugin->m_chart_catalog.charts.size(), m_updated_charts, m_new_charts,
801 GetCheckedChartCount()));
806bool ChartSource::ExistsLocally(
const wxString &chart_number,
807 const wxString &filename) {
810 wxStringTokenizer tk(filename,
".");
811 wxString file = tk.GetNextToken().MakeLower();
813 if (!m_update_data.empty()) {
814 return m_update_data.find(std::string(chart_number.Lower().mb_str())) !=
815 m_update_data.end() ||
816 m_update_data.find(std::string(file.mb_str())) !=
819 for (
size_t i = 0; i < m_localfiles.Count(); i++) {
820 if (m_localfiles.Item(i) == file)
return true;
825bool ChartSource::IsNewerThanLocal(
const wxString &chart_number,
826 const wxString &filename,
827 const wxDateTime &validDate) {
828 wxStringTokenizer tk(filename,
".");
829 wxString file = tk.GetNextToken().MakeLower();
830 time_t validTime = validDate.GetTicks();
831 if (!m_update_data.empty()) {
832 time_t chartNumberTime =
833 m_update_data[std::string(chart_number.Lower().mbc_str())];
834 time_t updateFileTime = m_update_data[std::string(file.mbc_str())];
836 chartNumberTime < validTime && updateFileTime < validTime;
837 if (wxLOG_Debug <= wxLog::GetLogLevel()) {
840 wxLogInfo(
"Latest Zip File Date: %sZ",
841 validDate.ToUTC().FormatISOCombined());
842 wxLogInfo(
"Local File: %s, Date: %sZ", filename,
843 wxDateTime(updateFileTime).ToUTC().FormatISOCombined());
844 wxLogInfo(
"Chart Number %s DOB: Date: %sZ", chart_number,
845 wxDateTime(chartNumberTime).ToUTC().FormatISOCombined());
846 wxLogInfo(
"Chart Number %s Needs update: %s", chart_number,
847 needsUpdate ? wxString(
"true") : wxString(
"false"));
852 bool update_candidate =
false;
854 for (
size_t i = 0; i < m_localfiles.Count(); i++) {
855 if (m_localfiles.Item(i) == file) {
856 if (validDate.IsLaterThan(m_localdt.at(i))) {
857 update_candidate =
true;
862 return update_candidate;
865int ChartDldrPanelImpl::GetSelectedCatalog() {
867 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
868 return static_cast<int>(item);
871void ChartDldrPanelImpl::SelectCatalog(
int item) {
873 m_bDeleteSource->Enable();
874 m_bEditSource->Enable();
875 m_bUpdateChartList->Enable();
877 m_bDeleteSource->Disable();
878 m_bEditSource->Disable();
879 m_bUpdateChartList->Disable();
881 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
882 wxLIST_STATE_SELECTED);
885void ChartDldrPanelImpl::AppendCatalog(std::unique_ptr<ChartSource> &cs) {
886 long id = m_lbChartSources->GetItemCount();
887 m_lbChartSources->InsertItem(
id, cs->GetName());
888 m_lbChartSources->SetItem(
id, 1, _(
"(Please update first)"));
889 m_lbChartSources->SetItem(
id, 2, cs->GetDir());
890 wxURI url(cs->GetUrl());
891 if (url.IsReference()) {
893 this, _(
"Error, the URL to the chart source data seems wrong."),
897 wxFileName fn(url.GetPath());
898 fn.SetPath(cs->GetDir());
899 wxString path = fn.GetFullPath();
900 if (wxFileExists(path)) {
901 if (m_plugin->m_chart_catalog.LoadFromFile(path,
true)) {
902 m_lbChartSources->SetItem(
id, 0, m_plugin->m_chart_catalog.title);
903 m_lbChartSources->SetItem(
905 m_plugin->m_chart_catalog.GetReleaseDate().Format(
"%Y-%m-%d %H:%M"));
906 m_lbChartSources->SetItem(
id, 2, path);
908 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
909 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
910 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
916void ChartDldrPanelImpl::UpdateAllCharts(wxCommandEvent &event) {
917 int failed_to_update = 0;
918 int attempted_to_update = 0;
919 if ((m_plugin->m_reselect_new) && (m_plugin->m_reselect_updated)) {
920 wxMessageDialog mess(
922 _(
"You have chosen to update all chart catalogs.\nThen download all "
923 "new and updated charts.\nThis may take a long time."),
924 _(
"Chart Downloader"), wxOK | wxCANCEL);
925 if (mess.ShowModal() == wxID_CANCEL)
return;
926 }
else if (m_plugin->m_reselect_new) {
927 wxMessageDialog mess(
929 _(
"You have chosen to update all chart catalogs.\nThen download only "
930 "new (but not updated) charts.\nThis may take a long time."),
931 _(
"Chart Downloader"), wxOK | wxCANCEL);
932 if (mess.ShowModal() == wxID_CANCEL)
return;
933 }
else if (m_plugin->m_reselect_updated) {
934 wxMessageDialog mess(
936 _(
"You have chosen to update all chart catalogs.\nThen download only "
937 "updated (but not new) charts.\nThis may take a long time."),
938 _(
"Chart Downloader"), wxOK | wxCANCEL);
939 if (mess.ShowModal() == wxID_CANCEL)
return;
941 m_updating_all =
true;
944 int oldPage = m_DLoadNB->SetSelection(1);
945 for (
long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
947 m_lbChartSources->SetItemState(chartIndex, wxLIST_STATE_SELECTED,
948 wxLIST_STATE_SELECTED);
949 if (m_cancelled)
break;
950 UpdateChartList(event);
952 attempted_to_update += m_downloading;
953 failed_to_update += m_failed_downloads;
955 wxLogMessage(wxString::Format(
956 "chartdldr_pi::UpdateAllCharts() downloaded %d out of %d charts.",
957 attempted_to_update - failed_to_update, attempted_to_update));
958 if (failed_to_update > 0)
961 wxString::Format(_(
"%d out of %d charts failed to download.\nCheck the "
962 "list, verify there is a working Internet "
963 "connection and repeat the operation if needed."),
964 failed_to_update, attempted_to_update),
965 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
967 m_updating_all =
false;
970 m_DLoadNB->SetSelection(oldPage);
973void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
975 if (!m_lbChartSources->GetSelectedItemCount())
return;
976 std::unique_ptr<ChartSource> &cs =
977 m_plugin->m_ChartSources.at(GetSelectedCatalog());
978 wxURI url(cs->GetUrl());
979 if (url.IsReference()) {
981 this, _(
"Error, the URL to the chart source data seems wrong."),
986 wxStringTokenizer tk(url.GetPath(),
"/");
989 file = tk.GetNextToken();
990 }
while (tk.HasMoreTokens());
992 fn.SetFullName(file);
993 fn.SetPath(cs->GetDir());
994 if (!wxDirExists(cs->GetDir())) {
995 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
998 wxString::Format(_(
"Directory %s can't be created."),
999 cs->GetDir().c_str()),
1000 _(
"Chart Downloader"));
1008 wxString file_URI =
"file://" + fn.GetFullPath();
1020 cs->GetUrl(), file_URI, _(
"Downloading file"),
1021 _(
"Reading Headers: ") + url.BuildURI(), wxNullBitmap,
this,
1030 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
1031 wxString file_URI = tfn.GetFullPath();
1034 cs->GetUrl(), file_URI, _(
"Downloading file"),
1035 _(
"Reading Headers: ") + url.BuildURI(), wxNullBitmap,
this,
1042 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
1043 wxRemoveFile(tfn.GetFullPath());
1052 int id = GetSelectedCatalog();
1055 m_lbChartSources->SetItem(
id, 0, m_plugin->m_chart_catalog.title);
1056 m_lbChartSources->SetItem(
1058 m_plugin->m_chart_catalog.GetReleaseDate().Format(
1060 m_lbChartSources->SetItem(
id, 2, cs->GetDir());
1065 wxString::Format(_(
"Failed to Find New Catalog: %s "),
1066 url.BuildURI().c_str()),
1067 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1073 wxString::Format(_(
"Failed to Download Catalog: %s \nVerify there is "
1074 "a working Internet connection."),
1075 url.BuildURI().c_str()),
1076 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1099void ChartSource::GetLocalFiles() {
1100 if (!UpdateDataExists() || m_update_data.empty()) {
1101 auto *allFiles =
new wxArrayString;
1102 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1104 m_localfiles.Clear();
1105 wxDateTime ct, mt, at;
1107 for (
size_t i = 0; i < allFiles->Count(); i++) {
1108 wxFileName fn(allFiles->Item(i));
1109 name = fn.GetFullName().Lower();
1113 if (!ExistsLocally(
"", name)) {
1114 fn.GetTimes(&at, &mt, &ct);
1115 m_localdt.push_back(mt);
1116 m_localfiles.Add(fn.GetName().Lower());
1118 wxStringTokenizer tk(name,
".");
1119 wxString file = tk.GetNextToken().MakeLower();
1120 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1131bool ChartSource::UpdateDataExists() {
1132 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1133 UPDATE_DATA_FILENAME);
1136void ChartSource::LoadUpdateData() {
1137 m_update_data.clear();
1139 GetDir() + wxFileName::GetPathSeparator() + UPDATE_DATA_FILENAME;
1141 if (!wxFileExists(fn))
return;
1143 std::ifstream infile(fn.mb_str());
1148 while (infile >> key >> value) m_update_data[key] = value;
1153void ChartSource::SaveUpdateData() {
1155 fn = GetDir() + wxFileName::GetPathSeparator() + UPDATE_DATA_FILENAME;
1158 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1159 UPDATE_DATA_FILENAME;
1162 std::ofstream outfile(fn.mb_str());
1163 if (!outfile.is_open())
return;
1165 std::map<std::string, time_t>::iterator iter;
1166 for (iter = m_update_data.begin(); iter != m_update_data.end(); ++iter) {
1167 if (iter->first.find(
" ") == std::string::npos)
1168 if (!iter->first.empty())
1169 outfile << iter->first <<
" " << iter->second <<
"\n";
1175 AndroidSecureCopyFile(
1176 fn, GetDir() + wxFileName::GetPathSeparator() + UPDATE_DATA_FILENAME);
1180void ChartSource::ChartUpdated(
const wxString &chart_number, time_t timestamp) {
1181 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1185bool ChartDldrPanelImpl::DownloadChart(
const wxString &url,
1186 const wxString &file,
1187 const wxString &title) {
1191void ChartDldrPanelImpl::DisableForDownload(
bool enabled) {
1192 m_bAddSource->Enable(enabled);
1193 m_bDeleteSource->Enable(enabled);
1194 m_bEditSource->Enable(enabled);
1195 m_bUpdateAllCharts->Enable(enabled);
1196 m_bUpdateChartList->Enable(enabled);
1197 m_lbChartSources->Enable(enabled);
1198#if defined(CHART_LIST)
1199 m_bSelectNew->Enable(enabled);
1200 m_bSelectUpdated->Enable(enabled);
1201 m_bSelectAll->Enable(enabled);
1205void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1206 if (m_download_is_cancel) {
1212#if defined(CHART_LIST)
1213void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1215 SetChartInfo(wxString::Format(
1216 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1217 m_plugin->m_chart_catalog.charts.size(), m_updated_charts, m_new_charts,
1218 GetCheckedChartCount()));
1223#if defined(CHART_LIST)
1224void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1225 CheckNewCharts(
true);
1229#if defined(CHART_LIST)
1230void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1231 CheckUpdatedCharts(
true);
1235#if defined(CHART_LIST)
1236void ChartDldrPanelImpl::OnSelectAllCharts(wxCommandEvent &event) {
1237 if (m_bSelectAll->GetLabel() == _(
"Select All")) {
1238 CheckAllCharts(
true);
1239 m_bSelectAll->SetLabel(_(
"Select None"));
1240 m_bSelectAll->SetToolTip(_(
"De-select all charts in the list."));
1242 CheckAllCharts(
false);
1243 m_bSelectAll->SetLabel(_(
"Select All"));
1244 m_bSelectAll->SetToolTip(_(
"Select all charts in the list."));
1249int ChartDldrPanelImpl::GetChartCount() {
1250#if defined(CHART_LIST)
1251 return getChartList()->GetItemCount();
1253 return static_cast<int>(m_panelArray.size());
1257int ChartDldrPanelImpl::GetCheckedChartCount() {
1258#if defined(CHART_LIST)
1260 int chartCnt = GetChartCount();
1261 for (
int i = 0; i < chartCnt; i++)
1262 if (isChartChecked(i)) cnt++;
1265 for (
int i = 0; i < GetChartCount(); i++) {
1266 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1272bool ChartDldrPanelImpl::isChartChecked(
int i) {
1273 wxASSERT_MSG(i >= 0,
1274 "This function should be called with non-negative index.");
1275 if (i <= GetChartCount())
1276#if defined(CHART_LIST)
1277 return getChartList()->GetToggleValue(i, 0);
1279 return m_panelArray.at(i)->GetCB()->IsChecked();
1285void ChartDldrPanelImpl::CheckAllCharts(
bool value) {
1286#if defined(CHART_LIST)
1290 for (
int i = 0; i < GetChartCount(); i++) {
1291#if defined(CHART_LIST)
1292 getChartList()->SetToggleValue(value, i, 0);
1294 m_panelArray.at(i)->GetCB()->SetValue(value);
1297#if defined(CHART_LIST)
1298 SetChartInfo(wxString::Format(
1299 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1300 m_plugin->m_chart_catalog.charts.size(), m_updated_charts, m_new_charts,
1301 GetCheckedChartCount()));
1302 m_hold_info =
false;
1306void ChartDldrPanelImpl::CheckNewCharts(
bool value) {
1307 for (
int i = 0; i < GetChartCount(); i++) {
1308#if defined(CHART_LIST)
1309 if (isNew(i)) getChartList()->SetToggleValue(
true, i, 0);
1311 if (m_panelArray.at(i)->isNew())
1312 m_panelArray.at(i)->GetCB()->SetValue(value);
1315#if defined(CHART_LIST)
1316 SetChartInfo(wxString::Format(
1317 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1318 m_plugin->m_chart_catalog.charts.size(), m_updated_charts, m_new_charts,
1319 GetCheckedChartCount()));
1323void ChartDldrPanelImpl::CheckUpdatedCharts(
bool value) {
1324 for (
int i = 0; i < GetChartCount(); i++) {
1325#if defined(CHART_LIST)
1326 if (isUpdated(i)) getChartList()->SetToggleValue(value, i, 0);
1328 if (m_panelArray.at(i)->isUpdated())
1329 m_panelArray.at(i)->GetCB()->SetValue(value);
1332#if defined(CHART_LIST)
1333 SetChartInfo(wxString::Format(
1334 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1335 m_plugin->m_chart_catalog.charts.size(), m_updated_charts, m_new_charts,
1336 GetCheckedChartCount()));
1340void ChartDldrPanelImpl::InvertCheckAllCharts() {
1341#if defined(CHART_LIST)
1344 for (
int i = 0; i < GetChartCount(); i++)
1345#
if defined(CHART_LIST)
1346 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1348 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1350#if defined(CHART_LIST)
1351 m_hold_info =
false;
1352 SetChartInfo(wxString::Format(
1353 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1354 m_plugin->m_chart_catalog.charts.size(), m_updated_charts, m_new_charts,
1355 GetCheckedChartCount()));
1360 if (!m_is_connected) {
1362 wxEVT_DOWNLOAD_EVENT,
1363 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1364 m_is_connected =
true;
1367 if (!GetCheckedChartCount() && !m_updating_all) {
1371 std::unique_ptr<ChartSource> &cs =
1372 m_plugin->m_ChartSources.at(GetSelectedCatalog());
1374 m_cancelled =
false;
1375 m_to_download = GetCheckedChartCount();
1377 m_failed_downloads = 0;
1378 DisableForDownload(
false);
1380 m_bDnldCharts->SetLabel(_(
"Abort download"));
1381 m_download_is_cancel =
true;
1383 wxFileName downloaded_p;
1386 for (
int i = 0; i < GetChartCount() && m_to_download; i++) {
1388 if (m_cancelled)
break;
1390 if (!isChartChecked(i))
continue;
1391 m_is_transfer_complete =
false;
1392 m_is_transfer_ok =
true;
1394 m_transferred_size = 0;
1396 if (m_plugin->m_chart_catalog.charts.at(index)->NeedsManualDownload()) {
1401 _(
"The selected chart '%s' can't be downloaded automatically, do you want me to open a browser window and download them manually?\n\n \
1402After downloading the charts, please extract them to %s"),
1403 m_plugin->m_chart_catalog.charts.at(index)->title.c_str(),
1404 m_plugin->m_chart_source->GetDir().c_str()),
1405 _(
"Chart Downloader"), wxYES_NO | wxCENTRE | wxICON_QUESTION)) {
1406 wxLaunchDefaultBrowser(
1407 m_plugin->m_chart_catalog.charts.at(index)->GetManualDownloadUrl());
1414 m_plugin->m_chart_catalog.charts.at(index)->GetDownloadLocation());
1415 if (url.IsReference()) {
1419 _(
"Error, the URL to the chart (%s) data seems wrong."),
1420 url.BuildURI().c_str()),
1428 m_plugin->m_chart_catalog.charts.at(index)->GetChartFilename(
false);
1430 fn.SetFullName(file);
1431 fn.SetPath(cs->GetDir());
1432 wxString path = fn.GetFullPath();
1433 if (wxFileExists(path)) wxRemoveFile(path);
1435 m_plugin->m_chart_catalog.charts.at(index)->GetChartTitle();
1439 wxString file_path =
"file://" + fn.GetFullPath();
1441 wxString file_path = fn.GetFullPath();
1448 if (m_plugin->ProcessFile(
1449 downloaded_p.GetFullPath(), downloaded_p.GetPath(),
true,
1450 m_plugin->m_chart_catalog.charts.at(idx)->GetUpdateDatetime())) {
1451 cs->ChartUpdated(m_plugin->m_chart_catalog.charts.at(idx)->number,
1452 m_plugin->m_chart_catalog.charts.at(idx)
1453 ->GetUpdateDatetime()
1456 m_failed_downloads++;
1461 while (!m_is_transfer_complete && m_is_transfer_ok && !m_cancelled) {
1462 if (m_failed_downloads)
1463 SetChartInfo(wxString::Format(
1464 _(
"Downloading chart %u of %u, %u downloads failed (%s / %s)"),
1465 m_downloading, m_to_download, m_failed_downloads,
1466 FormatBytes(m_transferred_size), FormatBytes(m_total_size)));
1468 SetChartInfo(wxString::Format(_(
"Downloading chart %u of %u (%s / %s)"),
1469 m_downloading, m_to_download,
1470 FormatBytes(m_transferred_size),
1471 FormatBytes(m_total_size)));
1476 wxTheApp->ProcessPendingEvents();
1486 if (m_is_transfer_ok && !m_cancelled) {
1488 downloaded_p = path;
1491 if (wxFileExists(path)) wxRemoveFile(path);
1492 m_failed_downloads++;
1496 if (m_plugin->ProcessFile(
1497 downloaded_p.GetFullPath(), downloaded_p.GetPath(),
true,
1498 m_plugin->m_chart_catalog.charts.at(idx)->GetUpdateDatetime())) {
1499 cs->ChartUpdated(m_plugin->m_chart_catalog.charts.at(idx)->number,
1500 m_plugin->m_chart_catalog.charts.at(idx)
1501 ->GetUpdateDatetime()
1504 m_failed_downloads++;
1507 DisableForDownload(
true);
1508 m_bDnldCharts->SetLabel(_(
"Download selected charts"));
1509 m_download_is_cancel =
false;
1510 SetSource(GetSelectedCatalog());
1511 if (m_failed_downloads > 0 && !m_updating_all && !m_cancelled)
1514 wxString::Format(_(
"%d out of %d charts failed to download.\nCheck the "
1515 "list, verify there is a working Internet "
1516 "connection and repeat the operation if needed."),
1517 m_failed_downloads, m_downloading),
1518 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1522 _(
"Chart Downloader"), wxOK | wxICON_INFORMATION);
1524 if ((m_downloading - m_failed_downloads > 0) && !m_updating_all)
1528ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1530 wxEVT_DOWNLOAD_EVENT,
1531 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1532 m_is_connected =
false;
1538#if defined(CHART_LIST)
1543ChartDldrPanelImpl::ChartDldrPanelImpl(
chartdldr_pi *plugin, wxWindow *parent,
1544 wxWindowID
id,
const wxPoint &pos,
1545 const wxSize &size,
long style)
1547 m_bDeleteSource->Disable();
1548 m_bUpdateChartList->Disable();
1549 m_bEditSource->Disable();
1550 m_lbChartSources->InsertColumn(0, _(
"Catalog"), wxLIST_FORMAT_LEFT,
1551 CATALOGS_NAME_WIDTH);
1552 m_lbChartSources->InsertColumn(1, _(
"Released"), wxLIST_FORMAT_LEFT,
1553 CATALOGS_DATE_WIDTH);
1554 m_lbChartSources->InsertColumn(2, _(
"Local path"), wxLIST_FORMAT_LEFT,
1555 CATALOGS_PATH_WIDTH);
1556 m_lbChartSources->Enable();
1557 m_hold_info =
false;
1561 m_updating_all =
false;
1563 m_is_populated =
false;
1564 m_download_is_cancel =
false;
1565 m_failed_downloads = 0;
1566 ChartDldrPanelImpl::SetChartInfo(
"");
1567 m_is_transfer_complete =
true;
1568 m_is_transfer_ok =
true;
1571 wxEVT_DOWNLOAD_EVENT,
1572 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1573 m_is_connected =
true;
1575 for (
size_t i = 0; i < m_plugin->m_ChartSources.size(); i++) {
1576 AppendCatalog(m_plugin->m_ChartSources.at(i));
1578 m_is_populated =
true;
1581void ChartDldrPanelImpl::OnPaint(wxPaintEvent &event) {
1582 if (!m_is_populated) {
1583 m_is_populated =
true;
1584 for (
size_t i = 0; i < m_plugin->m_ChartSources.size(); i++) {
1585 AppendCatalog(m_plugin->m_ChartSources.at(i));
1590 m_lbChartSources->Refresh(
true);
1595void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1596 if (!m_lbChartSources->GetSelectedItemCount())
return;
1599 _(
"Do you really want to remove the chart source?\nThe "
1600 "local chart files will not be removed,\nbut you will "
1601 "not be able to update the charts anymore."),
1602 _(
"Chart Downloader"), wxYES_NO | wxCENTRE))
1604 int ToBeRemoved = GetSelectedCatalog();
1605 m_lbChartSources->SetItemState(ToBeRemoved, 0,
1606 wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1607 m_plugin->m_ChartSources.erase(m_plugin->m_ChartSources.begin() +
1609 m_lbChartSources->DeleteItem(ToBeRemoved);
1611 m_plugin->SetSourceId(-1);
1613 m_plugin->SaveConfig();
1617void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1619 dialog->SetBasePath(m_plugin->GetBaseChartDir());
1621 wxSize sz = GetParent()
1624 dialog->SetSize(sz.GetWidth(), sz.GetHeight());
1628 androidDisableRotation();
1631 if (dialog->ShowModal() == wxID_OK) {
1632 std::unique_ptr<ChartSource> cs =
1633 std::make_unique<ChartSource>(dialog->m_tSourceName->GetValue(),
1634 dialog->m_tChartSourceUrl->GetValue(),
1635 dialog->m_tcChartDirectory->GetValue());
1638 bool covered =
false;
1646 wxString dir = cs->GetDir();
1650 long itemSelectedNow = GetSelectedCatalog();
1651 m_lbChartSources->SetItemState(itemSelectedNow, 0, wxLIST_STATE_SELECTED);
1653 SelectCatalog(m_lbChartSources->GetItemCount() - 1);
1654 m_plugin->m_ChartSources.push_back(std::move(cs));
1655 m_plugin->SaveConfig();
1658 androidEnableRotation();
1664void ChartDldrPanelImpl::DoEditSource() {
1665 if (!m_lbChartSources->GetSelectedItemCount())
return;
1666 int cat = GetSelectedCatalog();
1668 dialog->SetBasePath(m_plugin->GetBaseChartDir());
1669 dialog->SetSourceEdit(m_plugin->m_ChartSources.at(cat));
1670 dialog->SetTitle(_(
"Edit Chart Source"));
1672 dialog->ShowModal();
1673 int retcode = dialog->GetReturnCode();
1675 if (retcode == wxID_OK) {
1676 m_plugin->m_ChartSources.at(cat)->SetName(
1677 dialog->m_tSourceName->GetValue());
1678 m_plugin->m_ChartSources.at(cat)->SetUrl(
1679 dialog->m_tChartSourceUrl->GetValue());
1680 m_plugin->m_ChartSources.at(cat)->SetDir(
1681 dialog->m_tcChartDirectory->GetValue());
1683 m_lbChartSources->SetItem(cat, 0,
1684 m_plugin->m_ChartSources.at(cat)->GetName());
1685 m_lbChartSources->SetItem(cat, 1, _(
"(Please update first)"));
1686 m_lbChartSources->SetItem(cat, 2,
1687 m_plugin->m_ChartSources.at(cat)->GetDir());
1688 wxURI url(m_plugin->m_ChartSources.at(cat)->GetUrl());
1689 wxFileName fn(url.GetPath());
1690 fn.SetPath(m_plugin->m_ChartSources.at(cat)->GetDir());
1691 wxString path = fn.GetFullPath();
1692 if (wxFileExists(path)) {
1693 if (m_plugin->m_chart_catalog.LoadFromFile(path,
true)) {
1694 m_lbChartSources->SetItem(cat, 0, m_plugin->m_chart_catalog.title);
1695 m_lbChartSources->SetItem(
1697 m_plugin->m_chart_catalog.GetReleaseDate().Format(
1699 m_lbChartSources->SetItem(cat, 2, path);
1702 bool covered =
false;
1704 if (m_plugin->m_ChartSources.at(cat)->GetDir().StartsWith(
1714 _(
"Path %s seems not to be covered by your configured Chart "
1715 "Directories.\nTo see the charts you have to adjust the "
1716 "configuration on the 'Chart Files' tab."),
1717 m_plugin->m_ChartSources.at(cat)->GetDir().c_str()),
1718 _(
"Chart Downloader"));
1720 m_plugin->SaveConfig();
1726void ChartDldrPanelImpl::EditSource(wxCommandEvent &event) {
1731void ChartDldrPanelImpl::OnLeftDClick(wxMouseEvent &event) {
1736bool chartdldr_pi::ProcessFile(
const wxString &aFile,
1737 const wxString &aTargetDir,
bool aStripPath,
1738 wxDateTime aMTime) {
1739 if (aFile.Lower().EndsWith(
"zip"))
1741 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1743 wxRemoveFile(aFile);
1745 wxLogError(
"chartdldr_pi: Unable to extract: " + aFile);
1748#ifdef DLDR_USE_LIBARCHIVE
1749 else if (aFile.Lower().EndsWith(
"rar")) {
1750#ifdef CHARTDLDR_RAR_UNARR
1751 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1754 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1757 wxRemoveFile(aFile);
1759 wxLogError(
"chartdldr_pi: Unable to extract: " + aFile);
1761 }
else if (aFile.Lower().EndsWith(
"tar") || aFile.Lower().EndsWith(
"gz") ||
1762 aFile.Lower().EndsWith(
"bz2") || aFile.Lower().EndsWith(
"lzma") ||
1763 aFile.Lower().EndsWith(
"7z") || aFile.Lower().EndsWith(
"xz")) {
1765 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1767 wxRemoveFile(aFile);
1769 wxLogError(
"chartdldr_pi: Unable to extract: " + aFile);
1773 else if (aFile.Lower().EndsWith(
"rar") || aFile.Lower().EndsWith(
"tar")
1775 || aFile.Lower().EndsWith(
"bz2")
1778 || aFile.Lower().EndsWith(
"gz")
1782 aFile.Lower().EndsWith(
"7z")
1786 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1788 wxRemoveFile(aFile);
1790 wxLogError(
"chartdldr_pi: Unable to extract: " + aFile);
1796 else if (aFile.Lower().EndsWith(
"tar") || aFile.Lower().EndsWith(
"gz") ||
1797 aFile.Lower().EndsWith(
"bz2") || aFile.Lower().EndsWith(
"lzma") ||
1798 aFile.Lower().EndsWith(
"7z") || aFile.Lower().EndsWith(
"xz")) {
1800 if (aStripPath) nStrip = 1;
1802 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_(
"Installing charts."));
1804 androidShowBusyIcon();
1805 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip,
true);
1806 androidHideBusyIcon();
1814 wxFileName fn(aFile);
1815 if (fn.GetPath() != aTargetDir)
1817 if (!wxDirExists(aTargetDir)) {
1818 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1819 if (!wxRenameFile(aFile, aTargetDir))
return false;
1824 wxString name = fn.GetFullName();
1826 fn.Assign(aTargetDir, name);
1827 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1832#ifdef DLDR_USE_LIBARCHIVE
1834static int copy_data(
struct archive *ar,
struct archive *aw) {
1838 __LA_INT64_T offset;
1841 r = archive_read_data_block(ar, &buff, &size, &offset);
1842 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
1843 if (r < ARCHIVE_OK)
return (r);
1844 r =
static_cast<int>(archive_write_data_block(aw, buff, size, offset));
1845 if (r < ARCHIVE_OK) {
1847 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1848 archive_error_string(aw)));
1855bool chartdldr_pi::ExtractLibArchiveFiles(
const wxString &aArchiveFile,
1856 const wxString &aTargetDir,
1857 bool aStripPath, wxDateTime aMTime,
1858 bool aRemoveArchive) {
1860 struct archive *a =
nullptr;
1861 struct archive *ext =
nullptr;
1864 int flags = ARCHIVE_EXTRACT_TIME;
1865#ifdef ARCHIVE_EXTRACT_SECURE_NODOTDOT
1866 flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
1868#ifdef ARCHIVE_EXTRACT_SECURE_SYMLINKS
1869 flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
1872 a = archive_read_new();
1873 ext = archive_write_disk_new();
1876 wxLogError(
"Chartdldr_pi: Failed to create libarchive objects.");
1880 archive_read_support_format_all(a);
1881 archive_read_support_filter_all(a);
1882#if !defined(__clang__)
1883 archive_read_support_compression_all(a);
1886 archive_write_disk_set_options(ext, flags);
1887 archive_write_disk_set_standard_lookup(ext);
1890 if (archive_read_open_filename_w(a, aArchiveFile.wc_str(), 10240) !=
1892 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive open error: %s",
1893 archive_error_string(a)));
1898 if (archive_read_open_filename(a, aArchiveFile.mb_str().data(), 10240) !=
1900 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive open error: %s",
1901 archive_error_string(a)));
1908 struct archive_entry *entry =
nullptr;
1909 int r = archive_read_next_header(a, &entry);
1911 if (r == ARCHIVE_EOF) {
1915 if (r < ARCHIVE_OK) {
1916 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1917 archive_error_string(a)));
1919 if (r < ARCHIVE_WARN) {
1925 const char *rawUtf8 = archive_entry_pathname_utf8(entry);
1926 if (rawUtf8 && *rawUtf8) {
1927 entryName = wxString::FromUTF8(rawUtf8);
1929 const wchar_t *rawWide = archive_entry_pathname_w(entry);
1930 if (rawWide && *rawWide) entryName = wxString(rawWide);
1933 const char *rawPath = archive_entry_pathname(entry);
1934 if (rawPath && *rawPath) {
1935 entryName = wxString::FromUTF8(rawPath);
1936 if (entryName.IsEmpty()) {
1937 entryName = wxString::From8BitData(rawPath);
1942 if (entryName.IsEmpty()) {
1943 wxLogWarning(
"Skipping archive entry with empty pathname.");
1948 wxFileName stripped(entryName);
1949 entryName = stripped.GetFullName();
1951 if (entryName.IsEmpty()) {
1956 wxString outputPath = entryName;
1957 if (aTargetDir.empty()) {
1958 if (!IsPathInsideDir(aTargetDir, entryName, outputPath)) {
1959 wxLogWarning(
"Skipping archive entry with path traversal attempt: " +
1966 archive_entry_copy_pathname_w(entry, outputPath.wc_str());
1968 archive_entry_copy_pathname(entry, outputPath.fn_str().data());
1971 if (aMTime.IsValid()) {
1972 archive_entry_set_mtime(entry,
static_cast<time_t
>(aMTime.GetTicks()), 0);
1975 r = archive_write_header(ext, entry);
1976 if (r < ARCHIVE_OK) {
1977 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1978 archive_error_string(ext)));
1980 if (r < ARCHIVE_WARN) {
1984 if (archive_entry_size(entry) > 0) {
1985 r = copy_data(a, ext);
1986 if (r < ARCHIVE_OK) {
1987 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1988 archive_error_string(ext)));
1990 if (r < ARCHIVE_WARN) {
1995 r = archive_write_finish_entry(ext);
1996 if (r < ARCHIVE_OK) {
1997 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1998 archive_error_string(ext)));
2000 if (r < ARCHIVE_WARN) {
2009 archive_read_close(a);
2010 archive_read_free(a);
2013 archive_write_close(ext);
2014 archive_write_free(ext);
2017 if (ok && aRemoveArchive) wxRemoveFile(aArchiveFile);
2021 wxUnusedVar(aArchiveFile);
2022 wxUnusedVar(aTargetDir);
2023 wxUnusedVar(aStripPath);
2024 wxUnusedVar(aMTime);
2025 wxUnusedVar(aRemoveArchive);
2031#if defined(CHARTDLDR_RAR_UNARR) || !defined(DLDR_USE_LIBARCHIVE)
2032ar_archive *ar_open_any_archive(ar_stream *stream,
const char *fileext) {
2033 ar_archive *ar = ar_open_rar_archive(stream);
2036 ar_open_zip_archive(stream, fileext && (strcmp(fileext,
".xps") == 0 ||
2037 strcmp(fileext,
".epub") == 0));
2038 if (!ar) ar = ar_open_7z_archive(stream);
2039 if (!ar) ar = ar_open_tar_archive(stream);
2043bool chartdldr_pi::ExtractUnarrFiles(
const wxString &aRarFile,
2044 const wxString &aTargetDir,
2045 bool aStripPath, wxDateTime aMTime,
2047 ar_stream *stream = NULL;
2048 ar_archive *ar = NULL;
2049 int entry_count = 1;
2050 int entry_skips = 0;
2054 stream = ar_open_file(aRarFile.c_str());
2056 wxLogError(
"Can not open file '" + aRarFile +
"'.");
2057 ar_close_archive(ar);
2061 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(),
'.'));
2063 wxLogError(
"Can not open archive '" + aRarFile +
"'.");
2064 ar_close_archive(ar);
2068 while (ar_parse_entry(ar)) {
2069 size_t size = ar_entry_get_size(ar);
2070 wxString name = ar_entry_get_name(ar);
2071 wxString originalName = name;
2073 wxFileName fn(name);
2078 if (fn.GetDirCount() > 0) {
2080 name = fn.GetFullPath();
2086 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2087 wxLogWarning(
"Skipping archive entry with path traversal attempt: " +
2093 wxFileName fn(name);
2094 if (!fn.DirExists()) {
2095 if (!wxFileName::Mkdir(fn.GetPath())) {
2096 wxLogError(
"Can not create directory '" + fn.GetPath() +
"'.");
2101 wxFileOutputStream file(name);
2103 wxLogError(
"Can not create file '" + name +
"'.");
2108 unsigned char buffer[1024];
2109 size_t count = size <
sizeof(buffer) ? size : sizeof(buffer);
2110 if (!ar_entry_uncompress(ar, buffer, count))
break;
2111 file.Write(buffer, count);
2115 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2117 wxLogError(
"Warning: Failed to uncompress... skipping");
2122 if (!ar_at_eof(ar)) {
2123 wxLogError(
"Error: Failed to parse entry %d!", entry_count);
2126 ar_close_archive(ar);
2129 if (aRemoveRar) wxRemoveFile(aRarFile);
2134 setlocale(LC_NUMERIC,
"C");
2141bool chartdldr_pi::ExtractZipFiles(
const wxString &aZipFile,
2142 const wxString &aTargetDir,
bool aStripPath,
2143 wxDateTime aMTime,
bool aRemoveZip) {
2148 if (aStripPath) nStrip = 1;
2150 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip,
true);
2152 std::unique_ptr<wxZipEntry> entry(
new wxZipEntry());
2155 wxLogMessage(
"chartdldr_pi: Going to extract '" + aZipFile +
"'.");
2156 wxFileInputStream in(aZipFile);
2159 wxLogMessage(
"Can not open file '" + aZipFile +
"'.");
2163 wxZipInputStream zip(in);
2166 while (entry.reset(zip.GetNextEntry()), entry) {
2168 wxString name = entry->GetName();
2171 wxFileName fn(name);
2176 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2177 name = fn.GetFullPath();
2181 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2182 wxLogWarning(
"Skipping zip entry with path traversal attempt: " +
2189 if (entry->IsDir()) {
2190 int perm = entry->GetMode();
2191 if (!wxFileName::Mkdir(name, perm, wxPATH_MKDIR_FULL)) {
2192 wxLogMessage(
"Can not create directory '" + name +
"'.");
2197 if (!zip.OpenEntry(*entry)) {
2198 wxLogMessage(
"Can not open zip entry '" + entry->GetName() +
"'.");
2202 if (!zip.CanRead()) {
2203 wxLogMessage(
"Can not read zip entry '" + entry->GetName() +
"'.");
2208 wxFileName fn(name);
2209 if (!fn.DirExists()) {
2210 if (!wxFileName::Mkdir(fn.GetPath())) {
2211 wxLogMessage(
"Can not create directory '" + fn.GetPath() +
"'.");
2217 wxFileOutputStream file(name);
2220 wxLogMessage(
"Can not create file '" + name +
"'.");
2225 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2232 if (aRemoveZip) wxRemoveFile(aZipFile);
2238ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2242 fn.AppendDir(
"plugins");
2243 fn.AppendDir(
"chartdldr_pi");
2244 fn.AppendDir(
"data");
2253 p_buttonIconList =
new wxImageList(w, h);
2255 fn.SetFullName(
"button_right.png");
2256 wxImage im1(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2257 im1.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2258 p_buttonIconList->Add(im1);
2260 fn.SetFullName(
"button_right.png");
2261 wxImage im2(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2262 im2.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2263 p_buttonIconList->Add(im2);
2265 fn.SetFullName(
"button_down.png");
2266 wxImage im3(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2267 im3.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2268 p_buttonIconList->Add(im3);
2270 fn.SetFullName(
"button_down.png");
2271 wxImage im4(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2272 im4.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2273 p_buttonIconList->Add(im4);
2275 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2277 p_iconList =
new wxImageList(w, h);
2279 fn.SetFullName(
"folder.png");
2280 wxImage ima(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2281 ima.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2282 p_iconList->Add(ima);
2284 fn.SetFullName(
"file.png");
2285 wxImage imb(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2286 imb.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2287 p_iconList->Add(imb);
2289 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2292 m_treeCtrlPredefSrcs->SetIndent(w);
2297 m_nbChoice->SetSelection(0);
2305bool ChartDldrGuiAddSourceDlg::LoadSources() {
2306 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot(
"root");
2310 fn.SetFullName(
"chartdldr_pi-chart_sources.xml");
2311 if (!fn.FileExists()) {
2313 fn.AppendDir(
"plugins");
2314 fn.AppendDir(
"chartdldr_pi");
2315 fn.AppendDir(
"data");
2316 fn.SetFullName(
"chart_sources.xml");
2317 if (!fn.FileExists()) {
2319 wxString::Format(
"Error: chartdldr_pi::LoadSources() %s not found!",
2320 fn.GetFullPath().c_str()));
2324 wxString path = fn.GetFullPath();
2327 bool ret = doc->load_file(path.mb_str());
2332 element = element.next_sibling()) {
2333 if (!strcmp(element.name(),
"sections")) {
2334 LoadSections(tree, element);
2342bool ChartDldrGuiAddSourceDlg::LoadSections(
const wxTreeItemId &root,
2345 element = element.next_sibling()) {
2346 if (!strcmp(element.name(),
"section")) {
2347 LoadSection(root, element);
2353bool ChartDldrGuiAddSourceDlg::LoadSection(
const wxTreeItemId &root,
2357 element = element.next_sibling()) {
2358 if (!strcmp(element.name(),
"name")) {
2359 item = m_treeCtrlPredefSrcs->AppendItem(
2360 root, wxString::FromUTF8(element.first_child().value()), 0, 0);
2363 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2365 if (!strcmp(element.name(),
"sections")) LoadSections(item, element);
2366 if (!strcmp(element.name(),
"catalogs")) LoadCatalogs(item, element);
2372bool ChartDldrGuiAddSourceDlg::LoadCatalogs(
const wxTreeItemId &root,
2375 element = element.next_sibling()) {
2376 if (!strcmp(element.name(),
"catalog")) LoadCatalog(root, element);
2382bool ChartDldrGuiAddSourceDlg::LoadCatalog(
const wxTreeItemId &root,
2384 wxString name, location, dir;
2386 element = element.next_sibling()) {
2387 if (!strcmp(element.name(),
"name"))
2388 name = wxString::FromUTF8(element.first_child().value());
2389 else if (!strcmp(element.name(),
"location"))
2390 location = wxString::FromUTF8(element.first_child().value());
2391 else if (!strcmp(element.name(),
"dir"))
2392 dir = wxString::FromUTF8(element.first_child().value());
2395 wxTreeItemId
id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2398 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(
id, *pFont);
2403ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() =
default;
2405wxString ChartDldrGuiAddSourceDlg::FixPath(
const wxString &path) {
2406 wxString sep(wxFileName::GetPathSeparator());
2408 s.Replace(
"/", sep,
true);
2409 s.Replace(USERDATA, m_base_path);
2410 s.Replace(sep + sep, sep);
2414void ChartDldrGuiAddSourceDlg::OnChangeType(wxCommandEvent &event) {
2415 m_treeCtrlPredefSrcs->Enable(m_nbChoice->GetSelection() == 0);
2416 m_tSourceName->Enable(m_nbChoice->GetSelection() == 1);
2417 m_tChartSourceUrl->Enable(m_nbChoice->GetSelection() == 1);
2420void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2421 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2422 auto *cs = (
ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2424 m_dirExpanded = FixPath(cs->GetDir());
2426 m_tSourceName->SetValue(cs->GetName());
2427 m_tChartSourceUrl->SetValue(cs->GetUrl());
2428 if (m_tcChartDirectory->GetValue() == m_last_path) {
2429 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2430 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2432 m_buttonChartDirectory->Enable();
2433 m_last_path = m_tcChartDirectory->GetValue();
2439void ChartDldrGuiAddSourceDlg::SetSourceEdit(std::unique_ptr<ChartSource> &cs) {
2440 m_nbChoice->SetSelection(1);
2441 m_tChartSourceUrl->Enable();
2442 m_treeCtrlPredefSrcs->Disable();
2443 m_tSourceName->SetValue(cs->GetName());
2444 m_tChartSourceUrl->SetValue(cs->GetUrl());
2445 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2446 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2448 m_buttonChartDirectory->Enable();
2451ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2454ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() =
default;
2456void ChartDldrPrefsDlgImpl::SetPath(
const wxString &path) {
2464 m_tcDefaultDir->SetValue(path);
2467void ChartDldrPrefsDlgImpl::GetPreferences(
bool &preselect_new,
2468 bool &preselect_updated,
2469 bool &bulk_update) {
2470 preselect_new = m_cbSelectNew->GetValue();
2471 preselect_updated = m_cbSelectUpdated->GetValue();
2472 bulk_update = m_cbBulkUpdate->GetValue();
2474void ChartDldrPrefsDlgImpl::SetPreferences(
bool preselect_new,
2475 bool preselect_updated,
2477 m_cbSelectNew->SetValue(preselect_new);
2478 m_cbSelectUpdated->SetValue(preselect_updated);
2479 m_cbBulkUpdate->SetValue(bulk_update);
2482void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2485 if (m_nbChoice->GetSelection() == 0) {
2486 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2487 if (m_treeCtrlPredefSrcs->GetSelection().IsOk()) {
2488 auto *cs = (
ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2491 _(
"You must select one of the predefined chart sources or create "
2492 "one of your own.\n");
2495 _(
"You must select one of the predefined chart sources or create one "
2498 if (m_nbChoice->GetSelection() == 1 && m_tSourceName->GetValue().empty())
2499 msg += _(
"The chart source must have a name.\n");
2500 wxURI url(m_tChartSourceUrl->GetValue());
2501 if (m_nbChoice->GetSelection() == 1 &&
2502 (m_tChartSourceUrl->GetValue().empty() ||
2503 !ValidateUrl(m_tChartSourceUrl->GetValue())))
2504 msg += _(
"The chart source must have a valid URL.\n");
2505 if (m_tcChartDirectory->GetValue().empty())
2506 msg += _(
"You must select a local folder to store the charts.\n");
2507 else if (!wxDirExists(m_tcChartDirectory->GetValue()))
2508 if (!wxFileName::Mkdir(m_tcChartDirectory->GetValue(), 0755,
2510 msg += wxString::Format(_(
"Directory %s can't be created."),
2511 m_tcChartDirectory->GetValue().c_str()) +
2516 wxOK | wxCENTRE | wxICON_ERROR);
2519 SetReturnCode(wxID_OK);
2524void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2525 SetReturnCode(wxID_CANCEL);
2526 EndModal(wxID_CANCEL);
2529void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2530 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2531 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2532 wxPATH_MKDIR_FULL)) {
2535 wxString::Format(_(
"Directory %s can't be created."),
2536 m_tcDefaultDir->GetValue().c_str()),
2537 _(
"Chart Downloader"));
2543 g_pi->UpdatePrefs(
this);
2553void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2555 EndModal(wxID_CANCEL);
2559void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2564bool ChartDldrGuiAddSourceDlg::ValidateUrl(
const wxString &Url,
2569 "^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$");
2575 "^https?\\://[a-zA-Z0-9\\./_-]*$");
2577 return re.Matches(Url);
2585 switch (ev.getDLEventCondition()) {
2587 m_is_transfer_complete =
true;
2593 if (ev.getTransferred() > m_transferred_size) {
2594 m_total_size = ev.getTotal();
2595 m_transferred_size = ev.getTransferred();
OpenCPN Android support utilities.
double g_androidDPmm
Only used used by ANDROID
Chart Downloader Plugin – plugin implementation header.
Implementing ChartDldrPanel.
The PlugIn Class Definition.
wxBitmap * GetPlugInBitmap() override
Get the plugin's icon bitmap.
wxString GetShortDescription() override
Get a brief description of the plugin.
int GetAPIVersionMinor() override
Returns the minor version number of the plugin API that this plugin supports.
void ShowPreferencesDialog(wxWindow *parent) override
Shows the plugin preferences dialog.
int GetPlugInVersionMinor() override
Returns the minor version number of the plugin itself.
wxString GetCommonName() override
Get the plugin's common (short) name.
void OnSetupOptions() override
Allows plugin to add pages to global Options dialog.
bool DeInit() override
Clean up plugin resources.
wxString GetLongDescription() override
Get detailed plugin information.
int GetPlugInVersionMajor() override
Returns the major version number of the plugin itself.
void OnCloseToolboxPanel(int page_sel, int ok_apply_cancel) override
Handles preference page closure.
int Init() override
Initialize the plugin and declare its capabilities.
int GetAPIVersionMajor() override
Returns the major version number of the plugin API that this plugin supports.
Base class for OpenCPN plugins.
double g_androidDPmm
Only used used by ANDROID
@ OCPN_DL_EVENT_TYPE_PROGRESS
Download progress update.
@ OCPN_DL_EVENT_TYPE_END
Download has completed.
_OCPN_DLStatus
Status codes for HTTP file download operations.
@ OCPN_DL_NO_ERROR
Download completed successfully.
@ OCPN_DL_USER_TIMEOUT
Download timed out waiting for user action.
@ OCPN_DL_STARTED
Download has begun but not yet complete.
@ OCPN_DL_FAILED
Download failed (general error)
@ OCPN_DL_UNKNOWN
Unknown or uninitialized status.
@ OCPN_DL_ABORTED
Download was cancelled by user.
@ OCPN_DLDS_URL
The dialog shows the URL involved in the transfer.
@ OCPN_DLDS_AUTO_CLOSE
The dialog auto closes when transfer is complete.
@ OCPN_DLDS_CAN_ABORT
The transfer can be aborted by the user.
@ OCPN_DLDS_ESTIMATED_TIME
The dialog shows the estimated total time.
@ OCPN_DLDS_SIZE
The dialog shows the size of the resource to download/upload.
@ OCPN_DLDS_REMAINING_TIME
The dialog shows the remaining time.
@ OCPN_DLDS_SPEED
The dialog shows the transfer speed.
@ OCPN_DLDS_ELAPSED_TIME
The dialog shows the elapsed time.
@ OCPN_DLDS_CAN_PAUSE
The transfer can be paused.
#define INSTALLS_TOOLBOX_PAGE
Plugin will add pages to the toolbox/settings dialog.
#define WANTS_PREFERENCES
Plugin will add page(s) to global preferences dialog.
#define WANTS_CONFIG
Plugin requires persistent configuration storage.
@ PI_OPTIONS_PARENT_CHARTS
Charts section.
wxWindow * GetOCPNCanvasWindow()
Gets OpenCPN's main canvas window.
wxFont * OCPNGetFont(wxString TextElement, int default_size)
Gets a font for UI elements.
wxFileConfig * GetOCPNConfigObject()
Gets OpenCPN's configuration object.
wxFont GetOCPNGUIScaledFont_PlugIn(wxString item)
Gets a uniquely scaled font copy for responsive UI elements.
wxScrolledWindow * AddOptionsPage(OptionsParentPI parent, wxString title)
Adds a new preferences page to OpenCPN options dialog.
void AddChartDirectory(wxString &path)
Adds a chart directory to OpenCPN's chart database.
wxString * GetpSharedDataLocation()
Gets shared application data location.
wxArrayString GetChartDBDirArrayString()
Gets chart database directory list.
bool DeleteOptionsPage(wxScrolledWindow *page)
Remove a previously added options page.
bool AddLocaleCatalog(wxString catalog)
Adds a locale catalog for translations.
void ForceChartDBUpdate()
Forces an update of the chart database.
wxString * GetpPrivateApplicationDataLocation()
Gets private application data directory.
void OCPN_cancelDownloadFileBackground(long handle)
Cancels a background download.
int OCPNMessageBox_PlugIn(wxWindow *parent, const wxString &message, const wxString &caption, int style, int x, int y)
Shows a message box dialog.
_OCPN_DLStatus OCPN_downloadFileBackground(const wxString &url, const wxString &outputFile, wxEvtHandler *handler, long *handle)
Asynchronously downloads a file in the background.
_OCPN_DLStatus OCPN_downloadFile(const wxString &url, const wxString &outputFile, const wxString &title, const wxString &message, const wxBitmap &bitmap, wxWindow *parent, long style, int timeout_secs)
Synchronously download a file with progress dialog.
wxString GetWritableDocumentsDir()
Returns the platform-specific default documents directory.