improved: upnp caching, now refreshes the current directory or cache when a container...
[xbmc:xbmc-antiquated.git] / 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 *
6 * http://www.plutinosoft.com/blog/category/platinum/
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23
24 #include "stdafx.h"
25 #include "Util.h"
26 #include "UPnPDirectory.h"
27 #include "UPnP.h"
28 #include "Platinum.h"
29 #include "PltSyncMediaBrowser.h"
30 #include "VideoInfoTag.h"
31 #include "MusicInfoTag.h"
32 #include "FileItem.h"
33 #include "DirectoryCache.h"
34 #include "GUIWindowManager.h"
35 #include "GUIDialogProgress.h"
36
37 using namespace MUSIC_INFO;
38 using namespace DIRECTORY;
39 using namespace XFILE;
40
41 namespace 
42 {
43   static const NPT_String JoinString(const NPT_List<NPT_String>& array, const NPT_String& delimiter)
44   {
45     NPT_String result;
46     
47     for(NPT_List<NPT_String>::Iterator it = array.GetFirstItem(); it; it++ )
48       result += (*it) + delimiter;
49
50     if(result.IsEmpty())
51       return "";
52     else
53       return result.SubString(delimiter.GetLength());
54   }
55
56 }
57
58 namespace DIRECTORY
59 {
60 /*----------------------------------------------------------------------
61 |   CUPnPDirectory::GetFriendlyName
62 +---------------------------------------------------------------------*/
63 const char* 
64 CUPnPDirectory::GetFriendlyName(const char* url)
65 {
66     NPT_String path = url;
67     if (!path.EndsWith("/")) path += "/";
68
69     if (path.Left(7).Compare("upnp://", true) != 0) {
70         return NULL;
71     } else if (path.Compare("upnp://", true) == 0) {
72         return "UPnP Media Servers (Auto-Discover)";
73     } 
74
75     // look for nextslash 
76     int next_slash = path.Find('/', 7);
77     if (next_slash == -1) 
78         return NULL;
79
80     NPT_String uuid = path.SubString(7, next_slash-7);
81     NPT_String object_id = path.SubString(next_slash+1, path.GetLength()-next_slash-2);
82
83     // look for device 
84     PLT_DeviceDataReference* device;
85     const NPT_Lock<PLT_DeviceMap>& devices = CUPnP::GetInstance()->m_MediaBrowser->GetMediaServers();
86     if (NPT_FAILED(devices.Get(uuid, device)) || device == NULL) 
87         return NULL;
88
89     return (const char*)(*device)->GetFriendlyName();
90 }
91
92 /*----------------------------------------------------------------------
93 |   CUPnPDirectory::GetDirectory
94 +---------------------------------------------------------------------*/
95 bool 
96 CUPnPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
97 {
98     CGUIDialogProgress* dlgProgress = NULL;
99     
100     CUPnP* upnp = CUPnP::GetInstance();
101     
102     /* upnp should never be cached, it has internal cache */
103     items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
104     // start client if it hasn't been done yet
105     upnp->StartClient();
106
107     // We accept upnp://devuuid/[item_id/]
108     NPT_String path = strPath.c_str();
109     if (!path.StartsWith("upnp://", true)) {
110         return false;
111     } 
112       
113     if (path.Compare("upnp://", true) == 0) {       
114         // root -> get list of devices 
115         const NPT_Lock<PLT_DeviceMap>& devices = upnp->m_MediaBrowser->GetMediaServers();
116         const NPT_List<PLT_DeviceMapEntry*>& entries = devices.GetEntries();
117         NPT_List<PLT_DeviceMapEntry*>::Iterator entry = entries.GetFirstItem();
118         while (entry) {
119             PLT_DeviceDataReference device = (*entry)->GetValue();
120             NPT_String name = device->GetFriendlyName();
121             NPT_String uuid = (*entry)->GetKey();
122
123             CFileItemPtr pItem(new CFileItem((const char*)name));
124             pItem->m_strPath = (const char*) "upnp://" + uuid + "/";
125             pItem->m_bIsFolder = true;
126             pItem->SetThumbnailImage((const char*)device->GetIconUrl("image/jpeg"));
127
128             items.Add(pItem);
129
130             ++entry;
131         }
132     } else {
133         if (!path.EndsWith("/")) path += "/";
134         
135         // look for nextslash 
136         int next_slash = path.Find('/', 7);
137
138         NPT_String uuid = (next_slash==-1)?path.SubString(7):path.SubString(7, next_slash-7);
139         NPT_String object_id = (next_slash==-1)?"":path.SubString(next_slash+1);
140         object_id.TrimRight("/");
141         if (object_id.GetLength()) {
142             CStdString tmp = (char*) object_id;
143             CUtil::UrlDecode(tmp);
144             object_id = tmp;
145         }
146
147         // look for device 
148         PLT_DeviceDataReference* device;
149         const NPT_Lock<PLT_DeviceMap>& devices = upnp->m_MediaBrowser->GetMediaServers();
150         if (NPT_FAILED(devices.Get(uuid, device)) || device == NULL) 
151             goto failure;
152
153         // issue a browse request with object_id
154         // if object_id is empty use "0" for root 
155         object_id = object_id.IsEmpty()?"0":object_id;
156
157         // just a guess as to what types of files we want
158         bool video = true;
159         bool audio = true;
160         bool image = true;
161         m_strFileMask.TrimLeft("/");
162         if( !m_strFileMask.IsEmpty() ) {
163             video = m_strFileMask.Find(".wmv") >= 0;
164             audio = m_strFileMask.Find(".wma") >= 0;
165             image = m_strFileMask.Find(".jpg") >= 0;
166         }
167
168         // special case for Windows Media Connect and WMP11 when looking for root
169         // We can target which root subfolder we want based on directory mask
170         if (object_id == "0" && (((*device)->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) || 
171                                  ((*device)->m_ModelName == "Windows Media Player Sharing"))) {
172
173             // look for a specific type to differentiate which folder we want
174             if (audio && !video && !image) {
175                 // music
176                 object_id = "1";
177             } else if (!audio && video && !image) {
178                 // video
179                 object_id = "2";
180             } else if (!audio && !video && image) {
181                 // pictures
182                 object_id = "3";
183             }
184         }
185
186 #ifdef DISABLE_SPECIALCASE
187         // same thing but special case for XBMC
188         if (object_id == "0" && (((*device)->m_ModelName.Find("XBMC", 0, true) >= 0) || 
189                                  ((*device)->m_ModelName.Find("Xbox Media Center", 0, true) >= 0))) {
190             // look for a specific type to differentiate which folder we want
191             if (audio && !video && !image) {
192                 // music
193                 object_id = "virtualpath://upnpmusic";
194             } else if (!audio && video && !image) {
195                 // video
196                 object_id = "virtualpath://upnpvideo";
197             } else if (!audio && !video && image) {
198                 // pictures
199                 object_id = "virtualpath://upnppictures";
200             }
201         }
202 #endif        
203         // bring up dialog if object is not cached
204         if (!upnp->m_MediaBrowser->IsCached(uuid, object_id)) {
205             dlgProgress = (CGUIDialogProgress*)m_gWindowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
206             if (dlgProgress) {
207                 dlgProgress->ShowProgressBar(false);
208                 dlgProgress->SetCanCancel(false);
209                 dlgProgress->SetHeading(20334);
210                 dlgProgress->SetLine(0, 194);
211                 dlgProgress->SetLine(1, "");
212                 dlgProgress->SetLine(2, "");
213                 dlgProgress->StartModal();
214             }
215         }
216         
217         // if error, the list could be partial and that's ok
218         // we still want to process it
219         PLT_MediaObjectListReference list;
220         upnp->m_MediaBrowser->Browse(*device, object_id, list);
221
222         // empty list is ok
223         if (list.IsNull()) goto cleanup;
224
225         PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
226         while (entry) {
227             // disregard items with wrong class/type
228             if( (!video && (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0)
229              || (!audio && (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0)
230              || (!image && (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0) )
231             {
232                 ++entry;
233                 continue;
234             }
235
236             // never show empty containers in media views
237             if((*entry)->IsContainer()) {
238                 if( (audio || video || image) 
239                  && ((PLT_MediaContainer*)(*entry))->m_ChildrenCount == 0) {
240                     ++entry;
241                     continue;
242                 }
243             }
244
245             CFileItemPtr pItem(new CFileItem((const char*)(*entry)->m_Title));
246             pItem->SetLabelPreformated(true);
247             pItem->m_bIsFolder = (*entry)->IsContainer();
248
249             // if it's a container, format a string as upnp://uuid/object_id
250             if (pItem->m_bIsFolder) {
251                 CStdString id = (char*) (*entry)->m_ObjectID;
252                 CUtil::URLEncode(id);
253                 pItem->m_strPath = (const char*) "upnp://" + uuid + "/" + id.c_str() + "/";
254             } else {
255                 if ((*entry)->m_Resources.GetItemCount()) {
256                     // if it's an item, path is the first url to the item
257                     // we hope the server made the first one reachable for us
258                     // (it could be a format we dont know how to play however)
259                     pItem->m_strPath = (const char*) (*entry)->m_Resources[0].m_Uri;
260
261                     // set metadata
262                     if ((*entry)->m_Resources[0].m_Size > 0) {
263                         pItem->m_dwSize  = (*entry)->m_Resources[0].m_Size;
264                     }
265
266                     // look for content type in protocol info
267                     if ((*entry)->m_Resources[0].m_ProtocolInfo.GetLength()) {
268                         char proto[1024];
269                         char dummy1[1024];
270                         char ct[1204];
271                         char dummy2[1024];
272                         int fields = sscanf((*entry)->m_Resources[0].m_ProtocolInfo, "%[^:]:%[^:]:%[^:]:%[^:]", proto, dummy1, ct, dummy2);
273                         if (fields == 4) {
274                             pItem->SetContentType(ct);
275                         }
276                     }
277
278                     // look for date?
279                     if((*entry)->m_Description.date.GetLength()) {
280                         SYSTEMTIME time = {};
281                         sscanf((*entry)->m_Description.date, "%hu-%hu-%huT%hu:%hu:%hu",
282                                &time.wYear, &time.wMonth, &time.wDay, &time.wHour, &time.wMinute, &time.wSecond);
283                         pItem->m_dateTime = time;
284                     }
285
286                     if( (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0 ) {
287                         pItem->SetLabelPreformated(false);
288                         CVideoInfoTag* tag = pItem->GetVideoInfoTag();
289                         CStdStringArray strings, strings2;
290                         CStdString buffer;
291
292                         tag->m_strTitle = (*entry)->m_Title;                      
293                         tag->m_strGenre = JoinString((*entry)->m_Affiliation.genre, " / ");
294                         tag->m_strDirector = (*entry)->m_People.director;
295                         tag->m_strTagLine = (*entry)->m_Description.description;
296                         tag->m_strPlot = (*entry)->m_Description.long_description;
297                         tag->m_strRuntime.Format("%d",(*entry)->m_Resources[0].m_Duration);
298                     } else if( (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0 ) {
299                         pItem->SetLabelPreformated(false);
300                         CMusicInfoTag* tag = pItem->GetMusicInfoTag();
301                         CStdStringArray strings;
302                         CStdString buffer;
303  
304                         tag->SetDuration((*entry)->m_Resources[0].m_Duration);
305                         tag->SetTitle((const char*) (*entry)->m_Title);
306                         tag->SetGenre((const char*) JoinString((*entry)->m_Affiliation.genre, " / "));
307                         tag->SetAlbum((const char*) (*entry)->m_Affiliation.album);
308                         tag->SetTrackNumber((*entry)->m_MiscInfo.original_track_number); 
309                         if((*entry)->m_People.artists.GetItemCount()) {
310                             tag->SetAlbumArtist((const char*)(*entry)->m_People.artists.GetFirstItem()->name);
311                             tag->SetArtist((const char*)(*entry)->m_People.artists.GetFirstItem()->name);
312                         } else {
313                             tag->SetAlbumArtist((const char*)(*entry)->m_Creator);
314                             tag->SetArtist((const char*)(*entry)->m_Creator);
315                         }  
316
317                         tag->SetLoaded();
318                     } else if( (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0 ) {
319                       //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
320                     }
321                 }
322             }
323
324             // if there is a thumbnail available set it here
325             if((*entry)->m_ExtraInfo.album_art_uri.GetLength())
326                 pItem->SetThumbnailImage((const char*) (*entry)->m_ExtraInfo.album_art_uri);
327             else if((*entry)->m_Description.icon_uri.GetLength())
328                 pItem->SetThumbnailImage((const char*) (*entry)->m_Description.icon_uri);
329
330             items.Add(pItem);
331
332             ++entry;
333         }
334     }
335
336 cleanup:
337     if (dlgProgress) dlgProgress->Close();
338     return true;
339     
340 failure:
341     if (dlgProgress) dlgProgress->Close();
342     return false;
343 }
344 }