1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 BlackBerry Limited. All rights reserved.
4 ** Contact: BlackBerry (qt@blackberry.com), KDAB (info@kdab.com)
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qnxdeployqtlibrariesdialog.h"
27 #include "ui_qnxdeployqtlibrariesdialog.h"
28 
29 #include "qnxconstants.h"
30 #include "qnxqtversion.h"
31 
32 #include <projectexplorer/deployablefile.h>
33 #include <qtsupport/qtversionmanager.h>
34 #include <remotelinux/genericdirectuploadservice.h>
35 #include <ssh/sshremoteprocessrunner.h>
36 
37 #include <utils/algorithm.h>
38 #include <utils/qtcassert.h>
39 
40 #include <QDir>
41 #include <QMessageBox>
42 
43 using namespace QtSupport;
44 using namespace ProjectExplorer;
45 using namespace RemoteLinux;
46 
47 namespace Qnx {
48 namespace Internal {
49 
QnxDeployQtLibrariesDialog(const IDevice::ConstPtr & device,QWidget * parent)50 QnxDeployQtLibrariesDialog::QnxDeployQtLibrariesDialog(const IDevice::ConstPtr &device,
51                                                        QWidget *parent) :
52     QDialog(parent),
53     m_ui(new Ui::QnxDeployQtLibrariesDialog),
54     m_device(device),
55     m_progressCount(0),
56     m_state(Inactive)
57 {
58     m_ui->setupUi(this);
59 
60     const QList<BaseQtVersion*> qtVersions
61             = QtVersionManager::sortVersions(
62                 QtVersionManager::versions(BaseQtVersion::isValidPredicate(Utils::equal(&BaseQtVersion::type,
63                                                                                         QString::fromLatin1(Constants::QNX_QNX_QT)))));
64     for (BaseQtVersion *v : qtVersions)
65         m_ui->qtLibraryCombo->addItem(v->displayName(), v->uniqueId());
66 
67     m_ui->basePathLabel->setText(QString());
68     m_ui->remoteDirectory->setText(QLatin1String("/qt"));
69 
70     m_uploadService = new RemoteLinux::GenericDirectUploadService(this);
71     m_uploadService->setDevice(m_device);
72 
73     connect(m_uploadService, &AbstractRemoteLinuxDeployService::progressMessage,
74             this, &QnxDeployQtLibrariesDialog::updateProgress);
75     connect(m_uploadService, &AbstractRemoteLinuxDeployService::progressMessage,
76             m_ui->deployLogWindow, &QPlainTextEdit::appendPlainText);
77     connect(m_uploadService, &AbstractRemoteLinuxDeployService::errorMessage,
78             m_ui->deployLogWindow, &QPlainTextEdit::appendPlainText);
79     connect(m_uploadService, &AbstractRemoteLinuxDeployService::warningMessage,
80             this, [this](const QString &message) {
81         if (!message.contains("stat:"))
82             m_ui->deployLogWindow->appendPlainText(message);
83     });
84     connect(m_uploadService, &AbstractRemoteLinuxDeployService::stdOutData,
85             m_ui->deployLogWindow, &QPlainTextEdit::appendPlainText);
86     connect(m_uploadService, &AbstractRemoteLinuxDeployService::stdErrData,
87             m_ui->deployLogWindow, &QPlainTextEdit::appendPlainText);
88     connect(m_uploadService, &AbstractRemoteLinuxDeployService::finished,
89             this, &QnxDeployQtLibrariesDialog::handleUploadFinished);
90 
91     m_processRunner = new QSsh::SshRemoteProcessRunner(this);
92     connect(m_processRunner, &QSsh::SshRemoteProcessRunner::connectionError,
93             this, &QnxDeployQtLibrariesDialog::handleRemoteProcessError);
94     connect(m_processRunner, &QSsh::SshRemoteProcessRunner::processClosed,
95             this, &QnxDeployQtLibrariesDialog::handleRemoteProcessCompleted);
96 
97     connect(m_ui->deployButton, &QAbstractButton::clicked,
98             this, &QnxDeployQtLibrariesDialog::deployLibraries);
99     connect(m_ui->closeButton, &QAbstractButton::clicked,
100             this, &QWidget::close);
101 }
102 
~QnxDeployQtLibrariesDialog()103 QnxDeployQtLibrariesDialog::~QnxDeployQtLibrariesDialog()
104 {
105     delete m_ui;
106 }
107 
execAndDeploy(int qtVersionId,const QString & remoteDirectory)108 int QnxDeployQtLibrariesDialog::execAndDeploy(int qtVersionId, const QString &remoteDirectory)
109 {
110     m_ui->remoteDirectory->setText(remoteDirectory);
111     m_ui->qtLibraryCombo->setCurrentIndex(m_ui->qtLibraryCombo->findData(qtVersionId));
112 
113     deployLibraries();
114     return exec();
115 }
116 
closeEvent(QCloseEvent * event)117 void QnxDeployQtLibrariesDialog::closeEvent(QCloseEvent *event)
118 {
119     // A disabled Deploy button indicates the upload is still running
120     if (!m_ui->deployButton->isEnabled()) {
121         int answer = QMessageBox::question(this, windowTitle(),
122                                            tr("Closing the dialog will stop the deployment. "
123                                               "Are you sure you want to do this?"),
124                                            QMessageBox::Yes | QMessageBox::No);
125         if (answer == QMessageBox::No)
126             event->ignore();
127         else if (answer == QMessageBox::Yes)
128             m_uploadService->stop();
129     }
130 }
131 
deployLibraries()132 void QnxDeployQtLibrariesDialog::deployLibraries()
133 {
134     QTC_ASSERT(m_state == Inactive, return);
135 
136     if (m_ui->remoteDirectory->text().isEmpty()) {
137         QMessageBox::warning(this, windowTitle(),
138                              tr("Please input a remote directory to deploy to."));
139         return;
140     }
141 
142     QTC_ASSERT(!m_device.isNull(), return);
143 
144     m_progressCount = 0;
145     m_ui->deployProgress->setValue(0);
146     m_ui->remoteDirectory->setEnabled(false);
147     m_ui->deployButton->setEnabled(false);
148     m_ui->qtLibraryCombo->setEnabled(false);
149     m_ui->deployLogWindow->clear();
150 
151     checkRemoteDirectoryExistance();
152 }
153 
startUpload()154 void QnxDeployQtLibrariesDialog::startUpload()
155 {
156     QTC_CHECK(m_state == CheckingRemoteDirectory || m_state == RemovingRemoteDirectory);
157 
158     m_state = Uploading;
159 
160     QList<DeployableFile> filesToUpload = gatherFiles();
161 
162     m_ui->deployProgress->setRange(0, filesToUpload.count());
163 
164     m_uploadService->setDeployableFiles(filesToUpload);
165     m_uploadService->start();
166 }
167 
updateProgress(const QString & progressMessage)168 void QnxDeployQtLibrariesDialog::updateProgress(const QString &progressMessage)
169 {
170     QTC_CHECK(m_state == Uploading);
171 
172     const int progress = progressMessage.count("sftp> put");
173     if (progress != 0) {
174         m_progressCount += progress;
175         m_ui->deployProgress->setValue(m_progressCount);
176     }
177 }
178 
handleUploadFinished()179 void QnxDeployQtLibrariesDialog::handleUploadFinished()
180 {
181     m_ui->remoteDirectory->setEnabled(true);
182     m_ui->deployButton->setEnabled(true);
183     m_ui->qtLibraryCombo->setEnabled(true);
184 
185     m_state = Inactive;
186 }
187 
handleRemoteProcessError()188 void QnxDeployQtLibrariesDialog::handleRemoteProcessError()
189 {
190     QTC_CHECK(m_state == CheckingRemoteDirectory || m_state == RemovingRemoteDirectory);
191 
192     m_ui->deployLogWindow->appendPlainText(
193                 tr("Connection failed: %1")
194                 .arg(m_processRunner->lastConnectionErrorString()));
195     handleUploadFinished();
196 }
197 
handleRemoteProcessCompleted()198 void QnxDeployQtLibrariesDialog::handleRemoteProcessCompleted()
199 {
200     QTC_CHECK(m_state == CheckingRemoteDirectory || m_state == RemovingRemoteDirectory);
201 
202     if (m_state == CheckingRemoteDirectory) {
203         // Directory exists
204         if (m_processRunner->processExitCode() == 0) {
205             int answer = QMessageBox::question(this, windowTitle(),
206                                                tr("The remote directory \"%1\" already exists. "
207                                                   "Deploying to that directory will remove any files "
208                                                   "already present.\n\n"
209                                                   "Are you sure you want to continue?")
210                                                .arg(fullRemoteDirectory()),
211                                                QMessageBox::Yes | QMessageBox::No);
212             if (answer == QMessageBox::Yes)
213                 removeRemoteDirectory();
214             else
215                 handleUploadFinished();
216         } else {
217             startUpload();
218         }
219     } else if (m_state == RemovingRemoteDirectory) {
220         QTC_ASSERT(m_processRunner->processExitCode() == 0, return);
221 
222         startUpload();
223     }
224 }
225 
gatherFiles()226 QList<DeployableFile> QnxDeployQtLibrariesDialog::gatherFiles()
227 {
228     QList<DeployableFile> result;
229 
230     const int qtVersionId =
231             m_ui->qtLibraryCombo->itemData(m_ui->qtLibraryCombo->currentIndex()).toInt();
232 
233 
234     auto qtVersion = dynamic_cast<const QnxQtVersion *>(QtVersionManager::version(qtVersionId));
235 
236     QTC_ASSERT(qtVersion, return result);
237 
238     if (Utils::HostOsInfo::isWindowsHost()) {
239         result.append(gatherFiles(qtVersion->libraryPath().toString(),
240                                   QString(),
241                                   QStringList() << QLatin1String("*.so.?")));
242         result.append(gatherFiles(qtVersion->libraryPath().toString() + QLatin1String("/fonts")));
243     } else {
244         result.append(gatherFiles(qtVersion->libraryPath().toString()));
245     }
246 
247     result.append(gatherFiles(qtVersion->pluginPath().toString()));
248     result.append(gatherFiles(qtVersion->importsPath().toString()));
249     result.append(gatherFiles(qtVersion->qmlPath().toString()));
250 
251     return result;
252 }
253 
gatherFiles(const QString & dirPath,const QString & baseDirPath,const QStringList & nameFilters)254 QList<DeployableFile> QnxDeployQtLibrariesDialog::gatherFiles(
255         const QString &dirPath, const QString &baseDirPath, const QStringList &nameFilters)
256 {
257     QList<DeployableFile> result;
258     if (dirPath.isEmpty())
259         return result;
260 
261     static const QStringList unusedDirs = {"include", "mkspecs", "cmake", "pkgconfig"};
262     const QString dp = dirPath.endsWith('/') ? dirPath.left(dirPath.size() - 1) : dirPath;
263     if (unusedDirs.contains(dp))
264         return result;
265 
266     QDir dir(dirPath);
267     QFileInfoList list = dir.entryInfoList(nameFilters,
268             QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
269 
270     for (auto &fileInfo : list) {
271         if (fileInfo.isDir()) {
272             result.append(gatherFiles(fileInfo.absoluteFilePath(), baseDirPath.isEmpty() ?
273                                           dirPath : baseDirPath));
274         } else {
275             static const QStringList unusedSuffixes = {"cmake", "la", "prl", "a", "pc"};
276             if (unusedSuffixes.contains(fileInfo.suffix()))
277                 continue;
278 
279             QString remoteDir;
280             if (baseDirPath.isEmpty()) {
281                 remoteDir = fullRemoteDirectory() + QLatin1Char('/') +
282                         QFileInfo(dirPath).baseName();
283             } else {
284                 QDir baseDir(baseDirPath);
285                 baseDir.cdUp();
286                 remoteDir = fullRemoteDirectory() + QLatin1Char('/') +
287                         baseDir.relativeFilePath(dirPath);
288             }
289             result.append(DeployableFile(fileInfo.absoluteFilePath(), remoteDir));
290         }
291     }
292 
293     return result;
294 }
295 
fullRemoteDirectory() const296 QString QnxDeployQtLibrariesDialog::fullRemoteDirectory() const
297 {
298     return  m_ui->remoteDirectory->text();
299 }
300 
checkRemoteDirectoryExistance()301 void QnxDeployQtLibrariesDialog::checkRemoteDirectoryExistance()
302 {
303     QTC_CHECK(m_state == Inactive);
304 
305     m_state = CheckingRemoteDirectory;
306     m_ui->deployLogWindow->appendPlainText(tr("Checking existence of \"%1\"")
307                                            .arg(fullRemoteDirectory()));
308     m_processRunner->run("test -d " + fullRemoteDirectory(), m_device->sshParameters());
309 }
310 
removeRemoteDirectory()311 void QnxDeployQtLibrariesDialog::removeRemoteDirectory()
312 {
313     QTC_CHECK(m_state == CheckingRemoteDirectory);
314 
315     m_state = RemovingRemoteDirectory;
316     m_ui->deployLogWindow->appendPlainText(tr("Removing \"%1\"").arg(fullRemoteDirectory()));
317     m_processRunner->run("rm -rf " + fullRemoteDirectory(), m_device->sshParameters());
318 }
319 
320 } // namespace Internal
321 } // namespace Qnx
322