[release] version bump to 13.0 beta1
[vuplus_xbmc] / lib / libUPnP / Platinum / Source / Devices / MediaServer / PltMediaItem.cpp
1 /*****************************************************************
2 |
3 |   Platinum - AV Media Item
4 |
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
8 |
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
13 |
14 | OEMs, ISVs, VARs and other distributors that combine and 
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
21 |  
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 | GNU General Public License for more details.
26 |
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc., 
30 | 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
32 |
33 ****************************************************************/
34
35 /*----------------------------------------------------------------------
36 |   includes
37 +---------------------------------------------------------------------*/
38 #include "PltMediaItem.h"
39 #include "PltMediaServer.h"
40 #include "PltDidl.h"
41 #include "PltUtilities.h"
42 #include "PltService.h"
43 #include "PltMimeType.h"
44
45 NPT_SET_LOCAL_LOGGER("platinum.media.server.item")
46
47 /*----------------------------------------------------------------------
48 |   globals
49 +---------------------------------------------------------------------*/
50 NPT_DEFINE_DYNAMIC_CAST_ANCHOR(PLT_MediaObject)
51 NPT_DEFINE_DYNAMIC_CAST_ANCHOR(PLT_MediaItem)
52 NPT_DEFINE_DYNAMIC_CAST_ANCHOR(PLT_MediaContainer)
53
54 /*----------------------------------------------------------------------
55 |   PLT_PersonRoles::AddPerson
56 +---------------------------------------------------------------------*/
57 NPT_Result
58 PLT_PersonRoles::Add(const NPT_String& name, const NPT_String& role /* = "" */)
59 {
60     PLT_PersonRole person;
61     person.name = name;
62     person.role = role;
63
64     return NPT_List<PLT_PersonRole>::Add(person);
65 }
66
67 /*----------------------------------------------------------------------
68 |   PLT_PersonRoles::ToDidl
69 +---------------------------------------------------------------------*/
70 NPT_Result
71 PLT_PersonRoles::ToDidl(NPT_String& didl, const NPT_String& tag)
72 {
73     NPT_String tmp;
74     for (NPT_List<PLT_PersonRole>::Iterator it = 
75          NPT_List<PLT_PersonRole>::GetFirstItem(); it; it++) {
76         // if there's an empty artist, allow it only if there's nothing else
77         if (it->name.IsEmpty() && m_ItemCount>1 && !tmp.IsEmpty()) continue;
78
79         tmp += "<upnp:" + tag;
80         if (!it->role.IsEmpty()) {
81             tmp += " role=\"";
82             PLT_Didl::AppendXmlEscape(tmp, it->role);
83             tmp += "\"";
84         }
85         tmp += ">";
86         PLT_Didl::AppendXmlEscape(tmp, it->name);
87         tmp += "</upnp:" + tag + ">";
88     }
89
90     didl += tmp;
91     return NPT_SUCCESS;
92 }
93
94 /*----------------------------------------------------------------------
95 |   PLT_PersonRoles::ToDidl
96 +---------------------------------------------------------------------*/
97 NPT_Result
98 PLT_PersonRoles::FromDidl(const NPT_Array<NPT_XmlElementNode*>& nodes)
99 {
100     for (NPT_Cardinal i=0; i<nodes.GetItemCount(); i++) {
101         PLT_PersonRole person;
102         const NPT_String* name = nodes[i]->GetText();
103         const NPT_String* role = nodes[i]->GetAttribute("role");
104         // DLNA 7.3.17
105         if (name) person.name = name->SubString(0, 1024);
106         if (role) person.role = role->SubString(0, 1024);
107         NPT_CHECK(NPT_List<PLT_PersonRole>::Add(person));
108     }
109     return NPT_SUCCESS;
110 }
111
112 /*----------------------------------------------------------------------
113 |   PLT_MediaItemResource::PLT_MediaItemResource
114 +---------------------------------------------------------------------*/
115 PLT_MediaItemResource::PLT_MediaItemResource()
116 {
117     m_Uri             = "";
118     m_ProtocolInfo    = PLT_ProtocolInfo();
119     m_Duration        = (NPT_UInt32)-1;
120     m_Size            = (NPT_LargeSize)-1;
121     m_Protection      = "";
122     m_Bitrate         = (NPT_UInt32)-1;
123     m_BitsPerSample   = (NPT_UInt32)-1;
124     m_SampleFrequency = (NPT_UInt32)-1;
125     m_NbAudioChannels = (NPT_UInt32)-1;
126     m_Resolution      = "";
127     m_ColorDepth      = (NPT_UInt32)-1;
128 }
129
130 /*----------------------------------------------------------------------
131 |   PLT_MediaObject::GetUPnPClass
132 +---------------------------------------------------------------------*/
133 const char*
134 PLT_MediaObject::GetUPnPClass(const char*                   filename, 
135                               const PLT_HttpRequestContext* context /* = NULL */)
136 {
137     NPT_COMPILER_UNUSED(context);
138
139     const char* ret = NULL;
140     NPT_String mime_type = PLT_MimeType::GetMimeType(filename, context);
141
142     if (mime_type.StartsWith("audio")) {
143         ret = "object.item.audioItem.musicTrack";
144     } else if (mime_type.StartsWith("video")) {
145         ret = "object.item.videoItem"; //Note: 360 wants "object.item.videoItem" and not "object.item.videoItem.Movie"
146     } else if (mime_type.StartsWith("image")) {
147         ret = "object.item.imageItem.photo";
148     } else {
149         ret = "object.item";
150     }
151
152     return ret;
153 }
154
155 /*----------------------------------------------------------------------
156 |   PLT_MediaObject::Reset
157 +---------------------------------------------------------------------*/
158 NPT_Result
159 PLT_MediaObject::Reset() 
160 {
161     m_ObjectClass.type = "";
162     m_ObjectClass.friendly_name = "";
163     m_ObjectID   = "";
164     m_ParentID   = "";
165
166     m_Title      = "";
167     m_Creator    = "";
168     m_Date               = "";
169     m_Restricted = true;
170
171     m_People.actors.Clear();
172     m_People.artists.Clear();    
173     m_People.authors.Clear();
174     m_People.directors.Clear();
175
176     m_Affiliation.album     = "";
177     m_Affiliation.genres.Clear();
178     m_Affiliation.playlist  = "";
179
180     m_Description.description          = "";
181     m_Description.long_description     = "";
182     m_Description.icon_uri             = "";
183     m_ExtraInfo.album_arts.Clear();
184     m_ExtraInfo.artist_discography_uri = "";
185
186     m_MiscInfo.original_track_number = 0;
187     m_MiscInfo.last_position         = 0;
188     m_MiscInfo.last_time             = "";
189     m_MiscInfo.play_count            = -1;
190     m_MiscInfo.dvdregioncode             = 0;
191     m_MiscInfo.toc                                       = "";
192     m_MiscInfo.user_annotation           = "";
193
194     m_Recorded.program_title  = "";
195     m_Recorded.series_title   = "";
196     m_Recorded.episode_number = 0;
197
198     m_Resources.Clear();
199
200     m_Didl = "";
201
202     return NPT_SUCCESS;
203 }
204
205 /*----------------------------------------------------------------------
206 |   PLT_MediaObject::ToDidl
207 +---------------------------------------------------------------------*/
208 NPT_Result
209 PLT_MediaObject::ToDidl(const NPT_String& filter, NPT_String& didl)
210 {
211     return ToDidl(PLT_Didl::ConvertFilterToMask(filter), didl);
212 }
213
214 /*----------------------------------------------------------------------
215 |   PLT_MediaObject::ToDidl
216 +---------------------------------------------------------------------*/
217 NPT_Result
218 PLT_MediaObject::ToDidl(NPT_UInt64 mask, NPT_String& didl)
219 {
220     // title is required
221     didl += "<dc:title>";
222     PLT_Didl::AppendXmlEscape(didl, m_Title);
223     didl += "</dc:title>";
224
225     // creator
226     if (mask & PLT_FILTER_MASK_CREATOR) {
227         didl += "<dc:creator>";
228         if (m_Creator.IsEmpty()) m_Creator = "Unknown";
229         PLT_Didl::AppendXmlEscape(didl, m_Creator);
230         didl += "</dc:creator>";
231     }
232
233     // date
234     if ((mask & PLT_FILTER_MASK_DATE) && !m_Date.IsEmpty()) {
235         didl += "<dc:date>";
236         PLT_Didl::AppendXmlEscape(didl, m_Date);
237         didl += "</dc:date>";
238     } 
239
240     // artist
241     if (mask & PLT_FILTER_MASK_ARTIST) {
242         // force an empty artist just in case (not DLNA Compliant though)
243         //if (m_People.artists.GetItemCount() == 0) m_People.artists.Add("");
244         m_People.artists.ToDidl(didl, "artist");
245     }
246
247     // actor
248     if (mask & PLT_FILTER_MASK_ACTOR) {
249         m_People.actors.ToDidl(didl, "actor");
250     }
251
252     // actor
253     if (mask & PLT_FILTER_MASK_AUTHOR) {
254         m_People.authors.ToDidl(didl, "author");
255     }
256     
257     // director
258     if (mask & PLT_FILTER_MASK_DIRECTOR) {
259         m_People.directors.ToDidl(didl, "director");
260     }
261
262     // album
263     if ((mask & PLT_FILTER_MASK_ALBUM) && !m_Affiliation.album.IsEmpty()) {
264         didl += "<upnp:album>";
265         PLT_Didl::AppendXmlEscape(didl, m_Affiliation.album);
266         didl += "</upnp:album>";
267     }
268
269     // genre
270     if (mask & PLT_FILTER_MASK_GENRE) {
271         // Add unknown genre
272         if (m_Affiliation.genres.GetItemCount() == 0) 
273             m_Affiliation.genres.Add("Unknown");
274
275         for (NPT_List<NPT_String>::Iterator it = 
276              m_Affiliation.genres.GetFirstItem(); it; ++it) {
277             didl += "<upnp:genre>";
278             PLT_Didl::AppendXmlEscape(didl, (*it));
279             didl += "</upnp:genre>";        
280         }
281     }
282
283     // album art URI
284     if ((mask & PLT_FILTER_MASK_ALBUMARTURI) && m_ExtraInfo.album_arts.GetItemCount()) {
285         for (NPT_List<PLT_AlbumArtInfo>::Iterator iter = m_ExtraInfo.album_arts.GetFirstItem();
286              iter;
287              iter++) {
288             didl += "<upnp:albumArtURI";
289             if (!(*iter).dlna_profile.IsEmpty()) {
290                 didl += " dlna:profileID=\"";
291                 PLT_Didl::AppendXmlEscape(didl, (*iter).dlna_profile);
292                 didl += "\"";
293             }
294             didl += ">";
295             PLT_Didl::AppendXmlEscape(didl, (*iter).uri);
296             didl += "</upnp:albumArtURI>";
297         }
298     }
299     
300     // description
301     if ((mask & PLT_FILTER_MASK_DESCRIPTION) && !m_Description.description.IsEmpty()) {
302         didl += "<dc:description>";
303         PLT_Didl::AppendXmlEscape(didl, m_Description.description);
304         didl += "</dc:description>";
305     }
306
307     // long description
308     if ((mask & PLT_FILTER_MASK_LONGDESCRIPTION) && !m_Description.long_description.IsEmpty()) {
309         didl += "<upnp:longDescription>";
310         PLT_Didl::AppendXmlEscape(didl, m_Description.long_description);
311         didl += "</upnp:longDescription>";
312     }
313     
314     // icon
315     if ((mask & PLT_FILTER_MASK_ICON) && !m_Description.icon_uri.IsEmpty()) {
316         didl += "<upnp:icon>";
317         PLT_Didl::AppendXmlEscape(didl, m_Description.icon_uri);
318         didl += "</upnp:icon>";
319     }
320
321     // rating
322     if ((mask & PLT_FILTER_MASK_RATING) && !m_Description.rating.IsEmpty()) {
323         didl += "<upnp:rating>";
324         PLT_Didl::AppendXmlEscape(didl, m_Description.rating);
325         didl += "</upnp:rating>";
326     }
327
328     // original track number
329     if ((mask & PLT_FILTER_MASK_ORIGINALTRACK) && m_MiscInfo.original_track_number > 0) {
330         didl += "<upnp:originalTrackNumber>";
331         didl += NPT_String::FromInteger(m_MiscInfo.original_track_number);
332         didl += "</upnp:originalTrackNumber>";
333     }
334
335     // last playback position
336     if (mask & PLT_FILTER_MASK_LASTPOSITION && m_MiscInfo.last_position > 0) {
337         didl += "<upnp:lastPlaybackPosition>";
338         didl += NPT_String::FromInteger(m_MiscInfo.last_position);
339         didl += "</upnp:lastPlaybackPosition>";
340     }
341
342     // last playback datetime
343     if (mask & PLT_FILTER_MASK_LASTPLAYBACK && !m_MiscInfo.last_time.IsEmpty()) {
344         didl += "<upnp:lastPlaybackTime>";
345         PLT_Didl::AppendXmlEscape(didl, m_MiscInfo.last_time);
346         didl += "</upnp:lastPlaybackTime>";
347     }
348
349     // playcount
350     if (mask & PLT_FILTER_MASK_PLAYCOUNT && m_MiscInfo.play_count > -1) {
351         didl += "<upnp:playbackCount>";
352         didl += NPT_String::FromInteger(m_MiscInfo.play_count);
353         didl += "</upnp:playbackCount>";
354     }
355
356     // program title
357     if (mask & PLT_FILTER_MASK_PROGRAMTITLE && !m_Recorded.program_title.IsEmpty()) {
358         didl += "<upnp:programTitle>";
359         PLT_Didl::AppendXmlEscape(didl, m_Recorded.program_title);
360         didl += "</upnp:programTitle>";
361     }
362
363     // series title
364     if ((mask & PLT_FILTER_MASK_SERIESTITLE) && !m_Recorded.series_title.IsEmpty()) {
365         didl += "<upnp:seriesTitle>";
366         PLT_Didl::AppendXmlEscape(didl, m_Recorded.series_title);
367         didl += "</upnp:seriesTitle>";
368     }
369
370     // episode number
371     if ((mask & PLT_FILTER_MASK_EPISODE) && m_Recorded.episode_number > 0) {
372         didl += "<upnp:episodeNumber>";
373         didl += NPT_String::FromInteger(m_Recorded.episode_number);
374         didl += "</upnp:episodeNumber>";
375     }
376
377         if ((mask & PLT_FILTER_MASK_TOC) & !m_MiscInfo.toc.IsEmpty()) {
378         didl += "<upnp:toc>";
379                 PLT_Didl::AppendXmlEscape(didl, m_MiscInfo.toc);
380         didl += "</upnp:toc>";
381     }
382
383     // resource
384     if (mask & PLT_FILTER_MASK_RES) {
385         for (NPT_Cardinal i=0; i<m_Resources.GetItemCount(); i++) {
386             didl += "<res";
387             
388             if ((mask & PLT_FILTER_MASK_RES_DURATION) && m_Resources[i].m_Duration != (NPT_UInt32)-1) {
389                 didl += " duration=\"";
390                 didl += PLT_Didl::FormatTimeStamp(m_Resources[i].m_Duration);
391                 didl += "\"";
392             }
393
394             if ((mask & PLT_FILTER_MASK_RES_SIZE) && m_Resources[i].m_Size != (NPT_LargeSize)-1) {
395                 didl += " size=\"";
396                 didl += NPT_String::FromIntegerU(m_Resources[i].m_Size);
397                 didl += "\"";
398             }
399
400             if ((mask & PLT_FILTER_MASK_RES_PROTECTION) && !m_Resources[i].m_Protection.IsEmpty()) {
401                 didl += " protection=\"";
402                 PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_Protection);
403                 didl += "\"";
404             }
405             
406             if ((mask & PLT_FILTER_MASK_RES_RESOLUTION) && !m_Resources[i].m_Resolution.IsEmpty()) {
407                 didl += " resolution=\"";
408                 PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_Resolution);
409                 didl += "\"";
410             }
411             
412             if ((mask & PLT_FILTER_MASK_RES_BITRATE) && m_Resources[i].m_Bitrate != (NPT_Size)-1) {                    
413                 didl += " bitrate=\"";
414                 didl += NPT_String::FromIntegerU(m_Resources[i].m_Bitrate);
415                 didl += "\"";
416             }
417
418                         if ((mask & PLT_FILTER_MASK_RES_BITSPERSAMPLE) && m_Resources[i].m_BitsPerSample != (NPT_Size)-1) {                    
419                 didl += " bitsPerSample=\"";
420                 didl += NPT_String::FromIntegerU(m_Resources[i].m_BitsPerSample);
421                 didl += "\"";
422             }
423
424                         if ((mask & PLT_FILTER_MASK_RES_SAMPLEFREQUENCY) && m_Resources[i].m_SampleFrequency != (NPT_Size)-1) {                    
425                 didl += " sampleFrequency=\"";
426                 didl += NPT_String::FromIntegerU(m_Resources[i].m_SampleFrequency);
427                 didl += "\"";
428             }
429
430                         if ((mask & PLT_FILTER_MASK_RES_NRAUDIOCHANNELS) && m_Resources[i].m_NbAudioChannels != (NPT_Size)-1) {                    
431                 didl += " nrAudioChannels=\"";
432                 didl += NPT_String::FromIntegerU(m_Resources[i].m_NbAudioChannels);
433                 didl += "\"";
434             }
435             
436             didl += " protocolInfo=\"";
437             PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_ProtocolInfo.ToString());
438             didl += "\">";
439             PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_Uri);
440             didl += "</res>";
441         }
442     }
443
444     // class is required
445     didl += "<upnp:class";
446         if (!m_ObjectClass.friendly_name.IsEmpty()) {
447                 didl += " name=\"" + m_ObjectClass.friendly_name+"\"";
448         }
449         didl += ">";
450     PLT_Didl::AppendXmlEscape(didl, m_ObjectClass.type);
451     didl += "</upnp:class>";
452
453     return NPT_SUCCESS;
454 }
455
456 /*----------------------------------------------------------------------
457 |   PLT_MediaObject::FromDidl
458 +---------------------------------------------------------------------*/
459 NPT_Result
460 PLT_MediaObject::FromDidl(NPT_XmlElementNode* entry)
461 {
462     NPT_String str, xml;
463     NPT_Array<NPT_XmlElementNode*> children;
464     NPT_Result res;
465
466     // check if item is restricted (is default true?)
467     if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "restricted", str, "", 5))) {
468         m_Restricted = PLT_Service::IsTrue(str);
469     }
470     
471     // read non-required elements
472     PLT_XmlHelper::GetChildText(entry, "creator", m_Creator, didl_namespace_dc, 256);
473     PLT_XmlHelper::GetChildText(entry, "date", m_Date, didl_namespace_dc, 256);
474     
475     // parse date and make sure it's valid
476     NPT_String parsed_date;
477     for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) {
478         NPT_DateTime date;
479         if (NPT_SUCCEEDED(date.FromString(m_Date, (NPT_DateTime::Format)format))) {
480             parsed_date = date.ToString((NPT_DateTime::Format)format);
481             break;
482         }
483     }
484     m_Date = parsed_date;
485
486     res = PLT_XmlHelper::GetAttribute(entry, "id", m_ObjectID);
487     NPT_CHECK_SEVERE(res);
488
489     res = PLT_XmlHelper::GetAttribute(entry, "parentID", m_ParentID);
490     NPT_CHECK_SEVERE(res);
491
492     PLT_XmlHelper::GetAttribute(entry, "refID", m_ReferenceID);
493
494     res = PLT_XmlHelper::GetChildText(entry, "title", m_Title, didl_namespace_dc);
495     NPT_CHECK_SEVERE(res);
496     
497     res = PLT_XmlHelper::GetChildText(entry, "class", m_ObjectClass.type, didl_namespace_upnp);
498     NPT_CHECK_SEVERE(res);
499     
500     // DLNA 7.3.17.3 max bytes for dc:title and upnp:class is 256 bytes
501     m_Title = m_Title.SubString(0, 256);    
502     m_ObjectClass.type =  m_ObjectClass.type.SubString(0, 256);
503
504     children.Clear();
505     PLT_XmlHelper::GetChildren(entry, children, "artist", didl_namespace_upnp);
506     m_People.artists.FromDidl(children);
507     
508     children.Clear();
509     PLT_XmlHelper::GetChildren(entry, children, "author", didl_namespace_upnp);
510     m_People.authors.FromDidl(children);
511     
512     children.Clear();
513     PLT_XmlHelper::GetChildren(entry, children, "actor", didl_namespace_upnp);
514     m_People.actors.FromDidl(children);
515
516     children.Clear();
517     PLT_XmlHelper::GetChildren(entry, children, "director", didl_namespace_upnp);
518     m_People.directors.FromDidl(children);
519
520     PLT_XmlHelper::GetChildText(entry, "album", m_Affiliation.album, didl_namespace_upnp, 256);
521     PLT_XmlHelper::GetChildText(entry, "programTitle", m_Recorded.program_title, didl_namespace_upnp);
522     PLT_XmlHelper::GetChildText(entry, "seriesTitle", m_Recorded.series_title, didl_namespace_upnp);
523     PLT_XmlHelper::GetChildText(entry, "episodeNumber", str, didl_namespace_upnp);
524     NPT_UInt32 value;
525     if (NPT_FAILED(str.ToInteger(value))) value = 0;
526     m_Recorded.episode_number = value;
527
528     children.Clear();
529     PLT_XmlHelper::GetChildren(entry, children, "genre", didl_namespace_upnp);
530     for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
531         if (children[i]->GetText()) {
532             m_Affiliation.genres.Add(children[i]->GetText()->SubString(0, 256));
533         }
534     }
535     
536     PLT_XmlHelper::GetChildText(entry, "description", m_Description.description, didl_namespace_dc);
537     PLT_XmlHelper::GetChildText(entry, "longDescription", m_Description.long_description, didl_namespace_upnp);
538     PLT_XmlHelper::GetChildText(entry, "icon", m_Description.icon_uri, didl_namespace_upnp);
539     PLT_XmlHelper::GetChildText(entry, "rating", m_Description.rating, didl_namespace_upnp);
540         PLT_XmlHelper::GetChildText(entry, "toc", m_MiscInfo.toc, didl_namespace_upnp);
541     
542     // album arts
543     children.Clear();
544     PLT_XmlHelper::GetChildren(entry, children, "albumArtURI", didl_namespace_upnp);
545     for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
546         if (children[i]->GetText()) {
547             PLT_AlbumArtInfo info;
548             info.uri = children[i]->GetText()->SubString(0, 1024);
549             PLT_XmlHelper::GetAttribute(children[i], "profileID", info.dlna_profile, didl_namespace_dlna);
550             m_ExtraInfo.album_arts.Add(info);
551         }
552     }
553     
554     PLT_XmlHelper::GetChildText(entry, "originalTrackNumber", str, didl_namespace_upnp);
555     if (NPT_FAILED(str.ToInteger(value))) value = 0;
556     m_MiscInfo.original_track_number = value;
557
558     PLT_XmlHelper::GetChildText(entry, "lastPlaybackPosition", str, didl_namespace_upnp);
559     if (NPT_FAILED(str.ToInteger(value))) value = 0;
560     m_MiscInfo.last_position = value;
561
562     PLT_XmlHelper::GetChildText(entry, "lastPlaybackTime", m_MiscInfo.last_time, didl_namespace_dc, 256);
563     NPT_String parsed_last_time;
564     for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) {
565         NPT_DateTime date;
566         if (NPT_SUCCEEDED(date.FromString(m_MiscInfo.last_time, (NPT_DateTime::Format)format))) {
567             parsed_last_time = date.ToString((NPT_DateTime::Format)format);
568             break;
569         }
570     }
571     m_MiscInfo.last_time = parsed_last_time;
572
573     PLT_XmlHelper::GetChildText(entry, "playbackCount", str, didl_namespace_upnp);
574     if (NPT_FAILED(str.ToInteger(value))) value = -1;
575     m_MiscInfo.play_count = value;
576
577     children.Clear();
578     PLT_XmlHelper::GetChildren(entry, children, "res");
579     for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
580         PLT_MediaItemResource resource;
581
582         // extract url
583         if (children[i]->GetText() == NULL) {
584             NPT_LOG_WARNING_1("No resource text found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i]));
585         } else {
586             resource.m_Uri = children[i]->GetText()->SubString(0, 1024);
587
588             // basic uri validation, ignoring scheme (could be rtsp)
589             NPT_HttpUrl url(resource.m_Uri, true);
590             if (!url.IsValid()) {
591                 NPT_LOG_WARNING_1("Invalid resource uri: %s", (const char*)resource.m_Uri);
592                 continue;
593             }
594         }
595
596         // extract protocol info
597         NPT_String protocol_info;
598         res = PLT_XmlHelper::GetAttribute(children[i], "protocolInfo", protocol_info, "", 256);
599         if (NPT_FAILED(res)) {
600             NPT_LOG_WARNING_1("No protocol info found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i]));
601         } else {
602             resource.m_ProtocolInfo = PLT_ProtocolInfo(protocol_info);
603             if (!resource.m_ProtocolInfo.IsValid()) {
604                 NPT_LOG_WARNING_1("Invalid resource protocol info: %s", (const char*)protocol_info);
605             }
606         }
607
608         // extract known attributes
609         PLT_XmlHelper::GetAttribute(children[i], "protection", resource.m_Protection, "", 256);
610         PLT_XmlHelper::GetAttribute(children[i], "resolution", resource.m_Resolution, "", 256);
611
612         if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "size", str, "", 256))) {
613             if (NPT_FAILED(str.ToInteger64(resource.m_Size))) resource.m_Size = (NPT_Size)-1;
614         }
615
616         if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "duration", str, "", 256))) {
617             if (NPT_FAILED(PLT_Didl::ParseTimeStamp(str, resource.m_Duration))) {
618                 // if error while converting, ignore and set to -1 to indicate we don't know the duration
619                 resource.m_Duration = (NPT_UInt32)-1;
620                 PLT_XmlHelper::RemoveAttribute(children[i], "duration");
621             } else {
622                 // DLNA: reformat duration in case it was not compliant
623                 str = PLT_Didl::FormatTimeStamp(resource.m_Duration);
624                 PLT_XmlHelper::SetAttribute(children[i], "duration", str); 
625             }
626         }    
627         m_Resources.Add(resource);
628     }
629
630     // re serialize the entry didl as a we might need to pass it to a renderer
631     // we may have modified the tree to "fix" issues, so as not to break a renderer
632     // (don't write xml prefix as this didl could be part of a larger document)
633     //res = PLT_XmlHelper::Serialize(*entry, xml, false);
634     m_Didl = "";
635     res = ToDidl(PLT_FILTER_MASK_ALL, m_Didl);
636     NPT_CHECK_SEVERE(res);
637     
638     m_Didl = didl_header + m_Didl + didl_footer;    
639     return NPT_SUCCESS;
640 }
641
642 /*----------------------------------------------------------------------
643 |   PLT_MediaObjectList::PLT_MediaObjectList
644 +---------------------------------------------------------------------*/
645 PLT_MediaObjectList::PLT_MediaObjectList()
646 {
647 }
648
649 /*----------------------------------------------------------------------
650 |   PLT_MediaObjectList::~PLT_MediaObjectList
651 +---------------------------------------------------------------------*/
652 PLT_MediaObjectList::~PLT_MediaObjectList()
653 {
654     Apply(NPT_ObjectDeleter<PLT_MediaObject>());
655 }
656
657 /*----------------------------------------------------------------------
658 |   PLT_MediaItem::PLT_MediaItem
659 +---------------------------------------------------------------------*/
660 PLT_MediaItem::PLT_MediaItem()
661 {
662     Reset();
663 }
664
665 /*----------------------------------------------------------------------
666 |   PLT_MediaItem::~PLT_MediaItem
667 +---------------------------------------------------------------------*/
668 PLT_MediaItem::~PLT_MediaItem()
669 {
670 }
671
672 /*----------------------------------------------------------------------
673 |   PLT_MediaItem::ToDidl
674 +---------------------------------------------------------------------*/
675 NPT_Result
676 PLT_MediaItem::ToDidl(const NPT_String& filter, NPT_String& didl)
677 {
678     return PLT_MediaObject::ToDidl(filter, didl);
679 }
680
681 /*----------------------------------------------------------------------
682 |   PLT_MediaItem::ToDidl
683 +---------------------------------------------------------------------*/
684 NPT_Result
685 PLT_MediaItem::ToDidl(NPT_UInt64 mask, NPT_String& didl)
686 {
687     didl += "<item id=\"";
688
689     PLT_Didl::AppendXmlEscape(didl, m_ObjectID);
690     didl += "\" parentID=\"";
691     PLT_Didl::AppendXmlEscape(didl, m_ParentID);
692
693     if ((mask & PLT_FILTER_MASK_REFID) && !m_ReferenceID.IsEmpty()) {
694                 didl += "\" refID=\"";
695                 PLT_Didl::AppendXmlEscape(didl, m_ReferenceID);
696     }
697
698     didl += "\" restricted=\"";
699     didl += m_Restricted?"1\"":"0\"";
700
701     didl += ">";
702
703     NPT_CHECK_SEVERE(PLT_MediaObject::ToDidl(mask, didl));
704
705     /* close tag */
706     didl += "</item>";
707
708     return NPT_SUCCESS;
709 }
710
711 /*----------------------------------------------------------------------
712 |   PLT_MediaItem::FromDidl
713 +---------------------------------------------------------------------*/
714 NPT_Result
715 PLT_MediaItem::FromDidl(NPT_XmlElementNode* entry)
716 {
717     /* reset first */
718     Reset();
719
720     if (entry->GetTag().Compare("item", true) != 0) {
721         NPT_CHECK_SEVERE(NPT_ERROR_INTERNAL);
722     }
723
724     NPT_Result result = PLT_MediaObject::FromDidl(entry);
725
726     return result;
727 }
728
729 /*----------------------------------------------------------------------
730 |   PLT_MediaContainer::PLT_MediaContainer
731 +---------------------------------------------------------------------*/
732 PLT_MediaContainer::PLT_MediaContainer()
733 {
734     Reset();
735 }
736
737 /*----------------------------------------------------------------------
738 |   PLT_MediaContainer::~PLT_MediaContainer
739 +---------------------------------------------------------------------*/
740 PLT_MediaContainer::~PLT_MediaContainer(void)
741 {
742 }
743
744 /*----------------------------------------------------------------------
745 |   PLT_MediaContainer::Reset
746 +---------------------------------------------------------------------*/
747 NPT_Result
748 PLT_MediaContainer::Reset() 
749 {
750     m_SearchClasses.Clear();
751     m_Searchable        = false;
752     m_ChildrenCount     = -1;
753     m_ContainerUpdateID = 0;
754
755     return PLT_MediaObject::Reset();
756 }
757
758 /*----------------------------------------------------------------------
759 |   PLT_MediaContainer::ToDidl
760 +---------------------------------------------------------------------*/
761 NPT_Result
762 PLT_MediaContainer::ToDidl(const NPT_String& filter, NPT_String& didl)
763 {
764     return PLT_MediaObject::ToDidl(filter, didl);
765 }
766
767 /*----------------------------------------------------------------------
768 |   PLT_MediaContainer::ToDidl
769 +---------------------------------------------------------------------*/
770 NPT_Result
771 PLT_MediaContainer::ToDidl(NPT_UInt64 mask, NPT_String& didl)
772 {
773         // container id property
774     didl += "<container id=\"";
775     PLT_Didl::AppendXmlEscape(didl, m_ObjectID);
776
777         // parent id property
778     didl += "\" parentID=\"";
779     PLT_Didl::AppendXmlEscape(didl, m_ParentID);
780         
781         // ref id
782     if ((mask & PLT_FILTER_MASK_REFID) && !m_ReferenceID.IsEmpty()) {
783                 didl += "\" refID=\"";
784                 PLT_Didl::AppendXmlEscape(didl, m_ReferenceID);
785     }
786
787         // restricted property
788     didl += "\" restricted=\"";
789     didl += m_Restricted?"1\"":"0\"";
790
791         // searchable property
792     if (mask & PLT_FILTER_MASK_SEARCHABLE) {
793         didl += " searchable=\"";
794         didl += m_Searchable?"1\"":"0\"";
795     }
796     
797         // childcount property
798     if ((mask & PLT_FILTER_MASK_CHILDCOUNT) && m_ChildrenCount != -1) {
799         didl += " childCount=\"";
800         didl += NPT_String::FromInteger(m_ChildrenCount);
801         didl += "\"";
802     }
803
804     didl += ">";
805
806         if ((mask & PLT_FILTER_MASK_SEARCHCLASS) && m_SearchClasses.GetItemCount()) {
807                 NPT_List<PLT_SearchClass>::Iterator search_class = m_SearchClasses.GetFirstItem();
808                 while (search_class) {
809                         didl += "<upnp:searchClass includeDerived=\"";
810                         didl += (*search_class).include_derived?"1\"":"0\"";
811
812                         // frienly name is any
813                         if (!(*search_class).friendly_name.IsEmpty()) {
814                                 didl += " name=\"" + (*search_class).friendly_name + "\"";
815                         }
816                         didl += ">";
817                         didl += (*search_class).type;
818                         didl += "</upnp:searchClass>";
819
820                         ++search_class;
821                 }
822         }
823
824     NPT_CHECK_SEVERE(PLT_MediaObject::ToDidl(mask, didl));
825
826     /* close tag */
827     didl += "</container>";
828     return NPT_SUCCESS;
829 }
830
831 /*----------------------------------------------------------------------
832 |   PLT_MediaContainer::FromDidl
833 +---------------------------------------------------------------------*/
834 NPT_Result
835 PLT_MediaContainer::FromDidl(NPT_XmlElementNode* entry)
836 {
837     NPT_String str;
838
839     /* reset first */
840     Reset();
841
842     // check entry type
843     if (entry->GetTag().Compare("Container", true) != 0) 
844         return NPT_ERROR_INTERNAL;
845
846     // check if item is searchable (is default true?)
847     if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "searchable", str, "", 5))) {
848         m_Searchable = PLT_Service::IsTrue(str);
849     }
850
851     // look for childCount
852     if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "childCount", str, "", 256))) {
853         NPT_UInt32 count;
854         NPT_CHECK_SEVERE(str.ToInteger(count));
855         m_ChildrenCount = count;
856     }
857
858         // upnp:searchClass child elements
859     NPT_Array<NPT_XmlElementNode*> children;
860         PLT_XmlHelper::GetChildren(entry, children, "upnp:searchClass");
861
862     for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
863         PLT_SearchClass search_class;
864
865         // extract url
866         if (children[i]->GetText() == NULL) {
867             NPT_LOG_WARNING_1("No searchClass text found in: %s", 
868                                 (const char*)PLT_XmlHelper::Serialize(*children[i]));
869                         continue;
870         }
871                 
872         // DLNA 7.3.17.4
873                 search_class.type = children[i]->GetText()->SubString(0, 256);
874
875                 // extract optional attribute name
876                 PLT_XmlHelper::GetAttribute(children[i], "name", search_class.friendly_name);
877                     
878                 // includeDerived property
879                 if (NPT_FAILED(PLT_XmlHelper::GetAttribute(children[i], "includeDerived", str))) {
880             NPT_LOG_WARNING_1("No required attribute searchClass@includeDerived found in: %s", 
881                                 (const char*)PLT_XmlHelper::Serialize(*children[i]));
882                         continue;
883                 }
884
885                 search_class.include_derived = PLT_Service::IsTrue(str);
886                 m_SearchClasses.Add(search_class);
887         }
888
889     return PLT_MediaObject::FromDidl(entry);
890 }