OSDN Git Service

63519a588bb4c458ae1d1e3d9da0d85f0c6d2fd4
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qt4projectmanager / qt-maemo / maemodeploystep.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "maemodeploystep.h"
35
36 #include "maemoconstants.h"
37 #include "maemodeploystepwidget.h"
38 #include "maemoglobal.h"
39 #include "maemopackagecreationstep.h"
40 #include "maemopertargetdeviceconfigurationlistmodel.h"
41 #include "maemoqemumanager.h"
42 #include "maemoremotemounter.h"
43 #include "maemorunconfiguration.h"
44 #include "maemotoolchain.h"
45 #include "maemousedportsgatherer.h"
46 #include "qt4maemotarget.h"
47
48 #include <utils/ssh/sftpchannel.h>
49 #include <utils/ssh/sshconnection.h>
50 #include <utils/ssh/sshremoteprocess.h>
51
52 #include <projectexplorer/buildconfiguration.h>
53 #include <projectexplorer/projectexplorerconstants.h>
54 #include <projectexplorer/target.h>
55
56 #include <qt4projectmanager/qt4buildconfiguration.h>
57 #include <qt4projectmanager/qt4projectmanagerconstants.h>
58 #include <qt4projectmanager/qt4target.h>
59
60 #include <QtCore/QCoreApplication>
61 #include <QtCore/QDir>
62 #include <QtCore/QEventLoop>
63 #include <QtCore/QFileInfo>
64 #include <QtCore/QTimer>
65
66 #define ASSERT_STATE(state) ASSERT_STATE_GENERIC(State, state, m_state)
67
68 using namespace Core;
69 using namespace Utils;
70 using namespace ProjectExplorer;
71
72 namespace Qt4ProjectManager {
73 namespace Internal {
74 namespace { const int DefaultMountPort = 1050; }
75
76 const QLatin1String MaemoDeployStep::Id("Qt4ProjectManager.MaemoDeployStep");
77
78 MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildStepList *parent)
79     : BuildStep(parent, Id)
80 {
81     ctor();
82 }
83
84 MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildStepList *parent,
85     MaemoDeployStep *other)
86     : BuildStep(parent, other), m_lastDeployed(other->m_lastDeployed)
87 {
88     ctor();
89 }
90
91 MaemoDeployStep::~MaemoDeployStep() { }
92
93 void MaemoDeployStep::ctor()
94 {
95     //: MaemoDeployStep default display name
96     if (target()->id() == QLatin1String(Constants::MAEMO5_DEVICE_TARGET_ID))
97         setDefaultDisplayName(tr("Deploy to Maemo5 device"));
98     else if (target()->id() == QLatin1String(Constants::HARMATTAN_DEVICE_TARGET_ID))
99         setDefaultDisplayName(tr("Deploy to Harmattan device"));
100     else if (target()->id() == QLatin1String(Constants::MEEGO_DEVICE_TARGET_ID))
101         setDefaultDisplayName(tr("Deploy to Meego device"));
102
103     // A MaemoDeployables object is only dependent on the active build
104     // configuration and therefore can (and should) be shared among all
105     // deploy steps.
106     const QList<DeployConfiguration *> &deployConfigs
107         = target()->deployConfigurations();
108     if (deployConfigs.isEmpty()) {
109         const AbstractQt4MaemoTarget * const qt4Target = qobject_cast<AbstractQt4MaemoTarget *>(target());
110         Q_ASSERT(qt4Target);
111         m_deployables = QSharedPointer<MaemoDeployables>(new MaemoDeployables(qt4Target));
112     } else {
113         const MaemoDeployStep *const other
114             = MaemoGlobal::buildStep<MaemoDeployStep>(deployConfigs.first());
115         m_deployables = other->deployables();
116     }
117
118     m_state = Inactive;
119     m_deviceConfig = maemotarget()->deviceConfigurationsModel()->defaultDeviceConfig();
120     m_needsInstall = false;
121     m_sysrootInstaller = new QProcess(this);
122     connect(m_sysrootInstaller, SIGNAL(finished(int,QProcess::ExitStatus)),
123         this, SLOT(handleSysrootInstallerFinished()));
124     connect(m_sysrootInstaller, SIGNAL(readyReadStandardOutput()), this,
125         SLOT(handleSysrootInstallerOutput()));
126     connect(m_sysrootInstaller, SIGNAL(readyReadStandardError()), this,
127         SLOT(handleSysrootInstallerErrorOutput()));
128     m_mounter = new MaemoRemoteMounter(this);
129     connect(m_mounter, SIGNAL(mounted()), this, SLOT(handleMounted()));
130     connect(m_mounter, SIGNAL(unmounted()), this, SLOT(handleUnmounted()));
131     connect(m_mounter, SIGNAL(error(QString)), this,
132         SLOT(handleMountError(QString)));
133     connect(m_mounter, SIGNAL(reportProgress(QString)), this,
134         SLOT(handleProgressReport(QString)));
135     connect(m_mounter, SIGNAL(debugOutput(QString)), this,
136         SLOT(handleMountDebugOutput(QString)));
137     m_portsGatherer = new MaemoUsedPortsGatherer(this);
138     connect(m_portsGatherer, SIGNAL(error(QString)), this,
139         SLOT(handlePortsGathererError(QString)));
140     connect(m_portsGatherer, SIGNAL(portListReady()), this,
141         SLOT(handlePortListReady()));
142     connect(maemotarget()->deviceConfigurationsModel(), SIGNAL(updated()),
143         SLOT(handleDeviceConfigurationsUpdated()));
144 }
145
146 bool MaemoDeployStep::init()
147 {
148     return true;
149 }
150
151 void MaemoDeployStep::run(QFutureInterface<bool> &fi)
152 {
153     // Move to GUI thread for connection sharing with run control.
154     QTimer::singleShot(0, this, SLOT(start()));
155
156     MaemoDeployEventHandler eventHandler(this, fi);
157 }
158
159 BuildStepConfigWidget *MaemoDeployStep::createConfigWidget()
160 {
161     return new MaemoDeployStepWidget(this);
162 }
163
164 QVariantMap MaemoDeployStep::toMap() const
165 {
166     QVariantMap map(BuildStep::toMap());
167     addDeployTimesToMap(map);
168     map.insert(DeployToSysrootKey, m_deployToSysroot);
169     map.insert(DeviceIdKey,
170         MaemoDeviceConfigurations::instance()->internalId(m_deviceConfig));
171     return map;
172 }
173
174 void MaemoDeployStep::addDeployTimesToMap(QVariantMap &map) const
175 {
176     QVariantList hostList;
177     QVariantList fileList;
178     QVariantList remotePathList;
179     QVariantList timeList;
180     typedef QHash<DeployablePerHost, QDateTime>::ConstIterator DepIt;
181     for (DepIt it = m_lastDeployed.begin(); it != m_lastDeployed.end(); ++it) {
182         fileList << it.key().first.localFilePath;
183         remotePathList << it.key().first.remoteDir;
184         hostList << it.key().second;
185         timeList << it.value();
186     }
187     map.insert(LastDeployedHostsKey, hostList);
188     map.insert(LastDeployedFilesKey, fileList);
189     map.insert(LastDeployedRemotePathsKey, remotePathList);
190     map.insert(LastDeployedTimesKey, timeList);
191 }
192
193 bool MaemoDeployStep::fromMap(const QVariantMap &map)
194 {
195     if (!BuildStep::fromMap(map))
196         return false;
197     getDeployTimesFromMap(map);
198     setDeviceConfig(map.value(DeviceIdKey, MaemoDeviceConfig::InvalidId).toULongLong());
199     m_deployToSysroot = map.value(DeployToSysrootKey, true).toBool();
200     return true;
201 }
202
203 void MaemoDeployStep::getDeployTimesFromMap(const QVariantMap &map)
204 {
205     const QVariantList &hostList = map.value(LastDeployedHostsKey).toList();
206     const QVariantList &fileList = map.value(LastDeployedFilesKey).toList();
207     const QVariantList &remotePathList
208         = map.value(LastDeployedRemotePathsKey).toList();
209     const QVariantList &timeList = map.value(LastDeployedTimesKey).toList();
210     const int elemCount
211         = qMin(qMin(hostList.size(), fileList.size()),
212             qMin(remotePathList.size(), timeList.size()));
213     for (int i = 0; i < elemCount; ++i) {
214         const MaemoDeployable d(fileList.at(i).toString(),
215             remotePathList.at(i).toString());
216         m_lastDeployed.insert(DeployablePerHost(d, hostList.at(i).toString()),
217             timeList.at(i).toDateTime());
218     }
219 }
220
221 const MaemoPackageCreationStep *MaemoDeployStep::packagingStep() const
222 {
223     const MaemoPackageCreationStep * const step
224         = MaemoGlobal::buildStep<MaemoPackageCreationStep>(target()->activeDeployConfiguration());
225     Q_ASSERT_X(step, Q_FUNC_INFO,
226         "Impossible: Maemo build configuration without packaging step.");
227     return step;
228 }
229
230 void MaemoDeployStep::raiseError(const QString &errorString)
231 {
232     emit addTask(Task(Task::Error, errorString, QString(), -1,
233         ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
234     m_hasError = true;
235     emit error();
236 }
237
238 void MaemoDeployStep::writeOutput(const QString &text, OutputFormat format)
239 {
240     emit addOutput(text, format);
241 }
242
243 void MaemoDeployStep::stop()
244 {
245     if (m_state == StopRequested || m_state == Inactive)
246         return;
247
248     const State oldState = m_state;
249     setState(StopRequested);
250     switch (oldState) {
251     case InstallingToSysroot:
252         if (m_needsInstall)
253             m_sysrootInstaller->terminate();
254         break;
255     case Connecting:
256         m_connection->disconnectFromHost();
257         setState(Inactive);
258         break;
259     case InstallingToDevice:
260     case CopyingFile: {
261         const QByteArray programToKill = oldState == CopyingFile
262             ? " cp " : "dpkg";
263         const QByteArray killCommand
264             = MaemoGlobal::remoteSudo().toUtf8() + " pkill -f ";
265         const QByteArray cmdLine = killCommand + programToKill + "; sleep 1; "
266             + killCommand + "-9 " + programToKill;
267         SshRemoteProcess::Ptr killProc
268             = m_connection->createRemoteProcess(cmdLine);
269         killProc->start();
270         break;
271     }
272     case Uploading:
273         m_uploader->closeChannel();
274         break;
275     case UnmountingOldDirs:
276     case UnmountingCurrentDirs:
277     case UnmountingCurrentMounts:
278     case GatheringPorts:
279     case Mounting:
280     case InitializingSftp:
281         break; // Nothing to do here.
282     default:
283         Q_ASSERT_X(false, Q_FUNC_INFO, "Missing switch case.");
284     }
285 }
286
287 QString MaemoDeployStep::uploadDir() const
288 {
289     return MaemoGlobal::homeDirOnDevice(m_connection->connectionParameters().userName);
290 }
291
292 bool MaemoDeployStep::currentlyNeedsDeployment(const QString &host,
293     const MaemoDeployable &deployable) const
294 {
295     const QDateTime &lastDeployed
296         = m_lastDeployed.value(DeployablePerHost(deployable, host));
297     return !lastDeployed.isValid()
298         || QFileInfo(deployable.localFilePath).lastModified() > lastDeployed;
299 }
300
301 void MaemoDeployStep::setDeployed(const QString &host,
302     const MaemoDeployable &deployable)
303 {
304     m_lastDeployed.insert(DeployablePerHost(deployable, host),
305         QDateTime::currentDateTime());
306 }
307
308 void MaemoDeployStep::handleDeviceConfigurationsUpdated()
309 {
310     setDeviceConfig(MaemoDeviceConfigurations::instance()->internalId(m_deviceConfig));
311 }
312
313 void MaemoDeployStep::setDeviceConfig(MaemoDeviceConfig::Id internalId)
314 {
315     m_deviceConfig = maemotarget()->deviceConfigurationsModel()->find(internalId);
316     emit deviceConfigChanged();
317 }
318
319 void MaemoDeployStep::setDeviceConfig(int i)
320 {
321     m_deviceConfig = maemotarget()->deviceConfigurationsModel()->deviceAt(i);
322     emit deviceConfigChanged();
323 }
324
325 void MaemoDeployStep::start()
326 {
327     if (m_state != Inactive) {
328         raiseError(tr("Cannot deploy: Still cleaning up from last time."));
329         emit done();
330         return;
331     }
332
333     m_cachedDeviceConfig = m_deviceConfig;
334     if (!m_cachedDeviceConfig) {
335         raiseError(tr("Deployment failed: No valid device set."));
336         emit done();
337         return;
338     }
339
340     Q_ASSERT(!m_currentDeviceDeployAction);
341     Q_ASSERT(!m_needsInstall);
342     Q_ASSERT(m_filesToCopy.isEmpty());
343     m_installerStderr.clear();
344     m_hasError = false;
345     const MaemoPackageCreationStep * const pStep = packagingStep();
346     const QString hostName = m_cachedDeviceConfig->sshParameters().host;
347     if (pStep->isPackagingEnabled()) {
348         const MaemoDeployable d(pStep->packageFilePath(), QString());
349         if (currentlyNeedsDeployment(hostName, d))
350             m_needsInstall = true;
351     } else {
352         const int deployableCount = m_deployables->deployableCount();
353         for (int i = 0; i < deployableCount; ++i) {
354             const MaemoDeployable &d = m_deployables->deployableAt(i);
355             if (currentlyNeedsDeployment(hostName, d)
356                 || QFileInfo(d.localFilePath).isDir()) {
357                 m_filesToCopy << d;
358             }
359         }
360     }
361
362     if (m_needsInstall || !m_filesToCopy.isEmpty()) {
363         if (m_cachedDeviceConfig->type() == MaemoDeviceConfig::Emulator
364                 && !MaemoQemuManager::instance().qemuIsRunning()) {
365             MaemoQemuManager::instance().startRuntime();
366             raiseError(tr("Deployment failed: Qemu was not running. "
367                 "It has now been started up for you, but it will take "
368                 "a bit of time until it is ready."));
369             m_needsInstall = false;
370             m_filesToCopy.clear();
371             emit done();
372             return;
373         }
374
375         if (m_deployToSysroot)
376             installToSysroot();
377         else
378             connectToDevice();
379     } else {
380         writeOutput(tr("All files up to date, no installation necessary."));
381         emit done();
382     }
383 }
384
385 void MaemoDeployStep::handleConnectionFailure()
386 {
387     if (m_state == Inactive)
388         return;
389
390     const QString errorMsg = m_state == Connecting
391         ? MaemoGlobal::failedToConnectToServerMessage(m_connection, m_cachedDeviceConfig)
392         : tr("Connection error: %1").arg(m_connection->errorString());
393     raiseError(errorMsg);
394     setState(Inactive);
395 }
396
397 void MaemoDeployStep::handleSftpChannelInitialized()
398 {
399     ASSERT_STATE(QList<State>() << InitializingSftp << StopRequested);
400
401     switch (m_state) {
402     case InitializingSftp: {
403         const QString filePath = packagingStep()->packageFilePath();
404         const QString filePathNative = QDir::toNativeSeparators(filePath);
405         const QString fileName = QFileInfo(filePath).fileName();
406         const QString remoteFilePath = uploadDir() + QLatin1Char('/') + fileName;
407         const SftpJobId job = m_uploader->uploadFile(filePath,
408             remoteFilePath, SftpOverwriteExisting);
409         if (job == SftpInvalidJob) {
410             raiseError(tr("Upload failed: Could not open file '%1'")
411                 .arg(filePathNative));
412             setState(Inactive);
413         } else {
414             setState(Uploading);
415             writeOutput(tr("Started uploading file '%1'.").arg(filePathNative));
416         }
417         break;
418     }
419     case StopRequested:
420         setState(Inactive);
421         break;
422     default:
423         break;
424     }
425 }
426
427 void MaemoDeployStep::handleSftpChannelInitializationFailed(const QString &error)
428 {
429     ASSERT_STATE(QList<State>() << InitializingSftp << StopRequested);
430
431     switch (m_state) {
432     case InitializingSftp:
433     case StopRequested:
434         raiseError(tr("Could not set up SFTP connection: %1").arg(error));
435         setState(Inactive);
436         break;
437     default:
438         break;
439     }
440 }
441
442 void MaemoDeployStep::handleSftpJobFinished(Utils::SftpJobId,
443     const QString &error)
444 {
445     ASSERT_STATE(QList<State>() << Uploading << StopRequested);
446
447     const QString filePathNative
448         = QDir::toNativeSeparators(packagingStep()->packageFilePath());
449     if (!error.isEmpty()) {
450         raiseError(tr("Failed to upload file %1: %2")
451             .arg(filePathNative, error));
452         if (m_state == Uploading)
453             setState(Inactive);
454     } else if (m_state == Uploading) {
455         writeOutput(tr("Successfully uploaded file '%1'.")
456             .arg(filePathNative));
457         const QString remoteFilePath
458             = uploadDir() + QLatin1Char('/') + QFileInfo(filePathNative).fileName();
459         runPackageInstaller(remoteFilePath);
460     }
461 }
462
463 void MaemoDeployStep::handleSftpChannelClosed()
464 {
465     ASSERT_STATE(StopRequested);
466     setState(Inactive);
467 }
468
469 void MaemoDeployStep::handleMounted()
470 {
471     ASSERT_STATE(QList<State>() << Mounting << StopRequested << Inactive);
472
473     switch (m_state) {
474     case Mounting:
475         if (m_needsInstall) {
476             const QString remoteFilePath = deployMountPoint() + QLatin1Char('/')
477                 + QFileInfo(packagingStep()->packageFilePath()).fileName();
478             runPackageInstaller(remoteFilePath);
479         } else {
480             setState(CopyingFile);
481             copyNextFileToDevice();
482         }
483         break;
484     case StopRequested:
485         unmount();
486         break;
487     case Inactive:
488     default:
489         break;
490     }
491 }
492
493 void MaemoDeployStep::handleUnmounted()
494 {
495     ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
496         << UnmountingCurrentMounts << StopRequested << Inactive);
497
498     switch (m_state) {
499     case StopRequested:
500         m_mounter->resetMountSpecifications();
501         setState(Inactive);
502         break;
503     case UnmountingOldDirs:
504         if (maemotarget()->allowsRemoteMounts())
505             setupMount();
506         else
507             prepareSftpConnection();
508         break;
509     case UnmountingCurrentDirs:
510         setState(GatheringPorts);
511         m_portsGatherer->start(m_connection, freePorts());
512         break;
513     case UnmountingCurrentMounts:
514         if (m_hasError)
515             writeOutput(tr("Deployment failed."), ErrorMessageOutput);
516         else
517             writeOutput(tr("Deployment finished."));
518         setState(Inactive);
519         break;
520     case Inactive:
521     default:
522         break;
523     }
524 }
525
526 void MaemoDeployStep::handleMountError(const QString &errorMsg)
527 {
528     ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
529         << UnmountingCurrentMounts << Mounting << StopRequested << Inactive);
530
531     switch (m_state) {
532     case UnmountingOldDirs:
533     case UnmountingCurrentDirs:
534     case UnmountingCurrentMounts:
535     case Mounting:
536     case StopRequested:
537         raiseError(errorMsg);
538         setState(Inactive);
539         break;
540     case Inactive:
541     default:
542         break;
543     }
544 }
545
546 void MaemoDeployStep::handleMountDebugOutput(const QString &output)
547 {
548     ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
549         << UnmountingCurrentMounts << Mounting << StopRequested << Inactive);
550
551     switch (m_state) {
552     case UnmountingOldDirs:
553     case UnmountingCurrentDirs:
554     case UnmountingCurrentMounts:
555     case Mounting:
556     case StopRequested:
557         writeOutput(output, ErrorOutput);
558         break;
559     case Inactive:
560     default:
561         break;
562     }
563 }
564
565 void MaemoDeployStep::setupMount()
566 {
567     ASSERT_STATE(UnmountingOldDirs);
568     setState(UnmountingCurrentDirs);
569
570     Q_ASSERT(m_needsInstall || !m_filesToCopy.isEmpty());
571     m_mounter->resetMountSpecifications();
572     m_mounter->setBuildConfiguration(qt4BuildConfiguration());
573     if (m_needsInstall) {
574         const QString localDir
575             = QFileInfo(packagingStep()->packageFilePath()).absolutePath();
576         const MaemoMountSpecification mountSpec(localDir, deployMountPoint());
577         m_mounter->addMountSpecification(mountSpec, true);
578     } else {
579 #ifdef Q_OS_WIN
580         bool drivesToMount[26];
581         qFill(drivesToMount, drivesToMount + sizeof drivesToMount / sizeof drivesToMount[0], false);
582         for (int i = 0; i < m_filesToCopy.count(); ++i) {
583             const QString localDir
584                 = QFileInfo(m_filesToCopy.at(i).localFilePath).canonicalPath();
585             const char driveLetter = localDir.at(0).toLower().toLatin1();
586             if (driveLetter < 'a' || driveLetter > 'z') {
587                 qWarning("Weird: drive letter is '%c'.", driveLetter);
588                 continue;
589             }
590
591             const int index = driveLetter - 'a';
592             if (drivesToMount[index])
593                 continue;
594
595             const QString mountPoint = deployMountPoint() + QLatin1Char('/')
596                 + QLatin1Char(driveLetter);
597             const MaemoMountSpecification mountSpec(localDir.left(3),
598                 mountPoint);
599             m_mounter->addMountSpecification(mountSpec, true);
600             drivesToMount[index] = true;
601         }
602 #else
603         m_mounter->addMountSpecification(MaemoMountSpecification(QLatin1String("/"),
604             deployMountPoint()), true);
605 #endif
606     }
607     unmount();
608 }
609
610 void MaemoDeployStep::prepareSftpConnection()
611 {
612     setState(InitializingSftp);
613     m_uploader = m_connection->createSftpChannel();
614     connect(m_uploader.data(), SIGNAL(initialized()), this,
615         SLOT(handleSftpChannelInitialized()));
616     connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this,
617         SLOT(handleSftpChannelInitializationFailed(QString)));
618     connect(m_uploader.data(), SIGNAL(finished(Utils::SftpJobId, QString)),
619         this, SLOT(handleSftpJobFinished(Utils::SftpJobId, QString)));
620     connect(m_uploader.data(), SIGNAL(closed()), this,
621         SLOT(handleSftpChannelClosed()));
622     m_uploader->initialize();
623 }
624
625 void MaemoDeployStep::installToSysroot()
626 {
627     ASSERT_STATE(Inactive);
628     setState(InstallingToSysroot);
629
630     if (m_needsInstall) {
631         writeOutput(tr("Installing package to sysroot ..."));
632         const QtVersion * const qtVersion = qt4BuildConfiguration()->qtVersion();
633         const QString command = QLatin1String(
634             packagingStep()->debBasedMaemoTarget() ? "xdpkg" : "xrpm");
635         QStringList args = QStringList() << command << QLatin1String("-i");
636         if (packagingStep()->debBasedMaemoTarget())
637             args << QLatin1String("--no-force-downgrade");
638         args << packagingStep()->packageFilePath();
639         MaemoGlobal::callMadAdmin(*m_sysrootInstaller, args, qtVersion, true);
640         if (!m_sysrootInstaller->waitForStarted()) {
641             writeOutput(tr("Installation to sysroot failed, continuing anyway."),
642                 ErrorMessageOutput);
643             connectToDevice();
644         }
645     } else {
646         writeOutput(tr("Copying files to sysroot ..."));
647         Q_ASSERT(!m_filesToCopy.isEmpty());
648         QDir sysRootDir(toolChain()->sysroot());
649         foreach (const MaemoDeployable &d, m_filesToCopy) {
650             const QLatin1Char sep('/');
651             const QString targetFilePath = toolChain()->sysroot() + sep
652                 + d.remoteDir + sep + QFileInfo(d.localFilePath).fileName();
653             sysRootDir.mkpath(d.remoteDir.mid(1));
654             QFile::remove(targetFilePath);
655             QString dummy;
656             MaemoGlobal::removeRecursively(targetFilePath, dummy);
657             if (!MaemoGlobal::copyRecursively(d.localFilePath, targetFilePath)) {
658                 writeOutput(tr("Sysroot installation failed: "
659                     "Could not copy '%1' to '%2'. Continuing anyway.")
660                     .arg(QDir::toNativeSeparators(d.localFilePath),
661                          QDir::toNativeSeparators(targetFilePath)),
662                     ErrorMessageOutput);
663             }
664             QCoreApplication::processEvents();
665             if (m_state == StopRequested) {
666                 setState(Inactive);
667                 return;
668             }
669         }
670         connectToDevice();
671     }
672 }
673
674 void MaemoDeployStep::handleSysrootInstallerFinished()
675 {
676     ASSERT_STATE(QList<State>() << InstallingToSysroot << StopRequested);
677
678     if (m_state == StopRequested) {
679         setState(Inactive);
680         return;
681     }
682
683     if (m_sysrootInstaller->error() != QProcess::UnknownError
684         || m_sysrootInstaller->exitCode() != 0) {
685         writeOutput(tr("Installation to sysroot failed, continuing anyway."),
686             ErrorMessageOutput);
687     }
688     connectToDevice();
689 }
690
691 void MaemoDeployStep::connectToDevice()
692 {
693     ASSERT_STATE(QList<State>() << Inactive << InstallingToSysroot);
694     setState(Connecting);
695
696     const bool canReUse = m_connection
697         && m_connection->state() == SshConnection::Connected
698         && m_connection->connectionParameters() == m_cachedDeviceConfig->sshParameters();
699     if (!canReUse)
700         m_connection = SshConnection::create();
701     connect(m_connection.data(), SIGNAL(connected()), this,
702         SLOT(handleConnected()));
703     connect(m_connection.data(), SIGNAL(error(Utils::SshError)), this,
704         SLOT(handleConnectionFailure()));
705     if (canReUse) {
706         handleConnected();
707     } else {
708         writeOutput(tr("Connecting to device..."));
709         m_connection->connectToHost(m_cachedDeviceConfig->sshParameters());
710     }
711 }
712
713 void MaemoDeployStep::handleConnected()
714 {
715     ASSERT_STATE(QList<State>() << Connecting << StopRequested);
716
717     if (m_state == Connecting)
718         unmountOldDirs();
719 }
720
721 void MaemoDeployStep::unmountOldDirs()
722 {
723     setState(UnmountingOldDirs);
724     m_mounter->setConnection(m_connection);
725     unmount();
726 }
727
728 void MaemoDeployStep::runPackageInstaller(const QString &packageFilePath)
729 {
730     ASSERT_STATE(QList<State>() << Mounting << Uploading);
731     const bool removeAfterInstall = m_state == Uploading;
732     setState(InstallingToDevice);
733
734     writeOutput(tr("Installing package to device..."));
735     const QByteArray installCommand = packagingStep()->debBasedMaemoTarget()
736         ? "dpkg -i --no-force-downgrade"
737         : "rpm -U --replacepkgs --replacefiles";
738     QByteArray cmd = MaemoGlobal::remoteSudo().toUtf8() + ' '
739         + installCommand + ' ' + packageFilePath.toUtf8();
740     if (removeAfterInstall)
741         cmd += " && (rm " + packageFilePath.toUtf8() + " || :)";
742     m_deviceInstaller = m_connection->createRemoteProcess(cmd);
743     connect(m_deviceInstaller.data(), SIGNAL(closed(int)), this,
744         SLOT(handleInstallationFinished(int)));
745     connect(m_deviceInstaller.data(), SIGNAL(outputAvailable(QByteArray)),
746         this, SLOT(handleDeviceInstallerOutput(QByteArray)));
747     connect(m_deviceInstaller.data(),
748         SIGNAL(errorOutputAvailable(QByteArray)), this,
749         SLOT(handleDeviceInstallerErrorOutput(QByteArray)));
750     m_deviceInstaller->start();
751 }
752
753 void MaemoDeployStep::handleProgressReport(const QString &progressMsg)
754 {
755     ASSERT_STATE(QList<State>() << UnmountingOldDirs << UnmountingCurrentDirs
756         << UnmountingCurrentMounts << Mounting << StopRequested << Inactive);
757
758     switch (m_state) {
759     case UnmountingOldDirs:
760     case UnmountingCurrentDirs:
761     case UnmountingCurrentMounts:
762     case Mounting:
763     case StopRequested:
764         writeOutput(progressMsg);
765         break;
766     case Inactive:
767     default:
768         break;
769     }
770 }
771
772 void MaemoDeployStep::copyNextFileToDevice()
773 {
774     ASSERT_STATE(CopyingFile);
775     Q_ASSERT(!m_filesToCopy.isEmpty());
776     Q_ASSERT(!m_currentDeviceDeployAction);
777     const MaemoDeployable d = m_filesToCopy.takeFirst();
778     QString sourceFilePath = deployMountPoint();
779 #ifdef Q_OS_WIN
780     const QString localFilePath = QDir::fromNativeSeparators(d.localFilePath);
781     sourceFilePath += QLatin1Char('/') + localFilePath.at(0).toLower()
782         + localFilePath.mid(2);
783 #else
784     sourceFilePath += d.localFilePath;
785 #endif
786
787     QString command = QString::fromLatin1("%1 mkdir -p %3 && %1 cp -r %2 %3")
788         .arg(MaemoGlobal::remoteSudo(), sourceFilePath, d.remoteDir);
789     SshRemoteProcess::Ptr copyProcess
790         = m_connection->createRemoteProcess(command.toUtf8());
791     connect(copyProcess.data(), SIGNAL(errorOutputAvailable(QByteArray)),
792         this, SLOT(handleDeviceInstallerErrorOutput(QByteArray)));
793     connect(copyProcess.data(), SIGNAL(closed(int)), this,
794         SLOT(handleCopyProcessFinished(int)));
795     m_currentDeviceDeployAction.reset(new DeviceDeployAction(d, copyProcess));
796     writeOutput(tr("Copying file '%1' to path '%2' on the device...")
797         .arg(d.localFilePath, d.remoteDir));
798     copyProcess->start();
799 }
800
801 void MaemoDeployStep::handleCopyProcessFinished(int exitStatus)
802 {
803     ASSERT_STATE(QList<State>() << CopyingFile << StopRequested << Inactive);
804
805     switch (m_state) {
806     case CopyingFile: {
807         Q_ASSERT(m_currentDeviceDeployAction);
808         const QString localFilePath
809             = m_currentDeviceDeployAction->first.localFilePath;
810         if (exitStatus != SshRemoteProcess::ExitedNormally
811                 || m_currentDeviceDeployAction->second->exitCode() != 0) {
812             raiseError(tr("Copying file '%1' failed.").arg(localFilePath));
813             m_currentDeviceDeployAction.reset(0);
814             setState(UnmountingCurrentMounts);
815             unmount();
816         } else {
817             writeOutput(tr("Successfully copied file '%1'.").arg(localFilePath));
818             setDeployed(m_connection->connectionParameters().host,
819                 m_currentDeviceDeployAction->first);
820             m_currentDeviceDeployAction.reset(0);
821             if (m_filesToCopy.isEmpty()) {
822                 writeOutput(tr("All files copied."));
823                 setState(UnmountingCurrentMounts);
824                 unmount();
825             } else {
826                 copyNextFileToDevice();
827             }
828         }
829         break;
830     }
831     case StopRequested:
832         unmount();
833         break;
834     case Inactive:
835     default:
836         break;
837     }
838 }
839
840 QString MaemoDeployStep::deployMountPoint() const
841 {
842     return MaemoGlobal::homeDirOnDevice(m_cachedDeviceConfig->sshParameters().userName)
843         + QLatin1String("/deployMountPoint_") + packagingStep()->projectName();
844 }
845
846 const MaemoToolChain *MaemoDeployStep::toolChain() const
847 {
848     return static_cast<MaemoToolChain *>(qt4BuildConfiguration()->toolChain());
849 }
850
851 const AbstractQt4MaemoTarget *MaemoDeployStep::maemotarget() const
852 {
853     return static_cast<AbstractQt4MaemoTarget *>(qt4BuildConfiguration()->target());
854 }
855
856 void MaemoDeployStep::handleSysrootInstallerOutput()
857 {
858     ASSERT_STATE(QList<State>() << InstallingToSysroot << StopRequested);
859
860     switch (m_state) {
861     case InstallingToSysroot:
862     case StopRequested:
863         writeOutput(QString::fromLocal8Bit(m_sysrootInstaller->readAllStandardOutput()),
864             NormalOutput);
865         break;
866     default:
867         break;
868     }
869 }
870
871 void MaemoDeployStep::handleSysrootInstallerErrorOutput()
872 {
873     ASSERT_STATE(QList<State>() << InstallingToSysroot << StopRequested);
874
875     switch (m_state) {
876     case InstallingToSysroot:
877     case StopRequested:
878         writeOutput(QString::fromLocal8Bit(m_sysrootInstaller->readAllStandardError()),
879             BuildStep::ErrorOutput);
880         break;
881     default:
882         break;
883     }
884 }
885
886 void MaemoDeployStep::handleInstallationFinished(int exitStatus)
887 {
888     ASSERT_STATE(QList<State>() << InstallingToDevice << StopRequested
889         << Inactive);
890
891     switch (m_state) {
892     case InstallingToDevice:
893         if (exitStatus != SshRemoteProcess::ExitedNormally
894             || m_deviceInstaller->exitCode() != 0) {
895             raiseError(tr("Installing package failed."));
896         } else if (m_installerStderr.contains("Will not downgrade")) {
897             raiseError(tr("Installation failed: "
898                 "You tried to downgrade a package, which is not allowed."));
899         } else {
900             m_needsInstall = false;
901             setDeployed(m_connection->connectionParameters().host,
902                 MaemoDeployable(packagingStep()->packageFilePath(), QString()));
903             writeOutput(tr("Package installed."));
904         }
905         setState(UnmountingCurrentMounts);
906         unmount();
907         break;
908     case StopRequested:
909         unmount();
910         break;
911     case Inactive:
912     default:
913         break;
914     }
915 }
916
917 void MaemoDeployStep::handlePortsGathererError(const QString &errorMsg)
918 {
919     ASSERT_STATE(QList<State>() << GatheringPorts << StopRequested << Inactive);
920
921     if (m_state != Inactive) {
922         raiseError(errorMsg);
923         setState(Inactive);
924     }
925 }
926
927 void MaemoDeployStep::handlePortListReady()
928 {
929     ASSERT_STATE(QList<State>() << GatheringPorts << StopRequested);
930
931     if (m_state == GatheringPorts) {
932         setState(Mounting);
933         m_freePorts = freePorts();
934         m_mounter->mount(&m_freePorts, m_portsGatherer);
935     } else {
936         setState(Inactive);
937     }
938 }
939
940 void MaemoDeployStep::setState(State newState)
941 {
942     if (newState == m_state)
943         return;
944     m_state = newState;
945     if (m_state == Inactive) {
946         m_needsInstall = false;
947         m_filesToCopy.clear();
948         m_currentDeviceDeployAction.reset(0);
949         if (m_connection)
950             disconnect(m_connection.data(), 0, this, 0);
951         if (m_uploader) {
952             disconnect(m_uploader.data(), 0, this, 0);
953             m_uploader->closeChannel();
954         }
955         if (m_deviceInstaller)
956             disconnect(m_deviceInstaller.data(), 0, this, 0);
957         emit done();
958     }
959 }
960
961 void MaemoDeployStep::unmount()
962 {
963     if (m_mounter->hasValidMountSpecifications())
964         m_mounter->unmount();
965     else
966         handleUnmounted();
967 }
968
969 void MaemoDeployStep::handleDeviceInstallerOutput(const QByteArray &output)
970 {
971     ASSERT_STATE(QList<State>() << InstallingToDevice << StopRequested);
972
973     switch (m_state) {
974     case InstallingToDevice:
975     case StopRequested:
976         writeOutput(QString::fromUtf8(output), NormalOutput);
977         break;
978     default:
979         break;
980     }
981 }
982
983 void MaemoDeployStep::handleDeviceInstallerErrorOutput(const QByteArray &output)
984 {
985     ASSERT_STATE(QList<State>() << InstallingToDevice << CopyingFile
986         << StopRequested);
987
988     switch (m_state) {
989     case InstallingToDevice:
990     case CopyingFile:
991     case StopRequested:
992         m_installerStderr += output;
993         writeOutput(QString::fromUtf8(output), ErrorOutput);
994         break;
995     default:
996         break;
997     }
998 }
999
1000 MaemoPortList MaemoDeployStep::freePorts() const
1001 {
1002     const Qt4BuildConfiguration * const qt4bc = qt4BuildConfiguration();
1003     const MaemoDeviceConfig::ConstPtr &devConf
1004         = m_cachedDeviceConfig ? m_cachedDeviceConfig : m_deviceConfig;
1005     if (!devConf)
1006         return MaemoPortList();
1007     if (devConf->type() == MaemoDeviceConfig::Emulator && qt4bc) {
1008         MaemoQemuRuntime rt;
1009         const int id = qt4bc->qtVersion()->uniqueId();
1010         if (MaemoQemuManager::instance().runtimeForQtVersion(id, &rt))
1011             return rt.m_freePorts;
1012     }
1013     return devConf->freePorts();
1014 }
1015
1016 const Qt4BuildConfiguration *MaemoDeployStep::qt4BuildConfiguration() const
1017 {
1018     return static_cast<Qt4BuildConfiguration *>(buildConfiguration());
1019 }
1020
1021
1022 MaemoDeployEventHandler::MaemoDeployEventHandler(MaemoDeployStep *deployStep,
1023     QFutureInterface<bool> &future)
1024     : m_deployStep(deployStep), m_future(future), m_eventLoop(new QEventLoop),
1025       m_error(false)
1026 {
1027     connect(m_deployStep, SIGNAL(done()), this, SLOT(handleDeployingDone()));
1028     connect(m_deployStep, SIGNAL(error()), this, SLOT(handleDeployingFailed()));
1029     QTimer cancelChecker;
1030     connect(&cancelChecker, SIGNAL(timeout()), this, SLOT(checkForCanceled()));
1031     cancelChecker.start(500);
1032     future.reportResult(m_eventLoop->exec() == 0);
1033 }
1034
1035 void MaemoDeployEventHandler::handleDeployingDone()
1036 {
1037     m_eventLoop->exit(m_error ? 1 : 0);
1038 }
1039
1040 void MaemoDeployEventHandler::handleDeployingFailed()
1041 {
1042     m_error = true;
1043 }
1044
1045 void MaemoDeployEventHandler::checkForCanceled()
1046 {
1047     if (!m_error && m_future.isCanceled()) {
1048         QMetaObject::invokeMethod(m_deployStep, "stop");
1049         m_error = true;
1050         handleDeployingDone();
1051     }
1052 }
1053
1054 } // namespace Internal
1055 } // namespace Qt4ProjectManager