2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "MediaManager.h"
23 #include "guilib/LocalizeStrings.h"
25 #include "utils/URIUtils.h"
27 #include "WIN32Util.h"
28 #include "utils/CharsetConverter.h"
30 #include "guilib/GUIWindowManager.h"
32 #ifndef TARGET_WINDOWS
33 // TODO: switch all ports to use auto sources
34 #include "DetectDVDType.h"
35 #include "filesystem/iso9660.h"
39 #include "GUIUserMessages.h"
40 #include "settings/MediaSourceSettings.h"
41 #include "settings/Settings.h"
42 #include "utils/XBMCTinyXML.h"
43 #include "threads/SingleLock.h"
44 #include "utils/log.h"
45 #include "dialogs/GUIDialogKaiToast.h"
46 #include "utils/JobManager.h"
47 #include "utils/StringUtils.h"
48 #include "AutorunMediaJob.h"
51 #include "filesystem/File.h"
52 #include "filesystem/DirectoryFactory.h"
53 #include "filesystem/Directory.h"
55 #include "cores/dvdplayer/DVDInputStreams/DVDInputStreamNavigator.h"
57 #if defined(TARGET_DARWIN)
58 #include "osx/DarwinStorageProvider.h"
59 #elif defined(TARGET_ANDROID)
60 #include "android/AndroidStorageProvider.h"
61 #elif defined(TARGET_FREEBSD)
62 #include "linux/LinuxStorageProvider.h"
63 #elif defined(TARGET_POSIX)
64 #include "linux/LinuxStorageProvider.h"
65 #include <sys/ioctl.h>
66 #include <linux/cdrom.h>
68 #include "windows/Win32StorageProvider.h"
72 using namespace XFILE;
74 const char MEDIA_SOURCES_XML[] = { "special://profile/mediasources.xml" };
76 class CMediaManager g_mediaManager;
78 CMediaManager::CMediaManager()
80 m_bhasoptical = false;
81 m_platformStorage = NULL;
84 void CMediaManager::Stop()
86 m_platformStorage->Stop();
88 delete m_platformStorage;
89 m_platformStorage = NULL;
92 void CMediaManager::Initialize()
94 if (!m_platformStorage)
96 #if defined(TARGET_DARWIN)
97 m_platformStorage = new CDarwinStorageProvider();
98 #elif defined(TARGET_ANDROID)
99 m_platformStorage = new CAndroidStorageProvider();
100 #elif defined(TARGET_POSIX)
101 m_platformStorage = new CLinuxStorageProvider();
103 m_platformStorage = new CWin32StorageProvider();
107 m_strFirstAvailDrive = m_platformStorage->GetFirstOpticalDeviceFileName();
109 m_platformStorage->Initialize();
112 bool CMediaManager::LoadSources()
114 // clear our location list
119 if ( !xmlDoc.LoadFile( MEDIA_SOURCES_XML ) )
122 TiXmlElement* pRootElement = xmlDoc.RootElement();
123 if ( !pRootElement || strcmpi(pRootElement->Value(), "mediasources") != 0)
125 CLog::Log(LOGERROR, "Error loading %s, Line %d (%s)", MEDIA_SOURCES_XML, xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
129 // load the <network> block
130 TiXmlNode *pNetwork = pRootElement->FirstChild("network");
133 TiXmlElement *pLocation = pNetwork->FirstChildElement("location");
136 CNetworkLocation location;
137 pLocation->Attribute("id", &location.id);
138 if (pLocation->FirstChild())
140 location.path = pLocation->FirstChild()->Value();
141 m_locations.push_back(location);
143 pLocation = pLocation->NextSiblingElement("location");
149 bool CMediaManager::SaveSources()
152 TiXmlElement xmlRootElement("mediasources");
153 TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement);
154 if (!pRoot) return false;
156 TiXmlElement networkNode("network");
157 TiXmlNode *pNetworkNode = pRoot->InsertEndChild(networkNode);
160 for (vector<CNetworkLocation>::iterator it = m_locations.begin(); it != m_locations.end(); it++)
162 TiXmlElement locationNode("location");
163 locationNode.SetAttribute("id", (*it).id);
164 TiXmlText value((*it).path);
165 locationNode.InsertEndChild(value);
166 pNetworkNode->InsertEndChild(locationNode);
169 return xmlDoc.SaveFile(MEDIA_SOURCES_XML);
172 void CMediaManager::GetLocalDrives(VECSOURCES &localDrives, bool includeQ)
174 CSingleLock lock(m_CritSecStorageProvider);
175 m_platformStorage->GetLocalDrives(localDrives);
178 void CMediaManager::GetRemovableDrives(VECSOURCES &removableDrives)
180 CSingleLock lock(m_CritSecStorageProvider);
181 m_platformStorage->GetRemovableDrives(removableDrives);
184 void CMediaManager::GetNetworkLocations(VECSOURCES &locations, bool autolocations)
188 for (unsigned int i = 0; i < m_locations.size(); i++)
191 share.strPath = m_locations[i].path;
192 CURL url(share.strPath);
193 share.strName = url.GetWithoutUserDetails();
194 locations.push_back(share);
199 share.m_ignore = true;
200 #ifdef HAS_FILESYSTEM_SMB
201 share.strPath = "smb://";
202 share.strName = g_localizeStrings.Get(20171);
203 locations.push_back(share);
206 #ifdef HAS_FILESYSTEM_NFS
207 share.strPath = "nfs://";
208 share.strName = g_localizeStrings.Get(20259);
209 locations.push_back(share);
210 #endif// HAS_FILESYSTEM_NFS
213 share.strPath = "upnp://";
214 share.strName = "UPnP Devices";
215 locations.push_back(share);
219 share.strPath = "zeroconf://";
220 share.strName = "Zeroconf Browser";
221 locations.push_back(share);
226 bool CMediaManager::AddNetworkLocation(const CStdString &path)
228 CNetworkLocation location;
229 location.path = path;
230 location.id = (int)m_locations.size();
231 m_locations.push_back(location);
232 return SaveSources();
235 bool CMediaManager::HasLocation(const CStdString& path) const
237 for (unsigned int i=0;i<m_locations.size();++i)
239 if (URIUtils::CompareWithoutSlashAtEnd(m_locations[i].path, path))
247 bool CMediaManager::RemoveLocation(const CStdString& path)
249 for (unsigned int i=0;i<m_locations.size();++i)
251 if (URIUtils::CompareWithoutSlashAtEnd(m_locations[i].path, path))
253 // prompt for sources, remove, cancel,
254 m_locations.erase(m_locations.begin()+i);
255 return SaveSources();
262 bool CMediaManager::SetLocationPath(const CStdString& oldPath, const CStdString& newPath)
264 for (unsigned int i=0;i<m_locations.size();++i)
266 if (URIUtils::CompareWithoutSlashAtEnd(m_locations[i].path, oldPath))
268 m_locations[i].path = newPath;
269 return SaveSources();
276 void CMediaManager::AddAutoSource(const CMediaSource &share, bool bAutorun)
278 CMediaSourceSettings::Get().AddShare("files", share);
279 CMediaSourceSettings::Get().AddShare("video", share);
280 CMediaSourceSettings::Get().AddShare("pictures", share);
281 CMediaSourceSettings::Get().AddShare("music", share);
282 CMediaSourceSettings::Get().AddShare("programs", share);
283 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
284 g_windowManager.SendThreadMessage( msg );
288 MEDIA_DETECT::CAutorun::ExecuteAutorun(share.strPath);
292 void CMediaManager::RemoveAutoSource(const CMediaSource &share)
294 CMediaSourceSettings::Get().DeleteSource("files", share.strName, share.strPath, true);
295 CMediaSourceSettings::Get().DeleteSource("video", share.strName, share.strPath, true);
296 CMediaSourceSettings::Get().DeleteSource("pictures", share.strName, share.strPath, true);
297 CMediaSourceSettings::Get().DeleteSource("music", share.strName, share.strPath, true);
298 CMediaSourceSettings::Get().DeleteSource("programs", share.strName, share.strPath, true);
299 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_SOURCES);
300 g_windowManager.SendThreadMessage( msg );
303 // delete cached CdInfo if any
304 RemoveCdInfo(TranslateDevicePath(share.strPath, true));
308 /////////////////////////////////////////////////////////////
309 // AutoSource status functions:
310 // - TODO: translate cdda://<device>/
312 CStdString CMediaManager::TranslateDevicePath(const CStdString& devicePath, bool bReturnAsDevice)
314 CSingleLock waitLock(m_muAutoSource);
315 CStdString strDevice = devicePath;
316 // fallback for cdda://local/ and empty devicePath
318 if(devicePath.empty() || StringUtils::StartsWith(devicePath, "cdda://local"))
319 strDevice = m_strFirstAvailDrive;
322 #ifdef TARGET_WINDOWS
326 if(bReturnAsDevice == false)
327 StringUtils::Replace(strDevice, "\\\\.\\","");
328 else if(!strDevice.empty() && strDevice[1]==':')
329 strDevice = StringUtils::Format("\\\\.\\%c:", strDevice[0]);
331 URIUtils::RemoveSlashAtEnd(strDevice);
336 bool CMediaManager::IsDiscInDrive(const CStdString& devicePath)
339 #ifdef TARGET_WINDOWS
343 CStdString strDevice = TranslateDevicePath(devicePath, true);
344 std::map<CStdString,CCdInfo*>::iterator it;
345 CSingleLock waitLock(m_muAutoSource);
346 it = m_mapCdInfo.find(strDevice);
347 if(it != m_mapCdInfo.end())
352 if(URIUtils::IsDVD(devicePath) || devicePath.empty())
353 return MEDIA_DETECT::CDetectDVDMedia::IsDiscInDrive(); // TODO: switch all ports to use auto sources
355 return true; // Assume other paths to be mounted already
362 bool CMediaManager::IsAudio(const CStdString& devicePath)
365 #ifdef TARGET_WINDOWS
369 CCdInfo* pCdInfo = GetCdInfo(devicePath);
370 if(pCdInfo != NULL && pCdInfo->IsAudio(1))
375 // TODO: switch all ports to use auto sources
376 MEDIA_DETECT::CCdInfo* pInfo = MEDIA_DETECT::CDetectDVDMedia::GetCdInfo();
377 if (pInfo != NULL && pInfo->IsAudio(1))
384 bool CMediaManager::HasOpticalDrive()
387 if (!m_strFirstAvailDrive.empty())
393 DWORD CMediaManager::GetDriveStatus(const CStdString& devicePath)
396 #ifdef TARGET_WINDOWS
398 return DRIVE_NOT_READY;
400 CStdString strDevice = TranslateDevicePath(devicePath, true);
401 DWORD dwRet = DRIVE_NOT_READY;
402 int status = CWIN32Util::GetDriveStatus(strDevice);
407 dwRet = DRIVE_NOT_READY;
410 dwRet = DRIVE_CLOSED_NO_MEDIA;
415 case 2: // media accessible
416 dwRet = DRIVE_CLOSED_MEDIA_PRESENT;
421 return MEDIA_DETECT::CDetectDVDMedia::DriveReady();
424 return DRIVE_NOT_READY;
429 CCdInfo* CMediaManager::GetCdInfo(const CStdString& devicePath)
431 #ifdef TARGET_WINDOWS
435 CStdString strDevice = TranslateDevicePath(devicePath, true);
436 std::map<CStdString,CCdInfo*>::iterator it;
438 CSingleLock waitLock(m_muAutoSource);
439 it = m_mapCdInfo.find(strDevice);
440 if(it != m_mapCdInfo.end())
444 CCdInfo* pCdInfo=NULL;
446 pCdInfo = cdio.GetCdInfo((char*)strDevice.c_str());
449 CSingleLock waitLock(m_muAutoSource);
450 m_mapCdInfo.insert(std::pair<CStdString,CCdInfo*>(strDevice,pCdInfo));
455 return MEDIA_DETECT::CDetectDVDMedia::GetCdInfo();
459 bool CMediaManager::RemoveCdInfo(const CStdString& devicePath)
464 CStdString strDevice = TranslateDevicePath(devicePath, true);
466 std::map<CStdString,CCdInfo*>::iterator it;
467 CSingleLock waitLock(m_muAutoSource);
468 it = m_mapCdInfo.find(strDevice);
469 if(it != m_mapCdInfo.end())
471 if(it->second != NULL)
474 m_mapCdInfo.erase(it);
480 CStdString CMediaManager::GetDiskLabel(const CStdString& devicePath)
482 #ifdef TARGET_WINDOWS
486 CStdString strDevice = TranslateDevicePath(devicePath);
487 WCHAR cVolumenName[128];
489 URIUtils::AddSlashAtEnd(strDevice);
490 if(GetVolumeInformationW(CStdStringW(strDevice).c_str(), cVolumenName, 127, NULL, NULL, NULL, cFSName, 127)==0)
492 g_charsetConverter.wToUTF8(cVolumenName, strDevice);
493 return StringUtils::TrimRight(strDevice, " ");
495 return MEDIA_DETECT::CDetectDVDMedia::GetDVDLabel();
499 CStdString CMediaManager::GetDiskUniqueId(const CStdString& devicePath)
501 CStdString mediaPath;
503 CCdInfo* pInfo = g_mediaManager.GetCdInfo(devicePath);
507 if (mediaPath.empty() && pInfo->IsAudio(1))
508 mediaPath = "cdda://local/";
510 if (mediaPath.empty() && (pInfo->IsISOUDF(1) || pInfo->IsISOHFS(1) || pInfo->IsIso9660(1) || pInfo->IsIso9660Interactive(1)))
511 mediaPath = "iso9660://";
513 if (mediaPath.empty())
514 mediaPath = devicePath;
516 #ifdef TARGET_WINDOWS
517 if (mediaPath.empty() || mediaPath == "iso9660://")
519 mediaPath = g_mediaManager.TranslateDevicePath("");
520 URIUtils::AddSlashAtEnd(mediaPath);
524 // Try finding VIDEO_TS/VIDEO_TS.IFO - this indicates a DVD disc is inserted
525 CStdString pathVideoTS = URIUtils::AddFileToFolder(mediaPath, "VIDEO_TS");
526 if(!CFile::Exists(URIUtils::AddFileToFolder(pathVideoTS, "VIDEO_TS.IFO")))
527 return ""; // return empty
529 // correct the filename if needed
530 if (StringUtils::StartsWith(pathVideoTS, "dvd://") ||
531 StringUtils::StartsWith(pathVideoTS, "iso9660://"))
532 pathVideoTS = g_mediaManager.TranslateDevicePath("");
534 CLog::Log(LOGDEBUG, "GetDiskUniqueId: Trying to retrieve ID for path %s", pathVideoTS.c_str());
537 CDVDInputStreamNavigator dvdNavigator(NULL);
538 dvdNavigator.Open(pathVideoTS, "");
539 CStdString labelString;
540 dvdNavigator.GetDVDTitleString(labelString);
541 CStdString serialString;
542 dvdNavigator.GetDVDSerialString(serialString);
544 CStdString strID = StringUtils::Format("removable://%s_%s", labelString.c_str(), serialString.c_str());
545 CLog::Log(LOGDEBUG, "GetDiskUniqueId: Got ID %s for DVD disk", strID.c_str());
550 CStdString CMediaManager::GetDiscPath()
552 #ifdef TARGET_WINDOWS
553 return g_mediaManager.TranslateDevicePath("");
556 CSingleLock lock(m_CritSecStorageProvider);
558 m_platformStorage->GetRemovableDrives(drives);
559 for(unsigned i = 0; i < drives.size(); ++i)
561 if(drives[i].m_iDriveType == CMediaSource::SOURCE_TYPE_DVD)
562 return drives[i].strPath;
565 // iso9660://, cdda://local/ or D:\ depending on disc type
566 return MEDIA_DETECT::CDetectDVDMedia::GetDVDPath();
571 void CMediaManager::SetHasOpticalDrive(bool bstatus)
573 CSingleLock waitLock(m_muAutoSource);
574 m_bhasoptical = bstatus;
577 bool CMediaManager::Eject(CStdString mountpath)
579 CSingleLock lock(m_CritSecStorageProvider);
580 return m_platformStorage->Eject(mountpath);
583 void CMediaManager::EjectTray( const bool bEject, const char cDriveLetter )
586 #ifdef TARGET_WINDOWS
587 CWIN32Util::EjectTray(cDriveLetter);
589 boost::shared_ptr<CLibcdio> c_cdio = CLibcdio::GetInstance();
590 char* dvdDevice = c_cdio->GetDeviceFileName();
593 while (nRetries-- > 0)
595 CdIo_t* cdio = c_cdio->cdio_open(dvdDevice, DRIVER_UNKNOWN);
598 c_cdio->cdio_eject_media(&cdio);
599 c_cdio->cdio_destroy(cdio);
608 void CMediaManager::CloseTray(const char cDriveLetter)
611 #if defined(TARGET_DARWIN)
613 #elif defined(TARGET_FREEBSD)
615 #elif defined(TARGET_POSIX)
616 char* dvdDevice = CLibcdio::GetInstance()->GetDeviceFileName();
617 if (strlen(dvdDevice) != 0)
619 int fd = open(dvdDevice, O_RDONLY | O_NONBLOCK);
622 ioctl(fd, CDROMCLOSETRAY, 0);
626 #elif defined(TARGET_WINDOWS)
627 CWIN32Util::CloseTray(cDriveLetter);
632 void CMediaManager::ToggleTray(const char cDriveLetter)
635 #if defined(TARGET_WINDOWS)
636 CWIN32Util::ToggleTray(cDriveLetter);
638 if (GetDriveStatus() == TRAY_OPEN || GetDriveStatus() == DRIVE_OPEN)
646 void CMediaManager::ProcessEvents()
648 CSingleLock lock(m_CritSecStorageProvider);
649 if (m_platformStorage->PumpDriveChangeEvents(this))
651 #if defined(HAS_DVD_DRIVE) && defined(TARGET_DARWIN_OSX)
652 // darwins GetFirstOpticalDeviceFileName only gives us something
653 // when a disc is inserted
654 // so we have to refresh m_strFirstAvailDrive when this happens after Initialize
655 // was called (e.x. the disc was inserted after the start of xbmc)
656 // else TranslateDevicePath wouldn't give the correct device
657 m_strFirstAvailDrive = m_platformStorage->GetFirstOpticalDeviceFileName();
660 CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES);
661 g_windowManager.SendThreadMessage(msg);
665 std::vector<CStdString> CMediaManager::GetDiskUsage()
667 CSingleLock lock(m_CritSecStorageProvider);
668 return m_platformStorage->GetDiskUsage();
671 void CMediaManager::OnStorageAdded(const CStdString &label, const CStdString &path)
674 if (CSettings::Get().GetInt("audiocds.autoaction") != AUTOCD_NONE || CSettings::Get().GetBool("dvds.autorun"))
675 if (CSettings::Get().GetInt("audiocds.autoaction") == AUTOCD_RIP)
676 CJobManager::GetInstance().AddJob(new CAutorunMediaJob(label, path), this, CJob::PRIORITY_LOW);
678 CJobManager::GetInstance().AddJob(new CAutorunMediaJob(label, path), this, CJob::PRIORITY_HIGH);
680 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(13021), label, TOAST_DISPLAY_TIME, false);
684 void CMediaManager::OnStorageSafelyRemoved(const CStdString &label)
686 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(13023), label, TOAST_DISPLAY_TIME, false);
689 void CMediaManager::OnStorageUnsafelyRemoved(const CStdString &label)
691 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(13022), label);