added: much better improved UPnP Renderer support. Now provides didl for currently...
[xbmc:xbmc-antiquated.git] / xbmc / lib / libUPnP / Platinum / Source / Core / PltService.cpp
1 /*****************************************************************
2 |
3 |   Platinum - Service
4 |
5 |   Copyright (c) 2004-2008 Sylvain Rebaud
6 |   Author: Sylvain Rebaud (sylvain@rebaud.com)
7 |
8  ****************************************************************/
9
10 /*----------------------------------------------------------------------
11 |   includes
12 +---------------------------------------------------------------------*/
13 #include "PltService.h"
14 #include "PltSsdp.h"
15 #include "PltUPnP.h"
16 #include "PltDeviceData.h"
17 #include "PltXmlHelper.h"
18
19 NPT_SET_LOCAL_LOGGER("platinum.core.service")
20
21 /*----------------------------------------------------------------------
22 |   PLT_Service::PLT_Service
23 +---------------------------------------------------------------------*/
24 PLT_Service::PLT_Service(PLT_DeviceData* device,
25                          const char*     type, 
26                          const char*     id) :  
27     m_Device(device),
28     m_ServiceType(type),
29     m_ServiceID(id),
30     m_EventTask(NULL)
31 {
32 }
33
34 /*----------------------------------------------------------------------
35 |   PLT_Service::~PLT_Service
36 +---------------------------------------------------------------------*/
37 PLT_Service::~PLT_Service()
38 {
39     Cleanup();
40 }
41
42 /*----------------------------------------------------------------------
43  |   PLT_Service::~PLT_Service
44  +---------------------------------------------------------------------*/
45  void
46  PLT_Service::Cleanup()
47  {
48      m_ActionDescs.Apply(NPT_ObjectDeleter<PLT_ActionDesc>());
49      m_StateVars.Apply(NPT_ObjectDeleter<PLT_StateVariable>());
50      m_Subscribers.Apply(NPT_ObjectDeleter<PLT_EventSubscriber>());
51
52      m_ActionDescs.Clear();
53      m_StateVars.Clear();
54      m_Subscribers.Clear();
55  }
56
57 /*----------------------------------------------------------------------
58 |   PLT_Service::GetSCPDXML
59 +---------------------------------------------------------------------*/
60 NPT_Result
61 PLT_Service::GetSCPDXML(NPT_String& scpd)
62 {
63     // it is required to have at least 1 state variable
64     if (m_StateVars.GetItemCount() == 0) return NPT_FAILURE;
65
66     NPT_XmlElementNode* top = new NPT_XmlElementNode("scpd");
67     NPT_CHECK_SEVERE(top->SetNamespaceUri("", "urn:schemas-upnp-org:service-1-0"));
68
69     // add spec version
70     NPT_XmlElementNode* spec = new NPT_XmlElementNode("specVersion");
71     NPT_CHECK_SEVERE(top->AddChild(spec));
72     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(spec, "major", "1"));
73     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(spec, "minor", "0"));
74
75     // add actions
76     NPT_XmlElementNode* actionList = new NPT_XmlElementNode("actionList");
77     NPT_CHECK_SEVERE(top->AddChild(actionList));
78     NPT_CHECK_SEVERE(m_ActionDescs.ApplyUntil(PLT_GetSCPDXMLIterator<PLT_ActionDesc>(actionList), 
79         NPT_UntilResultNotEquals(NPT_SUCCESS)));
80
81     // add service state table
82     NPT_XmlElementNode* serviceStateTable = new NPT_XmlElementNode("serviceStateTable");
83     NPT_CHECK_SEVERE(top->AddChild(serviceStateTable));
84     NPT_CHECK_SEVERE(m_StateVars.ApplyUntil(PLT_GetSCPDXMLIterator<PLT_StateVariable>(serviceStateTable), 
85         NPT_UntilResultNotEquals(NPT_SUCCESS)));
86
87     // serialize node
88     NPT_CHECK_SEVERE(PLT_XmlHelper::Serialize(*top, scpd));
89     delete top;
90
91     return NPT_SUCCESS;
92 }
93
94 /*----------------------------------------------------------------------
95 |   PLT_Service::ToXML
96 +---------------------------------------------------------------------*/
97 NPT_Result
98 PLT_Service::GetDescription(NPT_XmlElementNode* parent, NPT_XmlElementNode** service_out /* = NULL */)
99 {
100     NPT_XmlElementNode* service = new NPT_XmlElementNode("service");
101     if (service_out) {
102         *service_out = service;
103     }
104     NPT_CHECK_SEVERE(parent->AddChild(service));
105     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "serviceType", m_ServiceType));
106     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "serviceId", m_ServiceID));
107     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "SCPDURL", GetSCPDURL()));
108     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "controlURL", GetControlURL()));
109     NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "eventSubURL", GetEventSubURL()));
110
111     return NPT_SUCCESS;
112 }
113
114 /*----------------------------------------------------------------------
115 |   PLT_Service::InitURLs
116 +---------------------------------------------------------------------*/
117 NPT_Result
118 PLT_Service::InitURLs(const char* service_name, 
119                       const char* device_uuid)
120 {
121     m_SCPDURL  = service_name;
122     m_SCPDURL += "/" + NPT_String(device_uuid) + NPT_String("/scpd.xml");
123     m_ControlURL  = service_name;
124     m_ControlURL += "/" + NPT_String(device_uuid) + NPT_String("/control.xml");
125     m_EventSubURL  = service_name;
126     m_EventSubURL += "/" + NPT_String(device_uuid) + NPT_String("/event.xml");
127     
128     return NPT_SUCCESS;
129 }
130
131 /*----------------------------------------------------------------------
132 |   PLT_Service::SetSCPDXML
133 +---------------------------------------------------------------------*/
134 NPT_Result
135 PLT_Service::SetSCPDXML(const char* scpd)
136 {
137     if (scpd == NULL) return NPT_FAILURE;
138
139     Cleanup();
140
141     NPT_XmlParser parser;
142     NPT_XmlNode*  tree = NULL;
143     NPT_Result    res;
144     NPT_Array<NPT_XmlElementNode*> stateVariables;
145     NPT_Array<NPT_XmlElementNode*> actions;
146
147     res = parser.Parse(scpd, tree);
148     if (NPT_FAILED(res)) {
149         delete tree;
150         return res;
151     }
152
153     // make sure root tag is right
154     NPT_XmlElementNode* root = tree->AsElementNode();
155     if (!root || NPT_String::Compare(root->GetTag(), "scpd")) {
156         delete tree;
157         return NPT_FAILURE;
158     }
159
160     // make sure we have required children presents
161     NPT_XmlElementNode* actionList = PLT_XmlHelper::GetChild(root, "actionList");
162     NPT_XmlElementNode* stateTable = PLT_XmlHelper::GetChild(root, "serviceStateTable");
163     if (!actionList || !stateTable || !actionList->GetChildren().GetItemCount() || !stateTable->GetChildren().GetItemCount()) {
164         goto failure;
165     }
166
167     // stateVariable table
168     if (NPT_FAILED(PLT_XmlHelper::GetChildren(stateTable, stateVariables, "stateVariable"))) {
169         goto failure;
170     }
171
172     for( int k = 0 ; k < (int)stateVariables.GetItemCount(); k++) {
173         NPT_String name, type, send;
174         PLT_XmlHelper::GetChildText(stateVariables[k], "name", name);
175         PLT_XmlHelper::GetChildText(stateVariables[k], "dataType", type);
176         PLT_XmlHelper::GetAttribute(stateVariables[k], "sendEvents", send);
177         if (name.GetLength() == 0 || type.GetLength() == 0) {
178             goto failure;
179         }
180         PLT_StateVariable* variable = new PLT_StateVariable(this);
181         m_StateVars.Add(variable);
182
183         variable->m_Name = name;
184         variable->m_DataType = type;
185         variable->m_IsSendingEvents = IsTrue(send); // could it be true/false ?
186         PLT_XmlHelper::GetChildText(stateVariables[k], "defaultValue", variable->m_DefaultValue);
187
188         NPT_XmlElementNode* allowedValueList = PLT_XmlHelper::GetChild(stateVariables[k], "allowedValueList");
189         if (allowedValueList) {
190             NPT_Array<NPT_XmlElementNode*> allowedValues;
191             PLT_XmlHelper::GetChildren(allowedValueList, allowedValues, "allowedValue");
192             for( int l = 0 ; l < (int)allowedValues.GetItemCount(); l++) {
193                 const NPT_String* text = allowedValues[l]->GetText();
194                 if (text) {
195                     variable->m_AllowedValues.Add(new NPT_String(*text));
196                 }
197             }
198         } else {
199             NPT_XmlElementNode* allowedValueRange = PLT_XmlHelper::GetChild(stateVariables[k], "allowedValueRange");
200             if (allowedValueRange) {
201                 NPT_String min, max, step;
202                 PLT_XmlHelper::GetChildText(allowedValueRange, "minimum", min);
203                 PLT_XmlHelper::GetChildText(allowedValueRange, "maximum", max);
204                 PLT_XmlHelper::GetChildText(allowedValueRange, "step", step);
205                 if (min.GetLength() == 0 || max.GetLength() == 0) {
206                     goto failure;
207                 }
208                 variable->m_AllowedValueRange = new NPT_AllowedValueRange;
209                 NPT_ParseInteger(min, variable->m_AllowedValueRange->min_value);
210                 NPT_ParseInteger(max, variable->m_AllowedValueRange->max_value);
211                 variable->m_AllowedValueRange->step = -1;
212                 if (step.GetLength() != 0) {
213                     NPT_ParseInteger(step, variable->m_AllowedValueRange->step);
214                 }
215             }
216         }
217     }
218
219     // actions
220     if (NPT_FAILED(PLT_XmlHelper::GetChildren(actionList, actions, "action"))) {
221         goto failure;
222     }
223
224     for( int i = 0 ; i < (int)actions.GetItemCount(); i++) {
225         NPT_String action_name;
226         PLT_XmlHelper::GetChildText(actions[i],  "name", action_name);
227
228         // action arguments
229         NPT_XmlElementNode* argumentList = PLT_XmlHelper::GetChild(actions[i], "argumentList");
230         if (action_name.GetLength() == 0 || argumentList == NULL || !argumentList->GetChildren().GetItemCount()) {
231             goto failure;
232         }
233
234         PLT_ActionDesc* action_desc = new PLT_ActionDesc(action_name, this);
235         m_ActionDescs.Add(action_desc);
236
237         NPT_Array<NPT_XmlElementNode*> arguments;
238         NPT_CHECK_SEVERE(PLT_XmlHelper::GetChildren(argumentList, arguments, "argument"));
239         bool foundRetValue = false;
240         for( int j = 0 ; j < (int)arguments.GetItemCount(); j++) {
241             NPT_String name, direction, relatedStateVar;
242             PLT_XmlHelper::GetChildText(arguments[j], "name", name);
243             PLT_XmlHelper::GetChildText(arguments[j], "direction", direction);
244             PLT_XmlHelper::GetChildText(arguments[j], "relatedStateVariable", relatedStateVar);
245             if (name.GetLength() == 0 || direction.GetLength() == 0 || relatedStateVar.GetLength() == 0) {
246                 goto failure;
247             }
248
249             // make sure the related state variable exists
250             PLT_StateVariable* variable = FindStateVariable(relatedStateVar);
251             if (variable == NULL) {
252                 goto failure;
253             }
254
255             bool bReturnValue = false;
256             NPT_XmlElementNode* retval_node = PLT_XmlHelper::GetChild(arguments[j], "retVal");
257             if (retval_node) {
258                 // verify this is the only retVal we've had
259                 if (foundRetValue) {
260                     goto failure;
261                 } else {
262                     bReturnValue = true;
263                     foundRetValue = true;
264                 }
265             }
266             action_desc->GetArgumentDescs().Add(new PLT_ArgumentDesc(name, direction, variable, bReturnValue));
267         }
268     }
269
270     // delete the tree
271     delete tree;
272
273     return NPT_SUCCESS;
274
275 failure:
276     delete tree;
277     return NPT_FAILURE;
278 }
279
280 /*----------------------------------------------------------------------
281 |   PLT_Service::FindActionDesc
282 +---------------------------------------------------------------------*/
283 PLT_ActionDesc*
284 PLT_Service::FindActionDesc(const char* name)
285 {
286     PLT_ActionDesc* action = NULL;
287     NPT_ContainerFind(m_ActionDescs, PLT_ActionDescNameFinder(this, name), action);
288     return action;
289 }
290
291 /*----------------------------------------------------------------------
292 |   PLT_Service::FindStateVariable
293 +---------------------------------------------------------------------*/
294 PLT_StateVariable*
295 PLT_Service::FindStateVariable(const char* name)
296 {
297     PLT_StateVariable* stateVariable = NULL;
298     NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
299     return stateVariable;
300 }
301
302 /*----------------------------------------------------------------------
303 |   PLT_Service::GetStateVariableValue
304 +---------------------------------------------------------------------*/
305 NPT_Result
306 PLT_Service::GetStateVariableValue(const char* name, NPT_String& value)
307 {
308     PLT_StateVariable* stateVariable = FindStateVariable(name);
309     NPT_CHECK_POINTER_FATAL(stateVariable);
310     value = stateVariable->GetValue();
311     return NPT_SUCCESS;
312 }
313
314 /*----------------------------------------------------------------------
315 |   PLT_Service::IsSubscribable
316 +---------------------------------------------------------------------*/
317 bool
318 PLT_Service::IsSubscribable()
319 {
320     NPT_List<PLT_StateVariable*>::Iterator var = m_StateVars.GetFirstItem();
321     while (var) {
322         if ((*var)->IsSendingEvents()) return true;
323         ++var;
324     }
325     return false;
326 }
327
328 /*----------------------------------------------------------------------
329 |   PLT_Service::SetStateVariable
330 +---------------------------------------------------------------------*/
331 NPT_Result
332 PLT_Service::SetStateVariable(const char* name, const char* value, bool publish)
333 {
334     PLT_StateVariable* stateVariable = NULL;
335     NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
336     if (stateVariable == NULL)
337         return NPT_FAILURE;
338
339     return stateVariable->SetValue(value, publish);
340 }
341
342 /*----------------------------------------------------------------------
343 |   PLT_Service::SetStateVariableRate
344 +---------------------------------------------------------------------*/
345 NPT_Result
346 PLT_Service::SetStateVariableRate(const char* name, NPT_TimeInterval rate)
347 {
348     PLT_StateVariable* stateVariable = NULL;
349     NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
350     if (stateVariable == NULL)
351         return NPT_FAILURE;
352
353     return stateVariable->SetRate(rate);
354 }
355
356 /*----------------------------------------------------------------------
357 |   PLT_Service::IncStateVariable
358 +---------------------------------------------------------------------*/
359 NPT_Result
360 PLT_Service::IncStateVariable(const char* name, bool publish)
361 {
362     PLT_StateVariable* stateVariable = NULL;
363     NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
364     if (stateVariable == NULL)
365         return NPT_FAILURE;
366
367     NPT_String value = stateVariable->GetValue();
368     long num;
369     if (value.GetLength() == 0 || NPT_FAILED(value.ToInteger(num))) {
370         return NPT_FAILURE;
371     }
372
373     // convert value to int
374     return stateVariable->SetValue(NPT_String::FromInteger(num+1), publish);
375 }
376
377 /*----------------------------------------------------------------------
378 |   PLT_Service::ProcessNewSubscription
379 +---------------------------------------------------------------------*/
380 NPT_Result
381 PLT_Service::ProcessNewSubscription(PLT_TaskManager*         task_manager,
382                                     const NPT_SocketAddress& addr, 
383                                     const NPT_String&        callback_urls, 
384                                     int                      timeout, 
385                                     NPT_HttpResponse&        response)
386 {
387 //    // first look if we don't have a subscriber with same callbackURL
388 //    PLT_EventSubscriber* subscriber = NULL;
389 //    if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers, PLT_EventSubscriberFinderByCallbackURL(strCallbackURL),
390 //        subscriber))) {
391 //        // update local interface and timeout
392 //        subscriber->m_local_if.SetIpAddress((unsigned long) addr.GetIpAddress());
393 //        subscriber->m_ExpirationTime = NPT_Time(NULL) + timeout;
394 //
395 //        PLT_UPnPMessageHelper::SetSID("uuid:" + subscriber->m_sid);
396 //        PLT_UPnPMessageHelper::SetTimeOut(timeout);
397 //        return NPT_SUCCESS;
398 //    }
399 //
400     // reject if we have too many subscribers already
401     if (m_Subscribers.GetItemCount() > 30) {
402         response.SetStatus(500, "Internal Server Error");
403         return NPT_FAILURE;
404     }
405
406     PLT_EventSubscriber* subscriber = new PLT_EventSubscriber(task_manager, this);
407     // parse the callback URLs
408     bool reachable = false;
409     if (callback_urls[0] == '<') {
410         char* szURLs = (char*)(const char*)callback_urls;
411         char* brackL = szURLs;
412         char* brackR = szURLs;
413         while (++brackR < szURLs + callback_urls.GetLength()) {
414             if (*brackR == '>') {
415                 NPT_String strCallbackURL = NPT_String(brackL+1, (NPT_Size)(brackR-brackL-1));
416                 NPT_HttpUrl url(strCallbackURL);
417                 if (url.IsValid()) {
418                     subscriber->AddCallbackURL(strCallbackURL);
419                     reachable = true;
420                 }
421                 brackL = ++brackR;
422             }
423         }
424     }
425
426     if (reachable == false) {
427         response.SetStatus(412, "Precondition Failed");
428         return NPT_FAILURE;
429     }
430
431     // keep track of which interface we receive the request, we will use this one
432     // when notifying
433     subscriber->SetLocalIf(addr);
434
435     // keep track of subscriber lifetime
436     // -1 means infinite so we set an expiration time of 0
437     if (timeout == -1) {
438         subscriber->SetExpirationTime(NPT_TimeStamp(0, 0));
439     } else {
440         NPT_TimeStamp life;
441         NPT_System::GetCurrentTimeStamp(life);
442         life += NPT_TimeInterval(timeout, 0);
443         subscriber->SetExpirationTime(life);    
444     }
445
446     // generate a unique subscriber ID
447     NPT_String sid;
448     PLT_UPnPMessageHelper::GenerateUUID(19, sid);
449     subscriber->SetSID("uuid:" + sid);
450     PLT_UPnPMessageHelper::SetSID(response, subscriber->GetSID());
451     PLT_UPnPMessageHelper::SetTimeOut(response, timeout);
452
453     {
454         NPT_AutoLock lock(m_Lock);
455
456         // new subscriber should get all vars in the LastChange var
457         UpdateLastChange(m_StateVars);
458
459         // send all state vars to sub
460         subscriber->Notify(m_StateVars);
461
462         if (m_StateVarsChanged.GetItemCount()) {
463             // reset lastchange to what was really just changed
464             UpdateLastChange(m_StateVarsChanged);
465         } else {
466             // remove LastChange variable from vars to publish next time
467             // as we just added it for that new subscriber when we called
468             // UpdateLastChange
469             PLT_StateVariable* var = FindStateVariable("LastChange");
470             if (var) m_StateVarsToPublish.Remove(var);
471         }
472
473         if (!m_EventTask) {
474             m_EventTask = new PLT_ServiceEventTask(this);
475             task_manager->StartTask(m_EventTask);
476         }
477
478         m_Subscribers.Add(subscriber);
479     }
480
481     return NPT_SUCCESS;
482 }
483
484 /*----------------------------------------------------------------------
485 |   PLT_Service::ProcessRenewSubscription
486 +---------------------------------------------------------------------*/
487 NPT_Result
488 PLT_Service::ProcessRenewSubscription(const NPT_SocketAddress& addr, 
489                                       const NPT_String&        sid, 
490                                       int                      timeout, 
491                                       NPT_HttpResponse&        response)
492 {
493     NPT_AutoLock lock(m_Lock);
494
495     // first look if we don't have a subscriber with same callbackURL
496     PLT_EventSubscriber* subscriber = NULL;
497     if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers, 
498                                         PLT_EventSubscriberFinderBySID(sid), 
499                                         subscriber))) {
500         // update local interface and timeout
501         subscriber->SetLocalIf(addr);
502
503         // keep track of subscriber lifetime
504         // -1 means infinite so we set an expiration time of 0
505         if (timeout == -1) {
506             subscriber->SetExpirationTime(NPT_TimeStamp(0, 0));
507         } else {
508             NPT_TimeStamp life;
509             NPT_System::GetCurrentTimeStamp(life);
510             life += NPT_TimeInterval(timeout, 0);
511             subscriber->SetExpirationTime(life);
512         }
513
514         PLT_UPnPMessageHelper::SetSID(response, subscriber->GetSID());
515         PLT_UPnPMessageHelper::SetTimeOut(response, timeout);
516         return NPT_SUCCESS;
517     }
518
519     // didn't find a valid Subscriber in our list
520     response.SetStatus(412, "Precondition Failed");
521     return NPT_FAILURE;
522 }
523
524 /*----------------------------------------------------------------------
525 |   PLT_Service::ProcessCancelSubscription
526 +---------------------------------------------------------------------*/
527 NPT_Result
528 PLT_Service::ProcessCancelSubscription(const NPT_SocketAddress& /* addr */, 
529                                        const NPT_String&        sid, 
530                                        NPT_HttpResponse&        response)
531 {
532     NPT_AutoLock lock(m_Lock);
533
534     // first look if we don't have a subscriber with same callbackURL
535     PLT_EventSubscriber* sub = NULL;
536     if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers, 
537                                         PLT_EventSubscriberFinderBySID(sid), 
538                                         sub))) {
539
540         // update local interface and timeout
541         m_Subscribers.Remove(sub);
542         sub->Cancel();
543         delete sub;
544         return NPT_SUCCESS;
545     }
546
547     // didn't find a valid Subscriber in our list
548     response.SetStatus(412, "Precondition Failed");
549     return NPT_FAILURE;
550 }
551
552 /*----------------------------------------------------------------------
553 |   PLT_Service::AddChanged
554 +---------------------------------------------------------------------*/
555 NPT_Result
556 PLT_Service::AddChanged(PLT_StateVariable* var)
557 {
558     NPT_AutoLock lock(m_Lock);
559
560     // no event task means no subscribers yet, so don't bother
561     if (!m_EventTask) return NPT_SUCCESS;
562     
563     if (var->IsSendingEvents()) {
564         if (!m_StateVarsToPublish.Contains(var)) 
565             m_StateVarsToPublish.Add(var);
566     } else {
567         if (!m_StateVarsChanged.Contains(var)) 
568             m_StateVarsChanged.Add(var);
569         UpdateLastChange(m_StateVarsChanged);
570     }
571
572     return NPT_SUCCESS;
573 }
574
575 /*----------------------------------------------------------------------
576 |   PLT_Service::UpdateLastChange
577 +---------------------------------------------------------------------*/
578 NPT_Result
579 PLT_Service::UpdateLastChange(NPT_List<PLT_StateVariable*>& vars)
580 {
581     PLT_StateVariable* var = FindStateVariable("LastChange");
582     if (var == NULL) return NPT_FAILURE;
583
584     if (vars.GetItemCount() == 0) return NPT_SUCCESS;
585
586     NPT_XmlElementNode* top = new NPT_XmlElementNode("Event");
587     NPT_CHECK_SEVERE(top->SetNamespaceUri("", "urn:schemas-upnp-org:metadata-1-0/AVT_RCS"));
588
589     NPT_XmlElementNode* instance = new NPT_XmlElementNode("InstanceID");
590     NPT_CHECK_SEVERE(top->AddChild(instance));
591     NPT_CHECK_SEVERE(instance->SetAttribute("val", "0"));
592
593     // build list of changes
594     NPT_CHECK_SEVERE(vars.Apply(PLT_LastChangeXMLIterator(instance)));
595
596     // serialize node
597     NPT_String value;
598     NPT_CHECK_SEVERE(PLT_XmlHelper::Serialize(*top, value));
599     delete top;
600
601     // set the state change but don't publish (to avoid recursive lock)
602     // instead add var to publish here directly
603     var->SetValue((const char*)value, false);
604     if (!m_StateVarsToPublish.Contains(var)) m_StateVarsToPublish.Add(var);
605     return NPT_SUCCESS;
606 }
607
608 /*----------------------------------------------------------------------
609 |   PLT_Service::NotifyChanged
610 +---------------------------------------------------------------------*/
611 NPT_Result
612 PLT_Service::NotifyChanged()
613 {
614     NPT_AutoLock lock(m_Lock);
615
616     // pick the vars that are ready to be published
617     // based on their moderation rate and last publication
618     NPT_List<PLT_StateVariable*> vars_ready;
619     NPT_List<PLT_StateVariable*>::Iterator iter = m_StateVarsToPublish.GetFirstItem();
620     while (iter) {
621         PLT_StateVariable* var = *iter;
622         if (var->IsReadyToPublish()) {
623             vars_ready.Add(var);
624             m_StateVarsToPublish.Erase(iter++);
625
626             // clear last changed list if we're about to send LastChange var
627             if (!var->GetName().Compare("LastChange")) m_StateVarsChanged.Clear();
628         } else {
629             iter++;
630         }
631     }
632     
633     if (vars_ready.GetItemCount()) {
634         int i = 0;
635         int count = m_Subscribers.GetItemCount();
636         while (i++ < count) {
637             PLT_EventSubscriber* sub;
638             if (NPT_SUCCEEDED(m_Subscribers.PopHead(sub))) {
639                 NPT_TimeStamp now, expiration;
640                 NPT_System::GetCurrentTimeStamp(now);
641                 expiration = sub->GetExpirationTime();
642
643                 // forget sub if it didn't renew in time or if notification failed
644                 if (NPT_SUCCEEDED(sub->Notify(vars_ready)) &&
645                     (expiration == NPT_TimeStamp() || expiration > now )) {
646                     m_Subscribers.Add(sub);
647                 } else {
648                     sub->Cancel();
649                     delete sub;
650                 }
651             }
652         }
653     }
654
655     return NPT_SUCCESS;
656 }
657
658 /*----------------------------------------------------------------------
659 |   PLT_ServiceSCPDURLFinder::operator()
660 +---------------------------------------------------------------------*/
661 bool 
662 PLT_ServiceSCPDURLFinder::operator()(PLT_Service* const & service) const 
663 {
664     NPT_String url = service->GetSCPDURL();
665     if (!url.StartsWith("/")) {
666         url = service->GetDevice()->GetURLBase().GetPath() + url;
667     }
668     return m_URL.Compare(url, true) ? false : true;
669 }
670
671 /*----------------------------------------------------------------------
672 |   PLT_ServiceControlURLFinder::operator()
673 +---------------------------------------------------------------------*/
674 bool 
675 PLT_ServiceControlURLFinder::operator()(PLT_Service* const & service) const 
676 {
677     NPT_String url = service->GetControlURL();
678     if (!url.StartsWith("/")) {
679         url = service->GetDevice()->GetURLBase().GetPath() + url;
680     }
681     return m_URL.Compare(url, true) ? false : true;
682 }
683
684 /*----------------------------------------------------------------------
685 |   PLT_ServiceEventSubURLFinder::operator()
686 +---------------------------------------------------------------------*/
687 bool
688 PLT_ServiceEventSubURLFinder::operator()(PLT_Service* const & service) const 
689 {
690     NPT_String url = service->GetEventSubURL();
691     if (!url.StartsWith("/")) {
692         url = service->GetDevice()->GetURLBase().GetPath() + url;
693     }
694     return m_URL.Compare(url, true) ? false : true;
695 }
696
697 /*----------------------------------------------------------------------
698 |   PLT_ServiceIDFinder::operator()
699 +---------------------------------------------------------------------*/
700 bool
701 PLT_ServiceIDFinder::operator()(PLT_Service* const & service) const 
702 {
703     return m_Id.Compare(service->GetServiceID(), true) ? false : true;
704 }
705
706 /*----------------------------------------------------------------------
707 |   PLT_ServiceTypeFinder::operator()
708 +---------------------------------------------------------------------*/
709 bool 
710 PLT_ServiceTypeFinder::operator()(PLT_Service* const & service) const 
711 {
712     return m_Type.Compare(service->GetServiceType(), true) ? false : true;
713 }
714
715 /*----------------------------------------------------------------------
716 |   PLT_GetLastChangeXMLIterator::operator()
717 +---------------------------------------------------------------------*/
718 NPT_Result
719 PLT_LastChangeXMLIterator::operator()(PLT_StateVariable* const &var) const
720 {   
721     // only add vars are indirectly evented
722     if (var->IsSendingEvents() || var->GetName().StartsWith("A_ARG_TYPE_")) 
723         return NPT_SUCCESS;
724
725     NPT_XmlElementNode* variable = new NPT_XmlElementNode((const char*)var->GetName());
726     NPT_CHECK_SEVERE(m_Node->AddChild(variable));
727     NPT_CHECK_SEVERE(variable->SetAttribute("val", var->GetValue()));
728     if(var->GetName() == "Volume" || var->GetName() == "VolumeDB" || var->GetName() == "Mute") {
729         if(var->GetService()->GetServiceType() == "urn:schemas-upnp-org:service:RenderingControl:1") {
730             NPT_CHECK_SEVERE(variable->SetAttribute("channel", "Master"));
731         }
732     }
733     return NPT_SUCCESS;
734 }