MythTV  0.26-pre
mythmedia.cpp
Go to the documentation of this file.
00001 // C header
00002 #include <fcntl.h>
00003 #include <unistd.h>
00004 #include <sys/types.h>
00005 #include <sys/stat.h>
00006 #include <sys/param.h>
00007 
00008 // Qt Headers
00009 #include <QDir>
00010 #include <QFileInfo>
00011 #include <QFileInfoList>
00012 #include <QTextStream>
00013 
00014 // MythTV headers
00015 #include "mythmedia.h"
00016 #include "mythconfig.h"
00017 #include "mythlogging.h"
00018 #include "mythmiscutil.h"
00019 #include "mythsystem.h"
00020 #include "exitcodes.h"
00021 
00022 using namespace std;
00023 
00024 #ifdef USING_MINGW
00025 #   define O_NONBLOCK 0
00026 #endif
00027 
00028 #define LOC QString("MythMediaDevice:")
00029 
00030 static const QString PATHTO_PMOUNT("/usr/bin/pmount");
00031 static const QString PATHTO_PUMOUNT("/usr/bin/pumount");
00032 #if CONFIG_DARWIN
00033     static const QString PATHTO_MOUNT("/sbin/mount");
00034 #else
00035     static const QString PATHTO_MOUNT("/bin/mount");
00036 #endif
00037 static const QString PATHTO_UNMOUNT("/bin/umount");
00038 static const QString PATHTO_MOUNTS("/proc/mounts");
00039 
00040 #if CONFIG_DARWIN
00041 #   define USE_MOUNT_COMMAND
00042 #endif
00043 
00044 const char* MythMediaDevice::MediaStatusStrings[] =
00045 {
00046     "MEDIASTAT_ERROR",
00047     "MEDIASTAT_UNKNOWN",
00048     "MEDIASTAT_UNPLUGGED",
00049     "MEDIASTAT_OPEN",
00050     "MEDIASTAT_NODISK",
00051     "MEDIASTAT_UNFORMATTED",
00052     "MEDIASTAT_USEABLE",
00053     "MEDIASTAT_NOTMOUNTED",
00054     "MEDIASTAT_MOUNTED"
00055 };
00056 
00057 const char* MythMediaDevice::MediaErrorStrings[] =
00058 {
00059     "MEDIAERR_OK",
00060     "MEDIAERR_FAILED",
00061     "MEDIAERR_UNSUPPORTED"
00062 };
00063 
00064 QEvent::Type MythMediaEvent::kEventType =
00065     (QEvent::Type) QEvent::registerEventType();
00066 
00067 MythMediaDevice::MythMediaDevice(QObject* par, const char* DevicePath,
00068                                  bool SuperMount,  bool AllowEject)
00069                : QObject(par)
00070 {
00071     m_DevicePath = DevicePath;
00072     m_AllowEject = AllowEject;
00073     m_Locked = false;
00074     m_DeviceHandle = -1;
00075     m_SuperMount = SuperMount;
00076     m_Status = MEDIASTAT_UNKNOWN;
00077     m_MediaType = MEDIATYPE_UNKNOWN;
00078     m_RealDevice = getSymlinkTarget(m_DevicePath);
00079 }
00080 
00081 bool MythMediaDevice::openDevice()
00082 {
00083     // Sanity check
00084     if (isDeviceOpen())
00085         return true;
00086 
00087     QByteArray dev = m_DevicePath.toLocal8Bit();
00088     m_DeviceHandle = open(dev.constData(), O_RDONLY | O_NONBLOCK);
00089 
00090     return isDeviceOpen();
00091 }
00092 
00093 bool MythMediaDevice::closeDevice()
00094 {
00095     // Sanity check
00096     if (!isDeviceOpen())
00097         return true;
00098 
00099     int ret = close(m_DeviceHandle);
00100     m_DeviceHandle = -1;
00101 
00102     return (ret != -1) ? true : false;
00103 }
00104 
00105 bool MythMediaDevice::isDeviceOpen() const
00106 {
00107     return (m_DeviceHandle >= 0) ? true : false;
00108 }
00109 
00110 bool MythMediaDevice::performMountCmd(bool DoMount)
00111 {
00112     if (DoMount && isMounted())
00113     {
00114 #ifdef Q_OS_MAC
00115         // Not an error - DiskArbitration has already mounted the device.
00116         // AddDevice calls mount() so onDeviceMounted() can get mediaType.
00117         onDeviceMounted();
00118 #else
00119         LOG(VB_MEDIA, LOG_ERR, "MythMediaDevice::performMountCmd(true)"
00120                                " - Logic Error? Device already mounted.");
00121         return true;
00122 #endif
00123     }
00124 
00125     if (isDeviceOpen())
00126         closeDevice();
00127 
00128     if (!m_SuperMount)
00129     {
00130         QString MountCommand;
00131 
00132         // Build a command line for mount/unmount and execute it...
00133         // Is there a better way to do this?
00134         if (QFile(PATHTO_PMOUNT).exists() && QFile(PATHTO_PUMOUNT).exists())
00135             MountCommand = QString("%1 %2")
00136                 .arg((DoMount) ? PATHTO_PMOUNT : PATHTO_PUMOUNT)
00137                 .arg(m_DevicePath);
00138         else
00139             MountCommand = QString("%1 %2")
00140                 .arg((DoMount) ? PATHTO_MOUNT : PATHTO_UNMOUNT)
00141                 .arg(m_DevicePath);
00142 
00143         LOG(VB_MEDIA, LOG_INFO, QString("Executing '%1'").arg(MountCommand));
00144         if (myth_system(MountCommand, kMSDontBlockInputDevs) == GENERIC_EXIT_OK)
00145         {
00146             if (DoMount)
00147             {
00148                 // we cannot tell beforehand what the pmount mount point is
00149                 // so verify the mount status of the device
00150                 if (!findMountPath())
00151                 {
00152                     LOG(VB_MEDIA, LOG_ERR, "performMountCmd() attempted to"
00153                                            " find mounted media, but failed?");
00154                     return false;
00155                 }
00156                 m_Status = MEDIASTAT_MOUNTED;
00157                 onDeviceMounted();
00158                 LOG(VB_GENERAL, LOG_INFO,
00159                         QString("Detected MediaType ") + MediaTypeString());
00160             }
00161             else
00162                 onDeviceUnmounted();
00163 
00164             return true;
00165         }
00166         else
00167             LOG(VB_GENERAL, LOG_ERR, QString("Failed to mount %1.")
00168                                        .arg(m_DevicePath));
00169     }
00170     else
00171     {
00172         LOG(VB_MEDIA, LOG_INFO, "Disk inserted on a supermount device");
00173         // If it's a super mount then the OS will handle mounting /  unmounting.
00174         // We just need to give derived classes a chance to perform their
00175         // mount / unmount logic.
00176         if (DoMount)
00177         {
00178             onDeviceMounted();
00179             LOG(VB_GENERAL, LOG_INFO,
00180                     QString("Detected MediaType ") + MediaTypeString());
00181         }
00182         else
00183             onDeviceUnmounted();
00184 
00185         return true;
00186     }
00187     return false;
00188 }
00189 
00193 MythMediaType MythMediaDevice::DetectMediaType(void)
00194 {
00195     MythMediaType mediatype = MEDIATYPE_UNKNOWN;
00196     ext_cnt_t ext_cnt;
00197 
00198     if (!ScanMediaType(m_MountPath, ext_cnt))
00199     {
00200         LOG(VB_MEDIA, LOG_NOTICE,
00201             QString("No files with extensions found in '%1'")
00202                 .arg(m_MountPath));
00203         return mediatype;
00204     }
00205 
00206     QMap<uint, uint> media_cnts, media_cnt;
00207 
00208     // convert raw counts to composite mediatype counts
00209     ext_cnt_t::const_iterator it = ext_cnt.begin();
00210     for (; it != ext_cnt.end(); ++it)
00211     {
00212         ext_to_media_t::const_iterator found = m_ext_to_media.find(it.key());
00213         if (found != m_ext_to_media.end())
00214             media_cnts[*found] += *it;
00215     }
00216 
00217     // break composite mediatypes into constituent components
00218     QMap<uint, uint>::const_iterator cit = media_cnts.begin();
00219     for (; cit != media_cnts.end(); ++cit)
00220     {
00221         for (uint key = 0, j = 0; key != MEDIATYPE_END; j++)
00222         {
00223             if ((key = 1 << j) & cit.key())
00224                 media_cnt[key] += *cit;
00225         }
00226     }
00227 
00228     // decide on mediatype based on which one has a handler for > # of files
00229     uint max_cnt = 0;
00230     for (cit = media_cnt.begin(); cit != media_cnt.end(); ++cit)
00231     {
00232         if (*cit > max_cnt)
00233         {
00234             mediatype = (MythMediaType) cit.key();
00235             max_cnt   = *cit;
00236         }
00237     }
00238 
00239     return mediatype;
00240 }
00241 
00246 bool MythMediaDevice::ScanMediaType(const QString &directory, ext_cnt_t &cnt)
00247 {
00248     QDir d(directory);
00249     if (!d.exists())
00250         return false;
00251 
00252 
00253     QFileInfoList list = d.entryInfoList();
00254 
00255     for( QFileInfoList::iterator it = list.begin();
00256                                  it != list.end();
00257                                ++it )
00258     {
00259         QFileInfo &fi = *it;
00260 
00261         if (("." == fi.fileName()) || (".." == fi.fileName()))
00262             continue;
00263 
00264         if (fi.isSymLink())
00265             continue;
00266 
00267         if (fi.isDir())
00268         {
00269             ScanMediaType(fi.absoluteFilePath(), cnt);
00270             continue;
00271         }
00272 
00273         const QString ext = fi.suffix();
00274         if (!ext.isEmpty())
00275             cnt[ext.toLower()]++;
00276     }
00277 
00278     return !cnt.empty();
00279 }
00280 
00287 void MythMediaDevice::RegisterMediaExtensions(uint mediatype,
00288                                               const QString &extensions)
00289 {
00290     const QStringList list = extensions.split(",");
00291     for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
00292         m_ext_to_media[*it] |= mediatype;
00293 }
00294 
00295 MythMediaError MythMediaDevice::eject(bool open_close)
00296 {
00297     (void) open_close;
00298 
00299 #if CONFIG_DARWIN
00300     // Backgrounding this is a bit naughty, but it can take up to five
00301     // seconds to execute, and freezing the frontend for that long is bad
00302 
00303     QString  command = "disktool -e " + m_DevicePath + " &";
00304 
00305     if (myth_system(command, kMSRunBackground) != GENERIC_EXIT_OK)
00306         return MEDIAERR_FAILED;
00307 
00308     return MEDIAERR_OK;
00309 #endif
00310 
00311     return MEDIAERR_UNSUPPORTED;
00312 }
00313 
00314 bool MythMediaDevice::isSameDevice(const QString &path)
00315 {
00316 #ifdef Q_OS_MAC
00317     // The caller may be using a raw device instead of the BSD 'leaf' name
00318     if (path == "/dev/r" + m_DevicePath)
00319         return true;
00320 #endif
00321 
00322     return (path == m_DevicePath);
00323 }
00324 
00325 void MythMediaDevice::setSpeed(int speed)
00326 {
00327     LOG(VB_MEDIA, LOG_ERR,
00328             QString("Cannot setSpeed(%1) for device %2 - not implemented.")
00329             .arg(speed).arg(m_DevicePath));
00330 }
00331 
00332 MythMediaError MythMediaDevice::lock()
00333 {
00334     // We just open the device here, which may or may not do the trick,
00335     // derived classes can do more...
00336     if (openDevice())
00337     {
00338         m_Locked = true;
00339         return MEDIAERR_OK;
00340     }
00341     m_Locked = false;
00342     return MEDIAERR_FAILED;
00343 }
00344 
00345 MythMediaError MythMediaDevice::unlock()
00346 {
00347     m_Locked = false;
00348 
00349     return MEDIAERR_OK;
00350 }
00351 
00353 bool MythMediaDevice::isMounted(bool Verify)
00354 {
00355     if (Verify)
00356         return findMountPath();
00357     else
00358         return (m_Status == MEDIASTAT_MOUNTED);
00359 }
00360 
00362 bool MythMediaDevice::findMountPath()
00363 {
00364     if (m_DevicePath.isEmpty())
00365     {
00366         LOG(VB_MEDIA, LOG_ERR, "findMountPath() - logic error, no device path");
00367         return false;
00368     }
00369 
00370 #ifdef USE_MOUNT_COMMAND
00371     // HACK. TODO: replace with something using popen()?
00372     if (myth_system(PATHTO_MOUNT + " > /tmp/mounts") != GENERIC_EXIT_OK)
00373         return false;
00374     QFile mountFile("/tmp/mounts");
00375 #else
00376     QFile mountFile(PATHTO_MOUNTS);
00377 #endif
00378 
00379     // Try to open the mounts file so we can search it for our device.
00380     if (!mountFile.open(QIODevice::ReadOnly))
00381         return false;
00382 
00383     QString     debug;
00384     QTextStream stream(&mountFile);
00385 
00386     for (;;)
00387     {
00388         QString mountPoint;
00389         QString deviceName;
00390 
00391 
00392 #ifdef USE_MOUNT_COMMAND
00393         // Extract mount point and device name from something like:
00394         //   /dev/disk0s3 on / (hfs, local, journaled)   - Mac OS X
00395         //   /dev/hdd on /tmp/AAA BBB type udf (ro)      - Linux
00396         stream >> deviceName;
00397         mountPoint = stream.readLine();
00398         mountPoint.remove(" on ");
00399         mountPoint.remove(QRegExp(" type \\w.*"));   // Linux
00400         mountPoint.remove(QRegExp(" \\(\\w.*"));     // Mac OS X
00401 #else
00402         // Extract the mount point and device name.
00403         stream >> deviceName >> mountPoint;
00404         stream.readLine(); // skip the rest of the line
00405 #endif
00406 
00407         if (deviceName.isNull())
00408             break;
00409 
00410         if (deviceName.isEmpty())
00411             continue;
00412 
00413         if (!deviceName.startsWith("/dev/"))
00414             continue;
00415 
00416         QStringList deviceNames;
00417         getSymlinkTarget(deviceName, &deviceNames);
00418 
00419 #if CONFIG_DARWIN
00420         // match short-style BSD node names:
00421         if (m_DevicePath.startsWith("disk"))
00422             deviceNames << deviceName.mid(5);   // remove 5 chars - /dev/
00423 #endif
00424 
00425         // Deal with escaped spaces
00426         if (mountPoint.contains("\\040"))
00427             mountPoint.replace("\\040", " ");
00428 
00429 
00430         if (deviceNames.contains(m_DevicePath) ||
00431             deviceNames.contains(m_RealDevice)  )
00432         {
00433             m_MountPath = mountPoint;
00434             mountFile.close();
00435             return true;
00436         }
00437 
00438         if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
00439             debug += QString("                 %1 | %2\n")
00440                      .arg(deviceName, 16).arg(mountPoint);
00441     }
00442 
00443     mountFile.close();
00444 
00445     if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
00446     {
00447         debug = LOC + ":findMountPath() - mount of '"
00448                 + m_DevicePath + "' not found.\n"
00449                 + "                 Device name/type | Current mountpoint\n"
00450                 + "                 -----------------+-------------------\n"
00451                 + debug
00452                 + "                 =================+===================";
00453         LOG(VB_MEDIA, LOG_DEBUG, debug);
00454     }
00455 
00456     return false;
00457 }
00458 
00459 MythMediaStatus MythMediaDevice::setStatus( MythMediaStatus NewStatus,
00460                                             bool CloseIt )
00461 {
00462     MythMediaStatus OldStatus = m_Status;
00463 
00464     m_Status = NewStatus;
00465 
00466     // If the status is changed we need to take some actions
00467     // depending on the old and new status.
00468     if (NewStatus != OldStatus)
00469     {
00470         switch (NewStatus)
00471         {
00472             // the disk is not / should not be mounted.
00473             case MEDIASTAT_ERROR:
00474             case MEDIASTAT_OPEN:
00475             case MEDIASTAT_NODISK:
00476             case MEDIASTAT_NOTMOUNTED:
00477                 if (isMounted())
00478                     unmount();
00479                 break;
00480             case MEDIASTAT_UNKNOWN:
00481             case MEDIASTAT_USEABLE:
00482             case MEDIASTAT_MOUNTED:
00483             case MEDIASTAT_UNPLUGGED:
00484                 // get rid of the compiler warning...
00485                 break;
00486         }
00487 
00488         // Don't fire off transitions to / from unknown states
00489         if (m_Status != MEDIASTAT_UNKNOWN && OldStatus != MEDIASTAT_UNKNOWN)
00490             emit statusChanged(OldStatus, this);
00491     }
00492 
00493 
00494     if (CloseIt)
00495         closeDevice();
00496 
00497     return m_Status;
00498 }
00499 
00500 void MythMediaDevice::clearData()
00501 {
00502     m_VolumeID = QString::null;
00503     m_KeyID = QString::null;
00504     m_MediaType = MEDIATYPE_UNKNOWN;
00505 }
00506 
00507 const char* MythMediaDevice::MediaTypeString()
00508 {
00509     return MediaTypeString(m_MediaType);
00510 }
00511 
00512 const char* MythMediaDevice::MediaTypeString(MythMediaType type)
00513 {
00514     // MythMediaType is currently a bitmask.  This code will only output the 
00515     // first matched type.
00516 
00517     if (type == MEDIATYPE_UNKNOWN)
00518         return "MEDIATYPE_UNKNOWN";
00519     if (type & MEDIATYPE_DATA)
00520         return "MEDIATYPE_DATA";
00521     if (type & MEDIATYPE_MIXED)
00522         return "MEDIATYPE_MIXED";
00523     if (type & MEDIATYPE_AUDIO)
00524         return "MEDIATYPE_AUDIO";
00525     if (type & MEDIATYPE_DVD)
00526         return "MEDIATYPE_DVD";
00527     if (type & MEDIATYPE_BD)
00528         return "MEDIATYPE_BD";
00529     if (type & MEDIATYPE_VCD)
00530         return "MEDIATYPE_VCD";
00531     if (type & MEDIATYPE_MMUSIC)
00532         return "MEDIATYPE_MMUSIC";
00533     if (type & MEDIATYPE_MVIDEO)
00534         return "MEDIATYPE_MVIDEO";
00535     if (type & MEDIATYPE_MGALLERY)
00536         return "MEDIATYPE_MGALLERY";
00537 
00538     return "MEDIATYPE_UNKNOWN";
00539 }
00540 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends