1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
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 "abstractremotelinuxdeployservice.h"
27 #include "deploymenttimeinfo.h"
28 
29 #include <projectexplorer/deployablefile.h>
30 #include <projectexplorer/kitinformation.h>
31 #include <projectexplorer/target.h>
32 
33 #include <ssh/sshconnection.h>
34 #include <ssh/sshconnectionmanager.h>
35 
36 #include <utils/qtcassert.h>
37 
38 #include <QDateTime>
39 #include <QFileInfo>
40 #include <QPointer>
41 #include <QString>
42 
43 using namespace ProjectExplorer;
44 using namespace QSsh;
45 
46 namespace RemoteLinux {
47 namespace Internal {
48 
49 namespace {
50 enum State { Inactive, SettingUpDevice, Connecting, Deploying };
51 } // anonymous namespace
52 
53 class AbstractRemoteLinuxDeployServicePrivate
54 {
55 public:
56     IDevice::ConstPtr deviceConfiguration;
57     QPointer<Target> target;
58 
59     DeploymentTimeInfo deployTimes;
60     SshConnection *connection = nullptr;
61     State state = Inactive;
62     bool stopRequested = false;
63 };
64 } // namespace Internal
65 
66 using namespace Internal;
67 
AbstractRemoteLinuxDeployService(QObject * parent)68 AbstractRemoteLinuxDeployService::AbstractRemoteLinuxDeployService(QObject *parent)
69     : QObject(parent), d(new AbstractRemoteLinuxDeployServicePrivate)
70 {
71 }
72 
~AbstractRemoteLinuxDeployService()73 AbstractRemoteLinuxDeployService::~AbstractRemoteLinuxDeployService()
74 {
75     delete d;
76 }
77 
target() const78 const Target *AbstractRemoteLinuxDeployService::target() const
79 {
80     return d->target;
81 }
82 
profile() const83 const Kit *AbstractRemoteLinuxDeployService::profile() const
84 {
85     return d->target ? d->target->kit() : nullptr;
86 }
87 
deviceConfiguration() const88 IDevice::ConstPtr AbstractRemoteLinuxDeployService::deviceConfiguration() const
89 {
90     return d->deviceConfiguration;
91 }
92 
connection() const93 SshConnection *AbstractRemoteLinuxDeployService::connection() const
94 {
95     return d->connection;
96 }
97 
saveDeploymentTimeStamp(const DeployableFile & deployableFile,const QDateTime & remoteTimestamp)98 void AbstractRemoteLinuxDeployService::saveDeploymentTimeStamp(const DeployableFile &deployableFile,
99                                                                const QDateTime &remoteTimestamp)
100 {
101     d->deployTimes.saveDeploymentTimeStamp(deployableFile, profile(), remoteTimestamp);
102 }
103 
hasLocalFileChanged(const DeployableFile & deployableFile) const104 bool AbstractRemoteLinuxDeployService::hasLocalFileChanged(
105         const DeployableFile &deployableFile) const
106 {
107     return d->deployTimes.hasLocalFileChanged(deployableFile, profile());
108 }
109 
hasRemoteFileChanged(const DeployableFile & deployableFile,const QDateTime & remoteTimestamp) const110 bool AbstractRemoteLinuxDeployService::hasRemoteFileChanged(
111         const DeployableFile &deployableFile, const QDateTime &remoteTimestamp) const
112 {
113     return d->deployTimes.hasRemoteFileChanged(deployableFile, profile(), remoteTimestamp);
114 }
115 
setTarget(Target * target)116 void AbstractRemoteLinuxDeployService::setTarget(Target *target)
117 {
118     d->target = target;
119     d->deviceConfiguration = DeviceKitAspect::device(profile());
120 }
121 
setDevice(const IDevice::ConstPtr & device)122 void AbstractRemoteLinuxDeployService::setDevice(const IDevice::ConstPtr &device)
123 {
124     d->deviceConfiguration = device;
125 }
126 
start()127 void AbstractRemoteLinuxDeployService::start()
128 {
129     QTC_ASSERT(d->state == Inactive, return);
130 
131     const CheckResult check = isDeploymentPossible();
132     if (!check) {
133         emit errorMessage(check.errorMessage());
134         emit finished();
135         return;
136     }
137 
138     if (!isDeploymentNecessary()) {
139         emit progressMessage(tr("No deployment action necessary. Skipping."));
140         emit finished();
141         return;
142     }
143 
144     d->state = SettingUpDevice;
145     doDeviceSetup();
146 }
147 
stop()148 void AbstractRemoteLinuxDeployService::stop()
149 {
150     if (d->stopRequested)
151         return;
152 
153     switch (d->state) {
154     case Inactive:
155         break;
156     case SettingUpDevice:
157         d->stopRequested = true;
158         stopDeviceSetup();
159         break;
160     case Connecting:
161         setFinished();
162         break;
163     case Deploying:
164         d->stopRequested = true;
165         stopDeployment();
166         break;
167     }
168 }
169 
isDeploymentPossible() const170 CheckResult AbstractRemoteLinuxDeployService::isDeploymentPossible() const
171 {
172     if (!deviceConfiguration())
173         return CheckResult::failure(tr("No device configuration set."));
174     return CheckResult::success();
175 }
176 
exportDeployTimes() const177 QVariantMap AbstractRemoteLinuxDeployService::exportDeployTimes() const
178 {
179     return d->deployTimes.exportDeployTimes();
180 }
181 
importDeployTimes(const QVariantMap & map)182 void AbstractRemoteLinuxDeployService::importDeployTimes(const QVariantMap &map)
183 {
184     d->deployTimes.importDeployTimes(map);
185 }
186 
handleDeviceSetupDone(bool success)187 void AbstractRemoteLinuxDeployService::handleDeviceSetupDone(bool success)
188 {
189     QTC_ASSERT(d->state == SettingUpDevice, return);
190 
191     if (!success || d->stopRequested) {
192         setFinished();
193         return;
194     }
195 
196     d->state = Connecting;
197     d->connection = QSsh::acquireConnection(deviceConfiguration()->sshParameters());
198     connect(d->connection, &SshConnection::errorOccurred,
199             this, &AbstractRemoteLinuxDeployService::handleConnectionFailure);
200     if (d->connection->state() == SshConnection::Connected) {
201         handleConnected();
202     } else {
203         connect(d->connection, &SshConnection::connected,
204                 this, &AbstractRemoteLinuxDeployService::handleConnected);
205         emit progressMessage(tr("Connecting to device \"%1\" (%2).")
206                              .arg(deviceConfiguration()->displayName())
207                              .arg(deviceConfiguration()->sshParameters().host()));
208         if (d->connection->state() == SshConnection::Unconnected)
209             d->connection->connectToHost();
210     }
211 }
212 
handleDeploymentDone()213 void AbstractRemoteLinuxDeployService::handleDeploymentDone()
214 {
215     QTC_ASSERT(d->state == Deploying, return);
216 
217     setFinished();
218 }
219 
handleConnected()220 void AbstractRemoteLinuxDeployService::handleConnected()
221 {
222     QTC_ASSERT(d->state == Connecting, return);
223 
224     if (d->stopRequested) {
225         setFinished();
226         return;
227     }
228 
229     d->state = Deploying;
230     doDeploy();
231 }
232 
handleConnectionFailure()233 void AbstractRemoteLinuxDeployService::handleConnectionFailure()
234 {
235     switch (d->state) {
236     case Inactive:
237     case SettingUpDevice:
238         qWarning("%s: Unexpected state %d.", Q_FUNC_INFO, d->state);
239         break;
240     case Connecting: {
241         QString errorMsg = tr("Could not connect to host: %1").arg(d->connection->errorString());
242         errorMsg += QLatin1Char('\n');
243         if (deviceConfiguration()->machineType() == IDevice::Emulator)
244             errorMsg += tr("Did the emulator fail to start?");
245         else
246             errorMsg += tr("Is the device connected and set up for network access?");
247         emit errorMessage(errorMsg);
248         setFinished();
249         break;
250     }
251     case Deploying:
252         emit errorMessage(tr("Connection error: %1").arg(d->connection->errorString()));
253         stopDeployment();
254     }
255 }
256 
setFinished()257 void AbstractRemoteLinuxDeployService::setFinished()
258 {
259     d->state = Inactive;
260     if (d->connection) {
261         disconnect(d->connection, nullptr, this, nullptr);
262         QSsh::releaseConnection(d->connection);
263         d->connection = nullptr;
264     }
265     d->stopRequested = false;
266     emit finished();
267 }
268 
269 } // namespace RemoteLinux
270