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 }