diff options
Diffstat (limited to 'xbmc/storage')
-rw-r--r-- | xbmc/storage/AutorunMediaJob.cpp | 75 | ||||
-rw-r--r-- | xbmc/storage/AutorunMediaJob.h | 25 | ||||
-rw-r--r-- | xbmc/storage/CMakeLists.txt | 16 | ||||
-rw-r--r-- | xbmc/storage/DetectDVDType.cpp | 446 | ||||
-rw-r--r-- | xbmc/storage/DetectDVDType.h | 88 | ||||
-rw-r--r-- | xbmc/storage/IStorageProvider.h | 95 | ||||
-rw-r--r-- | xbmc/storage/MediaManager.cpp | 821 | ||||
-rw-r--r-- | xbmc/storage/MediaManager.h | 147 | ||||
-rw-r--r-- | xbmc/storage/cdioSupport.cpp | 958 | ||||
-rw-r--r-- | xbmc/storage/cdioSupport.h | 347 | ||||
-rw-r--r-- | xbmc/storage/discs/IDiscDriveHandler.h | 93 |
11 files changed, 3111 insertions, 0 deletions
diff --git a/xbmc/storage/AutorunMediaJob.cpp b/xbmc/storage/AutorunMediaJob.cpp new file mode 100644 index 0000000..72e784f --- /dev/null +++ b/xbmc/storage/AutorunMediaJob.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "AutorunMediaJob.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "interfaces/builtins/Builtins.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +CAutorunMediaJob::CAutorunMediaJob(const std::string &label, const std::string &path): + m_path(path), + m_label(label) +{ +} + +bool CAutorunMediaJob::DoWork() +{ + CGUIDialogSelect* pDialog= CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + + // wake up and turn off the screensaver if it's active + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->WakeUpScreenSaverAndDPMS(); + + pDialog->Reset(); + if (!m_label.empty()) + pDialog->SetHeading(CVariant{m_label}); + else + pDialog->SetHeading(CVariant{g_localizeStrings.Get(21331)}); + + pDialog->Add(g_localizeStrings.Get(21332)); + pDialog->Add(g_localizeStrings.Get(21333)); + pDialog->Add(g_localizeStrings.Get(21334)); + pDialog->Add(g_localizeStrings.Get(21335)); + + pDialog->Open(); + + int selection = pDialog->GetSelectedItem(); + if (selection >= 0) + { + std::string strAction = + StringUtils::Format("ActivateWindow({}, {})", GetWindowString(selection), m_path); + CBuiltins::GetInstance().Execute(strAction); + } + + return true; +} + +const char *CAutorunMediaJob::GetWindowString(int selection) +{ + switch (selection) + { + case 0: + return "Videos"; + case 1: + return "Music"; + case 2: + return "Pictures"; + case 3: + return "FileManager"; + default: + return "FileManager"; + } +} diff --git a/xbmc/storage/AutorunMediaJob.h b/xbmc/storage/AutorunMediaJob.h new file mode 100644 index 0000000..52524f1 --- /dev/null +++ b/xbmc/storage/AutorunMediaJob.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Job.h" + +#include <string> + +class CAutorunMediaJob : public CJob +{ +public: + CAutorunMediaJob(const std::string &label, const std::string &path); + + bool DoWork() override; +private: + const char *GetWindowString(int selection); + + std::string m_path, m_label; +}; diff --git a/xbmc/storage/CMakeLists.txt b/xbmc/storage/CMakeLists.txt new file mode 100644 index 0000000..eb282bb --- /dev/null +++ b/xbmc/storage/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SOURCES AutorunMediaJob.cpp + MediaManager.cpp) + +set(HEADERS AutorunMediaJob.h + IStorageProvider.h + MediaManager.h) + +if(ENABLE_OPTICAL) + list(APPEND SOURCES cdioSupport.cpp + DetectDVDType.cpp) + list(APPEND HEADERS cdioSupport.h + DetectDVDType.h + discs/IDiscDriveHandler.h) +endif() + +core_add_library(storage) diff --git a/xbmc/storage/DetectDVDType.cpp b/xbmc/storage/DetectDVDType.cpp new file mode 100644 index 0000000..2392901 --- /dev/null +++ b/xbmc/storage/DetectDVDType.cpp @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DetectDVDType.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cdioSupport.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <mutex> + +using namespace MEDIA_DETECT; +using namespace std::chrono_literals; + +CCriticalSection CDetectDVDMedia::m_muReadingMedia; +CEvent CDetectDVDMedia::m_evAutorun; +DriveState CDetectDVDMedia::m_DriveState{DriveState::CLOSED_NO_MEDIA}; +CCdInfo* CDetectDVDMedia::m_pCdInfo = NULL; +time_t CDetectDVDMedia::m_LastPoll = 0; +CDetectDVDMedia* CDetectDVDMedia::m_pInstance = NULL; +std::string CDetectDVDMedia::m_diskLabel = ""; +std::string CDetectDVDMedia::m_diskPath = ""; +UTILS::DISCS::DiscInfo CDetectDVDMedia::m_discInfo; + +CDetectDVDMedia::CDetectDVDMedia() : CThread("DetectDVDMedia"), + m_cdio(CLibcdio::GetInstance()) +{ + m_bStop = false; + m_pInstance = this; +} + +CDetectDVDMedia::~CDetectDVDMedia() = default; + +void CDetectDVDMedia::OnStartup() +{ + // SetPriority( ThreadPriority::LOWEST ); + CLog::Log(LOGDEBUG, "Compiled with libcdio Version 0.{}", LIBCDIO_VERSION_NUM); +} + +void CDetectDVDMedia::Process() +{ +// for apple - currently disable this check since cdio will return null if no media is loaded +#if !defined(TARGET_DARWIN) + //Before entering loop make sure we actually have a CDrom drive + CdIo_t *p_cdio = m_cdio->cdio_open(NULL, DRIVER_DEVICE); + if (p_cdio == NULL) + return; + else + m_cdio->cdio_destroy(p_cdio); +#endif + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + while (!m_bStop) + { + if (appPlayer->IsPlayingVideo()) + { + CThread::Sleep(10000ms); + } + else + { + UpdateDvdrom(); + m_bStartup = false; + CThread::Sleep(2000ms); + if (m_bAutorun) + { + // Media in drive, wait 1.5s more to be sure the device is ready for playback + CThread::Sleep(1500ms); + m_evAutorun.Set(); + m_bAutorun = false; + } + } + } +} + +void CDetectDVDMedia::OnExit() +{ +} + +// Gets state of the DVD drive +void CDetectDVDMedia::UpdateDvdrom() +{ + // Signal for WaitMediaReady() + // that we are busy detecting the + // newly inserted media. + { + std::unique_lock<CCriticalSection> waitLock(m_muReadingMedia); + switch (PollDriveState()) + { + case DriveState::NONE: + //! @todo reduce / stop polling for drive updates + break; + + case DriveState::OPEN: + { + // Send Message to GUI that disc been ejected + SetNewDVDShareUrl(CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath), false, + g_localizeStrings.Get(502)); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REMOVED_MEDIA); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + // Clear all stored info + Clear(); + // Update drive state + waitLock.unlock(); + m_DriveState = DriveState::OPEN; + return; + } + break; + case DriveState::NOT_READY: + { + // Drive is not ready (closing, opening) + SetNewDVDShareUrl(CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath), false, + g_localizeStrings.Get(503)); + m_DriveState = DriveState::NOT_READY; + // DVD-ROM in undefined state + // Better delete old CD Information + if (m_pCdInfo != nullptr) + { + delete m_pCdInfo; + m_pCdInfo = nullptr; + } + waitLock.unlock(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + // Do we really need sleep here? This will fix: [ 1530771 ] "Open tray" problem + // CThread::Sleep(6000ms); + return; + } + break; + + case DriveState::CLOSED_NO_MEDIA: + { + // Nothing in there... + SetNewDVDShareUrl(CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath), false, + g_localizeStrings.Get(504)); + m_DriveState = DriveState::CLOSED_NO_MEDIA; + // Send Message to GUI that disc has changed + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES); + waitLock.unlock(); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + return; + } + break; + case DriveState::READY: +#if !defined(TARGET_DARWIN) + return ; +#endif + break; + case DriveState::CLOSED_MEDIA_PRESENT: + { + if (m_DriveState != DriveState::CLOSED_MEDIA_PRESENT) + { + m_DriveState = DriveState::CLOSED_MEDIA_PRESENT; + // Detect ISO9660(mode1/mode2) or CDDA filesystem + DetectMediaType(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES); + waitLock.unlock(); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + // Tell the application object that a new Cd is inserted + // So autorun can be started. + if (!m_bStartup) + m_bAutorun = true; + } + return; + } + default: + break; + } + + // We have finished media detection + // Signal for WaitMediaReady() + } + + +} + +// Generates the drive url, (like iso9660://) +// from the CCdInfo class +void CDetectDVDMedia::DetectMediaType() +{ + bool bCDDA(false); + CLog::Log(LOGINFO, "Detecting DVD-ROM media filesystem..."); + + // Probe and store DiscInfo result + // even if no valid tracks are detected we might still be able to play the disc via libdvdnav or libbluray + // as long as they can correctly detect the disc + UTILS::DISCS::DiscInfo discInfo; + if (UTILS::DISCS::GetDiscInfo(discInfo, + CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath))) + { + m_discInfo = discInfo; + } + + std::string strNewUrl; + CCdIoSupport cdio; + + // Delete old CD-Information + if ( m_pCdInfo != NULL ) + { + delete m_pCdInfo; + m_pCdInfo = NULL; + } + + // Detect new CD-Information + m_pCdInfo = cdio.GetCdInfo(); + if (m_pCdInfo == NULL) + { + CLog::Log(LOGERROR, "Detection of DVD-ROM media failed."); + return ; + } + CLog::Log(LOGINFO, "Tracks overall:{}; Audio tracks:{}; Data tracks:{}", + m_pCdInfo->GetTrackCount(), m_pCdInfo->GetAudioTrackCount(), + m_pCdInfo->GetDataTrackCount()); + + // Detect ISO9660(mode1/mode2), CDDA filesystem or UDF + if (m_pCdInfo->IsISOHFS(1) || m_pCdInfo->IsIso9660(1) || m_pCdInfo->IsIso9660Interactive(1)) + { + strNewUrl = "iso9660://"; + } + else + { + if (m_pCdInfo->IsUDF(1)) + strNewUrl = CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath); + else if (m_pCdInfo->IsAudio(1)) + { + strNewUrl = "cdda://local/"; + bCDDA = true; + } + else + strNewUrl = CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath); + } + + if (m_pCdInfo->IsISOUDF(1)) + { + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_detectAsUdf) + { + strNewUrl = "iso9660://"; + } + else + { + strNewUrl = CServiceBroker::GetMediaManager().TranslateDevicePath(m_diskPath); + } + } + + CLog::Log(LOGINFO, "Using protocol {}", strNewUrl); + + if (m_pCdInfo->IsValidFs()) + { + if (!m_pCdInfo->IsAudio(1)) + CLog::Log(LOGINFO, "Disc label: {}", m_pCdInfo->GetDiscLabel()); + } + else + { + CLog::Log(LOGWARNING, "Filesystem is not supported"); + } + + std::string strLabel; + if (bCDDA) + { + strLabel = "Audio-CD"; + } + else + { + strLabel = m_pCdInfo->GetDiscLabel(); + StringUtils::TrimRight(strLabel); + } + + SetNewDVDShareUrl( strNewUrl , bCDDA, strLabel); +} + +void CDetectDVDMedia::SetNewDVDShareUrl( const std::string& strNewUrl, bool bCDDA, const std::string& strDiscLabel ) +{ + std::string strDescription = "DVD"; + if (bCDDA) strDescription = "CD"; + + if (strDiscLabel != "") strDescription = strDiscLabel; + + // Store it in case others want it + m_diskLabel = strDescription; + m_diskPath = strNewUrl; +} + +DriveState CDetectDVDMedia::PollDriveState() +{ + const std::shared_ptr<IDiscDriveHandler> platformDiscDriveHandler = + CServiceBroker::GetMediaManager().GetDiscDriveHandler(); + if (!platformDiscDriveHandler) + { + return DriveState::NONE; + } + + const std::string discPath = CServiceBroker::GetMediaManager().TranslateDevicePath(""); + const DriveState driveState = platformDiscDriveHandler->GetDriveState(discPath); + switch (driveState) + { + case DriveState::CLOSED_MEDIA_UNDEFINED: + // We only poll for new traystatus when driveState has changed or if the last recorded + // tray state is undefined + if (driveState == DriveState::CLOSED_MEDIA_UNDEFINED && + (m_LastTrayState == TrayState::UNDEFINED || driveState != m_LastDriveState)) + { + m_TrayState = platformDiscDriveHandler->GetTrayState(discPath); + } + break; + case DriveState::OPEN: + m_TrayState = TrayState::OPEN; + break; + default: + m_TrayState = TrayState::UNDEFINED; + break; + } + m_LastDriveState = driveState; + + if (m_TrayState == TrayState::CLOSED_MEDIA_PRESENT) + { + if (m_LastTrayState != TrayState::CLOSED_MEDIA_PRESENT) + { + m_LastTrayState = m_TrayState; + return DriveState::CLOSED_MEDIA_PRESENT; + } + else + { + return DriveState::READY; + } + } + else if (m_TrayState == TrayState::CLOSED_NO_MEDIA) + { + if ((m_LastTrayState != TrayState::CLOSED_NO_MEDIA) && + (m_LastTrayState != TrayState::CLOSED_MEDIA_PRESENT)) + { + m_LastTrayState = m_TrayState; + return DriveState::CLOSED_NO_MEDIA; + } + else + { + return DriveState::READY; + } + } + else if (m_TrayState == TrayState::OPEN) + { + if (m_LastTrayState != TrayState::OPEN) + { + m_LastTrayState = m_TrayState; + return DriveState::OPEN; + } + else + { + return DriveState::READY; + } + } + else + { + m_LastTrayState = m_TrayState; + } + +#ifdef HAS_DVD_DRIVE + return DriveState::NOT_READY; +#else + return DriveState::READY; +#endif +} + +void CDetectDVDMedia::UpdateState() +{ + std::unique_lock<CCriticalSection> waitLock(m_muReadingMedia); + m_pInstance->DetectMediaType(); +} + +// Static function +// Wait for drive, to finish media detection. +void CDetectDVDMedia::WaitMediaReady() +{ + std::unique_lock<CCriticalSection> waitLock(m_muReadingMedia); +} + +// Static function +// Returns status of the DVD Drive +bool CDetectDVDMedia::DriveReady() +{ + return m_DriveState == DriveState::READY; +} + +DriveState CDetectDVDMedia::GetDriveState() +{ + return m_DriveState; +} + +// Static function +// Whether a disc is in drive +bool CDetectDVDMedia::IsDiscInDrive() +{ + return m_DriveState == DriveState::CLOSED_MEDIA_PRESENT; +} + +// Static function +// Returns a CCdInfo class, which contains +// Media information of the current inserted CD. +// Can be NULL +CCdInfo* CDetectDVDMedia::GetCdInfo() +{ + std::unique_lock<CCriticalSection> waitLock(m_muReadingMedia); + CCdInfo* pCdInfo = m_pCdInfo; + return pCdInfo; +} + +const std::string &CDetectDVDMedia::GetDVDLabel() +{ + if (!m_discInfo.empty()) + { + return m_discInfo.name; + } + + return m_diskLabel; +} + +const std::string &CDetectDVDMedia::GetDVDPath() +{ + return m_diskPath; +} + + +void CDetectDVDMedia::Clear() +{ + if (!m_discInfo.empty()) + { + m_discInfo.clear(); + } + m_diskLabel.clear(); + m_diskPath.clear(); +} diff --git a/xbmc/storage/DetectDVDType.h b/xbmc/storage/DetectDVDType.h new file mode 100644 index 0000000..c54272f --- /dev/null +++ b/xbmc/storage/DetectDVDType.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// CDetectDVDMedia +// Thread running in the background to detect a CD change and the filesystem +// +// by Bobbin007 in 2003 + +#include "PlatformDefs.h" + +#ifdef HAS_DVD_DRIVE + +#include "storage/discs/IDiscDriveHandler.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include "utils/DiscsUtils.h" + +#include <memory> +#include <string> + +namespace MEDIA_DETECT +{ +class CCdInfo; +class CLibcdio; + +class CDetectDVDMedia : public CThread +{ +public: + CDetectDVDMedia(); + ~CDetectDVDMedia() override; + + void OnStartup() override; + void OnExit() override; + void Process() override; + + static void WaitMediaReady(); + static bool IsDiscInDrive(); + static bool DriveReady(); + static DriveState GetDriveState(); + static CCdInfo* GetCdInfo(); + static CEvent m_evAutorun; + + static const std::string &GetDVDLabel(); + static const std::string &GetDVDPath(); + + static void UpdateState(); +protected: + void UpdateDvdrom(); + DriveState PollDriveState(); + + + void DetectMediaType(); + void SetNewDVDShareUrl( const std::string& strNewUrl, bool bCDDA, const std::string& strDiscLabel ); + + void Clear(); + +private: + static CCriticalSection m_muReadingMedia; + + static DriveState m_DriveState; + static time_t m_LastPoll; + static CDetectDVDMedia* m_pInstance; + + static CCdInfo* m_pCdInfo; + + bool m_bStartup = true; // Do not autorun on startup + bool m_bAutorun = false; + TrayState m_TrayState{TrayState::UNDEFINED}; + TrayState m_LastTrayState{TrayState::UNDEFINED}; + DriveState m_LastDriveState{DriveState::NONE}; + + static std::string m_diskLabel; + static std::string m_diskPath; + + std::shared_ptr<CLibcdio> m_cdio; + + /*! \brief Stores the DiscInfo of the current disk */ + static UTILS::DISCS::DiscInfo m_discInfo; +}; +} +#endif diff --git a/xbmc/storage/IStorageProvider.h b/xbmc/storage/IStorageProvider.h new file mode 100644 index 0000000..aa9ecaf --- /dev/null +++ b/xbmc/storage/IStorageProvider.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "MediaSource.h" + +#include <memory> +#include <string> +#include <vector> +#ifdef HAS_DVD_DRIVE +#include "cdioSupport.h" +#endif + +namespace MEDIA_DETECT +{ +namespace STORAGE +{ +/*! \brief Abstracts a generic storage device type*/ +enum class Type +{ + UNKNOWN, /*!< the storage type is unknown */ + OPTICAL /*!< an optical device (e.g. DVD or Bluray) */ +}; + +/*! \brief Abstracts a generic storage device */ +struct StorageDevice +{ + /*! Device name/label */ + std::string label{}; + /*! Device mountpoint/path */ + std::string path{}; + /*! The storage type (e.g. OPTICAL) */ + STORAGE::Type type{STORAGE::Type::UNKNOWN}; +}; +} // namespace STORAGE +} // namespace MEDIA_DETECT + +class IStorageEventsCallback +{ +public: + virtual ~IStorageEventsCallback() = default; + + /*! \brief Callback executed when a new storage device is added + * @param device the storage device + */ + virtual void OnStorageAdded(const MEDIA_DETECT::STORAGE::StorageDevice& device) = 0; + + /*! \brief Callback executed when a new storage device is safely removed + * @param device the storage device + */ + virtual void OnStorageSafelyRemoved(const MEDIA_DETECT::STORAGE::StorageDevice& device) = 0; + + /*! \brief Callback executed when a new storage device is unsafely removed + * @param device the storage device + */ + virtual void OnStorageUnsafelyRemoved(const MEDIA_DETECT::STORAGE::StorageDevice& device) = 0; +}; + +class IStorageProvider +{ +public: + virtual ~IStorageProvider() = default; + + virtual void Initialize() = 0; + virtual void Stop() = 0; + + virtual void GetLocalDrives(VECSOURCES &localDrives) = 0; + virtual void GetRemovableDrives(VECSOURCES &removableDrives) = 0; + virtual std::string GetFirstOpticalDeviceFileName() + { +#ifdef HAS_DVD_DRIVE + return std::string(MEDIA_DETECT::CLibcdio::GetInstance()->GetDeviceFileName()); +#else + return ""; +#endif + } + + virtual bool Eject(const std::string& mountpath) = 0; + + virtual std::vector<std::string> GetDiskUsage() = 0; + + virtual bool PumpDriveChangeEvents(IStorageEventsCallback *callback) = 0; + + /**\brief Called by media manager to create platform storage provider + * + * This method used to create platform specified storage provider + */ + static std::unique_ptr<IStorageProvider> CreateInstance(); +}; diff --git a/xbmc/storage/MediaManager.cpp b/xbmc/storage/MediaManager.cpp new file mode 100644 index 0000000..a1efbd9 --- /dev/null +++ b/xbmc/storage/MediaManager.cpp @@ -0,0 +1,821 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "MediaManager.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "guilib/GUIComponent.h" +#include "guilib/LocalizeStrings.h" +#include "utils/URIUtils.h" + +#include <mutex> +#ifdef TARGET_WINDOWS +#include "platform/win32/WIN32Util.h" +#include "utils/CharsetConverter.h" +#endif +#include "guilib/GUIWindowManager.h" +#ifdef HAS_DVD_DRIVE +#ifndef TARGET_WINDOWS +//! @todo switch all ports to use auto sources +#include <map> +#include <utility> +#include "DetectDVDType.h" +#endif +#endif +#include "Autorun.h" +#include "AutorunMediaJob.h" +#include "GUIUserMessages.h" +#include "addons/VFSEntry.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogPlayEject.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/FileUtils.h" +#include "utils/JobManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <string> +#include <vector> + +#ifdef HAS_DVD_DRIVE +using namespace MEDIA_DETECT; +#endif + +const char MEDIA_SOURCES_XML[] = { "special://profile/mediasources.xml" }; + +CMediaManager::CMediaManager() +{ + m_bhasoptical = false; +} + +void CMediaManager::Stop() +{ + if (m_platformStorage) + m_platformStorage->Stop(); + + m_platformStorage.reset(); +} + +void CMediaManager::Initialize() +{ + if (!m_platformStorage) + { + m_platformStorage = IStorageProvider::CreateInstance(); + } +#ifdef HAS_DVD_DRIVE + m_platformDiscDriveHander = IDiscDriveHandler::CreateInstance(); + m_strFirstAvailDrive = m_platformStorage->GetFirstOpticalDeviceFileName(); +#endif + m_platformStorage->Initialize(); +} + +bool CMediaManager::LoadSources() +{ + // clear our location list + m_locations.clear(); + + // load xml file... + CXBMCTinyXML xmlDoc; + if ( !xmlDoc.LoadFile( MEDIA_SOURCES_XML ) ) + return false; + + TiXmlElement* pRootElement = xmlDoc.RootElement(); + if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "mediasources") != 0) + { + CLog::Log(LOGERROR, "Error loading {}, Line {} ({})", MEDIA_SOURCES_XML, xmlDoc.ErrorRow(), + xmlDoc.ErrorDesc()); + return false; + } + + // load the <network> block + TiXmlNode *pNetwork = pRootElement->FirstChild("network"); + if (pNetwork) + { + TiXmlElement *pLocation = pNetwork->FirstChildElement("location"); + while (pLocation) + { + CNetworkLocation location; + pLocation->Attribute("id", &location.id); + if (pLocation->FirstChild()) + { + location.path = pLocation->FirstChild()->Value(); + m_locations.push_back(location); + } + pLocation = pLocation->NextSiblingElement("location"); + } + } + LoadAddonSources(); + return true; +} + +bool CMediaManager::SaveSources() +{ + CXBMCTinyXML xmlDoc; + TiXmlElement xmlRootElement("mediasources"); + TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement); + if (!pRoot) return false; + + TiXmlElement networkNode("network"); + TiXmlNode *pNetworkNode = pRoot->InsertEndChild(networkNode); + if (pNetworkNode) + { + for (std::vector<CNetworkLocation>::iterator it = m_locations.begin(); it != m_locations.end(); ++it) + { + TiXmlElement locationNode("location"); + locationNode.SetAttribute("id", (*it).id); + TiXmlText value((*it).path); + locationNode.InsertEndChild(value); + pNetworkNode->InsertEndChild(locationNode); + } + } + return xmlDoc.SaveFile(MEDIA_SOURCES_XML); +} + +void CMediaManager::GetLocalDrives(VECSOURCES &localDrives, bool includeQ) +{ + std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); + m_platformStorage->GetLocalDrives(localDrives); +} + +void CMediaManager::GetRemovableDrives(VECSOURCES &removableDrives) +{ + std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); + if (m_platformStorage) + m_platformStorage->GetRemovableDrives(removableDrives); +} + +void CMediaManager::GetNetworkLocations(VECSOURCES &locations, bool autolocations) +{ + for (unsigned int i = 0; i < m_locations.size(); i++) + { + CMediaSource share; + share.strPath = m_locations[i].path; + CURL url(share.strPath); + share.strName = url.GetWithoutUserDetails(); + locations.push_back(share); + } + if (autolocations) + { + CMediaSource share; + share.m_ignore = true; +#ifdef HAS_FILESYSTEM_SMB + share.strPath = "smb://"; + share.strName = g_localizeStrings.Get(20171); + locations.push_back(share); +#endif + +#ifdef HAS_FILESYSTEM_NFS + share.strPath = "nfs://"; + share.strName = g_localizeStrings.Get(20259); + locations.push_back(share); +#endif// HAS_FILESYSTEM_NFS + +#ifdef HAS_UPNP + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP)) + { + const std::string& strDevices = g_localizeStrings.Get(33040); //"% Devices" + share.strPath = "upnp://"; + share.strName = StringUtils::Format(strDevices, "UPnP"); //"UPnP Devices" + locations.push_back(share); + } +#endif + +#ifdef HAS_ZEROCONF + share.strPath = "zeroconf://"; + share.strName = g_localizeStrings.Get(20262); + locations.push_back(share); +#endif + + if (CServiceBroker::IsAddonInterfaceUp()) + { + for (const auto& addon : CServiceBroker::GetVFSAddonCache().GetAddonInstances()) + { + const auto& info = addon->GetProtocolInfo(); + if (!info.type.empty() && info.supportBrowsing) + { + share.strPath = info.type + "://"; + share.strName = g_localizeStrings.GetAddonString(addon->ID(), info.label); + if (share.strName.empty()) + share.strName = g_localizeStrings.Get(info.label); + locations.push_back(share); + } + } + } + } +} + +bool CMediaManager::AddNetworkLocation(const std::string &path) +{ + CNetworkLocation location; + location.path = path; + location.id = (int)m_locations.size(); + m_locations.push_back(location); + return SaveSources(); +} + +bool CMediaManager::HasLocation(const std::string& path) const +{ + for (unsigned int i=0;i<m_locations.size();++i) + { + if (URIUtils::CompareWithoutSlashAtEnd(m_locations[i].path, path)) + return true; + } + + return false; +} + + +bool CMediaManager::RemoveLocation(const std::string& path) +{ + for (unsigned int i=0;i<m_locations.size();++i) + { + if (URIUtils::CompareWithoutSlashAtEnd(m_locations[i].path, path)) + { + // prompt for sources, remove, cancel, + m_locations.erase(m_locations.begin()+i); + return SaveSources(); + } + } + + return false; +} + +bool CMediaManager::SetLocationPath(const std::string& oldPath, const std::string& newPath) +{ + for (unsigned int i=0;i<m_locations.size();++i) + { + if (URIUtils::CompareWithoutSlashAtEnd(m_locations[i].path, oldPath)) + { + m_locations[i].path = newPath; + return SaveSources(); + } + } + + return false; +} + +void CMediaManager::LoadAddonSources() const +{ + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bVirtualShares) + { + CMediaSourceSettings::GetInstance().AddShare("video", GetRootAddonTypeSource("video")); + CMediaSourceSettings::GetInstance().AddShare("programs", GetRootAddonTypeSource("programs")); + CMediaSourceSettings::GetInstance().AddShare("pictures", GetRootAddonTypeSource("pictures")); + CMediaSourceSettings::GetInstance().AddShare("music", GetRootAddonTypeSource("music")); + CMediaSourceSettings::GetInstance().AddShare("games", GetRootAddonTypeSource("games")); + } +} + +CMediaSource CMediaManager::GetRootAddonTypeSource(const std::string& type) const +{ + if (type == "programs" || type == "myprograms") + { + return ComputeRootAddonTypeSource("executable", g_localizeStrings.Get(1043), + "DefaultAddonProgram.png"); + } + else if (type == "video" || type == "videos") + { + return ComputeRootAddonTypeSource("video", g_localizeStrings.Get(1037), + "DefaultAddonVideo.png"); + } + else if (type == "music") + { + return ComputeRootAddonTypeSource("audio", g_localizeStrings.Get(1038), + "DefaultAddonMusic.png"); + } + else if (type == "pictures") + { + return ComputeRootAddonTypeSource("image", g_localizeStrings.Get(1039), + "DefaultAddonPicture.png"); + } + else if (type == "games") + { + return ComputeRootAddonTypeSource("game", g_localizeStrings.Get(35049), "DefaultAddonGame.png"); + } + else + { + CLog::LogF(LOGERROR, "Invalid type {} provided", type); + return {}; + } +} + +CMediaSource CMediaManager::ComputeRootAddonTypeSource(const std::string& type, + const std::string& label, + const std::string& thumb) const +{ + CMediaSource source; + source.strPath = "addons://sources/" + type + "/"; + source.strName = label; + source.m_strThumbnailImage = thumb; + source.m_iDriveType = CMediaSource::SOURCE_TYPE_VPATH; + source.m_ignore = true; + return source; +} + +void CMediaManager::AddAutoSource(const CMediaSource &share, bool bAutorun) +{ + CMediaSourceSettings::GetInstance().AddShare("files", share); + CMediaSourceSettings::GetInstance().AddShare("video", share); + CMediaSourceSettings::GetInstance().AddShare("pictures", share); + CMediaSourceSettings::GetInstance().AddShare("music", share); + CMediaSourceSettings::GetInstance().AddShare("programs", share); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES); + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetWindowManager().SendThreadMessage( msg ); + +#ifdef HAS_DVD_DRIVE + if(bAutorun) + MEDIA_DETECT::CAutorun::ExecuteAutorun(share.strPath); +#endif +} + +void CMediaManager::RemoveAutoSource(const CMediaSource &share) +{ + CMediaSourceSettings::GetInstance().DeleteSource("files", share.strName, share.strPath, true); + CMediaSourceSettings::GetInstance().DeleteSource("video", share.strName, share.strPath, true); + CMediaSourceSettings::GetInstance().DeleteSource("pictures", share.strName, share.strPath, true); + CMediaSourceSettings::GetInstance().DeleteSource("music", share.strName, share.strPath, true); + CMediaSourceSettings::GetInstance().DeleteSource("programs", share.strName, share.strPath, true); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage( msg ); + +#ifdef HAS_DVD_DRIVE + // delete cached CdInfo if any + RemoveCdInfo(TranslateDevicePath(share.strPath, true)); + RemoveDiscInfo(TranslateDevicePath(share.strPath, true)); +#endif +} + +///////////////////////////////////////////////////////////// +// AutoSource status functions: +//! @todo translate cdda://<device>/ + +std::string CMediaManager::TranslateDevicePath(const std::string& devicePath, bool bReturnAsDevice) +{ + std::unique_lock<CCriticalSection> waitLock(m_muAutoSource); + std::string strDevice = devicePath; + // fallback for cdda://local/ and empty devicePath +#ifdef HAS_DVD_DRIVE + if(devicePath.empty() || StringUtils::StartsWith(devicePath, "cdda://local")) + strDevice = m_strFirstAvailDrive; +#endif + +#ifdef TARGET_WINDOWS + if(!m_bhasoptical) + return ""; + + if(bReturnAsDevice == false) + StringUtils::Replace(strDevice, "\\\\.\\",""); + else if(!strDevice.empty() && strDevice[1]==':') + strDevice = StringUtils::Format("\\\\.\\{}:", strDevice[0]); + + URIUtils::RemoveSlashAtEnd(strDevice); +#endif + return strDevice; +} + +bool CMediaManager::IsDiscInDrive(const std::string& devicePath) +{ +#ifdef HAS_DVD_DRIVE +#ifdef TARGET_WINDOWS + if(!m_bhasoptical) + return false; + + std::string strDevice = TranslateDevicePath(devicePath, false); + std::map<std::string,CCdInfo*>::iterator it; + std::unique_lock<CCriticalSection> waitLock(m_muAutoSource); + it = m_mapCdInfo.find(strDevice); + if(it != m_mapCdInfo.end()) + return true; + else + return false; +#else + if(URIUtils::IsDVD(devicePath) || devicePath.empty()) + return MEDIA_DETECT::CDetectDVDMedia::IsDiscInDrive(); //! @todo switch all ports to use auto sources + else + return true; // Assume other paths to be mounted already +#endif +#else + return false; +#endif +} + +bool CMediaManager::IsAudio(const std::string& devicePath) +{ +#ifdef HAS_DVD_DRIVE +#ifdef TARGET_WINDOWS + if(!m_bhasoptical) + return false; + + CCdInfo* pCdInfo = GetCdInfo(devicePath); + if(pCdInfo != NULL && pCdInfo->IsAudio(1)) + return true; + + return false; +#else + //! @todo switch all ports to use auto sources + MEDIA_DETECT::CCdInfo* pInfo = MEDIA_DETECT::CDetectDVDMedia::GetCdInfo(); + if (pInfo != NULL && pInfo->IsAudio(1)) + return true; +#endif +#endif + return false; +} + +bool CMediaManager::HasOpticalDrive() +{ +#ifdef HAS_DVD_DRIVE + if (!m_strFirstAvailDrive.empty()) + return true; +#endif + return false; +} + +DriveState CMediaManager::GetDriveStatus(const std::string& devicePath) +{ +#ifdef HAS_DVD_DRIVE +#ifdef TARGET_WINDOWS + if (!m_bhasoptical || !m_platformDiscDriveHander) + return DriveState::NOT_READY; + + std::string translatedDevicePath = TranslateDevicePath(devicePath, true); + return m_platformDiscDriveHander->GetDriveState(translatedDevicePath); +#else + return MEDIA_DETECT::CDetectDVDMedia::GetDriveState(); +#endif +#else + return DriveState::NOT_READY; +#endif +} + +#ifdef HAS_DVD_DRIVE +CCdInfo* CMediaManager::GetCdInfo(const std::string& devicePath) +{ +#ifdef TARGET_WINDOWS + if(!m_bhasoptical) + return NULL; + + std::string strDevice = TranslateDevicePath(devicePath, false); + std::map<std::string,CCdInfo*>::iterator it; + { + std::unique_lock<CCriticalSection> waitLock(m_muAutoSource); + it = m_mapCdInfo.find(strDevice); + if(it != m_mapCdInfo.end()) + return it->second; + } + + CCdInfo* pCdInfo=NULL; + CCdIoSupport cdio; + pCdInfo = cdio.GetCdInfo((char*)strDevice.c_str()); + if(pCdInfo!=NULL) + { + std::unique_lock<CCriticalSection> waitLock(m_muAutoSource); + m_mapCdInfo.insert(std::pair<std::string,CCdInfo*>(strDevice,pCdInfo)); + } + + return pCdInfo; +#else + return MEDIA_DETECT::CDetectDVDMedia::GetCdInfo(); +#endif +} + +bool CMediaManager::RemoveCdInfo(const std::string& devicePath) +{ + if(!m_bhasoptical) + return false; + + std::string strDevice = TranslateDevicePath(devicePath, false); + + std::map<std::string,CCdInfo*>::iterator it; + std::unique_lock<CCriticalSection> waitLock(m_muAutoSource); + it = m_mapCdInfo.find(strDevice); + if(it != m_mapCdInfo.end()) + { + if(it->second != NULL) + delete it->second; + + m_mapCdInfo.erase(it); + return true; + } + return false; +} + +std::string CMediaManager::GetDiskLabel(const std::string& devicePath) +{ +#ifdef TARGET_WINDOWS_STORE + return ""; // GetVolumeInformationW nut support in UWP app +#elif defined(TARGET_WINDOWS) + if(!m_bhasoptical) + return ""; + + std::string mediaPath = CServiceBroker::GetMediaManager().TranslateDevicePath(devicePath); + + auto cached = m_mapDiscInfo.find(mediaPath); + if (cached != m_mapDiscInfo.end()) + return cached->second.name; + + // try to minimize the chance of a "device not ready" dialog + std::string drivePath = CServiceBroker::GetMediaManager().TranslateDevicePath(devicePath, true); + if (CServiceBroker::GetMediaManager().GetDriveStatus(drivePath) != + DriveState::CLOSED_MEDIA_PRESENT) + return ""; + + UTILS::DISCS::DiscInfo info; + info = GetDiscInfo(mediaPath); + if (!info.name.empty()) + { + m_mapDiscInfo[mediaPath] = info; + return info.name; + } + + std::string strDevice = TranslateDevicePath(devicePath); + WCHAR cVolumenName[128]; + WCHAR cFSName[128]; + URIUtils::AddSlashAtEnd(strDevice); + std::wstring strDeviceW; + g_charsetConverter.utf8ToW(strDevice, strDeviceW); + if(GetVolumeInformationW(strDeviceW.c_str(), cVolumenName, 127, NULL, NULL, NULL, cFSName, 127)==0) + return ""; + g_charsetConverter.wToUTF8(cVolumenName, strDevice); + info.name = StringUtils::TrimRight(strDevice, " "); + if (!info.name.empty()) + m_mapDiscInfo[mediaPath] = info; + + return info.name; +#else + return MEDIA_DETECT::CDetectDVDMedia::GetDVDLabel(); +#endif +} + +std::string CMediaManager::GetDiskUniqueId(const std::string& devicePath) +{ + std::string mediaPath; + + CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo(devicePath); + if (pInfo == NULL) + return ""; + + if (pInfo->IsAudio(1)) + mediaPath = "cdda://local/"; + + if (mediaPath.empty() && (pInfo->IsISOUDF(1) || pInfo->IsISOHFS(1) || pInfo->IsIso9660(1) || pInfo->IsIso9660Interactive(1))) + mediaPath = "iso9660://"; + + if (mediaPath.empty()) + mediaPath = devicePath; + +#ifdef TARGET_WINDOWS + if (mediaPath.empty() || mediaPath == "iso9660://") + { + mediaPath = CServiceBroker::GetMediaManager().TranslateDevicePath(devicePath); + } +#endif + + UTILS::DISCS::DiscInfo info = GetDiscInfo(mediaPath); + if (info.empty()) + { + CLog::Log(LOGDEBUG, "GetDiskUniqueId: Retrieving ID for path {} failed, ID is empty.", + CURL::GetRedacted(mediaPath)); + return ""; + } + + std::string strID = StringUtils::Format("removable://{}_{}", info.name, info.serial); + CLog::Log(LOGDEBUG, "GetDiskUniqueId: Got ID {} for disc with path {}", strID, + CURL::GetRedacted(mediaPath)); + + return strID; +} + +std::string CMediaManager::GetDiscPath() +{ +#ifdef TARGET_WINDOWS + return CServiceBroker::GetMediaManager().TranslateDevicePath(""); +#else + + std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); + VECSOURCES drives; + m_platformStorage->GetRemovableDrives(drives); + for(unsigned i = 0; i < drives.size(); ++i) + { + if(drives[i].m_iDriveType == CMediaSource::SOURCE_TYPE_DVD && !drives[i].strPath.empty()) + return drives[i].strPath; + } + + // iso9660://, cdda://local/ or D:\ depending on disc type + return MEDIA_DETECT::CDetectDVDMedia::GetDVDPath(); +#endif +} + +std::shared_ptr<IDiscDriveHandler> CMediaManager::GetDiscDriveHandler() +{ + return m_platformDiscDriveHander; +} +#endif + +void CMediaManager::SetHasOpticalDrive(bool bstatus) +{ + std::unique_lock<CCriticalSection> waitLock(m_muAutoSource); + m_bhasoptical = bstatus; +} + +bool CMediaManager::Eject(const std::string& mountpath) +{ + std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); + return m_platformStorage->Eject(mountpath); +} + +void CMediaManager::EjectTray( const bool bEject, const char cDriveLetter ) +{ +#ifdef HAS_DVD_DRIVE + if (m_platformDiscDriveHander) + { + m_platformDiscDriveHander->EjectDriveTray(TranslateDevicePath("")); + } +#endif +} + +void CMediaManager::CloseTray(const char cDriveLetter) +{ +#ifdef HAS_DVD_DRIVE + if (m_platformDiscDriveHander) + { + m_platformDiscDriveHander->ToggleDriveTray(TranslateDevicePath("")); + } +#endif +} + +void CMediaManager::ToggleTray(const char cDriveLetter) +{ +#ifdef HAS_DVD_DRIVE + if (m_platformDiscDriveHander) + { + m_platformDiscDriveHander->ToggleDriveTray(TranslateDevicePath("")); + } +#endif +} + +void CMediaManager::ProcessEvents() +{ + std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); + if (m_platformStorage->PumpDriveChangeEvents(this)) + { +#if defined(HAS_DVD_DRIVE) && defined(TARGET_DARWIN_OSX) + // darwins GetFirstOpticalDeviceFileName only gives us something + // when a disc is inserted + // so we have to refresh m_strFirstAvailDrive when this happens after Initialize + // was called (e.x. the disc was inserted after the start of xbmc) + // else TranslateDevicePath wouldn't give the correct device + m_strFirstAvailDrive = m_platformStorage->GetFirstOpticalDeviceFileName(); +#endif + + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } +} + +std::vector<std::string> CMediaManager::GetDiskUsage() +{ + std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); + return m_platformStorage->GetDiskUsage(); +} + +void CMediaManager::OnStorageAdded(const MEDIA_DETECT::STORAGE::StorageDevice& device) +{ +#ifdef HAS_DVD_DRIVE + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings->GetInt(CSettings::SETTING_AUDIOCDS_AUTOACTION) != AUTOCD_NONE || settings->GetBool(CSettings::SETTING_DVDS_AUTORUN)) + { + if (settings->GetInt(CSettings::SETTING_AUDIOCDS_AUTOACTION) == AUTOCD_RIP) + { + CServiceBroker::GetJobManager()->AddJob(new CAutorunMediaJob(device.label, device.path), this, + CJob::PRIORITY_LOW); + } + else + { + if (device.type == MEDIA_DETECT::STORAGE::Type::OPTICAL) + { + if (MEDIA_DETECT::CAutorun::ExecuteAutorun(device.path)) + { + return; + } + CLog::Log(LOGDEBUG, "{}: Could not execute autorun for optical disc with path {}", + __FUNCTION__, device.path); + } + CServiceBroker::GetJobManager()->AddJob(new CAutorunMediaJob(device.label, device.path), this, + CJob::PRIORITY_HIGH); + } + } + else + { + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(13021), + device.label, TOAST_DISPLAY_TIME, false); + } +#endif +} + +void CMediaManager::OnStorageSafelyRemoved(const MEDIA_DETECT::STORAGE::StorageDevice& device) +{ + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(13023), + device.label, TOAST_DISPLAY_TIME, false); +} + +void CMediaManager::OnStorageUnsafelyRemoved(const MEDIA_DETECT::STORAGE::StorageDevice& device) +{ + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(13022), + device.label); +} + +UTILS::DISCS::DiscInfo CMediaManager::GetDiscInfo(const std::string& mediaPath) +{ + UTILS::DISCS::DiscInfo info; + + if (mediaPath.empty()) + return info; + + // Try finding VIDEO_TS/VIDEO_TS.IFO - this indicates a DVD disc is inserted + std::string pathVideoTS = URIUtils::AddFileToFolder(mediaPath, "VIDEO_TS", "VIDEO_TS.IFO"); + // correct the filename if needed + if (StringUtils::StartsWith(mediaPath, "dvd://") || + StringUtils::StartsWith(mediaPath, "iso9660://")) + { + pathVideoTS = TranslateDevicePath(""); + } + + // check for DVD discs + if (CFileUtils::Exists(pathVideoTS)) + { + info = UTILS::DISCS::ProbeDVDDiscInfo(pathVideoTS); + if (!info.empty()) + return info; + } + // check for Blu-ray discs + if (CFileUtils::Exists(URIUtils::AddFileToFolder(mediaPath, "BDMV", "index.bdmv"))) + { + info = UTILS::DISCS::ProbeBlurayDiscInfo(mediaPath); + } + + return info; +} + +void CMediaManager::RemoveDiscInfo(const std::string& devicePath) +{ + std::string strDevice = TranslateDevicePath(devicePath, false); + + auto it = m_mapDiscInfo.find(strDevice); + if (it != m_mapDiscInfo.end()) + m_mapDiscInfo.erase(it); +} + +bool CMediaManager::playStubFile(const CFileItem& item) +{ + // Figure out Lines 1 and 2 of the dialog + std::string strLine1, strLine2; + + // use generic message by default + strLine1 = g_localizeStrings.Get(435).c_str(); + strLine2 = g_localizeStrings.Get(436).c_str(); + + CXBMCTinyXML discStubXML; + if (discStubXML.LoadFile(item.GetPath())) + { + TiXmlElement* pRootElement = discStubXML.RootElement(); + if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "discstub") != 0) + CLog::Log(LOGINFO, "No <discstub> node found for {}. Using default info dialog message", + item.GetPath()); + else + { + XMLUtils::GetString(pRootElement, "title", strLine1); + XMLUtils::GetString(pRootElement, "message", strLine2); + // no title? use the label of the CFileItem as line 1 + if (strLine1.empty()) + strLine1 = item.GetLabel(); + } + } + + if (HasOpticalDrive()) + { +#ifdef HAS_DVD_DRIVE + if (CGUIDialogPlayEject::ShowAndGetInput(strLine1, strLine2)) + return MEDIA_DETECT::CAutorun::PlayDiscAskResume(); +#endif + } + else + { + KODI::MESSAGING::HELPERS::ShowOKDialogText(strLine1, strLine2); + } + return true; +} diff --git a/xbmc/storage/MediaManager.h b/xbmc/storage/MediaManager.h new file mode 100644 index 0000000..af76bb6 --- /dev/null +++ b/xbmc/storage/MediaManager.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IStorageProvider.h" +#include "MediaSource.h" // for VECSOURCES +#include "storage/discs/IDiscDriveHandler.h" +#include "threads/CriticalSection.h" +#include "utils/DiscsUtils.h" +#include "utils/Job.h" + +#include <map> +#include <memory> +#include <vector> + +#include "PlatformDefs.h" + +class CFileItem; + +class CNetworkLocation +{ +public: + CNetworkLocation() { id = 0; } + int id; + std::string path; +}; + +class CMediaManager : public IStorageEventsCallback, public IJobCallback +{ +public: + CMediaManager(); + + void Initialize(); + void Stop(); + + bool LoadSources(); + bool SaveSources(); + + void GetLocalDrives(VECSOURCES &localDrives, bool includeQ = true); + void GetRemovableDrives(VECSOURCES &removableDrives); + void GetNetworkLocations(VECSOURCES &locations, bool autolocations = true); + + bool AddNetworkLocation(const std::string &path); + bool HasLocation(const std::string& path) const; + bool RemoveLocation(const std::string& path); + bool SetLocationPath(const std::string& oldPath, const std::string& newPath); + + void AddAutoSource(const CMediaSource &share, bool bAutorun=false); + void RemoveAutoSource(const CMediaSource &share); + bool IsDiscInDrive(const std::string& devicePath=""); + bool IsAudio(const std::string& devicePath=""); + bool HasOpticalDrive(); + std::string TranslateDevicePath(const std::string& devicePath, bool bReturnAsDevice=false); + DriveState GetDriveStatus(const std::string& devicePath = ""); +#ifdef HAS_DVD_DRIVE + MEDIA_DETECT::CCdInfo* GetCdInfo(const std::string& devicePath=""); + bool RemoveCdInfo(const std::string& devicePath=""); + std::string GetDiskLabel(const std::string& devicePath=""); + std::string GetDiskUniqueId(const std::string& devicePath=""); + + /*! \brief Gets the platform disc drive handler + * @todo this likely doesn't belong here but in some discsupport component owned by media manager + * let's keep it here for now + * \return The platform disc drive handler + */ + std::shared_ptr<IDiscDriveHandler> GetDiscDriveHandler(); +#endif + std::string GetDiscPath(); + void SetHasOpticalDrive(bool bstatus); + + bool Eject(const std::string& mountpath); + void EjectTray( const bool bEject=true, const char cDriveLetter='\0' ); + void CloseTray(const char cDriveLetter='\0'); + void ToggleTray(const char cDriveLetter='\0'); + + void ProcessEvents(); + + std::vector<std::string> GetDiskUsage(); + + /*! \brief Callback executed when a new storage device is added + * \sa IStorageEventsCallback + * @param device the storage device + */ + void OnStorageAdded(const MEDIA_DETECT::STORAGE::StorageDevice& device) override; + + /*! \brief Callback executed when a new storage device is safely removed + * \sa IStorageEventsCallback + * @param device the storage device + */ + void OnStorageSafelyRemoved(const MEDIA_DETECT::STORAGE::StorageDevice& device) override; + + /*! \brief Callback executed when a new storage device is unsafely removed + * \sa IStorageEventsCallback + * @param device the storage device + */ + void OnStorageUnsafelyRemoved(const MEDIA_DETECT::STORAGE::StorageDevice& device) override; + + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override { } + + bool playStubFile(const CFileItem& item); + +protected: + std::vector<CNetworkLocation> m_locations; + + CCriticalSection m_muAutoSource, m_CritSecStorageProvider; +#ifdef HAS_DVD_DRIVE + std::map<std::string,MEDIA_DETECT::CCdInfo*> m_mapCdInfo; +#endif + bool m_bhasoptical; + std::string m_strFirstAvailDrive; + +private: + /*! \brief Loads the addon sources for the different supported browsable addon types + */ + void LoadAddonSources() const; + + /*! \brief Get the addons root source for the given content type + \param type the type of addon content desired + \return the given CMediaSource for the addon root directory + */ + CMediaSource GetRootAddonTypeSource(const std::string& type) const; + + /*! \brief Generate the addons source for the given content type + \param type the type of addon content desired + \param label the name of the addons source + \param thumb image to use as the icon + \return the given CMediaSource for the addon root directory + */ + CMediaSource ComputeRootAddonTypeSource(const std::string& type, + const std::string& label, + const std::string& thumb) const; + + std::unique_ptr<IStorageProvider> m_platformStorage; +#ifdef HAS_DVD_DRIVE + std::shared_ptr<IDiscDriveHandler> m_platformDiscDriveHander; +#endif + + UTILS::DISCS::DiscInfo GetDiscInfo(const std::string& mediaPath); + void RemoveDiscInfo(const std::string& devicePath); + std::map<std::string, UTILS::DISCS::DiscInfo> m_mapDiscInfo; +}; diff --git a/xbmc/storage/cdioSupport.cpp b/xbmc/storage/cdioSupport.cpp new file mode 100644 index 0000000..70e2463 --- /dev/null +++ b/xbmc/storage/cdioSupport.cpp @@ -0,0 +1,958 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "cdioSupport.h" + +#include "platform/Environment.h" +#include "utils/log.h" + +#include <mutex> + +#include <cdio/cd_types.h> +#include <cdio/cdio.h> +#include <cdio/logging.h> +#include <cdio/mmc.h> +#include <cdio/util.h> + +namespace +{ +/* Helper constexpr to hide the 0 return code for tray closed. */ +constexpr int CDIO_TRAY_STATUS_CLOSED = 0; +/* Helper constexpr to hide the 1 return code for tray open */ +constexpr int CDIO_TRAY_STATUS_OPEN = 1; +/* Helper constexpr to hide the -2 driver code for unsupported tray status get operation */ +constexpr int CDIO_TRAY_STATUS_OP_UNSUPPORTED = -2; +} // namespace + +using namespace MEDIA_DETECT; + +std::shared_ptr<CLibcdio> CLibcdio::m_pInstance; + +/* Some interesting sector numbers stored in the above buffer. */ +#define ISO_SUPERBLOCK_SECTOR 16 /* buffer[0] */ +#define UFS_SUPERBLOCK_SECTOR 4 /* buffer[2] */ +#define BOOT_SECTOR 17 /* buffer[3] */ +#define VCD_INFO_SECTOR 150 /* buffer[4] */ +#define UDF_ANCHOR_SECTOR 256 /* buffer[5] */ + + +signature_t CCdIoSupport::sigs[] = { + /*buffer[x] off look for description */ + {0, 1, "CD001\0", "ISO 9660\0"}, + {0, 1, "CD-I", "CD-I"}, + {0, 8, "CDTV", "CDTV"}, + {0, 8, "CD-RTOS", "CD-RTOS"}, + {0, 9, "CDROM", "HIGH SIERRA"}, + {0, 16, "CD-BRIDGE", "BRIDGE"}, + {0, 1024, "CD-XA001", "XA"}, + {1, 64, "PPPPHHHHOOOOTTTTOOOO____CCCCDDDD", "PHOTO CD"}, + {1, 0x438, "\x53\xef", "EXT2 FS"}, + {2, 1372, "\x54\x19\x01\x0", "UFS"}, + {3, 7, "EL TORITO", "BOOTABLE"}, + {4, 0, "VIDEO_CD", "VIDEO CD"}, + {4, 0, "SUPERVCD", "Chaoji VCD"}, + {0, 1, "BEA01", "UDF"}, + {}}; + +#undef DEBUG_CDIO + +static void +cdio_log_handler (cdio_log_level_t level, const char message[]) +{ +#ifdef DEBUG_CDIO + switch (level) + { + case CDIO_LOG_ERROR: + CLog::Log(LOGDEBUG, "**ERROR: {}", message); + break; + case CDIO_LOG_DEBUG: + CLog::Log(LOGDEBUG, "--DEBUG: {}", message); + break; + case CDIO_LOG_WARN: + CLog::Log(LOGDEBUG, "++ WARN: {}", message); + break; + case CDIO_LOG_INFO: + CLog::Log(LOGDEBUG, " INFO: {}", message); + break; + case CDIO_LOG_ASSERT: + CLog::Log(LOGDEBUG, "!ASSERT: {}", message); + break; + default: + //cdio_assert_not_reached (); + break; + } +#endif +} + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +CLibcdio::CLibcdio(): s_defaultDevice(NULL) +{ + cdio_log_set_handler( cdio_log_handler ); +} + +CLibcdio::~CLibcdio() +{ + free(s_defaultDevice); + s_defaultDevice = NULL; +} + +void CLibcdio::ReleaseInstance() +{ + m_pInstance.reset(); +} + +std::shared_ptr<CLibcdio> CLibcdio::GetInstance() +{ + if (!m_pInstance) + { + m_pInstance = std::shared_ptr<CLibcdio>(new CLibcdio()); + } + return m_pInstance; +} + +CdIo_t* CLibcdio::cdio_open(const char *psz_source, driver_id_t driver_id) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_open(psz_source, driver_id) ); +} + +CdIo_t* CLibcdio::cdio_open_win32(const char *psz_source) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_open_win32(psz_source) ); +} + +void CLibcdio::cdio_destroy(CdIo_t *p_cdio) +{ + std::unique_lock<CCriticalSection> lock(*this); + + ::cdio_destroy(p_cdio); +} + +discmode_t CLibcdio::cdio_get_discmode(CdIo_t *p_cdio) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_get_discmode(p_cdio) ); +} + +CdioTrayStatus CLibcdio::mmc_get_tray_status(const CdIo_t* p_cdio) +{ + std::unique_lock<CCriticalSection> lock(*this); + + int status = ::mmc_get_tray_status(p_cdio); + switch (status) + { + case CDIO_TRAY_STATUS_CLOSED: + return CdioTrayStatus::CLOSED; + case CDIO_TRAY_STATUS_OPEN: + return CdioTrayStatus::OPEN; + case CDIO_TRAY_STATUS_OP_UNSUPPORTED: + return CdioTrayStatus::UNKNOWN; + default: + break; + } + return CdioTrayStatus::DRIVER_ERROR; +} + +driver_return_code_t CLibcdio::cdio_eject_media(CdIo_t** p_cdio) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_eject_media(p_cdio) ); +} + +track_t CLibcdio::cdio_get_last_track_num(const CdIo_t *p_cdio) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_get_last_track_num(p_cdio) ); +} + +lsn_t CLibcdio::cdio_get_track_lsn(const CdIo_t *p_cdio, track_t i_track) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_get_track_lsn(p_cdio, i_track) ); +} + +lsn_t CLibcdio::cdio_get_track_last_lsn(const CdIo_t *p_cdio, track_t i_track) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_get_track_last_lsn(p_cdio, i_track) ); +} + +driver_return_code_t CLibcdio::cdio_read_audio_sectors( + const CdIo_t *p_cdio, void *p_buf, lsn_t i_lsn, uint32_t i_blocks) +{ + std::unique_lock<CCriticalSection> lock(*this); + + return( ::cdio_read_audio_sectors(p_cdio, p_buf, i_lsn, i_blocks) ); +} + +driver_return_code_t CLibcdio::cdio_close_tray(const char* psz_source, driver_id_t* driver_id) +{ + std::unique_lock<CCriticalSection> lock(*this); + return (::cdio_close_tray(psz_source, driver_id)); +} + +const char* CLibcdio::cdio_driver_errmsg(driver_return_code_t drc) +{ + return (::cdio_driver_errmsg(drc)); +} + +char* CLibcdio::GetDeviceFileName() +{ + std::unique_lock<CCriticalSection> lock(*this); + + // If We don't have a DVD device initially present (Darwin or a USB DVD drive), + // We have to keep checking in case one appears. + if (s_defaultDevice && strlen(s_defaultDevice) == 0) + { + free(s_defaultDevice); + s_defaultDevice = NULL; + } + + if (s_defaultDevice == NULL) + { + std::string strEnvDvd = CEnvironment::getenv("KODI_DVD_DEVICE"); + if (!strEnvDvd.empty()) + s_defaultDevice = strdup(strEnvDvd.c_str()); + else + { + CdIo_t *p_cdio = ::cdio_open(NULL, DRIVER_UNKNOWN); + if (p_cdio != NULL) + { + s_defaultDevice = strdup(::cdio_get_arg(p_cdio, "source")); + ::cdio_destroy(p_cdio); + } + else + s_defaultDevice = strdup(""); + } + } + return s_defaultDevice; +} + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +CCdIoSupport::CCdIoSupport() +: cdio(nullptr) +{ + m_cdio = CLibcdio::GetInstance(); + m_nFirstData = -1; /* # of first data track */ + m_nNumData = 0; /* # of data tracks */ + m_nFirstAudio = -1; /* # of first audio track */ + m_nNumAudio = 0; /* # of audio tracks */ + m_nIsofsSize = 0; /* size of session */ + m_nJolietLevel = 0; + m_nFs = 0; + m_nUDFVerMinor = 0; + m_nUDFVerMajor = 0; + m_nDataStart = 0; + m_nMsOffset = 0; + m_nStartTrack = 0; +} + +CCdIoSupport::~CCdIoSupport() = default; + +bool CCdIoSupport::EjectTray() +{ + return false; +} + +bool CCdIoSupport::CloseTray() +{ + return false; +} + +HANDLE CCdIoSupport::OpenCDROM() +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + char* source_name = m_cdio->GetDeviceFileName(); + CdIo* cdio = ::cdio_open(source_name, DRIVER_UNKNOWN); + + return reinterpret_cast<HANDLE>(cdio); +} + +HANDLE CCdIoSupport::OpenIMAGE( std::string& strFilename ) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + CdIo* cdio = ::cdio_open(strFilename.c_str(), DRIVER_UNKNOWN); + + return reinterpret_cast<HANDLE>(cdio); +} + +int CCdIoSupport::ReadSector(HANDLE hDevice, DWORD dwSector, char* lpczBuffer) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + CdIo* cdio = (CdIo*) hDevice; + if ( cdio == NULL ) + return -1; + + if ( ::cdio_read_mode1_sector( cdio, lpczBuffer, dwSector, false ) == 0 ) + return dwSector; + + return -1; +} + +int CCdIoSupport::ReadSectorMode2(HANDLE hDevice, DWORD dwSector, char* lpczBuffer) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + CdIo* cdio = (CdIo*) hDevice; + if ( cdio == NULL ) + return -1; + + if ( ::cdio_read_mode2_sector( cdio, lpczBuffer, dwSector, false ) == 0 ) + return dwSector; + + return -1; +} + +int CCdIoSupport::ReadSectorCDDA(HANDLE hDevice, DWORD dwSector, char* lpczBuffer) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + CdIo* cdio = (CdIo*) hDevice; + if ( cdio == NULL ) + return -1; + + if ( ::cdio_read_audio_sector( cdio, lpczBuffer, dwSector ) == 0 ) + return dwSector; + + return -1; +} + +void CCdIoSupport::CloseCDROM(HANDLE hDevice) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + CdIo* cdio = (CdIo*) hDevice; + + if ( cdio == NULL ) + return ; + + ::cdio_destroy( cdio ); +} + +void CCdIoSupport::PrintAnalysis(int fs, int num_audio) +{ + switch (fs & FS_MASK) + { + case FS_UDF: + CLog::Log(LOGINFO, "CD-ROM with UDF filesystem"); + break; + case FS_NO_DATA: + CLog::Log(LOGINFO, "CD-ROM with audio tracks"); + break; + case FS_ISO_9660: + CLog::Log(LOGINFO, "CD-ROM with ISO 9660 filesystem"); + if (fs & JOLIET) + { + CLog::Log(LOGINFO, " with joliet extension level {}", m_nJolietLevel); + } + if (fs & ROCKRIDGE) + { + CLog::Log(LOGINFO, " and rockridge extensions"); + } + break; + case FS_ISO_9660_INTERACTIVE: + CLog::Log(LOGINFO, "CD-ROM with CD-RTOS and ISO 9660 filesystem"); + break; + case FS_HIGH_SIERRA: + CLog::Log(LOGINFO, "CD-ROM with High Sierra filesystem"); + break; + case FS_INTERACTIVE: + CLog::Log(LOGINFO, "CD-Interactive{}", num_audio > 0 ? "/Ready" : ""); + break; + case FS_HFS: + CLog::Log(LOGINFO, "CD-ROM with Macintosh HFS"); + break; + case FS_ISO_HFS: + CLog::Log(LOGINFO, "CD-ROM with both Macintosh HFS and ISO 9660 filesystem"); + break; + case FS_ISO_UDF: + CLog::Log(LOGINFO, "CD-ROM with both UDF and ISO 9660 filesystem"); + break; + case FS_UFS: + CLog::Log(LOGINFO, "CD-ROM with Unix UFS"); + break; + case FS_EXT2: + CLog::Log(LOGINFO, "CD-ROM with Linux second extended filesystem"); + break; + case FS_3DO: + CLog::Log(LOGINFO, "CD-ROM with Panasonic 3DO filesystem"); + break; + case FS_UNKNOWN: + CLog::Log(LOGINFO, "CD-ROM with unknown filesystem"); + break; + } + + switch (fs & FS_MASK) + { + case FS_ISO_9660: + case FS_ISO_9660_INTERACTIVE: + case FS_ISO_HFS: + case FS_ISO_UDF: + CLog::Log(LOGINFO, "ISO 9660: {} blocks, label {}", m_nIsofsSize, m_strDiscLabel); + break; + } + + switch (fs & FS_MASK) + { + case FS_UDF: + case FS_ISO_UDF: + CLog::Log(LOGINFO, "UDF: version {:x}.{:02x}", m_nUDFVerMajor, m_nUDFVerMinor); + break; + } + + if (m_nFirstData == 1 && num_audio > 0) + { + CLog::Log(LOGINFO, "mixed mode CD "); + } + if (fs & XA) + { + CLog::Log(LOGINFO, "XA sectors "); + } + if (fs & MULTISESSION) + { + CLog::Log(LOGINFO, "Multisession, offset = {} ", m_nMsOffset); + } + if (fs & HIDDEN_TRACK) + { + CLog::Log(LOGINFO, "Hidden Track "); + } + if (fs & PHOTO_CD) + { + CLog::Log(LOGINFO, "{}Photo CD ", num_audio > 0 ? " Portfolio " : ""); + } + if (fs & CDTV) + { + CLog::Log(LOGINFO, "Commodore CDTV "); + } + if (m_nFirstData > 1) + { + CLog::Log(LOGINFO, "CD-Plus/Extra "); + } + if (fs & BOOTABLE) + { + CLog::Log(LOGINFO, "bootable CD "); + } + if (fs & VIDEOCDI && num_audio == 0) + { + CLog::Log(LOGINFO, "Video CD "); +#if defined(HAVE_VCDINFO) && defined(DEBUG) + if (!opts.no_vcd) + { + printf("\n"); + print_vcd_info(); + } +#endif + + } + if (fs & CVD) + { + CLog::Log(LOGINFO, "Chaoji Video CD"); + } +} + +int CCdIoSupport::ReadBlock(int superblock, uint32_t offset, uint8_t bufnum, track_t track_num) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + unsigned int track_sec_count = ::cdio_get_track_sec_count(cdio, track_num); + memset(buffer[bufnum], 0, CDIO_CD_FRAMESIZE); + + if ( track_sec_count < static_cast<unsigned int>(superblock)) + { + ::cdio_debug("reading block %u skipped track %d has only %u sectors\n", + superblock, track_num, track_sec_count); + return -1; + } + + ::cdio_debug("about to read sector %lu\n", + (long unsigned int) offset + superblock); + + if (::cdio_get_track_green(cdio, track_num)) + { + if (0 < ::cdio_read_mode2_sector(cdio, buffer[bufnum], + offset + superblock, false)) + return -1; + } + else + { + if (0 < ::cdio_read_mode1_sector(cdio, buffer[bufnum], + offset + superblock, false)) + return -1; + } + + return 0; +} + +bool CCdIoSupport::IsIt(int num) +{ + signature_t *sigp = &sigs[num]; + int len = strlen(sigp->sig_str); + + //! @todo check that num < largest sig. + return 0 == memcmp(&buffer[sigp->buf_num][sigp->offset], sigp->sig_str, len); +} + +int CCdIoSupport::IsHFS(void) +{ + return (0 == memcmp(&buffer[1][512], "PM", 2)) || + (0 == memcmp(&buffer[1][512], "TS", 2)) || + (0 == memcmp(&buffer[1][1024], "BD", 2)); +} + +int CCdIoSupport::Is3DO(void) +{ + return (0 == memcmp(&buffer[1][0], "\x01\x5a\x5a\x5a\x5a\x5a\x01", 7)) && + (0 == memcmp(&buffer[1][40], "CD-ROM", 6)); +} + +int CCdIoSupport::IsJoliet(void) +{ + return 2 == buffer[3][0] && buffer[3][88] == 0x25 && buffer[3][89] == 0x2f; +} + +int CCdIoSupport::IsUDF(void) +{ + return 2 == ((uint16_t)buffer[5][0] | ((uint16_t)buffer[5][1] << 8)); +} + +/* ISO 9660 volume space in M2F1_SECTOR_SIZE byte units */ +int CCdIoSupport::GetSize(void) +{ + return ((buffer[0][80] & 0xff) | + ((buffer[0][81] & 0xff) << 8) | + ((buffer[0][82] & 0xff) << 16) | + ((buffer[0][83] & 0xff) << 24)); +} + +int CCdIoSupport::GetJolietLevel( void ) +{ + switch (buffer[3][90]) + { + case 0x40: + return 1; + case 0x43: + return 2; + case 0x45: + return 3; + } + return 0; +} + +#define is_it_dbg(sig) /*\ + if (is_it(sig)) printf("%s, ", sigs[sig].description)*/ + +int CCdIoSupport::GuessFilesystem(int start_session, track_t track_num) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + int ret = FS_UNKNOWN; + cdio_iso_analysis_t anal; + cdio_fs_anal_t fs; + bool udf = false; + + memset(&anal, 0, sizeof(anal)); + discmode_t mode = ::cdio_get_discmode(cdio); + if (::cdio_is_discmode_dvd(mode)) + { + m_strDiscLabel = ""; + m_nIsofsSize = ::cdio_get_disc_last_lsn(cdio); + m_nJolietLevel = ::cdio_get_joliet_level(cdio); + + return FS_ISO_9660; + } + + fs = ::cdio_guess_cd_type(cdio, start_session, track_num, &anal); + + switch(CDIO_FSTYPE(fs)) + { + case CDIO_FS_AUDIO: + ret = FS_NO_DATA; + break; + + case CDIO_FS_HIGH_SIERRA: + ret = FS_HIGH_SIERRA; + break; + + case CDIO_FS_ISO_9660: + ret = FS_ISO_9660; + break; + + case CDIO_FS_INTERACTIVE: + ret = FS_ISO_9660_INTERACTIVE; + break; + + case CDIO_FS_HFS: + ret = FS_HFS; + break; + + case CDIO_FS_UFS: + ret = FS_UFS; + break; + + case CDIO_FS_EXT2: + ret = FS_EXT2; + break; + + case CDIO_FS_UDF: + ret = FS_UDF; + udf = true; + break; + + case CDIO_FS_ISO_UDF: + ret = FS_ISO_UDF; + udf = true; + break; + + default: + break; + } + + if (udf) + { + m_nUDFVerMinor = anal.UDFVerMinor; + m_nUDFVerMajor = anal.UDFVerMajor; + } + + m_strDiscLabel = anal.iso_label; + m_nIsofsSize = anal.isofs_size; + m_nJolietLevel = anal.joliet_level; + + return ret; +} + +void CCdIoSupport::GetCdTextInfo(xbmc_cdtext_t &xcdt, int trackNum) +{ + // cdtext disabled for windows as some setup doesn't like mmc commands + // and stall for over a minute in cdio_get_cdtext 83 +#if !defined(TARGET_WINDOWS) + std::unique_lock<CCriticalSection> lock(*m_cdio); + + // Get the CD-Text , if any +#if defined(LIBCDIO_VERSION_NUM) && (LIBCDIO_VERSION_NUM >= 84) + cdtext_t *pcdtext = static_cast<cdtext_t*>( cdio_get_cdtext(cdio) ); +#else + //! @todo - remove after Ubuntu 16.04 (Xenial) is EOL + cdtext_t *pcdtext = (cdtext_t *)::cdio_get_cdtext(cdio, trackNum); +#endif + + if (pcdtext == NULL) + return ; + +#if defined(LIBCDIO_VERSION_NUM) && (LIBCDIO_VERSION_NUM >= 84) + for (int i=0; i < MAX_CDTEXT_FIELDS; i++) + if (cdtext_get_const(pcdtext, (cdtext_field_t)i, trackNum)) + xcdt[(cdtext_field_t)i] = cdtext_field2str((cdtext_field_t)i); +#else + //! @todo - remove after Ubuntu 16.04 (Xenial) is EOL + // Same ids used in libcdio and for our structure + the ids are consecutive make this copy loop safe. + for (int i = 0; i < MAX_CDTEXT_FIELDS; i++) + if (pcdtext->field[i]) + xcdt[(cdtext_field_t)i] = pcdtext->field[(cdtext_field_t)i]; +#endif +#endif // TARGET_WINDOWS +} + +CCdInfo* CCdIoSupport::GetCdInfo(char* cDeviceFileName) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + char* source_name; + if(cDeviceFileName == NULL) + source_name = m_cdio->GetDeviceFileName(); + else + source_name = cDeviceFileName; + + cdio = ::cdio_open(source_name, DRIVER_UNKNOWN); + if (cdio == NULL) + { + CLog::Log(LOGERROR, "{}: Error in automatically selecting driver with input", __FUNCTION__); + return NULL; + } + + bool bIsCDRom = true; + + m_nFirstTrackNum = ::cdio_get_first_track_num(cdio); + if (m_nFirstTrackNum == CDIO_INVALID_TRACK) + { +#if !defined(TARGET_DARWIN) + ::cdio_destroy(cdio); + return NULL; +#else + m_nFirstTrackNum = 1; + bIsCDRom = false; +#endif + } + + m_nNumTracks = ::cdio_get_num_tracks(cdio); + if (m_nNumTracks == CDIO_INVALID_TRACK) + { +#if !defined(TARGET_DARWIN) + ::cdio_destroy(cdio); + return NULL; +#else + m_nNumTracks = 1; + bIsCDRom = false; +#endif + } + + CCdInfo* info = new CCdInfo; + info->SetFirstTrack( m_nFirstTrackNum ); + info->SetTrackCount( m_nNumTracks ); + + for (i = m_nFirstTrackNum; i <= CDIO_CDROM_LEADOUT_TRACK; i++) + { + msf_t msf; + if (bIsCDRom && !::cdio_get_track_msf(cdio, i, &msf)) + { + trackinfo ti; + ti.nfsInfo = FS_UNKNOWN; + ti.ms_offset = 0; + ti.isofs_size = 0; + ti.nJolietLevel = 0; + ti.nFrames = 0; + ti.nMins = 0; + ti.nSecs = 0; + info->SetTrackInformation( i, ti ); + CLog::Log(LOGDEBUG, "cdio_track_msf for track {} failed, I give up.", i); + delete info; + ::cdio_destroy(cdio); + return NULL; + } + + trackinfo ti; + if (bIsCDRom && TRACK_FORMAT_AUDIO == ::cdio_get_track_format(cdio, i)) + { + m_nNumAudio++; + ti.nfsInfo = FS_NO_DATA; + m_nFs = FS_NO_DATA; + int temp1 = ::cdio_get_track_lba(cdio, i) - CDIO_PREGAP_SECTORS; + int temp2 = ::cdio_get_track_lba(cdio, i + 1) - CDIO_PREGAP_SECTORS; + // The length is the address of the second track minus the address of the first track + temp2 -= temp1; // temp2 now has length of track1 in frames + ti.nMins = temp2 / (60 * 75); // calculate the number of minutes + temp2 %= 60 * 75; // calculate the left-over frames + ti.nSecs = temp2 / 75; // calculate the number of seconds + if ( -1 == m_nFirstAudio) + m_nFirstAudio = i; + + // Make sure that we have the Disc related info available + if (i == 1) + { + xbmc_cdtext_t xcdt; + GetCdTextInfo(xcdt, 0); + info->SetDiscCDTextInformation( xcdt ); + } + + // Get this tracks info + GetCdTextInfo(ti.cdtext, i); + } + else + { + m_nNumData++; + if ( -1 == m_nFirstData) + m_nFirstData = i; + } + ti.nfsInfo = FS_NO_DATA; + ti.ms_offset = 0; + ti.isofs_size = 0; + ti.nJolietLevel = 0; + ti.nFrames = ::cdio_get_track_lba(cdio, i); + ti.nMins = 0; + ti.nSecs = 0; + + info->SetTrackInformation( i, ti ); + /* skip to leadout? */ + if (i == m_nNumTracks) + i = CDIO_CDROM_LEADOUT_TRACK; + } + + info->SetCddbDiscId( CddbDiscId() ); + info->SetDiscLength( ::cdio_get_track_lba(cdio, CDIO_CDROM_LEADOUT_TRACK) / CDIO_CD_FRAMES_PER_SEC ); + + info->SetAudioTrackCount( m_nNumAudio ); + info->SetDataTrackCount( m_nNumData ); + info->SetFirstAudioTrack( m_nFirstAudio ); + info->SetFirstDataTrack( m_nFirstData ); + + CLog::Log(LOGINFO, "CD Analysis Report"); + CLog::Log(LOGINFO, STRONG); + + /* Try to find out what sort of CD we have */ + if (0 == m_nNumData) + { + /* no data track, may be a "real" audio CD or hidden track CD */ + + msf_t msf; + ::cdio_get_track_msf(cdio, 1, &msf); + m_nStartTrack = ::cdio_msf_to_lsn(&msf); + + /* CD-I/Ready says start_track <= 30*75 then CDDA */ + if (m_nStartTrack > 100 /* 100 is just a guess */) + { + m_nFs = GuessFilesystem(0, 1); + if ((m_nFs & FS_MASK) != FS_UNKNOWN) + m_nFs |= HIDDEN_TRACK; + else + { + m_nFs &= ~FS_MASK; /* del filesystem info */ + CLog::Log(LOGDEBUG, "Oops: {} unused sectors at start, but hidden track check failed.", + m_nStartTrack); + } + } + PrintAnalysis(m_nFs, m_nNumAudio); + } + else + { + /* We have data track(s) */ + for (j = 2, i = m_nFirstData; i <= m_nNumTracks; i++) + { + msf_t msf; + track_format_t track_format = ::cdio_get_track_format(cdio, i); + + ::cdio_get_track_msf(cdio, i, &msf); + + switch ( track_format ) + { + case TRACK_FORMAT_AUDIO: + { + trackinfo ti; + ti.nfsInfo = FS_NO_DATA; + m_nFs = FS_NO_DATA; + ti.ms_offset = 0; + ti.isofs_size = 0; + ti.nJolietLevel = 0; + ti.nFrames = ::cdio_get_track_lba(cdio, i); + ti.nMins = 0; + ti.nSecs = 0; + info->SetTrackInformation( i + 1, ti ); + } + case TRACK_FORMAT_ERROR: + break; + case TRACK_FORMAT_CDI: + case TRACK_FORMAT_XA: + case TRACK_FORMAT_DATA: + case TRACK_FORMAT_PSX: + break; + } + + m_nStartTrack = (i == 1) ? 0 : ::cdio_msf_to_lsn(&msf); + + /* Save the start of the data area */ + if (i == m_nFirstData) + m_nDataStart = m_nStartTrack; + + /* Skip tracks which belong to the current walked session */ + if (m_nStartTrack < m_nDataStart + m_nIsofsSize) + continue; + + m_nFs = GuessFilesystem(m_nStartTrack, i); + trackinfo ti; + ti.nfsInfo = m_nFs; + ti.ms_offset = m_nMsOffset; + ti.isofs_size = m_nIsofsSize; + ti.nJolietLevel = m_nJolietLevel; + ti.nFrames = ::cdio_get_track_lba(cdio, i); + ti.nMins = 0; + ti.nSecs = 0; + info->SetDiscLabel(m_strDiscLabel); + + + if (i > 1) + { + /* Track is beyond last session -> new session found */ + m_nMsOffset = m_nStartTrack; + + CLog::Log(LOGINFO, + "Session #{} starts at track {:2}, LSN: {:6}," + " ISO 9660 blocks: {:6}", + j++, i, m_nStartTrack, m_nIsofsSize); + + CLog::Log(LOGINFO, "ISO 9660: {} blocks, label {}", m_nIsofsSize, m_strDiscLabel); + m_nFs |= MULTISESSION; + ti.nfsInfo = m_nFs; + } + else + { + PrintAnalysis(m_nFs, m_nNumAudio); + } + + info->SetTrackInformation( i, ti ); + + } + } + ::cdio_destroy( cdio ); + return info; +} + + +// Returns the sum of the decimal digits in a number. Eg. 1955 = 20 +int CCdIoSupport::CddbDecDigitSum(int n) +{ + int ret = 0; + + for (;;) + { + ret += n % 10; + n = n / 10; + if (!n) + return ret; + } +} + +// Return the number of seconds (discarding frame portion) of an MSF +unsigned int CCdIoSupport::MsfSeconds(msf_t *msf) +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + return ::cdio_from_bcd8(msf->m)*60 + ::cdio_from_bcd8(msf->s); +} + + +// Compute the CDDB disk ID for an Audio disk. +// This is a funny checksum consisting of the concatenation of 3 things: +// The sum of the decimal digits of sizes of all tracks, +// The total length of the disk, and +// The number of tracks. + +uint32_t CCdIoSupport::CddbDiscId() +{ + std::unique_lock<CCriticalSection> lock(*m_cdio); + + int i, t, n = 0; + msf_t start_msf; + msf_t msf; + + for (i = 1; i <= m_nNumTracks; i++) + { + ::cdio_get_track_msf(cdio, i, &msf); + n += CddbDecDigitSum(MsfSeconds(&msf)); + } + + ::cdio_get_track_msf(cdio, 1, &start_msf); + ::cdio_get_track_msf(cdio, CDIO_CDROM_LEADOUT_TRACK, &msf); + + t = MsfSeconds(&msf) - MsfSeconds(&start_msf); + + return ((n % 0xff) << 24 | t << 8 | m_nNumTracks); +} diff --git a/xbmc/storage/cdioSupport.h b/xbmc/storage/cdioSupport.h new file mode 100644 index 0000000..b786ff2 --- /dev/null +++ b/xbmc/storage/cdioSupport.h @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// CCdInfo - Information about media type of an inserted cd +// CCdIoSupport - Wrapper class for libcdio with the interface of CIoSupport +// and detecting the filesystem on the Disc. +// +// by Bobbin007 in 2003 +// CD-Text support by Mog - Oct 2004 + +#include "threads/CriticalSection.h" + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "PlatformDefs.h" // for ssize_t typedef, used by cdio + +#include <cdio/cdio.h> + +namespace MEDIA_DETECT +{ + +#define STRONG "__________________________________\n" +//#define NORMAL "" + +#define FS_NO_DATA 0 /* audio only */ +#define FS_HIGH_SIERRA 1 +#define FS_ISO_9660 2 +#define FS_INTERACTIVE 3 +#define FS_HFS 4 +#define FS_UFS 5 +#define FS_EXT2 6 +#define FS_ISO_HFS 7 /* both hfs & isofs filesystem */ +#define FS_ISO_9660_INTERACTIVE 8 /* both CD-RTOS and isofs filesystem */ +#define FS_3DO 9 +#define FS_UDF 11 +#define FS_ISO_UDF 12 +#define FS_UNKNOWN 15 +#define FS_MASK 15 + +#define XA 16 +#define MULTISESSION 32 +#define PHOTO_CD 64 +#define HIDDEN_TRACK 128 +#define CDTV 256 +#define BOOTABLE 512 +#define VIDEOCDI 1024 +#define ROCKRIDGE 2048 +#define JOLIET 4096 +#define CVD 8192 /* Choiji Video CD */ + +#define IS_ISOFS 0 +#define IS_CD_I 1 +#define IS_CDTV 2 +#define IS_CD_RTOS 3 +#define IS_HS 4 +#define IS_BRIDGE 5 +#define IS_XA 6 +#define IS_PHOTO_CD 7 +#define IS_EXT2 8 +#define IS_UFS 9 +#define IS_BOOTABLE 10 +#define IS_VIDEO_CD 11 /* Video CD */ +#define IS_CVD 12 /* Chinese Video CD - slightly incompatible with SVCD */ +#define IS_UDF 14 + +typedef struct signature +{ + unsigned int buf_num; + unsigned int offset; + const char *sig_str; + const char *description; +} +signature_t; + +typedef std::map<cdtext_field_t, std::string> xbmc_cdtext_t; + +typedef struct TRACKINFO +{ + int nfsInfo; // Information of the Tracks Filesystem + int nJolietLevel; // Jouliet Level + int ms_offset; // Multisession Offset + int isofs_size; // Size of the ISO9660 Filesystem + int nFrames; // Can be used for cddb query + int nMins; // minutes playtime part of Track + int nSecs; // seconds playtime part of Track + xbmc_cdtext_t cdtext; // CD-Text for this track +} +trackinfo; + +/*! \brief Helper enum class for the MMC tray state +*/ +enum class CdioTrayStatus +{ + /* The MMC tray state is reported closed */ + CLOSED, + /* The MMC tray state is reported open */ + OPEN, + /* The MMC tray status operation is not supported */ + UNKNOWN, + /* Generic driver error */ + DRIVER_ERROR +}; + +class CCdInfo +{ +public: + CCdInfo() + { + m_bHasCDDBInfo = true; + m_nLength = m_nFirstTrack = m_nNumTrack = m_nNumAudio = m_nFirstAudio = m_nNumData = m_nFirstData = 0; + } + + trackinfo GetTrackInformation( int nTrack ) { return m_ti[nTrack -1]; } + xbmc_cdtext_t GetDiscCDTextInformation() { return m_cdtext; } + + bool HasDataTracks() { return (m_nNumData > 0); } + bool HasAudioTracks() { return (m_nNumAudio > 0); } + int GetFirstTrack() { return m_nFirstTrack; } + int GetTrackCount() { return m_nNumTrack; } + int GetFirstAudioTrack() { return m_nFirstAudio; } + int GetFirstDataTrack() { return m_nFirstData; } + int GetDataTrackCount() { return m_nNumData; } + int GetAudioTrackCount() { return m_nNumAudio; } + uint32_t GetCddbDiscId() { return m_ulCddbDiscId; } + int GetDiscLength() { return m_nLength; } + std::string GetDiscLabel(){ return m_strDiscLabel; } + + // CD-ROM with ISO 9660 filesystem + bool IsIso9660( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_9660); } + // CD-ROM with joliet extension + bool IsJoliet( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & JOLIET) ? false : true; } + // Joliet extension level + int GetJolietLevel( int nTrack ) { return m_ti[nTrack - 1].nJolietLevel; } + // ISO filesystem size + int GetIsoSize( int nTrack ) { return m_ti[nTrack - 1].isofs_size; } + // CD-ROM with rockridge extensions + bool IsRockridge( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & ROCKRIDGE) ? false : true; } + + // CD-ROM with CD-RTOS and ISO 9660 filesystem + bool IsIso9660Interactive( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_9660_INTERACTIVE); } + + // CD-ROM with High Sierra filesystem + bool IsHighSierra( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_HIGH_SIERRA); } + + // CD-Interactive, with audiotracks > 0 CD-Interactive/Ready + bool IsCDInteractive( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_INTERACTIVE); } + + // CD-ROM with Macintosh HFS + bool IsHFS( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_HFS); } + + // CD-ROM with both Macintosh HFS and ISO 9660 filesystem + bool IsISOHFS( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_HFS); } + + // CD-ROM with both UDF and ISO 9660 filesystem + bool IsISOUDF( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_UDF); } + + // CD-ROM with Unix UFS + bool IsUFS( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_UFS); } + + // CD-ROM with Linux second extended filesystem + bool IsEXT2( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_EXT2); } + + // CD-ROM with Panasonic 3DO filesystem + bool Is3DO( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_3DO); } + + // Mixed Mode CD-ROM + bool IsMixedMode( int nTrack ) { return (m_nFirstData == 1 && m_nNumAudio > 0); } + + // CD-ROM with XA sectors + bool IsXA( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & XA) ? false : true; } + + // Multisession CD-ROM + bool IsMultiSession( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & MULTISESSION) ? false : true; } + // Gets multisession offset + int GetMultisessionOffset( int nTrack ) { return m_ti[nTrack - 1].ms_offset; } + + // Hidden Track on Audio CD + bool IsHiddenTrack( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & HIDDEN_TRACK) ? false : true; } + + // Photo CD, with audiotracks > 0 Portfolio Photo CD + bool IsPhotoCd( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & PHOTO_CD) ? false : true; } + + // CD-ROM with Commodore CDTV + bool IsCdTv( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & CDTV) ? false : true; } + + // CD-Plus/Extra + bool IsCDExtra( int nTrack ) { return (m_nFirstData > 1); } + + // Bootable CD + bool IsBootable( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & BOOTABLE) ? false : true; } + + // Video CD + bool IsVideoCd( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & VIDEOCDI && m_nNumAudio == 0); } + + // Chaoji Video CD + bool IsChaojiVideoCD( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & CVD) ? false : true; } + + // Audio Track + bool IsAudio( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_NO_DATA); } + + // UDF filesystem + bool IsUDF( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_UDF); } + + // Has the cd a filesystem that is readable by the xbox + bool IsValidFs() { return (IsISOHFS(1) || IsIso9660(1) || IsIso9660Interactive(1) || IsISOUDF(1) || IsUDF(1) || IsAudio(1)); } + + void SetFirstTrack( int nTrack ) { m_nFirstTrack = nTrack; } + void SetTrackCount( int nCount ) { m_nNumTrack = nCount; } + void SetFirstAudioTrack( int nTrack ) { m_nFirstAudio = nTrack; } + void SetFirstDataTrack( int nTrack ) { m_nFirstData = nTrack; } + void SetDataTrackCount( int nCount ) { m_nNumData = nCount; } + void SetAudioTrackCount( int nCount ) { m_nNumAudio = nCount; } + void SetTrackInformation(int nTrack, trackinfo nInfo) + { + if (nTrack > 0 && nTrack <= 99) + m_ti[nTrack - 1] = std::move(nInfo); + } + void SetDiscCDTextInformation(xbmc_cdtext_t cdtext) { m_cdtext = std::move(cdtext); } + + void SetCddbDiscId( uint32_t ulCddbDiscId ) { m_ulCddbDiscId = ulCddbDiscId; } + void SetDiscLength( int nLength ) { m_nLength = nLength; } + bool HasCDDBInfo() { return m_bHasCDDBInfo; } + void SetNoCDDBInfo() { m_bHasCDDBInfo = false; } + + void SetDiscLabel(const std::string& strDiscLabel){ m_strDiscLabel = strDiscLabel; } + +private: + int m_nFirstData; /* # of first data track */ + int m_nNumData; /* # of data tracks */ + int m_nFirstAudio; /* # of first audio track */ + int m_nNumAudio; /* # of audio tracks */ + int m_nNumTrack; + int m_nFirstTrack; + trackinfo m_ti[100]; + uint32_t m_ulCddbDiscId; + int m_nLength; // Disclength can be used for cddb query, also see trackinfo.nFrames + bool m_bHasCDDBInfo; + std::string m_strDiscLabel; + xbmc_cdtext_t m_cdtext; // CD-Text for this disc +}; + +class CLibcdio : public CCriticalSection +{ +private: + CLibcdio(); +public: + virtual ~CLibcdio(); + + static void ReleaseInstance(); + static std::shared_ptr<CLibcdio> GetInstance(); + + // libcdio is not thread safe so these are wrappers to libcdio routines + CdIo_t* cdio_open(const char *psz_source, driver_id_t driver_id); + CdIo_t* cdio_open_win32(const char *psz_source); + void cdio_destroy(CdIo_t *p_cdio); + discmode_t cdio_get_discmode(CdIo_t *p_cdio); + CdioTrayStatus mmc_get_tray_status(const CdIo_t* p_cdio); + driver_return_code_t cdio_eject_media(CdIo_t** p_cdio); + track_t cdio_get_last_track_num(const CdIo_t *p_cdio); + lsn_t cdio_get_track_lsn(const CdIo_t *p_cdio, track_t i_track); + lsn_t cdio_get_track_last_lsn(const CdIo_t *p_cdio, track_t i_track); + driver_return_code_t cdio_read_audio_sectors(const CdIo_t *p_cdio, void *p_buf, lsn_t i_lsn, uint32_t i_blocks); + driver_return_code_t cdio_close_tray(const char* psz_source, driver_id_t* driver_id); + const char* cdio_driver_errmsg(driver_return_code_t drc); + + char* GetDeviceFileName(); + +private: + char* s_defaultDevice; + CCriticalSection m_critSection; + static std::shared_ptr<CLibcdio> m_pInstance; +}; + +class CCdIoSupport +{ +public: + CCdIoSupport(); + virtual ~CCdIoSupport(); + + bool EjectTray(); + bool CloseTray(); + + HANDLE OpenCDROM(); + HANDLE OpenIMAGE( std::string& strFilename ); + int ReadSector(HANDLE hDevice, DWORD dwSector, char* lpczBuffer); + int ReadSectorMode2(HANDLE hDevice, DWORD dwSector, char* lpczBuffer); + int ReadSectorCDDA(HANDLE hDevice, DWORD dwSector, char* lpczBuffer); + void CloseCDROM(HANDLE hDevice); + + void PrintAnalysis(int fs, int num_audio); + + CCdInfo* GetCdInfo(char* cDeviceFileName=NULL); + void GetCdTextInfo(xbmc_cdtext_t &xcdt, int trackNum); + +protected: + int ReadBlock(int superblock, uint32_t offset, uint8_t bufnum, track_t track_num); + bool IsIt(int num); + int IsHFS(void); + int Is3DO(void); + int IsJoliet(void); + int IsUDF(void); + int GetSize(void); + int GetJolietLevel( void ); + int GuessFilesystem(int start_session, track_t track_num); + + uint32_t CddbDiscId(); + int CddbDecDigitSum(int n); + unsigned int MsfSeconds(msf_t *msf); + +private: + char buffer[7][CDIO_CD_FRAMESIZE_RAW]; /* for CD-Data */ + static signature_t sigs[17]; + int i = 0, j = 0; /* index */ + int m_nStartTrack; /* first sector of track */ + int m_nIsofsSize; /* size of session */ + int m_nJolietLevel; + int m_nMsOffset; /* multisession offset found by track-walking */ + int m_nDataStart; /* start of data area */ + int m_nFs; + int m_nUDFVerMinor; + int m_nUDFVerMajor; + + CdIo* cdio; + track_t m_nNumTracks = CDIO_INVALID_TRACK; + track_t m_nFirstTrackNum = CDIO_INVALID_TRACK; + + std::string m_strDiscLabel; + + int m_nFirstData; /* # of first data track */ + int m_nNumData; /* # of data tracks */ + int m_nFirstAudio; /* # of first audio track */ + int m_nNumAudio; /* # of audio tracks */ + + std::shared_ptr<CLibcdio> m_cdio; +}; + +} diff --git a/xbmc/storage/discs/IDiscDriveHandler.h b/xbmc/storage/discs/IDiscDriveHandler.h new file mode 100644 index 0000000..a3b007a --- /dev/null +++ b/xbmc/storage/discs/IDiscDriveHandler.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +/*! \brief Represents the state of a disc (optical) drive */ +enum class DriveState +{ + /*! The drive is open */ + OPEN, + /*! The drive is not ready (happens when openning or closing) */ + NOT_READY, + /*! The drive is ready */ + READY, + /*! The drive is closed but no media could be detected in the drive */ + CLOSED_NO_MEDIA, + /*! The drive is closed and there is media in the drive */ + CLOSED_MEDIA_PRESENT, + /*! The system does not have an optical drive */ + NONE, + /*! The drive is closed but we don't know yet if there's media there */ + CLOSED_MEDIA_UNDEFINED +}; + +/*! \brief Represents the state of the drive tray */ +enum class TrayState +{ + /*! The tray is in an undefined state, we don't know yet */ + UNDEFINED, + /*! The tray is open */ + OPEN, + /*! The tray is closed and doesn't have any optical media */ + CLOSED_NO_MEDIA, + /*! The tray is closed and contains optical media */ + CLOSED_MEDIA_PRESENT +}; + +/*! \brief Generic interface for platform disc drive handling +*/ +class IDiscDriveHandler +{ +public: + /*! \brief Get the optical drive state provided its device path + * \param devicePath the path for the device drive (e.g. /dev/sr0) + * \return The drive state + */ + virtual DriveState GetDriveState(const std::string& devicePath) = 0; + + /*! \brief Get the optical drive tray state provided the drive device path + * \param devicePath the path for the device drive (e.g. /dev/sr0) + * \return The drive state + */ + virtual TrayState GetTrayState(const std::string& devicePath) = 0; + + /*! \brief Eject the provided drive device + * \param devicePath the path for the device drive (e.g. /dev/sr0) + */ + virtual void EjectDriveTray(const std::string& devicePath) = 0; + + /*! \brief Close the provided drive device + * \note Some drives support closing appart from opening/eject + * \param devicePath the path for the device drive (e.g. /dev/sr0) + */ + virtual void CloseDriveTray(const std::string& devicePath) = 0; + + /*! \brief Toggle the state of a given drive device + * + * Will internally call EjectDriveTray or CloseDriveTray depending on + * the internal state of the drive (i.e. if open -> CloseDriveTray / + * if closed -> EjectDriveTray) + * + * \param devicePath the path for the device drive (e.g. /dev/sr0) + */ + virtual void ToggleDriveTray(const std::string& devicePath) = 0; + + /*! \brief Called to create platform-specific disc drive handler + * + * This method is used to create platform-specific disc drive handler + */ + static std::shared_ptr<IDiscDriveHandler> CreateInstance(); + +protected: + virtual ~IDiscDriveHandler() = default; + IDiscDriveHandler() = default; +}; |