1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
35 #include <QtCore/QCoreApplication>
36 #include <QtCore/QDateTime>
37 #include <QtCore/QDir>
38 #include <QtCore/QFile>
39 #include <QtCore/QFileInfo>
43 using namespace Utils;
45 SftpTest::SftpTest(const Parameters ¶ms)
46 : m_parameters(params), m_state(Inactive), m_error(false),
47 m_bigFileUploadJob(SftpInvalidJob),
48 m_bigFileDownloadJob(SftpInvalidJob),
49 m_bigFileRemovalJob(SftpInvalidJob)
60 m_connection = SshConnection::create();
61 connect(m_connection.data(), SIGNAL(connected()), this,
62 SLOT(handleConnected()));
63 connect(m_connection.data(), SIGNAL(error(Utils::SshError)), this,
65 connect(m_connection.data(), SIGNAL(disconnected()), this,
66 SLOT(handleDisconnected()));
67 std::cout << "Connecting to host '"
68 << qPrintable(m_parameters.sshParams.host) << "'..." << std::endl;
70 m_connection->connectToHost(m_parameters.sshParams);
73 void SftpTest::handleConnected()
75 if (m_state != Connecting) {
76 std::cerr << "Unexpected state " << m_state << " in function "
77 << Q_FUNC_INFO << "." << std::endl;
78 earlyDisconnectFromHost();
80 std::cout << "Connected. Initializing SFTP channel..." << std::endl;
81 m_channel = m_connection->createSftpChannel();
82 connect(m_channel.data(), SIGNAL(initialized()), this,
83 SLOT(handleChannelInitialized()));
84 connect(m_channel.data(), SIGNAL(initializationFailed(QString)), this,
85 SLOT(handleChannelInitializationFailure(QString)));
86 connect(m_channel.data(), SIGNAL(finished(Utils::SftpJobId, QString)),
87 this, SLOT(handleJobFinished(Utils::SftpJobId, QString)));
88 connect(m_channel.data(), SIGNAL(closed()), this,
89 SLOT(handleChannelClosed()));
90 m_state = InitializingChannel;
91 m_channel->initialize();
95 void SftpTest::handleDisconnected()
97 if (m_state != Disconnecting) {
98 std::cerr << "Unexpected state " << m_state << " in function "
99 << Q_FUNC_INFO << std::endl;
102 std::cout << "Connection closed." << std::endl;
104 std::cout << "Test finished. ";
106 std::cout << "There were errors.";
108 std::cout << "No errors encountered.";
109 std::cout << std::endl;
113 void SftpTest::handleError()
115 std::cerr << "Encountered SSH error: "
116 << qPrintable(m_connection->errorString()) << "." << std::endl;
120 void SftpTest::handleChannelInitialized()
122 if (m_state != InitializingChannel) {
123 std::cerr << "Unexpected state " << m_state << "in function "
124 << Q_FUNC_INFO << "." << std::endl;
125 earlyDisconnectFromHost();
129 std::cout << "Creating " << m_parameters.smallFileCount
130 << " files of 1 KB each ..." << std::endl;
131 qsrand(QDateTime::currentDateTime().toTime_t());
132 for (int i = 0; i < m_parameters.smallFileCount; ++i) {
133 const QString fileName
134 = QLatin1String("sftptestfile") + QString::number(i + 1);
135 const FilePtr file(new QFile(QDir::tempPath() + QLatin1Char('/')
138 if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate))
141 int content[1024/sizeof(int)];
142 for (size_t j = 0; j < sizeof content / sizeof content[0]; ++j)
143 content[j] = qrand();
144 file->write(reinterpret_cast<char *>(content), sizeof content);
147 success = success && file->error() == QFile::NoError;
149 std::cerr << "Error creating local file "
150 << qPrintable(file->fileName()) << "." << std::endl;
151 earlyDisconnectFromHost();
154 m_localSmallFiles << file;
156 std::cout << "Files created. Now uploading..." << std::endl;
157 foreach (const FilePtr &file, m_localSmallFiles) {
158 const QString localFilePath = file->fileName();
159 const QString remoteFp
160 = remoteFilePath(QFileInfo(localFilePath).fileName());
161 const SftpJobId uploadJob = m_channel->uploadFile(file->fileName(),
162 remoteFp, SftpOverwriteExisting);
163 if (uploadJob == SftpInvalidJob) {
164 std::cerr << "Error uploading local file "
165 << qPrintable(localFilePath) << " to remote file "
166 << qPrintable(remoteFp) << "." << std::endl;
167 earlyDisconnectFromHost();
170 m_smallFilesUploadJobs.insert(uploadJob, remoteFp);
172 m_state = UploadingSmall;
175 void SftpTest::handleChannelInitializationFailure(const QString &reason)
177 std::cerr << "Could not initialize SFTP channel: " << qPrintable(reason)
179 earlyDisconnectFromHost();
182 void SftpTest::handleChannelClosed()
184 if (m_state != ChannelClosing) {
185 std::cerr << "Unexpected state " << m_state << " in function "
186 << Q_FUNC_INFO << "." << std::endl;
188 std::cout << "SFTP channel closed. Now disconnecting..." << std::endl;
190 m_state = Disconnecting;
191 m_connection->disconnectFromHost();
194 void SftpTest::handleJobFinished(Utils::SftpJobId job, const QString &error)
198 if (!handleJobFinished(job, m_smallFilesUploadJobs, error, "uploading"))
200 if (m_smallFilesUploadJobs.isEmpty()) {
201 std::cout << "Uploading finished, now downloading for comparison..."
203 foreach (const FilePtr &file, m_localSmallFiles) {
204 const QString localFilePath = file->fileName();
205 const QString remoteFp
206 = remoteFilePath(QFileInfo(localFilePath).fileName());
207 const QString downloadFilePath = cmpFileName(localFilePath);
208 const SftpJobId downloadJob = m_channel->downloadFile(remoteFp,
209 downloadFilePath, SftpOverwriteExisting);
210 if (downloadJob == SftpInvalidJob) {
211 std::cerr << "Error downloading remote file "
212 << qPrintable(remoteFp) << " to local file "
213 << qPrintable(downloadFilePath) << "." << std::endl;
214 earlyDisconnectFromHost();
217 m_smallFilesDownloadJobs.insert(downloadJob, remoteFp);
219 m_state = DownloadingSmall;
222 case DownloadingSmall:
223 if (!handleJobFinished(job, m_smallFilesDownloadJobs, error, "downloading"))
225 if (m_smallFilesDownloadJobs.isEmpty()) {
226 std::cout << "Downloading finished, now comparing..." << std::endl;
227 foreach (const FilePtr &ptr, m_localSmallFiles) {
228 if (!ptr->open(QIODevice::ReadOnly)) {
229 std::cerr << "Error opening local file "
230 << qPrintable(ptr->fileName()) << "." << std::endl;
231 earlyDisconnectFromHost();
234 const QString downloadedFilePath = cmpFileName(ptr->fileName());
235 QFile downloadedFile(downloadedFilePath);
236 if (!downloadedFile.open(QIODevice::ReadOnly)) {
237 std::cerr << "Error opening downloaded file "
238 << qPrintable(downloadedFilePath) << "." << std::endl;
239 earlyDisconnectFromHost();
242 if (!compareFiles(ptr.data(), &downloadedFile))
246 std::cout << "Comparisons successful, now removing files..."
248 QList<QString> remoteFilePaths;
249 foreach (const FilePtr &ptr, m_localSmallFiles) {
250 const QString downloadedFilePath = cmpFileName(ptr->fileName());
252 << remoteFilePath(QFileInfo(ptr->fileName()).fileName());
253 if (!ptr->remove()) {
254 std::cerr << "Error: Failed to remove local file '"
255 << qPrintable(ptr->fileName()) << "'." << std::endl;
256 earlyDisconnectFromHost();
258 if (!QFile::remove(downloadedFilePath)) {
259 std::cerr << "Error: Failed to remove downloaded file '"
260 << qPrintable(downloadedFilePath) << "'." << std::endl;
261 earlyDisconnectFromHost();
264 m_localSmallFiles.clear();
265 foreach (const QString &remoteFp, remoteFilePaths) {
266 m_smallFilesRemovalJobs.insert(m_channel->removeFile(remoteFp),
269 m_state = RemovingSmall;
273 if (!handleJobFinished(job, m_smallFilesRemovalJobs, error, "removing"))
275 if (m_smallFilesRemovalJobs.isEmpty()) {
276 std::cout << "Small files successfully removed. "
277 << "Now creating big file..." << std::endl;
278 const QLatin1String bigFileName("sftpbigfile");
279 m_localBigFile = FilePtr(new QFile(QDir::tempPath()
280 + QLatin1Char('/') + bigFileName));
281 bool success = m_localBigFile->open(QIODevice::WriteOnly);
282 const int blockSize = 8192;
283 const int blockCount = m_parameters.bigFileSize*1024*1024/blockSize;
284 for (int block = 0; block < blockCount; ++block) {
285 int content[blockSize/sizeof(int)];
286 for (size_t j = 0; j < sizeof content / sizeof content[0]; ++j)
287 content[j] = qrand();
288 m_localBigFile->write(reinterpret_cast<char *>(content),
291 m_localBigFile->close();
292 success = success && m_localBigFile->error() == QFile::NoError;
294 std::cerr << "Error trying to create big file '"
295 << qPrintable(m_localBigFile->fileName()) << "'."
297 earlyDisconnectFromHost();
301 std::cout << "Big file created. Now uploading ..." << std::endl;
302 m_bigJobTimer.start();
304 = m_channel->uploadFile(m_localBigFile->fileName(),
305 remoteFilePath(bigFileName), SftpOverwriteExisting);
306 if (m_bigFileUploadJob == SftpInvalidJob) {
307 std::cerr << "Error uploading file '" << bigFileName.latin1()
308 << "'." << std::endl;
309 earlyDisconnectFromHost();
312 m_state = UploadingBig;
316 if (!handleBigJobFinished(job, m_bigFileUploadJob, error, "uploading"))
318 const qint64 msecs = m_bigJobTimer.elapsed();
319 std::cout << "Successfully uploaded big file. Took " << (msecs/1000)
320 << " seconds for " << m_parameters.bigFileSize << " MB."
322 const QString localFilePath = m_localBigFile->fileName();
323 const QString downloadedFilePath = cmpFileName(localFilePath);
324 const QString remoteFp
325 = remoteFilePath(QFileInfo(localFilePath).fileName());
326 std::cout << "Now downloading big file for comparison..." << std::endl;
327 m_bigJobTimer.start();
328 m_bigFileDownloadJob = m_channel->downloadFile(remoteFp,
329 downloadedFilePath, SftpOverwriteExisting);
330 if (m_bigFileDownloadJob == SftpInvalidJob) {
331 std::cerr << "Error downloading remote file '"
332 << qPrintable(remoteFp) << "' to local file '"
333 << qPrintable(downloadedFilePath) << "'." << std::endl;
334 earlyDisconnectFromHost();
337 m_state = DownloadingBig;
340 case DownloadingBig: {
341 if (!handleBigJobFinished(job, m_bigFileDownloadJob, error, "downloading"))
343 const qint64 msecs = m_bigJobTimer.elapsed();
344 std::cout << "Successfully downloaded big file. Took " << (msecs/1000)
345 << " seconds for " << m_parameters.bigFileSize << " MB."
347 std::cout << "Now comparing big files..." << std::endl;
348 QFile downloadedFile(cmpFileName(m_localBigFile->fileName()));
349 if (!downloadedFile.open(QIODevice::ReadOnly)) {
350 std::cerr << "Error opening downloaded file '"
351 << qPrintable(downloadedFile.fileName()) << "': "
352 << qPrintable(downloadedFile.errorString()) << "." << std::endl;
353 earlyDisconnectFromHost();
356 if (!m_localBigFile->open(QIODevice::ReadOnly)) {
357 std::cerr << "Error opening big file '"
358 << qPrintable(m_localBigFile->fileName()) << "': "
359 << qPrintable(m_localBigFile->errorString()) << "."
361 earlyDisconnectFromHost();
364 if (!compareFiles(m_localBigFile.data(), &downloadedFile))
366 std::cout << "Comparison successful. Now removing big files..."
368 if (!m_localBigFile->remove()) {
369 std::cerr << "Error: Could not remove file '"
370 << qPrintable(m_localBigFile->fileName()) << "'." << std::endl;
371 earlyDisconnectFromHost();
374 if (!downloadedFile.remove()) {
375 std::cerr << "Error: Could not remove file '"
376 << qPrintable(downloadedFile.fileName()) << "'." << std::endl;
377 earlyDisconnectFromHost();
380 const QString remoteFp
381 = remoteFilePath(QFileInfo(m_localBigFile->fileName()).fileName());
382 m_bigFileRemovalJob = m_channel->removeFile(remoteFp);
383 m_state = RemovingBig;
387 if (!handleBigJobFinished(job, m_bigFileRemovalJob, error, "removing"))
389 const QString remoteFp
390 = remoteFilePath(QFileInfo(m_localBigFile->fileName()).fileName());
391 std::cout << "Big files successfully removed. "
392 << "Now closing the SFTP channel..." << std::endl;
393 m_state = ChannelClosing;
394 m_channel->closeChannel();
401 std::cerr << "Unexpected state " << m_state << " in function "
402 << Q_FUNC_INFO << "." << std::endl;
403 earlyDisconnectFromHost();
408 void SftpTest::removeFile(const FilePtr &file, bool remoteToo)
412 const QString localFilePath = file->fileName();
414 QFile::remove(cmpFileName(localFilePath));
415 if (remoteToo && m_channel
416 && m_channel->state() == SftpChannel::Initialized)
417 m_channel->removeFile(remoteFilePath(QFileInfo(localFilePath).fileName()));
420 QString SftpTest::cmpFileName(const QString &fileName) const
422 return fileName + QLatin1String(".cmp");
425 QString SftpTest::remoteFilePath(const QString &localFileName) const
427 return QLatin1String("/tmp/") + localFileName + QLatin1String(".upload");
430 void SftpTest::earlyDisconnectFromHost()
435 disconnect(m_channel.data(), 0, this, 0);
436 m_state = Disconnecting;
438 m_connection->disconnectFromHost();
441 void SftpTest::removeFiles(bool remoteToo)
443 foreach (const FilePtr &file, m_localSmallFiles)
444 removeFile(file, remoteToo);
445 removeFile(m_localBigFile, remoteToo);
448 bool SftpTest::handleJobFinished(SftpJobId job, JobMap &jobMap,
449 const QString &error, const char *activity)
451 JobMap::Iterator it = jobMap.find(job);
452 if (it == jobMap.end()) {
453 std::cerr << "Error: Unknown job " << job << "finished."
455 earlyDisconnectFromHost();
458 if (!error.isEmpty()) {
459 std::cerr << "Error " << activity << " file " << qPrintable(it.value())
460 << ": " << qPrintable(error) << "." << std::endl;
461 earlyDisconnectFromHost();
468 bool SftpTest::handleBigJobFinished(SftpJobId job, SftpJobId expectedJob,
469 const QString &error, const char *activity)
471 if (job != expectedJob) {
472 std::cerr << "Error " << activity << " file '"
473 << qPrintable(m_localBigFile->fileName())
474 << "': Expected job id " << expectedJob
475 << ", got job id " << job << '.' << std::endl;
476 earlyDisconnectFromHost();
479 if (!error.isEmpty()) {
480 std::cerr << "Error " << activity << " file '"
481 << qPrintable(m_localBigFile->fileName()) << "': "
482 << qPrintable(error) << std::endl;
483 earlyDisconnectFromHost();
489 bool SftpTest::compareFiles(QFile *orig, QFile *copy)
491 bool success = orig->size() == copy->size();
492 qint64 bytesLeft = orig->size();
494 while (success && bytesLeft > 0) {
495 const qint64 bytesToRead = qMin(bytesLeft, Q_INT64_C(1024*1024));
496 const QByteArray origBlock = orig->read(bytesToRead);
497 const QByteArray copyBlock = copy->read(bytesToRead);
498 if (origBlock.size() != bytesToRead || origBlock != copyBlock)
500 bytesLeft -= bytesToRead;
503 success = success && orig->error() == QFile::NoError
504 && copy->error() == QFile::NoError;
506 std::cerr << "Error: Original file '" << qPrintable(orig->fileName())
507 << "'' differs from downloaded file '"
508 << qPrintable(copy->fileName()) << "'." << std::endl;
509 earlyDisconnectFromHost();