|
MythTV
0.26-pre
|
00001 00008 #include <QDir> 00009 #include <QMetaType> 00010 00011 #include "mythmediamonitor.h" 00012 #include "mediamonitor-darwin.h" 00013 #include "mythcdrom.h" 00014 #include "mythhdd.h" 00015 00016 #include "mythlogging.h" 00017 00018 #include <IOKit/IOKitLib.h> 00019 #include <IOKit/storage/IOMedia.h> 00020 #include <IOKit/storage/IOCDMedia.h> 00021 #include <IOKit/storage/IODVDMedia.h> 00022 #include <IOKit/storage/IOBlockStorageDevice.h> 00023 #include <IOKit/storage/IOStorageDeviceCharacteristics.h> 00024 #include <IOKit/storage/IOStorageProtocolCharacteristics.h> 00025 #include <DiskArbitration/DiskArbitration.h> 00026 00027 00028 // These aren't external, they are defined in this file. 00029 // The 'extern "C"' forces them in the C namespace, not the C++ 00030 extern "C" void diskAppearedCallback(DADiskRef disk, void *context); 00031 extern "C" void diskDisappearedCallback(DADiskRef disk, void *context); 00032 extern "C" void diskChangedCallback(DADiskRef disk, 00033 CFArrayRef keys, void *context); 00034 extern "C" MythMediaType MediaTypeForBSDName(const char *bsdName); 00035 00036 static mach_port_t sMasterPort; 00037 00038 00042 MythMediaType FindMediaType(io_service_t service) 00043 { 00044 kern_return_t kernResult; 00045 io_iterator_t iter; 00046 MythMediaType mediaType = MEDIATYPE_UNKNOWN; 00047 QString msg = QString("FindMediaType() - "); 00048 bool isWholeMedia = false; 00049 00050 // Create an iterator across all parents of the service object passed in. 00051 kernResult = IORegistryEntryCreateIterator(service, 00052 kIOServicePlane, 00053 kIORegistryIterateRecursively 00054 | kIORegistryIterateParents, 00055 &iter); 00056 00057 if (KERN_SUCCESS != kernResult) 00058 LOG(VB_GENERAL, LOG_CRIT, msg + 00059 QString("IORegistryEntryCreateIterator returned %1") 00060 .arg(kernResult)); 00061 else if (!iter) 00062 LOG(VB_GENERAL, LOG_CRIT, msg + 00063 "IORegistryEntryCreateIterator returned NULL iterator"); 00064 else 00065 { 00066 // A reference on the initial service object is released in 00067 // the do-while loop below, so add a reference to balance 00068 IOObjectRetain(service); 00069 00070 do 00071 { 00072 isWholeMedia = false; 00073 if (IOObjectConformsTo(service, kIOMediaClass)) 00074 { 00075 CFTypeRef wholeMedia; 00076 00077 wholeMedia = IORegistryEntryCreateCFProperty 00078 (service, CFSTR(kIOMediaWholeKey), 00079 kCFAllocatorDefault, 0); 00080 00081 if (!wholeMedia) 00082 LOG(VB_GENERAL, LOG_ALERT, msg + 00083 "Could not retrieve Whole property"); 00084 else 00085 { 00086 isWholeMedia = CFBooleanGetValue((CFBooleanRef)wholeMedia); 00087 CFRelease(wholeMedia); 00088 } 00089 } 00090 00091 if (isWholeMedia) 00092 { 00093 if (IOObjectConformsTo(service, kIODVDMediaClass)) 00094 mediaType = MEDIATYPE_DVD; 00095 else if (IOObjectConformsTo(service, kIOCDMediaClass)) 00096 mediaType = MEDIATYPE_AUDIO; 00097 } 00098 00099 IOObjectRelease(service); 00100 00101 } while ((service = IOIteratorNext(iter)) 00102 && (mediaType == MEDIATYPE_UNKNOWN)); 00103 00104 IOObjectRelease(iter); 00105 } 00106 return mediaType; 00107 } 00108 00112 MythMediaType MediaTypeForBSDName(const char *bsdName) 00113 { 00114 CFMutableDictionaryRef matchingDict; 00115 kern_return_t kernResult; 00116 io_iterator_t iter; 00117 io_service_t service; 00118 QString msg = QString("MediaTypeForBSDName(%1)") 00119 .arg(bsdName); 00120 MythMediaType mediaType; 00121 00122 00123 if (!bsdName || !*bsdName) 00124 { 00125 LOG(VB_GENERAL, LOG_ALERT, msg + " - No name supplied?"); 00126 return MEDIATYPE_UNKNOWN; 00127 } 00128 00129 matchingDict = IOBSDNameMatching(sMasterPort, 0, bsdName); 00130 if (!matchingDict) 00131 { 00132 LOG(VB_GENERAL, LOG_ALERT, 00133 msg + " - IOBSDNameMatching() returned a NULL dictionary."); 00134 return MEDIATYPE_UNKNOWN; 00135 } 00136 00137 // Return an iterator across all objects with the matching 00138 // BSD node name. Note that there should only be one match! 00139 kernResult = IOServiceGetMatchingServices(sMasterPort, matchingDict, &iter); 00140 00141 if (KERN_SUCCESS != kernResult) 00142 { 00143 LOG(VB_GENERAL, LOG_ALERT, 00144 QString(msg + " - IOServiceGetMatchingServices() returned %2") 00145 .arg(kernResult)); 00146 return MEDIATYPE_UNKNOWN; 00147 } 00148 if (!iter) 00149 { 00150 LOG(VB_GENERAL, LOG_ALERT, 00151 msg + " - IOServiceGetMatchingServices() returned a NULL " 00152 "iterator"); 00153 return MEDIATYPE_UNKNOWN; 00154 } 00155 00156 service = IOIteratorNext(iter); 00157 00158 // Release this now because we only expect 00159 // the iterator to contain a single io_service_t. 00160 IOObjectRelease(iter); 00161 00162 if (!service) 00163 { 00164 LOG(VB_GENERAL, LOG_ALERT, 00165 msg + " - IOIteratorNext() returned a NULL iterator"); 00166 return MEDIATYPE_UNKNOWN; 00167 } 00168 mediaType = FindMediaType(service); 00169 IOObjectRelease(service); 00170 return mediaType; 00171 } 00172 00173 00177 static char * getVolName(CFDictionaryRef diskDetails) 00178 { 00179 CFStringRef name; 00180 CFIndex size; 00181 char *volName; 00182 00183 name = (CFStringRef) 00184 CFDictionaryGetValue(diskDetails, kDADiskDescriptionVolumeNameKey); 00185 00186 if (!name) 00187 return NULL; 00188 00189 size = CFStringGetLength(name) + 1; 00190 volName = (char *) malloc(size); 00191 if (!volName) 00192 { 00193 LOG(VB_GENERAL, LOG_ALERT, 00194 QString("getVolName() - Can't malloc(%1)?").arg(size)); 00195 return NULL; 00196 } 00197 00198 if (!CFStringGetCString(name, volName, size, kCFStringEncodingUTF8)) 00199 { 00200 free(volName); 00201 return NULL; 00202 } 00203 00204 return volName; 00205 } 00206 00207 /* 00208 * Given a DA description, return a compound description to help identify it. 00209 */ 00210 static const QString getModel(CFDictionaryRef diskDetails) 00211 { 00212 QString desc; 00213 const void *strRef; 00214 00215 // Location 00216 if (kCFBooleanTrue == 00217 CFDictionaryGetValue(diskDetails, 00218 kDADiskDescriptionDeviceInternalKey)) 00219 desc.append("Internal "); 00220 00221 // Manufacturer 00222 strRef = CFDictionaryGetValue(diskDetails, 00223 kDADiskDescriptionDeviceVendorKey); 00224 if (strRef) 00225 { 00226 desc.append(CFStringGetCStringPtr((CFStringRef)strRef, 00227 kCFStringEncodingMacRoman)); 00228 desc.append(' '); 00229 } 00230 00231 // Product 00232 strRef = CFDictionaryGetValue(diskDetails, 00233 kDADiskDescriptionDeviceModelKey); 00234 if (strRef) 00235 { 00236 desc.append(CFStringGetCStringPtr((CFStringRef)strRef, 00237 kCFStringEncodingMacRoman)); 00238 desc.append(' '); 00239 } 00240 00241 // Remove the trailing space 00242 desc.truncate(desc.length() - 1); 00243 00244 // and multiple spaces 00245 desc.remove(" "); 00246 00247 return desc; 00248 } 00249 00250 00251 /* 00252 * Callbacks which the Disk Arbitration session invokes 00253 * whenever a disk comes or goes, or is renamed 00254 */ 00255 00256 void diskAppearedCallback(DADiskRef disk, void *context) 00257 { 00258 const char *BSDname = DADiskGetBSDName(disk); 00259 CFDictionaryRef details; 00260 bool isCDorDVD; 00261 MythMediaType mediaType; 00262 QString model; 00263 MonitorThreadDarwin *mtd; 00264 QString msg = "diskAppearedCallback() - "; 00265 char *volName; 00266 00267 00268 if (!BSDname) 00269 { 00270 LOG(VB_MEDIA, LOG_INFO, msg + "Skipping non-local device"); 00271 return; 00272 } 00273 00274 if (!context) 00275 { 00276 LOG(VB_GENERAL, LOG_ALERT, msg + "Error. Invoked with a NULL context."); 00277 return; 00278 } 00279 00280 mtd = reinterpret_cast<MonitorThreadDarwin*>(context); 00281 00282 00283 // We want to monitor CDs/DVDs and USB cameras or flash drives, 00284 // but probably not hard disk or network drives. For now, ignore 00285 // any disk or partitions that are not on removable media. 00286 // Seems OK for hot-plug USB/FireWire disks (i.e. they are removable) 00287 00288 details = DADiskCopyDescription(disk); 00289 00290 if (kCFBooleanFalse == 00291 CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey)) 00292 { 00293 LOG(VB_MEDIA, LOG_INFO, msg + QString("Skipping non-removable %1") 00294 .arg(BSDname)); 00295 CFRelease(details); 00296 return; 00297 } 00298 00299 // Get the volume and model name for more user-friendly interaction 00300 volName = getVolName(details); 00301 if (!volName) 00302 { 00303 LOG(VB_MEDIA, LOG_INFO, msg + QString("No volume name for dev %1") 00304 .arg(BSDname)); 00305 CFRelease(details); 00306 return; 00307 } 00308 00309 model = getModel(details); 00310 mediaType = MediaTypeForBSDName(BSDname); 00311 isCDorDVD = (mediaType == MEDIATYPE_DVD) || (mediaType == MEDIATYPE_AUDIO); 00312 00313 00314 // We know it is removable, and have guessed the type. 00315 // Call a helper function to create appropriate objects and insert 00316 00317 LOG(VB_MEDIA, LOG_INFO, QString("Found disk %1 - volume name '%2'.") 00318 .arg(BSDname).arg(volName)); 00319 00320 mtd->diskInsert(BSDname, volName, model, isCDorDVD); 00321 00322 CFRelease(details); 00323 free(volName); 00324 } 00325 00326 void diskDisappearedCallback(DADiskRef disk, void *context) 00327 { 00328 const char *BSDname = DADiskGetBSDName(disk); 00329 00330 if (context) 00331 reinterpret_cast<MonitorThreadDarwin *>(context)->diskRemove(BSDname); 00332 } 00333 00334 void diskChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) 00335 { 00336 if (CFArrayContainsValue(keys, CFRangeMake(0, CFArrayGetCount(keys)), 00337 kDADiskDescriptionVolumeNameKey)) 00338 { 00339 const char *BSDname = DADiskGetBSDName(disk); 00340 CFDictionaryRef details = DADiskCopyDescription(disk); 00341 char *volName = getVolName(details); 00342 00343 LOG(VB_MEDIA, LOG_INFO, QString("Disk %1 - changed name to '%2'.") 00344 .arg(BSDname).arg(volName)); 00345 00346 reinterpret_cast<MonitorThreadDarwin *>(context) 00347 ->diskRename(BSDname, volName); 00348 CFRelease(details); 00349 free(volName); 00350 } 00351 } 00352 00353 00357 void MonitorThreadDarwin::run(void) 00358 { 00359 CFDictionaryRef match = kDADiskDescriptionMatchVolumeMountable; 00360 DASessionRef daSession = DASessionCreate(kCFAllocatorDefault); 00361 00362 IOMasterPort(MACH_PORT_NULL, &sMasterPort); 00363 00364 DARegisterDiskAppearedCallback(daSession, match, 00365 diskAppearedCallback, this); 00366 DARegisterDiskDisappearedCallback(daSession, match, 00367 diskDisappearedCallback, this); 00368 DARegisterDiskDescriptionChangedCallback(daSession, match, 00369 kDADiskDescriptionWatchVolumeName, 00370 diskChangedCallback, this); 00371 00372 DASessionScheduleWithRunLoop(daSession, 00373 CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 00374 00375 00376 // Nice and simple, as long as our monitor is valid and active, 00377 // loop and let daSession check the devices. 00378 while (m_Monitor && m_Monitor->IsActive()) 00379 { 00380 // Run the run loop for interval (milliseconds) - this will 00381 // handle any disk arbitration appeared/dissappeared events 00382 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 00383 (float) m_Interval / 1000.0f, false ); 00384 } 00385 00386 DAUnregisterCallback(daSession, (void(*))diskChangedCallback, this); 00387 DAUnregisterCallback(daSession, (void(*))diskDisappearedCallback, this); 00388 DAUnregisterCallback(daSession, (void(*))diskAppearedCallback, this); 00389 CFRelease(daSession); 00390 } 00391 00398 void MonitorThreadDarwin::diskInsert(const char *devName, 00399 const char *volName, 00400 QString model, bool isCDorDVD) 00401 { 00402 MythMediaDevice *media; 00403 QString msg = "MonitorThreadDarwin::diskInsert"; 00404 00405 LOG(VB_MEDIA, LOG_DEBUG, msg + QString("(%1,%2,'%3',%4)") 00406 .arg(devName).arg(volName).arg(model).arg(isCDorDVD)); 00407 00408 if (isCDorDVD) 00409 media = MythCDROM::get(NULL, devName, true, m_Monitor->m_AllowEject); 00410 else 00411 media = MythHDD::Get(NULL, devName, true, false); 00412 00413 if (!media) 00414 { 00415 LOG(VB_GENERAL, LOG_ALERT, msg + "Couldn't create MythMediaDevice."); 00416 return; 00417 } 00418 00419 // We store the volume name for user activities like ChooseAndEjectMedia(). 00420 media->setVolumeID(volName); 00421 media->setDeviceModel(model.toAscii()); // Same for the Manufacturer and model 00422 00423 // Mac OS X devices are pre-mounted here: 00424 QString mnt = "/Volumes/"; mnt += volName; 00425 media->setMountPath(mnt.toAscii()); 00426 00427 int attempts = 0; 00428 QDir d(mnt); 00429 while (!d.exists()) 00430 { 00431 LOG(VB_MEDIA, LOG_WARNING, 00432 (msg + "() - Waiting for mount '%1' to become stable.").arg(mnt)); 00433 usleep(120000); 00434 if ( ++attempts > 4 ) 00435 usleep(200000); 00436 if ( attempts > 8 ) 00437 { 00438 delete media; 00439 LOG(VB_MEDIA, LOG_ALERT, msg + "() - Giving up"); 00440 return; 00441 } 00442 } 00443 00444 media->setStatus(MEDIASTAT_MOUNTED); 00445 00446 // This is checked in AddDevice(), but checking earlier means 00447 // we can avoid scanning all the files to determine its type 00448 if (m_Monitor->shouldIgnore(media)) 00449 return; 00450 00451 // We want to use MythMedia's code to work out the mediaType. 00452 // media->onDeviceMounted() is protected, 00453 // so to call it indirectly, we pretend to mount it here. 00454 media->mount(); 00455 00456 m_Monitor->AddDevice(media); 00457 } 00458 00459 void MonitorThreadDarwin::diskRemove(QString devName) 00460 { 00461 LOG(VB_MEDIA, LOG_DEBUG, 00462 QString("MonitorThreadDarwin::diskRemove(%1)").arg(devName)); 00463 00464 if (m_Monitor->m_SendEvent) 00465 { 00466 MythMediaDevice *pDevice = m_Monitor->GetMedia(devName); 00467 00468 if (pDevice) // Probably should ValidateAndLock() here? 00469 pDevice->setStatus(MEDIASTAT_NODISK); 00470 else 00471 LOG(VB_MEDIA, LOG_INFO, 00472 "Couldn't find MythMediaDevice: " + devName); 00473 } 00474 00475 m_Monitor->RemoveDevice(devName); 00476 } 00477 00484 void MonitorThreadDarwin::diskRename(const char *devName, const char *volName) 00485 { 00486 LOG(VB_MEDIA, LOG_DEBUG, 00487 QString("MonitorThreadDarwin::diskRename(%1,%2)") 00488 .arg(devName).arg(volName)); 00489 00490 MythMediaDevice *pDevice = m_Monitor->GetMedia(devName); 00491 00492 if (m_Monitor->ValidateAndLock(pDevice)) 00493 { 00494 // Send message to plugins to ignore this drive: 00495 if (m_Monitor->m_SendEvent) 00496 pDevice->setStatus(MEDIASTAT_NODISK); 00497 00498 pDevice->setVolumeID(volName); 00499 pDevice->setMountPath((QString("/Volumes/") + volName).toAscii()); 00500 00501 // Plugins can now use it again: 00502 if (m_Monitor->m_SendEvent) 00503 pDevice->setStatus(MEDIASTAT_USEABLE); 00504 00505 m_Monitor->Unlock(pDevice); 00506 } 00507 else 00508 LOG(VB_MEDIA, LOG_INFO, 00509 QString("Couldn't find MythMediaDevice: %1").arg(devName)); 00510 } 00511 00518 void MediaMonitorDarwin::StartMonitoring(void) 00519 { 00520 // Sanity check 00521 if (m_Active) 00522 return; 00523 00524 if (!m_StartThread) 00525 return; 00526 00527 00528 // If something (like the MythMusic plugin) stops and starts monitoring, 00529 // DiskArbitration would re-add the same drives several times over. 00530 // So, we make sure the device list is deleted. 00531 m_Devices.clear(); 00532 00533 00534 if (!m_Thread) 00535 m_Thread = new MonitorThreadDarwin(this, m_MonitorPollingInterval); 00536 00537 qRegisterMetaType<MythMediaStatus>("MythMediaStatus"); 00538 00539 LOG(VB_MEDIA, LOG_NOTICE, "Starting MediaMonitor"); 00540 m_Active = true; 00541 m_Thread->start(); 00542 } 00543 00549 bool MediaMonitorDarwin::AddDevice(MythMediaDevice* pDevice) 00550 { 00551 if ( !pDevice ) 00552 { 00553 LOG(VB_GENERAL, LOG_ERR, "MediaMonitor::AddDevice(null)"); 00554 return false; 00555 } 00556 00557 // If the user doesn't want this device to be monitored, stop now: 00558 if (shouldIgnore(pDevice)) 00559 return false; 00560 00561 m_Devices.push_back( pDevice ); 00562 m_UseCount[pDevice] = 0; 00563 00564 00565 // Devices on Mac OS X don't change status the way Linux ones do, 00566 // so we force a status change for mediaStatusChanged() to send an event 00567 if (m_SendEvent) 00568 { 00569 pDevice->setStatus(MEDIASTAT_NODISK); 00570 connect(pDevice, SIGNAL(statusChanged(MythMediaStatus, MythMediaDevice*)), 00571 this, SLOT(mediaStatusChanged(MythMediaStatus, MythMediaDevice*))); 00572 pDevice->setStatus(MEDIASTAT_USEABLE); 00573 } 00574 00575 00576 return true; 00577 } 00578 00579 /* 00580 * Given a device, return a compound description to help identify it. 00581 * We try to find out if it is internal, its manufacturer, and model. 00582 */ 00583 static const QString getModel(io_object_t drive) 00584 { 00585 QString desc; 00586 CFMutableDictionaryRef props = NULL; 00587 00588 props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively); 00589 CFShow(props); 00590 if (props) 00591 { 00592 const void *location = CFDictionaryGetValue(props, CFSTR(kIOPropertyPhysicalInterconnectLocationKey)); 00593 if (CFEqual(location, CFSTR("Internal"))) 00594 desc.append("Internal "); 00595 } 00596 00597 props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively); 00598 if (props) 00599 { 00600 const void *product = CFDictionaryGetValue(props, CFSTR(kIOPropertyProductNameKey)); 00601 const void *vendor = CFDictionaryGetValue(props, CFSTR(kIOPropertyVendorNameKey)); 00602 if (vendor) 00603 { 00604 desc.append(CFStringGetCStringPtr((CFStringRef)vendor, kCFStringEncodingMacRoman)); 00605 desc.append(" "); 00606 } 00607 if (product) 00608 { 00609 desc.append(CFStringGetCStringPtr((CFStringRef)product, kCFStringEncodingMacRoman)); 00610 desc.append(" "); 00611 } 00612 } 00613 00614 // Omit the trailing space 00615 desc.truncate(desc.length() - 1); 00616 00617 return desc; 00618 } 00619 00629 QStringList MediaMonitorDarwin::GetCDROMBlockDevices() 00630 { 00631 kern_return_t kernResult; 00632 CFMutableDictionaryRef devices; 00633 io_iterator_t iter; 00634 QStringList list; 00635 QString msg = QString("GetCDRomBlockDevices() - "); 00636 00637 00638 devices = IOServiceMatching(kIOBlockStorageDeviceClass); 00639 if (!devices) 00640 { 00641 LOG(VB_GENERAL, LOG_ALERT, msg + "No Storage Devices? Unlikely!"); 00642 return list; 00643 } 00644 00645 // Create an iterator across all parents of the service object passed in. 00646 kernResult = IOServiceGetMatchingServices(sMasterPort, devices, &iter); 00647 00648 if (KERN_SUCCESS != kernResult) 00649 { 00650 LOG(VB_GENERAL, LOG_ALERT, msg + 00651 QString("IORegistryEntryCreateIterator returned %1") 00652 .arg(kernResult)); 00653 return list; 00654 } 00655 if (!iter) 00656 { 00657 LOG(VB_GENERAL, LOG_ALERT, msg + 00658 "IORegistryEntryCreateIterator returned a NULL iterator"); 00659 return list; 00660 } 00661 00662 io_object_t drive; 00663 00664 while ((drive = IOIteratorNext(iter))) 00665 { 00666 CFMutableDictionaryRef p = NULL; // properties of drive 00667 00668 IORegistryEntryCreateCFProperties(drive, &p, kCFAllocatorDefault, 0); 00669 if (p) 00670 { 00671 const void *type = CFDictionaryGetValue(p, CFSTR("device-type")); 00672 00673 if (CFEqual(type, CFSTR("DVD")) || CFEqual(type, CFSTR("CD"))) 00674 { 00675 QString desc = getModel(drive); 00676 00677 list.append(desc); 00678 LOG(VB_MEDIA, LOG_INFO, desc.prepend("Found CD/DVD: ")); 00679 CFRelease(p); 00680 } 00681 } 00682 else 00683 LOG(VB_GENERAL, LOG_ALERT, 00684 msg + "Could not retrieve drive properties"); 00685 00686 IOObjectRelease(drive); 00687 } 00688 00689 IOObjectRelease(iter); 00690 00691 return list; 00692 }
1.7.6.1