Merge remote branch 'mine/ext-python'
[vuplus_xbmc] / xbmc / filesystem / MythFile.cpp
1 /*
2  *      Copyright (C) 2005-2008 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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "MythFile.h"
23 #include "XBDateTime.h"
24 #include "FileItem.h"
25 #include "utils/URIUtils.h"
26 #include "DllLibCMyth.h"
27 #include "URL.h"
28 #include "DirectoryCache.h"
29 #include "threads/SingleLock.h"
30 #include "utils/log.h"
31 #include "utils/TimeUtils.h"
32
33 extern "C" {
34 #include "cmyth/include/cmyth/cmyth.h"
35 #include "cmyth/include/refmem/refmem.h"
36 }
37
38 using namespace XFILE;
39 using namespace std;
40
41 static void prog_update_callback(cmyth_proginfo_t prog)
42 {
43   CLog::Log(LOGDEBUG, "%s - prog_update_callback", __FUNCTION__);
44 }
45
46 void CMythFile::OnEvent(int event, const string& data)
47 {
48   CSingleLock lock(m_section);
49   m_events.push(make_pair(event, data));
50 }
51
52 bool CMythFile::HandleEvents()
53 {
54   CSingleLock lock(m_section);
55
56   if(m_events.empty())
57     return false;
58
59   while(!m_events.empty())
60   {
61     int event   = m_events.front().first;
62     string data = m_events.front().second;
63     m_events.pop();
64
65     lock.Leave();
66
67     switch (event) {
68     case CMYTH_EVENT_CLOSE:
69       Close();
70       break;
71     case CMYTH_EVENT_LIVETV_CHAIN_UPDATE:
72       {
73         if(m_recorder)
74           m_dll->livetv_chain_update(m_recorder, (char*)data.c_str(), 4096);
75       }
76       break;
77     }
78
79     lock.Enter();
80   }
81   return true;
82 }
83
84 bool CMythFile::SetupConnection(const CURL& url, bool control, bool event, bool database)
85 {
86   if(!m_session)
87     m_session =  CMythSession::AquireSession(url);
88
89   if(!m_session)
90     return false;
91
92   if(!m_dll)
93   {
94     m_dll = m_session->GetLibrary();
95     if(!m_dll)
96       return false;
97   }
98
99   if(control && !m_control)
100   {
101     m_control = m_session->GetControl();
102     if(!m_control)
103       return false;
104   }
105   if(event)
106   {
107     if(!m_session->SetListener(this))
108       return false;
109   }
110   if(database && !m_database)
111   {
112     m_database = m_session->GetDatabase();
113     if(!m_database)
114       return false;
115   }
116
117   return true;
118 }
119
120 bool CMythFile::SetupRecording(const CURL& url)
121 {
122   if (url.GetFileName().Left(11) != "recordings/" &&
123       url.GetFileName().Left(7)  != "movies/" &&
124       url.GetFileName().Left(8)  != "tvshows/")
125     return false;
126
127   if(!SetupConnection(url, true, false, false))
128     return false;
129
130   m_filename = url.GetFileNameWithoutPath();
131
132   m_program = m_dll->proginfo_get_from_basename(m_control, m_filename.c_str());
133   if(!m_program)
134   {
135     CLog::Log(LOGERROR, "%s - unable to get find selected file", __FUNCTION__);
136     return false;
137   }
138
139   m_file = m_dll->conn_connect_file(m_program, m_control, 16*1024, 4096);
140   if(!m_file)
141   {
142     CLog::Log(LOGERROR, "%s - unable to connect to file", __FUNCTION__);
143     return false;
144   }
145
146   /*
147    * proginfo_get_from_basename doesn't return the recording status. Hopefully this will be added to
148    * mythbackend eventually.
149    *
150    * Since cycling through the recorders to check if the program is recording takes some time
151    * (depending on the MythTV backend configuration), make some assumptions based on the recording
152    * end time since nearly all recordings opened won't be recording.
153    */
154   m_recording = false;
155   CDateTime start = GetValue(m_dll->proginfo_rec_start(m_program));
156   CDateTime end   = GetValue(m_dll->proginfo_rec_end(m_program));
157   if (end > start // Assume could be recording if empty date comes back as the epoch
158   &&  end < CDateTime::GetCurrentDateTime())
159     CLog::Log(LOGDEBUG, "%s - Assumed not recording since recording end time before current time: %s",
160               __FUNCTION__, end.GetAsLocalizedDateTime().c_str());
161   else
162   {
163     CLog::Log(LOGDEBUG, "%s - Checking recording status using tuners since recording end time NULL or before current time: %s",
164               __FUNCTION__, end.GetAsLocalizedDateTime().c_str());
165     for(int i=0;i<16 && !m_recording;i++)
166     {
167       cmyth_recorder_t recorder = m_dll->conn_get_recorder_from_num(m_control, i);
168       if(!recorder)
169         continue;
170       if(m_dll->recorder_is_recording(recorder))
171       {
172         cmyth_proginfo_t program = m_dll->recorder_get_cur_proginfo(recorder);
173
174         if(m_dll->proginfo_compare(program, m_program) == 0)
175           m_recording = true;
176         m_dll->ref_release(program);
177       }
178       m_dll->ref_release(recorder);
179     }
180   }
181
182   if (m_recording)
183     CLog::Log(LOGDEBUG, "%s - Currently recording: %s", __FUNCTION__, m_filename.c_str());
184
185   return true;
186 }
187
188 bool CMythFile::SetupLiveTV(const CURL& url)
189 {
190   if (url.GetFileName().Left(9) != "channels/")
191     return false;
192
193   if(!SetupConnection(url, true, true, true))
194     return false;
195
196   CStdString channel = url.GetFileNameWithoutPath();
197   if(!URIUtils::GetExtension(channel).Equals(".ts"))
198   {
199     CLog::Log(LOGERROR, "%s - invalid channel url %s", __FUNCTION__, channel.c_str());
200     return false;
201   }
202   URIUtils::RemoveExtension(channel);
203
204   for(int i=0;i<16;i++)
205   {
206     m_recorder = m_dll->conn_get_recorder_from_num(m_control, i);
207     if(!m_recorder)
208       continue;
209
210     if(m_dll->recorder_is_recording(m_recorder))
211     {
212       /* for now don't allow reuse of tuners, we would have to change tuner on channel *
213        * and make sure we don't stop the tuner when stopping playback as that affects  *
214        * other clients                                                                 */
215 #if 0
216       /* if already recording, check if it is this channel */
217       cmyth_proginfo_t program;
218       program = m_dll->recorder_get_cur_proginfo(m_recorder);
219       if(program)
220       {
221         if(channel == GetValue(m_dll->proginfo_chanstr(program)))
222         {
223           m_dll->ref_release(program);
224           break;
225         }
226         m_dll->ref_release(program);
227       }
228 #endif
229     }
230     else
231     {
232       /* not recording, check if it supports this channel */
233       if(m_dll->recorder_check_channel(m_recorder, (char*)channel.c_str()) == 0)
234         break;
235     }
236     m_dll->ref_release(m_recorder);
237     m_recorder = NULL;
238   }
239
240   if(!m_recorder)
241   {
242     CLog::Log(LOGERROR, "%s - unable to get recorder", __FUNCTION__);
243     return false;
244   }
245
246   m_recording = !!m_dll->recorder_is_recording(m_recorder);
247   if(!m_recording)
248     CLog::Log(LOGDEBUG, "%s - recorder isn't running, let's start it", __FUNCTION__);
249
250   char* msg = NULL;
251   if(!(m_recorder = m_dll->spawn_live_tv(m_recorder, 16*1024, 4096, prog_update_callback, &msg, (char*)channel.c_str())))
252   {
253     CLog::Log(LOGERROR, "%s - unable to spawn live tv: %s", __FUNCTION__, msg ? msg : "");
254     return false;
255   }
256
257   m_program = m_dll->recorder_get_cur_proginfo(m_recorder);
258   m_timestamp = CTimeUtils::GetTimeMS();
259   if(m_program)
260     m_starttime = m_dll->proginfo_rec_start(m_program);
261
262   if(m_recording)
263   {
264     /* recorder was running when we started, seek to last position */
265     if(!m_dll->livetv_seek(m_recorder, 0, SEEK_END))
266       CLog::Log(LOGDEBUG, "%s - failed to seek to last position", __FUNCTION__);
267   }
268
269   m_filename = GetValue(m_dll->recorder_get_filename(m_recorder));
270   return true;
271 }
272
273 bool CMythFile::SetupFile(const CURL& url)
274 {
275   if (url.GetFileName().Left(6) != "files/")
276     return false;
277
278   if(!SetupConnection(url, true, false, false))
279     return false;
280
281   m_filename = url.GetFileName().Mid(6);
282
283   m_file = m_dll->conn_connect_path((char*)m_filename.c_str(), m_control, 16*1024, 4096);
284   if(!m_file)
285   {
286     CLog::Log(LOGERROR, "%s - unable to connect to file", __FUNCTION__);
287     return false;
288   }
289
290   if(m_dll->file_length(m_file) == 0)
291   {
292     CLog::Log(LOGERROR, "%s - file is empty, probably doesn't even exist", __FUNCTION__);
293     return false;
294   }
295
296   return true;
297 }
298
299 bool CMythFile::Open(const CURL& url)
300 {
301   Close();
302
303   CStdString path(url.GetFileName());
304
305   if (path.Left(11) == "recordings/" ||
306       path.Left(7)  == "movies/" ||
307       path.Left(8)  == "tvshows/")
308   {
309     if(!SetupRecording(url))
310       return false;
311
312     CLog::Log(LOGDEBUG, "%s - file: size %"PRIu64", start %"PRIu64", ", __FUNCTION__,  (int64_t)m_dll->file_length(m_file), (int64_t)m_dll->file_start(m_file));
313   }
314   else if (path.Left(9) == "channels/")
315   {
316
317     if(!SetupLiveTV(url))
318       return false;
319
320     CLog::Log(LOGDEBUG, "%s - recorder has started on filename %s", __FUNCTION__, m_filename.c_str());
321   }
322   else if (path.Left(6) == "files/")
323   {
324     if(!SetupFile(url))
325       return false;
326
327     CLog::Log(LOGDEBUG, "%s - file: size %"PRId64", start %"PRId64", ", __FUNCTION__,  (int64_t)m_dll->file_length(m_file), (int64_t)m_dll->file_start(m_file));
328   }
329   else
330   {
331     CLog::Log(LOGERROR, "%s - invalid path specified %s", __FUNCTION__, path.c_str());
332     return false;
333   }
334
335   /* check for any events */
336   HandleEvents();
337
338   return true;
339 }
340
341
342 void CMythFile::Close()
343 {
344   if(!m_dll)
345     return;
346
347   if(m_starttime)
348   {
349     m_dll->ref_release(m_starttime);
350     m_starttime = NULL;
351   }
352   if(m_program)
353   {
354     m_dll->ref_release(m_program);
355     m_program = NULL;
356   }
357   if(m_recorder)
358   {
359     m_dll->recorder_stop_livetv(m_recorder);
360     m_dll->ref_release(m_recorder);
361     m_recorder = NULL;
362   }
363   if(m_file)
364   {
365     m_dll->ref_release(m_file);
366     m_file = NULL;
367   }
368   if(m_session)
369   {
370     m_session->SetListener(NULL);
371     CMythSession::ReleaseSession(m_session);
372     m_session = NULL;
373   }
374 }
375
376 CMythFile::CMythFile()
377 {
378   m_dll         = NULL;
379   m_starttime   = NULL;
380   m_program     = NULL;
381   m_recorder    = NULL;
382   m_control     = NULL;
383   m_database    = NULL;
384   m_file        = NULL;
385   m_session     = NULL;
386   m_timestamp   = 0;
387   m_recording   = false;
388 }
389
390 CMythFile::~CMythFile()
391 {
392   Close();
393 }
394
395 bool CMythFile::Exists(const CURL& url)
396 {
397   CStdString path(url.GetFileName());
398
399   /*
400    * mythbackend provides access to the .mpg or .nuv recordings. The associated thumbnails
401    * (*.mpg.png or *.nuv.png) and channel icons, which are an arbitrary image format, are requested
402    * through the files/ path.
403    */
404   if ((path.Left(11) == "recordings/"
405     || path.Left(7)  == "movies/"
406     || path.Left(8)  == "tvshows/")
407     && (URIUtils::GetExtension(path).Equals(".mpg")
408     ||  URIUtils::GetExtension(path).Equals(".nuv")))
409   {
410     if(!SetupConnection(url, true, false, false))
411       return false;
412
413     m_filename = url.GetFileNameWithoutPath();
414     m_program = m_dll->proginfo_get_from_basename(m_control, m_filename.c_str());
415     if(!m_program)
416     {
417       CLog::Log(LOGERROR, "%s - unable to get find %s", __FUNCTION__, m_filename.c_str());
418       return false;
419     }
420     return true;
421   }
422   else if(path.Left(6) == "files/")
423     return true;
424
425   return false;
426 }
427
428 bool CMythFile::Delete(const CURL& url)
429 {
430   CStdString path(url.GetFileName());
431
432   if (path.Left(11) == "recordings/" ||
433       path.Left(7)  == "movies/" ||
434       path.Left(8)  == "tvshows/")
435   {
436     /* this will setup all interal variables */
437     if(!Exists(url))
438       return false;
439     if(!m_program)
440       return false;
441
442     if(m_dll->proginfo_delete_recording(m_control, m_program))
443     {
444       CLog::Log(LOGDEBUG, "%s - Error deleting recording: %s", __FUNCTION__, url.GetFileName().c_str());
445       return false;
446     }
447
448     if (path.Left(8) == "tvshows/")
449     {
450       /*
451        * Clear the directory cache for the TV Shows folder so the listing is accurate if this was
452        * the last TV Show in the current directory that was deleted.
453        */
454       CURL tvshows(url);
455       tvshows.SetFileName("tvshows/");
456       g_directoryCache.ClearDirectory(tvshows.Get());
457     }
458
459     return true;
460   }
461   return false;
462 }
463
464 int64_t CMythFile::Seek(int64_t pos, int whence)
465 {
466   CLog::Log(LOGDEBUG, "%s - seek to pos %"PRId64", whence %d", __FUNCTION__, pos, whence);
467
468   if(whence == SEEK_POSSIBLE)
469   {
470     if(m_recorder)
471       return 0;
472     else
473       return 1;
474   }
475
476   int64_t result;
477   if(m_recorder)
478     result = -1; //m_dll->livetv_seek(m_recorder, pos, whence);
479   else if(m_file)
480     result = m_dll->file_seek(m_file, pos, whence);
481   else
482     result = -1;
483
484   return result;
485 }
486
487 int64_t CMythFile::GetPosition()
488 {
489   if(m_recorder)
490     return m_dll->livetv_seek(m_recorder, 0, SEEK_CUR);
491   else
492     return m_dll->file_seek(m_file, 0, SEEK_CUR);
493   return -1;
494 }
495
496 int64_t CMythFile::GetLength()
497 {
498   if(m_file)
499     return m_dll->file_length(m_file);
500   return -1;
501 }
502
503 unsigned int CMythFile::Read(void* buffer, int64_t size)
504 {
505   /* check for any events */
506   HandleEvents();
507
508   /* file might have gotten closed */
509   if(!m_recorder && !m_file)
510     return 0;
511
512   int ret;
513   if(m_recorder)
514     ret = m_dll->livetv_read(m_recorder, (char*)buffer, (unsigned long)size);
515   else
516     ret = m_dll->file_read(m_file, (char*)buffer, (unsigned long)size);
517
518   if(ret < 0)
519   {
520     CLog::Log(LOGERROR, "%s - cmyth read returned error %d", __FUNCTION__, ret);
521     return 0;
522   }
523   return ret;
524 }
525
526 bool CMythFile::SkipNext()
527 {
528   HandleEvents();
529   if(m_recorder)
530     return m_dll->recorder_is_recording(m_recorder) > 0;
531
532   return false;
533 }
534
535 bool CMythFile::UpdateItem(CFileItem& item)
536 {
537   /*
538    * UpdateItem should only return true if a LiveTV item has changed via a channel change, or the
539    * program being aired on the current channel has changed requiring the UI to update the currently
540    * playing information.
541    *
542    * Check by comparing the current title with the new title.
543    */
544   if (!m_recorder)
545     return false;
546
547   if (!m_program || !m_session)
548     return false;
549
550   CStdString title = item.m_strTitle;
551   m_session->SetFileItemMetaData(item, m_program);
552   return title != item.m_strTitle;
553 }
554
555 int CMythFile::GetTotalTime()
556 {
557   if(m_recorder && m_timestamp + 5000 < CTimeUtils::GetTimeMS())
558   {
559     m_timestamp = CTimeUtils::GetTimeMS();
560     if(m_program)
561       m_dll->ref_release(m_program);
562     m_program = m_dll->recorder_get_cur_proginfo(m_recorder);
563   }
564
565   if(m_program && m_recorder)
566     return m_dll->proginfo_length_sec(m_program) * 1000;
567
568   return -1;
569 }
570
571 int CMythFile::GetStartTime()
572 {
573   if(m_program && m_recorder && m_starttime)
574   {
575     cmyth_timestamp_t start = m_dll->proginfo_start(m_program);
576
577     double diff = difftime(m_dll->timestamp_to_unixtime(start), m_dll->timestamp_to_unixtime(m_starttime));
578
579     m_dll->ref_release(start);
580
581     return (int)(diff * 1000);
582   }
583   return 0;
584 }
585
586 bool CMythFile::ChangeChannel(int direction, const CStdString &channel)
587 {
588   CLog::Log(LOGDEBUG, "%s - channel change started", __FUNCTION__);
589
590   if(direction == CHANNEL_DIRECTION_SAME)
591   {
592     if(!m_program || channel != GetValue(m_dll->proginfo_chanstr(m_program)))
593     {
594       if(m_dll->recorder_pause(m_recorder) < 0)
595       {
596         CLog::Log(LOGDEBUG, "%s - failed to pause recorder", __FUNCTION__);
597         return false;
598       }
599
600       CLog::Log(LOGDEBUG, "%s - chainging channel to %s", __FUNCTION__, channel.c_str());
601       if(m_dll->recorder_set_channel(m_recorder, (char*)channel.c_str()) < 0)
602       {
603         CLog::Log(LOGDEBUG, "%s - failed to change channel", __FUNCTION__);
604         return false;
605       }
606     }
607   }
608   else
609   {
610     if(m_dll->recorder_pause(m_recorder) < 0)
611     {
612       CLog::Log(LOGDEBUG, "%s - failed to pause recorder", __FUNCTION__);
613       return false;
614     }
615
616     CLog::Log(LOGDEBUG, "%s - chainging channel direction %d", __FUNCTION__, direction);
617     if(m_dll->recorder_change_channel(m_recorder, (cmyth_channeldir_t)direction) < 0)
618     {
619       CLog::Log(LOGDEBUG, "%s - failed to change channel", __FUNCTION__);
620       return false;
621     }
622   }
623
624   if(!m_dll->livetv_chain_switch_last(m_recorder))
625     CLog::Log(LOGDEBUG, "%s - failed to change to last item in chain", __FUNCTION__);
626
627   if(m_program)
628     m_dll->ref_release(m_program);
629   m_program = m_dll->recorder_get_cur_proginfo(m_recorder);
630
631   CLog::Log(LOGDEBUG, "%s - channel change done", __FUNCTION__);
632   return true;
633 }
634
635 bool CMythFile::NextChannel()
636 {
637   return ChangeChannel(CHANNEL_DIRECTION_UP, "");
638 }
639
640 bool CMythFile::PrevChannel()
641 {
642   return ChangeChannel(CHANNEL_DIRECTION_DOWN, "");
643 }
644
645 bool CMythFile::SelectChannel(unsigned int channel)
646 {
647   return ChangeChannel(CHANNEL_DIRECTION_SAME,""+channel);
648 }
649
650 bool CMythFile::CanRecord()
651 {
652   if(m_recorder || m_recording)
653     return true;
654
655   return false;
656 }
657
658 bool CMythFile::IsRecording()
659 {
660   return m_recording;
661 }
662
663 bool CMythFile::Record(bool bOnOff)
664 {
665   if(m_recorder)
666   {
667     if(!m_database)
668       return false;
669
670     int ret;
671     if(bOnOff)
672       ret = m_dll->livetv_keep_recording(m_recorder, m_database, 1);
673     else
674       ret = m_dll->livetv_keep_recording(m_recorder, m_database, 0);
675
676     if(ret < 0)
677     {
678       CLog::Log(LOGERROR, "%s - failed to turn on recording", __FUNCTION__);
679       return false;
680     }
681
682     m_recording = bOnOff;
683     return true;
684   }
685   else
686   {
687     if(m_recording)
688     {
689       if(m_dll->proginfo_stop_recording(m_control, m_program) < 0)
690         return false;
691
692       m_recording = false;
693       return true;
694     }
695   }
696   return false;
697 }
698
699 bool CMythFile::GetCommBreakList(cmyth_commbreaklist_t& commbreaklist)
700 {
701   if (m_program)
702   {
703     commbreaklist = m_dll->get_commbreaklist(m_control, m_program);
704     return true;
705   }
706   return false;
707 }
708
709 bool CMythFile::GetCutList(cmyth_commbreaklist_t& commbreaklist)
710 {
711   if (m_program)
712   {
713     commbreaklist = m_dll->get_cutlist(m_control, m_program);
714     return true;
715   }
716   return false;
717 }