diff --git a/mythtv/libs/libmyth/libmyth.pro b/mythtv/libs/libmyth/libmyth.pro index 9b0ad2fc87b..3dfc720d1ea 100644 --- a/mythtv/libs/libmyth/libmyth.pro +++ b/mythtv/libs/libmyth/libmyth.pro @@ -144,6 +144,7 @@ using_pulse { unix:!cygwin { SOURCES += mediamonitor-unix.cpp HEADERS += mediamonitor-unix.h + using_qtdbus: CONFIG += qdbus } linux:DEFINES += linux diff --git a/mythtv/libs/libmyth/mediamonitor-unix.cpp b/mythtv/libs/libmyth/mediamonitor-unix.cpp index 042e98ec07b..9a5b7beb05a 100644 --- a/mythtv/libs/libmyth/mediamonitor-unix.cpp +++ b/mythtv/libs/libmyth/mediamonitor-unix.cpp @@ -1,4 +1,5 @@ // -*- Mode: c++ -*- +#include "config.h" // Standard C headers #include @@ -22,6 +23,10 @@ using namespace std; // Qt headers +#if CONFIG_QTDBUS +#include +#include +#endif #include #include #include @@ -66,6 +71,16 @@ extern "C" { #endif #define SUPER_OPT_DEV "dev=" +#if CONFIG_QTDBUS +// DBus UDisk service - http://hal.freedesktop.org/docs/udisks/ +#define UDISKS_SVC "org.freedesktop.UDisks" +#define UDISKS_PATH "/org/freedesktop/UDisks" +#define UDISKS_IFACE "org.freedesktop.UDisks" +#define UDISKS_DEVADD "DeviceAdded" +#define UDISKS_DEVRMV "DeviceRemoved" +#define UDISKS_DEVSIG "o" // OBJECT_PATH +#endif + const char * MediaMonitorUnix::kUDEV_FIFO = "/tmp/mythtv_media"; @@ -91,7 +106,7 @@ static void statError(const QString &methodName, const QString devPath) MediaMonitorUnix::MediaMonitorUnix(QObject* par, unsigned long interval, bool allowEject) - : MediaMonitor(par, interval, allowEject) + : MediaMonitor(par, interval, allowEject), m_fifo(-1) { CheckFileSystemTable(); CheckMountable(); @@ -100,6 +115,7 @@ MediaMonitorUnix::MediaMonitorUnix(QObject* par, } +#if !CONFIG_QTDBUS void MediaMonitorUnix::deleteLater(void) { if (m_fifo >= 0) @@ -110,6 +126,7 @@ void MediaMonitorUnix::deleteLater(void) } MediaMonitor::deleteLater(); } +#endif // !CONFIG_QTDBUS // Loop through the file system table and add any supported devices. @@ -136,6 +153,21 @@ bool MediaMonitorUnix::CheckFileSystemTable(void) return true; } +#if CONFIG_QTDBUS +// Get a device property by name +static QVariant DeviceProperty(const QDBusObjectPath& o, const char kszProperty[]) +{ + QVariant v; + + QDBusInterface iface(UDISKS_SVC, o.path(), UDISKS_IFACE".Device", + QDBusConnection::systemBus() ); + if (iface.isValid()) + v = iface.property(kszProperty); + + return v; +} +#endif + /** * \brief Search /sys/block for valid removable media devices. * @@ -149,7 +181,63 @@ bool MediaMonitorUnix::CheckFileSystemTable(void) */ bool MediaMonitorUnix::CheckMountable(void) { -#ifdef linux +#if CONFIG_QTDBUS + // Listen on DBus for UDisk add/remove device messages + QDBusConnection::systemBus().connect( + UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, UDISKS_DEVADD, UDISKS_DEVSIG, + this, SLOT(deviceAdded(QDBusObjectPath)) ); + QDBusConnection::systemBus().connect( + UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, UDISKS_DEVRMV, UDISKS_DEVSIG, + this, SLOT(deviceRemoved(QDBusObjectPath)) ); + + // Connect to UDisks + QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, + QDBusConnection::systemBus() ); + if (!iface.isValid()) + { + VERBOSE(VB_IMPORTANT, LOC + ":CheckMountable: DBus interface error: " + + iface.lastError().message() ); + return false; + } + + // Enumerate devices + typedef QList QDBusObjectPathList; + QDBusReply reply = iface.call("EnumerateDevices"); + if (!reply.isValid()) + { + VERBOSE(VB_IMPORTANT, LOC + ":CheckMountable DBus EnumerateDevices error: " + + reply.error().message() ); + return false; + } + + // Parse the returned device array + const QDBusObjectPathList& list(reply.value()); + for (QDBusObjectPathList::const_iterator it = list.begin(); + it != list.end(); ++it) + { + if (!DeviceProperty(*it, "DeviceIsSystemInternal").toBool() && + !DeviceProperty(*it, "DeviceIsPartitionTable").toBool() ) + { + QString dev = DeviceProperty(*it, "DeviceFile").toString(); + + // ignore floppies, too slow + if (dev.startsWith("/dev/fd")) + continue; + + MythMediaDevice* pDevice; + if (DeviceProperty(*it, "DeviceIsRemovable").toBool()) + pDevice = MythCDROM::get(this, dev.toAscii(), false, m_AllowEject); + else + pDevice = MythHDD::Get(this, dev.toAscii(), false, false); + + if (pDevice && !AddDevice(pDevice)) + pDevice->deleteLater(); + } + } + return true; + +#elif defined linux + // NB needs script in /etc/udev/rules.d mkfifo(kUDEV_FIFO, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); m_fifo = open(kUDEV_FIFO, O_RDONLY | O_NONBLOCK); @@ -174,11 +262,12 @@ bool MediaMonitorUnix::CheckMountable(void) sysfs.cdUp(); } return true; -#else // if !linux +#else // linux return false; -#endif // !linux +#endif } +#if !CONFIG_QTDBUS /** * \brief Is /sys/block/dev a removable device? */ @@ -227,7 +316,7 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs) #ifdef linux #if HAVE_LIBUDEV // Use libudev to determine the name - ret = ""; + ret.clear(); struct udev *udev = udev_new(); if (udev != NULL) { @@ -239,11 +328,27 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs) if (name != NULL) ret = tr(name); + else + { + // This can happen when udev sends an AddDevice for a block + // device with partitions. FindPartition locates a partition + // in sysfs but udev hasn't created the devnode for it yet. + // Udev will send another AddDevice for the partition later. + VERBOSE(VB_MEDIA+VB_EXTRA, msg + " devnode not (yet) known"); + } udev_device_unref(device); } + else + { + VERBOSE(VB_IMPORTANT, msg + " udev_device_new_from_syspath returned NULL"); + ret = ""; + } + udev_unref(udev); } + else + VERBOSE(VB_IMPORTANT, "MediaMonitorUnix::GetDeviceFile udev_new failed"); #else // HAVE_LIBUDEV // Use udevadm info to determine the name QStringList args; @@ -271,12 +376,9 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs) } QTextStream ostream(udevinfo->ReadAll()); - ret = ostream.readLine(); - if( ret.startsWith("device not found in database") ) - { - delete udevinfo; - return ret; - } + QString udevLine = ostream.readLine(); + if (!udevLine.startsWith("device not found in database") ) + ret = udevLine; delete udevinfo; #endif // HAVE_LIBUDEV @@ -285,16 +387,43 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs) VERBOSE(VB_MEDIA, msg + "->'" + ret + "'"); return ret; } +#endif // !CONFIG_QTDBUS /* * \brief Reads the list devices known to be CD or DVD devices. * \return list of CD and DVD device names. */ +// pure virtual QStringList MediaMonitorUnix::GetCDROMBlockDevices(void) { QStringList l; -#ifdef linux +#if CONFIG_QTDBUS + QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, + QDBusConnection::systemBus() ); + if (iface.isValid()) + { + // Enumerate devices + typedef QList QDBusObjectPathList; + QDBusReply reply = iface.call("EnumerateDevices"); + if (reply.isValid()) + { + const QDBusObjectPathList& list(reply.value()); + for (QDBusObjectPathList::const_iterator it = list.begin(); + it != list.end(); ++it) + { + if (DeviceProperty(*it, "DeviceIsRemovable").toBool()) + { + QString dev = DeviceProperty(*it, "DeviceFile").toString(); + if (dev.startsWith("/dev/")) + dev.remove(0,5); + l.push_back(dev); + } + } + } + } + +#elif defined linux QFile file("/proc/sys/dev/cdrom/info"); if (file.open(QIODevice::ReadOnly)) { @@ -315,21 +444,36 @@ QStringList MediaMonitorUnix::GetCDROMBlockDevices(void) } #endif // linux - VERBOSE(VB_MEDIA, LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'"); + VERBOSE(VB_MEDIA+VB_EXTRA, + LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'"); return l; } static void LookupModel(MythMediaDevice* device) { QString desc; - QString devname = device->getRealDevice(); +#if CONFIG_QTDBUS + QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, + QDBusConnection::systemBus() ); + if (iface.isValid()) + { + QDBusReply reply = iface.call( + "FindDeviceByDeviceFile", device->getRealDevice()); + if (reply.isValid()) + { + desc = DeviceProperty(reply, "DriveVendor").toString(); + if (!desc.isEmpty()) + desc += " "; + desc += DeviceProperty(reply, "DriveModel").toString(); + } + } + +#elif defined linux // Given something like /dev/hda1, extract hda1 - devname = devname.mid(5,5); + QString devname = device->getRealDevice().mid(5,5); - -#ifdef linux if (devname.startsWith("hd")) // IDE drive { QFile file("/proc/ide/" + devname.left(3) + "/model"); @@ -373,6 +517,8 @@ static void LookupModel(MythMediaDevice* device) } #endif + VERBOSE(VB_MEDIA+VB_EXTRA, QString("LookupModel '%1' -> '%2'"). + arg(device->getRealDevice()).arg(desc) ); device->setDeviceModel(desc.toAscii().constData()); } @@ -534,6 +680,49 @@ bool MediaMonitorUnix::AddDevice(struct fstab * mep) return false; } +#if CONFIG_QTDBUS +/* + * DBus UDisk AddDevice handler + */ +void MediaMonitorUnix::deviceAdded( QDBusObjectPath o) +{ + VERBOSE(VB_MEDIA, LOC + ":deviceAdded " + o.path()); + + // Don't add devices with partition tables, just the partitions + if (!DeviceProperty(o, "DeviceIsPartitionTable").toBool()) + { + QString dev = DeviceProperty(o, "DeviceFile").toString(); + + MythMediaDevice* pDevice; + if (DeviceProperty(o, "DeviceIsRemovable").toBool()) + pDevice = MythCDROM::get(this, dev.toAscii(), false, m_AllowEject); + else + pDevice = MythHDD::Get(this, dev.toAscii(), false, false); + + if (pDevice && !AddDevice(pDevice)) + pDevice->deleteLater(); + } +} + +/* + * DBus UDisk RemoveDevice handler + */ +void MediaMonitorUnix::deviceRemoved( QDBusObjectPath o) +{ + VERBOSE(VB_MEDIA, LOC + "deviceRemoved " + o.path()); +#if 0 // This fails because the DeviceFile has just been deleted + QString dev = DeviceProperty(o, "DeviceFile"); + if (!dev.isEmpty()) + RemoveDevice(dev); +#else + QString dev = QFileInfo(o.path()).baseName(); + dev.prepend("/dev/"); + RemoveDevice(dev); +#endif +} + +#else //CONFIG_QTDBUS + /** * \brief Creates MythMedia instances for sysfs removable media devices. * @@ -552,6 +741,8 @@ bool MediaMonitorUnix::AddDevice(struct fstab * mep) */ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions) { + VERBOSE(VB_MEDIA+VB_EXTRA, LOC + ":FindPartitions(" + dev + QString(",%1"). + arg(checkPartitions ? " true" : " false" ) + ")"); MythMediaDevice* pDevice = NULL; if (checkPartitions) @@ -570,7 +761,8 @@ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions) // skip some sysfs dirs that are _not_ sub-partitions if (*pit == "device" || *pit == "holders" || *pit == "queue" - || *pit == "slaves" || *pit == "subsystem") + || *pit == "slaves" || *pit == "subsystem" + || *pit == "bdi" || *pit == "power") continue; found_partitions |= FindPartitions( @@ -586,7 +778,7 @@ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions) QString device_file = GetDeviceFile(dev); - if (device_file.isNull()) + if (device_file.isEmpty()) return false; QStringList cdroms = GetCDROMBlockDevices(); @@ -621,9 +813,9 @@ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions) void MediaMonitorUnix::CheckDeviceNotifications(void) { char buffer[256]; - QString qBuffer = ""; + QString qBuffer; - if (!m_fifo) + if (m_fifo == -1) return; int size = read(m_fifo, buffer, 255); @@ -642,13 +834,18 @@ void MediaMonitorUnix::CheckDeviceNotifications(void) if ((*it).startsWith("add")) { QString dev = (*it).section(' ', 1, 1); + VERBOSE(VB_MEDIA, "Udev add " + dev); if (CheckRemovable(dev)) FindPartitions(dev, true); } else if ((*it).startsWith("remove")) { - RemoveDevice((*it).section(' ', 2, 2)); + QString dev = (*it).section(' ', 2, 2); + VERBOSE(VB_MEDIA, "Udev remove " + dev); + RemoveDevice(dev); } } } +#endif //!CONFIG_QTDBUS + diff --git a/mythtv/libs/libmyth/mediamonitor-unix.h b/mythtv/libs/libmyth/mediamonitor-unix.h index 706685879b2..0987617d996 100644 --- a/mythtv/libs/libmyth/mediamonitor-unix.h +++ b/mythtv/libs/libmyth/mediamonitor-unix.h @@ -4,28 +4,51 @@ #define DEFAULT_DVD "/dev/dvd" #define DEFAULT_CD "/dev/cdrom" +#include "config.h" + +#include +#if CONFIG_QTDBUS +#include +#endif + +#include "mythmediamonitor.h" + class MediaMonitorUnix : public MediaMonitor { +#if CONFIG_QTDBUS + Q_OBJECT + public slots: + Q_NOREPLY void deviceAdded(QDBusObjectPath); + Q_NOREPLY void deviceRemoved(QDBusObjectPath); +#endif + public: MediaMonitorUnix(QObject *par, unsigned long interval, bool allowEject); +#if !CONFIG_QTDBUS virtual void deleteLater(void); +#endif protected: ~MediaMonitorUnix() {} - void CheckDevices(void); - void CheckDeviceNotifications(void); +#if !CONFIG_QTDBUS + virtual void CheckDeviceNotifications(void); +#endif bool CheckFileSystemTable(void); bool CheckMountable(void); +#if !CONFIG_QTDBUS bool CheckRemovable(const QString &dev); bool FindPartitions(const QString &dev, bool checkPartitions); +#endif virtual bool AddDevice(MythMediaDevice* pDevice); bool AddDevice(struct fstab* mep); +#if !CONFIG_QTDBUS QString GetDeviceFile(const QString &sysfs); +#endif - QStringList GetCDROMBlockDevices(void); + virtual QStringList GetCDROMBlockDevices(void); protected: int m_fifo;