1 /*=============================================================================
2
3 Library: XNAT/Core
4
5 Copyright (c) University College London,
6 Centre for Medical Image Computing
7
8 Licensed under the Apache License, Version 2.0 (the "License");
9 you may not use this file except in compliance with the License.
10 You may obtain a copy of the License at
11
12 http://www.apache.org/licenses/LICENSE-2.0
13
14 Unless required by applicable law or agreed to in writing, software
15 distributed under the License is distributed on an "AS IS" BASIS,
16 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 See the License for the specific language governing permissions and
18 limitations under the License.
19
20 =============================================================================*/
21
22 #include "ctkXnatSession.h"
23
24 #include "ctkXnatAssessor.h"
25 #include "ctkXnatDataModel.h"
26 #include "ctkXnatDefaultSchemaTypes.h"
27 #include "ctkXnatException.h"
28 #include "ctkXnatExperiment.h"
29 #include "ctkXnatFile.h"
30 #include "ctkXnatLoginProfile.h"
31 #include "ctkXnatObject.h"
32 #include "ctkXnatProject.h"
33 #include "ctkXnatReconstruction.h"
34 #include "ctkXnatResource.h"
35 #include "ctkXnatScan.h"
36 #include "ctkXnatSubject.h"
37
38 #include <QCryptographicHash>
39 #include <QDateTime>
40 #include <QTimer>
41 #include <QDebug>
42 #include <QDir>
43 #include <QScopedPointer>
44 #include <QStringBuilder>
45 #include <QNetworkCookie>
46
47 #include <ctkXnatAPI_p.h>
48 #include <qRestResult.h>
49
50 //----------------------------------------------------------------------------
51 static const char* HEADER_AUTHORIZATION = "Authorization";
52 static const char* HEADER_USER_AGENT = "User-Agent";
53 static const char* HEADER_COOKIE = "Cookie";
54
55 static QString SERVER_VERSION = "version";
56 static QString SESSION_EXPIRATION_DATE = "expires";
57
58 //----------------------------------------------------------------------------
59 class ctkXnatSessionPrivate
60 {
61 public:
62 const ctkXnatLoginProfile loginProfile;
63
64 QScopedPointer<ctkXnatAPI> xnat;
65 QScopedPointer<ctkXnatDataModel> dataModel;
66 QString sessionId;
67 QString defaultDownloadDir;
68
69 QMap<QString, QString> sessionProperties;
70
71 ctkXnatSession* q;
72
73 QTimer* timer;
74
75 // The time in milliseconds until the signal aboutToTimeOut gets emitted
76 // XNAT sessions time out after 15 minutes (=900000 ms) by default
77 // i.e. by default this signal will be emitted after 840000 ms
78 int timeOutWarningPeriod;
79
80 // The time in milliseconds until the signal timedOut gets emitted
81 // Default XNAT session timeout setting. This value will be updated after
82 // "updateExpirationDate" is called the first time.
83 int timeOutPeriod;
84
85 ctkXnatSessionPrivate(const ctkXnatLoginProfile& loginProfile, ctkXnatSession* q);
86 ~ctkXnatSessionPrivate();
87
88 void throwXnatException(const QString& msg);
89
90 void createConnections();
91 void setDefaultHttpHeaders();
92 void checkSession() const;
93 void setSessionProperties();
94 QDateTime updateExpirationDate(qRestResult* restResult);
95
96 void close();
97
98 static QList<ctkXnatObject*> results(qRestResult* restResult, QString schemaType);
99 };
100
101 //----------------------------------------------------------------------------
ctkXnatSessionPrivate(const ctkXnatLoginProfile & loginProfile,ctkXnatSession * q)102 ctkXnatSessionPrivate::ctkXnatSessionPrivate(const ctkXnatLoginProfile& loginProfile,
103 ctkXnatSession* q)
104 : loginProfile(loginProfile)
105 , xnat(new ctkXnatAPI())
106 , defaultDownloadDir(".")
107 , q(q)
108 , timer(new QTimer(q))
109 , timeOutWarningPeriod (840000)
110 , timeOutPeriod(60000)
111 {
112 // TODO This is a workaround for connecting to sites with self-signed
113 // certificate. Should be replaced with something more clever.
114 xnat->setSuppressSslErrors(true);
115
116 createConnections();
117 }
118
119 //----------------------------------------------------------------------------
~ctkXnatSessionPrivate()120 ctkXnatSessionPrivate::~ctkXnatSessionPrivate()
121 {
122 }
123
124 //----------------------------------------------------------------------------
throwXnatException(const QString & msg)125 void ctkXnatSessionPrivate::throwXnatException(const QString& msg)
126 {
127 QString errorMsg = msg.trimmed();
128 if (!errorMsg.isEmpty())
129 {
130 errorMsg.append(' ');
131 }
132 errorMsg.append(xnat->errorString());
133
134 switch (xnat->error())
135 {
136 case qRestAPI::TimeoutError:
137 throw ctkXnatTimeoutException(errorMsg);
138 case qRestAPI::ResponseParseError:
139 throw ctkXnatProtocolFailureException(errorMsg);
140 case qRestAPI::UnknownUuidError:
141 throw ctkInvalidArgumentException(errorMsg);
142 case qRestAPI::AuthenticationError:
143 // This signals either an initial authentication error
144 // or a session timeout.
145 this->close();
146 throw ctkXnatAuthenticationException(errorMsg);
147 default:
148 throw ctkRuntimeException(errorMsg);
149 }
150 }
151
152 //----------------------------------------------------------------------------
createConnections()153 void ctkXnatSessionPrivate::createConnections()
154 {
155 // Q_D(ctkXnatSession);
156 // connect(d->xnat, SIGNAL(resultReceived(QUuid,QList<QVariantMap>)),
157 // this, SLOT(processResult(QUuid,QList<QVariantMap>)));
158 // connect(d->xnat, SIGNAL(progress(QUuid,double)),
159 // this, SLOT(progress(QUuid,double)));
160 }
161
162 //----------------------------------------------------------------------------
setDefaultHttpHeaders()163 void ctkXnatSessionPrivate::setDefaultHttpHeaders()
164 {
165 ctkXnatAPI::RawHeaders rawHeaders;
166 rawHeaders[HEADER_USER_AGENT] = "Qt";
167 /*
168 rawHeaders["Authorization"] = "Basic " +
169 QByteArray(QString("%1:%2").arg(d->loginProfile.userName())
170 .arg(d->loginProfile.password()).toAscii()).toBase64();
171 */
172 if (!sessionId.isEmpty())
173 {
174 rawHeaders[HEADER_COOKIE] = QString("JSESSIONID=%1").arg(sessionId).toLatin1();
175 }
176 xnat->setDefaultRawHeaders(rawHeaders);
177 }
178
179 //----------------------------------------------------------------------------
checkSession() const180 void ctkXnatSessionPrivate::checkSession() const
181 {
182 if (sessionId.isEmpty())
183 {
184 throw ctkXnatInvalidSessionException("Session closed.");
185 }
186 }
187
188 //----------------------------------------------------------------------------
setSessionProperties()189 void ctkXnatSessionPrivate::setSessionProperties()
190 {
191 sessionProperties.clear();
192 QUuid uuid = xnat->get("/data/version");
193 QScopedPointer<qRestResult> restResult(xnat->takeResult(uuid));
194 if (restResult)
195 {
196 QString version = restResult->result()["content"].toString();
197 if (version.isEmpty())
198 {
199 throw ctkXnatProtocolFailureException("No version information available.");
200 }
201 sessionProperties[SERVER_VERSION] = version;
202 }
203 else
204 {
205 this->throwXnatException("Retrieving session properties failed.");
206 }
207 }
208
209 //----------------------------------------------------------------------------
updateExpirationDate(qRestResult * restResult)210 QDateTime ctkXnatSessionPrivate::updateExpirationDate(qRestResult* restResult)
211 {
212 QByteArray cookieHeader = restResult->rawHeader("Set-Cookie");
213 QDateTime expirationDate = QDateTime::currentDateTime();
214 if (!cookieHeader.isEmpty())
215 {
216 QList<QNetworkCookie> cookies = QNetworkCookie::parseCookies(cookieHeader);
217 foreach(const QNetworkCookie& cookie, cookies)
218 {
219 if (cookie.name() == "SESSION_EXPIRATION_TIME")
220 {
221 QList<QByteArray> expirationCookie = cookie.value().split(',');
222 if (expirationCookie.size() == 2)
223 {
224 unsigned long long startTime = expirationCookie[0].mid(1).toULongLong();
225 if (startTime > 0)
226 {
227 expirationDate = QDateTime::fromTime_t(startTime / 1000);
228 }
229 QByteArray timeSpan = expirationCookie[1];
230 timeSpan.chop(1);
231 this->timeOutWarningPeriod = timeSpan.toLong() - this->timeOutPeriod;
232 expirationDate = expirationDate.addMSecs(timeSpan.toLong());
233 sessionProperties[SESSION_EXPIRATION_DATE] = expirationDate.toString(Qt::ISODate);
234 this->timer->start(this->timeOutWarningPeriod);
235 emit q->sessionRenewed(expirationDate);
236 }
237 }
238 }
239 }
240 return expirationDate;
241 }
242
243 //----------------------------------------------------------------------------
close()244 void ctkXnatSessionPrivate::close()
245 {
246 sessionProperties.clear();
247 sessionId.clear();
248 this->setDefaultHttpHeaders();
249
250 dataModel.reset();
251 }
252
253 //----------------------------------------------------------------------------
results(qRestResult * restResult,QString schemaType)254 QList<ctkXnatObject*> ctkXnatSessionPrivate::results(qRestResult* restResult, QString schemaType)
255 {
256 QList<ctkXnatObject*> results;
257 foreach (const QVariantMap& propertyMap, restResult->results())
258 {
259 QString customSchemaType;
260 if (propertyMap.contains("xsiType"))
261 {
262 customSchemaType = propertyMap["xsiType"].toString();
263 }
264
265 int typeId = 0;
266 // try to create an object based on the custom schema type first
267 if (!customSchemaType.isEmpty())
268 {
269 typeId = QMetaType::type(qPrintable(customSchemaType));
270 }
271
272 // Fall back. Create the default class according to the default schema type
273 if (!typeId)
274 {
275 if (!customSchemaType.isEmpty())
276 {
277 qWarning() << QString("No ctkXnatObject sub-class registered for the schema %1. Falling back to the default class %2.").arg(customSchemaType).arg(schemaType);
278 }
279 typeId = QMetaType::type(qPrintable(schemaType));
280 }
281
282 if (!typeId)
283 {
284 qWarning() << QString("No ctkXnatObject sub-class registered as a meta-type for the schema %1. Skipping result.").arg(schemaType);
285 continue;
286 }
287
288 #if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
289 ctkXnatObject* object = reinterpret_cast<ctkXnatObject*>(QMetaType::construct(typeId));
290 #else
291 ctkXnatObject* object = reinterpret_cast<ctkXnatObject*>(QMetaType(typeId).create());
292 #endif
293 if (!customSchemaType.isEmpty())
294 {
295 // We might have created the default ctkXnatObject sub-class, but can still set
296 // the custom schema type.
297 object->setSchemaType(customSchemaType);
298 }
299
300 // Fill in the properties
301 QMapIterator<QString, QVariant> it(propertyMap);
302 QString description;
303
304 while (it.hasNext())
305 {
306 it.next();
307
308 QString str = it.key().toLatin1().data();
309 QVariant var = it.value();
310
311 object->setProperty(str, var);
312 description.append (str + QString ("\t::\t") + var.toString() + "\n");
313 }
314
315 QVariant lastModifiedHeader = restResult->rawHeader("Last-Modified");
316 QDateTime lastModifiedTime;
317 if (lastModifiedHeader.isValid())
318 {
319 lastModifiedTime = lastModifiedHeader.toDateTime();
320 }
321 if (lastModifiedTime.isValid())
322 {
323 object->setLastModifiedTime(lastModifiedTime);
324 }
325
326 object->setDescription(description);
327 results.push_back(object);
328 }
329 return results;
330 }
331
332
333 //----------------------------------------------------------------------------
334 // ctkXnatSession class
335
336 //----------------------------------------------------------------------------
ctkXnatSession(const ctkXnatLoginProfile & loginProfile)337 ctkXnatSession::ctkXnatSession(const ctkXnatLoginProfile& loginProfile)
338 : d_ptr(new ctkXnatSessionPrivate(loginProfile, this))
339 {
340 Q_D(ctkXnatSession);
341
342 qRegisterMetaType<ctkXnatProject>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_PROJECT));
343 qRegisterMetaType<ctkXnatSubject>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_SUBJECT));
344 qRegisterMetaType<ctkXnatExperiment>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_EXPERIMENT));
345 qRegisterMetaType<ctkXnatScan>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_SCAN));
346 qRegisterMetaType<ctkXnatReconstruction>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_RECONSTRUCTION));
347 qRegisterMetaType<ctkXnatResource>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_RESOURCE));
348 qRegisterMetaType<ctkXnatAssessor>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_ASSESSOR));
349 qRegisterMetaType<ctkXnatFile>(qPrintable(ctkXnatDefaultSchemaTypes::XSI_FILE));
350
351 QString url = d->loginProfile.serverUrl().toString();
352 d->xnat->setServerUrl(url);
353
354 // QObject::connect(d->xnat.data(), SIGNAL(uploadFinished()), this, SIGNAL(uploadFinished()));
355 QObject::connect(d->xnat.data(), SIGNAL(progress(QUuid,double)),
356 this, SIGNAL(progress(QUuid,double)));
357 // QObject::connect(d->xnat.data(), SIGNAL(progress(QUuid,double)),
358 // this, SLOT(onProgress(QUuid,double)));
359
360 d->setDefaultHttpHeaders();
361 }
362
363 //----------------------------------------------------------------------------
~ctkXnatSession()364 ctkXnatSession::~ctkXnatSession()
365 {
366 this->close();
367 }
368
369 //----------------------------------------------------------------------------
open()370 void ctkXnatSession::open()
371 {
372 Q_D(ctkXnatSession);
373
374 if (this->isOpen()) return;
375
376 qRestAPI::RawHeaders headers;
377 headers[HEADER_AUTHORIZATION] = "Basic " +
378 QByteArray(QString("%1:%2").arg(this->userName())
379 .arg(this->password()).toLatin1()).toBase64();
380 QUuid uuid = d->xnat->get("/data/JSESSION", qRestAPI::Parameters(), headers);
381 QScopedPointer<qRestResult> restResult(d->xnat->takeResult(uuid));
382 if (restResult)
383 {
384 QObject::connect(d->timer, SIGNAL(timeout()), this, SLOT(emitTimeOut()));
385
386 QString sessionId = restResult->result()["content"].toString();
387 d->sessionId = sessionId;
388 d->setDefaultHttpHeaders();
389 d->setSessionProperties();
390 d->updateExpirationDate(restResult.data());
391 }
392 else
393 {
394 d->throwXnatException("Could not get a session id.");
395 }
396
397 d->dataModel.reset(new ctkXnatDataModel(this));
398 d->dataModel->setProperty(ctkXnatObject::LABEL, this->url().toString());
399 emit sessionOpened();
400 }
401
402 //----------------------------------------------------------------------------
close()403 void ctkXnatSession::close()
404 {
405 Q_D(ctkXnatSession);
406
407 if (!this->isOpen()) return;
408
409 emit sessionAboutToBeClosed();
410 d->close();
411 }
412
413 //----------------------------------------------------------------------------
isOpen() const414 bool ctkXnatSession::isOpen() const
415 {
416 Q_D(const ctkXnatSession);
417 return !d->sessionId.isEmpty();
418 }
419
420 //----------------------------------------------------------------------------
version() const421 QString ctkXnatSession::version() const
422 {
423 Q_D(const ctkXnatSession);
424 if (d->sessionProperties.contains(SERVER_VERSION))
425 {
426 return d->sessionProperties[SERVER_VERSION];
427 }
428 else
429 {
430 return QString::null;
431 }
432 }
433
434 //----------------------------------------------------------------------------
expirationDate() const435 QDateTime ctkXnatSession::expirationDate() const
436 {
437 Q_D(const ctkXnatSession);
438 d->checkSession();
439 return QDateTime::fromString(d->sessionProperties[SESSION_EXPIRATION_DATE], Qt::ISODate);
440 }
441
442 //----------------------------------------------------------------------------
renew()443 QDateTime ctkXnatSession::renew()
444 {
445 Q_D(ctkXnatSession);
446 d->checkSession();
447
448 QUuid uuid = d->xnat->get("/data/auth");
449 QScopedPointer<qRestResult> restResult(d->xnat->takeResult(uuid));
450 if (!restResult)
451 {
452 d->throwXnatException("Session renewal failed.");
453 }
454 return d->updateExpirationDate(restResult.data());
455 }
456
457 //----------------------------------------------------------------------------
loginProfile() const458 ctkXnatLoginProfile ctkXnatSession::loginProfile() const
459 {
460 Q_D(const ctkXnatSession);
461 return d->loginProfile;
462 }
463
464 //----------------------------------------------------------------------------
onProgress(QUuid,double)465 void ctkXnatSession::onProgress(QUuid /*queryId*/, double /*progress*/)
466 {
467 // qDebug() << "ctkXnatSession::progress(QUuid queryId, double progress)";
468 // qDebug() << "query id:" << queryId;
469 // qDebug() << "progress:" << (progress * 100.0) << "%";
470 }
471
472 //----------------------------------------------------------------------------
url() const473 QUrl ctkXnatSession::url() const
474 {
475 Q_D(const ctkXnatSession);
476 return d->loginProfile.serverUrl();
477 }
478
479 //----------------------------------------------------------------------------
userName() const480 QString ctkXnatSession::userName() const
481 {
482 Q_D(const ctkXnatSession);
483 return d->loginProfile.userName();
484 }
485
486 //----------------------------------------------------------------------------
password() const487 QString ctkXnatSession::password() const
488 {
489 Q_D(const ctkXnatSession);
490 return d->loginProfile.password();
491 }
492
493 //----------------------------------------------------------------------------
sessionId() const494 QString ctkXnatSession::sessionId() const
495 {
496 Q_D(const ctkXnatSession);
497 return d->sessionId;
498 }
499
500 //----------------------------------------------------------------------------
setDefaultDownloadDir(const QString & path)501 void ctkXnatSession::setDefaultDownloadDir(const QString &path)
502 {
503 Q_D(ctkXnatSession);
504
505 QDir directory(path);
506 if (directory.exists())
507 {
508 d->defaultDownloadDir = path;
509 }
510 else
511 {
512 d->defaultDownloadDir = QDir::currentPath();
513 qWarning() << "Specified directory: ["<<path<<"] does not exists! Setting default filepath to :"<<d->defaultDownloadDir;
514 }
515 }
516
517 //----------------------------------------------------------------------------
defaultDownloadDir() const518 QString ctkXnatSession::defaultDownloadDir() const
519 {
520 Q_D(const ctkXnatSession);
521 return d->defaultDownloadDir;
522 }
523
524 //----------------------------------------------------------------------------
dataModel() const525 ctkXnatDataModel* ctkXnatSession::dataModel() const
526 {
527 Q_D(const ctkXnatSession);
528 d->checkSession();
529 return d->dataModel.data();
530 }
531
532 //----------------------------------------------------------------------------
httpGet(const QString & resource,const ctkXnatSession::UrlParameters & parameters,const ctkXnatSession::HttpRawHeaders & rawHeaders)533 QUuid ctkXnatSession::httpGet(const QString& resource, const ctkXnatSession::UrlParameters& parameters, const ctkXnatSession::HttpRawHeaders& rawHeaders)
534 {
535 Q_D(ctkXnatSession);
536 d->checkSession();
537 d->timer->start(d->timeOutWarningPeriod);
538 return d->xnat->get(resource, parameters, rawHeaders);
539 }
540
541 //----------------------------------------------------------------------------
httpResults(const QUuid & uuid,const QString & schemaType)542 QList<ctkXnatObject*> ctkXnatSession::httpResults(const QUuid& uuid, const QString& schemaType)
543 {
544 Q_D(ctkXnatSession);
545 d->checkSession();
546
547 QScopedPointer<qRestResult> restResult(d->xnat->takeResult(uuid));
548 if (restResult == NULL)
549 {
550 d->throwXnatException("Http request failed.");
551 }
552 d->timer->start(d->timeOutWarningPeriod);
553 return d->results(restResult.data(), schemaType);
554 }
555
httpPut(const QString & resource,const ctkXnatSession::UrlParameters & parameters,const ctkXnatSession::HttpRawHeaders &)556 QUuid ctkXnatSession::httpPut(const QString& resource, const ctkXnatSession::UrlParameters& parameters,
557 const ctkXnatSession::HttpRawHeaders& /*rawHeaders*/)
558 {
559 Q_D(ctkXnatSession);
560 d->checkSession();
561 d->timer->start(d->timeOutWarningPeriod);
562 return d->xnat->put(resource, parameters);
563 }
564
565 //----------------------------------------------------------------------------
httpSync(const QUuid & uuid)566 QList<QVariantMap> ctkXnatSession::httpSync(const QUuid& uuid)
567 {
568 Q_D(ctkXnatSession);
569 d->checkSession();
570
571 QList<QVariantMap> result;
572 qRestResult* restResult = d->xnat->takeResult(uuid);
573 if (restResult == NULL)
574 {
575 d->throwXnatException("Syncing with http request failed.");
576 }
577 else
578 {
579 d->updateExpirationDate(restResult); // restarts session timer as well
580 result = restResult->results();
581 }
582 return result;
583 }
584
585 //----------------------------------------------------------------------------
httpHeadSync(const QUuid & uuid)586 const QMap<QByteArray, QByteArray> ctkXnatSession::httpHeadSync(const QUuid &uuid)
587 {
588 Q_D(ctkXnatSession);
589 QScopedPointer<qRestResult> result (d->xnat->takeResult(uuid));
590 d->timer->start(d->timeOutWarningPeriod);
591 if (result == NULL)
592 {
593 d->throwXnatException("Sending HEAD request failed.");
594 }
595 return result->rawHeaders();
596 }
597
598 //----------------------------------------------------------------------------
httpHead(const QString & resourceUri)599 QUuid ctkXnatSession::httpHead(const QString& resourceUri)
600 {
601 Q_D(ctkXnatSession);
602 QUuid queryId = d->xnat->head(resourceUri);
603 d->timer->start(d->timeOutWarningPeriod);
604 return queryId;
605 }
606
607 //----------------------------------------------------------------------------
exists(const ctkXnatObject * object)608 bool ctkXnatSession::exists(const ctkXnatObject* object)
609 {
610 Q_D(ctkXnatSession);
611
612 QString query = object->resourceUri();
613 bool success = d->xnat->sync(d->xnat->get(query));
614 d->timer->start(d->timeOutWarningPeriod);
615
616 return success;
617 }
618
619 //----------------------------------------------------------------------------
remove(ctkXnatObject * object)620 void ctkXnatSession::remove(ctkXnatObject* object)
621 {
622 Q_D(ctkXnatSession);
623
624 QString query = object->resourceUri();
625 bool success = d->xnat->sync(d->xnat->del(query));
626 d->timer->start(d->timeOutWarningPeriod);
627
628 if (!success)
629 {
630 d->throwXnatException("Error occurred while removing the data.");
631 }
632 }
633
634 //----------------------------------------------------------------------------
download(const QString & fileName,const QString & resource,const UrlParameters & parameters,const HttpRawHeaders & rawHeaders)635 void ctkXnatSession::download(const QString& fileName,
636 const QString& resource,
637 const UrlParameters& parameters,
638 const HttpRawHeaders& rawHeaders)
639 {
640 Q_D(ctkXnatSession);
641
642 QUuid queryId = d->xnat->download(fileName, resource, parameters, rawHeaders);
643 d->xnat->sync(queryId);
644 d->timer->start(d->timeOutWarningPeriod);
645 }
646
647 //----------------------------------------------------------------------------
upload(ctkXnatFile * xnatFile,const UrlParameters & parameters,const HttpRawHeaders &)648 void ctkXnatSession::upload(ctkXnatFile *xnatFile,
649 const UrlParameters ¶meters,
650 const HttpRawHeaders &/*rawHeaders*/)
651 {
652 Q_D(ctkXnatSession);
653
654 QFile file(xnatFile->localFilePath());
655
656 if (!file.exists())
657 {
658 QString msg = "Error uploading file! ";
659 msg.append(QString("File \"%1\" does not exist!").arg(xnatFile->localFilePath()));
660 throw ctkXnatException(msg);
661 }
662
663 QUuid queryId = d->xnat->upload(xnatFile->localFilePath(), xnatFile->resourceUri(), parameters);
664 d->xnat->sync(queryId);
665
666 // Validating the file upload by requesting the catalog XML
667 // of the parent resource. Unfortunately for XNAT versions <= 1.6.4
668 // this is the only way to get the file's MD5 hash form the server.
669 QString md5Query = xnatFile->parent()->resourceUri();
670 QUuid md5QueryID = this->httpGet(md5Query);
671 QList<QVariantMap> result = this->httpSync(md5QueryID);
672
673 d->timer->start(d->timeOutWarningPeriod);
674
675 QString md5ChecksumRemote ("0");
676 // Newly added files are usually at the end of the catalog
677 // and hence at the end of the result list.
678 // So iterating backward is for performance reasons.
679 QList<QVariantMap>::const_iterator it = result.constEnd()-1;
680 while (it != result.constBegin()-1)
681 {
682 QVariantMap::const_iterator it2 = (*it).find(xnatFile->name());
683 if (it2 != (*it).constEnd())
684 {
685 md5ChecksumRemote = it2.value().toString();
686 break;
687 }
688 --it;
689 }
690
691 QFile localFile(xnatFile->localFilePath());
692 if (localFile.open(QFile::ReadOnly) && md5ChecksumRemote != "0")
693 {
694 QCryptographicHash hash(QCryptographicHash::Md5);
695
696 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
697 hash.addData(&localFile);
698 #else
699 hash.addData(localFile.readAll());
700 #endif
701
702 QString md5ChecksumLocal(hash.result().toHex());
703 // Retrieving the md5 checksum on the server and comparing
704 // it with the local file md5 sum
705 if (md5ChecksumLocal != md5ChecksumRemote)
706 {
707 // Remove corrupted file from server
708 xnatFile->erase();
709 d->timer->start(d->timeOutWarningPeriod);
710 throw ctkXnatException("Upload failed! An error occurred during file upload.");
711 }
712 }
713 else
714 {
715 qWarning()<<"Could not validate file upload! Remote MD5: "<<md5ChecksumRemote;
716 }
717 }
718
719 //----------------------------------------------------------------------------
processResult(QUuid queryId,QList<QVariantMap> parameters)720 void ctkXnatSession::processResult(QUuid queryId, QList<QVariantMap> parameters)
721 {
722 Q_UNUSED(queryId)
723 Q_UNUSED(parameters)
724 }
725
726 //----------------------------------------------------------------------------
emitTimeOut()727 void ctkXnatSession::emitTimeOut()
728 {
729 Q_D(ctkXnatSession);
730
731 if (d->timer->interval() == d->timeOutWarningPeriod)
732 {
733 d->timer->start(d->timeOutPeriod);
734 emit aboutToTimeOut();
735 }
736 else if (d->timer->interval() == d->timeOutPeriod)
737 {
738 d->timer->stop();
739 emit timedOut();
740 }
741 }
742
743 //----------------------------------------------------------------------------
setHttpNetworkProxy(const QNetworkProxy & proxy)744 void ctkXnatSession::setHttpNetworkProxy(const QNetworkProxy& proxy)
745 {
746 Q_D(ctkXnatSession);
747
748 d->xnat->setHttpNetworkProxy(proxy);
749 }
750