File indexing completed on 2024-05-12 04:59:44

0001 /*
0002     This file is part of the MTP KIOD module, part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2018 Andreas Krutzler <andreas.krutzler@gmx.net>
0005     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kmtpd.h"
0011 
0012 #include <chrono>
0013 
0014 #include <QDBusConnection>
0015 #include <QDebug>
0016 
0017 #include <KDirNotify>
0018 #include <KPluginFactory>
0019 #include <Solid/DeviceNotifier>
0020 #include <Solid/GenericInterface>
0021 
0022 #include "daemonadaptor.h"
0023 #include "kiod_kmtpd_debug.h"
0024 #include "mtpdevice.h"
0025 
0026 using namespace std::chrono_literals;
0027 
0028 K_PLUGIN_CLASS_WITH_JSON(KMTPd, "kmtpd.json")
0029 
0030 KMTPd::KMTPd(QObject *parent, const QList<QVariant> &parameters)
0031     : KDEDModule(parent)
0032 {
0033     Q_UNUSED(parameters)
0034 
0035     LIBMTP_Init();
0036 
0037     // search for already connected devices
0038     for (const Solid::Device &solidDevice : Solid::Device::listFromType(Solid::DeviceInterface::PortableMediaPlayer)) {
0039         checkDevice(solidDevice);
0040     }
0041 
0042     auto notifier = Solid::DeviceNotifier::instance();
0043     connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &KMTPd::deviceAdded);
0044     connect(notifier, &Solid::DeviceNotifier::deviceRemoved, this, &KMTPd::deviceRemoved);
0045 
0046     new DaemonAdaptor(this);
0047 }
0048 
0049 KMTPd::~KMTPd()
0050 {
0051     // Release devices
0052     for (const MTPDevice *device : qAsConst(m_devices)) {
0053         deviceRemoved(device->udi());
0054     }
0055 }
0056 
0057 QString KMTPd::version() const
0058 {
0059     return QStringLiteral(LIBMTP_VERSION_STRING);
0060 }
0061 
0062 void KMTPd::checkDevice(const Solid::Device &solidDevice)
0063 {
0064     if (!deviceFromUdi(solidDevice.udi())) {
0065         qCDebug(LOG_KIOD_KMTPD) << "new device, getting raw devices";
0066 
0067         const Solid::GenericInterface *iface = solidDevice.as<Solid::GenericInterface>();
0068         if (!iface) {
0069             qCDebug(LOG_KIOD_KMTPD) << "Solid device " << solidDevice.udi() << " has NOT a Solid::GenericInterface";
0070             return;
0071         }
0072 
0073         const QMap<QString, QVariant> &properties = iface->allProperties();
0074         const quint32 solidBusNum = properties.value(QStringLiteral("BUSNUM")).toUInt();
0075         const quint32 solidDevNum = properties.value(QStringLiteral("DEVNUM")).toUInt();
0076 
0077         LIBMTP_raw_device_t *rawdevices = nullptr;
0078         int numrawdevices;
0079         LIBMTP_error_number_t err;
0080 
0081         err = LIBMTP_Detect_Raw_Devices(&rawdevices, &numrawdevices);
0082         switch (err) {
0083         case LIBMTP_ERROR_CONNECTING:
0084             qCWarning(LOG_KIOD_KMTPD) << "There has been an error connecting to the devices";
0085             break;
0086         case LIBMTP_ERROR_MEMORY_ALLOCATION:
0087             qCWarning(LOG_KIOD_KMTPD) << "Encountered a Memory Allocation Error";
0088             break;
0089         case LIBMTP_ERROR_NONE: {
0090             qCDebug(LOG_KIOD_KMTPD) << "No Error, continuing";
0091 
0092             for (int i = 0; i < numrawdevices; i++) {
0093                 LIBMTP_raw_device_t *rawDevice = &rawdevices[i];
0094                 uint32_t rawBusNum = rawDevice->bus_location;
0095                 uint32_t rawDevNum = rawDevice->devnum;
0096 
0097                 if (rawBusNum == solidBusNum && rawDevNum == solidDevNum) {
0098                     qCDebug(LOG_KIOD_KMTPD) << "Found device matching the Solid description";
0099 
0100                     LIBMTP_mtpdevice_t *mtpDevice = LIBMTP_Open_Raw_Device_Uncached(rawDevice);
0101                     if (mtpDevice) {
0102                         auto device = new MTPDevice(QStringLiteral("/modules/kmtpd/device%1").arg(m_devices.count()), mtpDevice, rawDevice, solidDevice.udi());
0103 
0104                         // Always let MTPDevice know any changes to devices. It might be helpful Daemon interfaces
0105                         connect(this, &KMTPd::devicesChanged, device, [device] {
0106                             device->setDevicesUpdatedStatus(true);
0107                             org::kde::KDirNotify::emitFilesAdded(device->url());
0108                         });
0109                         org::kde::KDirNotify::emitFilesAdded(device->url()); // notify for the current change as well
0110 
0111                         m_devices.append(device);
0112                         Q_EMIT devicesChanged();
0113                     } else {
0114                         qCWarning(LOG_KIOD_KMTPD) << "LIBMTP_Open_Raw_Device_Uncached: Could not open MTP device";
0115                     }
0116                 }
0117             }
0118         } break;
0119         case LIBMTP_ERROR_GENERAL:
0120         default:
0121             qCWarning(LOG_KIOD_KMTPD) << "Unknown connection error";
0122             break;
0123         }
0124         free(rawdevices);
0125     }
0126 }
0127 
0128 MTPDevice *KMTPd::deviceFromUdi(const QString &udi) const
0129 {
0130     auto deviceIt = std::find_if(m_devices.constBegin(), m_devices.constEnd(), [udi](const MTPDevice *device) {
0131         return device->udi() == udi;
0132     });
0133 
0134     return deviceIt == m_devices.constEnd() ? nullptr : *deviceIt;
0135 }
0136 
0137 QList<QDBusObjectPath> KMTPd::listDevices() const
0138 {
0139     QList<QDBusObjectPath> list;
0140     for (const auto &device : m_devices) {
0141         list.append(QDBusObjectPath(device->dbusObjectName()));
0142     }
0143 
0144     return list;
0145 }
0146 
0147 void KMTPd::deviceAdded(const QString &udi)
0148 {
0149     qCDebug(LOG_KIOD_KMTPD) << "New device attached with udi=" << udi << ". Checking if PortableMediaPlayer...";
0150 
0151     const Solid::Device device(udi);
0152     if (device.isDeviceInterface(Solid::DeviceInterface::PortableMediaPlayer)) {
0153         qCDebug(LOG_KIOD_KMTPD) << "SOLID: New Device with udi=" << udi;
0154 
0155         org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("mtp:/")));
0156         checkDevice(device);
0157     }
0158 }
0159 
0160 void KMTPd::deviceRemoved(const QString &udi)
0161 {
0162     MTPDevice *device = deviceFromUdi(udi);
0163     if (device) {
0164         qCDebug(LOG_KIOD_KMTPD) << "SOLID: Device with udi=" << udi << " removed.";
0165 
0166         const QUrl url = device->url();
0167 
0168         // When allowing access on the Device side the device is briefly removed and then added again.
0169         // If we were to signal removal right away that would lead the KDirModel to leave the directory as it thinks
0170         // the dir no longer exists. To mitigate we delay the removal and if it was a real removal, rather than a
0171         // temporary remove-add dance, we'll emit it.
0172         QTimer::singleShot(5s, this, [this, udi, url] {
0173             if (!deviceFromUdi(udi)) {
0174                 qCDebug(LOG_KIOD_KMTPD) << "executing scheduled removal of " << udi;
0175                 org::kde::KDirNotify::emitFilesRemoved({url});
0176             }
0177         });
0178 
0179         Q_EMIT devicesChanged();
0180         m_devices.removeOne(device);
0181         delete device;
0182     }
0183 }
0184 
0185 #include "kmtpd.moc"
0186 #include "moc_kmtpd.cpp"