File indexing completed on 2024-04-21 04:34:29
0001 /* 0002 * This file is part of KDevelop project 0003 * Copyright 2016 Patrick José Pereira <patrickelectric@gmail.com> 0004 * Copyright 2010 Denis Martinez 0005 * Copyright 2010 Martin Peres 0006 * 0007 * This program is free software; you can redistribute it and/or modify 0008 * it under the terms of the GNU Library General Public License as 0009 * published by the Free Software Foundation; either version 2 of the 0010 * License, or (at your option) any later version. 0011 * 0012 * This program is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 * GNU General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU General Public 0018 * License along with this program; if not, write to the 0019 * Free Software Foundation, Inc., 0020 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "firsttimewizard.h" 0024 0025 #include <QStandardPaths> 0026 0027 #include <QNetworkAccessManager> 0028 #include <QNetworkRequest> 0029 #include <QNetworkReply> 0030 #include <QEventLoop> 0031 #include <QFutureWatcher> 0032 #include <QFuture> 0033 #include <QtConcurrent/QtConcurrent> 0034 #include <QProcess> 0035 #include <QMessageBox> 0036 0037 #include <QPushButton> 0038 #include <QThread> 0039 0040 #include <QFileDialog> 0041 #include <QDir> 0042 #include <QDebug> 0043 #include <QStringList> 0044 #include <QLocale> 0045 0046 #include <interfaces/isession.h> 0047 #include <interfaces/icore.h> 0048 0049 #include <KLocalizedString> 0050 #include <KConfigGroup> 0051 #include <KFormat> 0052 #include <KTar> 0053 0054 #include "toolkit.h" 0055 0056 Q_LOGGING_CATEGORY(FtwIo, "Kdev.embedded.ftw.io") 0057 Q_LOGGING_CATEGORY(FtwMsg, "Kdev.embedded.ftw.msg") 0058 0059 using namespace KDevelop; 0060 0061 #ifdef Q_OS_DARWIN 0062 QString FirstTimeWizard::downloadOsUrl = QStringLiteral("macosx"); 0063 QString FirstTimeWizard::downloadExtensionUrl = QStringLiteral("zip"); 0064 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) 0065 QString FirstTimeWizard::downloadOsUrl = QStringLiteral("windows"); 0066 QString FirstTimeWizard::downloadExtensionUrl = QStringLiteral("zip"); 0067 #else 0068 QString FirstTimeWizard::downloadOsUrl = QStringLiteral("linux"); 0069 QString FirstTimeWizard::downloadExtensionUrl = QStringLiteral("tar.xz"); 0070 #endif 0071 0072 #ifdef Q_OS_WIN32 0073 QString FirstTimeWizard::downloadArchUrl = QStringLiteral("32"); 0074 #else 0075 QString FirstTimeWizard::downloadArchUrl = QStringLiteral("64"); 0076 #endif 0077 0078 QString FirstTimeWizard::arduinoDownloadUrl = 0079 QStringLiteral("https://downloads.arduino.cc/arduino-%0-%1.%2") 0080 .arg(QStringLiteral(ARDUINO_SDK_VERSION_NAME)) 0081 .arg(downloadOsUrl+downloadArchUrl) 0082 .arg(downloadExtensionUrl); 0083 0084 FirstTimeWizard::FirstTimeWizard(QWidget *parent) : 0085 QWizard(parent), 0086 m_mDownloadManager(new QNetworkAccessManager), 0087 m_reply(nullptr), 0088 m_downloadFinished(false), 0089 m_installFinished(false), 0090 m_avrdudeProcess(new QProcess(parent)), 0091 m_format(new KFormat(*new QLocale())) 0092 { 0093 setupUi(this); 0094 0095 downloadStatusLabel->clear(); 0096 installStatusLabel->clear(); 0097 0098 urlLabel->setTextFormat(Qt::TextFormat::RichText); 0099 urlLabel->setText(i18n("<p>More information at: <a href=\"mailto:%1\">%1</a></p>", QStringLiteral("patrickelectric@gmail.com"))); 0100 projectLabel->setText(i18n("Embedded plugin is an unofficial project by Patrick J. Pereira.")); 0101 0102 existingInstallButton->setText(existingInstallButton->text()+QStringLiteral("(Arduino SDK " ARDUINO_SDK_MIN_VERSION_NAME " or superior)")); 0103 automaticInstallButton->setText(automaticInstallButton->text()+QStringLiteral("(Arduino SDK " ARDUINO_SDK_VERSION_NAME ")")); 0104 0105 // Download mode is default 0106 automaticInstallButton->setChecked(true); 0107 m_downloadRunning = false; 0108 // Arduino path 0109 fetchArduinoPath(); 0110 // Sketchbook path 0111 fetchSketchbookPath(); 0112 0113 //TODO support others OS 0114 QString mDownloadOs = QStringLiteral("Linux"); 0115 0116 downloadLabel->setText(i18n("Arduino %1 for %2", QStringLiteral(ARDUINO_SDK_VERSION_NAME), mDownloadOs)); 0117 0118 connect(arduinoPathButton, &QToolButton::clicked, this, &FirstTimeWizard::chooseArduinoPath); 0119 connect(sketchbookPathButton, &QToolButton::clicked, this, &FirstTimeWizard::chooseSketchbookPath); 0120 connect(this, &QWizard::currentIdChanged, this, &FirstTimeWizard::validateCurrentId); 0121 connect(button(QWizard::CancelButton), &QAbstractButton::clicked, this, &FirstTimeWizard::cancelButtonClicked); 0122 0123 m_avrdudeProcess->connect(m_avrdudeProcess, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, this, &FirstTimeWizard::avrdudeStderr); 0124 m_avrdudeProcess->connect(m_avrdudeProcess, &QProcess::readyReadStandardOutput, this, &FirstTimeWizard::avrdudeStdout); 0125 } 0126 0127 bool FirstTimeWizard::validateCurrentPage() 0128 { 0129 switch (currentId()) 0130 { 0131 case 0: 0132 if (existingInstallButton->isChecked() && !Toolkit::instance().isValidArduinoPath(arduinoPathEdit->text())) 0133 { 0134 return false; 0135 } 0136 break; 0137 0138 case 1: 0139 { 0140 if (m_downloadFinished && m_installFinished) 0141 { 0142 return true; 0143 } 0144 else 0145 { 0146 download(); 0147 } 0148 0149 return false; 0150 break; 0151 } 0152 0153 case 2: 0154 { 0155 KConfigGroup settings = ICore::self()->activeSession()->config()->group("Embedded"); 0156 settings.writeEntry("arduinoFolder", arduinoPathEdit->text()); 0157 settings.writeEntry("sketchbookFolder", sketchbookPathEdit->text()); 0158 qCDebug(FtwMsg) << "Saving settings " << settings.groupList(); 0159 QString avrdudeConf = arduinoPathEdit->text()+Toolkit::instance().avrdudeConfigPath(); 0160 QStringList flags; 0161 flags 0162 << QStringLiteral("-p") 0163 << QStringLiteral("partno") 0164 << QStringLiteral("-c") 0165 << QStringLiteral("alf") 0166 << QStringLiteral("-C") // need after 1.6.10 Arduino version 0167 << avrdudeConf; 0168 0169 QString avrdude = arduinoPathEdit->text()+Toolkit::instance().avrdudePath(); 0170 qCDebug(FtwMsg) << "Starting.." << avrdude << flags; 0171 // Check if file exist to not create a zombie 0172 if (QFileInfo(avrdude).exists()) 0173 { 0174 m_avrdudeProcess->start(avrdude, flags); 0175 m_avrdudeProcess->waitForFinished(); 0176 } 0177 } 0178 break; 0179 0180 default: 0181 break; 0182 0183 } 0184 return true; 0185 } 0186 0187 void FirstTimeWizard::download() 0188 { 0189 button(QWizard::NextButton)->setEnabled(false); 0190 0191 if (m_downloadRunning == true && !m_downloadFinished) 0192 { 0193 return; 0194 } 0195 else if (m_downloadFinished) 0196 { 0197 button(QWizard::NextButton)->setEnabled(true); 0198 return; 0199 } 0200 0201 m_downloadRunning = true; 0202 downloadProgressBar->setValue(0); 0203 0204 const QUrl downloadLink = QUrl(arduinoDownloadUrl); 0205 QNetworkRequest request(downloadLink); 0206 0207 qCDebug(FtwIo) << "Download :" << arduinoDownloadUrl; 0208 0209 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); 0210 m_reply = m_mDownloadManager->get(request); 0211 connect(m_reply, &QNetworkReply::downloadProgress, this, &FirstTimeWizard::onDownloadProgress); 0212 connect(m_reply, &QNetworkReply::finished, this, &FirstTimeWizard::install); 0213 downloadStatusLabel->setText(i18n("Downloading...")); 0214 0215 } 0216 0217 void FirstTimeWizard::install() 0218 { 0219 m_downloadFinished = m_reply->isOpen(); 0220 qCDebug(FtwIo) << "at install m_downloadFinished" << m_downloadFinished; 0221 0222 if (m_downloadFinished) 0223 { 0224 downloadStatusLabel->setText(i18n("Downloaded")); 0225 } 0226 else 0227 { 0228 downloadStatusLabel->setText(i18n("Download cancelled")); 0229 return; 0230 } 0231 0232 // Extract the archive 0233 QTemporaryFile archive; 0234 bool extractSuccess = archive.open(); 0235 QString destinationPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); 0236 QDir destinationDir(destinationPath); 0237 0238 if (!extractSuccess) 0239 { 0240 qCDebug(FtwIo) << "Cant open file" << archive.fileName(); 0241 } 0242 0243 if (!destinationDir.exists()) 0244 { 0245 qCDebug(FtwIo) << "Destination directory already exists at" << destinationPath; 0246 extractSuccess = extractSuccess && destinationDir.mkpath(QStringLiteral(".")); 0247 } 0248 0249 if (extractSuccess) 0250 { 0251 installStatusLabel->setText(i18n("Extracting...")); 0252 // Write the reply to the temporary file 0253 QByteArray buffer; 0254 // Create a buffer of 8kB 0255 static const int bufferSize = 8192; 0256 buffer.resize(bufferSize); 0257 qint64 readBytes = m_reply->read(buffer.data(), bufferSize); 0258 while (readBytes > 0) 0259 { 0260 archive.write(buffer.data(), readBytes); 0261 readBytes = m_reply->read(buffer.data(), bufferSize); 0262 } 0263 installStatusLabel->setText(i18n("Extracting... ")+m_format->formatByteSize(readBytes)); 0264 archive.seek(0); 0265 0266 // Call Ktar to extract 0267 KTar extract(archive.fileName()); 0268 extract.open(QIODevice::ReadOnly); 0269 extractSuccess = extract.directory()->copyTo(destinationPath, true); 0270 qCDebug(FtwIo) << "Downloaded file extracted with success ? :" << extractSuccess; 0271 qCDebug(FtwIo) << archive.fileName() << "extracted in" << destinationPath; 0272 0273 QDir(destinationPath).rename(QStringLiteral("arduino-") + QStringLiteral(ARDUINO_SDK_VERSION_NAME), QStringLiteral("arduino")); 0274 destinationPath += QStringLiteral("/arduino"); 0275 installStatusLabel->setText(i18n("Extracted")); 0276 arduinoPathEdit->setText(destinationPath); 0277 m_installFinished = true; 0278 } 0279 this->button(QWizard::NextButton)->setEnabled(true); 0280 } 0281 0282 void FirstTimeWizard::cancelButtonClicked(bool state) 0283 { 0284 Q_UNUSED(state); 0285 qCDebug(FtwIo) << "CancelButton clicked"; 0286 if (m_reply) 0287 { 0288 if (m_reply->isRunning()) 0289 { 0290 m_reply->abort(); 0291 } 0292 } 0293 } 0294 0295 void FirstTimeWizard::validateCurrentId(int id) 0296 { 0297 if (id == 1 && !existingInstallButton->isChecked()) 0298 { 0299 download(); 0300 } 0301 } 0302 0303 int FirstTimeWizard::nextId() const 0304 { 0305 if (currentId() == 0 && existingInstallButton->isChecked()) 0306 { 0307 return 2; 0308 } 0309 0310 return QWizard::nextId(); 0311 } 0312 0313 void FirstTimeWizard::fetchArduinoPath() 0314 { 0315 KConfigGroup settings = ICore::self()->activeSession()->config()->group("Embedded"); 0316 if (settings.hasKey("arduinoFolder")) 0317 { 0318 arduinoPathEdit->setText(settings.readEntry("arduinoFolder")); 0319 existingInstallButton->setChecked(true); 0320 return; 0321 } 0322 0323 // Find Arduino path 0324 #ifdef Q_OS_DARWIN 0325 static QStringList defaultArduinoPaths; 0326 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) 0327 static QStringList defaultArduinoPaths; 0328 #else 0329 const QString applicationPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); 0330 // Paths to search for an existing installation 0331 static QStringList defaultArduinoPaths = QStringList() 0332 << QDir(applicationPath).filePath(QStringLiteral("arduino-") + QStringLiteral(ARDUINO_SDK_VERSION_NAME)) 0333 << QDir(applicationPath).filePath(QStringLiteral("arduino")) 0334 << QStringLiteral("/usr/local/share/arduino-") + QStringLiteral(ARDUINO_SDK_VERSION_NAME) 0335 << QStringLiteral("/usr/local/share/arduino") 0336 << QStringLiteral("/usr/share/arduino-") + QStringLiteral(ARDUINO_SDK_VERSION_NAME) 0337 << QStringLiteral("/usr/share/arduino"); 0338 #endif 0339 0340 foreach (const auto& path, defaultArduinoPaths) 0341 { 0342 qCDebug(FtwIo) << "Looking for valid arduino path in " << path; 0343 if (Toolkit::instance().isValidArduinoPath(path)) 0344 { 0345 qCDebug(FtwIo) << "Valid Arduino path at" << path; 0346 arduinoPathEdit->setText(path); 0347 existingInstallButton->setChecked(true); 0348 } 0349 } 0350 qCDebug(FtwIo) << "No valid Arduino path"; 0351 } 0352 0353 void FirstTimeWizard::fetchSketchbookPath() 0354 { 0355 KConfigGroup settings = ICore::self()->activeSession()->config()->group("Embedded"); 0356 if (settings.hasKey("sketchbookFolder")) 0357 { 0358 sketchbookPathEdit->setText(settings.readEntry("sketchbookFolder")); 0359 return; 0360 } 0361 // Find Sketchbook path 0362 QDir sketchbookPath; 0363 #ifdef Q_OS_DARWIN 0364 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) 0365 #else 0366 sketchbookPath = QDir(QDir::homePath()).filePath(QStringLiteral("sketchbook")); 0367 #endif 0368 0369 if (sketchbookPath.exists()) 0370 { 0371 sketchbookPathEdit->setText(sketchbookPath.absolutePath()); 0372 } 0373 } 0374 0375 void FirstTimeWizard::chooseArduinoPath() 0376 { 0377 const QString path = QFileDialog::getExistingDirectory(this, i18n("Find Files"), QDir::currentPath()); 0378 if (!path.isEmpty()) 0379 { 0380 arduinoPathEdit->setText(path); 0381 } 0382 0383 } 0384 0385 void FirstTimeWizard::chooseSketchbookPath() 0386 { 0387 const QString path = QFileDialog::getExistingDirectory(this, i18n("Find Files"), QDir::currentPath()); 0388 if (!path.isEmpty()) 0389 { 0390 sketchbookPathEdit->setText(path); 0391 } 0392 } 0393 0394 void FirstTimeWizard::onDownloadProgress(qint64 received, qint64 total) 0395 { 0396 int percent = 0; 0397 if (total) 0398 { 0399 percent = 100 * received / total; 0400 } 0401 0402 qCDebug(FtwIo) << "Download in Progress" << percent << "%"; 0403 qCDebug(FtwIo) << "Download in Progress" << received << "/" << total; 0404 0405 downloadStatusLabel->setText(i18n("Downloading... (%1 / %2)", m_format->formatByteSize(received), m_format->formatByteSize(total))); 0406 downloadProgressBar->setValue(percent); 0407 } 0408 0409 void FirstTimeWizard::avrdudeStderr(int exitCode, QProcess::ExitStatus exitStatus) 0410 { 0411 Q_UNUSED(exitCode) 0412 Q_UNUSED(exitStatus) 0413 0414 qCDebug(FtwMsg) << "avrdudeStderr"; 0415 m_avrdudeProcess->setReadChannel(QProcess::StandardError); 0416 QTextStream stream(m_avrdudeProcess); 0417 QStringList mcus; 0418 while (!stream.atEnd()) 0419 { 0420 QString mcu = stream.readLine().split(QChar::fromLatin1(' ')).takeLast(); 0421 if (mcu.contains(QStringLiteral("AT"))) 0422 { 0423 mcus.append(mcu.toLower()); 0424 } 0425 } 0426 qCDebug(FtwMsg) << "mcus" << mcus; 0427 0428 KConfigGroup settings = ICore::self()->activeSession()->config()->group("Embedded"); 0429 settings.writeEntry("avrdudeMCUList", mcus); 0430 } 0431 0432 void FirstTimeWizard::avrdudeStdout() 0433 { 0434 qCDebug(FtwMsg) << "avrdudeStdout"; 0435 m_avrdudeProcess->setReadChannel(QProcess::StandardOutput); 0436 QTextStream stream(m_avrdudeProcess); 0437 while (!stream.atEnd()) 0438 { 0439 qCDebug(FtwMsg) << "avrdudeStdout" << stream.readLine(); 0440 } 0441 } 0442 0443 FirstTimeWizard::~FirstTimeWizard() 0444 { 0445 delete m_mDownloadManager; 0446 }