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 &parameters,
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