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 "ctkXnatObject.h"
23 #include "ctkXnatObjectPrivate.h"
24 
25 #include "ctkXnatDataModel.h"
26 #include "ctkXnatDefaultSchemaTypes.h"
27 #include "ctkXnatException.h"
28 #include "ctkXnatResource.h"
29 #include "ctkXnatResourceFolder.h"
30 #include "ctkXnatSession.h"
31 
32 #include <QDateTime>
33 #include <QDebug>
34 #include <QStringList>
35 #include <QVariant>
36 
37 
38 const QString ctkXnatObject::ID = "ID";
39 const QString ctkXnatObject::NAME = "name";
40 const QString ctkXnatObject::LABEL = "label";
41 const QString ctkXnatObject::URI = "URI";
42 const QString ctkXnatObject::XSI_SCHEMA_TYPE = "xsiType";
43 
44 //----------------------------------------------------------------------------
ctkXnatObject(const ctkXnatObject &)45 ctkXnatObject::ctkXnatObject(const ctkXnatObject&)
46 {
47   throw ctkRuntimeException("Copy constructor not implemented");
48 }
49 
50 //----------------------------------------------------------------------------
ctkXnatObject(ctkXnatObject * parent,const QString & schemaType)51 ctkXnatObject::ctkXnatObject(ctkXnatObject* parent, const QString& schemaType)
52 : d_ptr(new ctkXnatObjectPrivate())
53 {
54   this->setParent(parent);
55   this->setSchemaType(schemaType);
56 }
57 
58 //----------------------------------------------------------------------------
ctkXnatObject(ctkXnatObjectPrivate & dd,ctkXnatObject * parent,const QString & schemaType)59 ctkXnatObject::ctkXnatObject(ctkXnatObjectPrivate& dd, ctkXnatObject* parent, const QString& schemaType)
60 : d_ptr(&dd)
61 {
62   this->setParent(parent);
63   this->setSchemaType(schemaType);
64 }
65 
66 //----------------------------------------------------------------------------
~ctkXnatObject()67 ctkXnatObject::~ctkXnatObject()
68 {
69   Q_D(ctkXnatObject);
70   foreach (ctkXnatObject* child, d->children)
71   {
72     delete child;
73   }
74 }
75 
76 //----------------------------------------------------------------------------
id() const77 QString ctkXnatObject::id() const
78 {
79   return this->property(ID);
80 }
81 
82 //----------------------------------------------------------------------------
setId(const QString & id)83 void ctkXnatObject::setId(const QString& id)
84 {
85   this->setProperty(ID, id);
86 }
87 
88 //----------------------------------------------------------------------------
name() const89 QString ctkXnatObject::name() const
90 {
91   return this->property(NAME);
92 }
93 
94 //----------------------------------------------------------------------------
setName(const QString & name)95 void ctkXnatObject::setName(const QString& name)
96 {
97   this->setProperty(NAME, name);
98 }
99 
100 //----------------------------------------------------------------------------
description() const101 QString ctkXnatObject::description() const
102 {
103   Q_D(const ctkXnatObject);
104   return d->description;
105 }
106 
107 //----------------------------------------------------------------------------
setDescription(const QString & description)108 void ctkXnatObject::setDescription(const QString& description)
109 {
110   Q_D(ctkXnatObject);
111   d->description = description;
112 }
113 
114 //----------------------------------------------------------------------------
childDataType() const115 QString ctkXnatObject::childDataType() const
116 {
117   return "Resources";
118 }
119 
lastModifiedTimeOnServer()120 QDateTime ctkXnatObject::lastModifiedTimeOnServer()
121 {
122   Q_D(ctkXnatObject);
123   QUuid queryId = this->session()->httpHead(this->resourceUri());
124   QMap<QByteArray, QByteArray> header = this->session()->httpHeadSync(queryId);
125   QVariant lastModifiedHeader = header.value("Last-Modified");
126   QDateTime lastModifiedTime;
127 
128   if (lastModifiedHeader.isValid())
129   {
130     QStringList dateformates;
131     // In case http date formate RFC 822 ( "Sun, 06 Nov 1994 08:49:37 GMT" )
132     dateformates<<"ddd, dd MMM yyyy HH:mm:ss";
133     // In case http date formate ANSI ( "Sun Nov  6 08:49:37 1994" )
134     dateformates<<"ddd MMM  d HH:mm:ss yyyy";
135     // In case http date formate RFC 850 ( "Sunday, 06-Nov-94 08:49:37 GMT" )
136     dateformates<<"dddd, dd-MMM-yy HH:mm:ss";
137 
138     QString dateText = lastModifiedHeader.toString();
139     // Remove "GMT" addition at the end of the http timestamp
140     if (dateText.indexOf("GMT") != -1)
141     {
142       dateText = dateText.left(dateText.length()-4);
143     }
144 
145     foreach (QString format, dateformates)
146     {
147       lastModifiedTime = QDateTime::fromString(dateText, format);
148       if (lastModifiedTime.isValid())
149         break;
150     }
151   }
152   return lastModifiedTime;
153 }
154 
setLastModifiedTime(const QDateTime & lastModifiedTime)155 void ctkXnatObject::setLastModifiedTime(const QDateTime &lastModifiedTime)
156 {
157   Q_D(ctkXnatObject);
158   if (d->lastModifiedTime < lastModifiedTime)
159   {
160     d->lastModifiedTime = lastModifiedTime;
161   }
162 }
163 
164 //----------------------------------------------------------------------------
property(const QString & name) const165 QString ctkXnatObject::property(const QString& name) const
166 {
167   Q_D(const ctkXnatObject);
168   ctkXnatObjectPrivate::PropertyMapConstInterator iter = d->properties.find(name);
169   if (iter != d->properties.end())
170   {
171     return iter.value();
172   }
173   return QString::null;
174 }
175 
176 //----------------------------------------------------------------------------
setProperty(const QString & name,const QVariant & value)177 void ctkXnatObject::setProperty(const QString& name, const QVariant& value)
178 {
179   Q_D(ctkXnatObject);
180   if (d->properties[name] != value)
181   {
182     d->properties.insert(name, value.toString());
183   }
184 }
185 
186 //----------------------------------------------------------------------------
properties() const187 const QMap<QString, QString>& ctkXnatObject::properties() const
188 {
189   Q_D(const ctkXnatObject);
190   return d->properties;
191 }
192 
193 //----------------------------------------------------------------------------
parent() const194 ctkXnatObject* ctkXnatObject::parent() const
195 {
196   Q_D(const ctkXnatObject);
197   return d->parent;
198 }
199 
200 //----------------------------------------------------------------------------
setParent(ctkXnatObject * parent)201 void ctkXnatObject::setParent(ctkXnatObject* parent)
202 {
203   Q_D(ctkXnatObject);
204   if (d->parent != parent)
205   {
206     if (d->parent)
207     {
208       d->parent->remove(this);
209     }
210     if (parent)
211     {
212       parent->add(this);
213     }
214   }
215 }
216 
217 //----------------------------------------------------------------------------
children() const218 QList<ctkXnatObject*> ctkXnatObject::children() const
219 {
220   Q_D(const ctkXnatObject);
221   return d->children;
222 }
223 
224 //----------------------------------------------------------------------------
add(ctkXnatObject * child)225 void ctkXnatObject::add(ctkXnatObject* child)
226 {
227   Q_D(ctkXnatObject);
228   if (child->parent() != this)
229   {
230     child->d_func()->parent = this;
231   }
232 
233   bool childExists (false);
234 
235   QList<ctkXnatObject*>::iterator iter;
236   for (iter = d->children.begin(); iter != d->children.end(); ++iter)
237   {
238     if (((*iter)->id().length() != 0 && (*iter)->id() == child->id()) ||
239         ((*iter)->id().length() == 0 && (*iter)->name() == child->name()))
240     {
241       *iter = child;
242       childExists = true;
243     }
244   }
245 
246   if (!childExists)
247   {
248     d->children.push_back(child);
249   }
250 }
251 
252 //----------------------------------------------------------------------------
remove(ctkXnatObject * child)253 void ctkXnatObject::remove(ctkXnatObject* child)
254 {
255   Q_D(ctkXnatObject);
256   if (!d->children.removeOne(child))
257   {
258     qWarning() << "ctkXnatObject::remove(): Child does not exist";
259   }
260 }
261 
262 //----------------------------------------------------------------------------
reset()263 void ctkXnatObject::reset()
264 {
265   Q_D(ctkXnatObject);
266   // d->properties.clear();
267   d->children.clear();
268   d->fetched = false;
269 }
270 
271 //----------------------------------------------------------------------------
isFetched() const272 bool ctkXnatObject::isFetched() const
273 {
274   Q_D(const ctkXnatObject);
275   return d->fetched;
276 }
277 
278 //----------------------------------------------------------------------------
schemaType() const279 QString ctkXnatObject::schemaType() const
280 {
281   return this->property(XSI_SCHEMA_TYPE);
282 }
283 
284 //----------------------------------------------------------------------------
setSchemaType(const QString & schemaType)285 void ctkXnatObject::setSchemaType(const QString& schemaType)
286 {
287   this->setProperty(XSI_SCHEMA_TYPE, schemaType);
288 }
289 
290 //----------------------------------------------------------------------------
fetch(bool forceFetch)291 void ctkXnatObject::fetch(bool forceFetch)
292 {
293   Q_D(ctkXnatObject);
294   if (!d->fetched || forceFetch)
295   {
296     this->fetchImpl();
297     d->fetched = true;
298   }
299 }
300 
301 //----------------------------------------------------------------------------
session() const302 ctkXnatSession* ctkXnatObject::session() const
303 {
304   const ctkXnatObject* xnatObject = this;
305   while (ctkXnatObject* parent = xnatObject->parent())
306   {
307     xnatObject = parent;
308   }
309   const ctkXnatDataModel* dataModel = dynamic_cast<const ctkXnatDataModel*>(xnatObject);
310   return dataModel ? dataModel->session() : NULL;
311 }
312 
313 //----------------------------------------------------------------------------
download(const QString & filename)314 void ctkXnatObject::download(const QString& filename)
315 {
316   this->downloadImpl(filename);
317 }
318 
319 //----------------------------------------------------------------------------
save(bool overwrite)320 void ctkXnatObject::save(bool overwrite)
321 {
322   Q_D(ctkXnatObject);
323   this->saveImpl(overwrite);
324 }
325 
326 //----------------------------------------------------------------------------
addResourceFolder(QString foldername,QString format,QString content,QString tags)327 ctkXnatResource* ctkXnatObject::addResourceFolder(QString foldername, QString format,
328                                    QString content, QString tags)
329 {
330   if (foldername.size() == 0)
331   {
332     throw ctkXnatException("Error creating resource! Foldername must not be empty!");
333   }
334 
335   ctkXnatResourceFolder* resFolder = 0;
336   QList<ctkXnatObject*> children = this->children();
337   for (int i = 0; i < children.size(); ++i)
338   {
339     resFolder = dynamic_cast<ctkXnatResourceFolder*>(children.at(i));
340     if (resFolder)
341     {
342       break;
343     }
344   }
345 
346   if (!resFolder)
347   {
348     resFolder = new ctkXnatResourceFolder();
349     this->add(resFolder);
350   }
351 
352   ctkXnatResource* resource = new ctkXnatResource();
353   resource->setName(foldername);
354   if (format.size() != 0)
355     resource->setFormat(format);
356   if (content.size() != 0)
357     resource->setContent(content);
358   if (tags.size() != 0)
359     resource->setTags(tags);
360 
361   resFolder->add(resource);
362 
363   if (!resource->exists())
364     resource->save();
365   else
366     qDebug()<<"Not adding resource folder. Folder already exists!";
367 
368   return resource;
369 }
370 
371 //----------------------------------------------------------------------------
exists() const372 bool ctkXnatObject::exists() const
373 {
374   return this->session()->exists(this);
375 }
376 
377 //----------------------------------------------------------------------------
saveImpl(bool)378 void ctkXnatObject::saveImpl(bool /*overwrite*/)
379 {
380   Q_D(ctkXnatObject);
381   ctkXnatSession::UrlParameters urlParams;
382   urlParams["xsiType"] = this->schemaType();
383 
384   // Just do this if there is already a valid last-modification-time,
385   // otherwise the object is not yet on the server!
386   QDateTime remoteModTime;
387   if (d->lastModifiedTime.isValid())
388   {
389     // TODO Overwrite this for e.g. project and subject which already support modification time!
390     remoteModTime = this->lastModifiedTimeOnServer();
391     // If the object has been modified on the server, perform an update
392     if (d->lastModifiedTime < remoteModTime)
393     {
394       qWarning()<<"Uploaded object maybe overwritten on server!";
395       // TODO update from server, since modification time is not really supported
396       // by xnat right now this is not of high priority
397       // something like this->updateImpl + setLastModifiedTime()
398     }
399   }
400 
401   const QMap<QString, QString>& properties = this->properties();
402   QMapIterator<QString, QString> itProperties(properties);
403   while (itProperties.hasNext())
404   {
405     itProperties.next();
406     if (itProperties.key() == "ID" || itProperties.key() == "xsiType")
407       continue;
408 
409     urlParams[itProperties.key()] = itProperties.value();
410   }
411 
412   // Execute the update
413   QUuid queryID = this->session()->httpPut(this->resourceUri(), urlParams);
414   const QList<QVariantMap> results = this->session()->httpSync(queryID);
415 
416   // If this xnat object did not exist before on the server set the ID returned by Xnat
417   if (results.size() == 1 && results[0].size() == 2)
418   {
419     QVariant id = results[0][ID];
420     if (!id.isNull())
421     {
422       this->setId(id.toString());
423     }
424   }
425 
426   // Finally update the modification time on the server
427   remoteModTime = this->lastModifiedTimeOnServer();
428   d->lastModifiedTime = remoteModTime;
429 }
430 
431 //----------------------------------------------------------------------------
fetchResources(const QString & path)432 void ctkXnatObject::fetchResources(const QString& path)
433 {
434   Q_UNUSED(path);
435   ctkXnatResourceFolder* resFolder = new ctkXnatResourceFolder();
436   this->add(resFolder);
437 }
438 
439 //----------------------------------------------------------------------------
erase()440 void ctkXnatObject::erase()
441 {
442   this->session()->remove(this);
443   this->parent()->remove(this);
444 }
445