[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / filesystem / HTSPDirectory.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "HTSPDirectory.h"
23 #include "URL.h"
24 #include "FileItem.h"
25 #include "settings/Settings.h"
26 #include "guilib/LocalizeStrings.h"
27 #include "cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.h"
28 #include "threads/SingleLock.h"
29 #include "utils/log.h"
30 #include "utils/TimeUtils.h"
31 #include "utils/StringUtils.h"
32
33 extern "C" {
34 #include "libhts/htsmsg.h"
35 #include "libhts/htsmsg_binary.h"
36 }
37
38 using namespace XFILE;
39 using namespace HTSP;
40
41 struct SSession
42 {
43   SSession()
44   {
45     session = NULL;
46     port    = 0;
47     refs    = 0;
48     last    = 0;
49   }
50
51   std::string            hostname;
52   int                    port;
53   std::string            username;
54   std::string            password;
55   CHTSPDirectorySession* session;
56   int                    refs;
57   unsigned int           last;
58 };
59
60 struct STimedOut
61 {
62   STimedOut(DWORD idle) : m_idle(idle)
63   {
64     m_time = XbmcThreads::SystemClockMillis();
65   }
66   bool operator()(SSession& data)
67   {
68     return data.refs == 0 && (m_time - data.last) > m_idle;
69   }
70   unsigned int m_idle;
71   unsigned int m_time;
72 };
73
74 typedef std::vector<SSession> SSessions;
75
76
77 static SSessions         g_sessions;
78 static CCriticalSection  g_section;
79
80
81 CHTSPDirectorySession::CHTSPDirectorySession() : CThread("HTSPDirectorySession")
82 {
83 }
84
85 CHTSPDirectorySession::~CHTSPDirectorySession()
86 {
87   Close();
88 }
89
90 CHTSPDirectorySession* CHTSPDirectorySession::Acquire(const CURL& url)
91 {
92   CSingleLock lock(g_section);
93
94   for(SSessions::iterator it = g_sessions.begin(); it != g_sessions.end(); it++)
95   {
96     if(it->hostname == url.GetHostName()
97     && it->port     == url.GetPort()
98     && it->username == url.GetUserName()
99     && it->password == url.GetPassWord())
100     {
101       it->refs++;
102       return it->session;
103     }
104   }
105   lock.Leave();
106
107   CHTSPDirectorySession* session = new CHTSPDirectorySession();
108   if(session->Open(url))
109   {
110     SSession data;
111     data.hostname = url.GetHostName();
112     data.port     = url.GetPort();
113     data.username = url.GetUserName();
114     data.password = url.GetPassWord();
115     data.session  = session;
116     data.refs     = 1;
117     lock.Enter();
118     g_sessions.push_back(data);
119     return session;
120   }
121
122   delete session;
123   return NULL;
124 }
125
126 void CHTSPDirectorySession::Release(CHTSPDirectorySession* &session)
127 {
128   if(session == NULL)
129     return;
130
131   CSingleLock lock(g_section);
132   for(SSessions::iterator it = g_sessions.begin(); it != g_sessions.end(); it++)
133   {
134     if(it->session == session)
135     {
136       it->refs--;
137       it->last = XbmcThreads::SystemClockMillis();
138       return;
139     }
140   }
141   CLog::Log(LOGERROR, "CHTSPDirectorySession::Release - release of invalid session");
142   ASSERT(0);
143 }
144
145 void CHTSPDirectorySession::CheckIdle(DWORD idle)
146 {
147   CSingleLock lock(g_section);
148   STimedOut timeout(idle);
149
150   for(SSessions::iterator it = g_sessions.begin(); it != g_sessions.end();)
151   {
152     if(timeout(*it))
153     {
154       CLog::Log(LOGINFO, "CheckIdle - Closing session to htsp://%s:%i", it->hostname.c_str(), it->port);
155       delete it->session;
156       it = g_sessions.erase(it);
157     }
158     else
159       it++;
160   }
161 }
162
163 bool CHTSPDirectorySession::Open(const CURL& url)
164 {
165   if(!m_session.Connect(url.GetHostName(), url.GetPort()))
166     return false;
167
168   if(m_session.GetProtocol() < 2)
169   {
170     CLog::Log(LOGERROR, "CHTSPDirectory::GetDirectory - incompatible protocol version %d", m_session.GetProtocol());
171     return false;
172   }
173
174   if(!url.GetUserName().IsEmpty())
175     m_session.Auth(url.GetUserName(), url.GetPassWord());
176
177   if(!m_session.SendEnableAsync())
178     return false;
179
180   Create();
181
182   m_started.WaitMSec(30000);
183   return !m_bStop;
184 }
185
186 void CHTSPDirectorySession::Close()
187 {
188   m_bStop = true;
189   m_session.Abort();
190   StopThread();
191   m_session.Close();
192 }
193
194 htsmsg_t* CHTSPDirectorySession::ReadResult(htsmsg_t* m)
195 {
196   CSingleLock lock(m_section);
197   unsigned    seq (m_session.AddSequence());
198
199   SMessage &message(m_queue[seq]);
200   message.event = new CEvent();
201   message.msg   = NULL;
202
203   lock.Leave();
204   htsmsg_add_u32(m, "seq", seq);
205   if(!m_session.SendMessage(m))
206   {
207     m_queue.erase(seq);
208     return NULL;
209   }
210
211   if(!message.event->WaitMSec(2000))
212     CLog::Log(LOGERROR, "CHTSPDirectorySession::ReadResult - Timeout waiting for response");
213   lock.Enter();
214
215   m =    message.msg;
216   delete message.event;
217
218   m_queue.erase(seq);
219
220   return m;
221 }
222
223 bool CHTSPDirectorySession::GetEvent(SEvent& event, uint32_t id)
224 {
225   if(id == 0)
226   {
227     event.Clear();
228     return false;
229   }
230
231   SEvents::iterator it = m_events.find(id);
232   if(it != m_events.end())
233   {
234     event = it->second;
235     return true;
236   }
237
238   htsmsg_t *msg = htsmsg_create_map();
239   htsmsg_add_str(msg, "method", "getEvent");
240   htsmsg_add_u32(msg, "eventId", id);
241   if((msg = ReadResult(msg)) == NULL)
242   {
243     CLog::Log(LOGDEBUG, "CHTSPSession::GetEvent - failed to get event %u", id);
244     return false;
245   }
246   if(!CHTSPSession::ParseEvent(msg, id, event))
247     return false;
248
249   m_events[id] = event;
250   return true;
251 }
252
253 void CHTSPDirectorySession::Process()
254 {
255   CLog::Log(LOGDEBUG, "CHTSPDirectorySession::Process() - Starting");
256
257   htsmsg_t* msg;
258
259   while(!m_bStop)
260   {
261     if((msg = m_session.ReadMessage()) == NULL)
262       break;
263
264     uint32_t seq;
265     if(htsmsg_get_u32(msg, "seq", &seq) == 0)
266     {
267       CSingleLock lock(m_section);
268       SMessages::iterator it = m_queue.find(seq);
269       if(it != m_queue.end())
270       {
271         it->second.msg = msg;
272         it->second.event->Set();
273         continue;
274       }
275     }
276
277     const char* method;
278     if((method = htsmsg_get_str(msg, "method")) == NULL)
279     {
280       htsmsg_destroy(msg);
281       continue;
282     }
283
284     if     (strstr(method, "channelAdd"))
285       CHTSPSession::ParseChannelUpdate(msg, m_channels);
286     else if(strstr(method, "channelUpdate"))
287       CHTSPSession::ParseChannelUpdate(msg, m_channels);
288     else if(strstr(method, "channelRemove"))
289       CHTSPSession::ParseChannelRemove(msg, m_channels);
290     if     (strstr(method, "tagAdd"))
291       CHTSPSession::ParseTagUpdate(msg, m_tags);
292     else if(strstr(method, "tagUpdate"))
293       CHTSPSession::ParseTagUpdate(msg, m_tags);
294     else if(strstr(method, "tagRemove"))
295       CHTSPSession::ParseTagRemove(msg, m_tags);
296     else if(strstr(method, "initialSyncCompleted"))
297       m_started.Set();
298
299     htsmsg_destroy(msg);
300   }
301
302   m_started.Set();
303   CLog::Log(LOGDEBUG, "CHTSPDirectorySession::Process() - Exiting");
304 }
305
306 SChannels CHTSPDirectorySession::GetChannels()
307 {
308   return GetChannels(0);
309 }
310
311 SChannels CHTSPDirectorySession::GetChannels(int tag)
312 {
313   CSingleLock lock(m_section);
314   if(tag == 0)
315     return m_channels;
316
317   STags::iterator it = m_tags.find(tag);
318   if(it == m_tags.end())
319   {
320     SChannels channels;
321     return channels;
322   }
323   return GetChannels(it->second);
324 }
325
326 SChannels CHTSPDirectorySession::GetChannels(STag& tag)
327 {
328   CSingleLock lock(m_section);
329   SChannels channels;
330
331   std::vector<int>::iterator it;
332   for(it = tag.channels.begin(); it != tag.channels.end(); it++)
333   {
334     SChannels::iterator it2 = m_channels.find(*it);
335     if(it2 == m_channels.end())
336     {
337       CLog::Log(LOGERROR, "CHTSPDirectorySession::GetChannels - tag points to unknown channel %d", *it);
338       continue;
339     }
340     channels[*it] = it2->second;
341   }
342   return channels;
343 }
344
345
346 STags CHTSPDirectorySession::GetTags()
347 {
348   CSingleLock lock(m_section);
349   return m_tags;
350 }
351
352 CHTSPDirectory::CHTSPDirectory(void)
353 {
354   m_session = NULL;
355 }
356
357 CHTSPDirectory::~CHTSPDirectory(void)
358 {
359   CHTSPDirectorySession::Release(m_session);
360 }
361
362
363 bool CHTSPDirectory::GetChannels(const CURL &base, CFileItemList &items)
364 {
365   SChannels channels = m_session->GetChannels();
366   return GetChannels(base, items, channels, 0);
367 }
368
369 bool CHTSPDirectory::GetChannels( const CURL &base
370                                 , CFileItemList &items
371                                 , SChannels channels
372                                 , int tag)
373 {
374   CURL url(base);
375
376   SEvent event;
377
378   for(SChannels::iterator it = channels.begin(); it != channels.end(); it++)
379   {
380     if(!m_session->GetEvent(event, it->second.event))
381       event.Clear();
382
383     CFileItemPtr item(new CFileItem("", true));
384
385     url.SetFileName("");
386     item->SetPath(url.Get());
387     CHTSPSession::ParseItem(it->second, tag, event, *item);
388     item->m_bIsFolder = false;
389     item->SetLabel(item->m_strTitle);
390     item->m_strTitle = StringUtils::Format("%d", it->second.num);
391
392     items.Add(item);
393   }
394
395   items.AddSortMethod(SortByTrackNumber,   554, LABEL_MASKS("%K[ - %B]", "%Z", "%L", ""));
396   items.AddSortMethod(SortByAlbum,         558, LABEL_MASKS("%B", "%Z", "%L", ""), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
397   items.AddSortMethod(SortByLabel,         551, LABEL_MASKS("%Z", "%B", "%L", ""), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
398
399   items.SetContent("livetv");
400
401   return !channels.empty();
402
403 }
404
405 bool CHTSPDirectory::GetTag(const CURL &base, CFileItemList &items)
406 {
407   CURL url(base);
408
409   int id = atoi(url.GetFileName().Mid(5));
410
411   SChannels channels = m_session->GetChannels(id);
412   if(channels.empty())
413     return false;
414
415   return GetChannels(base, items, channels, id);
416 }
417
418
419 bool CHTSPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
420 {
421   CURL                    url(strPath);
422
423   CHTSPDirectorySession::Release(m_session);
424   if(!(m_session = CHTSPDirectorySession::Acquire(url)))
425     return false;
426
427
428   if(url.GetFileName().IsEmpty())
429   {
430     CFileItemPtr item;
431
432     item.reset(new CFileItem("", true));
433     url.SetFileName("tags/0/");
434     item->SetPath(url.Get());
435     item->SetLabel(g_localizeStrings.Get(22018));
436     item->SetLabelPreformated(true);
437     items.Add(item);
438
439     STags tags = m_session->GetTags();
440     CStdString filename, label;
441     for(STags::iterator it = tags.begin(); it != tags.end(); it++)
442     {
443       filename = StringUtils::Format("tags/%d/", it->second.id);
444       label = StringUtils::Format("Tag: %s", it->second.name.c_str());
445
446       item.reset(new CFileItem("", true));
447       url.SetFileName(filename);
448       item->SetPath(url.Get());
449       item->SetLabel(label);
450       item->SetLabelPreformated(true);
451       item->SetArt("thumb", it->second.icon);
452       items.Add(item);
453     }
454
455     return true;
456   }
457   else if(url.GetFileName().Left(5) == "tags/")
458     return GetTag(url, items);
459   return false;
460 }