Merge remote branch 'origin/trunk' into trac-8658
[xbmc:xbmc-antiquated.git] / xbmc / DetectDVDType.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 "system.h"
23
24 #ifdef HAS_DVD_DRIVE
25
26 #include "DetectDVDType.h"
27 #include "LocalizeStrings.h"
28 #include "StringUtils.h"
29 #include "utils/log.h"
30 #include "FileSystem/cdioSupport.h"
31 #include "FileSystem/iso9660.h"
32 #include "SingleLock.h"
33 #ifdef _LINUX
34 #include <sys/types.h>
35 #include <sys/ioctl.h>
36 #include <fcntl.h>
37 #ifndef __APPLE__
38 #include <linux/cdrom.h>
39 #endif
40 #endif
41 #include "AdvancedSettings.h"
42 #include "GUIUserMessages.h"
43 #include "Util.h"
44 #include "Picture.h"
45 #if defined (LIBCDIO_VERSION_NUM) && (LIBCDIO_VERSION_NUM > 77) || defined (__APPLE__)
46 #define USING_CDIO78
47 #endif
48 #include "GUIWindowManager.h"
49 #include "FileSystem/File.h"
50 #include "FileItem.h"
51
52 using namespace XFILE;
53 using namespace MEDIA_DETECT;
54
55 CCriticalSection CDetectDVDMedia::m_muReadingMedia;
56 CEvent CDetectDVDMedia::m_evAutorun;
57 int CDetectDVDMedia::m_DriveState = DRIVE_CLOSED_NO_MEDIA;
58 CCdInfo* CDetectDVDMedia::m_pCdInfo = NULL;
59 time_t CDetectDVDMedia::m_LastPoll = 0;
60 CDetectDVDMedia* CDetectDVDMedia::m_pInstance = NULL;
61 CStdString CDetectDVDMedia::m_diskLabel = "";
62 CStdString CDetectDVDMedia::m_diskPath = "";
63
64 CDetectDVDMedia::CDetectDVDMedia()
65 {
66   m_bAutorun = false;
67   m_bStop = false;
68   m_dwLastTrayState = 0;
69   m_bStartup = true;  // Do not autorun on startup
70   m_pInstance = this;
71   m_cdio = CLibcdio::GetInstance();
72 }
73
74 CDetectDVDMedia::~CDetectDVDMedia()
75 {
76 }
77
78 void CDetectDVDMedia::OnStartup()
79 {
80   // SetPriority( THREAD_PRIORITY_LOWEST );
81   CLog::Log(LOGDEBUG, "Compiled with libcdio Version 0.%d", LIBCDIO_VERSION_NUM);
82 }
83
84 void CDetectDVDMedia::Process()
85 {
86   SetName("CDetectDVDMedia");
87 // for apple - currently disable this check since cdio will return null if no media is loaded
88 #ifndef __APPLE__
89   //Before entering loop make sure we actually have a CDrom drive
90   CdIo_t *p_cdio = m_cdio->cdio_open(NULL, DRIVER_DEVICE);
91   if (p_cdio == NULL)
92     return;
93   else
94     m_cdio->cdio_destroy(p_cdio);
95 #endif
96
97   while (( !m_bStop ))
98   {
99     UpdateDvdrom();
100     m_bStartup = false;
101     Sleep(2000);
102     if ( m_bAutorun )
103     {
104       Sleep(1500); // Media in drive, wait 1.5s more to be sure the device is ready for playback
105       m_evAutorun.Set();
106       m_bAutorun = false;
107     }
108   }
109 }
110
111 void CDetectDVDMedia::OnExit()
112 {
113 }
114
115 // Gets state of the DVD drive
116 VOID CDetectDVDMedia::UpdateDvdrom()
117 {
118   // Signal for WaitMediaReady()
119   // that we are busy detecting the
120   // newly inserted media.
121   {
122     CSingleLock waitLock(m_muReadingMedia);
123     switch (GetTrayState())
124     {
125       case DRIVE_NONE:
126         // TODO: reduce / stop polling for drive updates
127         break;
128
129       case DRIVE_OPEN:
130         {
131           // Send Message to GUI that disc been ejected
132           SetNewDVDShareUrl("D:\\", false, g_localizeStrings.Get(502));
133           m_isoReader.Reset();
134           CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REMOVED_MEDIA);
135           g_windowManager.SendThreadMessage( msg );
136           waitLock.Leave();
137           m_DriveState = DRIVE_OPEN;
138           return;
139         }
140         break;
141
142       case DRIVE_NOT_READY:
143         {
144           // drive is not ready (closing, opening)
145           m_isoReader.Reset();
146           SetNewDVDShareUrl("D:\\", false, g_localizeStrings.Get(503));
147           m_DriveState = DRIVE_NOT_READY;
148           // DVD-ROM in undefined state
149           // better delete old CD Information
150           if ( m_pCdInfo != NULL )
151           {
152             delete m_pCdInfo;
153             m_pCdInfo = NULL;
154           }
155           waitLock.Leave();
156           CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
157           g_windowManager.SendThreadMessage( msg );
158           // Do we really need sleep here? This will fix: [ 1530771 ] "Open tray" problem
159           // Sleep(6000);
160           return ;
161         }
162         break;
163
164       case DRIVE_CLOSED_NO_MEDIA:
165         {
166           // nothing in there...
167           m_isoReader.Reset();
168           SetNewDVDShareUrl("D:\\", false, g_localizeStrings.Get(504));
169           m_DriveState = DRIVE_CLOSED_NO_MEDIA;
170           // Send Message to GUI that disc has changed
171           CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
172           waitLock.Leave();
173           g_windowManager.SendThreadMessage( msg );
174           return ;
175         }
176         break;
177       case DRIVE_READY:
178 #ifndef __APPLE__
179         return ;
180 #endif
181       case DRIVE_CLOSED_MEDIA_PRESENT:
182         {
183           if ( m_DriveState != DRIVE_CLOSED_MEDIA_PRESENT)
184           {
185             m_DriveState = DRIVE_CLOSED_MEDIA_PRESENT;
186             // drive has been closed and is ready
187             OutputDebugString("Drive closed media present, remounting...\n");
188             CIoSupport::Dismount("Cdrom0");
189             CIoSupport::RemapDriveLetter('D', "Cdrom0");
190             // Detect ISO9660(mode1/mode2) or CDDA filesystem
191             DetectMediaType();
192             CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
193             waitLock.Leave();
194             g_windowManager.SendThreadMessage( msg );
195             // Tell the application object that a new Cd is inserted
196             // So autorun can be started.
197             if ( !m_bStartup )
198               m_bAutorun = true;
199           }
200           return ;
201         }
202         break;
203     }
204
205     // We have finished media detection
206     // Signal for WaitMediaReady()
207   }
208
209
210 }
211
212 // Generates the drive url, (like iso9660://)
213 // from the CCdInfo class
214 void CDetectDVDMedia::DetectMediaType()
215 {
216   bool bCDDA(false);
217   CLog::Log(LOGINFO, "Detecting DVD-ROM media filesystem...");
218
219   CStdString strNewUrl;
220   CCdIoSupport cdio;
221
222   // Delete old CD-Information
223   if ( m_pCdInfo != NULL )
224   {
225     delete m_pCdInfo;
226     m_pCdInfo = NULL;
227   }
228
229   // Detect new CD-Information
230   m_pCdInfo = cdio.GetCdInfo();
231   if (m_pCdInfo == NULL)
232   {
233     CLog::Log(LOGERROR, "Detection of DVD-ROM media failed.");
234     return ;
235   }
236   CLog::Log(LOGINFO, "Tracks overall:%i; Audio tracks:%i; Data tracks:%i",
237             m_pCdInfo->GetTrackCount(),
238             m_pCdInfo->GetAudioTrackCount(),
239             m_pCdInfo->GetDataTrackCount() );
240
241   // Detect ISO9660(mode1/mode2), CDDA filesystem or UDF
242   if (m_pCdInfo->IsISOHFS(1) || m_pCdInfo->IsIso9660(1) || m_pCdInfo->IsIso9660Interactive(1))
243   {
244     strNewUrl = "iso9660://";
245     m_isoReader.Scan();
246   }
247   else
248   {
249     if (m_pCdInfo->IsUDF(1) || m_pCdInfo->IsUDFX(1))
250       strNewUrl = "D:\\";
251     else if (m_pCdInfo->IsAudio(1))
252     {
253       strNewUrl = "cdda://local/";
254       bCDDA = true;
255     }
256     else
257       strNewUrl = "D:\\";
258   }
259
260   if (m_pCdInfo->IsISOUDF(1))
261   {
262     if (!g_advancedSettings.m_detectAsUdf)
263     {
264       strNewUrl = "iso9660://";
265       m_isoReader.Scan();
266     }
267     else
268     {
269       strNewUrl = "D:\\";
270     }
271   }
272
273   CLog::Log(LOGINFO, "Using protocol %s", strNewUrl.c_str());
274
275   if (m_pCdInfo->IsValidFs())
276   {
277     if (!m_pCdInfo->IsAudio(1))
278       CLog::Log(LOGINFO, "Disc label: %s", m_pCdInfo->GetDiscLabel().c_str());
279   }
280   else
281   {
282     CLog::Log(LOGWARNING, "Filesystem is not supported");
283   }
284
285   CStdString strLabel = "";
286   if (bCDDA)
287   {
288     strLabel = "Audio-CD";
289   }
290   else
291   {
292     strLabel = m_pCdInfo->GetDiscLabel();
293     strLabel.TrimRight(" ");
294   }
295
296   SetNewDVDShareUrl( strNewUrl , bCDDA, strLabel);
297 }
298
299 void CDetectDVDMedia::SetNewDVDShareUrl( const CStdString& strNewUrl, bool bCDDA, const CStdString& strDiscLabel )
300 {
301   CStdString strDescription = "DVD";
302   if (bCDDA) strDescription = "CD";
303
304   if (strDiscLabel != "") strDescription = strDiscLabel;
305
306   // store it in case others want it
307   m_diskLabel = strDescription;
308   m_diskPath = strNewUrl;
309
310   // delete any previously cached disc thumbnail
311   CStdString strCache = "special://temp/dvdicon.tbn";
312   if (CFile::Exists(strCache))
313     CFile::Delete(strCache);
314
315   // find and cache disc thumbnail
316   if (IsDiscInDrive() && !bCDDA)
317   {
318     CStdString strThumb;
319     CStdStringArray thumbs;
320     StringUtils::SplitString(g_advancedSettings.m_dvdThumbs, "|", thumbs);
321     for (unsigned int i = 0; i < thumbs.size(); ++i)
322     {
323       CUtil::AddFileToFolder(m_diskPath, thumbs[i], strThumb);
324       CLog::Log(LOGDEBUG,"%s: looking for disc thumb:[%s]", __FUNCTION__, strThumb.c_str());
325       if (CFile::Exists(strThumb))
326       {
327         CLog::Log(LOGDEBUG,"%s: found disc thumb:[%s], caching as:[%s]", __FUNCTION__, strThumb.c_str(), strCache.c_str());
328         CPicture::CreateThumbnail(strThumb, strCache);
329         break;
330       }
331     }
332   }
333 }
334
335 DWORD CDetectDVDMedia::GetTrayState()
336 {
337 #ifdef _LINUX
338
339   char* dvdDevice = m_cdio->GetDeviceFileName();
340   if (strlen(dvdDevice) == 0)
341     return DRIVE_NONE;
342
343 #ifndef USING_CDIO78
344
345   int fd = 0;
346
347   fd = open(dvdDevice, O_RDONLY | O_NONBLOCK);
348   if (fd<0)
349   {
350     CLog::Log(LOGERROR, "Unable to open CD-ROM device %s for polling.", dvdDevice);
351     return DRIVE_NOT_READY;
352   }
353
354   int drivestatus = ioctl(fd, CDROM_DRIVE_STATUS, 0);
355
356   switch(drivestatus)
357   {
358   case CDS_NO_INFO:
359     m_dwTrayState = TRAY_CLOSED_NO_MEDIA;
360     break;
361
362   case CDS_NO_DISC:
363     m_dwTrayState = TRAY_CLOSED_NO_MEDIA;
364     break;
365
366   case CDS_TRAY_OPEN:
367     m_dwTrayState = TRAY_OPEN;
368     break;
369
370   case CDS_DISC_OK:
371     m_dwTrayState = TRAY_CLOSED_MEDIA_PRESENT;
372     break;
373
374   case CDS_DRIVE_NOT_READY:
375     close(fd);
376     return DRIVE_NOT_READY;
377
378   default:
379     m_dwTrayState = TRAY_CLOSED_NO_MEDIA;
380   }
381
382   close(fd);
383
384 #else
385
386   // The following code works with libcdio >= 0.78
387   // To enable it, download and install the latest version from
388   // http://www.gnu.org/software/libcdio/
389   // -d4rk 06/27/07
390
391
392   m_dwTrayState = TRAY_CLOSED_MEDIA_PRESENT;
393   CdIo_t* cdio = m_cdio->cdio_open(dvdDevice, DRIVER_UNKNOWN);
394   if (cdio)
395   {
396     static discmode_t discmode = CDIO_DISC_MODE_NO_INFO;
397     int status = m_cdio->mmc_get_tray_status(cdio);
398     static int laststatus = -1;
399     // We only poll for new discmode when status has changed or there have been read errors (The last usually happens when new media is inserted)
400     if (status == 0 && (laststatus != status || discmode == CDIO_DISC_MODE_ERROR))
401       discmode = m_cdio->cdio_get_discmode(cdio);
402
403     switch(status)
404     {
405     case 0: //closed
406       if (discmode==CDIO_DISC_MODE_NO_INFO || discmode==CDIO_DISC_MODE_ERROR)
407         m_dwTrayState = TRAY_CLOSED_NO_MEDIA;
408       else
409         m_dwTrayState = TRAY_CLOSED_MEDIA_PRESENT;
410       break;
411
412     case 1: //open
413       m_dwTrayState = TRAY_OPEN;
414       break;
415     }
416     laststatus = status;
417     m_cdio->cdio_destroy(cdio);
418   }
419   else
420     return DRIVE_NOT_READY;
421
422 #endif // USING_CDIO78
423 #endif // _LINUX
424
425   if (m_dwTrayState == TRAY_CLOSED_MEDIA_PRESENT)
426   {
427     if (m_dwLastTrayState != TRAY_CLOSED_MEDIA_PRESENT)
428     {
429       m_dwLastTrayState = m_dwTrayState;
430       return DRIVE_CLOSED_MEDIA_PRESENT;
431     }
432     else
433     {
434       return DRIVE_READY;
435     }
436   }
437   else if (m_dwTrayState == TRAY_CLOSED_NO_MEDIA)
438   {
439     if ( (m_dwLastTrayState != TRAY_CLOSED_NO_MEDIA) && (m_dwLastTrayState != TRAY_CLOSED_MEDIA_PRESENT) )
440     {
441       m_dwLastTrayState = m_dwTrayState;
442       return DRIVE_CLOSED_NO_MEDIA;
443     }
444     else
445     {
446       return DRIVE_READY;
447     }
448   }
449   else if (m_dwTrayState == TRAY_OPEN)
450   {
451     if (m_dwLastTrayState != TRAY_OPEN)
452     {
453       m_dwLastTrayState = m_dwTrayState;
454       return DRIVE_OPEN;
455     }
456     else
457     {
458       return DRIVE_READY;
459     }
460   }
461   else
462   {
463     m_dwLastTrayState = m_dwTrayState;
464   }
465
466 #ifdef HAS_DVD_DRIVE
467   return DRIVE_NOT_READY;
468 #else
469   return DRIVE_READY;
470 #endif
471 }
472
473 void CDetectDVDMedia::UpdateState()
474 {
475   CSingleLock waitLock(m_muReadingMedia);
476   m_pInstance->DetectMediaType();
477 }
478
479 // Static function
480 // Wait for drive, to finish media detection.
481 void CDetectDVDMedia::WaitMediaReady()
482 {
483   CSingleLock waitLock(m_muReadingMedia);
484 }
485
486 // Static function
487 // Returns status of the DVD Drive
488 int CDetectDVDMedia::DriveReady()
489 {
490   return m_DriveState;
491 }
492
493 // Static function
494 // Whether a disc is in drive
495 bool CDetectDVDMedia::IsDiscInDrive()
496 {
497   return m_DriveState == DRIVE_CLOSED_MEDIA_PRESENT;
498 }
499
500 // Static function
501 // Returns a CCdInfo class, which contains
502 // Media information of the current
503 // inserted CD.
504 // Can be NULL
505 CCdInfo* CDetectDVDMedia::GetCdInfo()
506 {
507   CSingleLock waitLock(m_muReadingMedia);
508   CCdInfo* pCdInfo = m_pCdInfo;
509   return pCdInfo;
510 }
511
512 const CStdString &CDetectDVDMedia::GetDVDLabel()
513 {
514   return m_diskLabel;
515 }
516
517 const CStdString &CDetectDVDMedia::GetDVDPath()
518 {
519   return m_diskPath;
520 }
521
522 #endif