2
* Copyright (C) 2011 Tuomo Penttinen, all rights reserved.
4
* Author: Tuomo Penttinen <tp@herqq.org>
6
* This file is part of Herqq UPnP Av (HUPnPAv) library.
8
* Herqq UPnP Av is free software: you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation, either version 3 of the License, or
11
* (at your option) any later version.
13
* Herqq UPnP Av is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with Herqq UPnP Av. If not, see <http://www.gnu.org/licenses/>.
22
#include "hcontentdirectory_service.h"
23
#include "hcontentdirectory_service_p.h"
25
#include "hsearchresult.h"
26
#include "htransferprogressinfo.h"
28
#include "../cds_model/hsortinfo.h"
29
#include "../cds_model/model_mgmt/hcdsproperty.h"
30
#include "../cds_model/model_mgmt/hcdsproperty_db.h"
31
#include "../cds_model/model_mgmt/hcds_dlite_serializer.h"
33
#include <HUpnpCore/private/hlogger_p.h>
35
#include <QtCore/QSet>
36
#include <QtCore/QString>
37
#include <QtCore/QStringList>
38
#include <QtCore/QXmlStreamWriter>
49
/*******************************************************************************
50
* HContentDirectoryServicePrivate
51
******************************************************************************/
52
HContentDirectoryServicePrivate::HContentDirectoryServicePrivate() :
53
m_dataSource(0), m_lastEventSent(false), m_timer(), m_modificationEvents()
57
HContentDirectoryServicePrivate::~HContentDirectoryServicePrivate()
59
qDeleteAll(m_modificationEvents);
68
QList<HSortInfo> m_sortInfoObjects;
74
Sorter(const QList<HSortInfo>& infoObjects) :
75
m_sortInfoObjects(infoObjects)
79
bool operator()(HObject* obj1, HObject* obj2) const
84
for(qint32 i = 0; i < m_sortInfoObjects.size(); ++i)
87
HSortInfo si = m_sortInfoObjects[i];
88
QString property = si.property();
90
QVariant value1, value2;
91
if (obj1->getCdsProperty(property, &value1))
93
if (obj2->getCdsProperty(property, &value2))
95
HCdsProperty prop = HCdsPropertyDb::instance().property(property);
98
if (!prop.handler().comparer()(value1, value2, &rc))
108
return si.sortModifier().ascending();
112
return !si.sortModifier().ascending();
121
qint32 HContentDirectoryServicePrivate::sort(
122
const QStringList& sortCriteria, QList<HObject*>& objects)
124
HLOG2(H_AT, H_FUN, m_loggingIdentifier);
125
H_Q(HContentDirectoryService);
127
QList<HSortInfo> sortInfoObjects;
128
for (qint32 i = 0; i < sortCriteria.size(); ++i)
130
QString tmp = sortCriteria[i].trimmed();
139
QString sortExtension;
140
for(; j < tmp.size(); ++j)
143
if (ch == '+' || ch == '-')
147
sortExtension.append(ch);
150
if (ch != '+' && ch != '-')
152
return HContentDirectoryInfo::InvalidSortCriteria;
155
bool ascending = ch == '+';
157
QStringList sortExtensions;
158
q->getSortExtensionCapabilities(&sortExtensions);
160
if (!sortExtension.isEmpty() && !sortExtension.contains(
161
sortExtension, Qt::CaseInsensitive))
163
return HContentDirectoryInfo::InvalidSortCriteria;
166
QString sortProperty;
167
for(++j; j < tmp.size(); ++j)
170
sortProperty.append(ch);
173
QStringList sortCapabilities;
174
q->getSortCapabilities(&sortCapabilities);
176
if (!sortCapabilities.contains(sortProperty, Qt::CaseInsensitive) &&
177
!sortCapabilities.contains("*"))
179
return HContentDirectoryInfo::InvalidSortCriteria;
182
HSortModifier modifier(
184
sortExtension, ascending ? QString("+") : QString("-")));
186
HSortInfo so(sortProperty, modifier);
187
sortInfoObjects.append(so);
190
qStableSort(objects.begin(), objects.end(), Sorter(sortInfoObjects));
195
qint32 HContentDirectoryServicePrivate::browseDirectChildren(
196
const QString& containerId, const QSet<QString>& filter,
197
const QStringList& sortCriteria, quint32 startingIndex,
198
quint32 requestedCount, HSearchResult* result)
200
HLOG2(H_AT, H_FUN, m_loggingIdentifier);
201
H_Q(HContentDirectoryService);
203
HContainer* container = m_dataSource->findContainer(containerId);
207
"The specified object ID [%1] does not map to a container").arg(
210
return HContentDirectoryInfo::InvalidObjectId;
214
"Browsing container [id: %1, startingIndex: %2, "
215
"requestedCount: %3, filter: %4, sortCriteria: %5]").arg(
217
QString::number(startingIndex),
218
QString::number(requestedCount),
219
QStringList(filter.toList()).join(","),
220
sortCriteria.join(",")));
222
QSet<QString> childIDs = container->childIds();
223
quint32 childCount = static_cast<quint32>(childIDs.size());
225
if (startingIndex > childCount)
227
return UpnpInvalidArgs;
230
HObjects objects = m_dataSource->findObjects(childIDs);
231
Q_ASSERT(objects.size() == childIDs.size());
233
if (!sortCriteria.isEmpty())
235
qint32 rc = sort(sortCriteria, objects);
242
quint32 numberReturned = requestedCount > 0 ?
243
qMin(requestedCount, childCount - startingIndex) :
244
childCount - startingIndex;
246
objects = objects.mid(startingIndex, requestedCount ? requestedCount : -1);
248
HCdsDidlLiteSerializer ser;
249
QString dliteDoc = ser.serializeToXml(objects, filter);
251
HSearchResult retVal(
252
dliteDoc, numberReturned, childCount,
253
q->stateVariables().value("A_ARG_TYPE_UpdateID")->value().toUInt());
260
qint32 HContentDirectoryServicePrivate::browseMetadata(
261
const QString& objectId, const QSet<QString>& filter, quint32 startingIndex,
262
HSearchResult* result)
264
HLOG2(H_AT, H_FUN, m_loggingIdentifier);
265
H_Q(HContentDirectoryService);
270
"The starting index was specified as [%1], although it "
271
"should be zero when browsing meta data").arg(
272
QString::number(startingIndex)));
274
return UpnpInvalidArgs;
277
HObject* object = m_dataSource->findObject(objectId);
281
"No object was found with the specified object ID [%1]").arg(
284
return HContentDirectoryInfo::InvalidObjectId;
287
HCdsDidlLiteSerializer serializer;
288
QString dliteDoc = serializer.serializeToXml(
289
*object, filter, HCdsDidlLiteSerializer::Document);
291
HSearchResult retVal(
293
q->stateVariables().value("A_ARG_TYPE_UpdateID")->value().toUInt());
300
void HContentDirectoryServicePrivate::enableChangeTracking()
302
H_Q(HContentDirectoryService);
304
bool ok = QObject::connect(
305
m_dataSource, SIGNAL(objectModified(Herqq::Upnp::Av::HObject*, Herqq::Upnp::Av::HObjectEventInfo)),
306
q, SLOT(objectModified(Herqq::Upnp::Av::HObject*, Herqq::Upnp::Av::HObjectEventInfo)));
307
Q_ASSERT(ok); Q_UNUSED(ok)
309
ok = QObject::connect(
310
m_dataSource, SIGNAL(containerModified(Herqq::Upnp::Av::HContainer*, Herqq::Upnp::Av::HContainerEventInfo)),
311
q, SLOT(containerModified(Herqq::Upnp::Av::HContainer*, Herqq::Upnp::Av::HContainerEventInfo)));
314
ok = QObject::connect(
315
m_dataSource, SIGNAL(independentObjectAdded(Herqq::Upnp::Av::HObject*)),
316
q, SLOT(independentObjectAdded(Herqq::Upnp::Av::HObject*)));
319
foreach(HObject* object, m_dataSource->objects())
321
object->setTrackChangesOption(true);
327
QString HContentDirectoryServicePrivate::generateLastChange()
331
QXmlStreamWriter writer(&retVal);
333
writer.setCodec("UTF-8");
334
writer.writeStartDocument();
335
writer.writeStartElement("StateEvent");
336
writer.writeDefaultNamespace("urn:schemas-upnp-org:av:cds-event");
337
writer.writeAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
338
writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
340
writer.writeAttribute("xsi:schemaLocation",
341
"urn:schemas-upnp-org:av:cds-event" \
342
"http://www.upnp.org/schemas/av/cds-events.xsd");
344
foreach(const HModificationEvent* event, m_modificationEvents)
346
if (event->type() == HModificationEvent::ContainerModification)
348
HContainerEventInfo cEvent = event->containerEvent();
350
switch(cEvent.type())
352
case HContainerEventInfo::ChildAdded:
355
HObject* newObj = m_dataSource->findObject(cEvent.childId());
358
writer.writeStartElement("objAdd");
359
writer.writeAttribute("objParentID", event->source()->id());
360
writer.writeAttribute("objClass", newObj->clazz());
364
case HContainerEventInfo::ChildRemoved:
365
writer.writeStartElement("objDel");
367
case HContainerEventInfo::ChildModified:
368
writer.writeStartElement("objMod");
375
writer.writeAttribute("objID", cEvent.childId());
376
writer.writeAttribute("updateID", QString::number(cEvent.updateId()));
377
writer.writeAttribute("stUpdate", "0");
378
writer.writeEndElement();
384
HObjectEventInfo oEvent = event->objectEvent();
385
writer.writeStartElement("objMod");
386
writer.writeAttribute("objID", event->source()->id());
387
writer.writeAttribute("updateID", QString::number(oEvent.updateId()));
388
writer.writeAttribute("stUpdate", "0");
389
writer.writeEndElement();
393
writer.writeEndElement();
398
/*******************************************************************************
399
* HContentDirectoryService
400
******************************************************************************/
401
HContentDirectoryService::HContentDirectoryService(
402
HContentDirectoryServicePrivate& dd) :
403
HAbstractContentDirectoryService(dd)
405
H_D(HContentDirectoryService);
406
h->m_timer.setInterval(200);
407
bool ok = connect(&h->m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
408
Q_ASSERT(ok); Q_UNUSED(ok)
411
HContentDirectoryService::HContentDirectoryService(HAbstractCdsDataSource* dataSource) :
412
HAbstractContentDirectoryService(
413
*new HContentDirectoryServicePrivate())
415
H_D(HContentDirectoryService);
416
Q_ASSERT_X(dataSource, "", "Valid HCdsDataSource has to be provided");
417
h->m_dataSource = dataSource;
418
h->m_timer.setInterval(200);
419
bool ok = connect(&h->m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
420
Q_ASSERT(ok); Q_UNUSED(ok)
423
void HContentDirectoryService::timeout()
425
H_D(HContentDirectoryService);
426
if (!h->m_lastEventSent && h->m_modificationEvents.size())
428
QString lastChangeData = h->generateLastChange();
429
bool ok = setValue("LastChange", lastChangeData);
430
h->m_lastEventSent = true;
431
Q_ASSERT(ok); Q_UNUSED(ok)
435
void HContentDirectoryService::objectModified(
436
HObject* source, const HObjectEventInfo& eventInfo)
438
H_D(HContentDirectoryService);
439
if (h->m_lastEventSent)
441
h->m_modificationEvents.clear();
442
h->m_lastEventSent = false;
445
HObjectEventInfo einfo(eventInfo);
448
qint32 retVal = getSystemUpdateId(&sysUpdateId);
449
Q_ASSERT(retVal == UpnpSuccess); Q_UNUSED(retVal)
451
einfo.setUpdateId(sysUpdateId);
453
source->setObjectUpdateId(sysUpdateId);
455
h->m_modificationEvents.append(new HModificationEvent(source, einfo));
458
void HContentDirectoryService::containerModified(
459
HContainer* source, const HContainerEventInfo& eventInfo)
461
H_D(HContentDirectoryService);
463
if (eventInfo.type() == HContainerEventInfo::ChildAdded)
465
HItem* item = h->m_dataSource->findItem(eventInfo.childId());
468
if (stateVariables().contains("LastChange"))
470
item->setTrackChangesOption(true);
475
if (h->m_lastEventSent)
477
h->m_modificationEvents.clear();
478
h->m_lastEventSent = false;
481
HContainerEventInfo einfo(eventInfo);
484
qint32 retVal = getSystemUpdateId(&sysUpdateId);
485
Q_ASSERT(retVal == UpnpSuccess); Q_UNUSED(retVal)
487
einfo.setUpdateId(sysUpdateId);
489
source->setContainerUpdateId(sysUpdateId);
491
h->m_modificationEvents.append(new HModificationEvent(source, einfo));
494
void HContentDirectoryService::independentObjectAdded(HObject* source)
497
//H_D(HContentDirectoryService);
500
bool HContentDirectoryService::init()
502
H_D(HContentDirectoryService);
504
if (stateVariables().contains("LastChange"))
506
h->enableChangeTracking();
512
HContentDirectoryService::~HContentDirectoryService()
516
qint32 HContentDirectoryService::getSearchCapabilities(QStringList* oarg) const
518
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
519
Q_ASSERT_X(oarg, H_AT, "Out argument(s) cannot be null");
524
// "@id,@parentID,upnp:class,upnp:objectUpdateID,"
525
// "upnp:containerUpdateID,dc:title,dc:creator,dc:date,res@size").split(',');
526
// TODO once search is included
531
qint32 HContentDirectoryService::getSortCapabilities(QStringList* oarg) const
533
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
534
Q_ASSERT_X(oarg, H_AT, "Out argument(s) cannot be null");
536
*oarg = QString("dc:title,dc:creator,dc:date,res@size").split(',');
540
qint32 HContentDirectoryService::getSortExtensionCapabilities(
541
QStringList* oarg) const
543
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
544
Q_ASSERT_X(oarg, H_AT, "Out argument(s) cannot be null");
546
if (!actions().value("GetSortExtensionCapabilities"))
548
return UpnpOptionalActionNotImplemented;
551
*oarg = QString("+,-,TIME+,TIME-").split(',');
555
qint32 HContentDirectoryService::getFeatureList(QString* oarg) const
557
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
558
Q_ASSERT_X(oarg, H_AT, "Out argument(s) cannot be null");
564
qint32 HContentDirectoryService::getSystemUpdateId(quint32* oarg) const
566
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
567
Q_ASSERT_X(oarg, H_AT, "Out argument(s) cannot be null");
569
*oarg = stateVariables().value("SystemUpdateID")->value().toUInt();
573
qint32 HContentDirectoryService::getServiceResetToken(QString* oarg) const
575
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
576
Q_ASSERT_X(oarg, H_AT, "Out argument(s) cannot be null");
578
*oarg = stateVariables().value("ServiceResetToken")->value().toString();
582
qint32 HContentDirectoryService::browse(
583
const QString& objectId, HContentDirectoryInfo::BrowseFlag browseFlag,
584
const QSet<QString>& filter, quint32 startingIndex, quint32 requestedCount,
585
const QStringList& sortCriteria, HSearchResult* result)
587
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
588
Q_ASSERT_X(result, H_AT, "Out argument(s) cannot be null");
590
H_D(HContentDirectoryService);
595
return UpnpInvalidArgs;
598
HLOG_INFO(QString("processing browse request to object id %1").arg(objectId));
603
case HContentDirectoryInfo::BrowseDirectChildren:
605
retVal = h->browseDirectChildren(
606
objectId, filter, sortCriteria, startingIndex, requestedCount,
611
case HContentDirectoryInfo::BrowseMetadata:
613
retVal = h->browseMetadata(objectId, filter, startingIndex, result);
617
HLOG_WARN(QString("received invalid browse flag"));
618
retVal = UpnpInvalidArgs;
622
if (retVal != UpnpSuccess)
628
"Browse handled successfully: returned: [%1] matching objects of [%2] "
629
"possible totals.").arg(
630
QString::number(result->numberReturned()),
631
QString::number(result->totalMatches())));
636
qint32 HContentDirectoryService::search(
637
const QString& containerId, const QString& /*searchCriteria*/,
638
const QSet<QString>& /*filter*/, quint32 /*startingIndex*/,
639
quint32 /*requestedCount*/, const QStringList& /*sortCriteria*/,
640
HSearchResult* result)
642
H_D(HContentDirectoryService);
643
HLOG2(H_AT, H_FUN, h_ptr->m_loggingIdentifier);
648
return UpnpInvalidArgs;
651
if (!actions().value("Search"))
653
return UpnpOptionalActionNotImplemented;
656
HLOG_INFO(QString("attempting to locate container with id %1").arg(
659
HContainer* container = qobject_cast<HContainer*>(
660
h->m_dataSource->findObject(containerId));
664
return HContentDirectoryInfo::InvalidObjectId;