swift
Loading...
Searching...
No Matches
installxswiftbuscomponent.cpp
1// SPDX-FileCopyrightText: Copyright (C) 2017 swift Project Community / Contributors
2// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3
5
6#include <QDesktopServices>
7#include <QDir>
8#include <QFileDialog>
9#include <QFileInfo>
10#include <QMessageBox>
11#include <QPointer>
12#include <QStandardPaths>
13#include <QTimer>
14
15#include "ui_installxswiftbuscomponent.h"
16
17#include "config/buildconfig.h"
18#include "gui/guiapplication.h"
20#include "misc/compressutils.h"
21#include "misc/directoryutils.h"
22#include "misc/fileutils.h"
23#include "misc/logmessage.h"
25
26using namespace swift::config;
27using namespace swift::misc;
28using namespace swift::misc::db;
29using namespace swift::misc::network;
30using namespace swift::misc::simulation;
31using namespace swift::misc::simulation::settings;
32using namespace swift::misc::simulation::xplane;
33
35{
38 {
39 ui->setupUi(this);
40
41 ui->le_XSwiftBusPluginDir->setText(this->getXPlanePluginDirectory());
43 ui->cb_DownloadFile->setEnabled(false);
44
45 connect(ui->tb_DialogInstallDir, &QPushButton::pressed, this,
46 &CInstallXSwiftBusComponent::selectPluginDirectory);
47 connect(ui->tb_DialogDownloadDir, &QPushButton::pressed, this,
48 &CInstallXSwiftBusComponent::selectDownloadDirectory);
49 connect(ui->pb_Download, &QPushButton::pressed, this,
50 &CInstallXSwiftBusComponent::triggerDownloadingOfXSwiftBusFile);
51 connect(ui->pb_OpenDownloadDir, &QPushButton::pressed, this, &CInstallXSwiftBusComponent::openDownloadDir);
52 connect(ui->pb_OpenInstallDir, &QPushButton::pressed, this, &CInstallXSwiftBusComponent::openInstallDir);
53
54 // init upate info
55 this->updatesChanged();
56 }
57
59
61 {
62 m_defaultDownloadName = defaultDownload;
63 this->updatesChanged();
64 }
65
66 void CInstallXSwiftBusComponent::selectPluginDirectory()
67 {
68 QString xPlanePluginDir = CFileUtils::fixWindowsUncPath(ui->le_XSwiftBusPluginDir->text().trimmed());
69 xPlanePluginDir = QFileDialog::getExistingDirectory(parentWidget(), tr("Choose your X-Plane plugin directory"),
70 xPlanePluginDir, m_fileDialogOptions);
71
72 if (xPlanePluginDir.isEmpty())
73 {
74 return;
75 } // canceled
76 if (!QDir(xPlanePluginDir).exists())
77 {
79 .warning(u"'%1' is not a valid X-Plane plugin directory")
80 << xPlanePluginDir;
81 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
82 return;
83 }
84 ui->le_XSwiftBusPluginDir->setText(xPlanePluginDir);
85 }
86
87 void CInstallXSwiftBusComponent::selectDownloadDirectory()
88 {
89 QString downloadDir = CFileUtils::fixWindowsUncPath(ui->le_DownloadDir->text().trimmed());
90 downloadDir = QFileDialog::getExistingDirectory(parentWidget(), tr("Choose your X-Plane plugin directory"),
91 downloadDir, m_fileDialogOptions);
92
93 if (downloadDir.isEmpty())
94 {
95 return;
96 } // canceled
97 if (!QDir(downloadDir).exists())
98 {
99 const CStatusMessage msg =
100 CStatusMessage(this, CLogCategories::validation()).warning(u"'%1' is not a valid download directory")
101 << downloadDir;
102 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
103 return;
104 }
105 ui->le_DownloadDir->setText(downloadDir);
106 }
107
108 void CInstallXSwiftBusComponent::installXSwiftBus()
109 {
110 const CRemoteFile rf = this->getRemoteFileSelected();
111 const QString downloadFileName = CFileUtils::appendFilePathsAndFixUnc(this->getDownloadDir(), rf.getBaseName());
112 QPointer<CInstallXSwiftBusComponent> myself(this);
113 QFile downloadFile(downloadFileName);
114
115 if (!downloadFile.exists())
116 {
117 const CStatusMessage msg =
118 CStatusMessage(this, CLogCategories::validation()).error(u"Cannot read downloaded file '%1'")
119 << downloadFileName;
120 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
121 return;
122 }
123
124 const QString xSwiftBusDirectory = this->GetXSwiftBusDir();
125 if (xSwiftBusDirectory.isEmpty())
126 {
127 const CStatusMessage msg =
128 CStatusMessage(this, CLogCategories::validation()).error(u"No directory to install to'");
129 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
130 return;
131 }
132
133 const QDir installDir(xSwiftBusDirectory);
134 if (!installDir.exists())
135 {
136 const CStatusMessage msg =
137 CStatusMessage(this, CLogCategories::validation()).error(u"Directory '%1' does not exist")
138 << xSwiftBusDirectory;
139 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
140 return;
141 }
142
143 const QString destFileName = CFileUtils::appendFilePathsAndFixUnc(xSwiftBusDirectory, rf.getBaseName());
144 {
145 QFile destFile(destFileName);
146 if (destFile.exists())
147 {
148 const bool removed = destFile.remove();
149 if (!removed)
150 {
151 const CStatusMessage msg =
152 CStatusMessage(this, CLogCategories::validation()).error(u"Cannot remove '%1'") << destFileName;
153 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
154 return;
155 }
156 }
157 }
158
159 const bool copied = QFile::copy(downloadFileName, destFileName);
160 if (!copied)
161 {
162 const CStatusMessage msg =
163 CStatusMessage(this, CLogCategories::validation()).error(u"Cannot copy '%1' to '%2'")
164 << downloadFileName << destFileName;
165 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
166 return;
167 }
168
169 // we need to unzip the destination file
170 const QFileInfo destFile(destFileName);
171 if (!destFile.exists())
172 {
173 const CStatusMessage msg =
174 CStatusMessage(this, CLogCategories::validation()).error(u"xswiftbus file '%1' does not exist")
175 << destFileName;
176 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
177 return;
178 }
179
180 // if possible we will unzip
181 QStringList stdOutAndError;
182 if (CCompressUtils::zipUncompress(destFile.absoluteFilePath(), xSwiftBusDirectory, &stdOutAndError))
183 {
184 // capture values by copy!
185 const CStatusMessage msg =
186 CStatusMessage(this, CLogCategories::validation()).info(u"Uncompressed xswiftbus in '%1'")
187 << xSwiftBusDirectory;
188 this->showOverlayMessagesWithConfirmation(msg, false, "Delete downloaded file?", [=] {
189 if (!myself)
190 {
191 return;
192 }
193 QFile downloadFile(downloadFileName);
194 if (!downloadFile.exists())
195 {
196 return;
197 } // removed in meantime
198 const bool removed = downloadFile.remove();
199 Q_UNUSED(removed);
200 });
201 return;
202 }
203 else
204 {
205 const CStatusMessage msg =
206 CStatusMessage(this, CLogCategories::validation()).warning(u"Unzip failed: stdout '%1' stderr '%2'")
207 << safeAt(stdOutAndError, 0) << safeAt(stdOutAndError, 1);
208 this->showOverlayMessage(msg);
209 }
210
212 const QMessageBox::StandardButton reply =
213 QMessageBox::question(this, "Install XSwiftXBus",
214 "You need to manually unzip xswiftbus into the plugins directory.\nIt needs to look "
215 "like 'plugin/xswiftbus'.\n\nOpen the archive?",
217
218 if (reply == QMessageBox::Yes)
219 {
220 QDesktopServices::openUrl(QUrl::fromLocalFile(destFile.absoluteFilePath()));
221 }
222 }
223
224 void CInstallXSwiftBusComponent::triggerDownloadingOfXSwiftBusFile()
225 {
226 using namespace std::chrono_literals;
228 {
229 return;
230 }
231 const CRemoteFile rf = this->getRemoteFileSelected();
233 {
235 this, "Download xswiftbus",
236 QStringLiteral(u"The xswiftbus versions seems to be for a different version\n"
237 u"Your version is '%1'. Use this version.\n\n"
238 u"If not available, you can try the version next to your version number.\n\n"
239 u"Continue with this version?")
242 if (reply != QMessageBox::Yes)
243 {
244 return;
245 }
246 }
247
248 if (!this->existsDownloadDir())
249 {
250 const CStatusMessage msg =
251 CStatusMessage(this, CLogCategories::validation()).error(u"Invalid download directory");
252 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
253 return;
254 }
255
256 const CUrl download = rf.getSmartUrl();
257 if (download.isEmpty())
258 {
259 const CStatusMessage msg =
260 CStatusMessage(this, CLogCategories::validation()).error(u"No download URL for file name '%1'")
261 << rf.getBaseNameAndSize();
262 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
263 return;
264 }
265
266 const QString saveAsFile = CFileUtils::appendFilePathsAndFixUnc(ui->le_DownloadDir->text(), rf.getBaseName());
267 const QFile saveFile(saveAsFile);
268 if (saveFile.exists())
269 {
271 this, "The file already exists", "Do you want to use the existing '" + saveAsFile + "'?",
273 if (reply == QMessageBox::Cancel)
274 {
275 return;
276 }
277 if (reply == QMessageBox::Yes)
278 {
279 const CStatusMessage msg = CStatusMessage(this).info(u"Using existing file '%1'") << saveAsFile;
280 const QPointer<CInstallXSwiftBusComponent> guard(this);
281 QTimer::singleShot(100, this, [=, this] {
282 if (guard.isNull())
283 {
284 return;
285 }
286 this->downloadedXSwiftBusFile(msg);
287 });
288 return;
289 }
290 }
291
292 const QNetworkReply *r = sGui->downloadFromNetwork(
293 download, saveAsFile, { this, &CInstallXSwiftBusComponent::downloadedXSwiftBusFile });
294 if (r)
295 {
296 CLogMessage(this).info(u"Triggered downloading of xswiftbus file from '%1'") << download.getHost();
297 this->showLoading(120s); // timeout in any case
298 }
299 else
300 {
301 const CStatusMessage msg =
302 CStatusMessage(this, CLogCategories::validation()).error(u"Starting download for '%1' failed")
303 << download.getFullUrl();
304 this->showOverlayMessage(msg, CInstallXSwiftBusComponent::OverlayMsgTimeout);
305 }
306 }
307
308 void CInstallXSwiftBusComponent::downloadedXSwiftBusFile(const CStatusMessage &status)
309 {
310 this->hideLoading();
311 if (sGui && sGui->isShuttingDown())
312 {
313 return;
314 }
315 if (status.isWarningOrAbove())
316 {
317 this->showOverlayMessage(status);
318 return;
319 }
320 if (!this->existsXSwiftBusPluginDir())
321 {
322 const CStatusMessage msg = CStatusMessage(this).warning(u"No valid install directory, cannot continue.");
323 this->showOverlayMessage(msg);
324 return;
325 }
326
327 static const QString confirm("Install in '%1'?");
329 status, false, confirm.arg(ui->le_XSwiftBusPluginDir->text()),
330 [=, this] { QTimer::singleShot(0, this, &CInstallXSwiftBusComponent::installXSwiftBus); });
331 }
332
333 CRemoteFile CInstallXSwiftBusComponent::getRemoteFileSelected() const
334 {
335 const QString baseNameAndSize = ui->cb_DownloadFile->currentText();
336 const CUpdateInfo update = m_updates.get();
337 const CRemoteFileList remoteFiles = update.getArtifactsXSwiftBus().asRemoteFiles();
338 return remoteFiles.findFirstByMatchingBaseNameOrDefault(baseNameAndSize);
339 }
340
341 QString CInstallXSwiftBusComponent::getDownloadDir() const
342 {
343 return CFileUtils::fixWindowsUncPath(ui->le_DownloadDir->text().trimmed());
344 }
345
346 QString CInstallXSwiftBusComponent::GetXSwiftBusDir() const
347 {
348 return CFileUtils::fixWindowsUncPath(ui->le_XSwiftBusPluginDir->text().trimmed());
349 }
350
351 bool CInstallXSwiftBusComponent::existsDownloadDir() const
352 {
353 const QDir dir(this->getDownloadDir());
354 return dir.exists() && dir.isReadable();
355 }
356
357 bool CInstallXSwiftBusComponent::existsXSwiftBusPluginDir() const
358 {
359 const QDir dir(this->GetXSwiftBusDir());
360 return dir.exists() && dir.isReadable();
361 }
362
363 QString CInstallXSwiftBusComponent::getXPlanePluginDirectory() const
364 {
365 const CXPlaneSimulatorSettings settings = m_simulatorSettings.getSettings(CSimulatorInfo::XPLANE);
366 return settings.getPluginDirOrDefault();
367 }
368
369 void CInstallXSwiftBusComponent::updatesChanged()
370 {
371 const CUpdateInfo updateInfo = m_updates.get();
372 if (updateInfo.getArtifactsXSwiftBus().isEmpty())
373 {
374 return;
375 }
376 const CArtifactList artifacts =
378 if (artifacts.isEmpty())
379 {
380 return;
381 }
382
383 const CRemoteFileList remoteFiles = artifacts.asRemoteFiles();
384 if (!remoteFiles.isEmpty())
385 {
386 const QStringList xSwiftBusFiles(remoteFiles.getBaseNamesPlusSize(false));
387 m_xSwiftBusArtifacts = artifacts;
388 ui->cb_DownloadFile->addItems(xSwiftBusFiles);
389
390 // current text
391 QString current = xSwiftBusFiles.front(); // default latest first
392 if (m_defaultDownloadName.isEmpty())
393 {
394 const CRemoteFile rf =
396 if (rf.hasName())
397 {
398 current = rf.getBaseNameAndSize();
399 }
400 }
401 else
402 {
403 const CRemoteFile rf = remoteFiles.findFirstByMatchingBaseNameOrDefault(m_defaultDownloadName);
404 if (rf.hasName())
405 {
406 current = rf.getBaseNameAndSize();
407 }
408 }
409
410 ui->cb_DownloadFile->setCurrentText(current.isEmpty() ? remoteFiles.frontOrDefault().getBaseNameAndSize() :
411 current); // latest version
412 }
413 ui->cb_DownloadFile->setEnabled(!remoteFiles.isEmpty());
414 }
415
416 void CInstallXSwiftBusComponent::openInstallDir()
417 {
418 if (!this->existsXSwiftBusPluginDir())
419 {
420 return;
421 }
422 const QString file = CFileUtils::fixWindowsUncPath(ui->le_XSwiftBusPluginDir->text());
424 }
425
426 void CInstallXSwiftBusComponent::openDownloadDir()
427 {
428 if (!this->existsDownloadDir())
429 {
430 return;
431 }
432 const QString file = CFileUtils::fixWindowsUncPath(ui->le_DownloadDir->text());
434 }
435
437} // namespace swift::gui::components
static const QString & getVersionString()
Version as QVersionNumber.
QNetworkReply * downloadFromNetwork(const swift::misc::network::CUrl &url, const QString &saveAsFileName, const swift::misc::CSlot< void(const swift::misc::CStatusMessage &)> &callback, int maxRedirects=DefaultMaxRedirects)
Download file from network and store it as passed.
bool hasWebDataServices() const
Web data services available?
bool isShuttingDown() const
Is application shutting down?
void showLoading(std::chrono::milliseconds timeout=std::chrono::milliseconds(0), bool processEvents=true)
Show load indicator.
void hideLoading()
Hide load indicator.
CLoadIndicatorEnabled(QWidget *usingWidget)
Ctor.
bool showOverlayMessage(const swift::misc::CStatusMessage &message, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
void showOverlayMessagesWithConfirmation(const swift::misc::CStatusMessageList &messages, bool appendOldMessages, const QString &confirmationMessage, std::function< void()> okLambda, QMessageBox::StandardButton defaultButton=QMessageBox::Cancel, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
COverlayMessagesFrame(QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags())
Constructor.
void setDefaultDownloadName(const QString &defaultDownload)
Set a default name for download.
CInstallXSwiftBusComponent(QWidget *parent=nullptr)
Default constructor.
static bool zipUncompress(const QString &file, const QString &directory, QStringList *stdOutAndError=nullptr)
Unzip file.
static QString appendFilePathsAndFixUnc(const QString &path1, const QString &path2)
Append file paths.
static QString fixWindowsUncPath(const QString &filePath)
UNC file paths on Qt start with "/", but UNC file paths only work when they start with "//".
static const QString & validation()
Validation.
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
const_reference frontOrDefault() const
Access the first element, or a default-initialized value if the sequence is empty.
Definition sequence.h:239
reference front()
Access the first element.
Definition sequence.h:225
bool isEmpty() const
Synonym for empty.
Definition sequence.h:285
Streamable status message, e.g.
bool isWarningOrAbove() const
Warning or above.
CArtifactList findWithUnrestrictedDistributions() const
Find artifacts with public (unrestricted) distributions.
network::CRemoteFileList asRemoteFiles() const
As remote files.
CArtifactList getArtifactsXSwiftBusLatestVersionFirst() const
Artifacts (xswiftbus).
const CArtifactList & getArtifactsXSwiftBus() const
Artifacts (xswiftbus).
Definition updateinfo.h:51
QString getBaseName() const
Name with directory stripped.
Definition remotefile.h:55
CUrl getSmartUrl() const
Automatically concatenates the name if missing.
bool hasName() const
Has name?
Definition remotefile.h:58
QString getBaseNameAndSize() const
Name + human readable size.
CRemoteFile findFirstContainingNameOrDefault(const QString &name, Qt::CaseSensitivity cs) const
First by name contained of default.
QStringList getBaseNamesPlusSize(bool sorted=true) const
All file names plus size.
CRemoteFile findFirstByMatchingBaseNameOrDefault(const QString &name) const
Find first matching name of default.
const QString & getHost() const
Get host.
Definition url.h:55
bool isEmpty() const
Empty.
Definition url.cpp:63
QString getFullUrl(bool withQuery=true) const
Qualified name.
Definition url.cpp:102
QString getPluginDirOrDefault() const
Plugin directory or default plugin dir.
SWIFT_GUI_EXPORT swift::gui::CGuiApplication * sGui
Single instance of GUI application object.
High level reusable GUI components.
Free functions in swift::misc.
const QString & safeAt(const QStringList &stringList, int index)
Safe "at" function, returns empty string if index does not exists.
bool openUrl(const QUrl &url)
bool copy(const QString &fileName, const QString &newName)
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options)
QMessageBox::StandardButton question(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
QString writableLocation(QStandardPaths::StandardLocation type)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
CaseInsensitive
QUrl fromLocalFile(const QString &localFile)
QWidget(QWidget *parent, Qt::WindowFlags f)
QWidget * parentWidget() const const
void update()