[bluray] Fix stream info/language retrieval for blurays in non-nav mode.
[vuplus_xbmc] / xbmc / network / upnp / UPnPServer.cpp
1 #include "UPnPServer.h"
2 #include "UPnPInternal.h"
3 #include "Application.h"
4 #include "view/GUIViewState.h"
5 #include "Platinum.h"
6 #include "video/VideoThumbLoader.h"
7 #include "music/Artist.h"
8 #include "music/MusicThumbLoader.h"
9 #include "interfaces/AnnouncementManager.h"
10 #include "filesystem/Directory.h"
11 #include "filesystem/MusicDatabaseDirectory.h"
12 #include "filesystem/SpecialProtocol.h"
13 #include "filesystem/VideoDatabaseDirectory.h"
14 #include "guilib/WindowIDs.h"
15 #include "music/tags/MusicInfoTag.h"
16 #include "settings/AdvancedSettings.h"
17 #include "settings/Settings.h"
18 #include "utils/log.h"
19 #include "utils/md5.h"
20 #include "utils/StringUtils.h"
21 #include "utils/URIUtils.h"
22 #include "Util.h"
23 #include "music/MusicDatabase.h"
24 #include "video/VideoDatabase.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "xbmc/GUIUserMessages.h"
27 #include "utils/FileUtils.h"
28
29 using namespace std;
30 using namespace ANNOUNCEMENT;
31 using namespace XFILE;
32
33 namespace UPNP
34 {
35
36 NPT_UInt32 CUPnPServer::m_MaxReturnedItems = 0;
37
38 const char* audio_containers[] = { "musicdb://genres/", "musicdb://artists/", "musicdb://albums/",
39                                    "musicdb://songs/", "musicdb://recentlyaddedalbums/", "musicdb://years/",
40                                    "musicdb://singles/" };
41
42 const char* video_containers[] = { "library://video/movies/titles.xml/", "library://video/tvshows/titles.xml/",
43                                    "videodb://recentlyaddedmovies/", "videodb://recentlyaddedepisodes/"  };
44
45 /*----------------------------------------------------------------------
46 |   CUPnPServer::CUPnPServer
47 +---------------------------------------------------------------------*/
48 CUPnPServer::CUPnPServer(const char* friendly_name, const char* uuid /*= NULL*/, int port /*= 0*/) :
49     PLT_MediaConnect(friendly_name, false, uuid, port),
50     PLT_FileMediaConnectDelegate("/", "/"),
51     m_scanning(g_application.IsMusicScanning() || g_application.IsVideoScanning())
52 {
53 }
54
55 CUPnPServer::~CUPnPServer()
56 {
57     ANNOUNCEMENT::CAnnouncementManager::RemoveAnnouncer(this);
58 }
59
60 /*----------------------------------------------------------------------
61 |   CUPnPServer::ProcessGetSCPD
62 +---------------------------------------------------------------------*/
63 NPT_Result
64 CUPnPServer::ProcessGetSCPD(PLT_Service*                  service,
65                             NPT_HttpRequest&              request,
66                             const NPT_HttpRequestContext& context,
67                             NPT_HttpResponse&             response)
68 {
69   // needed because PLT_MediaConnect only allows Xbox360 & WMP to search
70   return PLT_MediaServer::ProcessGetSCPD(service, request, context, response);
71 }
72
73 /*----------------------------------------------------------------------
74 |   CUPnPServer::SetupServices
75 +---------------------------------------------------------------------*/
76 NPT_Result
77 CUPnPServer::SetupServices()
78 {
79     PLT_MediaConnect::SetupServices();
80     PLT_Service* service = NULL;
81     NPT_Result result = FindServiceById("urn:upnp-org:serviceId:ContentDirectory", service);
82     if (service)
83       service->SetStateVariable("SortCapabilities", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
84
85     m_scanning = true;
86     OnScanCompleted(AudioLibrary);
87     m_scanning = true;
88     OnScanCompleted(VideoLibrary);
89
90     // now safe to start passing on new notifications
91     ANNOUNCEMENT::CAnnouncementManager::AddAnnouncer(this);
92
93     return result;
94 }
95
96 /*----------------------------------------------------------------------
97 |   CUPnPServer::OnScanCompleted
98 +---------------------------------------------------------------------*/
99 void
100 CUPnPServer::OnScanCompleted(int type)
101 {
102     if (type == AudioLibrary) {
103         for (size_t i = 0; i < sizeof(audio_containers)/sizeof(audio_containers[0]); i++)
104             UpdateContainer(audio_containers[i]);
105     }
106     else if (type == VideoLibrary) {
107         for (size_t i = 0; i < sizeof(video_containers)/sizeof(video_containers[0]); i++)
108             UpdateContainer(video_containers[i]);
109     }
110     else
111         return;
112     m_scanning = false;
113     PropagateUpdates();
114 }
115
116 /*----------------------------------------------------------------------
117 |   CUPnPServer::UpdateContainer
118 +---------------------------------------------------------------------*/
119 void
120 CUPnPServer::UpdateContainer(const string& id)
121 {
122     map<string,pair<bool, unsigned long> >::iterator itr = m_UpdateIDs.find(id);
123     unsigned long count = 0;
124     if (itr != m_UpdateIDs.end())
125         count = ++itr->second.second;
126     m_UpdateIDs[id] = make_pair(true, count);
127     PropagateUpdates();
128 }
129
130 /*----------------------------------------------------------------------
131 |   CUPnPServer::PropagateUpdates
132 +---------------------------------------------------------------------*/
133 void
134 CUPnPServer::PropagateUpdates()
135 {
136     PLT_Service* service = NULL;
137     NPT_String current_ids;
138     string buffer;
139     map<string,pair<bool, unsigned long> >::iterator itr;
140
141     if (m_scanning || !CSettings::Get().GetBool("services.upnpannounce"))
142         return;
143
144     NPT_CHECK_LABEL(FindServiceById("urn:upnp-org:serviceId:ContentDirectory", service), failed);
145
146     // we pause, and we must retain any changes which have not been
147     // broadcast yet
148     NPT_CHECK_LABEL(service->PauseEventing(), failed);
149     NPT_CHECK_LABEL(service->GetStateVariableValue("ContainerUpdateIDs", current_ids), failed);
150     buffer = (const char*)current_ids;
151     if (!buffer.empty())
152         buffer.append(",");
153
154     // only broadcast ids with modified bit set
155     for (itr = m_UpdateIDs.begin(); itr != m_UpdateIDs.end(); ++itr) {
156         if (itr->second.first) {
157             buffer.append(StringUtils::Format("%s,%ld,", itr->first.c_str(), itr->second.second).c_str());
158             itr->second.first = false;
159         }
160     }
161
162     // set the value, Platinum will clear ContainerUpdateIDs after sending
163     NPT_CHECK_LABEL(service->SetStateVariable("ContainerUpdateIDs", buffer.substr(0,buffer.size()-1).c_str(), true), failed);
164     NPT_CHECK_LABEL(service->IncStateVariable("SystemUpdateID"), failed);
165
166     service->PauseEventing(false);
167     return;
168
169 failed:
170     // should attempt to start eventing on a failure
171     if (service) service->PauseEventing(false);
172     CLog::Log(LOGERROR, "UPNP: Unable to propagate updates");
173 }
174
175 /*----------------------------------------------------------------------
176 |   CUPnPServer::SetupIcons
177 +---------------------------------------------------------------------*/
178 NPT_Result
179 CUPnPServer::SetupIcons()
180 {
181     NPT_String file_root = CSpecialProtocol::TranslatePath("special://xbmc/media/").c_str();
182     AddIcon(
183         PLT_DeviceIcon("image/png", 256, 256, 24, "/icon-flat-256x256.png"),
184         file_root);
185     AddIcon(
186         PLT_DeviceIcon("image/png", 120, 120, 24, "/icon-flat-120x120.png"),
187         file_root);
188     return NPT_SUCCESS;
189 }
190
191 /*----------------------------------------------------------------------
192 |   CUPnPServer::BuildSafeResourceUri
193 +---------------------------------------------------------------------*/
194 NPT_String CUPnPServer::BuildSafeResourceUri(const NPT_HttpUrl &rooturi,
195                                              const char* host,
196                                              const char* file_path)
197 {
198     CURL url(file_path);
199     CStdString md5;
200     XBMC::XBMC_MD5 md5state;
201
202     // determine the filename to provide context to md5'd urls
203     CStdString filename;
204     if (url.GetProtocol() == "image")
205       filename = URIUtils::GetFileName(url.GetHostName());
206     else
207       filename = URIUtils::GetFileName(file_path);
208
209     CURL::Encode(filename);
210     md5state.append(file_path);
211     md5state.getDigest(md5);
212     md5 += "/" + filename;
213     { NPT_AutoLock lock(m_FileMutex);
214       NPT_CHECK(m_FileMap.Put(md5.c_str(), file_path));
215     }
216     return PLT_FileMediaServer::BuildSafeResourceUri(rooturi, host, md5.c_str());
217 }
218
219 /*----------------------------------------------------------------------
220 |   CUPnPServer::Build
221 +---------------------------------------------------------------------*/
222 PLT_MediaObject*
223 CUPnPServer::Build(CFileItemPtr                  item,
224                    bool                          with_count,
225                    const PLT_HttpRequestContext& context,
226                    NPT_Reference<CThumbLoader>&  thumb_loader,
227                    const char*                   parent_id /* = NULL */)
228 {
229     PLT_MediaObject* object = NULL;
230     NPT_String       path = item->GetPath().c_str();
231
232     //HACK: temporary disabling count as it thrashes HDD
233     with_count = false;
234
235     CLog::Log(LOGDEBUG, "Preparing upnp object for item '%s'", (const char*)path);
236
237     if (path == "virtualpath://upnproot") {
238         path.TrimRight("/");
239         if (path.StartsWith("virtualpath://")) {
240             object = new PLT_MediaContainer;
241             object->m_Title = item->GetLabel();
242             object->m_ObjectClass.type = "object.container";
243             object->m_ObjectID = path;
244
245             // root
246             object->m_ObjectID = "0";
247             object->m_ParentID = "-1";
248             // root has 5 children
249             if (with_count) {
250                 ((PLT_MediaContainer*)object)->m_ChildrenCount = 5;
251             }
252         } else {
253             goto failure;
254         }
255
256     } else {
257         // db path handling
258         NPT_String file_path, share_name;
259         file_path = item->GetPath();
260         share_name = "";
261
262         if (path.StartsWith("musicdb://")) {
263             if (path == "musicdb://" ) {
264                 item->SetLabel("Music Library");
265                 item->SetLabelPreformated(true);
266             } else {
267                 if (!item->HasMusicInfoTag()) {
268                     MUSICDATABASEDIRECTORY::CQueryParams params;
269                     MUSICDATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo((const char*)path, params);
270
271                     CMusicDatabase db;
272                     if (!db.Open() ) return NULL;
273
274                     if (params.GetSongId() >= 0 ) {
275                         CSong song;
276                         if (db.GetSong(params.GetSongId(), song))
277                             item->GetMusicInfoTag()->SetSong(song);
278                     }
279                     else if (params.GetAlbumId() >= 0 ) {
280                         CAlbum album;
281                         if (db.GetAlbumInfo(params.GetAlbumId(), album, NULL))
282                             item->GetMusicInfoTag()->SetAlbum(album);
283                     }
284                     else if (params.GetArtistId() >= 0 ) {
285                         CArtist artist;
286                         if (db.GetArtistInfo(params.GetArtistId(), artist, false))
287                             item->GetMusicInfoTag()->SetArtist(artist);
288                     }
289                 }
290
291
292                 if (item->GetLabel().IsEmpty()) {
293                     /* if no label try to grab it from node type */
294                     CStdString label;
295                     if (CMusicDatabaseDirectory::GetLabel((const char*)path, label)) {
296                         item->SetLabel(label);
297                         item->SetLabelPreformated(true);
298                     }
299                 }
300             }
301         } else if (file_path.StartsWith("library://") || file_path.StartsWith("videodb://")) {
302             if (path == "library://video" ) {
303                 item->SetLabel("Video Library");
304                 item->SetLabelPreformated(true);
305             } else {
306                 if (!item->HasVideoInfoTag()) {
307                     VIDEODATABASEDIRECTORY::CQueryParams params;
308                     VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo((const char*)path, params);
309
310                     CVideoDatabase db;
311                     if (!db.Open() ) return NULL;
312
313                     if (params.GetMovieId() >= 0 )
314                         db.GetMovieInfo((const char*)path, *item->GetVideoInfoTag(), params.GetMovieId());
315                     else if (params.GetMVideoId() >= 0 )
316                         db.GetMusicVideoInfo((const char*)path, *item->GetVideoInfoTag(), params.GetMVideoId());
317                     else if (params.GetEpisodeId() >= 0 )
318                         db.GetEpisodeInfo((const char*)path, *item->GetVideoInfoTag(), params.GetEpisodeId());
319                     else if (params.GetTvShowId() >= 0 )
320                         db.GetTvShowInfo((const char*)path, *item->GetVideoInfoTag(), params.GetTvShowId());
321                 }
322
323                 if (item->GetVideoInfoTag()->m_type == "tvshow" || item->GetVideoInfoTag()->m_type == "season") {
324                     // for tvshows and seasons, iEpisode and playCount are
325                     // invalid
326                     item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("totalepisodes").asInteger();
327                     item->GetVideoInfoTag()->m_playCount = (int)item->GetProperty("watchedepisodes").asInteger();
328                 }
329
330                 // try to grab title from tag
331                 if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strTitle.IsEmpty()) {
332                     item->SetLabel(item->GetVideoInfoTag()->m_strTitle);
333                     item->SetLabelPreformated(true);
334                 }
335
336                 // try to grab it from the folder
337                 if (item->GetLabel().IsEmpty()) {
338                     CStdString label;
339                     if (CVideoDatabaseDirectory::GetLabel((const char*)path, label)) {
340                         item->SetLabel(label);
341                         item->SetLabelPreformated(true);
342                     }
343                 }
344             }
345         }
346
347         // not a virtual path directory, new system
348         object = BuildObject(*item.get(), file_path, with_count, thumb_loader, &context, this);
349
350         // set parent id if passed, otherwise it should have been determined
351         if (object && parent_id) {
352             object->m_ParentID = parent_id;
353         }
354     }
355
356     if (object) {
357         // remap Root virtualpath://upnproot/ to id "0"
358         if (object->m_ObjectID == "virtualpath://upnproot/")
359             object->m_ObjectID = "0";
360
361         // remap Parent Root virtualpath://upnproot/ to id "0"
362         if (object->m_ParentID == "virtualpath://upnproot/")
363             object->m_ParentID = "0";
364     }
365
366     return object;
367
368 failure:
369     delete object;
370     return NULL;
371 }
372
373 /*----------------------------------------------------------------------
374 |   CUPnPServer::Announce
375 +---------------------------------------------------------------------*/
376 void
377 CUPnPServer::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
378 {
379     NPT_String path;
380     int item_id;
381     string item_type;
382
383     if (strcmp(sender, "xbmc"))
384         return;
385
386     if (strcmp(message, "OnUpdate") && strcmp(message, "OnRemove")
387         && strcmp(message, "OnScanStarted") && strcmp(message, "OnScanFinished"))
388         return;
389
390     if (data.isNull()) {
391         if (!strcmp(message, "OnScanStarted") || !strcmp(message, "OnCleanStarted")) {
392             m_scanning = true;
393         }
394         else if (!strcmp(message, "OnScanFinished") || !strcmp(message, "OnCleanFinished")) {
395             OnScanCompleted(flag);
396         }
397     }
398     else {
399         // handle both updates & removals
400         if (!data["item"].isNull()) {
401             item_id = (int)data["item"]["id"].asInteger();
402             item_type = data["item"]["type"].asString();
403         }
404         else {
405             item_id = (int)data["id"].asInteger();
406             item_type = data["type"].asString();
407         }
408
409         // we always update 'recently added' nodes along with the specific container,
410         // as we don't differentiate 'updates' from 'adds' in RPC interface
411         if (flag == VideoLibrary) {
412             if(item_type == "episode") {
413                 CVideoDatabase db;
414                 if (!db.Open()) return;
415                 int show_id = db.GetTvShowForEpisode(item_id);
416                 int season_id = db.GetSeasonForEpisode(item_id);
417                 UpdateContainer(StringUtils::Format("videodb://tvshows/titles/%d/", show_id));
418                 UpdateContainer(StringUtils::Format("videodb://tvshows/titles/%d/%d/?tvshowid=%d", show_id, season_id, show_id));
419                 UpdateContainer("videodb://recentlyaddedepisodes/");
420             }
421             else if(item_type == "tvshow") {
422                 UpdateContainer("library://video/tvshows/titles.xml/");
423                 UpdateContainer("videodb://recentlyaddedepisodes/");
424             }
425             else if(item_type == "movie") {
426                 UpdateContainer("library://video/movies/titles.xml/");
427                 UpdateContainer("videodb://recentlyaddedmovies/");
428             }
429             else if(item_type == "musicvideo") {
430                 UpdateContainer("library://video/musicvideos/titles.xml/");
431                 UpdateContainer("videodb://recentlyaddedmusicvideos/");
432             }
433         }
434         else if (flag == AudioLibrary && item_type == "song") {
435             // we also update the 'songs' container is maybe a performance drop too
436             // high? would need to check if slow clients even cache at all anyway
437             CMusicDatabase db;
438             CAlbum album;
439             if (!db.Open()) return;
440             if (db.GetAlbumFromSong(item_id, album)) {
441                 UpdateContainer(StringUtils::Format("musicdb://albums/%ld", album.idAlbum));
442                 UpdateContainer("musicdb://songs/");
443                 UpdateContainer("musicdb://recentlyaddedalbums/");
444             }
445         }
446     }
447 }
448
449 /*----------------------------------------------------------------------
450 |   TranslateWMPObjectId
451 +---------------------------------------------------------------------*/
452 static NPT_String TranslateWMPObjectId(NPT_String id)
453 {
454     if (id == "0") {
455         id = "virtualpath://upnproot/";
456     } else if (id == "15") {
457         // Xbox 360 asking for videos
458         id = "library://video";
459     } else if (id == "16") {
460         // Xbox 360 asking for photos
461     } else if (id == "107") {
462         // Sonos uses 107 for artists root container id
463         id = "musicdb://artists/";
464     } else if (id == "7") {
465         // Sonos uses 7 for albums root container id
466         id = "musicdb://albums/";
467     } else if (id == "4") {
468         // Sonos uses 4 for tracks root container id
469         id = "musicdb://songs/";
470     }
471
472     CLog::Log(LOGDEBUG, "UPnP Translated id to '%s'", (const char*)id);
473     return id;
474 }
475
476 NPT_Result
477 ObjectIDValidate(const NPT_String& id)
478 {
479     if (CFileUtils::RemoteAccessAllowed(id.GetChars()))
480         return NPT_SUCCESS;
481     return NPT_ERROR_NO_SUCH_FILE;
482 }
483
484 /*----------------------------------------------------------------------
485 |   CUPnPServer::OnBrowseMetadata
486 +---------------------------------------------------------------------*/
487 NPT_Result
488 CUPnPServer::OnBrowseMetadata(PLT_ActionReference&          action,
489                               const char*                   object_id,
490                               const char*                   filter,
491                               NPT_UInt32                    starting_index,
492                               NPT_UInt32                    requested_count,
493                               const char*                   sort_criteria,
494                               const PLT_HttpRequestContext& context)
495 {
496     NPT_COMPILER_UNUSED(sort_criteria);
497     NPT_COMPILER_UNUSED(requested_count);
498     NPT_COMPILER_UNUSED(starting_index);
499
500     NPT_String                     didl;
501     NPT_Reference<PLT_MediaObject> object;
502     NPT_String                     id = TranslateWMPObjectId(object_id);
503     vector<CStdString>             paths;
504     CFileItemPtr                   item;
505     NPT_Reference<CThumbLoader>    thumb_loader;
506
507     CLog::Log(LOGINFO, "Received UPnP Browse Metadata request for object '%s'", (const char*)object_id);
508
509     if(NPT_FAILED(ObjectIDValidate(id))) {
510         action->SetError(701, "Incorrect ObjectID.");
511         return NPT_FAILURE;
512     }
513
514     if (id.StartsWith("virtualpath://")) {
515         id.TrimRight("/");
516         if (id == "virtualpath://upnproot") {
517             id += "/";
518             item.reset(new CFileItem((const char*)id, true));
519             item->SetLabel("Root");
520             item->SetLabelPreformated(true);
521             object = Build(item, true, context, thumb_loader);
522             object->m_ParentID = "-1";
523         } else {
524             return NPT_FAILURE;
525         }
526     } else {
527         // determine if it's a container by calling CDirectory::Exists
528         item.reset(new CFileItem((const char*)id, CDirectory::Exists((const char*)id)));
529
530         // determine parent id for shared paths only
531         // otherwise let db find out
532         CStdString parent;
533         if (!URIUtils::GetParentPath((const char*)id, parent)) parent = "0";
534
535 //#ifdef WMP_ID_MAPPING
536 //        if (!id.StartsWith("musicdb://") && !id.StartsWith("videodb://")) {
537 //            parent = "";
538 //        }
539 //#endif
540
541         if (item->IsVideoDb()) {
542             thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader());
543         }
544         else if (item->IsMusicDb()) {
545             thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader());
546         }
547         if (!thumb_loader.IsNull()) {
548             thumb_loader->OnLoaderStart();
549         }
550         object = Build(item, true, context, thumb_loader, parent.empty()?NULL:parent.c_str());
551     }
552
553     if (object.IsNull()) {
554         /* error */
555         NPT_LOG_WARNING_1("CUPnPServer::OnBrowseMetadata - Object null (%s)", object_id);
556         action->SetError(701, "No Such Object.");
557         return NPT_FAILURE;
558     }
559
560     NPT_String tmp;
561     NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
562
563     /* add didl header and footer */
564     didl = didl_header + tmp + didl_footer;
565
566     NPT_CHECK(action->SetArgumentValue("Result", didl));
567     NPT_CHECK(action->SetArgumentValue("NumberReturned", "1"));
568     NPT_CHECK(action->SetArgumentValue("TotalMatches", "1"));
569
570     // update ID may be wrong here, it should be the one of the container?
571     NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
572
573     // TODO: We need to keep track of the overall SystemUpdateID of the CDS
574
575     return NPT_SUCCESS;
576 }
577
578 /*----------------------------------------------------------------------
579 |   CUPnPServer::OnBrowseDirectChildren
580 +---------------------------------------------------------------------*/
581 NPT_Result
582 CUPnPServer::OnBrowseDirectChildren(PLT_ActionReference&          action,
583                                     const char*                   object_id,
584                                     const char*                   filter,
585                                     NPT_UInt32                    starting_index,
586                                     NPT_UInt32                    requested_count,
587                                     const char*                   sort_criteria,
588                                     const PLT_HttpRequestContext& context)
589 {
590     CFileItemList items;
591     NPT_String    parent_id = TranslateWMPObjectId(object_id);
592
593     CLog::Log(LOGINFO, "UPnP: Received Browse DirectChildren request for object '%s', with sort criteria %s", object_id, sort_criteria);
594
595     if(NPT_FAILED(ObjectIDValidate(parent_id))) {
596         action->SetError(701, "Incorrect ObjectID.");
597         return NPT_FAILURE;
598     }
599
600     items.SetPath(CStdString(parent_id));
601
602     // guard against loading while saving to the same cache file
603     // as CArchive currently performs no locking itself
604     bool load;
605     { NPT_AutoLock lock(m_CacheMutex);
606       load = items.Load();
607     }
608
609     if (!load) {
610         // cache anything that takes more than a second to retrieve
611         unsigned int time = XbmcThreads::SystemClockMillis();
612
613         if (parent_id.StartsWith("virtualpath://upnproot")) {
614             CFileItemPtr item;
615
616             // music library
617             item.reset(new CFileItem("musicdb://", true));
618             item->SetLabel("Music Library");
619             item->SetLabelPreformated(true);
620             items.Add(item);
621
622             // video library
623             item.reset(new CFileItem("library://video", true));
624             item->SetLabel("Video Library");
625             item->SetLabelPreformated(true);
626             items.Add(item);
627
628             items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
629         } else {
630             // this is the only way to hide unplayable items in the 'files'
631             // view as we cannot tell what context (eg music vs video) the
632             // request came from
633             string supported = g_advancedSettings.m_pictureExtensions + "|"
634                              + g_advancedSettings.m_videoExtensions + "|"
635                              + g_advancedSettings.m_musicExtensions + "|"
636                              + g_advancedSettings.m_discStubExtensions;
637             CDirectory::GetDirectory((const char*)parent_id, items, supported);
638             DefaultSortItems(items);
639         }
640
641         if (items.CacheToDiscAlways() || (items.CacheToDiscIfSlow() && (XbmcThreads::SystemClockMillis() - time) > 1000 )) {
642             NPT_AutoLock lock(m_CacheMutex);
643             items.Save();
644         }
645     }
646
647     // as there's no library://music support, manually add playlists and music
648     // video nodes
649     if (items.GetPath() == "musicdb://") {
650       CFileItemPtr playlists(new CFileItem("special://musicplaylists/", true));
651       playlists->SetLabel(g_localizeStrings.Get(136));
652       items.Add(playlists);
653
654       CVideoDatabase database;
655       database.Open();
656       if (database.HasContent(VIDEODB_CONTENT_MUSICVIDEOS)) {
657           CFileItemPtr mvideos(new CFileItem("library://video/musicvideos/", true));
658           mvideos->SetLabel(g_localizeStrings.Get(20389));
659           items.Add(mvideos);
660       }
661     }
662
663     // Don't pass parent_id if action is Search not BrowseDirectChildren, as
664     // we want the engine to determine the best parent id, not necessarily the one
665     // passed
666     NPT_String action_name = action->GetActionDesc().GetName();
667     return BuildResponse(
668         action,
669         items,
670         filter,
671         starting_index,
672         requested_count,
673         sort_criteria,
674         context,
675         (action_name.Compare("Search", true)==0)?NULL:parent_id.GetChars());
676 }
677
678 /*----------------------------------------------------------------------
679 |   CUPnPServer::BuildResponse
680 +---------------------------------------------------------------------*/
681 NPT_Result
682 CUPnPServer::BuildResponse(PLT_ActionReference&          action,
683                            CFileItemList&                items,
684                            const char*                   filter,
685                            NPT_UInt32                    starting_index,
686                            NPT_UInt32                    requested_count,
687                            const char*                   sort_criteria,
688                            const PLT_HttpRequestContext& context,
689                            const char*                   parent_id /* = NULL */)
690 {
691     NPT_COMPILER_UNUSED(sort_criteria);
692
693     CLog::Log(LOGDEBUG, "Building UPnP response with filter '%s', starting @ %d with %d requested",
694         (const char*)filter,
695         starting_index,
696         requested_count);
697
698     // we will reuse this ThumbLoader for all items
699     NPT_Reference<CThumbLoader> thumb_loader;
700
701     if (URIUtils::IsVideoDb(items.GetPath()) ||
702         StringUtils::StartsWith(items.GetPath(), "library://video") ||
703         StringUtils::StartsWith(items.GetPath(), "special://profile/playlists/video/")) {
704
705         thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader());
706     }
707     else if (URIUtils::IsMusicDb(items.GetPath()) ||
708         StringUtils::StartsWith(items.GetPath(), "special://profile/playlists/music/")) {
709
710         thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader());
711     }
712     if (!thumb_loader.IsNull()) {
713         thumb_loader->OnLoaderStart();
714     }
715
716     // this isn't pretty but needed to properly hide the addons node from clients
717     if (items.GetPath().Left(7) == "library") {
718         for (int i=0; i<items.Size(); i++) {
719             if (items[i]->GetPath().Left(6) == "addons")
720                 items.Remove(i);
721         }
722     }
723
724     // won't return more than UPNP_MAX_RETURNED_ITEMS items at a time to keep things smooth
725     // 0 requested means as many as possible
726     NPT_UInt32 max_count  = (requested_count == 0)?m_MaxReturnedItems:min((unsigned long)requested_count, (unsigned long)m_MaxReturnedItems);
727     NPT_UInt32 stop_index = min((unsigned long)(starting_index + max_count), (unsigned long)items.Size()); // don't return more than we can
728
729     NPT_Cardinal count = 0;
730     NPT_Cardinal total = items.Size();
731     NPT_String didl = didl_header;
732     PLT_MediaObjectReference object;
733     for (unsigned long i=starting_index; i<stop_index; ++i) {
734         object = Build(items[i], true, context, thumb_loader, parent_id);
735         if (object.IsNull()) {
736             // don't tell the client this item ever existed
737             --total;
738             continue;
739         }
740
741         NPT_String tmp;
742         NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
743
744         // Neptunes string growing is dead slow for small additions
745         if (didl.GetCapacity() < tmp.GetLength() + didl.GetLength()) {
746             didl.Reserve((tmp.GetLength() + didl.GetLength())*2);
747         }
748         didl += tmp;
749         ++count;
750     }
751
752     didl += didl_footer;
753
754     CLog::Log(LOGDEBUG, "Returning UPnP response with %d items out of %d total matches",
755         count,
756         total);
757
758     NPT_CHECK(action->SetArgumentValue("Result", didl));
759     NPT_CHECK(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(count)));
760     NPT_CHECK(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(total)));
761     NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
762     return NPT_SUCCESS;
763 }
764
765 /*----------------------------------------------------------------------
766 |   FindSubCriteria
767 +---------------------------------------------------------------------*/
768 static
769 NPT_String
770 FindSubCriteria(NPT_String criteria, const char* name)
771 {
772     NPT_String result;
773     int search = criteria.Find(name);
774     if (search >= 0) {
775         criteria = criteria.Right(criteria.GetLength() - search - NPT_StringLength(name));
776         criteria.TrimLeft(" ");
777         if (criteria.GetLength()>0 && criteria[0] == '=') {
778             criteria.TrimLeft("= ");
779             if (criteria.GetLength()>0 && criteria[0] == '\"') {
780                 search = criteria.Find("\"", 1);
781                 if (search > 0) result = criteria.SubString(1, search-1);
782             }
783         }
784     }
785     return result;
786 }
787
788 /*----------------------------------------------------------------------
789 |   CUPnPServer::OnSearchContainer
790 +---------------------------------------------------------------------*/
791 NPT_Result
792 CUPnPServer::OnSearchContainer(PLT_ActionReference&          action,
793                                const char*                   object_id,
794                                const char*                   search_criteria,
795                                const char*                   filter,
796                                NPT_UInt32                    starting_index,
797                                NPT_UInt32                    requested_count,
798                                const char*                   sort_criteria,
799                                const PLT_HttpRequestContext& context)
800 {
801     CLog::Log(LOGDEBUG, "Received Search request for object '%s' with search '%s'",
802         (const char*)object_id,
803         (const char*)search_criteria);
804
805     NPT_String id = object_id;
806     if (id.StartsWith("musicdb://")) {
807         // we browse for all tracks given a genre, artist or album
808         if (NPT_String(search_criteria).Find("object.item.audioItem") >= 0) {
809             if (!id.EndsWith("/")) id += "/";
810             NPT_Cardinal count = id.SubString(10).Split("/").GetItemCount();
811             // remove extra empty node count
812             count = count?count-1:0;
813
814             // genre
815             if (id.StartsWith("musicdb://genres/")) {
816                 // all tracks of all genres
817                 if (count == 1)
818                     id += "-1/-1/-1/";
819                 // all tracks of a specific genre
820                 else if (count == 2)
821                     id += "-1/-1/";
822                 // all tracks of a specific genre of a specfic artist
823                 else if (count == 3)
824                     id += "-1/";
825             } else if (id.StartsWith("musicdb://artists/")) {
826                 // all tracks by all artists
827                 if (count == 1)
828                     id += "-1/-1/";
829                 // all tracks of a specific artist
830                 else if (count == 2)
831                     id += "-1/";
832             } else if (id.StartsWith("musicdb://albums/")) {
833                 // all albums ?
834                 if (count == 1) id += "-1/";
835             }
836         }
837         return OnBrowseDirectChildren(action, id, filter, starting_index, requested_count, sort_criteria, context);
838     } else if (NPT_String(search_criteria).Find("object.item.audioItem") >= 0) {
839         // look for artist, album & genre filters
840         NPT_String genre = FindSubCriteria(search_criteria, "upnp:genre");
841         NPT_String album = FindSubCriteria(search_criteria, "upnp:album");
842         NPT_String artist = FindSubCriteria(search_criteria, "upnp:artist");
843         // sonos looks for microsoft specific stuff
844         artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistPerformer");
845         artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistAlbumArtist");
846         artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:authorComposer");
847
848         CMusicDatabase database;
849         database.Open();
850
851         if (genre.GetLength() > 0) {
852             // all tracks by genre filtered by artist and/or album
853             CStdString strPath;
854             strPath.Format("musicdb://genres/%ld/%ld/%ld/",
855                 database.GetGenreByName((const char*)genre),
856                 database.GetArtistByName((const char*)artist), // will return -1 if no artist
857                 database.GetAlbumByName((const char*)album));  // will return -1 if no album
858
859             return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
860         } else if (artist.GetLength() > 0) {
861             // all tracks by artist name filtered by album if passed
862             CStdString strPath;
863             strPath.Format("musicdb://artists/%ld/%ld/",
864                 database.GetArtistByName((const char*)artist),
865                 database.GetAlbumByName((const char*)album)); // will return -1 if no album
866
867             return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
868         } else if (album.GetLength() > 0) {
869             // all tracks by album name
870             CStdString strPath;
871             strPath.Format("musicdb://albums/%ld/",
872                 database.GetAlbumByName((const char*)album));
873
874             return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
875         }
876
877         // browse all songs
878         return OnBrowseDirectChildren(action, "musicdb://songs/", filter, starting_index, requested_count, sort_criteria, context);
879     } else if (NPT_String(search_criteria).Find("object.container.album.musicAlbum") >= 0) {
880         // sonos filters by genre
881         NPT_String genre = FindSubCriteria(search_criteria, "upnp:genre");
882
883         // 360 hack: artist/albums using search
884         NPT_String artist = FindSubCriteria(search_criteria, "upnp:artist");
885         // sonos looks for microsoft specific stuff
886         artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistPerformer");
887         artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistAlbumArtist");
888         artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:authorComposer");
889
890         CMusicDatabase database;
891         database.Open();
892
893         if (genre.GetLength() > 0) {
894             CStdString strPath;
895             strPath.Format("musicdb://genres/%ld/%ld/",
896                 database.GetGenreByName((const char*)genre),
897                 database.GetArtistByName((const char*)artist)); // no artist should return -1
898             return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
899         } else if (artist.GetLength() > 0) {
900             CStdString strPath;
901             strPath.Format("musicdb://artists/%ld/",
902                 database.GetArtistByName((const char*)artist));
903             return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
904         }
905
906         // all albums
907         return OnBrowseDirectChildren(action, "musicdb://albums/", filter, starting_index, requested_count, sort_criteria, context);
908     } else if (NPT_String(search_criteria).Find("object.container.person.musicArtist") >= 0) {
909         // Sonos filters by genre
910         NPT_String genre = FindSubCriteria(search_criteria, "upnp:genre");
911         if (genre.GetLength() > 0) {
912             CMusicDatabase database;
913             database.Open();
914             CStdString strPath;
915             strPath.Format("musicdb://genres/%ld/", database.GetGenreByName((const char*)genre));
916             return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
917         }
918         return OnBrowseDirectChildren(action, "musicdb://artists/", filter, starting_index, requested_count, sort_criteria, context);
919     }  else if (NPT_String(search_criteria).Find("object.container.genre.musicGenre") >= 0) {
920         return OnBrowseDirectChildren(action, "musicdb://genres/", filter, starting_index, requested_count, sort_criteria, context);
921     } else if (NPT_String(search_criteria).Find("object.container.playlistContainer") >= 0) {
922         return OnBrowseDirectChildren(action, "special://musicplaylists/", filter, starting_index, requested_count, sort_criteria, context);
923     } else if (NPT_String(search_criteria).Find("object.item.videoItem") >= 0) {
924       CFileItemList items, itemsall;
925
926       CVideoDatabase database;
927       if (!database.Open()) {
928         action->SetError(800, "Internal Error");
929         return NPT_SUCCESS;
930       }
931
932       if (!database.GetMoviesNav("videodb://movies/titles/", items)) {
933         action->SetError(800, "Internal Error");
934         return NPT_SUCCESS;
935       }
936       itemsall.Append(items);
937       items.Clear();
938
939       if (!database.GetEpisodesByWhere("videodb://tvshows/titles/", "", items)) {
940         action->SetError(800, "Internal Error");
941         return NPT_SUCCESS;
942       }
943       itemsall.Append(items);
944       items.Clear();
945
946       return BuildResponse(action, itemsall, filter, starting_index, requested_count, sort_criteria, context, NULL);
947   } else if (NPT_String(search_criteria).Find("object.item.imageItem") >= 0) {
948       CFileItemList items;
949       return BuildResponse(action, items, filter, starting_index, requested_count, sort_criteria, context, NULL);;
950   }
951
952   return NPT_FAILURE;
953 }
954
955 /*----------------------------------------------------------------------
956 |   CUPnPServer::OnUpdateObject
957 +---------------------------------------------------------------------*/
958 NPT_Result
959 CUPnPServer::OnUpdateObject(PLT_ActionReference&             action,
960                             const char*                      object_id,
961                             NPT_Map<NPT_String,NPT_String>&  current_vals,
962                             NPT_Map<NPT_String,NPT_String>&  new_vals,
963                             const PLT_HttpRequestContext&    context)
964 {
965     CStdString path = CURL::Decode(object_id);
966     CFileItem updated;
967     updated.SetPath(path);
968     CLog::Log(LOGINFO, "UPnP: OnUpdateObject: %s from %s", path.c_str(),
969                        (const char*) context.GetRemoteAddress().GetIpAddress().ToString());
970
971     NPT_String playCount, position;
972     int err;
973     const char* msg = NULL;
974     bool updatelisting(false);
975
976     // we pause eventing as multiple announces may happen in this operation
977     PLT_Service* service = NULL;
978     NPT_CHECK_LABEL(FindServiceById("urn:upnp-org:serviceId:ContentDirectory", service), error);
979     NPT_CHECK_LABEL(service->PauseEventing(), error);
980
981     if (updated.IsVideoDb()) {
982         CVideoDatabase db;
983         NPT_CHECK_LABEL(!db.Open(), error);
984
985         // must first determine type of file from object id
986         VIDEODATABASEDIRECTORY::CQueryParams params;
987         VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(path.c_str(), params);
988
989         int id = -1;
990         VIDEODB_CONTENT_TYPE content_type;
991         if ((id = params.GetMovieId()) >= 0 )
992             content_type = VIDEODB_CONTENT_MOVIES;
993         else if ((id = params.GetEpisodeId()) >= 0 )
994             content_type = VIDEODB_CONTENT_EPISODES;
995         else if ((id = params.GetMVideoId()) >= 0 )
996             content_type = VIDEODB_CONTENT_MUSICVIDEOS;
997         else {
998             err = 701;
999             msg = "No such object";
1000             goto failure;
1001         }
1002
1003         CStdString file_path;
1004         db.GetFilePathById(id, file_path, content_type);
1005         CVideoInfoTag tag;
1006         db.LoadVideoInfo(file_path, tag);
1007         updated.SetFromVideoInfoTag(tag);
1008         CLog::Log(LOGINFO, "UPNP: Translated to %s", file_path.c_str());
1009
1010         position = new_vals["lastPlaybackPosition"];
1011         playCount = new_vals["playCount"];
1012
1013         if (!position.IsEmpty()
1014               && position.Compare(current_vals["lastPlaybackPosition"]) != 0) {
1015             NPT_UInt32 resume;
1016             NPT_CHECK_LABEL(position.ToInteger32(resume), args);
1017
1018             if (resume <= 0)
1019                 db.ClearBookMarksOfFile(file_path, CBookmark::RESUME);
1020             else {
1021                 CBookmark bookmark;
1022                 bookmark.timeInSeconds = resume;
1023                 bookmark.totalTimeInSeconds = resume + 100; // not required to be correct
1024
1025                 db.AddBookMarkToFile(file_path, bookmark, CBookmark::RESUME);
1026             }
1027             if (playCount.IsEmpty()) {
1028               CVariant data;
1029               data["id"] = updated.GetVideoInfoTag()->m_iDbId;
1030               data["type"] = updated.GetVideoInfoTag()->m_type;
1031               ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", data);
1032             }
1033             updatelisting = true;
1034         }
1035
1036         if (!playCount.IsEmpty()
1037               && playCount.Compare(current_vals["playCount"]) != 0) {
1038
1039             NPT_UInt32 count;
1040             NPT_CHECK_LABEL(playCount.ToInteger32(count), args);
1041             db.SetPlayCount(updated, count);
1042             updatelisting = true;
1043         }
1044
1045         // we must load the changed settings before propagating to local UI
1046         if (updatelisting) {
1047             db.LoadVideoInfo(file_path, tag);
1048             updated.SetFromVideoInfoTag(tag);
1049         }
1050
1051     } else if (updated.IsMusicDb()) {
1052       //TODO implement this
1053
1054     } else {
1055         err = 701;
1056         msg = "No such object";
1057         goto failure;
1058     }
1059
1060     if (updatelisting) {
1061         updated.SetPath(path);
1062         if (updated.IsVideoDb())
1063              CUtil::DeleteVideoDatabaseDirectoryCache();
1064         else if (updated.IsMusicDb())
1065              CUtil::DeleteMusicDatabaseDirectoryCache();
1066
1067         CFileItemPtr msgItem(new CFileItem(updated));
1068         CGUIMessage message(GUI_MSG_NOTIFY_ALL, g_windowManager.GetActiveWindow(), 0, GUI_MSG_UPDATE_ITEM, 1, msgItem);
1069         g_windowManager.SendThreadMessage(message);
1070     }
1071
1072     NPT_CHECK_LABEL(service->PauseEventing(false), error);
1073     return NPT_SUCCESS;
1074
1075 args:
1076     err = 402;
1077     msg = "Invalid args";
1078     goto failure;
1079
1080 error:
1081     err = 501;
1082     msg = "Internal error";
1083
1084 failure:
1085     CLog::Log(LOGERROR, "UPNP: OnUpdateObject failed with err %d:%s", err, msg);
1086     action->SetError(err, msg);
1087     service->PauseEventing(false);
1088     return NPT_FAILURE;
1089 }
1090
1091 /*----------------------------------------------------------------------
1092 |   CUPnPServer::ServeFile
1093 +---------------------------------------------------------------------*/
1094 NPT_Result
1095 CUPnPServer::ServeFile(const NPT_HttpRequest&              request,
1096                        const NPT_HttpRequestContext& context,
1097                        NPT_HttpResponse&             response,
1098                        const NPT_String&             md5)
1099 {
1100     // Translate hash to filename
1101     NPT_String file_path(md5), *file_path2;
1102     { NPT_AutoLock lock(m_FileMutex);
1103       if(NPT_SUCCEEDED(m_FileMap.Get(md5, file_path2))) {
1104         file_path = *file_path2;
1105         CLog::Log(LOGDEBUG, "Received request to serve '%s' = '%s'", (const char*)md5, (const char*)file_path);
1106       } else {
1107         CLog::Log(LOGDEBUG, "Received request to serve unknown md5 '%s'", (const char*)md5);
1108         response.SetStatus(404, "File Not Found");
1109         return NPT_SUCCESS;
1110       }
1111     }
1112
1113     // File requested
1114     NPT_HttpUrl rooturi(context.GetLocalAddress().GetIpAddress().ToString(), context.GetLocalAddress().GetPort(), "/");
1115
1116     if (file_path.Left(8).Compare("stack://", true) == 0) {
1117
1118         NPT_List<NPT_String> files = file_path.SubString(8).Split(" , ");
1119         if (files.GetItemCount() == 0) {
1120             response.SetStatus(404, "File Not Found");
1121             return NPT_SUCCESS;
1122         }
1123
1124         NPT_String output;
1125         output.Reserve(file_path.GetLength()*2);
1126         output += "#EXTM3U\r\n";
1127
1128         NPT_List<NPT_String>::Iterator url = files.GetFirstItem();
1129         for (;url;url++) {
1130             output += "#EXTINF:-1," + URIUtils::GetFileName((const char*)*url);
1131             output += "\r\n";
1132             output += BuildSafeResourceUri(
1133                           rooturi,
1134                           context.GetLocalAddress().GetIpAddress().ToString(),
1135                           *url);
1136             output += "\r\n";
1137         }
1138
1139         PLT_HttpHelper::SetBody(response, (const char*)output, output.GetLength());
1140         response.GetHeaders().SetHeader("Content-Disposition", "inline; filename=\"stack.m3u\"");
1141         return NPT_SUCCESS;
1142     }
1143
1144     if(URIUtils::IsURL((const char*)file_path))
1145     {
1146       CStdString disp = "inline; filename=\"" + URIUtils::GetFileName((const char*)file_path) + "\"";
1147       response.GetHeaders().SetHeader("Content-Disposition", disp.c_str());
1148     }
1149
1150     return PLT_HttpServer::ServeFile(request,
1151                                        context,
1152                                        response,
1153                                        file_path);
1154 }
1155
1156 /*----------------------------------------------------------------------
1157 |   CUPnPServer::SortItems
1158 |
1159 |   Only support upnp: & dc: namespaces for now.
1160 |   Other servers add their own vendor-specific sort methods. This could
1161 |   possibly be handled with 'quirks' in the long run.
1162 |
1163 |   return true if sort criteria was matched
1164 +---------------------------------------------------------------------*/
1165 bool
1166 CUPnPServer::SortItems(CFileItemList& items, const char* sort_criteria)
1167 {
1168   CStdString criteria(sort_criteria);
1169   if (criteria.IsEmpty()) {
1170     return false;
1171   }
1172
1173   bool sorted = false;
1174   CStdStringArray tokens = StringUtils::SplitString(criteria, ",");
1175   for (vector<CStdString>::reverse_iterator itr = tokens.rbegin(); itr != tokens.rend(); itr++) {
1176     /* Platinum guarantees 1st char is - or + */
1177     SortOrder order = itr->Left(1).Equals("+") ? SortOrderAscending : SortOrderDescending;
1178     CStdString method = itr->Mid(1);
1179
1180     SORT_METHOD scheme = SORT_METHOD_LABEL_IGNORE_THE;
1181
1182     /* resource specific */
1183     if (method.Equals("res@duration"))
1184       scheme = SORT_METHOD_DURATION;
1185     else if (method.Equals("res@size"))
1186       scheme = SORT_METHOD_SIZE;
1187     else if (method.Equals("res@bitrate"))
1188       scheme = SORT_METHOD_BITRATE;
1189
1190     /* dc: */
1191     else if (method.Equals("dc:date"))
1192       scheme = SORT_METHOD_DATE;
1193     else if (method.Equals("dc:title"))
1194       scheme = SORT_METHOD_TITLE_IGNORE_THE;
1195
1196     /* upnp: */
1197     else if (method.Equals("upnp:album"))
1198       scheme = SORT_METHOD_ALBUM;
1199     else if (method.Equals("upnp:artist") || method.Equals("upnp:albumArtist"))
1200       scheme = SORT_METHOD_ARTIST;
1201     else if (method.Equals("upnp:episodeNumber"))
1202       scheme = SORT_METHOD_EPISODE;
1203     else if (method.Equals("upnp:genre"))
1204       scheme = SORT_METHOD_GENRE;
1205     else if (method.Equals("upnp:originalTrackNumber"))
1206       scheme = SORT_METHOD_TRACKNUM;
1207     else if(method.Equals("upnp:rating"))
1208       scheme = SORT_METHOD_SONG_RATING;
1209     else {
1210       CLog::Log(LOGINFO, "UPnP: unsupported sort criteria '%s' passed", method.c_str());
1211       continue; // needed so unidentified sort methods don't re-sort by label
1212     }
1213
1214     CLog::Log(LOGINFO, "UPnP: Sorting by %d, %d", scheme, order);
1215     items.Sort(scheme, order);
1216     sorted = true;
1217   }
1218
1219   return sorted;
1220 }
1221
1222 void
1223 CUPnPServer::DefaultSortItems(CFileItemList& items)
1224 {
1225   CGUIViewState* viewState = CGUIViewState::GetViewState(items.IsVideoDb() ? WINDOW_VIDEO_NAV : -1, items);
1226   if (viewState)
1227   {
1228     items.Sort(viewState->GetSortMethod(), viewState->GetSortOrder());
1229     delete viewState;
1230   }
1231 }
1232
1233 } /* namespace UPNP */
1234