FlightGear next
InstallSceneryDialog.cxx
Go to the documentation of this file.
1// InstallSceneryDialog.cxx - part of GUI launcher using Qt5
2//
3// Written by James Turner, started June 2016.
4//
5// Copyright (C) 2016 James Turner <zakalawe@mac.com>
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "config.h"
22
24#include "ui_InstallSceneryDialog.h"
25
26#include <QPushButton>
27#include <QDebug>
28#include <QFileDialog>
29#include <QThread>
30#include <QFile>
31#include <QFileInfo>
32#include <QRegularExpression>
33#include <QDir>
34#include <QStandardPaths>
35
36#include <Main/globals.hxx>
37#include <Main/options.hxx>
38
39#include <simgear/io/untar.hxx>
40
41class SceneryExtractor : public simgear::ArchiveExtractor
42{
43public:
44 SceneryExtractor(const SGPath& root) :
45 ArchiveExtractor(root)
46 {}
47protected:
48
49 auto filterPath(std::string& path) -> PathResult override
50 {
51 if ((path.find("Objects/") == 0) || (path.find("Terrain/") == 0)) {
52 return Accepted;
53 }
54
55 path = "Terrain/" + path;
56 return Modified;
57 }
58};
59
60class InstallSceneryThread : public QThread
61{
62 Q_OBJECT
63public:
64 InstallSceneryThread(QString extractDir, QStringList files) :
65 m_extractDir(extractDir),
66 m_remainingPaths(files),
67 m_error(false),
68 m_totalBytes(0),
69 m_bytesRead(0)
70 {
71
72 }
73
74 virtual void run()
75 {
76 // pre-check each file
77 Q_FOREACH (QString path, m_remainingPaths) {
78 QFileInfo finfo(path);
79 QString baseName = finfo.baseName();
80 QRegularExpression re("[e|w]\\d{2}0[n|s]\\d0", QRegularExpression::CaseInsensitiveOption);
81 Q_ASSERT(re.isValid());
82 if (!re.match(baseName).hasMatch()) {
83 emit extractionError(path,tr("scenery archive name is not correct."));
84 m_error = true;
85 return;
86 }
87
88 QFile f(path);
89 f.open(QIODevice::ReadOnly);
90 QByteArray firstData = f.read(8192);
91
92 auto archiveType = simgear::ArchiveExtractor::determineType((uint8_t*) firstData.data(), firstData.size());
93 if (archiveType == simgear::ArchiveExtractor::Invalid) {
94 emit extractionError(path,tr("file does not appear to be a scenery archive."));
95 m_error = true;
96 return;
97 }
98
99 m_totalBytes += f.size();
100 }
101
102 while (!m_remainingPaths.isEmpty() && !m_error) {
103 extractNextArchive();
104 }
105 }
106
107signals:
108 void extractionError(QString file, QString msg);
109
110 void progress(int percent);
111
112 void extractingArchive(QString archiveName);
113private:
114 void extractNextArchive()
115 {
116 SGPath root(m_extractDir.toStdString());
117 m_archive.reset(new SceneryExtractor(root));
118
119 QString path = m_remainingPaths.front();
120 m_remainingPaths.pop_front();
121 QFileInfo finfo(path);
122
123 emit extractingArchive(path);
124
125 QFile f(path);
126 f.open(QIODevice::ReadOnly);
127 Q_ASSERT(f.isOpen());
128
129 while (!f.atEnd()) {
130 QByteArray bytes = f.read(4 * 1024 * 1024);
131 m_archive->extractBytes((const uint8_t*) bytes.constData(), bytes.size());
132 m_bytesRead += bytes.size();
133
134 if (m_archive->hasError()) {
135 break;
136 }
137
138 emit progress((m_bytesRead * 100) / m_totalBytes);
139 }
140
141 m_archive->flush();
142 if (m_archive->hasError() || !m_archive->isAtEndOfArchive()) {
143 emit extractionError(path, tr("unarchiving failed"));
144 m_error = true;
145 // try to clean up?
146 }
147 }
148
149 QString m_extractDir;
150 QStringList m_remainingPaths;
151 std::unique_ptr<simgear::ArchiveExtractor> m_archive;
152 bool m_error;
153 quint64 m_totalBytes;
154 quint64 m_bytesRead;
155};
156
157InstallSceneryDialog::InstallSceneryDialog(QWidget *parent, QString downloadDir) :
158 QDialog(parent, Qt::Dialog
159 | Qt::CustomizeWindowHint
160 | Qt::WindowTitleHint
161 | Qt::WindowSystemMenuHint
162 | Qt::WindowContextHelpButtonHint
163 | Qt::MSWindowsFixedSizeDialogHint),
164 m_state(STATE_START),
165 m_downloadDir(downloadDir),
166 ui(new Ui::InstallSceneryDialog)
167{
168 ui->setupUi(this);
169 if (m_downloadDir.isEmpty()) {
170 m_downloadDir = QString::fromStdString(flightgear::defaultDownloadDir().utf8Str());
171 }
172
173 QString baseIntroString = ui->introText->text();
174 ui->introText->setText(baseIntroString.arg(m_downloadDir));
175 updateUi();
176}
177
182
183void InstallSceneryDialog::updateUi()
184{
185 QPushButton* b = ui->buttonBox->button(QDialogButtonBox::Ok);
186 QPushButton* cancel = ui->buttonBox->button(QDialogButtonBox::Cancel);
187
188 switch (m_state) {
189 case STATE_START:
190 b->setText(tr("Next"));
191 // b->setEnabled(m_catalogUrl.isValid() && !m_catalogUrl.isRelative());
192 break;
193
194 case STATE_EXTRACTING:
195 b->setEnabled(false);
196 cancel->setEnabled(false);
197 ui->progressText->setText(tr("Extracting"));
198 ui->stack->setCurrentIndex(1);
199 break;
200
201 case STATE_EXTRACT_FAILED:
202 b->setEnabled(false);
203 cancel->setEnabled(true);
204 ui->stack->setCurrentIndex(2);
205 break;
206
207 case STATE_FINISHED:
208 b->setEnabled(true);
209 cancel->setEnabled(false);
210 b->setText(tr("Okay"));
211 ui->stack->setCurrentIndex(2);
212 QString basicDesc = ui->resultsSummaryLabel->text();
213
214 ui->resultsSummaryLabel->setText(basicDesc.arg(sceneryPath()));
215 break;
216 }
217}
218
219
220void InstallSceneryDialog::accept()
221{
222 switch (m_state) {
223 case STATE_START:
224 pickFiles();
225 break;
226
227 case STATE_EXTRACTING:
228 case STATE_EXTRACT_FAILED:
229 // can't happen, button is disabled
230 break;
231
232 case STATE_FINISHED:
233 // check if download path is in scenery list, add if not
234 QDialog::accept();
235 break;
236 }
237}
238
239void InstallSceneryDialog::reject()
240{
241
242 QDialog::reject();
243}
244
245void InstallSceneryDialog::pickFiles()
246{
247 QStringList downloads = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation);
248 QStringList files = QFileDialog::getOpenFileNames(this, tr("Choose scenery to install"),
249 downloads.first(), "Compressed data (*.tar *.gz *.tgz *.zip)");
250 if (!files.isEmpty()) {
251 QDir d(m_downloadDir);
252 if (!d.exists("Scenery")) {
253 d.mkdir("Scenery");
254 }
255
256 m_state = STATE_EXTRACTING;
257 m_thread.reset(new InstallSceneryThread(d.filePath("Scenery"), files));
258 // connect up some signals
259 connect(m_thread.data(), &QThread::finished, this, &InstallSceneryDialog::onThreadFinished);
260 connect(m_thread.data(), &InstallSceneryThread::extractionError,
261 this, &InstallSceneryDialog::onExtractError);
262 connect(m_thread.data(), &InstallSceneryThread::progress,
263 this, &InstallSceneryDialog::onExtractProgress);
264 connect(m_thread.data(), &InstallSceneryThread::extractingArchive,
265 this, &InstallSceneryDialog::onExtractFile);
266 updateUi();
267 m_thread->start();
268 } else {
269 // user cancelled file dialog, cancel us as well
270 QDialog::reject();
271 }
272}
273
274void InstallSceneryDialog::onThreadFinished()
275{
276 m_state = STATE_FINISHED;
277 updateUi();
278}
279
280void InstallSceneryDialog::onExtractError(QString file, QString msg)
281{
282 ui->resultsSummaryLabel->setText(tr("Problems occured extracting the archive '%1': %2").arg(file).arg(msg));
283 m_state = STATE_EXTRACT_FAILED;
284 updateUi();
285}
286
287void InstallSceneryDialog::onExtractProgress(int percent)
288{
289 ui->progressBar->setValue(percent);
290}
291
292void InstallSceneryDialog::onExtractFile(QString file)
293{
294 ui->progressText->setText(tr("Extracting %1").arg(file));
295}
296
298{
299 if (m_state == STATE_FINISHED) {
300 QDir d(m_downloadDir);
301 return d.filePath("Scenery");
302 }
303
304 return QString();
305}
306
307#include "InstallSceneryDialog.moc"
InstallSceneryDialog(QWidget *parent, QString downloadDir)
void extractingArchive(QString archiveName)
void progress(int percent)
InstallSceneryThread(QString extractDir, QStringList files)
void extractionError(QString file, QString msg)
SceneryExtractor(const SGPath &root)
auto filterPath(std::string &path) -> PathResult override