OpenCPN Partial API docs
Loading...
Searching...
No Matches
chartdldr_pi.cpp
1/******************************************************************************
2 * $Id: chartdldr_pi.cpp,v 1.0 2011/02/26 01:54:37 nohal Exp $
3 *
4 * Project: OpenCPN
5 * Purpose: Chart downloader Plugin
6 * Author: Pavel Kalian
7 *
8 ***************************************************************************
9 * Copyright (C) 2011 by Pavel Kalian *
10 * $EMAIL$ *
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 * This program is distributed in the hope that it will be useful, *
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
20 * GNU General Public License for more details. *
21 * *
22 * You should have received a copy of the GNU General Public License *
23 * along with this program; if not, write to the *
24 * Free Software Foundation, Inc., *
25 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
26 ***************************************************************************
27 */
28
29#include "wx/wxprec.h"
30
31#ifndef WX_PRECOMP
32#include "wx/wx.h"
33#endif // precompiled headers
34
35#include "chartdldr_pi.h"
36#include "wxWTranslateCatalog.h"
37#include <wx/stdpaths.h>
38#include <wx/url.h>
39#include <wx/progdlg.h>
40#include <wx/sstream.h>
41#include <wx/wfstream.h>
42#include <wx/filename.h>
43#include <wx/listctrl.h>
44#include <wx/dir.h>
45#include <wx/filesys.h>
46#include <wx/zipstrm.h>
47#include <wx/wfstream.h>
48#include <memory>
49#include <wx/regex.h>
50#include <wx/debug.h>
51
52#ifdef DLDR_USE_LIBARCHIVE
53#include <archive.h>
54#include <archive_entry.h>
55#ifdef CHARTDLDR_RAR_UNARR
56#include "unarr.h"
57#endif
58#else
59#include "unarr.h"
60#endif
61
62#ifdef __ANDROID__
63#define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
64#endif
65
66#include <fstream>
67
68#ifdef __ANDROID__
69#include "androidSupport.h"
70#include "android_jvm.h"
71#include <jni.h>
72#endif
73
74#ifdef __WXMAC__
75#define CATALOGS_NAME_WIDTH 300
76#define CATALOGS_DATE_WIDTH 120
77#define CATALOGS_PATH_WIDTH 100
78#define CHARTS_NAME_WIDTH 300
79#define CHARTS_STATUS_WIDTH 100
80#define CHARTS_DATE_WIDTH 120
81#else
82#ifdef __ANDROID__
83
84#define CATALOGS_NAME_WIDTH 350
85#define CATALOGS_DATE_WIDTH 500
86#define CATALOGS_PATH_WIDTH 1000
87#define CHARTS_NAME_WIDTH 520
88#define CHARTS_STATUS_WIDTH 150
89#define CHARTS_DATE_WIDTH 200
90
91#else
92
93#define CATALOGS_NAME_WIDTH 200
94#define CATALOGS_DATE_WIDTH 130
95#define CATALOGS_PATH_WIDTH 250
96#define CHARTS_NAME_WIDTH 320
97#define CHARTS_STATUS_WIDTH 150
98#define CHARTS_DATE_WIDTH 130
99
100#endif
101#endif // __WXMAC__
102
103#ifdef __ANDROID__
104#include <QtAndroidExtras/QAndroidJniObject>
105#include "qdebug.h"
106
107#endif
108
109bool getDisplayMetrics();
110
111#define CHART_DIR "Charts"
112
113// Helper function to check if a path is safely inside the target directory
114// Returns true if normalizedPath is inside targetDir, false otherwise (path
115// traversal attempt)
116static bool IsPathInsideDir(const wxString &targetDir,
117 const wxString &entryName, wxString &outFullPath) {
118 // Construct the full path
119 wxString combinedPath = targetDir;
120 if (!combinedPath.EndsWith(wxFileName::GetPathSeparator())) {
121 combinedPath += wxFileName::GetPathSeparator();
122 }
123 combinedPath += entryName;
124
125 // Normalize the combined path to resolve any ".." components
126 wxFileName fn(combinedPath);
127 fn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG);
128 outFullPath = fn.GetFullPath();
129
130 // Normalize target dir for comparison
131 wxFileName targetFn(targetDir);
132 targetFn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE |
133 wxPATH_NORM_LONG);
134 wxString normalizedTarget = targetFn.GetFullPath();
135
136 // Ensure target ends with separator for proper prefix matching
137 if (!normalizedTarget.EndsWith(wxFileName::GetPathSeparator())) {
138 normalizedTarget += wxFileName::GetPathSeparator();
139 }
140
141 // Check if the normalized path starts with the target directory
142 // This catches all path traversal attempts including "../", absolute paths,
143 // etc.
144 if (outFullPath.StartsWith(normalizedTarget)) {
145 return true;
146 }
147
148 // Also allow if it's exactly the target directory (for directory entries)
149 if (outFullPath == targetFn.GetFullPath()) {
150 return true;
151 }
152
153 return false;
154}
155
156static wxString FormatBytes(double bytes) {
157 if (bytes <= 0) return "?";
158 return wxString::Format(_T("%.1fMB"), bytes / 1024 / 1024);
159}
160
161static wxString FormatBytes(long bytes) {
162 return FormatBytes(static_cast<double>(bytes));
163}
164
165int g_Android_SDK_Version;
166
167bool IsDLDirWritable(wxFileName fn) {
168#ifndef __ANDROID__
169 return fn.IsDirWritable();
170#else
171 if (g_Android_SDK_Version >= 30) { // scoped storage?
172 // Use a simple test here
173 return (fn.GetFullPath().Contains("org.opencpn.opencpn")); // fast test
174 } else
175 return fn.IsDirWritable();
176
177#endif
178}
179
180// the class factories, used to create and destroy instances of the PlugIn
181
182extern "C" DECL_EXP opencpn_plugin *create_pi(void *ppimgr) {
183 return new chartdldr_pi(ppimgr);
184}
185
186extern "C" DECL_EXP void destroy_pi(opencpn_plugin *p) { delete p; }
187
188double g_androidDPmm;
189chartdldr_pi *g_pi;
190
191//---------------------------------------------------------------------------------------------------------
192//
193// ChartDldr PlugIn Implementation
194//
195//---------------------------------------------------------------------------------------------------------
196
197#include "icons.h"
198
199//---------------------------------------------------------------------------------------------------------
200//
201// PlugIn initialization and de-init
202//
203//---------------------------------------------------------------------------------------------------------
204
205chartdldr_pi::chartdldr_pi(void *ppimgr) : opencpn_plugin_113(ppimgr) {
206 // Create the PlugIn icons
207 initialize_images();
208
209 m_parent_window = NULL;
210 m_pChartSource = NULL;
211 m_pconfig = NULL;
212 m_preselect_new = false;
213 m_preselect_updated = false;
214 m_allow_bulk_update = false;
215 m_pOptionsPage = NULL;
216 m_selected_source = -1;
217 m_dldrpanel = NULL;
218 m_schartdldr_sources = wxEmptyString;
219
220 g_pi = this;
221}
222
224 AddLocaleCatalog(PLUGIN_CATALOG_NAME);
225
226 // Get a pointer to the opencpn display canvas, to use as a parent for the
227 // POI Manager dialog
228 m_parent_window = GetOCPNCanvasWindow();
229
230 // Get a pointer to the opencpn configuration object
231 m_pconfig = GetOCPNConfigObject();
232 m_pOptionsPage = NULL;
233
234 m_pChartSource = NULL;
235
236#ifdef __ANDROID__
237 androidGetSDKVersion();
238#endif
239
240 // And load the configuration items
241 LoadConfig();
242
243 getDisplayMetrics();
244
245 wxStringTokenizer st(m_schartdldr_sources, _T("|"), wxTOKEN_DEFAULT);
246 while (st.HasMoreTokens()) {
247 wxString s1 = st.GetNextToken();
248 wxString s2 = st.GetNextToken();
249 wxString s3 = st.GetNextToken();
250 if (!s2.IsEmpty()) // scrub empty sources.
251 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
252 }
254}
255
257 wxLogMessage(_T("chartdldr_pi: DeInit"));
258
259 m_ChartSources.clear();
260 // wxDELETE(m_pChartSource);
261 /* TODO: Seth */
262 // dialog->Close();
263 // dialog->Destroy();
264 // wxDELETE(dialog);
265 /* We must delete remaining page if the plugin is disabled while in Options
266 * dialog */
267 if (m_pOptionsPage) {
268 if (DeleteOptionsPage(m_pOptionsPage)) m_pOptionsPage = NULL;
269 // TODO: any other memory leak?
270 }
271 return true;
272}
273
274int chartdldr_pi::GetAPIVersionMajor() { return MY_API_VERSION_MAJOR; }
275
276int chartdldr_pi::GetAPIVersionMinor() { return MY_API_VERSION_MINOR; }
277
278int chartdldr_pi::GetPlugInVersionMajor() { return PLUGIN_VERSION_MAJOR; }
279
280int chartdldr_pi::GetPlugInVersionMinor() { return PLUGIN_VERSION_MINOR; }
281
282wxBitmap *chartdldr_pi::GetPlugInBitmap() { return _img_chartdldr_pi; }
283
284wxString chartdldr_pi::GetCommonName() { return _("ChartDownloader"); }
285
287 return _("Chart Downloader PlugIn for OpenCPN");
288}
289
291 return _(
292 "Chart Downloader PlugIn for OpenCPN\n\
293Manages chart downloads and updates from sources supporting\n\
294NOAA Chart Catalog format");
295}
296
298 m_pOptionsPage =
299 AddOptionsPage(PI_OPTIONS_PARENT_CHARTS, _("Chart Downloader"));
300 if (!m_pOptionsPage) {
301 wxLogMessage(
302 _T("Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!"));
303 return;
304 }
305 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
306 m_pOptionsPage->SetSizer(sizer);
307
308 m_dldrpanel =
309 new ChartDldrPanelImpl(this, m_pOptionsPage, wxID_ANY, wxDefaultPosition,
310 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
311
312 m_pOptionsPage->InvalidateBestSize();
313 sizer->Add(m_dldrpanel, 1, wxALL | wxEXPAND);
314 m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
315 m_dldrpanel->FitInside();
316}
317
318void chartdldr_pi::OnCloseToolboxPanel(int page_sel, int ok_apply_cancel) {
319 /* TODO: Seth */
320 m_dldrpanel->CancelDownload();
321#ifndef __ANDROID__
323 0); // Stop the thread, is something like this needed on Android as well?
324#endif
325 m_selected_source = m_dldrpanel->GetSelectedCatalog();
326 SaveConfig();
327}
328
329bool chartdldr_pi::LoadConfig(void) {
330 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
331
332 if (pConf) {
333 pConf->SetPath(_T ( "/Settings/ChartDnldr" ));
334 pConf->Read(_T ( "ChartSources" ), &m_schartdldr_sources, wxEmptyString);
335 pConf->Read(_T ( "Source" ), &m_selected_source, -1);
336
337 wxFileName fn(GetWritableDocumentsDir(), wxEmptyString);
338 fn.AppendDir(_T(CHART_DIR));
339
340 pConf->Read(_T ( "BaseChartDir" ), &m_base_chart_dir, fn.GetPath());
341 wxLogMessage(_T ( "chartdldr_pi:m_base_chart_dir: " ) + m_base_chart_dir);
342
343 // Check to see if the directory is writeable, esp. on App updates.
344 wxFileName testFN(m_base_chart_dir);
345 if (!IsDLDirWritable(testFN)) {
346 wxLogMessage(
347 "Cannot write to m_base_chart_dir, override to "
348 "GetWritableDocumentsDir()");
349 m_base_chart_dir = fn.GetPath();
350 wxLogMessage(_T ( "chartdldr_pi: Corrected: " ) + m_base_chart_dir);
351 }
352
353 pConf->Read(_T ( "PreselectNew" ), &m_preselect_new, true);
354 pConf->Read(_T ( "PreselectUpdated" ), &m_preselect_updated, true);
355 pConf->Read(_T ( "AllowBulkUpdate" ), &m_allow_bulk_update, false);
356 return true;
357 } else
358 return false;
359}
360
361bool chartdldr_pi::SaveConfig(void) {
362 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
363
364 m_schartdldr_sources.Clear();
365
366 for (size_t i = 0; i < m_ChartSources.size(); i++) {
367 std::unique_ptr<ChartSource> &cs = m_ChartSources.at(i);
368 m_schartdldr_sources.Append(
369 wxString::Format(_T("%s|%s|%s|"), cs->GetName().c_str(),
370 cs->GetUrl().c_str(), cs->GetDir().c_str()));
371 }
372
373 if (pConf) {
374 pConf->SetPath(_T ( "/Settings/ChartDnldr" ));
375 pConf->Write(_T ( "ChartSources" ), m_schartdldr_sources);
376 pConf->Write(_T ( "Source" ), m_selected_source);
377 pConf->Write(_T ( "BaseChartDir" ), m_base_chart_dir);
378 pConf->Write(_T ( "PreselectNew" ), m_preselect_new);
379 pConf->Write(_T ( "PreselectUpdated" ), m_preselect_updated);
380 pConf->Write(_T ( "AllowBulkUpdate" ), m_allow_bulk_update);
381
382 return true;
383 } else
384 return false;
385}
386
387void SetBackColor(wxWindow *ctrl, wxColour col) {
388 static int depth = 0; // recursion count
389 if (depth == 0) { // only for the window root, not for every child
390
391 ctrl->SetBackgroundColour(col);
392 }
393
394 wxWindowList kids = ctrl->GetChildren();
395 for (unsigned int i = 0; i < kids.GetCount(); i++) {
396 wxWindowListNode *node = kids.Item(i);
397 wxWindow *win = node->GetData();
398
399 if (dynamic_cast<wxListBox *>(win))
400 dynamic_cast<wxListBox *>(win)->SetBackgroundColour(col);
401
402 else if (dynamic_cast<wxTextCtrl *>(win))
403 dynamic_cast<wxTextCtrl *>(win)->SetBackgroundColour(col);
404
405 // else if( win->IsKindOf( CLASSINFO(wxStaticText) ) )
406 // ( (wxStaticText*) win )->SetForegroundColour( uitext );
407
408 else if (dynamic_cast<wxChoice *>(win))
409 dynamic_cast<wxChoice *>(win)->SetBackgroundColour(col);
410
411 else if (dynamic_cast<wxComboBox *>(win))
412 dynamic_cast<wxComboBox *>(win)->SetBackgroundColour(col);
413
414 else if (dynamic_cast<wxRadioButton *>(win))
415 dynamic_cast<wxRadioButton *>(win)->SetBackgroundColour(col);
416
417 else if (dynamic_cast<wxScrolledWindow *>(win)) {
418 dynamic_cast<wxScrolledWindow *>(win)->SetBackgroundColour(col);
419 }
420
421 else if (dynamic_cast<wxButton *>(win)) {
422 dynamic_cast<wxButton *>(win)->SetBackgroundColour(col);
423 }
424
425 else {
426 ;
427 }
428
429 if (win->GetChildren().GetCount() > 0) {
430 depth++;
431 wxWindow *w = win;
432 SetBackColor(w, col);
433 depth--;
434 }
435 }
436}
437
439 ChartDldrPrefsDlgImpl *dialog = new ChartDldrPrefsDlgImpl(parent);
440
441 wxFont fo = GetOCPNGUIScaledFont_PlugIn(_("Dialog"));
442 dialog->SetFont(fo);
443
444#ifdef __ANDROID__
445 if (m_parent_window) {
446 int xmax = m_parent_window->GetSize().GetWidth();
447 int ymax = m_parent_window->GetParent()
448 ->GetSize()
449 .GetHeight(); // This would be the Options dialog itself
450 dialog->SetSize(xmax, ymax);
451 dialog->Layout();
452
453 dialog->Move(0, 0);
454 }
455
456 wxColour cl = wxColour(214, 218, 222);
457 SetBackColor(dialog, cl);
458#endif
459
460 dialog->SetPath(m_base_chart_dir);
461 dialog->SetPreferences(m_preselect_new, m_preselect_updated,
462 m_allow_bulk_update);
463
464 dialog->ShowModal();
465 dialog->Destroy();
466}
467
468void chartdldr_pi::UpdatePrefs(ChartDldrPrefsDlgImpl *dialog) {
469 m_base_chart_dir = dialog->GetPath();
470 dialog->GetPreferences(m_preselect_new, m_preselect_updated,
471 m_allow_bulk_update);
472 SaveConfig();
473 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
474}
475
476bool getDisplayMetrics() {
477#ifdef __ANDROID__
478
479 g_androidDPmm = 4.0; // nominal default
480
481 // Get a reference to the running native activity
482 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
483 "org/qtproject/qt5/android/QtNative", "activity",
484 "()Landroid/app/Activity;");
485
486 if (!activity.isValid()) {
487 return false;
488 }
489
490 // Call the desired method
491 QAndroidJniObject data =
492 activity.callObjectMethod("getDisplayMetrics", "()Ljava/lang/String;");
493
494 wxString return_string;
495 jstring s = data.object<jstring>();
496
497 // Need a Java environment to decode the resulting string
498 JNIEnv *jenv;
499 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
500 // qDebug() << "GetEnv failed.";
501 } else {
502 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
503 return_string = wxString(ret_string, wxConvUTF8);
504 }
505
506 // Return string may have commas instead of periods, if using Euro locale
507 // We just fix it here...
508 return_string.Replace(_T(","), _T("."));
509
510 // wxLogMessage(_T("Metrics:") + return_string);
511 // wxSize screen_size = ::wxGetDisplaySize();
512 // wxString msg;
513 // msg.Printf(_T("wxGetDisplaySize(): %d %d"), screen_size.x,
514 // screen_size.y); wxLogMessage(msg);
515
516 double density = 1.0;
517 wxStringTokenizer tk(return_string, _T(";"));
518 if (tk.HasMoreTokens()) {
519 wxString token = tk.GetNextToken(); // xdpi
520 token = tk.GetNextToken(); // density
521
522 long b = ::wxGetDisplaySize().y;
523 token.ToDouble(&density);
524
525 token = tk.GetNextToken(); // ldpi
526
527 token = tk.GetNextToken(); // width
528 token = tk.GetNextToken(); // height - statusBarHeight
529 token = tk.GetNextToken(); // width
530 token = tk.GetNextToken(); // height
531 token = tk.GetNextToken(); // dm.widthPixels
532 token = tk.GetNextToken(); // dm.heightPixels
533
534 token = tk.GetNextToken(); // actionBarHeight
535 long abh;
536 token.ToLong(&abh);
537 // g_ActionBarHeight = wxMax(abh, 50);
538
539 // qDebug() << "g_ActionBarHeight" << abh << g_ActionBarHeight;
540 }
541
542 double ldpi = 160. * density;
543
544 // double maxDim = wxMax(::wxGetDisplaySize().x, ::wxGetDisplaySize().y);
545 // ret = (maxDim / ldpi) * 25.4;
546
547 // msg.Printf(_T("Android Auto Display Size (mm, est.): %g"), ret);
548 // wxLogMessage(msg);
549
550 // Save some items as global statics for convenience
551 g_androidDPmm = ldpi / 25.4;
552 // g_androidDensity = density;
553
554 // qDebug() << "PI Metrics" << g_androidDPmm << density;
555 return true;
556#else
557
558 return true;
559#endif
560}
561
562ChartSource::ChartSource(wxString name, wxString url, wxString localdir) {
563 m_name = name;
564 m_url = url;
565 m_dir = localdir;
566 m_update_data.clear();
567}
568
569ChartSource::~ChartSource() { m_update_data.clear(); }
570
571#define ID_MNU_SELALL 2001
572#define ID_MNU_DELALL 2002
573#define ID_MNU_INVSEL 2003
574#define ID_MNU_SELUPD 2004
575#define ID_MNU_SELNEW 2005
576
577enum { ThreadId = wxID_HIGHEST + 1 };
578
579BEGIN_EVENT_TABLE(ChartDldrPanelImpl, ChartDldrPanel)
580END_EVENT_TABLE()
581
582void ChartDldrPanelImpl::OnPopupClick(wxCommandEvent &evt) {
583 switch (evt.GetId()) {
584 case ID_MNU_SELALL:
585 CheckAllCharts(true);
586 break;
587 case ID_MNU_DELALL:
588 CheckAllCharts(false);
589 break;
590 case ID_MNU_INVSEL:
591 InvertCheckAllCharts();
592 break;
593 case ID_MNU_SELUPD:
594 CheckUpdatedCharts(true);
595 break;
596 case ID_MNU_SELNEW:
597 CheckNewCharts(true);
598 break;
599 }
600}
601
602void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
603 wxMenu menu;
604
605 wxPoint mouseScreen = wxGetMousePosition();
606 wxPoint mouseClient = ScreenToClient(mouseScreen);
607
608#ifdef __ANDROID__
609 wxFont *pf = OCPNGetFont(_("Menu"));
610
611 // add stuff
612 wxMenuItem *item1 = new wxMenuItem(&menu, ID_MNU_SELALL, _("Select all"));
613 item1->SetFont(*pf);
614 menu.Append(item1);
615
616 wxMenuItem *item2 = new wxMenuItem(&menu, ID_MNU_DELALL, _("Deselect all"));
617 item2->SetFont(*pf);
618 menu.Append(item2);
619
620 wxMenuItem *item3 =
621 new wxMenuItem(&menu, ID_MNU_INVSEL, _("Invert selection"));
622 item3->SetFont(*pf);
623 menu.Append(item3);
624
625 wxMenuItem *item4 = new wxMenuItem(&menu, ID_MNU_SELUPD, _("Select updated"));
626 item4->SetFont(*pf);
627 menu.Append(item4);
628
629 wxMenuItem *item5 = new wxMenuItem(&menu, ID_MNU_SELNEW, _("Select new"));
630 item5->SetFont(*pf);
631 menu.Append(item5);
632
633#else
634
635 menu.Append(ID_MNU_SELALL, _("Select all"), wxT(""));
636 menu.Append(ID_MNU_DELALL, _("Deselect all"), wxT(""));
637 menu.Append(ID_MNU_INVSEL, _("Invert selection"), wxT(""));
638 menu.Append(ID_MNU_SELUPD, _("Select updated"), wxT(""));
639 menu.Append(ID_MNU_SELNEW, _("Select new"), wxT(""));
640
641#endif
642
643 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
644 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick, NULL,
645 this);
646 // and then display
647 PopupMenu(&menu, mouseClient.x, mouseClient.y);
648}
649
650void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
651 if (pPlugIn->m_pChartSource == NULL) return;
652#ifdef __WXGTK__
653 wxExecute(wxString::Format(_T("xdg-open %s"),
654 pPlugIn->m_pChartSource->GetDir().c_str()));
655#endif
656#ifdef __WXMAC__
657 wxExecute(wxString::Format(_T("open %s"),
658 pPlugIn->m_pChartSource->GetDir().c_str()));
659#endif
660#ifdef __WXMSW__
661 wxExecute(wxString::Format(_T("explorer %s"),
662 pPlugIn->m_pChartSource->GetDir().c_str()));
663#endif
664}
665
666void ChartDldrPanelImpl::SetSource(int id) {
667 pPlugIn->SetSourceId(id);
668
669 m_bDeleteSource->Enable(id >= 0);
670 m_bUpdateChartList->Enable(id >= 0);
671 m_bEditSource->Enable(id >= 0);
672
673 // TODO: DAN - Need to optimze to only update the chart list if needed.
674 // Right now it updates multiple times unnecessarily.
675 CleanForm();
676 if (id >= 0 && id < (int)pPlugIn->m_ChartSources.size()) {
677 ::wxBeginBusyCursor(); // wxSetCursor(wxCURSOR_WAIT);
678 // wxYield();
679 std::unique_ptr<ChartSource> &cs = pPlugIn->m_ChartSources.at(id);
680 cs->LoadUpdateData();
681 cs->UpdateLocalFiles();
682 pPlugIn->m_pChartSource = cs.get();
683 FillFromFile(cs->GetUrl(), cs->GetDir(), pPlugIn->m_preselect_new,
684 pPlugIn->m_preselect_updated);
685 wxURI url(cs->GetUrl());
686 m_chartsLabel->SetLabel(wxString::Format(
687 _("Charts: %s"), (cs->GetName() + _(" from ") + url.BuildURI() +
688 _T(" -> ") + cs->GetDir())
689 .c_str()));
690 if (::wxIsBusy()) ::wxEndBusyCursor();
691 } else {
692 pPlugIn->m_pChartSource = NULL;
693 m_chartsLabel->SetLabel(_("Charts"));
694 }
695}
696
697void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
698 int i = GetSelectedCatalog();
699 if (i >= 0) SetSource(i);
700 event.Skip();
701}
702
703void ChartDldrPanelImpl::SetBulkUpdate(bool bulk_update) {
704 m_bUpdateAllCharts->Enable(bulk_update);
705 m_bUpdateAllCharts->Show(bulk_update);
706 Layout();
707 m_parent->Layout();
708}
709
710void ChartDldrPanelImpl::CleanForm() {
711#if defined(CHART_LIST)
712 clearChartList();
713#else
714 m_scrollWinChartList->ClearBackground();
715#endif /* CHART_LIST */
716 // m_stCatalogInfo->Show( false );
717}
718
719void ChartDldrPanelImpl::FillFromFile(wxString url, wxString dir, bool selnew,
720 bool selupd) {
721 // load if exists
722 wxStringTokenizer tk(url, _T("/"));
723 wxString file;
724 do {
725 file = tk.GetNextToken();
726 } while (tk.HasMoreTokens());
727 wxFileName fn;
728 fn.SetFullName(file);
729 fn.SetPath(dir);
730 wxString path = fn.GetFullPath();
731 if (wxFileExists(path)) {
732 pPlugIn->m_pChartCatalog.LoadFromFile(path);
733 // m_tChartSourceInfo->SetValue(pPlugIn->m_pChartCatalog.GetDescription());
734 // fill in the rest of the form
735
736 m_updatedCharts = 0;
737 m_newCharts = 0;
738
739#if !defined(CHART_LIST)
740 // Clear any existing panels
741 m_panelArray.clear();
742 m_scrollWinChartList->ClearBackground();
743#endif /* CHART_LIST */
744
745 for (size_t i = 0; i < pPlugIn->m_pChartCatalog.charts.size(); i++) {
746 wxString status;
747 wxString latest;
748 bool bcheck = false;
749 wxString file_ =
750 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartFilename(true);
751 if (!pPlugIn->m_pChartSource->ExistsLocaly(
752 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_)) {
753 m_newCharts++;
754 status = _("New");
755 if (selnew) bcheck = true;
756 } else {
757 if (pPlugIn->m_pChartSource->IsNewerThanLocal(
758 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_,
759 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime())) {
760 m_updatedCharts++;
761 status = _("Out of date");
762 if (selupd) bcheck = true;
763 } else {
764 status = _("Up to date");
765 }
766 }
767 latest =
768 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime().Format(
769 _T("%Y-%m-%d"));
770
771#if defined(CHART_LIST)
772 wxVector<wxVariant> data;
773 data.push_back(wxVariant(bcheck));
774 data.push_back(wxVariant(status));
775 data.push_back(wxVariant(latest));
776 data.push_back(
777 wxVariant(pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle()));
778 getChartList()->AppendItem(data);
779#else
780 auto pC = std::make_unique<ChartPanel>(
781 m_scrollWinChartList, wxID_ANY, wxDefaultPosition, wxSize(-1, -1),
782 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle(), status,
783 latest, this, bcheck);
784 pC->Connect(wxEVT_RIGHT_DOWN,
785 wxMouseEventHandler(ChartDldrPanel::OnContextMenu), NULL,
786 this);
787
788 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
789 m_panelArray.push_back(std::move(pC));
790#endif /* CHART_LIST */
791 }
792
793#if !defined(CHART_LIST) // wxDataViewListCtrl handles all of this AFAIK: Dan
794 m_scrollWinChartList->ClearBackground();
795 m_scrollWinChartList->FitInside();
796 m_scrollWinChartList->GetSizer()->Layout();
797 Layout();
798 m_scrollWinChartList->ClearBackground();
799 SetChartInfo(wxString::Format(_("%lu charts total, %lu updated, %lu new"),
800 pPlugIn->m_pChartCatalog.charts.size(),
801 m_updatedCharts, m_newCharts));
802#else
803 SetChartInfo(wxString::Format(
804 _("%lu charts total, %lu updated, %lu new, %lu selected"),
805 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
806 GetCheckedChartCount()));
807#endif /* CHART_LIST */
808 }
809}
810
811bool ChartSource::ExistsLocaly(wxString chart_number, wxString filename) {
812 wxASSERT(this);
813
814 wxStringTokenizer tk(filename, _T("."));
815 wxString file = tk.GetNextToken().MakeLower();
816
817 if (!m_update_data.empty()) {
818 return m_update_data.find(std::string(chart_number.Lower().mb_str())) !=
819 m_update_data.end() ||
820 m_update_data.find(std::string(file.mb_str())) !=
821 m_update_data.end();
822 }
823 for (size_t i = 0; i < m_localfiles.Count(); i++) {
824 if (m_localfiles.Item(i) == file) return true;
825 }
826 return false;
827}
828
829bool ChartSource::IsNewerThanLocal(wxString chart_number, wxString filename,
830 wxDateTime validDate) {
831 wxStringTokenizer tk(filename, _T("."));
832 wxString file = tk.GetNextToken().MakeLower();
833 time_t validTime = validDate.GetTicks();
834 if (!m_update_data.empty()) {
835 time_t chartNumberTime =
836 m_update_data[std::string(chart_number.Lower().mbc_str())];
837 time_t updateFileTime = m_update_data[std::string(file.mbc_str())];
838 bool needsUpdate =
839 chartNumberTime < validTime && updateFileTime < validTime;
840 if (wxLOG_Debug <= wxLog::GetLogLevel()) {
841 // Show these only if user has selected loglevel debug, otherwise save a
842 // few cpu cycles
843 wxLogInfo("Latest Zip File Date: %sZ",
844 validDate.ToUTC().FormatISOCombined());
845 wxLogInfo("Local File: %s, Date: %sZ", filename,
846 wxDateTime(updateFileTime).ToUTC().FormatISOCombined());
847 wxLogInfo("Chart Number %s DOB: Date: %sZ", chart_number,
848 wxDateTime(chartNumberTime).ToUTC().FormatISOCombined());
849 wxLogInfo("Chart Number %s Needs update: %s", chart_number,
850 needsUpdate ? wxString("true") : wxString("false"));
851 }
852 return needsUpdate;
853 }
854
855 bool update_candidate = false;
856
857 for (size_t i = 0; i < m_localfiles.Count(); i++) {
858 if (m_localfiles.Item(i) == file) {
859 if (validDate.IsLaterThan(m_localdt.at(i))) {
860 update_candidate = true;
861 } else
862 return false;
863 }
864 }
865 return update_candidate;
866}
867
868int ChartDldrPanelImpl::GetSelectedCatalog() {
869 long item =
870 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
871 return item;
872}
873
874void ChartDldrPanelImpl::SelectCatalog(int item) {
875 if (item >= 0) {
876 m_bDeleteSource->Enable();
877 m_bEditSource->Enable();
878 m_bUpdateChartList->Enable();
879 } else {
880 m_bDeleteSource->Disable();
881 m_bEditSource->Disable();
882 m_bUpdateChartList->Disable();
883 }
884 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
885 wxLIST_STATE_SELECTED);
886}
887
888void ChartDldrPanelImpl::AppendCatalog(std::unique_ptr<ChartSource> &cs) {
889 long id = m_lbChartSources->GetItemCount();
890 m_lbChartSources->InsertItem(id, cs->GetName());
891 m_lbChartSources->SetItem(id, 1, _("(Please update first)"));
892 m_lbChartSources->SetItem(id, 2, cs->GetDir());
893 wxURI url(cs->GetUrl());
894 if (url.IsReference()) {
896 this, _("Error, the URL to the chart source data seems wrong."),
897 _("Error"));
898 return;
899 }
900 wxFileName fn(url.GetPath());
901 fn.SetPath(cs->GetDir());
902 wxString path = fn.GetFullPath();
903 if (wxFileExists(path)) {
904 if (pPlugIn->m_pChartCatalog.LoadFromFile(path, true)) {
905 m_lbChartSources->SetItem(id, 0, pPlugIn->m_pChartCatalog.title);
906 m_lbChartSources->SetItem(
907 id, 1,
908 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
909 _T("%Y-%m-%d %H:%M")));
910 m_lbChartSources->SetItem(id, 2, path);
911#ifdef __ANDROID__
912 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
913 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
914 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
915#endif
916 }
917 }
918}
919
920void ChartDldrPanelImpl::UpdateAllCharts(wxCommandEvent &event) {
921 int failed_to_update = 0;
922 int attempted_to_update = 0;
923 if ((pPlugIn->m_preselect_new) && (pPlugIn->m_preselect_updated)) {
924 wxMessageDialog mess(
925 this,
926 _("You have chosen to update all chart catalogs.\nThen download all "
927 "new and updated charts.\nThis may take a long time."),
928 _("Chart Downloader"), wxOK | wxCANCEL);
929 if (mess.ShowModal() == wxID_CANCEL) return;
930 } else if (pPlugIn->m_preselect_new) {
931 wxMessageDialog mess(
932 this,
933 _("You have chosen to update all chart catalogs.\nThen download only "
934 "new (but not updated) charts.\nThis may take a long time."),
935 _("Chart Downloader"), wxOK | wxCANCEL);
936 if (mess.ShowModal() == wxID_CANCEL) return;
937 } else if (pPlugIn->m_preselect_updated) {
938 wxMessageDialog mess(
939 this,
940 _("You have chosen to update all chart catalogs.\nThen download only "
941 "updated (but not new) charts.\nThis may take a long time."),
942 _("Chart Downloader"), wxOK | wxCANCEL);
943 if (mess.ShowModal() == wxID_CANCEL) return;
944 }
945 updatingAll = true;
946 cancelled = false;
947 // Flip to the list of charts so user can observe the download progress
948 int oldPage = m_DLoadNB->SetSelection(1);
949 for (long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
950 chartIndex++) {
951 m_lbChartSources->SetItemState(chartIndex, wxLIST_STATE_SELECTED,
952 wxLIST_STATE_SELECTED);
953 if (cancelled) break;
954 UpdateChartList(event);
956 attempted_to_update += m_downloading;
957 failed_to_update += m_failed_downloads;
958 }
959 wxLogMessage(wxString::Format(
960 _T("chartdldr_pi::UpdateAllCharts() downloaded %d out of %d charts."),
961 attempted_to_update - failed_to_update, attempted_to_update));
962 if (failed_to_update > 0)
964 this,
965 wxString::Format(_("%d out of %d charts failed to download.\nCheck the "
966 "list, verify there is a working Internet "
967 "connection and repeat the operation if needed."),
968 failed_to_update, attempted_to_update),
969 _("Chart Downloader"), wxOK | wxICON_ERROR);
970 if (attempted_to_update > failed_to_update) ForceChartDBUpdate();
971 updatingAll = false;
972 cancelled = false;
973 // Flip back to the original page
974 m_DLoadNB->SetSelection(oldPage);
975}
976
977void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
978 // TODO: check if everything exists and we can write to the output dir etc.
979 if (!m_lbChartSources->GetSelectedItemCount()) return;
980 std::unique_ptr<ChartSource> &cs =
981 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
982 wxURI url(cs->GetUrl());
983 if (url.IsReference()) {
985 this, _("Error, the URL to the chart source data seems wrong."),
986 _("Error"));
987 return;
988 }
989
990 wxStringTokenizer tk(url.GetPath(), _T("/"));
991 wxString file;
992 do {
993 file = tk.GetNextToken();
994 } while (tk.HasMoreTokens());
995 wxFileName fn;
996 fn.SetFullName(file);
997 fn.SetPath(cs->GetDir());
998 if (!wxDirExists(cs->GetDir())) {
999 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
1001 this,
1002 wxString::Format(_("Directory %s can't be created."),
1003 cs->GetDir().c_str()),
1004 _("Chart Downloader"));
1005 return;
1006 }
1007 }
1008
1009 bool bok = false;
1010
1011#ifdef __ANDROID__
1012 wxString file_URI = _T("file://") + fn.GetFullPath();
1013
1014 // wxFile testFile(tfn.GetFullPath().c_str(), wxFile::write);
1015 // if(!testFile.IsOpened()){
1016 // wxMessageBox(this, wxString::Format(_("File %s can't be written.
1017 // \nChoose a writable folder for Chart Downloader file storage."),
1018 // tfn.GetFullPath().c_str()), _("Chart Downloader")); return;
1019 // }
1020 // testFile.Close();
1021 // ::wxRemoveFile(tfn.GetFullPath());
1022
1024 cs->GetUrl(), file_URI, _("Downloading file"),
1025 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
1030 10);
1031 bok = true;
1032
1033#else
1034 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
1035 wxString file_URI = tfn.GetFullPath();
1036
1038 cs->GetUrl(), file_URI, _("Downloading file"),
1039 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
1044 10);
1045
1046 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
1047 wxRemoveFile(tfn.GetFullPath());
1048
1049#endif
1050
1051 // wxLogMessage(_T("chartdldr_pi: OCPN_downloadFile done:"));
1052
1053 switch (ret) {
1054 case OCPN_DL_NO_ERROR: {
1055 if (bok) {
1056 long id = GetSelectedCatalog();
1057 SetSource(id);
1058
1059 m_lbChartSources->SetItem(id, 0, pPlugIn->m_pChartCatalog.title);
1060 m_lbChartSources->SetItem(
1061 id, 1,
1062 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1063 _T("%Y-%m-%d %H:%M")));
1064 m_lbChartSources->SetItem(id, 2, cs->GetDir());
1065
1066 } else
1068 this,
1069 wxString::Format(_("Failed to Find New Catalog: %s "),
1070 url.BuildURI().c_str()),
1071 _("Chart Downloader"), wxOK | wxICON_ERROR);
1072 break;
1073 }
1074 case OCPN_DL_FAILED: {
1076 this,
1077 wxString::Format(_("Failed to Download Catalog: %s \nVerify there is "
1078 "a working Internet connection."),
1079 url.BuildURI().c_str()),
1080 _("Chart Downloader"), wxOK | wxICON_ERROR);
1081 break;
1082 }
1083
1085 case OCPN_DL_ABORTED: {
1086 cancelled = true;
1087 break;
1088 }
1089
1090 case OCPN_DL_UNKNOWN:
1091 case OCPN_DL_STARTED: {
1092 break;
1093 }
1094
1095 default:
1096 wxASSERT(false); // This should never happen because we handle all
1097 // possible cases of ret
1098 }
1099
1100 if ((ret == OCPN_DL_NO_ERROR) && bok) m_DLoadNB->SetSelection(1);
1101}
1102
1103void ChartSource::GetLocalFiles() {
1104 if (!UpdateDataExists() || m_update_data.empty()) {
1105 wxArrayString *allFiles = new wxArrayString;
1106 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1107 m_localdt.clear();
1108 m_localfiles.Clear();
1109 wxDateTime ct, mt, at;
1110 wxString name;
1111 for (size_t i = 0; i < allFiles->Count(); i++) {
1112 wxFileName fn(allFiles->Item(i));
1113 name = fn.GetFullName().Lower();
1114 // Only add unique files names to the local list.
1115 // This is safe because all chart names within a catalog
1116 // are necessarily unique.
1117 if (!ExistsLocaly(wxEmptyString, name)) {
1118 fn.GetTimes(&at, &mt, &ct);
1119 m_localdt.push_back(mt);
1120 m_localfiles.Add(fn.GetName().Lower());
1121
1122 wxStringTokenizer tk(name, _T("."));
1123 wxString file = tk.GetNextToken().MakeLower();
1124 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1125 }
1126 }
1127 allFiles->Clear();
1128 wxDELETE(allFiles);
1129 SaveUpdateData();
1130 } else {
1131 LoadUpdateData();
1132 }
1133}
1134
1135bool ChartSource::UpdateDataExists() {
1136 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1137 _T(UPDATE_DATA_FILENAME));
1138}
1139
1140void ChartSource::LoadUpdateData() {
1141 m_update_data.clear();
1142 wxString fn =
1143 GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1144
1145 if (!wxFileExists(fn)) return;
1146
1147 std::ifstream infile(fn.mb_str());
1148
1149 std::string key;
1150 time_t value(0);
1151
1152 while (infile >> key >> value) m_update_data[key] = value;
1153
1154 infile.close();
1155}
1156
1157void ChartSource::SaveUpdateData() {
1158 wxString fn;
1159 fn = GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1160
1161#ifdef __ANDROID__
1162 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1163 _T(UPDATE_DATA_FILENAME);
1164#endif
1165
1166 std::ofstream outfile(fn.mb_str());
1167 if (!outfile.is_open()) return;
1168
1169 std::map<std::string, time_t>::iterator iter;
1170 for (iter = m_update_data.begin(); iter != m_update_data.end(); ++iter) {
1171 if (iter->first.find(" ") == std::string::npos)
1172 if (!iter->first.empty())
1173 outfile << iter->first << " " << iter->second << "\n";
1174 }
1175
1176 outfile.close();
1177
1178#ifdef __ANDROID__
1179 AndroidSecureCopyFile(
1180 fn, GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME));
1181#endif
1182}
1183
1184void ChartSource::ChartUpdated(wxString chart_number, time_t timestamp) {
1185 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1186 SaveUpdateData();
1187}
1188
1189bool ChartDldrPanelImpl::DownloadChart(wxString url, wxString file,
1190 wxString title) {
1191 return false;
1192}
1193
1194void ChartDldrPanelImpl::DisableForDownload(bool enabled) {
1195 m_bAddSource->Enable(enabled);
1196 m_bDeleteSource->Enable(enabled);
1197 m_bEditSource->Enable(enabled);
1198 m_bUpdateAllCharts->Enable(enabled);
1199 m_bUpdateChartList->Enable(enabled);
1200 m_lbChartSources->Enable(enabled);
1201#if defined(CHART_LIST)
1202 m_bSelectNew->Enable(enabled);
1203 m_bSelectUpdated->Enable(enabled);
1204 m_bSelectAll->Enable(enabled);
1205#endif /* CHART_LIST */
1206}
1207
1208void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1209 if (DownloadIsCancel) {
1210 cancelled = true;
1211 return;
1212 }
1214}
1215#if defined(CHART_LIST)
1216void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1217 if (!m_bInfoHold)
1218 SetChartInfo(wxString::Format(
1219 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1220 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1221 GetCheckedChartCount()));
1222 else
1223 event.Skip();
1224}
1225#endif /* CHART_LIST */
1226#if defined(CHART_LIST)
1227void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1228 CheckNewCharts(true);
1229}
1230#endif /* CHART_LIST */
1231
1232#if defined(CHART_LIST)
1233void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1234 CheckUpdatedCharts(true);
1235}
1236#endif /* CHART_LIST */
1237
1238#if defined(CHART_LIST)
1239void ChartDldrPanelImpl::OnSelectAllCharts(wxCommandEvent &event) {
1240 if (m_bSelectAll->GetLabel() == _("Select All")) {
1241 CheckAllCharts(true);
1242 m_bSelectAll->SetLabel(_("Select None"));
1243 m_bSelectAll->SetToolTip(_("De-select all charts in the list."));
1244 } else {
1245 CheckAllCharts(false);
1246 m_bSelectAll->SetLabel(_("Select All"));
1247 m_bSelectAll->SetToolTip(_("Select all charts in the list."));
1248 }
1249}
1250#endif /* CHART_LIST */
1251
1252int ChartDldrPanelImpl::GetChartCount() {
1253#if defined(CHART_LIST)
1254 return getChartList()->GetItemCount();
1255#else
1256 return m_panelArray.size();
1257#endif /* CHART_LIST*/
1258}
1259
1260int ChartDldrPanelImpl::GetCheckedChartCount() {
1261#if defined(CHART_LIST)
1262 int cnt = 0;
1263 int chartCnt = GetChartCount();
1264 for (int i = 0; i < chartCnt; i++)
1265 if (isChartChecked(i)) cnt++;
1266#else
1267 int cnt = 0;
1268 for (int i = 0; i < GetChartCount(); i++) {
1269 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1270 }
1271#endif /* CHART_LIST*/
1272 return cnt;
1273}
1274
1275bool ChartDldrPanelImpl::isChartChecked(int i) {
1276 wxASSERT_MSG(i >= 0,
1277 wxT("This function should be called with non-negative index."));
1278 if (i <= GetChartCount())
1279#if defined(CHART_LIST)
1280 return getChartList()->GetToggleValue(i, 0);
1281#else
1282 return m_panelArray.at(i)->GetCB()->IsChecked();
1283#endif /* CHART_LIST*/
1284 else
1285 return false;
1286}
1287
1288void ChartDldrPanelImpl::CheckAllCharts(bool value) {
1289#if defined(CHART_LIST)
1290 m_bInfoHold = true;
1291#endif /* CHART_LIST */
1292
1293 for (int i = 0; i < GetChartCount(); i++) {
1294#if defined(CHART_LIST)
1295 getChartList()->SetToggleValue(value, i, 0);
1296#else
1297 m_panelArray.at(i)->GetCB()->SetValue(value);
1298#endif /* CHART_LIST*/
1299 }
1300#if defined(CHART_LIST)
1301 SetChartInfo(wxString::Format(
1302 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1303 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1304 GetCheckedChartCount()));
1305 m_bInfoHold = false;
1306#endif /* CHART_LIST */
1307}
1308
1309void ChartDldrPanelImpl::CheckNewCharts(bool value) {
1310 for (int i = 0; i < GetChartCount(); i++) {
1311#if defined(CHART_LIST)
1312 if (isNew(i)) getChartList()->SetToggleValue(true, i, 0);
1313#else
1314 if (m_panelArray.at(i)->isNew())
1315 m_panelArray.at(i)->GetCB()->SetValue(value);
1316#endif /* CHART_LIST*/
1317 }
1318#if defined(CHART_LIST)
1319 SetChartInfo(wxString::Format(
1320 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1321 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1322 GetCheckedChartCount()));
1323#endif /* CHART_LIST */
1324}
1325
1326void ChartDldrPanelImpl::CheckUpdatedCharts(bool value) {
1327 for (int i = 0; i < GetChartCount(); i++) {
1328#if defined(CHART_LIST)
1329 if (isUpdated(i)) getChartList()->SetToggleValue(value, i, 0);
1330#else
1331 if (m_panelArray.at(i)->isUpdated())
1332 m_panelArray.at(i)->GetCB()->SetValue(value);
1333#endif /* CHART_LIST */
1334 }
1335#if defined(CHART_LIST)
1336 SetChartInfo(wxString::Format(
1337 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1338 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1339 GetCheckedChartCount()));
1340#endif /* CHART_LIST */
1341}
1342
1343void ChartDldrPanelImpl::InvertCheckAllCharts() {
1344#if defined(CHART_LIST)
1345 m_bInfoHold = true;
1346#endif /* CHART_LIST */
1347 for (int i = 0; i < GetChartCount(); i++)
1348#if defined(CHART_LIST)
1349 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1350#else
1351 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1352#endif /* CHART_LIST */
1353#if defined(CHART_LIST)
1354 m_bInfoHold = false;
1355 SetChartInfo(wxString::Format(
1356 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1357 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1358 GetCheckedChartCount()));
1359#endif /* CHART_LIST */
1360}
1361
1363 if (!m_bconnected) {
1364 Connect(
1365 wxEVT_DOWNLOAD_EVENT,
1366 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1367 m_bconnected = true;
1368 }
1369
1370 if (!GetCheckedChartCount() && !updatingAll) {
1371 OCPNMessageBox_PlugIn(this, _("No charts selected for download."));
1372 return;
1373 }
1374 std::unique_ptr<ChartSource> &cs =
1375 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
1376
1377 cancelled = false;
1378 to_download = GetCheckedChartCount();
1379 m_downloading = 0;
1380 m_failed_downloads = 0;
1381 DisableForDownload(false);
1382 // wxString old_label = m_bDnldCharts->GetLabel(); // Broken on Android??
1383 m_bDnldCharts->SetLabel(_("Abort download"));
1384 DownloadIsCancel = true;
1385
1386 wxFileName downloaded_p;
1387 int idx = -1;
1388
1389 for (int i = 0; i < GetChartCount() && to_download; i++) {
1390 int index = i;
1391 if (cancelled) break;
1392 // Prepare download queues
1393 if (!isChartChecked(i)) continue;
1394 m_bTransferComplete = false;
1395 m_bTransferSuccess = true;
1396 m_totalsize = -1;
1397 m_transferredsize = 0;
1398 m_downloading++;
1399 if (pPlugIn->m_pChartCatalog.charts.at(index)->NeedsManualDownload()) {
1400 if (wxID_YES ==
1402 this,
1403 wxString::Format(
1404 _("The selected chart '%s' can't be downloaded automatically, do you want me to open a browser window and download them manually?\n\n \
1405After downloading the charts, please extract them to %s"),
1406 pPlugIn->m_pChartCatalog.charts.at(index)->title.c_str(),
1407 pPlugIn->m_pChartSource->GetDir().c_str()),
1408 _("Chart Downloader"), wxYES_NO | wxCENTRE | wxICON_QUESTION)) {
1409 wxLaunchDefaultBrowser(
1410 pPlugIn->m_pChartCatalog.charts.at(index)->GetManualDownloadUrl());
1411 }
1412 continue;
1413 }
1414
1415 // download queue
1416 wxURI url(pPlugIn->m_pChartCatalog.charts.at(index)->GetDownloadLocation());
1417 if (url.IsReference()) {
1419 this,
1420 wxString::Format(
1421 _("Error, the URL to the chart (%s) data seems wrong."),
1422 url.BuildURI().c_str()),
1423 _("Error"));
1424 this->Enable();
1426 return;
1427 }
1428 // construct local file path
1429 wxString file =
1430 pPlugIn->m_pChartCatalog.charts.at(index)->GetChartFilename();
1431 wxFileName fn;
1432 fn.SetFullName(file);
1433 fn.SetPath(cs->GetDir());
1434 wxString path = fn.GetFullPath();
1435 if (wxFileExists(path)) wxRemoveFile(path);
1436 wxString title = pPlugIn->m_pChartCatalog.charts.at(index)->GetChartTitle();
1437
1438 // Ready to start download
1439#ifdef __ANDROID__
1440 wxString file_path = _T("file://") + fn.GetFullPath();
1441#else
1442 wxString file_path = fn.GetFullPath();
1443#endif
1444
1445 long handle;
1446 OCPN_downloadFileBackground(url.BuildURI(), file_path, this, &handle);
1447
1448 if (idx >= 0) {
1449 if (pPlugIn->ProcessFile(
1450 downloaded_p.GetFullPath(), downloaded_p.GetPath(), true,
1451 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1452 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1453 pPlugIn->m_pChartCatalog.charts.at(idx)
1454 ->GetUpdateDatetime()
1455 .GetTicks());
1456 } else {
1457 m_failed_downloads++;
1458 }
1459 idx = -1;
1460 }
1461
1462 while (!m_bTransferComplete && m_bTransferSuccess && !cancelled) {
1463 if (m_failed_downloads)
1464 SetChartInfo(wxString::Format(
1465 _("Downloading chart %u of %u, %u downloads failed (%s / %s)"),
1466 m_downloading, to_download, m_failed_downloads,
1467 FormatBytes(m_transferredsize), FormatBytes(m_totalsize)));
1468 else
1469 SetChartInfo(wxString::Format(_("Downloading chart %u of %u (%s / %s)"),
1470 m_downloading, to_download,
1471 FormatBytes(m_transferredsize),
1472 FormatBytes(m_totalsize)));
1473
1474 Update();
1475 Refresh();
1476
1477 wxTheApp->ProcessPendingEvents();
1478 wxYield();
1479 wxMilliSleep(20);
1480 }
1481
1482 if (cancelled) {
1483 idx = -1;
1485 }
1486
1487 if (m_bTransferSuccess && !cancelled) {
1488 idx = index;
1489 downloaded_p = path;
1490 } else {
1491 idx = -1;
1492 if (wxFileExists(path)) wxRemoveFile(path);
1493 m_failed_downloads++;
1494 }
1495 }
1496 if (idx >= 0) {
1497 if (pPlugIn->ProcessFile(
1498 downloaded_p.GetFullPath(), downloaded_p.GetPath(), true,
1499 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1500 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1501 pPlugIn->m_pChartCatalog.charts.at(idx)
1502 ->GetUpdateDatetime()
1503 .GetTicks());
1504 } else {
1505 m_failed_downloads++;
1506 }
1507 }
1508 DisableForDownload(true);
1509 m_bDnldCharts->SetLabel(_("Download selected charts"));
1510 DownloadIsCancel = false;
1511 SetSource(GetSelectedCatalog());
1512 if (m_failed_downloads > 0 && !updatingAll && !cancelled)
1514 this,
1515 wxString::Format(_("%d out of %d charts failed to download.\nCheck the "
1516 "list, verify there is a working Internet "
1517 "connection and repeat the operation if needed."),
1518 m_failed_downloads, m_downloading),
1519 _("Chart Downloader"), wxOK | wxICON_ERROR);
1520
1521 if (cancelled)
1522 OCPNMessageBox_PlugIn(this, _("Chart download cancelled."),
1523 _("Chart Downloader"), wxOK | wxICON_INFORMATION);
1524
1525 if ((m_downloading - m_failed_downloads > 0) && !updatingAll)
1527}
1528
1529ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1530 Disconnect(
1531 wxEVT_DOWNLOAD_EVENT,
1532 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1533 m_bconnected = false;
1534
1535#ifndef __ANDROID__
1537 0); // Stop the thread, is something like this needed on Android as well?
1538#endif
1539#if defined(CHART_LIST)
1540 clearChartList();
1541#endif /* CHART_LIST */
1542}
1543
1544ChartDldrPanelImpl::ChartDldrPanelImpl(chartdldr_pi *plugin, wxWindow *parent,
1545 wxWindowID id, const wxPoint &pos,
1546 const wxSize &size, long style)
1547 : ChartDldrPanel(parent, id, pos, size, style) {
1548 m_bDeleteSource->Disable();
1549 m_bUpdateChartList->Disable();
1550 m_bEditSource->Disable();
1551 m_lbChartSources->InsertColumn(0, _("Catalog"), wxLIST_FORMAT_LEFT,
1552 CATALOGS_NAME_WIDTH);
1553 m_lbChartSources->InsertColumn(1, _("Released"), wxLIST_FORMAT_LEFT,
1554 CATALOGS_DATE_WIDTH);
1555 m_lbChartSources->InsertColumn(2, _("Local path"), wxLIST_FORMAT_LEFT,
1556 CATALOGS_PATH_WIDTH);
1557 m_lbChartSources->Enable();
1558 m_bInfoHold = false;
1559 cancelled = true;
1560 to_download = -1;
1561 m_downloading = -1;
1562 updatingAll = false;
1563 pPlugIn = plugin;
1564 m_populated = false;
1565 DownloadIsCancel = false;
1566 m_failed_downloads = 0;
1567 SetChartInfo(wxEmptyString);
1568 m_bTransferComplete = true;
1569 m_bTransferSuccess = true;
1570
1571 Connect(
1572 wxEVT_DOWNLOAD_EVENT,
1573 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1574 m_bconnected = true;
1575
1576 for (size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1577 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1578 }
1579 m_populated = true;
1580}
1581
1582void ChartDldrPanelImpl::OnPaint(wxPaintEvent &event) {
1583 if (!m_populated) {
1584 m_populated = true;
1585 for (size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1586 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1587 }
1588 }
1589#ifdef __WXMAC__
1590 // Mojave does not paint the controls correctly without this.
1591 m_lbChartSources->Refresh(true);
1592#endif
1593 event.Skip();
1594}
1595
1596void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1597 if (!m_lbChartSources->GetSelectedItemCount()) return;
1598 if (wxID_YES != OCPNMessageBox_PlugIn(
1599 this,
1600 _("Do you really want to remove the chart source?\nThe "
1601 "local chart files will not be removed,\nbut you will "
1602 "not be able to update the charts anymore."),
1603 _("Chart Downloader"), wxYES_NO | wxCENTRE))
1604 return;
1605 int ToBeRemoved = GetSelectedCatalog();
1606 m_lbChartSources->SetItemState(ToBeRemoved, 0,
1607 wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1608 pPlugIn->m_ChartSources.erase(pPlugIn->m_ChartSources.begin() + ToBeRemoved);
1609 m_lbChartSources->DeleteItem(ToBeRemoved);
1610 CleanForm();
1611 pPlugIn->SetSourceId(-1);
1612 SelectCatalog(-1);
1613 pPlugIn->SaveConfig();
1614 event.Skip();
1615}
1616
1617void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1619 dialog->SetBasePath(pPlugIn->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 pPlugIn->m_ChartSources.push_back(std::move(cs));
1655 pPlugIn->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();
1668 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1669 dialog->SetSourceEdit(pPlugIn->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 pPlugIn->m_ChartSources.at(cat)->SetName(
1677 dialog->m_tSourceName->GetValue());
1678 pPlugIn->m_ChartSources.at(cat)->SetUrl(
1679 dialog->m_tChartSourceUrl->GetValue());
1680 pPlugIn->m_ChartSources.at(cat)->SetDir(
1681 dialog->m_tcChartDirectory->GetValue());
1682
1683 m_lbChartSources->SetItem(cat, 0,
1684 pPlugIn->m_ChartSources.at(cat)->GetName());
1685 m_lbChartSources->SetItem(cat, 1, _("(Please update first)"));
1686 m_lbChartSources->SetItem(cat, 2,
1687 pPlugIn->m_ChartSources.at(cat)->GetDir());
1688 wxURI url(pPlugIn->m_ChartSources.at(cat)->GetUrl());
1689 wxFileName fn(url.GetPath());
1690 fn.SetPath(pPlugIn->m_ChartSources.at(cat)->GetDir());
1691 wxString path = fn.GetFullPath();
1692 if (wxFileExists(path)) {
1693 if (pPlugIn->m_pChartCatalog.LoadFromFile(path, true)) {
1694 m_lbChartSources->SetItem(cat, 0, pPlugIn->m_pChartCatalog.title);
1695 m_lbChartSources->SetItem(
1696 cat, 1,
1697 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1698 _T("%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 (pPlugIn->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 pPlugIn->m_ChartSources.at(cat)->GetDir().c_str()),
1718 _("Chart Downloader"));
1719
1720 pPlugIn->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(_T("zip"))) // Zip compressed
1740 {
1741 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1742 if (ret)
1743 wxRemoveFile(aFile);
1744 else
1745 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1746 return ret;
1747 }
1748#ifdef DLDR_USE_LIBARCHIVE
1749 else if (aFile.Lower().EndsWith(_T("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(_T("chartdldr_pi: Unable to extract: ") + aFile);
1760 return ret;
1761 } else if (aFile.Lower().EndsWith(_T("tar")) ||
1762 aFile.Lower().EndsWith(_T("gz")) ||
1763 aFile.Lower().EndsWith(_T("bz2")) ||
1764 aFile.Lower().EndsWith(_T("lzma")) ||
1765 aFile.Lower().EndsWith(_T("7z")) ||
1766 aFile.Lower().EndsWith(_T("xz"))) {
1767 bool ret =
1768 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1769 if (ret)
1770 wxRemoveFile(aFile);
1771 else
1772 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1773 return ret;
1774 }
1775#else
1776 else if (aFile.Lower().EndsWith(_T("rar")) ||
1777 aFile.Lower().EndsWith(_T("tar"))
1778#ifdef HAVE_BZIP2
1779 || aFile.Lower().EndsWith(_T("bz2"))
1780#endif
1781#ifdef HAVE_ZLIB
1782 || aFile.Lower().EndsWith(_T("gz"))
1783#endif
1784#ifdef HAVE_7Z
1785 || aFile.Lower().EndsWith(
1786 _T("7z")) // TODO: Could it actually extract more formats the
1787 // LZMA SDK supports?
1788#endif
1789 ) {
1790 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1791 if (ret)
1792 wxRemoveFile(aFile);
1793 else
1794 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1795 return ret;
1796 }
1797#endif
1798
1799#ifdef __ANDROID__
1800 else if (aFile.Lower().EndsWith(_T("tar")) ||
1801 aFile.Lower().EndsWith(_T("gz")) ||
1802 aFile.Lower().EndsWith(_T("bz2")) ||
1803 aFile.Lower().EndsWith(_T("lzma")) ||
1804 aFile.Lower().EndsWith(_T("7z")) ||
1805 aFile.Lower().EndsWith(_T("xz"))) {
1806 int nStrip = 0;
1807 if (aStripPath) nStrip = 1;
1808
1809 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_("Installing charts."));
1810
1811 androidShowBusyIcon();
1812 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip, true);
1813 androidHideBusyIcon();
1814
1815 return ret;
1816 }
1817#endif
1818
1819 else // Uncompressed
1820 {
1821 wxFileName fn(aFile);
1822 if (fn.GetPath() != aTargetDir) // We have to move the file somewhere
1823 {
1824 if (!wxDirExists(aTargetDir)) {
1825 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1826 if (!wxRenameFile(aFile, aTargetDir)) return false;
1827 } else
1828 return false;
1829 }
1830 }
1831 wxString name = fn.GetFullName();
1832 fn.Clear();
1833 fn.Assign(aTargetDir, name);
1834 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1835 }
1836 return true;
1837}
1838
1839#ifdef DLDR_USE_LIBARCHIVE
1840#ifndef __ANDROID__
1841static int copy_data(struct archive *ar, struct archive *aw) {
1842 int r;
1843 const void *buff;
1844 size_t size;
1845 __LA_INT64_T offset;
1846
1847 for (;;) {
1848 r = archive_read_data_block(ar, &buff, &size, &offset);
1849 if (r == ARCHIVE_EOF) return (ARCHIVE_OK);
1850 if (r < ARCHIVE_OK) return (r);
1851 r = archive_write_data_block(aw, buff, size, offset);
1852 if (r < ARCHIVE_OK) {
1853 // fprintf(stderr, "%s\n", archive_error_string(aw));
1854 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1855 archive_error_string(aw)));
1856 return (r);
1857 }
1858 }
1859}
1860#endif
1861
1862bool chartdldr_pi::ExtractLibArchiveFiles(const wxString &aArchiveFile,
1863 const wxString &aTargetDir,
1864 bool aStripPath, wxDateTime aMTime,
1865 bool aRemoveArchive) {
1866#ifndef __ANDROID__
1867 struct archive *a = NULL;
1868 struct archive *ext = NULL;
1869 bool ok = false;
1870
1871 int flags = ARCHIVE_EXTRACT_TIME;
1872#ifdef ARCHIVE_EXTRACT_SECURE_NODOTDOT
1873 flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
1874#endif
1875#ifdef ARCHIVE_EXTRACT_SECURE_SYMLINKS
1876 flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
1877#endif
1878
1879 a = archive_read_new();
1880 ext = archive_write_disk_new();
1881
1882 if (!a || !ext) {
1883 wxLogError(_T("Chartdldr_pi: Failed to create libarchive objects."));
1884 goto cleanup;
1885 }
1886
1887 archive_read_support_format_all(a);
1888 archive_read_support_filter_all(a);
1889#if !defined(__clang__)
1890 archive_read_support_compression_all(a);
1891#endif
1892
1893 archive_write_disk_set_options(ext, flags);
1894 archive_write_disk_set_standard_lookup(ext);
1895
1896#ifdef _WIN32
1897 if (archive_read_open_filename_w(a, aArchiveFile.wc_str(), 10240) !=
1898 ARCHIVE_OK) {
1899 wxLogError(wxString::Format("Chartdldr_pi: LibArchive open error: %s",
1900 archive_error_string(a)));
1901 goto cleanup;
1902 }
1903#else
1904 {
1905 if (archive_read_open_filename(a, aArchiveFile.mb_str().data(), 10240) !=
1906 ARCHIVE_OK) {
1907 wxLogError(wxString::Format("Chartdldr_pi: LibArchive open error: %s",
1908 archive_error_string(a)));
1909 goto cleanup;
1910 }
1911 }
1912#endif
1913
1914 for (;;) {
1915 struct archive_entry *entry = NULL;
1916 int r = archive_read_next_header(a, &entry);
1917
1918 if (r == ARCHIVE_EOF) {
1919 break;
1920 }
1921
1922 if (r < ARCHIVE_OK) {
1923 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1924 archive_error_string(a)));
1925 }
1926 if (r < ARCHIVE_WARN) {
1927 goto cleanup;
1928 }
1929
1930 wxString entryName;
1931#ifdef _WIN32
1932 const char *rawUtf8 = archive_entry_pathname_utf8(entry);
1933 if (rawUtf8 && *rawUtf8) {
1934 entryName = wxString::FromUTF8(rawUtf8);
1935 } else {
1936 const wchar_t *rawWide = archive_entry_pathname_w(entry);
1937 if (rawWide && *rawWide) entryName = wxString(rawWide);
1938 }
1939#else
1940 const char *rawPath = archive_entry_pathname(entry);
1941 if (rawPath && *rawPath) {
1942 entryName = wxString::FromUTF8(rawPath);
1943 if (entryName.IsEmpty()) {
1944 entryName = wxString::From8BitData(rawPath);
1945 }
1946 }
1947#endif
1948
1949 if (entryName.IsEmpty()) {
1950 wxLogWarning(_T("Skipping archive entry with empty pathname."));
1951 continue;
1952 }
1953
1954 if (aStripPath) {
1955 wxFileName stripped(entryName);
1956 entryName = stripped.GetFullName();
1957
1958 if (entryName.IsEmpty()) {
1959 continue;
1960 }
1961 }
1962
1963 wxString outputPath = entryName;
1964 if (aTargetDir != wxEmptyString) {
1965 if (!IsPathInsideDir(aTargetDir, entryName, outputPath)) {
1966 wxLogWarning(
1967 _T("Skipping archive entry with path traversal attempt: ") +
1968 entryName);
1969 continue;
1970 }
1971 }
1972
1973#ifdef _WIN32
1974 archive_entry_copy_pathname_w(entry, outputPath.wc_str());
1975#else
1976 archive_entry_copy_pathname(entry, outputPath.fn_str().data());
1977#endif
1978
1979 if (aMTime.IsValid()) {
1980 archive_entry_set_mtime(entry, static_cast<time_t>(aMTime.GetTicks()), 0);
1981 }
1982
1983 r = archive_write_header(ext, entry);
1984 if (r < ARCHIVE_OK) {
1985 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1986 archive_error_string(ext)));
1987 }
1988 if (r < ARCHIVE_WARN) {
1989 goto cleanup;
1990 }
1991
1992 if (archive_entry_size(entry) > 0) {
1993 r = copy_data(a, ext);
1994 if (r < ARCHIVE_OK) {
1995 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1996 archive_error_string(ext)));
1997 }
1998 if (r < ARCHIVE_WARN) {
1999 goto cleanup;
2000 }
2001 }
2002
2003 r = archive_write_finish_entry(ext);
2004 if (r < ARCHIVE_OK) {
2005 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
2006 archive_error_string(ext)));
2007 }
2008 if (r < ARCHIVE_WARN) {
2009 goto cleanup;
2010 }
2011 }
2012
2013 ok = true;
2014
2015cleanup:
2016 if (a) {
2017 archive_read_close(a);
2018 archive_read_free(a);
2019 }
2020 if (ext) {
2021 archive_write_close(ext);
2022 archive_write_free(ext);
2023 }
2024
2025 if (ok && aRemoveArchive) wxRemoveFile(aArchiveFile);
2026 return ok;
2027
2028#else
2029 wxUnusedVar(aArchiveFile);
2030 wxUnusedVar(aTargetDir);
2031 wxUnusedVar(aStripPath);
2032 wxUnusedVar(aMTime);
2033 wxUnusedVar(aRemoveArchive);
2034 return false;
2035#endif
2036}
2037#endif // DLDR_USE_LIBARCHIVE
2038
2039#if defined(CHARTDLDR_RAR_UNARR) || !defined(DLDR_USE_LIBARCHIVE)
2040ar_archive *ar_open_any_archive(ar_stream *stream, const char *fileext) {
2041 ar_archive *ar = ar_open_rar_archive(stream);
2042 if (!ar)
2043 ar =
2044 ar_open_zip_archive(stream, fileext && (strcmp(fileext, ".xps") == 0 ||
2045 strcmp(fileext, ".epub") == 0));
2046 if (!ar) ar = ar_open_7z_archive(stream);
2047 if (!ar) ar = ar_open_tar_archive(stream);
2048 return ar;
2049}
2050
2051bool chartdldr_pi::ExtractUnarrFiles(const wxString &aRarFile,
2052 const wxString &aTargetDir,
2053 bool aStripPath, wxDateTime aMTime,
2054 bool aRemoveRar) {
2055 ar_stream *stream = NULL;
2056 ar_archive *ar = NULL;
2057 int entry_count = 1;
2058 int entry_skips = 0;
2059 int error_step = 1;
2060 bool ret = true;
2061
2062 stream = ar_open_file(aRarFile.c_str());
2063 if (!stream) {
2064 wxLogError(_T("Can not open file '") + aRarFile + _T("'."));
2065 ar_close_archive(ar);
2066 ar_close(stream);
2067 return false;
2068 }
2069 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(), '.'));
2070 if (!ar) {
2071 wxLogError(_T("Can not open archive '") + aRarFile + _T("'."));
2072 ar_close_archive(ar);
2073 ar_close(stream);
2074 return false;
2075 }
2076 while (ar_parse_entry(ar)) {
2077 size_t size = ar_entry_get_size(ar);
2078 wxString name = ar_entry_get_name(ar);
2079 wxString originalName = name; // Save for logging
2080 if (aStripPath) {
2081 wxFileName fn(name);
2082 /* We can completly replace the entry path */
2083 // fn.SetPath(aTargetDir);
2084 // name = fn.GetFullPath();
2085 /* Or only remove the first dir (eg. ENC_ROOT) */
2086 if (fn.GetDirCount() > 0) {
2087 fn.RemoveDir(0);
2088 name = fn.GetFullPath();
2089 }
2090 }
2091
2092 // Path traversal protection: validate path stays inside target directory
2093 wxString fullPath;
2094 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2095 wxLogWarning(_T("Skipping archive entry with path traversal attempt: ") +
2096 originalName);
2097 continue;
2098 }
2099 name = fullPath;
2100
2101 wxFileName fn(name);
2102 if (!fn.DirExists()) {
2103 if (!wxFileName::Mkdir(fn.GetPath())) {
2104 wxLogError(_T("Can not create directory '") + fn.GetPath() + _T("'."));
2105 ret = false;
2106 break;
2107 }
2108 }
2109 wxFileOutputStream file(name);
2110 if (!file) {
2111 wxLogError(_T("Can not create file '") + name + _T("'."));
2112 ret = false;
2113 break;
2114 }
2115 while (size > 0) {
2116 unsigned char buffer[1024];
2117 size_t count = size < sizeof(buffer) ? size : sizeof(buffer);
2118 if (!ar_entry_uncompress(ar, buffer, count)) break;
2119 file.Write(buffer, count);
2120 size -= count;
2121 }
2122 file.Close();
2123 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2124 if (size > 0) {
2125 wxLogError("Warning: Failed to uncompress... skipping");
2126 entry_skips++;
2127 ret = false;
2128 }
2129 }
2130 if (!ar_at_eof(ar)) {
2131 wxLogError("Error: Failed to parse entry %d!", entry_count);
2132 ret = false;
2133 }
2134 ar_close_archive(ar);
2135 ar_close(stream);
2136
2137 if (aRemoveRar) wxRemoveFile(aRarFile);
2138
2139#ifdef _UNIX
2140 // reset LC_NUMERIC locale, some locales use a comma for decimal point
2141 // and it corrupts navobj.xml file
2142 setlocale(LC_NUMERIC, "C");
2143#endif
2144
2145 return ret;
2146}
2147#endif
2148
2149bool chartdldr_pi::ExtractZipFiles(const wxString &aZipFile,
2150 const wxString &aTargetDir, bool aStripPath,
2151 wxDateTime aMTime, bool aRemoveZip) {
2152 bool ret = true;
2153
2154#ifdef __ANDROID__
2155 int nStrip = 0;
2156 if (aStripPath) nStrip = 1;
2157
2158 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip, true);
2159#else
2160 std::unique_ptr<wxZipEntry> entry(new wxZipEntry());
2161
2162 do {
2163 wxLogMessage(_T("chartdldr_pi: Going to extract '") + aZipFile + _T("'."));
2164 wxFileInputStream in(aZipFile);
2165
2166 if (!in) {
2167 wxLogMessage(_T("Can not open file '") + aZipFile + _T("'."));
2168 ret = false;
2169 break;
2170 }
2171 wxZipInputStream zip(in);
2172 ret = false;
2173
2174 while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
2175 // access meta-data
2176 wxString name = entry->GetName();
2177 wxString fullPath;
2178 if (aStripPath) {
2179 wxFileName fn(name);
2180 /* We can completly replace the entry path */
2181 // fn.SetPath(aTargetDir);
2182 // name = fn.GetFullPath();
2183 /* Or only remove the first dir (eg. ENC_ROOT) */
2184 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2185 name = fn.GetFullPath();
2186 }
2187
2188 // Path traversal protection: validate path stays inside target directory
2189 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2190 wxLogWarning(_T("Skipping zip entry with path traversal attempt: ") +
2191 entry->GetName());
2192 continue;
2193 }
2194 name = fullPath;
2195
2196 // read 'zip' to access the entry's data
2197 if (entry->IsDir()) {
2198 int perm = entry->GetMode();
2199 if (!wxFileName::Mkdir(name, perm, wxPATH_MKDIR_FULL)) {
2200 wxLogMessage(_T("Can not create directory '") + name + _T("'."));
2201 ret = false;
2202 break;
2203 }
2204 } else {
2205 if (!zip.OpenEntry(*entry.get())) {
2206 wxLogMessage(_T("Can not open zip entry '") + entry->GetName() +
2207 _T("'."));
2208 ret = false;
2209 break;
2210 }
2211 if (!zip.CanRead()) {
2212 wxLogMessage(_T("Can not read zip entry '") + entry->GetName() +
2213 _T("'."));
2214 ret = false;
2215 break;
2216 }
2217
2218 wxFileName fn(name);
2219 if (!fn.DirExists()) {
2220 if (!wxFileName::Mkdir(fn.GetPath())) {
2221 wxLogMessage(_T("Can not create directory '") + fn.GetPath() +
2222 _T("'."));
2223 ret = false;
2224 break;
2225 }
2226 }
2227
2228 wxFileOutputStream file(name);
2229
2230 if (!file) {
2231 wxLogMessage(_T("Can not create file '") + name + _T("'."));
2232 ret = false;
2233 break;
2234 }
2235 zip.Read(file);
2236 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2237 ret = true;
2238 }
2239 }
2240
2241 } while (false);
2242
2243 if (aRemoveZip) wxRemoveFile(aZipFile);
2244#endif // Android
2245
2246 return ret;
2247}
2248
2249ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2250 : AddSourceDlg(parent) {
2251 wxFileName fn;
2252 fn.SetPath(*GetpSharedDataLocation());
2253 fn.AppendDir(_T("plugins"));
2254 fn.AppendDir(_T("chartdldr_pi"));
2255 fn.AppendDir(_T("data"));
2256
2257 int w = 16; // default for desktop
2258 int h = 16;
2259
2260#ifdef __ANDROID__
2261 w = 6 * g_androidDPmm; // mm nominal size
2262 h = w;
2263
2264 p_buttonIconList = new wxImageList(w, h);
2265
2266 fn.SetFullName(_T("button_right.png"));
2267 wxImage im1(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2268 im1.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2269 p_buttonIconList->Add(im1);
2270
2271 fn.SetFullName(_T("button_right.png"));
2272 wxImage im2(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2273 im2.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2274 p_buttonIconList->Add(im2);
2275
2276 fn.SetFullName(_T("button_down.png"));
2277 wxImage im3(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2278 im3.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2279 p_buttonIconList->Add(im3);
2280
2281 fn.SetFullName(_T("button_down.png"));
2282 wxImage im4(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2283 im4.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2284 p_buttonIconList->Add(im4);
2285
2286 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2287#else
2288 p_iconList = new wxImageList(w, h);
2289
2290 fn.SetFullName(_T("folder.png"));
2291 wxImage ima(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2292 ima.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2293 p_iconList->Add(ima);
2294
2295 fn.SetFullName(_T("file.png"));
2296 wxImage imb(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2297 imb.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2298 p_iconList->Add(imb);
2299
2300 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2301#endif /* __ANDROID__ */
2302
2303 m_treeCtrlPredefSrcs->SetIndent(w);
2304
2305 m_base_path = wxEmptyString;
2306 m_last_path = wxEmptyString;
2307 LoadSources();
2308 m_nbChoice->SetSelection(0);
2309 // m_treeCtrlPredefSrcs->ExpandAll();
2310
2311 Fit();
2312
2313 applyStyle();
2314}
2315
2316bool ChartDldrGuiAddSourceDlg::LoadSources() {
2317 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot(_T("root"));
2318
2319 wxFileName fn;
2321 fn.SetFullName(_T("chartdldr_pi-chart_sources.xml"));
2322 if (!fn.FileExists()) {
2323 fn.SetPath(*GetpSharedDataLocation());
2324 fn.AppendDir(_T("plugins"));
2325 fn.AppendDir(_T("chartdldr_pi"));
2326 fn.AppendDir(_T("data"));
2327 fn.SetFullName(_T("chart_sources.xml"));
2328 if (!fn.FileExists()) {
2329 wxLogMessage(wxString::Format(
2330 _T("Error: chartdldr_pi::LoadSources() %s not found!"),
2331 fn.GetFullPath().c_str()));
2332 return false;
2333 }
2334 }
2335 wxString path = fn.GetFullPath();
2336
2338 bool ret = doc->load_file(path.mb_str());
2339 if (ret) {
2340 pugi::xml_node root = doc->first_child();
2341
2342 for (pugi::xml_node element = root.first_child(); element;
2343 element = element.next_sibling()) {
2344 if (!strcmp(element.name(), "sections")) {
2345 LoadSections(tree, element);
2346 }
2347 }
2348 }
2349 wxDELETE(doc);
2350 return true;
2351}
2352
2353bool ChartDldrGuiAddSourceDlg::LoadSections(const wxTreeItemId &root,
2354 pugi::xml_node &node) {
2355 for (pugi::xml_node element = node.first_child(); element;
2356 element = element.next_sibling()) {
2357 if (!strcmp(element.name(), "section")) {
2358 LoadSection(root, element);
2359 }
2360 }
2361 return true;
2362}
2363
2364bool ChartDldrGuiAddSourceDlg::LoadSection(const wxTreeItemId &root,
2365 pugi::xml_node &node) {
2366 wxTreeItemId item;
2367 for (pugi::xml_node element = node.first_child(); element;
2368 element = element.next_sibling()) {
2369 if (!strcmp(element.name(), "name")) {
2370 item = m_treeCtrlPredefSrcs->AppendItem(
2371 root, wxString::FromUTF8(element.first_child().value()), 0, 0);
2372
2373 wxFont *pFont = OCPNGetFont(_("Dialog"));
2374 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2375 }
2376 if (!strcmp(element.name(), "sections")) LoadSections(item, element);
2377 if (!strcmp(element.name(), "catalogs")) LoadCatalogs(item, element);
2378 }
2379
2380 return true;
2381}
2382
2383bool ChartDldrGuiAddSourceDlg::LoadCatalogs(const wxTreeItemId &root,
2384 pugi::xml_node &node) {
2385 for (pugi::xml_node element = node.first_child(); element;
2386 element = element.next_sibling()) {
2387 if (!strcmp(element.name(), "catalog")) LoadCatalog(root, element);
2388 }
2389
2390 return true;
2391}
2392
2393bool ChartDldrGuiAddSourceDlg::LoadCatalog(const wxTreeItemId &root,
2394 pugi::xml_node &node) {
2395 wxString name, type, location, dir;
2396 for (pugi::xml_node element = node.first_child(); element;
2397 element = element.next_sibling()) {
2398 if (!strcmp(element.name(), "name"))
2399 name = wxString::FromUTF8(element.first_child().value());
2400 else if (!strcmp(element.name(), "type"))
2401 type = wxString::FromUTF8(element.first_child().value());
2402 else if (!strcmp(element.name(), "location"))
2403 location = wxString::FromUTF8(element.first_child().value());
2404 else if (!strcmp(element.name(), "dir"))
2405 dir = wxString::FromUTF8(element.first_child().value());
2406 }
2407 ChartSource *cs = new ChartSource(name, location, dir);
2408 wxTreeItemId id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2409
2410 wxFont *pFont = OCPNGetFont(_("Dialog"));
2411 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(id, *pFont);
2412
2413 return true;
2414}
2415
2416ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() {}
2417
2418wxString ChartDldrGuiAddSourceDlg::FixPath(wxString path) {
2419 wxString sep(wxFileName::GetPathSeparator());
2420 wxString s = path;
2421 s.Replace(_T("/"), sep, true);
2422 s.Replace(_T(USERDATA), m_base_path);
2423 s.Replace(sep + sep, sep);
2424 return s;
2425}
2426
2427void ChartDldrGuiAddSourceDlg::OnChangeType(wxCommandEvent &event) {
2428 m_treeCtrlPredefSrcs->Enable(m_nbChoice->GetSelection() == 0);
2429 m_tSourceName->Enable(m_nbChoice->GetSelection() == 1);
2430 m_tChartSourceUrl->Enable(m_nbChoice->GetSelection() == 1);
2431}
2432
2433void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2434 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2435 ChartSource *cs = (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2436 if (cs) {
2437 m_dirExpanded = FixPath(cs->GetDir());
2438
2439 m_tSourceName->SetValue(cs->GetName());
2440 m_tChartSourceUrl->SetValue(cs->GetUrl());
2441 if (m_tcChartDirectory->GetValue() == m_last_path) {
2442 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2443 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2444
2445 m_buttonChartDirectory->Enable();
2446 m_last_path = m_tcChartDirectory->GetValue();
2447 }
2448 }
2449 event.Skip();
2450}
2451
2452void ChartDldrGuiAddSourceDlg::SetSourceEdit(std::unique_ptr<ChartSource> &cs) {
2453 m_nbChoice->SetSelection(1);
2454 m_tChartSourceUrl->Enable();
2455 m_treeCtrlPredefSrcs->Disable();
2456 m_tSourceName->SetValue(cs->GetName());
2457 m_tChartSourceUrl->SetValue(cs->GetUrl());
2458 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2459 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2460
2461 m_buttonChartDirectory->Enable();
2462}
2463
2464ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2465 : ChartDldrPrefsDlg(parent) {}
2466
2467ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() {}
2468
2469void ChartDldrPrefsDlgImpl::SetPath(const wxString path) {
2470 // if( !wxDirExists(path) )
2471 // if( !wxFileName::Mkdir(path, 0755, wxPATH_MKDIR_FULL) )
2472 //{
2473 // OCPNMessageBox_PlugIn(this, wxString::Format(_("Directory %s can't be
2474 // created."), m_dpDefaultDir->GetTextCtrlValue().c_str()), _("Chart
2475 // Downloader")); return;
2476 //}
2477 m_tcDefaultDir->SetValue(path);
2478}
2479
2480void ChartDldrPrefsDlgImpl::GetPreferences(bool &preselect_new,
2481 bool &preselect_updated,
2482 bool &bulk_update) {
2483 preselect_new = m_cbSelectNew->GetValue();
2484 preselect_updated = m_cbSelectUpdated->GetValue();
2485 bulk_update = m_cbBulkUpdate->GetValue();
2486}
2487void ChartDldrPrefsDlgImpl::SetPreferences(bool preselect_new,
2488 bool preselect_updated,
2489 bool bulk_update) {
2490 m_cbSelectNew->SetValue(preselect_new);
2491 m_cbSelectUpdated->SetValue(preselect_updated);
2492 m_cbBulkUpdate->SetValue(bulk_update);
2493}
2494
2495void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2496 wxString msg = wxEmptyString;
2497
2498 if (m_nbChoice->GetSelection() == 0) {
2499 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2500 if (m_treeCtrlPredefSrcs->GetSelection().IsOk()) {
2501 ChartSource *cs =
2502 (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2503 if (!cs)
2504 msg +=
2505 _("You must select one of the predefined chart sources or create "
2506 "one of your own.\n");
2507 } else
2508 msg +=
2509 _("You must select one of the predefined chart sources or create one "
2510 "of your own.\n");
2511 }
2512 if (m_nbChoice->GetSelection() == 1 &&
2513 m_tSourceName->GetValue() == wxEmptyString)
2514 msg += _("The chart source must have a name.\n");
2515 wxURI url(m_tChartSourceUrl->GetValue());
2516 if (m_nbChoice->GetSelection() == 1 &&
2517 (m_tChartSourceUrl->GetValue() == wxEmptyString ||
2518 !ValidateUrl(m_tChartSourceUrl->GetValue())))
2519 msg += _("The chart source must have a valid URL.\n");
2520 if (m_tcChartDirectory->GetValue() == wxEmptyString)
2521 msg += _("You must select a local folder to store the charts.\n");
2522 else if (!wxDirExists(m_tcChartDirectory->GetValue()))
2523 if (!wxFileName::Mkdir(m_tcChartDirectory->GetValue(), 0755,
2524 wxPATH_MKDIR_FULL))
2525 msg += wxString::Format(_("Directory %s can't be created."),
2526 m_tcChartDirectory->GetValue().c_str()) +
2527 _T("\n");
2528
2529 if (msg != wxEmptyString)
2530 OCPNMessageBox_PlugIn(this, msg, _("Chart source definition problem"),
2531 wxOK | wxCENTRE | wxICON_ERROR);
2532 else {
2533 event.Skip();
2534 SetReturnCode(wxID_OK);
2535 EndModal(wxID_OK);
2536 }
2537}
2538
2539void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2540 SetReturnCode(wxID_CANCEL);
2541 EndModal(wxID_CANCEL);
2542}
2543
2544void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2545 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2546 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2547 wxPATH_MKDIR_FULL)) {
2549 this,
2550 wxString::Format(_("Directory %s can't be created."),
2551 m_tcDefaultDir->GetValue().c_str()),
2552 _("Chart Downloader"));
2553 return;
2554 }
2555 }
2556
2557 if (g_pi) {
2558 g_pi->UpdatePrefs(this);
2559 }
2560
2561 event.Skip();
2562 EndModal(wxID_OK);
2563
2564 // Hide();
2565 // Close();
2566}
2567
2568void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2569 event.Skip();
2570 EndModal(wxID_CANCEL);
2571 // Close();
2572}
2573
2574void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2575 event.Skip();
2576 // Close();
2577}
2578
2579bool ChartDldrGuiAddSourceDlg::ValidateUrl(const wxString Url,
2580 bool catalog_xml) {
2581 wxRegEx re;
2582 if (catalog_xml)
2583 re.Compile(
2584 _T("^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$")); // TODO: wxRegEx
2585 // sucks a bit,
2586 // this RE is
2587 // way too naive
2588 else
2589 re.Compile(
2590 _T("^https?\\://[a-zA-Z0-9\\./_-]*$")); // TODO: wxRegEx sucks a bit,
2591 // this RE is way too naive
2592 return re.Matches(Url);
2593}
2594
2595void ChartDldrPanelImpl::onDLEvent(OCPN_downloadEvent &ev) {
2596 // wxString msg;
2597 // msg.Printf(_T("onDLEvent %d %d"),ev.getDLEventCondition(),
2598 // ev.getDLEventStatus()); wxLogMessage(msg);
2599
2600 switch (ev.getDLEventCondition()) {
2602 m_bTransferComplete = true;
2603 m_bTransferSuccess =
2604 (ev.getDLEventStatus() == OCPN_DL_NO_ERROR) ? true : false;
2605 break;
2606
2608 if (ev.getTransferred() > m_transferredsize) {
2609 m_totalsize = ev.getTotal();
2610 m_transferredsize = ev.getTransferred();
2611 }
2612
2613 break;
2614 default:
2615 break;
2616 }
2617 wxYieldIfNeeded();
2618}
Class AddSourceDlg.
Implementing ChartDldrPanel.
Class ChartDldrPanel.
Class ChartDldrPrefsDlg.
int GetPlugInVersionMinor()
Returns the minor version number of the plugin itself.
wxString GetLongDescription()
Get detailed plugin information.
wxBitmap * GetPlugInBitmap()
Get the plugin's icon bitmap.
void ShowPreferencesDialog(wxWindow *parent)
Shows the plugin preferences dialog.
int Init(void)
Initialize the plugin and declare its capabilities.
int GetAPIVersionMinor()
Returns the minor version number of the plugin API that this plugin supports.
bool DeInit(void)
Clean up plugin resources.
void OnCloseToolboxPanel(int page_sel, int ok_apply_cancel)
Handles preference page closure.
int GetAPIVersionMajor()
Returns the major version number of the plugin API that this plugin supports.
wxString GetShortDescription()
Get a brief description of the plugin.
wxString GetCommonName()
Get the plugin's common (short) name.
int GetPlugInVersionMajor()
Returns the major version number of the plugin itself.
void OnSetupOptions(void)
Allows plugin to add pages to global Options dialog.
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.