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