1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "DetectDVDType.h"
10 #include "guilib/LocalizeStrings.h"
11 #include "utils/StringUtils.h"
12 #include "utils/log.h"
13 #include "cdioSupport.h"
14 #include "filesystem/File.h"
15 #include "threads/SingleLock.h"
16 #ifdef TARGET_POSIX
17 #include <sys/types.h>
18 #include <sys/ioctl.h>
19 #include <fcntl.h>
20 #if !defined(TARGET_DARWIN) && !(defined(TARGET_FREEBSD)||defined(TARGET_DRAGONFLY))
21 #include <linux/cdrom.h>
22 #endif
23 #endif
24 #include "settings/AdvancedSettings.h"
25 #include "settings/SettingsComponent.h"
26 #include "GUIUserMessages.h"
27 #include "guilib/GUIComponent.h"
28 #include "guilib/GUIWindowManager.h"
29 #include "Application.h"
30 #include "ServiceBroker.h"
31 #include "storage/MediaManager.h"
32 
33 
34 using namespace XFILE;
35 using namespace MEDIA_DETECT;
36 
37 CCriticalSection CDetectDVDMedia::m_muReadingMedia;
38 CEvent CDetectDVDMedia::m_evAutorun;
39 int CDetectDVDMedia::m_DriveState = DRIVE_CLOSED_NO_MEDIA;
40 CCdInfo* CDetectDVDMedia::m_pCdInfo = NULL;
41 time_t CDetectDVDMedia::m_LastPoll = 0;
42 CDetectDVDMedia* CDetectDVDMedia::m_pInstance = NULL;
43 std::string CDetectDVDMedia::m_diskLabel = "";
44 std::string CDetectDVDMedia::m_diskPath = "";
45 
CDetectDVDMedia()46 CDetectDVDMedia::CDetectDVDMedia() : CThread("DetectDVDMedia"),
47   m_cdio(CLibcdio::GetInstance())
48 {
49   m_bStop = false;
50   m_pInstance = this;
51 }
52 
53 CDetectDVDMedia::~CDetectDVDMedia() = default;
54 
OnStartup()55 void CDetectDVDMedia::OnStartup()
56 {
57   // SetPriority( THREAD_PRIORITY_LOWEST );
58   CLog::Log(LOGDEBUG, "Compiled with libcdio Version 0.%d", LIBCDIO_VERSION_NUM);
59 }
60 
Process()61 void CDetectDVDMedia::Process()
62 {
63 // for apple - currently disable this check since cdio will return null if no media is loaded
64 #if !defined(TARGET_DARWIN)
65   //Before entering loop make sure we actually have a CDrom drive
66   CdIo_t *p_cdio = m_cdio->cdio_open(NULL, DRIVER_DEVICE);
67   if (p_cdio == NULL)
68     return;
69   else
70     m_cdio->cdio_destroy(p_cdio);
71 #endif
72 
73   while (( !m_bStop ))
74   {
75     if (g_application.GetAppPlayer().IsPlayingVideo())
76     {
77       CThread::Sleep(10000);
78     }
79     else
80     {
81       UpdateDvdrom();
82       m_bStartup = false;
83       CThread::Sleep(2000);
84       if ( m_bAutorun )
85       {
86         CThread::Sleep(
87             1500); // Media in drive, wait 1.5s more to be sure the device is ready for playback
88         m_evAutorun.Set();
89         m_bAutorun = false;
90       }
91     }
92   }
93 }
94 
OnExit()95 void CDetectDVDMedia::OnExit()
96 {
97 }
98 
99 // Gets state of the DVD drive
UpdateDvdrom()100 void CDetectDVDMedia::UpdateDvdrom()
101 {
102   // Signal for WaitMediaReady()
103   // that we are busy detecting the
104   // newly inserted media.
105   {
106     CSingleLock waitLock(m_muReadingMedia);
107     switch (GetTrayState())
108     {
109       case DRIVE_NONE:
110         //! @todo reduce / stop polling for drive updates
111         break;
112 
113       case DRIVE_OPEN:
114         {
115           // Send Message to GUI that disc been ejected
116           SetNewDVDShareUrl("D:\\", false, g_localizeStrings.Get(502));
117           CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REMOVED_MEDIA);
118           CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage( msg );
119           waitLock.Leave();
120           m_DriveState = DRIVE_OPEN;
121           return;
122         }
123         break;
124 
125       case DRIVE_NOT_READY:
126         {
127           // Drive is not ready (closing, opening)
128           SetNewDVDShareUrl("D:\\", false, g_localizeStrings.Get(503));
129           m_DriveState = DRIVE_NOT_READY;
130           // DVD-ROM in undefined state
131           // Better delete old CD Information
132           if ( m_pCdInfo != NULL )
133           {
134             delete m_pCdInfo;
135             m_pCdInfo = NULL;
136           }
137           waitLock.Leave();
138           CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
139           CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage( msg );
140           // Do we really need sleep here? This will fix: [ 1530771 ] "Open tray" problem
141           // CThread::Sleep(6000);
142           return ;
143         }
144         break;
145 
146       case DRIVE_CLOSED_NO_MEDIA:
147         {
148           // Nothing in there...
149           SetNewDVDShareUrl("D:\\", false, g_localizeStrings.Get(504));
150           m_DriveState = DRIVE_CLOSED_NO_MEDIA;
151           // Send Message to GUI that disc has changed
152           CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
153           waitLock.Leave();
154           CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage( msg );
155           return ;
156         }
157         break;
158       case DRIVE_READY:
159 #if !defined(TARGET_DARWIN)
160         return ;
161 #endif
162       case DRIVE_CLOSED_MEDIA_PRESENT:
163         {
164           if ( m_DriveState != DRIVE_CLOSED_MEDIA_PRESENT)
165           {
166             m_DriveState = DRIVE_CLOSED_MEDIA_PRESENT;
167             // Detect ISO9660(mode1/mode2) or CDDA filesystem
168             DetectMediaType();
169             CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
170             waitLock.Leave();
171             CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage( msg );
172             // Tell the application object that a new Cd is inserted
173             // So autorun can be started.
174             if ( !m_bStartup )
175               m_bAutorun = true;
176           }
177           return ;
178         }
179         break;
180     }
181 
182     // We have finished media detection
183     // Signal for WaitMediaReady()
184   }
185 
186 
187 }
188 
189 // Generates the drive url, (like iso9660://)
190 // from the CCdInfo class
DetectMediaType()191 void CDetectDVDMedia::DetectMediaType()
192 {
193   bool bCDDA(false);
194   CLog::Log(LOGINFO, "Detecting DVD-ROM media filesystem...");
195 
196   std::string strNewUrl;
197   CCdIoSupport cdio;
198 
199   // Delete old CD-Information
200   if ( m_pCdInfo != NULL )
201   {
202     delete m_pCdInfo;
203     m_pCdInfo = NULL;
204   }
205 
206   // Detect new CD-Information
207   m_pCdInfo = cdio.GetCdInfo();
208   if (m_pCdInfo == NULL)
209   {
210     CLog::Log(LOGERROR, "Detection of DVD-ROM media failed.");
211     return ;
212   }
213   CLog::Log(LOGINFO, "Tracks overall:%i; Audio tracks:%i; Data tracks:%i",
214             m_pCdInfo->GetTrackCount(),
215             m_pCdInfo->GetAudioTrackCount(),
216             m_pCdInfo->GetDataTrackCount() );
217 
218   // Detect ISO9660(mode1/mode2), CDDA filesystem or UDF
219   if (m_pCdInfo->IsISOHFS(1) || m_pCdInfo->IsIso9660(1) || m_pCdInfo->IsIso9660Interactive(1))
220   {
221     strNewUrl = "iso9660://";
222   }
223   else
224   {
225     if (m_pCdInfo->IsUDF(1))
226       strNewUrl = "D:\\";
227     else if (m_pCdInfo->IsAudio(1))
228     {
229       strNewUrl = "cdda://local/";
230       bCDDA = true;
231     }
232     else
233       strNewUrl = "D:\\";
234   }
235 
236   if (m_pCdInfo->IsISOUDF(1))
237   {
238     if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_detectAsUdf)
239     {
240       strNewUrl = "iso9660://";
241     }
242     else
243     {
244       strNewUrl = "D:\\";
245     }
246   }
247 
248   CLog::Log(LOGINFO, "Using protocol %s", strNewUrl.c_str());
249 
250   if (m_pCdInfo->IsValidFs())
251   {
252     if (!m_pCdInfo->IsAudio(1))
253       CLog::Log(LOGINFO, "Disc label: %s", m_pCdInfo->GetDiscLabel().c_str());
254   }
255   else
256   {
257     CLog::Log(LOGWARNING, "Filesystem is not supported");
258   }
259 
260   std::string strLabel;
261   if (bCDDA)
262   {
263     strLabel = "Audio-CD";
264   }
265   else
266   {
267     strLabel = m_pCdInfo->GetDiscLabel();
268     StringUtils::TrimRight(strLabel);
269   }
270 
271   SetNewDVDShareUrl( strNewUrl , bCDDA, strLabel);
272 }
273 
SetNewDVDShareUrl(const std::string & strNewUrl,bool bCDDA,const std::string & strDiscLabel)274 void CDetectDVDMedia::SetNewDVDShareUrl( const std::string& strNewUrl, bool bCDDA, const std::string& strDiscLabel )
275 {
276   std::string strDescription = "DVD";
277   if (bCDDA) strDescription = "CD";
278 
279   if (strDiscLabel != "") strDescription = strDiscLabel;
280 
281   // Store it in case others want it
282   m_diskLabel = strDescription;
283   m_diskPath = strNewUrl;
284 }
285 
GetTrayState()286 DWORD CDetectDVDMedia::GetTrayState()
287 {
288 #ifdef TARGET_POSIX
289 
290   char* dvdDevice = m_cdio->GetDeviceFileName();
291   if (strlen(dvdDevice) == 0)
292     return DRIVE_NONE;
293 
294   // The following code works with libcdio >= 0.78
295   // To enable it, download and install the latest version from
296   // http://www.gnu.org/software/libcdio/
297   // -d4rk 06/27/07
298 
299 
300   m_dwTrayState = TRAY_CLOSED_MEDIA_PRESENT;
301   CdIo_t* cdio = m_cdio->cdio_open(dvdDevice, DRIVER_UNKNOWN);
302   if (cdio)
303   {
304     static discmode_t discmode = CDIO_DISC_MODE_NO_INFO;
305     int status = m_cdio->mmc_get_tray_status(cdio);
306     static int laststatus = -1;
307     // 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)
308     if (status == 0 && (laststatus != status || discmode == CDIO_DISC_MODE_ERROR))
309       discmode = m_cdio->cdio_get_discmode(cdio);
310 
311     switch(status)
312     {
313     case 0: //closed
314       if (discmode==CDIO_DISC_MODE_NO_INFO || discmode==CDIO_DISC_MODE_ERROR)
315         m_dwTrayState = TRAY_CLOSED_NO_MEDIA;
316       else
317         m_dwTrayState = TRAY_CLOSED_MEDIA_PRESENT;
318       break;
319 
320     case 1: //open
321       m_dwTrayState = TRAY_OPEN;
322       break;
323     }
324     laststatus = status;
325     m_cdio->cdio_destroy(cdio);
326   }
327   else
328     return DRIVE_NOT_READY;
329 
330 #endif // TARGET_POSIX
331 
332   if (m_dwTrayState == TRAY_CLOSED_MEDIA_PRESENT)
333   {
334     if (m_dwLastTrayState != TRAY_CLOSED_MEDIA_PRESENT)
335     {
336       m_dwLastTrayState = m_dwTrayState;
337       return DRIVE_CLOSED_MEDIA_PRESENT;
338     }
339     else
340     {
341       return DRIVE_READY;
342     }
343   }
344   else if (m_dwTrayState == TRAY_CLOSED_NO_MEDIA)
345   {
346     if ( (m_dwLastTrayState != TRAY_CLOSED_NO_MEDIA) && (m_dwLastTrayState != TRAY_CLOSED_MEDIA_PRESENT) )
347     {
348       m_dwLastTrayState = m_dwTrayState;
349       return DRIVE_CLOSED_NO_MEDIA;
350     }
351     else
352     {
353       return DRIVE_READY;
354     }
355   }
356   else if (m_dwTrayState == TRAY_OPEN)
357   {
358     if (m_dwLastTrayState != TRAY_OPEN)
359     {
360       m_dwLastTrayState = m_dwTrayState;
361       return DRIVE_OPEN;
362     }
363     else
364     {
365       return DRIVE_READY;
366     }
367   }
368   else
369   {
370     m_dwLastTrayState = m_dwTrayState;
371   }
372 
373 #ifdef HAS_DVD_DRIVE
374   return DRIVE_NOT_READY;
375 #else
376   return DRIVE_READY;
377 #endif
378 }
379 
UpdateState()380 void CDetectDVDMedia::UpdateState()
381 {
382   CSingleLock waitLock(m_muReadingMedia);
383   m_pInstance->DetectMediaType();
384 }
385 
386 // Static function
387 // Wait for drive, to finish media detection.
WaitMediaReady()388 void CDetectDVDMedia::WaitMediaReady()
389 {
390   CSingleLock waitLock(m_muReadingMedia);
391 }
392 
393 // Static function
394 // Returns status of the DVD Drive
DriveReady()395 int CDetectDVDMedia::DriveReady()
396 {
397   return m_DriveState;
398 }
399 
400 // Static function
401 // Whether a disc is in drive
IsDiscInDrive()402 bool CDetectDVDMedia::IsDiscInDrive()
403 {
404   return m_DriveState == DRIVE_CLOSED_MEDIA_PRESENT;
405 }
406 
407 // Static function
408 // Returns a CCdInfo class, which contains
409 // Media information of the current inserted CD.
410 // Can be NULL
GetCdInfo()411 CCdInfo* CDetectDVDMedia::GetCdInfo()
412 {
413   CSingleLock waitLock(m_muReadingMedia);
414   CCdInfo* pCdInfo = m_pCdInfo;
415   return pCdInfo;
416 }
417 
GetDVDLabel()418 const std::string &CDetectDVDMedia::GetDVDLabel()
419 {
420   return m_diskLabel;
421 }
422 
GetDVDPath()423 const std::string &CDetectDVDMedia::GetDVDPath()
424 {
425   return m_diskPath;
426 }
427