Merge pull request #4314 from MartijnKaijser/beta1
[vuplus_xbmc] / xbmc / filesystem / UPnPDirectory.cpp
1 /*
2  * UPnP Support for XBMC
3  *      Copyright (c) 2006 c0diq (Sylvain Rebaud)
4  *      Portions Copyright (c) by the authors of libPlatinum
5  *      http://www.plutinosoft.com/blog/category/platinum/
6  *
7  *      Copyright (C) 2010-2013 Team XBMC
8  *      http://xbmc.org
9  *
10  *  This Program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2, or (at your option)
13  *  any later version.
14  *
15  *  This Program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with XBMC; see the file COPYING.  If not, see
22  *  <http://www.gnu.org/licenses/>.
23  *
24  */
25 #include "UPnPDirectory.h"
26 #include "URL.h"
27 #include "network/upnp/UPnP.h"
28 #include "network/upnp/UPnPInternal.h"
29 #include "Platinum.h"
30 #include "PltSyncMediaBrowser.h"
31 #include "video/VideoInfoTag.h"
32 #include "FileItem.h"
33 #include "utils/log.h"
34 #include "utils/URIUtils.h"
35 #include "utils/StringUtils.h"
36
37 using namespace MUSIC_INFO;
38 using namespace XFILE;
39 using namespace UPNP;
40
41 namespace XFILE
42 {
43
44 static CStdString GetContentMapping(NPT_String& objectClass)
45 {
46     struct SClassMapping
47     {
48         const char* ObjectClass;
49         const char* Content;
50     };
51     static const SClassMapping mapping[] = {
52           { "object.item.videoItem.videoBroadcast", "episodes"      }
53         , { "object.item.videoItem.musicVideoClip", "musicvideos"  }
54         , { "object.item.videoItem"               , "movies"       }
55         , { "object.item.audioItem.musicTrack"    , "songs"        }
56         , { "object.item.audioItem"               , "songs"        }
57         , { "object.item.imageItem.photo"         , "photos"       }
58         , { "object.item.imageItem"               , "photos"       }
59         , { "object.container.album.videoAlbum"   , "tvshows"      }
60         , { "object.container.album.musicAlbum"   , "albums"       }
61         , { "object.container.album.photoAlbum"   , "photos"       }
62         , { "object.container.album"              , "albums"       }
63         , { "object.container.person"             , "artists"      }
64         , { NULL                                  , NULL           }
65     };
66     for(const SClassMapping* map = mapping; map->ObjectClass; map++)
67     {
68         if(objectClass.StartsWith(map->ObjectClass, true))
69         {
70           return map->Content;
71           break;
72         }
73     }
74     return "unknown";
75 }
76
77 static bool FindDeviceWait(CUPnP* upnp, const char* uuid, PLT_DeviceDataReference& device)
78 {
79     bool client_started = upnp->IsClientStarted();
80     upnp->StartClient();
81
82     // look for device in our list
83     // (and wait for it to respond for 5 secs if we're just starting upnp client)
84     NPT_TimeStamp watchdog;
85     NPT_System::GetCurrentTimeStamp(watchdog);
86     watchdog += 5.f;
87
88     for (;;) {
89         if (NPT_SUCCEEDED(upnp->m_MediaBrowser->FindServer(uuid, device)) && !device.IsNull())
90             break;
91
92         // fail right away if device not found and upnp client was already running
93         if (client_started)
94             return false;
95
96         // otherwise check if we've waited long enough without success
97         NPT_TimeStamp now;
98         NPT_System::GetCurrentTimeStamp(now);
99         if (now > watchdog)
100             return false;
101
102         // sleep a bit and try again
103         NPT_System::Sleep(NPT_TimeInterval((double)1));
104     }
105
106     return !device.IsNull();
107 }
108
109 /*----------------------------------------------------------------------
110 |   CUPnPDirectory::GetFriendlyName
111 +---------------------------------------------------------------------*/
112 const char*
113 CUPnPDirectory::GetFriendlyName(const char* url)
114 {
115     NPT_String path = url;
116     if (!path.EndsWith("/")) path += "/";
117
118     if (path.Left(7).Compare("upnp://", true) != 0) {
119         return NULL;
120     } else if (path.Compare("upnp://", true) == 0) {
121         return "UPnP Media Servers (Auto-Discover)";
122     }
123
124     // look for nextslash
125     int next_slash = path.Find('/', 7);
126     if (next_slash == -1)
127         return NULL;
128
129     NPT_String uuid = path.SubString(7, next_slash-7);
130     NPT_String object_id = path.SubString(next_slash+1, path.GetLength()-next_slash-2);
131
132     // look for device
133     PLT_DeviceDataReference device;
134     if(!FindDeviceWait(CUPnP::GetInstance(), uuid, device))
135         return NULL;
136
137     return (const char*)device->GetFriendlyName();
138 }
139
140 /*----------------------------------------------------------------------
141 |   CUPnPDirectory::GetDirectory
142 +---------------------------------------------------------------------*/
143 bool CUPnPDirectory::GetResource(const CURL& path, CFileItem &item)
144 {
145     if(path.GetProtocol() != "upnp")
146       return false;
147
148     CUPnP* upnp = CUPnP::GetInstance();
149     if(!upnp)
150         return false;
151
152     CStdString uuid   = path.GetHostName();
153     CStdString object = path.GetFileName();
154     StringUtils::TrimRight(object, "/");
155     object = CURL::Decode(object);
156
157     PLT_DeviceDataReference device;
158     if(!FindDeviceWait(upnp, uuid.c_str(), device)) {
159         CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find uuid %s", uuid.c_str());
160         return false;
161     }
162
163     PLT_MediaObjectListReference list;
164     if (NPT_FAILED(upnp->m_MediaBrowser->BrowseSync(device, object.c_str(), list, true))) {
165         CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find object %s", object.c_str());
166         return false;
167     }
168
169     if (list.IsNull() || !list->GetItemCount()) {
170       CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - no items returned for object %s", object.c_str());
171       return false;
172     }
173
174     PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
175     if (entry == 0)
176         return false;
177
178   return UPNP::GetResource(*entry, item);
179 }
180
181
182 /*----------------------------------------------------------------------
183 |   CUPnPDirectory::GetDirectory
184 +---------------------------------------------------------------------*/
185 bool
186 CUPnPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
187 {
188     CUPnP* upnp = CUPnP::GetInstance();
189
190     /* upnp should never be cached, it has internal cache */
191     items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
192
193     // We accept upnp://devuuid/[item_id/]
194     NPT_String path = strPath.c_str();
195     if (!path.StartsWith("upnp://", true)) {
196         return false;
197     }
198
199     if (path.Compare("upnp://", true) == 0) {
200         upnp->StartClient();
201
202         // root -> get list of devices
203         const NPT_Lock<PLT_DeviceDataReferenceList>& devices = upnp->m_MediaBrowser->GetMediaServers();
204         NPT_List<PLT_DeviceDataReference>::Iterator device = devices.GetFirstItem();
205         while (device) {
206             NPT_String name = (*device)->GetFriendlyName();
207             NPT_String uuid = (*device)->GetUUID();
208
209             CFileItemPtr pItem(new CFileItem((const char*)name));
210             pItem->SetPath(CStdString((const char*) "upnp://" + uuid + "/"));
211             pItem->m_bIsFolder = true;
212             pItem->SetArt("thumb", (const char*)(*device)->GetIconUrl("image/png"));
213
214             items.Add(pItem);
215
216             ++device;
217         }
218     } else {
219         if (!path.EndsWith("/")) path += "/";
220
221         // look for nextslash
222         int next_slash = path.Find('/', 7);
223
224         NPT_String uuid = (next_slash==-1)?path.SubString(7):path.SubString(7, next_slash-7);
225         NPT_String object_id = (next_slash==-1)?"":path.SubString(next_slash+1);
226         object_id.TrimRight("/");
227         if (object_id.GetLength()) {
228             object_id = CStdString(CURL::Decode((char*)object_id));
229         }
230
231         // try to find the device with wait on startup
232         PLT_DeviceDataReference device;
233         if (!FindDeviceWait(upnp, uuid, device))
234             goto failure;
235
236         // issue a browse request with object_id
237         // if object_id is empty use "0" for root
238         object_id = object_id.IsEmpty()?"0":object_id;
239
240         // remember a count of object classes
241         std::map<NPT_String, int> classes;
242
243         // just a guess as to what types of files we want
244         bool video = true;
245         bool audio = true;
246         bool image = true;
247         StringUtils::TrimLeft(m_strFileMask, "/");
248         if (!m_strFileMask.empty()) {
249             video = m_strFileMask.find(".wmv") != std::string::npos;
250             audio = m_strFileMask.find(".wma") != std::string::npos;
251             image = m_strFileMask.find(".jpg") != std::string::npos;
252         }
253
254         // special case for Windows Media Connect and WMP11 when looking for root
255         // We can target which root subfolder we want based on directory mask
256         if (object_id == "0" && ((device->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) ||
257                                  (device->m_ModelName == "Windows Media Player Sharing"))) {
258
259             // look for a specific type to differentiate which folder we want
260             if (audio && !video && !image) {
261                 // music
262                 object_id = "1";
263             } else if (!audio && video && !image) {
264                 // video
265                 object_id = "2";
266             } else if (!audio && !video && image) {
267                 // pictures
268                 object_id = "3";
269             }
270         }
271
272 #ifdef DISABLE_SPECIALCASE
273         // same thing but special case for XBMC
274         if (object_id == "0" && ((device->m_ModelName.Find("XBMC", 0, true) >= 0) ||
275                                  (device->m_ModelName.Find("Xbox Media Center", 0, true) >= 0))) {
276             // look for a specific type to differentiate which folder we want
277             if (audio && !video && !image) {
278                 // music
279                 object_id = "virtualpath://upnpmusic";
280             } else if (!audio && video && !image) {
281                 // video
282                 object_id = "virtualpath://upnpvideo";
283             } else if (!audio && !video && image) {
284                 // pictures
285                 object_id = "virtualpath://upnppictures";
286             }
287         }
288 #endif
289
290         // if error, return now, the device could have gone away
291         // this will make us go back to the sources list
292         PLT_MediaObjectListReference list;
293         NPT_Result res = upnp->m_MediaBrowser->BrowseSync(device, object_id, list);
294         if (NPT_FAILED(res)) goto failure;
295
296         // empty list is ok
297         if (list.IsNull()) goto cleanup;
298
299         PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
300         while (entry) {
301             // disregard items with wrong class/type
302             if( (!video && (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0)
303              || (!audio && (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0)
304              || (!image && (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0) )
305             {
306                 ++entry;
307                 continue;
308             }
309
310             // never show empty containers in media views
311             if((*entry)->IsContainer()) {
312                 if( (audio || video || image)
313                  && ((PLT_MediaContainer*)(*entry))->m_ChildrenCount == 0) {
314                     ++entry;
315                     continue;
316                 }
317             }
318
319
320             // keep count of classes
321             classes[(*entry)->m_ObjectClass.type]++;
322             CFileItemPtr pItem = BuildObject(*entry);
323             if(!pItem) {
324                 ++entry;
325                 continue;
326             }
327
328             CStdString id;
329             if ((*entry)->m_ReferenceID.IsEmpty())
330                 id = (const char*) (*entry)->m_ObjectID;
331             else
332                 id = (const char*) (*entry)->m_ReferenceID;
333
334             id = CURL::Encode(id);
335             URIUtils::AddSlashAtEnd(id);
336             pItem->SetPath(CStdString((const char*) "upnp://" + uuid + "/" + id.c_str()));
337
338             items.Add(pItem);
339
340             ++entry;
341         }
342
343         NPT_String max_string = "";
344         int        max_count  = 0;
345         for(std::map<NPT_String, int>::iterator it = classes.begin(); it != classes.end(); it++)
346         {
347           if(it->second > max_count)
348           {
349             max_string = it->first;
350             max_count  = it->second;
351           }
352         }
353         std::string content = GetContentMapping(max_string);
354         items.SetContent(content);
355         if (content == "unknown")
356         {
357           items.AddSortMethod(SortByNone, 571, LABEL_MASKS("%L", "%I", "%L", ""));
358           items.AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS("%L", "%I", "%L", ""));
359           items.AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I"));
360           items.AddSortMethod(SortByDate, 552, LABEL_MASKS("%L", "%J", "%L", "%J"));
361         }
362     }
363
364 cleanup:
365     return true;
366
367 failure:
368     return false;
369 }
370 }