OpenCPN Partial API docs
Loading...
Searching...
No Matches
chartdldr_pi.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2011 by Pavel Kalian *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#ifdef __ANDROID__
25#define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
26#endif
27
28#include "chartdldr_pi.h"
29
30#include <fstream>
31#include <memory>
32
33#ifdef DLDR_USE_LIBARCHIVE
34#include <archive.h>
35#include <archive_entry.h>
36#ifdef CHARTDLDR_RAR_UNARR
37#include "unarr.h"
38#endif
39#else
40#include "unarr.h"
41#endif
42
43#include <wx/wxprec.h>
44
45#ifndef WX_PRECOMP
46#include <wx/wx.h>
47#endif
48
49#include <wx/debug.h>
50#include <wx/dir.h>
51#include <wx/filename.h>
52// #include <wx/filesys.h>
53#include <wx/listctrl.h>
54#include <wx/progdlg.h>
55#include <wx/regex.h>
56#include <wx/sstream.h>
57// #include <wx/stdpaths.h>
58#include <wx/url.h>
59#include <wx/wfstream.h>
60// #include <wx/wfstream.h>
61#include <wxWTranslateCatalog.h>
62#include <wx/zipstrm.h>
63
64#include "icons.h"
65#include "version.h"
66
67#ifdef __ANDROID__
68#include <QtAndroidExtras/QAndroidJniObject>
69#include "qdebug.h"
70#include "android_support.h"
71#include "android_jvm.h"
72#include <jni.h>
73#endif
74
75#ifdef __WXMAC__
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
82#else
83#ifdef __ANDROID__
84
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
91
92#else
93
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
100
101#endif
102#endif // __WXMAC__
103
104#define CHART_DIR "Charts"
105
106extern "C" DECL_EXP opencpn_plugin *create_pi(void *ppimgr) {
107 return new chartdldr_pi(ppimgr);
108}
109
110extern "C" DECL_EXP void destroy_pi(opencpn_plugin *p) { delete p; }
111
113chartdldr_pi *g_pi;
114
115#ifdef __ANDROID__
116int g_Android_SDK_Version;
117#endif
118// the class factories, used to create and destroy instances of the PlugIn
119
120bool getDisplayMetrics(); // External in chartdldr_pi.h
121
122// Helper function to check if a path is safely inside the target directory
123// Returns true if normalizedPath is inside targetDir, false otherwise (path
124// traversal attempt)
125static bool IsPathInsideDir(const wxString &targetDir,
126 const wxString &entryName, wxString &outFullPath) {
127 // Construct the full path
128 wxString combinedPath = targetDir;
129 if (!combinedPath.EndsWith(wxFileName::GetPathSeparator())) {
130 combinedPath += wxFileName::GetPathSeparator();
131 }
132 combinedPath += entryName;
133
134 // Normalize the combined path to resolve any ".." components
135 wxFileName fn(combinedPath);
136 fn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG);
137 outFullPath = fn.GetFullPath();
138
139 // Normalize target dir for comparison
140 wxFileName targetFn(targetDir);
141 targetFn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE |
142 wxPATH_NORM_LONG);
143 wxString normalizedTarget = targetFn.GetFullPath();
144
145 // Ensure target ends with separator for proper prefix matching
146 if (!normalizedTarget.EndsWith(wxFileName::GetPathSeparator())) {
147 normalizedTarget += wxFileName::GetPathSeparator();
148 }
149
150 // Check if the normalized path starts with the target directory
151 // This catches all path traversal attempts including "../", absolute paths,
152 // etc.
153 if (outFullPath.StartsWith(normalizedTarget)) {
154 return true;
155 }
156
157 // Also allow if it's exactly the target directory (for directory entries)
158 if (outFullPath == targetFn.GetFullPath()) {
159 return true;
160 }
161
162 return false;
163}
164
165static wxString FormatBytes(double bytes) {
166 if (bytes <= 0) return "?";
167 return wxString::Format("%.1fMB", bytes / 1024 / 1024);
168}
169
170static wxString FormatBytes(long bytes) {
171 return FormatBytes(static_cast<double>(bytes));
172}
173
174static bool IsDLDirWritable(const wxFileName &fn) {
175#ifndef __ANDROID__
176 return fn.IsDirWritable();
177#else
178 if (g_Android_SDK_Version >= 30) { // scoped storage?
179 // Use a simple test here
180 return (fn.GetFullPath().Contains("org.opencpn.opencpn")); // fast test
181 } else
182 return fn.IsDirWritable();
183#endif
184}
185
186static void SetBackColor(wxWindow *ctrl, const wxColour &col) {
187 static int depth = 0; // recursion count
188 if (depth == 0) { // only for the window root, not for every child
189
190 ctrl->SetBackgroundColour(col);
191 }
192
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();
197
198 if (dynamic_cast<wxListBox *>(win))
199 dynamic_cast<wxListBox *>(win)->SetBackgroundColour(col);
200
201 else if (dynamic_cast<wxTextCtrl *>(win))
202 dynamic_cast<wxTextCtrl *>(win)->SetBackgroundColour(col);
203
204 // else if( win->IsKindOf( CLASSINFO(wxStaticText) ) )
205 // ( (wxStaticText*) win )->SetForegroundColour( uitext );
206
207 else if (dynamic_cast<wxChoice *>(win))
208 dynamic_cast<wxChoice *>(win)->SetBackgroundColour(col);
209
210 else if (dynamic_cast<wxComboBox *>(win))
211 dynamic_cast<wxComboBox *>(win)->SetBackgroundColour(col);
212
213 else if (dynamic_cast<wxRadioButton *>(win))
214 dynamic_cast<wxRadioButton *>(win)->SetBackgroundColour(col);
215
216 else if (dynamic_cast<wxScrolledWindow *>(win)) {
217 dynamic_cast<wxScrolledWindow *>(win)->SetBackgroundColour(col);
218 }
219
220 else if (dynamic_cast<wxButton *>(win)) {
221 dynamic_cast<wxButton *>(win)->SetBackgroundColour(col);
222 }
223
224 else {
225 ;
226 }
227
228 if (win->GetChildren().GetCount() > 0) {
229 depth++;
230 wxWindow *w = win;
231 SetBackColor(w, col);
232 depth--;
233 }
234 }
235}
236
237//---------------------------------------------------------------------------------------------------------
238//
239// ChartDldr PlugIn Implementation
240//
241//---------------------------------------------------------------------------------------------------------
242
243//---------------------------------------------------------------------------------------------------------
244//
245// PlugIn initialization and de-init
246//
247//---------------------------------------------------------------------------------------------------------
248
249chartdldr_pi::chartdldr_pi(void *ppimgr) : opencpn_plugin_113(ppimgr) {
250 // Create the PlugIn icons
251 initialize_images();
252
253 m_parent_window = nullptr;
254 m_chart_source = nullptr;
255 m_config = 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 = "";
263
264 g_pi = this;
265}
266
268 AddLocaleCatalog(PLUGIN_CATALOG_NAME);
269
270 // Get a pointer to the opencpn display canvas, to use as a parent for the
271 // POI Manager dialog
272 m_parent_window = GetOCPNCanvasWindow();
273
274 // Get a pointer to the opencpn configuration object
275 m_config = GetOCPNConfigObject();
276 m_options_page = nullptr;
277
278 m_chart_source = nullptr;
279
280#ifdef __ANDROID__
281 androidGetSDKVersion();
282#endif
283
284 // And load the configuration items
285 LoadConfig();
286
287 getDisplayMetrics();
288
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();
294 if (!s2.IsEmpty()) // scrub empty sources.
295 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
296 }
298}
299
301 wxLogMessage("chartdldr_pi: DeInit");
302
303 m_ChartSources.clear();
304 // wxDELETE(m_pChartSource);
305 /* TODO: Seth */
306 // dialog->Close();
307 // dialog->Destroy();
308 // wxDELETE(dialog);
309 /* We must delete remaining page if the plugin is disabled while in Options
310 * dialog */
311 if (m_options_page) {
312 if (DeleteOptionsPage(m_options_page)) m_options_page = nullptr;
313 // TODO: any other memory leak?
314 }
315 return true;
316}
317
318int chartdldr_pi::GetAPIVersionMajor() { return MY_API_VERSION_MAJOR; }
319
320int chartdldr_pi::GetAPIVersionMinor() { return MY_API_VERSION_MINOR; }
321
322int chartdldr_pi::GetPlugInVersionMajor() { return PLUGIN_VERSION_MAJOR; }
323
324int chartdldr_pi::GetPlugInVersionMinor() { return PLUGIN_VERSION_MINOR; }
325
326wxBitmap *chartdldr_pi::GetPlugInBitmap() { return _img_chartdldr_pi; }
327
328wxString chartdldr_pi::GetCommonName() { return _("ChartDownloader"); }
329
331 return _("Chart Downloader PlugIn for OpenCPN");
332}
333
335 return _(
336 "Chart Downloader PlugIn for OpenCPN\n\
337Manages chart downloads and updates from sources supporting\n\
338NOAA Chart Catalog format");
339}
340
342 m_options_page =
343 AddOptionsPage(PI_OPTIONS_PARENT_CHARTS, _("Chart Downloader"));
344 if (!m_options_page) {
345 wxLogMessage("Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!");
346 return;
347 }
348 auto *sizer = new wxBoxSizer(wxVERTICAL);
349 m_options_page->SetSizer(sizer);
350
351 m_dldrpanel =
352 new ChartDldrPanelImpl(this, m_options_page, wxID_ANY, wxDefaultPosition,
353 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
354
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();
359}
360
361void chartdldr_pi::OnCloseToolboxPanel(int page_sel, int ok_apply_cancel) {
362 /* TODO: Seth */
363 m_dldrpanel->CancelDownload();
364#ifndef __ANDROID__
366 0); // Stop the thread, is something like this needed on Android as well?
367#endif
368 m_selected_source = m_dldrpanel->GetSelectedCatalog();
369 SaveConfig();
370}
371
372bool chartdldr_pi::LoadConfig() {
373 auto *pConf = (wxFileConfig *)m_config;
374
375 if (pConf) {
376 pConf->SetPath("/Settings/ChartDnldr");
377 pConf->Read("ChartSources", &m_schartdldr_sources, "");
378 pConf->Read("Source", &m_selected_source, -1);
379
380 wxFileName fn(GetWritableDocumentsDir(), "");
381 fn.AppendDir(CHART_DIR);
382
383 pConf->Read("BaseChartDir", &m_base_chart_dir, fn.GetPath());
384 wxLogMessage("chartdldr_pi:m_base_chart_dir: " + m_base_chart_dir);
385
386 // Check to see if the directory is writeable, esp. on App updates.
387 wxFileName testFN(m_base_chart_dir);
388 if (!IsDLDirWritable(testFN)) {
389 wxLogMessage(
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);
394 }
395
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);
399 return true;
400 } else
401 return false;
402}
403
404bool chartdldr_pi::SaveConfig() {
405 auto *pConf = (wxFileConfig *)m_config;
406
407 m_schartdldr_sources.Clear();
408
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()));
413 }
414
415 if (pConf) {
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);
423
424 return true;
425 } else
426 return false;
427}
428
430 auto *dialog = new ChartDldrPrefsDlgImpl(parent);
431
432 wxFont fo = GetOCPNGUIScaledFont_PlugIn(_("Dialog"));
433 dialog->SetFont(fo);
434
435#ifdef __ANDROID__
436 if (m_parent_window) {
437 int xmax = m_parent_window->GetSize().GetWidth();
438 int ymax = m_parent_window->GetParent()
439 ->GetSize()
440 .GetHeight(); // This would be the Options dialog itself
441 dialog->SetSize(xmax, ymax);
442 dialog->Layout();
443
444 dialog->Move(0, 0);
445 }
446
447 wxColour cl = wxColour(214, 218, 222);
448 SetBackColor(dialog, cl);
449#endif
450
451 dialog->SetPath(m_base_chart_dir);
452 dialog->SetPreferences(m_reselect_new, m_reselect_updated,
453 m_allow_bulk_update);
454
455 dialog->ShowModal();
456 dialog->Destroy();
457}
458
459void chartdldr_pi::UpdatePrefs(ChartDldrPrefsDlgImpl *dialog) {
460 m_base_chart_dir = dialog->GetPath();
461 dialog->GetPreferences(m_reselect_new, m_reselect_updated,
462 m_allow_bulk_update);
463 SaveConfig();
464 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
465}
466
467bool getDisplayMetrics() {
468#ifdef __ANDROID__
469
470 g_androidDPmm = 4.0; // nominal default
471
472 // Get a reference to the running native activity
473 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
474 "org/qtproject/qt5/android/QtNative", "activity",
475 "()Landroid/app/Activity;");
476
477 if (!activity.isValid()) {
478 return false;
479 }
480
481 // Call the desired method
482 QAndroidJniObject data =
483 activity.callObjectMethod("getDisplayMetrics", "()Ljava/lang/String;");
484
485 wxString return_string;
486 jstring s = data.object<jstring>();
487
488 // Need a Java environment to decode the resulting string
489 JNIEnv *jenv;
490 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
491 // qDebug() << "GetEnv failed.";
492 } else {
493 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
494 return_string = wxString(ret_string, wxConvUTF8);
495 }
496
497 // Return string may have commas instead of periods, if using Euro locale
498 // We just fix it here...
499 return_string.Replace(",", ".");
500
501 // wxLogMessage("Metrics:" + return_string);
502 // wxSize screen_size = ::wxGetDisplaySize();
503 // wxString msg;
504 // msg.Printf("wxGetDisplaySize(): %d %d", screen_size.x,
505 // screen_size.y); wxLogMessage(msg);
506
507 double density = 1.0;
508 wxStringTokenizer tk(return_string, ";");
509 if (tk.HasMoreTokens()) {
510 wxString token = tk.GetNextToken(); // xdpi
511 token = tk.GetNextToken(); // density
512
513 long b = ::wxGetDisplaySize().y;
514 token.ToDouble(&density);
515
516 token = tk.GetNextToken(); // ldpi
517
518 token = tk.GetNextToken(); // width
519 token = tk.GetNextToken(); // height - statusBarHeight
520 token = tk.GetNextToken(); // width
521 token = tk.GetNextToken(); // height
522 token = tk.GetNextToken(); // dm.widthPixels
523 token = tk.GetNextToken(); // dm.heightPixels
524
525 token = tk.GetNextToken(); // actionBarHeight
526 long abh;
527 token.ToLong(&abh);
528 // g_ActionBarHeight = wxMax(abh, 50);
529
530 // qDebug() << "g_ActionBarHeight" << abh << g_ActionBarHeight;
531 }
532
533 double ldpi = 160. * density;
534
535 // double maxDim = wxMax(::wxGetDisplaySize().x, ::wxGetDisplaySize().y);
536 // ret = (maxDim / ldpi) * 25.4;
537
538 // msg.Printf("Android Auto Display Size (mm, est.): %g", ret);
539 // wxLogMessage(msg);
540
541 // Save some items as global statics for convenience
542 g_androidDPmm = ldpi / 25.4;
543 // g_androidDensity = density;
544
545 // qDebug() << "PI Metrics" << g_androidDPmm << density;
546 return true;
547#else
548
549 return true;
550#endif
551}
552
553ChartSource::ChartSource(const wxString &name, const wxString &url,
554 const wxString &localdir) {
555 m_name = name;
556 m_url = url;
557 m_dir = localdir;
558 m_update_data.clear();
559}
560
561ChartSource::~ChartSource() { m_update_data.clear(); }
562
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
568
569enum { ThreadId = wxID_HIGHEST + 1 };
570
571BEGIN_EVENT_TABLE(ChartDldrPanelImpl, ChartDldrPanel)
572END_EVENT_TABLE()
573
574void ChartDldrPanelImpl::OnPopupClick(wxCommandEvent &evt) {
575 switch (evt.GetId()) {
576 case ID_MNU_SELALL:
577 CheckAllCharts(true);
578 break;
579 case ID_MNU_DELALL:
580 CheckAllCharts(false);
581 break;
582 case ID_MNU_INVSEL:
583 InvertCheckAllCharts();
584 break;
585 case ID_MNU_SELUPD:
586 CheckUpdatedCharts(true);
587 break;
588 case ID_MNU_SELNEW:
589 CheckNewCharts(true);
590 break;
591 default:
592 assert(false && "Illegal popup menu id");
593 break;
594 }
595}
596
597void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
598 wxMenu menu;
599
600 wxPoint mouseScreen = wxGetMousePosition();
601 wxPoint mouseClient = ScreenToClient(mouseScreen);
602
603#ifdef __ANDROID__
604 wxFont *pf = OCPNGetFont(_("Menu"));
605
606 // add stuff
607 wxMenuItem *item1 = new wxMenuItem(&menu, ID_MNU_SELALL, _("Select all"));
608 item1->SetFont(*pf);
609 menu.Append(item1);
610
611 wxMenuItem *item2 = new wxMenuItem(&menu, ID_MNU_DELALL, _("Deselect all"));
612 item2->SetFont(*pf);
613 menu.Append(item2);
614
615 wxMenuItem *item3 =
616 new wxMenuItem(&menu, ID_MNU_INVSEL, _("Invert selection"));
617 item3->SetFont(*pf);
618 menu.Append(item3);
619
620 wxMenuItem *item4 = new wxMenuItem(&menu, ID_MNU_SELUPD, _("Select updated"));
621 item4->SetFont(*pf);
622 menu.Append(item4);
623
624 wxMenuItem *item5 = new wxMenuItem(&menu, ID_MNU_SELNEW, _("Select new"));
625 item5->SetFont(*pf);
626 menu.Append(item5);
627
628#else
629
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"), "");
635
636#endif
637
638 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
639 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick,
640 nullptr, this);
641 // and then display
642 PopupMenu(&menu, mouseClient.x, mouseClient.y);
643}
644
645void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
646 if (!m_plugin->m_chart_source) return;
647#ifdef __WXGTK__
648 wxExecute(wxString::Format("xdg-open %s",
649 m_plugin->m_chart_source->GetDir().c_str()));
650#endif
651#ifdef __WXMAC__
652 wxExecute(
653 wxString::Format("open %s", m_plugin->m_chart_source->GetDir().c_str()));
654#endif
655#ifdef __WXMSW__
656 wxExecute(wxString::Format("explorer %s",
657 m_plugin->m_chart_source->GetDir().c_str()));
658#endif
659}
660
661void ChartDldrPanelImpl::SetSource(int id) {
662 m_plugin->SetSourceId(id);
663
664 m_bDeleteSource->Enable(id >= 0);
665 m_bUpdateChartList->Enable(id >= 0);
666 m_bEditSource->Enable(id >= 0);
667
668 // TODO: DAN - Need to optimze to only update the chart list if needed.
669 // Right now it updates multiple times unnecessarily.
670 CleanForm();
671 if (id >= 0 && id < (int)m_plugin->m_ChartSources.size()) {
672 ::wxBeginBusyCursor(); // wxSetCursor(wxCURSOR_WAIT);
673 // wxYield();
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(
682 _("Charts: %s"),
683 (cs->GetName() + _(" from ") + url.BuildURI() + " -> " + cs->GetDir())
684 .c_str()));
685 if (::wxIsBusy()) ::wxEndBusyCursor();
686 } else {
687 m_plugin->m_chart_source = nullptr;
688 m_chartsLabel->SetLabel(_("Charts"));
689 }
690}
691
692void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
693 int i = GetSelectedCatalog();
694 if (i >= 0) SetSource(i);
695 event.Skip();
696}
697
698void ChartDldrPanelImpl::SetBulkUpdate(bool bulk_update) {
699 m_bUpdateAllCharts->Enable(bulk_update);
700 m_bUpdateAllCharts->Show(bulk_update);
701 Layout();
702 m_parent->Layout();
703}
704
705void ChartDldrPanelImpl::CleanForm() {
706#if defined(CHART_LIST)
707 clearChartList();
708#else
709 m_scrollWinChartList->ClearBackground();
710#endif /* CHART_LIST */
711 // m_stCatalogInfo->Show( false );
712}
713
714void ChartDldrPanelImpl::FillFromFile(const wxString &url, const wxString &dir,
715 bool selnew, bool selupd) {
716 // load if exists
717 wxStringTokenizer tk(url, "/");
718 wxString file;
719 do {
720 file = tk.GetNextToken();
721 } while (tk.HasMoreTokens());
722 wxFileName fn;
723 fn.SetFullName(file);
724 fn.SetPath(dir);
725 wxString path = fn.GetFullPath();
726 if (wxFileExists(path)) {
727 m_plugin->m_chart_catalog.LoadFromFile(path);
728 // m_tChartSourceInfo->SetValue(pPlugIn->m_pChartCatalog.GetDescription());
729 // fill in the rest of the form
730
731 m_updated_charts = 0;
732 m_new_charts = 0;
733
734#if !defined(CHART_LIST)
735 // Clear any existing panels
736 m_panelArray.clear();
737 m_scrollWinChartList->ClearBackground();
738#endif /* CHART_LIST */
739
740 for (size_t i = 0; i < m_plugin->m_chart_catalog.charts.size(); i++) {
741 wxString status;
742 wxString latest;
743 bool bcheck = false;
744 wxString file_ =
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_)) {
748 m_new_charts++;
749 status = _("New");
750 if (selnew) bcheck = true;
751 } else {
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())) {
755 m_updated_charts++;
756 status = _("Out of date");
757 if (selupd) bcheck = true;
758 } else {
759 status = _("Up to date");
760 }
761 }
762 latest =
763 m_plugin->m_chart_catalog.charts.at(i)->GetUpdateDatetime().Format(
764 "%Y-%m-%d");
765
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));
771 data.push_back(
772 wxVariant(m_plugin->m_chart_catalog.charts.at(i)->GetChartTitle()));
773 getChartList()->AppendItem(data);
774#else
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,
781 this);
782
783 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
784 m_panelArray.push_back(std::move(pC));
785#endif /* CHART_LIST */
786 }
787
788#if !defined(CHART_LIST) // wxDataViewListCtrl handles all of this AFAIK: Dan
789 m_scrollWinChartList->ClearBackground();
790 m_scrollWinChartList->FitInside();
791 m_scrollWinChartList->GetSizer()->Layout();
792 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));
797#else
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()));
802#endif /* CHART_LIST */
803 }
804}
805
806bool ChartSource::ExistsLocally(const wxString &chart_number,
807 const wxString &filename) {
808 wxASSERT(this);
809
810 wxStringTokenizer tk(filename, ".");
811 wxString file = tk.GetNextToken().MakeLower();
812
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())) !=
817 m_update_data.end();
818 }
819 for (size_t i = 0; i < m_localfiles.Count(); i++) {
820 if (m_localfiles.Item(i) == file) return true;
821 }
822 return false;
823}
824
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())];
835 bool needsUpdate =
836 chartNumberTime < validTime && updateFileTime < validTime;
837 if (wxLOG_Debug <= wxLog::GetLogLevel()) {
838 // Show these only if user has selected loglevel debug, otherwise save a
839 // few cpu cycles
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"));
848 }
849 return needsUpdate;
850 }
851
852 bool update_candidate = false;
853
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;
858 } else
859 return false;
860 }
861 }
862 return update_candidate;
863}
864
865int ChartDldrPanelImpl::GetSelectedCatalog() {
866 long item =
867 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
868 return static_cast<int>(item);
869}
870
871void ChartDldrPanelImpl::SelectCatalog(int item) {
872 if (item >= 0) {
873 m_bDeleteSource->Enable();
874 m_bEditSource->Enable();
875 m_bUpdateChartList->Enable();
876 } else {
877 m_bDeleteSource->Disable();
878 m_bEditSource->Disable();
879 m_bUpdateChartList->Disable();
880 }
881 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
882 wxLIST_STATE_SELECTED);
883}
884
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."),
894 _("Error"));
895 return;
896 }
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(
904 id, 1,
905 m_plugin->m_chart_catalog.GetReleaseDate().Format("%Y-%m-%d %H:%M"));
906 m_lbChartSources->SetItem(id, 2, path);
907#ifdef __ANDROID__
908 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
909 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
910 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
911#endif
912 }
913 }
914}
915
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(
921 this,
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(
928 this,
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(
935 this,
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;
940 }
941 m_updating_all = true;
942 m_cancelled = false;
943 // Flip to the list of charts so user can observe the download progress
944 int oldPage = m_DLoadNB->SetSelection(1);
945 for (long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
946 chartIndex++) {
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;
954 }
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)
960 this,
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);
966 if (attempted_to_update > failed_to_update) ForceChartDBUpdate();
967 m_updating_all = false;
968 m_cancelled = false;
969 // Flip back to the original page
970 m_DLoadNB->SetSelection(oldPage);
971}
972
973void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
974 // TODO: check if everything exists and we can write to the output dir etc.
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."),
982 _("Error"));
983 return;
984 }
985
986 wxStringTokenizer tk(url.GetPath(), "/");
987 wxString file;
988 do {
989 file = tk.GetNextToken();
990 } while (tk.HasMoreTokens());
991 wxFileName fn;
992 fn.SetFullName(file);
993 fn.SetPath(cs->GetDir());
994 if (!wxDirExists(cs->GetDir())) {
995 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
997 this,
998 wxString::Format(_("Directory %s can't be created."),
999 cs->GetDir().c_str()),
1000 _("Chart Downloader"));
1001 return;
1002 }
1003 }
1004
1005 bool bok = false;
1006
1007#ifdef __ANDROID__
1008 wxString file_URI = "file://" + fn.GetFullPath();
1009
1010 // wxFile testFile(tfn.GetFullPath().c_str(), wxFile::write);
1011 // if(!testFile.IsOpened()){
1012 // wxMessageBox(this, wxString::Format(_("File %s can't be written.
1013 // \nChoose a writable folder for Chart Downloader file storage."),
1014 // tfn.GetFullPath().c_str()), _("Chart Downloader")); return;
1015 // }
1016 // testFile.Close();
1017 // ::wxRemoveFile(tfn.GetFullPath());
1018
1020 cs->GetUrl(), file_URI, _("Downloading file"),
1021 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
1026 10);
1027 bok = true;
1028
1029#else
1030 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
1031 wxString file_URI = tfn.GetFullPath();
1032
1034 cs->GetUrl(), file_URI, _("Downloading file"),
1035 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
1040 10);
1041
1042 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
1043 wxRemoveFile(tfn.GetFullPath());
1044
1045#endif
1046
1047 // wxLogMessage("chartdldr_pi: OCPN_downloadFile done:");
1048
1049 switch (ret) {
1050 case OCPN_DL_NO_ERROR: {
1051 if (bok) {
1052 int id = GetSelectedCatalog();
1053 SetSource(id);
1054
1055 m_lbChartSources->SetItem(id, 0, m_plugin->m_chart_catalog.title);
1056 m_lbChartSources->SetItem(
1057 id, 1,
1058 m_plugin->m_chart_catalog.GetReleaseDate().Format(
1059 "%Y-%m-%d %H:%M"));
1060 m_lbChartSources->SetItem(id, 2, cs->GetDir());
1061
1062 } else
1064 this,
1065 wxString::Format(_("Failed to Find New Catalog: %s "),
1066 url.BuildURI().c_str()),
1067 _("Chart Downloader"), wxOK | wxICON_ERROR);
1068 break;
1069 }
1070 case OCPN_DL_FAILED: {
1072 this,
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);
1077 break;
1078 }
1079
1081 case OCPN_DL_ABORTED: {
1082 m_cancelled = true;
1083 break;
1084 }
1085
1086 case OCPN_DL_UNKNOWN:
1087 case OCPN_DL_STARTED: {
1088 break;
1089 }
1090
1091 default:
1092 wxASSERT(false); // This should never happen because we handle all
1093 // possible cases of ret
1094 }
1095
1096 if ((ret == OCPN_DL_NO_ERROR) && bok) m_DLoadNB->SetSelection(1);
1097}
1098
1099void ChartSource::GetLocalFiles() {
1100 if (!UpdateDataExists() || m_update_data.empty()) {
1101 auto *allFiles = new wxArrayString;
1102 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1103 m_localdt.clear();
1104 m_localfiles.Clear();
1105 wxDateTime ct, mt, at;
1106 wxString name;
1107 for (size_t i = 0; i < allFiles->Count(); i++) {
1108 wxFileName fn(allFiles->Item(i));
1109 name = fn.GetFullName().Lower();
1110 // Only add unique files names to the local list.
1111 // This is safe because all chart names within a catalog
1112 // are necessarily unique.
1113 if (!ExistsLocally("", name)) {
1114 fn.GetTimes(&at, &mt, &ct);
1115 m_localdt.push_back(mt);
1116 m_localfiles.Add(fn.GetName().Lower());
1117
1118 wxStringTokenizer tk(name, ".");
1119 wxString file = tk.GetNextToken().MakeLower();
1120 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1121 }
1122 }
1123 allFiles->Clear();
1124 wxDELETE(allFiles);
1125 SaveUpdateData();
1126 } else {
1127 LoadUpdateData();
1128 }
1129}
1130
1131bool ChartSource::UpdateDataExists() {
1132 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1133 UPDATE_DATA_FILENAME);
1134}
1135
1136void ChartSource::LoadUpdateData() {
1137 m_update_data.clear();
1138 wxString fn =
1139 GetDir() + wxFileName::GetPathSeparator() + UPDATE_DATA_FILENAME;
1140
1141 if (!wxFileExists(fn)) return;
1142
1143 std::ifstream infile(fn.mb_str());
1144
1145 std::string key;
1146 time_t value(0);
1147
1148 while (infile >> key >> value) m_update_data[key] = value;
1149
1150 infile.close();
1151}
1152
1153void ChartSource::SaveUpdateData() {
1154 wxString fn;
1155 fn = GetDir() + wxFileName::GetPathSeparator() + UPDATE_DATA_FILENAME;
1156
1157#ifdef __ANDROID__
1158 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1159 UPDATE_DATA_FILENAME;
1160#endif
1161
1162 std::ofstream outfile(fn.mb_str());
1163 if (!outfile.is_open()) return;
1164
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";
1170 }
1171
1172 outfile.close();
1173
1174#ifdef __ANDROID__
1175 AndroidSecureCopyFile(
1176 fn, GetDir() + wxFileName::GetPathSeparator() + UPDATE_DATA_FILENAME);
1177#endif
1178}
1179
1180void ChartSource::ChartUpdated(const wxString &chart_number, time_t timestamp) {
1181 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1182 SaveUpdateData();
1183}
1184
1185bool ChartDldrPanelImpl::DownloadChart(const wxString &url,
1186 const wxString &file,
1187 const wxString &title) {
1188 return false;
1189}
1190
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);
1202#endif /* CHART_LIST */
1203}
1204
1205void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1206 if (m_download_is_cancel) {
1207 m_cancelled = true;
1208 return;
1209 }
1211}
1212#if defined(CHART_LIST)
1213void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1214 if (!m_hold_info)
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()));
1219 else
1220 event.Skip();
1221}
1222#endif /* CHART_LIST */
1223#if defined(CHART_LIST)
1224void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1225 CheckNewCharts(true);
1226}
1227#endif /* CHART_LIST */
1228
1229#if defined(CHART_LIST)
1230void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1231 CheckUpdatedCharts(true);
1232}
1233#endif /* CHART_LIST */
1234
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."));
1241 } else {
1242 CheckAllCharts(false);
1243 m_bSelectAll->SetLabel(_("Select All"));
1244 m_bSelectAll->SetToolTip(_("Select all charts in the list."));
1245 }
1246}
1247#endif /* CHART_LIST */
1248
1249int ChartDldrPanelImpl::GetChartCount() {
1250#if defined(CHART_LIST)
1251 return getChartList()->GetItemCount();
1252#else
1253 return static_cast<int>(m_panelArray.size());
1254#endif /* CHART_LIST*/
1255}
1256
1257int ChartDldrPanelImpl::GetCheckedChartCount() {
1258#if defined(CHART_LIST)
1259 int cnt = 0;
1260 int chartCnt = GetChartCount();
1261 for (int i = 0; i < chartCnt; i++)
1262 if (isChartChecked(i)) cnt++;
1263#else
1264 int cnt = 0;
1265 for (int i = 0; i < GetChartCount(); i++) {
1266 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1267 }
1268#endif /* CHART_LIST*/
1269 return cnt;
1270}
1271
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);
1278#else
1279 return m_panelArray.at(i)->GetCB()->IsChecked();
1280#endif /* CHART_LIST*/
1281 else
1282 return false;
1283}
1284
1285void ChartDldrPanelImpl::CheckAllCharts(bool value) {
1286#if defined(CHART_LIST)
1287 m_hold_info = true;
1288#endif /* CHART_LIST */
1289
1290 for (int i = 0; i < GetChartCount(); i++) {
1291#if defined(CHART_LIST)
1292 getChartList()->SetToggleValue(value, i, 0);
1293#else
1294 m_panelArray.at(i)->GetCB()->SetValue(value);
1295#endif /* CHART_LIST*/
1296 }
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;
1303#endif /* CHART_LIST */
1304}
1305
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);
1310#else
1311 if (m_panelArray.at(i)->isNew())
1312 m_panelArray.at(i)->GetCB()->SetValue(value);
1313#endif /* CHART_LIST*/
1314 }
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()));
1320#endif /* CHART_LIST */
1321}
1322
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);
1327#else
1328 if (m_panelArray.at(i)->isUpdated())
1329 m_panelArray.at(i)->GetCB()->SetValue(value);
1330#endif /* CHART_LIST */
1331 }
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()));
1337#endif /* CHART_LIST */
1338}
1339
1340void ChartDldrPanelImpl::InvertCheckAllCharts() {
1341#if defined(CHART_LIST)
1342 m_hold_info = true;
1343#endif /* CHART_LIST */
1344 for (int i = 0; i < GetChartCount(); i++)
1345#if defined(CHART_LIST)
1346 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1347#else
1348 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1349#endif /* CHART_LIST */
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()));
1356#endif /* CHART_LIST */
1357}
1358
1360 if (!m_is_connected) {
1361 Connect(
1362 wxEVT_DOWNLOAD_EVENT,
1363 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1364 m_is_connected = true;
1365 }
1366
1367 if (!GetCheckedChartCount() && !m_updating_all) {
1368 OCPNMessageBox_PlugIn(this, _("No charts selected for download."));
1369 return;
1370 }
1371 std::unique_ptr<ChartSource> &cs =
1372 m_plugin->m_ChartSources.at(GetSelectedCatalog());
1373
1374 m_cancelled = false;
1375 m_to_download = GetCheckedChartCount();
1376 m_downloading = 0;
1377 m_failed_downloads = 0;
1378 DisableForDownload(false);
1379 // wxString old_label = m_bDnldCharts->GetLabel(); // Broken on Android??
1380 m_bDnldCharts->SetLabel(_("Abort download"));
1381 m_download_is_cancel = true;
1382
1383 wxFileName downloaded_p;
1384 int idx = -1;
1385
1386 for (int i = 0; i < GetChartCount() && m_to_download; i++) {
1387 int index = i;
1388 if (m_cancelled) break;
1389 // Prepare download queues
1390 if (!isChartChecked(i)) continue;
1391 m_is_transfer_complete = false;
1392 m_is_transfer_ok = true;
1393 m_total_size = -1;
1394 m_transferred_size = 0;
1395 m_downloading++;
1396 if (m_plugin->m_chart_catalog.charts.at(index)->NeedsManualDownload()) {
1397 if (wxID_YES ==
1399 this,
1400 wxString::Format(
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());
1408 }
1409 continue;
1410 }
1411
1412 // download queue
1413 wxURI url(
1414 m_plugin->m_chart_catalog.charts.at(index)->GetDownloadLocation());
1415 if (url.IsReference()) {
1417 this,
1418 wxString::Format(
1419 _("Error, the URL to the chart (%s) data seems wrong."),
1420 url.BuildURI().c_str()),
1421 _("Error"));
1422 this->Enable();
1424 return;
1425 }
1426 // construct local file path
1427 wxString file =
1428 m_plugin->m_chart_catalog.charts.at(index)->GetChartFilename(false);
1429 wxFileName fn;
1430 fn.SetFullName(file);
1431 fn.SetPath(cs->GetDir());
1432 wxString path = fn.GetFullPath();
1433 if (wxFileExists(path)) wxRemoveFile(path);
1434 wxString title =
1435 m_plugin->m_chart_catalog.charts.at(index)->GetChartTitle();
1436
1437 // Ready to start download
1438#ifdef __ANDROID__
1439 wxString file_path = "file://" + fn.GetFullPath();
1440#else
1441 wxString file_path = fn.GetFullPath();
1442#endif
1443
1444 long handle;
1445 OCPN_downloadFileBackground(url.BuildURI(), file_path, this, &handle);
1446
1447 if (idx >= 0) {
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()
1454 .GetTicks());
1455 } else {
1456 m_failed_downloads++;
1457 }
1458 idx = -1;
1459 }
1460
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)));
1467 else
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)));
1472
1473 Update();
1474 Refresh();
1475
1476 wxTheApp->ProcessPendingEvents();
1477 wxYield();
1478 wxMilliSleep(20);
1479 }
1480
1481 if (m_cancelled) {
1482 idx = -1;
1484 }
1485
1486 if (m_is_transfer_ok && !m_cancelled) {
1487 idx = index;
1488 downloaded_p = path;
1489 } else {
1490 idx = -1;
1491 if (wxFileExists(path)) wxRemoveFile(path);
1492 m_failed_downloads++;
1493 }
1494 }
1495 if (idx >= 0) {
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()
1502 .GetTicks());
1503 } else {
1504 m_failed_downloads++;
1505 }
1506 }
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)
1513 this,
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);
1519
1520 if (m_cancelled)
1521 OCPNMessageBox_PlugIn(this, _("Chart download cancelled."),
1522 _("Chart Downloader"), wxOK | wxICON_INFORMATION);
1523
1524 if ((m_downloading - m_failed_downloads > 0) && !m_updating_all)
1526}
1527
1528ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1529 Disconnect(
1530 wxEVT_DOWNLOAD_EVENT,
1531 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1532 m_is_connected = false;
1533
1534#ifndef __ANDROID__
1536 0); // Stop the thread, is something like this needed on Android as well?
1537#endif
1538#if defined(CHART_LIST)
1539 clearChartList();
1540#endif /* CHART_LIST */
1541}
1542
1543ChartDldrPanelImpl::ChartDldrPanelImpl(chartdldr_pi *plugin, wxWindow *parent,
1544 wxWindowID id, const wxPoint &pos,
1545 const wxSize &size, long style)
1546 : ChartDldrPanel(parent, id, pos, size, 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;
1558 m_cancelled = true;
1559 m_to_download = -1;
1560 m_downloading = -1;
1561 m_updating_all = false;
1562 m_plugin = plugin;
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;
1569
1570 Connect(
1571 wxEVT_DOWNLOAD_EVENT,
1572 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1573 m_is_connected = true;
1574
1575 for (size_t i = 0; i < m_plugin->m_ChartSources.size(); i++) {
1576 AppendCatalog(m_plugin->m_ChartSources.at(i));
1577 }
1578 m_is_populated = true;
1579}
1580
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));
1586 }
1587 }
1588#ifdef __WXMAC__
1589 // Mojave does not paint the controls correctly without this.
1590 m_lbChartSources->Refresh(true);
1591#endif
1592 event.Skip();
1593}
1594
1595void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1596 if (!m_lbChartSources->GetSelectedItemCount()) return;
1597 if (wxID_YES != OCPNMessageBox_PlugIn(
1598 this,
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))
1603 return;
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() +
1608 ToBeRemoved);
1609 m_lbChartSources->DeleteItem(ToBeRemoved);
1610 CleanForm();
1611 m_plugin->SetSourceId(-1);
1612 SelectCatalog(-1);
1613 m_plugin->SaveConfig();
1614 event.Skip();
1615}
1616
1617void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1618 auto *dialog = new ChartDldrGuiAddSourceDlg(this);
1619 dialog->SetBasePath(m_plugin->GetBaseChartDir());
1620
1621 wxSize sz = GetParent()
1622 ->GetGrandParent()
1623 ->GetSize(); // This is the options panel true size
1624 dialog->SetSize(sz.GetWidth(), sz.GetHeight());
1625 dialog->Center();
1626
1627#ifdef __ANDROID__
1628 androidDisableRotation();
1629#endif
1630
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());
1636 dialog->Destroy();
1637 AppendCatalog(cs);
1638 bool covered = false;
1639 for (size_t i = 0; i < GetChartDBDirArrayString().GetCount(); i++) {
1640 if (cs->GetDir().StartsWith((GetChartDBDirArrayString().Item(i)))) {
1641 covered = true;
1642 break;
1643 }
1644 }
1645 if (!covered) {
1646 wxString dir = cs->GetDir();
1647 AddChartDirectory(dir);
1648 }
1649
1650 long itemSelectedNow = GetSelectedCatalog();
1651 m_lbChartSources->SetItemState(itemSelectedNow, 0, wxLIST_STATE_SELECTED);
1652
1653 SelectCatalog(m_lbChartSources->GetItemCount() - 1);
1654 m_plugin->m_ChartSources.push_back(std::move(cs));
1655 m_plugin->SaveConfig();
1656 }
1657#ifdef __ANDROID__
1658 androidEnableRotation();
1659#endif
1660
1661 event.Skip();
1662}
1663
1664void ChartDldrPanelImpl::DoEditSource() {
1665 if (!m_lbChartSources->GetSelectedItemCount()) return;
1666 int cat = GetSelectedCatalog();
1667 auto *dialog = new ChartDldrGuiAddSourceDlg(this);
1668 dialog->SetBasePath(m_plugin->GetBaseChartDir());
1669 dialog->SetSourceEdit(m_plugin->m_ChartSources.at(cat));
1670 dialog->SetTitle(_("Edit Chart Source"));
1671
1672 dialog->ShowModal();
1673 int retcode = dialog->GetReturnCode();
1674 {
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());
1682
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(
1696 cat, 1,
1697 m_plugin->m_chart_catalog.GetReleaseDate().Format(
1698 "%Y-%m-%d %H:%M"));
1699 m_lbChartSources->SetItem(cat, 2, path);
1700 }
1701 }
1702 bool covered = false;
1703 for (size_t i = 0; i < GetChartDBDirArrayString().GetCount(); i++) {
1704 if (m_plugin->m_ChartSources.at(cat)->GetDir().StartsWith(
1705 (GetChartDBDirArrayString().Item(i)))) {
1706 covered = true;
1707 break;
1708 }
1709 }
1710 if (!covered)
1712 this,
1713 wxString::Format(
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"));
1719
1720 m_plugin->SaveConfig();
1721 SetSource(cat);
1722 }
1723 }
1724}
1725
1726void ChartDldrPanelImpl::EditSource(wxCommandEvent &event) {
1727 DoEditSource();
1728 event.Skip();
1729}
1730
1731void ChartDldrPanelImpl::OnLeftDClick(wxMouseEvent &event) {
1732 DoEditSource();
1733 event.Skip();
1734}
1735
1736bool chartdldr_pi::ProcessFile(const wxString &aFile,
1737 const wxString &aTargetDir, bool aStripPath,
1738 wxDateTime aMTime) {
1739 if (aFile.Lower().EndsWith("zip")) // Zip compressed
1740 {
1741 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1742 if (ret)
1743 wxRemoveFile(aFile);
1744 else
1745 wxLogError("chartdldr_pi: Unable to extract: " + aFile);
1746 return ret;
1747 }
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);
1752#else
1753 bool ret =
1754 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1755#endif
1756 if (ret)
1757 wxRemoveFile(aFile);
1758 else
1759 wxLogError("chartdldr_pi: Unable to extract: " + aFile);
1760 return ret;
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")) {
1764 bool ret =
1765 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1766 if (ret)
1767 wxRemoveFile(aFile);
1768 else
1769 wxLogError("chartdldr_pi: Unable to extract: " + aFile);
1770 return ret;
1771 }
1772#else
1773 else if (aFile.Lower().EndsWith("rar") || aFile.Lower().EndsWith("tar")
1774#ifdef HAVE_BZIP2
1775 || aFile.Lower().EndsWith("bz2")
1776#endif
1777#ifdef HAVE_ZLIB
1778 || aFile.Lower().EndsWith("gz")
1779#endif
1780#ifdef HAVE_7Z
1781 ||
1782 aFile.Lower().EndsWith("7z") // TODO: Could it actually extract more
1783 // formats the LZMA SDK supports?
1784#endif
1785 ) {
1786 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1787 if (ret)
1788 wxRemoveFile(aFile);
1789 else
1790 wxLogError("chartdldr_pi: Unable to extract: " + aFile);
1791 return ret;
1792 }
1793#endif
1794
1795#ifdef __ANDROID__
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")) {
1799 int nStrip = 0;
1800 if (aStripPath) nStrip = 1;
1801
1802 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_("Installing charts."));
1803
1804 androidShowBusyIcon();
1805 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip, true);
1806 androidHideBusyIcon();
1807
1808 return ret;
1809 }
1810#endif
1811
1812 else // Uncompressed
1813 {
1814 wxFileName fn(aFile);
1815 if (fn.GetPath() != aTargetDir) // We have to move the file somewhere
1816 {
1817 if (!wxDirExists(aTargetDir)) {
1818 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1819 if (!wxRenameFile(aFile, aTargetDir)) return false;
1820 } else
1821 return false;
1822 }
1823 }
1824 wxString name = fn.GetFullName();
1825 fn.Clear();
1826 fn.Assign(aTargetDir, name);
1827 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1828 }
1829 return true;
1830}
1831
1832#ifdef DLDR_USE_LIBARCHIVE
1833#ifndef __ANDROID__
1834static int copy_data(struct archive *ar, struct archive *aw) {
1835 int r;
1836 const void *buff;
1837 size_t size;
1838 __LA_INT64_T offset;
1839
1840 for (;;) {
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) {
1846 // fprintf(stderr, "%s\n", archive_error_string(aw));
1847 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1848 archive_error_string(aw)));
1849 return (r);
1850 }
1851 }
1852}
1853#endif
1854
1855bool chartdldr_pi::ExtractLibArchiveFiles(const wxString &aArchiveFile,
1856 const wxString &aTargetDir,
1857 bool aStripPath, wxDateTime aMTime,
1858 bool aRemoveArchive) {
1859#ifndef __ANDROID__
1860 struct archive *a = nullptr;
1861 struct archive *ext = nullptr;
1862 bool ok = false;
1863
1864 int flags = ARCHIVE_EXTRACT_TIME;
1865#ifdef ARCHIVE_EXTRACT_SECURE_NODOTDOT
1866 flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
1867#endif
1868#ifdef ARCHIVE_EXTRACT_SECURE_SYMLINKS
1869 flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
1870#endif
1871
1872 a = archive_read_new();
1873 ext = archive_write_disk_new();
1874
1875 if (!a || !ext) {
1876 wxLogError("Chartdldr_pi: Failed to create libarchive objects.");
1877 goto cleanup;
1878 }
1879
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);
1884#endif
1885
1886 archive_write_disk_set_options(ext, flags);
1887 archive_write_disk_set_standard_lookup(ext);
1888
1889#ifdef _WIN32
1890 if (archive_read_open_filename_w(a, aArchiveFile.wc_str(), 10240) !=
1891 ARCHIVE_OK) {
1892 wxLogError(wxString::Format("Chartdldr_pi: LibArchive open error: %s",
1893 archive_error_string(a)));
1894 goto cleanup;
1895 }
1896#else
1897 {
1898 if (archive_read_open_filename(a, aArchiveFile.mb_str().data(), 10240) !=
1899 ARCHIVE_OK) {
1900 wxLogError(wxString::Format("Chartdldr_pi: LibArchive open error: %s",
1901 archive_error_string(a)));
1902 goto cleanup;
1903 }
1904 }
1905#endif
1906
1907 for (;;) {
1908 struct archive_entry *entry = nullptr;
1909 int r = archive_read_next_header(a, &entry);
1910
1911 if (r == ARCHIVE_EOF) {
1912 break;
1913 }
1914
1915 if (r < ARCHIVE_OK) {
1916 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1917 archive_error_string(a)));
1918 }
1919 if (r < ARCHIVE_WARN) {
1920 goto cleanup;
1921 }
1922
1923 wxString entryName;
1924#ifdef _WIN32
1925 const char *rawUtf8 = archive_entry_pathname_utf8(entry);
1926 if (rawUtf8 && *rawUtf8) {
1927 entryName = wxString::FromUTF8(rawUtf8);
1928 } else {
1929 const wchar_t *rawWide = archive_entry_pathname_w(entry);
1930 if (rawWide && *rawWide) entryName = wxString(rawWide);
1931 }
1932#else
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);
1938 }
1939 }
1940#endif
1941
1942 if (entryName.IsEmpty()) {
1943 wxLogWarning("Skipping archive entry with empty pathname.");
1944 continue;
1945 }
1946
1947 if (aStripPath) {
1948 wxFileName stripped(entryName);
1949 entryName = stripped.GetFullName();
1950
1951 if (entryName.IsEmpty()) {
1952 continue;
1953 }
1954 }
1955
1956 wxString outputPath = entryName;
1957 if (aTargetDir.empty()) {
1958 if (!IsPathInsideDir(aTargetDir, entryName, outputPath)) {
1959 wxLogWarning("Skipping archive entry with path traversal attempt: " +
1960 entryName);
1961 continue;
1962 }
1963 }
1964
1965#ifdef _WIN32
1966 archive_entry_copy_pathname_w(entry, outputPath.wc_str());
1967#else
1968 archive_entry_copy_pathname(entry, outputPath.fn_str().data());
1969#endif
1970
1971 if (aMTime.IsValid()) {
1972 archive_entry_set_mtime(entry, static_cast<time_t>(aMTime.GetTicks()), 0);
1973 }
1974
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)));
1979 }
1980 if (r < ARCHIVE_WARN) {
1981 goto cleanup;
1982 }
1983
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)));
1989 }
1990 if (r < ARCHIVE_WARN) {
1991 goto cleanup;
1992 }
1993 }
1994
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)));
1999 }
2000 if (r < ARCHIVE_WARN) {
2001 goto cleanup;
2002 }
2003 }
2004
2005 ok = true;
2006
2007cleanup:
2008 if (a) {
2009 archive_read_close(a);
2010 archive_read_free(a);
2011 }
2012 if (ext) {
2013 archive_write_close(ext);
2014 archive_write_free(ext);
2015 }
2016
2017 if (ok && aRemoveArchive) wxRemoveFile(aArchiveFile);
2018 return ok;
2019
2020#else
2021 wxUnusedVar(aArchiveFile);
2022 wxUnusedVar(aTargetDir);
2023 wxUnusedVar(aStripPath);
2024 wxUnusedVar(aMTime);
2025 wxUnusedVar(aRemoveArchive);
2026 return false;
2027#endif
2028}
2029#endif // DLDR_USE_LIBARCHIVE
2030
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);
2034 if (!ar)
2035 ar =
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);
2040 return ar;
2041}
2042
2043bool chartdldr_pi::ExtractUnarrFiles(const wxString &aRarFile,
2044 const wxString &aTargetDir,
2045 bool aStripPath, wxDateTime aMTime,
2046 bool aRemoveRar) {
2047 ar_stream *stream = NULL;
2048 ar_archive *ar = NULL;
2049 int entry_count = 1;
2050 int entry_skips = 0;
2051 int error_step = 1;
2052 bool ret = true;
2053
2054 stream = ar_open_file(aRarFile.c_str());
2055 if (!stream) {
2056 wxLogError("Can not open file '" + aRarFile + "'.");
2057 ar_close_archive(ar);
2058 ar_close(stream);
2059 return false;
2060 }
2061 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(), '.'));
2062 if (!ar) {
2063 wxLogError("Can not open archive '" + aRarFile + "'.");
2064 ar_close_archive(ar);
2065 ar_close(stream);
2066 return false;
2067 }
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; // Save for logging
2072 if (aStripPath) {
2073 wxFileName fn(name);
2074 /* We can completly replace the entry path */
2075 // fn.SetPath(aTargetDir);
2076 // name = fn.GetFullPath();
2077 /* Or only remove the first dir (eg. ENC_ROOT) */
2078 if (fn.GetDirCount() > 0) {
2079 fn.RemoveDir(0);
2080 name = fn.GetFullPath();
2081 }
2082 }
2083
2084 // Path traversal protection: validate path stays inside target directory
2085 wxString fullPath;
2086 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2087 wxLogWarning("Skipping archive entry with path traversal attempt: " +
2088 originalName);
2089 continue;
2090 }
2091 name = fullPath;
2092
2093 wxFileName fn(name);
2094 if (!fn.DirExists()) {
2095 if (!wxFileName::Mkdir(fn.GetPath())) {
2096 wxLogError("Can not create directory '" + fn.GetPath() + "'.");
2097 ret = false;
2098 break;
2099 }
2100 }
2101 wxFileOutputStream file(name);
2102 if (!file) {
2103 wxLogError("Can not create file '" + name + "'.");
2104 ret = false;
2105 break;
2106 }
2107 while (size > 0) {
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);
2112 size -= count;
2113 }
2114 file.Close();
2115 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2116 if (size > 0) {
2117 wxLogError("Warning: Failed to uncompress... skipping");
2118 entry_skips++;
2119 ret = false;
2120 }
2121 }
2122 if (!ar_at_eof(ar)) {
2123 wxLogError("Error: Failed to parse entry %d!", entry_count);
2124 ret = false;
2125 }
2126 ar_close_archive(ar);
2127 ar_close(stream);
2128
2129 if (aRemoveRar) wxRemoveFile(aRarFile);
2130
2131#ifdef _UNIX
2132 // reset LC_NUMERIC locale, some locales use a comma for decimal point
2133 // and it corrupts navobj.xml file
2134 setlocale(LC_NUMERIC, "C");
2135#endif
2136
2137 return ret;
2138}
2139#endif
2140
2141bool chartdldr_pi::ExtractZipFiles(const wxString &aZipFile,
2142 const wxString &aTargetDir, bool aStripPath,
2143 wxDateTime aMTime, bool aRemoveZip) {
2144 bool ret = true;
2145
2146#ifdef __ANDROID__
2147 int nStrip = 0;
2148 if (aStripPath) nStrip = 1;
2149
2150 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip, true);
2151#else
2152 std::unique_ptr<wxZipEntry> entry(new wxZipEntry());
2153
2154 do {
2155 wxLogMessage("chartdldr_pi: Going to extract '" + aZipFile + "'.");
2156 wxFileInputStream in(aZipFile);
2157
2158 if (!in) {
2159 wxLogMessage("Can not open file '" + aZipFile + "'.");
2160 ret = false;
2161 break;
2162 }
2163 wxZipInputStream zip(in);
2164 ret = false;
2165
2166 while (entry.reset(zip.GetNextEntry()), entry) {
2167 // access meta-data
2168 wxString name = entry->GetName();
2169 wxString fullPath;
2170 if (aStripPath) {
2171 wxFileName fn(name);
2172 /* We can completly replace the entry path */
2173 // fn.SetPath(aTargetDir);
2174 // name = fn.GetFullPath();
2175 /* Or only remove the first dir (eg. ENC_ROOT) */
2176 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2177 name = fn.GetFullPath();
2178 }
2179
2180 // Path traversal protection: validate path stays inside target directory
2181 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2182 wxLogWarning("Skipping zip entry with path traversal attempt: " +
2183 entry->GetName());
2184 continue;
2185 }
2186 name = fullPath;
2187
2188 // read 'zip' to access the entry's data
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 + "'.");
2193 ret = false;
2194 break;
2195 }
2196 } else {
2197 if (!zip.OpenEntry(*entry)) {
2198 wxLogMessage("Can not open zip entry '" + entry->GetName() + "'.");
2199 ret = false;
2200 break;
2201 }
2202 if (!zip.CanRead()) {
2203 wxLogMessage("Can not read zip entry '" + entry->GetName() + "'.");
2204 ret = false;
2205 break;
2206 }
2207
2208 wxFileName fn(name);
2209 if (!fn.DirExists()) {
2210 if (!wxFileName::Mkdir(fn.GetPath())) {
2211 wxLogMessage("Can not create directory '" + fn.GetPath() + "'.");
2212 ret = false;
2213 break;
2214 }
2215 }
2216
2217 wxFileOutputStream file(name);
2218
2219 if (!file) {
2220 wxLogMessage("Can not create file '" + name + "'.");
2221 ret = false;
2222 break;
2223 }
2224 zip.Read(file);
2225 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2226 ret = true;
2227 }
2228 }
2229
2230 } while (false);
2231
2232 if (aRemoveZip) wxRemoveFile(aZipFile);
2233#endif // __ANDROID__
2234
2235 return ret;
2236}
2237
2238ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2239 : AddSourceDlg(parent) {
2240 wxFileName fn;
2241 fn.SetPath(*GetpSharedDataLocation());
2242 fn.AppendDir("plugins");
2243 fn.AppendDir("chartdldr_pi");
2244 fn.AppendDir("data");
2245
2246 int w = 16; // default for desktop
2247 int h = 16;
2248
2249#ifdef __ANDROID__
2250 w = 6 * g_androidDPmm; // mm nominal size
2251 h = w;
2252
2253 p_buttonIconList = new wxImageList(w, h);
2254
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);
2259
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);
2264
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);
2269
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);
2274
2275 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2276#else
2277 p_iconList = new wxImageList(w, h);
2278
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);
2283
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);
2288
2289 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2290#endif /* __ANDROID__ */
2291
2292 m_treeCtrlPredefSrcs->SetIndent(w);
2293
2294 m_base_path = "";
2295 m_last_path = "";
2296 LoadSources();
2297 m_nbChoice->SetSelection(0);
2298 // m_treeCtrlPredefSrcs->ExpandAll();
2299
2300 wxWindow::Fit();
2301
2302 applyStyle();
2303}
2304
2305bool ChartDldrGuiAddSourceDlg::LoadSources() {
2306 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot("root");
2307
2308 wxFileName fn;
2310 fn.SetFullName("chartdldr_pi-chart_sources.xml");
2311 if (!fn.FileExists()) {
2312 fn.SetPath(*GetpSharedDataLocation());
2313 fn.AppendDir("plugins");
2314 fn.AppendDir("chartdldr_pi");
2315 fn.AppendDir("data");
2316 fn.SetFullName("chart_sources.xml");
2317 if (!fn.FileExists()) {
2318 wxLogMessage(
2319 wxString::Format("Error: chartdldr_pi::LoadSources() %s not found!",
2320 fn.GetFullPath().c_str()));
2321 return false;
2322 }
2323 }
2324 wxString path = fn.GetFullPath();
2325
2326 auto *doc = new pugi::xml_document;
2327 bool ret = doc->load_file(path.mb_str());
2328 if (ret) {
2329 pugi::xml_node root = doc->first_child();
2330
2331 for (pugi::xml_node element = root.first_child(); element;
2332 element = element.next_sibling()) {
2333 if (!strcmp(element.name(), "sections")) {
2334 LoadSections(tree, element);
2335 }
2336 }
2337 }
2338 wxDELETE(doc);
2339 return true;
2340}
2341
2342bool ChartDldrGuiAddSourceDlg::LoadSections(const wxTreeItemId &root,
2343 pugi::xml_node &node) {
2344 for (pugi::xml_node element = node.first_child(); element;
2345 element = element.next_sibling()) {
2346 if (!strcmp(element.name(), "section")) {
2347 LoadSection(root, element);
2348 }
2349 }
2350 return true;
2351}
2352
2353bool ChartDldrGuiAddSourceDlg::LoadSection(const wxTreeItemId &root,
2354 pugi::xml_node &node) {
2355 wxTreeItemId item;
2356 for (pugi::xml_node element = node.first_child(); element;
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);
2361
2362 wxFont *pFont = OCPNGetFont(_("Dialog"));
2363 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2364 }
2365 if (!strcmp(element.name(), "sections")) LoadSections(item, element);
2366 if (!strcmp(element.name(), "catalogs")) LoadCatalogs(item, element);
2367 }
2368
2369 return true;
2370}
2371
2372bool ChartDldrGuiAddSourceDlg::LoadCatalogs(const wxTreeItemId &root,
2373 pugi::xml_node &node) {
2374 for (pugi::xml_node element = node.first_child(); element;
2375 element = element.next_sibling()) {
2376 if (!strcmp(element.name(), "catalog")) LoadCatalog(root, element);
2377 }
2378
2379 return true;
2380}
2381
2382bool ChartDldrGuiAddSourceDlg::LoadCatalog(const wxTreeItemId &root,
2383 pugi::xml_node &node) {
2384 wxString name, location, dir;
2385 for (pugi::xml_node element = node.first_child(); element;
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());
2393 }
2394 auto *cs = new ChartSource(name, location, dir);
2395 wxTreeItemId id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2396
2397 wxFont *pFont = OCPNGetFont(_("Dialog"));
2398 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(id, *pFont);
2399
2400 return true;
2401}
2402
2403ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() = default;
2404
2405wxString ChartDldrGuiAddSourceDlg::FixPath(const wxString &path) {
2406 wxString sep(wxFileName::GetPathSeparator());
2407 wxString s = path;
2408 s.Replace("/", sep, true);
2409 s.Replace(USERDATA, m_base_path);
2410 s.Replace(sep + sep, sep);
2411 return s;
2412}
2413
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);
2418}
2419
2420void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2421 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2422 auto *cs = (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2423 if (cs) {
2424 m_dirExpanded = FixPath(cs->GetDir());
2425
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()));
2431
2432 m_buttonChartDirectory->Enable();
2433 m_last_path = m_tcChartDirectory->GetValue();
2434 }
2435 }
2436 event.Skip();
2437}
2438
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()));
2447
2448 m_buttonChartDirectory->Enable();
2449}
2450
2451ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2452 : ChartDldrPrefsDlg(parent) {}
2453
2454ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() = default;
2455
2456void ChartDldrPrefsDlgImpl::SetPath(const wxString &path) {
2457 // if( !wxDirExists(path) )
2458 // if( !wxFileName::Mkdir(path, 0755, wxPATH_MKDIR_FULL) )
2459 //{
2460 // OCPNMessageBox_PlugIn(this, wxString::Format(_("Directory %s can't be
2461 // created."), m_dpDefaultDir->GetTextCtrlValue().c_str()), _("Chart
2462 // Downloader")); return;
2463 //}
2464 m_tcDefaultDir->SetValue(path);
2465}
2466
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();
2473}
2474void ChartDldrPrefsDlgImpl::SetPreferences(bool preselect_new,
2475 bool preselect_updated,
2476 bool bulk_update) {
2477 m_cbSelectNew->SetValue(preselect_new);
2478 m_cbSelectUpdated->SetValue(preselect_updated);
2479 m_cbBulkUpdate->SetValue(bulk_update);
2480}
2481
2482void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2483 wxString msg = "";
2484
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));
2489 if (!cs)
2490 msg +=
2491 _("You must select one of the predefined chart sources or create "
2492 "one of your own.\n");
2493 } else
2494 msg +=
2495 _("You must select one of the predefined chart sources or create one "
2496 "of your own.\n");
2497 }
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,
2509 wxPATH_MKDIR_FULL))
2510 msg += wxString::Format(_("Directory %s can't be created."),
2511 m_tcChartDirectory->GetValue().c_str()) +
2512 "\n";
2513
2514 if (!msg.empty())
2515 OCPNMessageBox_PlugIn(this, msg, _("Chart source definition problem"),
2516 wxOK | wxCENTRE | wxICON_ERROR);
2517 else {
2518 event.Skip();
2519 SetReturnCode(wxID_OK);
2520 EndModal(wxID_OK);
2521 }
2522}
2523
2524void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2525 SetReturnCode(wxID_CANCEL);
2526 EndModal(wxID_CANCEL);
2527}
2528
2529void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2530 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2531 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2532 wxPATH_MKDIR_FULL)) {
2534 this,
2535 wxString::Format(_("Directory %s can't be created."),
2536 m_tcDefaultDir->GetValue().c_str()),
2537 _("Chart Downloader"));
2538 return;
2539 }
2540 }
2541
2542 if (g_pi) {
2543 g_pi->UpdatePrefs(this);
2544 }
2545
2546 event.Skip();
2547 EndModal(wxID_OK);
2548
2549 // Hide();
2550 // Close();
2551}
2552
2553void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2554 event.Skip();
2555 EndModal(wxID_CANCEL);
2556 // Close();
2557}
2558
2559void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2560 event.Skip();
2561 // Close();
2562}
2563
2564bool ChartDldrGuiAddSourceDlg::ValidateUrl(const wxString &Url,
2565 bool catalog_xml) {
2566 wxRegEx re;
2567 if (catalog_xml)
2568 re.Compile(
2569 "^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$"); // TODO: wxRegEx
2570 // sucks a bit,
2571 // this RE is
2572 // way too naive
2573 else
2574 re.Compile(
2575 "^https?\\://[a-zA-Z0-9\\./_-]*$"); // TODO: wxRegEx sucks a bit,
2576 // this RE is way too naive
2577 return re.Matches(Url);
2578}
2579
2580void ChartDldrPanelImpl::onDLEvent(OCPN_downloadEvent &ev) {
2581 // wxString msg;
2582 // msg.Printf("onDLEvent %d %d",ev.getDLEventCondition(),
2583 // ev.getDLEventStatus()); wxLogMessage(msg);
2584
2585 switch (ev.getDLEventCondition()) {
2587 m_is_transfer_complete = true;
2588 m_is_transfer_ok =
2589 (ev.getDLEventStatus() == OCPN_DL_NO_ERROR) ? true : false;
2590 break;
2591
2593 if (ev.getTransferred() > m_transferred_size) {
2594 m_total_size = ev.getTotal();
2595 m_transferred_size = ev.getTransferred();
2596 }
2597
2598 break;
2599 default:
2600 break;
2601 }
2602 wxYieldIfNeeded();
2603}
OpenCPN Android support utilities.
double g_androidDPmm
Only used used by ANDROID
Chart Downloader Plugin – plugin implementation header.
Class AddSourceDlg.
Implementing ChartDldrPanel.
Class ChartDldrPanel.
Class ChartDldrPrefsDlg.
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
Definition gui_vars.cpp:66
@ 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.