diff --git a/kio/kiokdeconnect.cpp b/kio/kiokdeconnect.cpp index 7e9f6c99..37127da4 100644 --- a/kio/kiokdeconnect.cpp +++ b/kio/kiokdeconnect.cpp @@ -1,246 +1,246 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kiokdeconnect.h" #include #include #include #include #include Q_LOGGING_CATEGORY(KDECONNECT_KIO, "kdeconnect.kio") class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.kdeconnect" FILE "kdeconnect.json") }; extern "C" int Q_DECL_EXPORT kdemain(int argc, char** argv) { QCoreApplication app(argc, argv); app.setApplicationName(QStringLiteral("kio_kdeconnect")); if (argc != 4) { fprintf(stderr, "Usage: kio_kdeconnect protocol pool app\n"); exit(-1); } KioKdeconnect slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } //Some useful error mapping KIO::Error toKioError(const QDBusError::ErrorType type) { switch (type) { case QDBusError::NoError: return KIO::Error(KJob::NoError); case QDBusError::NoMemory: return KIO::ERR_OUT_OF_MEMORY; case QDBusError::Timeout: return KIO::ERR_SERVER_TIMEOUT; case QDBusError::TimedOut: return KIO::ERR_SERVER_TIMEOUT; default: return KIO::ERR_INTERNAL; }; }; template bool handleDBusError(QDBusReply& reply, KIO::SlaveBase* slave) { if (!reply.isValid()) { qCDebug(KDECONNECT_KIO) << "Error in DBus request:" << reply.error(); slave->error(toKioError(reply.error().type()),reply.error().message()); return true; } return false; } KioKdeconnect::KioKdeconnect(const QByteArray& pool, const QByteArray& app) : SlaveBase("kdeconnect", pool, app), m_dbusInterface(new DaemonDbusInterface(this)) { } void KioKdeconnect::listAllDevices() { infoMessage(i18n("Listing devices...")); //TODO: Change to all devices and show different icons for connected and disconnected? const QStringList devices = m_dbusInterface->devices(true, true); for (const QString& deviceId : devices) { DeviceDbusInterface interface(deviceId); if (!interface.hasPlugin(QStringLiteral("kdeconnect_sftp"))) continue; const QString path = QStringLiteral("kdeconnect://").append(deviceId).append("/"); const QString name = interface.name(); const QString icon = QStringLiteral("kdeconnect"); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, name); entry.insert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); entry.insert(KIO::UDSEntry::UDS_URL, path); listEntry(entry); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_SIZE, 0); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); listEntry(entry); infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDevice() { infoMessage(i18n("Accessing device...")); qCDebug(KDECONNECT_KIO) << "ListDevice" << m_currentDevice; SftpDbusInterface interface(m_currentDevice); QDBusReply mountreply = interface.mountAndWait(); if (handleDBusError(mountreply, this)) { return; } if (!mountreply.value()) { - error(KIO::ERR_COULD_NOT_MOUNT, i18n("Could not mount device filesystem")); + error(KIO::ERR_COULD_NOT_MOUNT, interface.getMountError()); return; } QDBusReply< QVariantMap > urlreply = interface.getDirectories(); if (handleDBusError(urlreply, this)) { return; } QVariantMap urls = urlreply.value(); for (QVariantMap::iterator it = urls.begin(); it != urls.end(); ++it) { const QString path = it.key(); const QString name = it.value().toString(); const QString icon = QStringLiteral("folder"); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, name); entry.insert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); entry.insert(KIO::UDSEntry::UDS_URL, QUrl::fromLocalFile(path).toString()); listEntry(entry); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_SIZE, 0); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); listEntry(entry); infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDir(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Listing..." << url; /// Url is not used here because all we could care about the url is the host, and that's already /// handled in @p setHost Q_UNUSED(url); if (!m_dbusInterface->isValid()) { infoMessage(i18n("Could not contact background service.")); finished(); return; } if (m_currentDevice.isEmpty()) { listAllDevices(); } else { listDevice(); } } void KioKdeconnect::stat(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Stat: " << url; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); statEntry(entry); finished(); } void KioKdeconnect::get(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Get: " << url; mimeType(QLatin1String("")); finished(); } void KioKdeconnect::setHost(const QString& hostName, quint16 port, const QString& user, const QString& pass) { //This is called before everything else to set the file we want to show qCDebug(KDECONNECT_KIO) << "Setting host: " << hostName; // In this kio only the hostname is used Q_UNUSED(port) Q_UNUSED(user) Q_UNUSED(pass) m_currentDevice = hostName; } //needed for JSON file embedding #include "kiokdeconnect.moc" diff --git a/plugins/sftp/mounter.cpp b/plugins/sftp/mounter.cpp index 3d3526ed..5480f4ca 100644 --- a/plugins/sftp/mounter.cpp +++ b/plugins/sftp/mounter.cpp @@ -1,248 +1,253 @@ /** * Copyright 2014 Samoilenko Yuri * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mounter.h" #include #include #include #include #include "mountloop.h" #include "config-sftp.h" #include "sftp_debug.h" #include "kdeconnectconfig.h" Mounter::Mounter(SftpPlugin* sftp) : QObject(sftp) , m_sftp(sftp) , m_proc(nullptr) , m_mountPoint(sftp->mountPoint()) , m_started(false) { connect(m_sftp, &SftpPlugin::packetReceived, this, &Mounter::onPackageReceived); connect(&m_connectTimer, &QTimer::timeout, this, &Mounter::onMountTimeout); connect(this, &Mounter::mounted, &m_connectTimer, &QTimer::stop); connect(this, &Mounter::failed, &m_connectTimer, &QTimer::stop); m_connectTimer.setInterval(10000); m_connectTimer.setSingleShot(true); QTimer::singleShot(0, this, &Mounter::start); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created mounter"; } Mounter::~Mounter() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Destroy mounter"; unmount(false); } bool Mounter::wait() { if (m_started) { return true; } qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting loop to wait for mount"; MountLoop loop; connect(this, &Mounter::mounted, &loop, &MountLoop::successed); connect(this, &Mounter::failed, &loop, &MountLoop::failed); return loop.exec(); } void Mounter::onPackageReceived(const NetworkPacket& np) { if (np.get(QStringLiteral("stop"), false)) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "SFTP server stopped"; unmount(false); return; } + + if (np.has("errorMessage")) { + Q_EMIT failed(np.get("errorMessage", "")); + return; + } //This is the previous code, to access sftp server using KIO. Now we are //using the external binary sshfs, and accessing it as a local filesystem. /* * QUrl url; * url.setScheme("sftp"); * url.setHost(np.get("ip")); * url.setPort(np.get("port").toInt()); * url.setUserName(np.get("user")); * url.setPassword(np.get("password")); * url.setPath(np.get("path")); * new KRun(url, 0); * Q_EMIT mounted(); */ unmount(false); m_proc = new KProcess(); m_proc->setOutputChannelMode(KProcess::MergedChannels); connect(m_proc, &QProcess::started, this, &Mounter::onStarted); connect(m_proc, SIGNAL(error(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError))); connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(onFinished(int,QProcess::ExitStatus))); QDir().mkpath(m_mountPoint); const QString program = QStringLiteral("sshfs"); QString path; if (np.has(QStringLiteral("multiPaths"))) path = '/'; else path = np.get(QStringLiteral("path")); QHostAddress addr = m_sftp->device()->getLocalIpAddress(); if (addr == QHostAddress::Null) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Device doesn't have a LanDeviceLink, unable to get IP address"; return; } QString ip = addr.toString(); const QStringList arguments = QStringList() << QStringLiteral("%1@%2:%3").arg( np.get(QStringLiteral("user")), ip, path) << m_mountPoint << QStringLiteral("-p") << np.get(QStringLiteral("port")) << QStringLiteral("-s") // This fixes a bug where file chunks are sent out of order and get corrupted on reception << QStringLiteral("-f") << QStringLiteral("-F") << QStringLiteral("/dev/null") //Do not use ~/.ssh/config << QStringLiteral("-o") << "IdentityFile=" + KdeConnectConfig::instance()->privateKeyPath() << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no") //Do not ask for confirmation because it is not a known host << QStringLiteral("-o") << QStringLiteral("UserKnownHostsFile=/dev/null") //Prevent storing as a known host << QStringLiteral("-o") << QStringLiteral("HostKeyAlgorithms=+ssh-dss") //https://bugs.kde.org/show_bug.cgi?id=351725 << QStringLiteral("-o") << QStringLiteral("uid=") + QString::number(getuid()) << QStringLiteral("-o") << QStringLiteral("gid=") + QString::number(getgid()) << QStringLiteral("-o") << QStringLiteral("reconnect") << QStringLiteral("-o") << QStringLiteral("ServerAliveInterval=30") << QStringLiteral("-o") << QStringLiteral("password_stdin") ; m_proc->setProgram(program, arguments); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting process: " << m_proc->program().join(QStringLiteral(" ")); m_proc->start(); //qCDebug(KDECONNECT_PLUGIN_SFTP) << "Passing password: " << np.get("password").toLatin1(); m_proc->write(np.get(QStringLiteral("password")).toLatin1()); m_proc->write("\n"); } void Mounter::onStarted() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process started"; m_started = true; Q_EMIT mounted(); //m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out"); //m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err"); auto proc = m_proc; connect(m_proc, &KProcess::readyReadStandardError, [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << proc->readAll(); }); connect(m_proc, &KProcess::readyReadStandardOutput, [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << proc->readAll(); }); } void Mounter::onError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed to start"; m_started = false; Q_EMIT failed(i18n("Failed to start sshfs")); } } void Mounter::onFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process finished (exit code: " << exitCode << ")"; Q_EMIT unmounted(); } else { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed (exit code:" << exitCode << ")"; Q_EMIT failed(i18n("Error when accessing to filesystem")); } unmount(true); } void Mounter::onMountTimeout() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Timeout: device not responding"; Q_EMIT failed(i18n("Failed to mount filesystem: device not responding")); } void Mounter::start() { NetworkPacket np(PACKET_TYPE_SFTP_REQUEST, {{"startBrowsing", true}}); m_sftp->sendPacket(np); m_connectTimer.start(); } void Mounter::unmount(bool finished) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Unmount" << m_proc; if (m_proc) { if (!finished) { //Process is still running, we want to stop it //But when the finished signal come, we might have already gone. //Disconnect everything. m_proc->disconnect(); m_proc->kill(); auto proc = m_proc; m_proc = nullptr; connect(proc, static_cast(&QProcess::finished), [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Free" << proc; proc->deleteLater(); }); Q_EMIT unmounted(); } else m_proc->deleteLater(); //Free mount point (won't always succeed if the path is in use) #if defined(HAVE_FUSERMOUNT) KProcess::execute(QStringList() << QStringLiteral("fusermount") << QStringLiteral("-u") << m_mountPoint, 10000); #else KProcess::execute(QStringList() << QStringLiteral("umount") << m_mountPoint, 10000); #endif m_proc = nullptr; } m_started = false; } diff --git a/plugins/sftp/sftpplugin.cpp b/plugins/sftp/sftpplugin.cpp index 7ab6e5ea..60825362 100644 --- a/plugins/sftp/sftpplugin.cpp +++ b/plugins/sftp/sftpplugin.cpp @@ -1,193 +1,207 @@ /** * Copyright 2014 Samoilenko Yuri * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "sftpplugin.h" #include #include #include #include #include #include #include #include #include #include "mounter.h" #include "sftp_debug.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_sftp.json", registerPlugin< SftpPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SFTP, "kdeconnect.plugin.sftp") static const QSet fields_c = QSet() << QStringLiteral("ip") << QStringLiteral("port") << QStringLiteral("user") << QStringLiteral("port") << QStringLiteral("path"); struct SftpPlugin::Pimpl { Pimpl() : m_mounter(nullptr) {} //Add KIO entry to Dolphin's Places KFilePlacesModel m_placesModel; Mounter* m_mounter; }; SftpPlugin::SftpPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , d(new Pimpl()) { deviceId = device()->id(); addToDolphin(); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created device:" << device()->name(); } SftpPlugin::~SftpPlugin() { QDBusConnection::sessionBus().unregisterObject(dbusPath(), QDBusConnection::UnregisterTree); removeFromDolphin(); unmount(); } void SftpPlugin::addToDolphin() { removeFromDolphin(); QUrl kioUrl("kdeconnect://"+deviceId+"/"); d->m_placesModel.addPlace(device()->name(), kioUrl, QStringLiteral("kdeconnect")); qCDebug(KDECONNECT_PLUGIN_SFTP) << "add to dolphin"; } void SftpPlugin::removeFromDolphin() { QUrl kioUrl("kdeconnect://"+deviceId+"/"); QModelIndex index = d->m_placesModel.closestItem(kioUrl); while (index.row() != -1) { d->m_placesModel.removePlace(index); index = d->m_placesModel.closestItem(kioUrl); } } void SftpPlugin::mount() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Mount device:" << device()->name(); if (d->m_mounter) { return; } d->m_mounter = new Mounter(this); connect(d->m_mounter, &Mounter::mounted, this, &SftpPlugin::onMounted); connect(d->m_mounter, &Mounter::unmounted, this, &SftpPlugin::onUnmounted); connect(d->m_mounter, &Mounter::failed, this, &SftpPlugin::onFailed); } void SftpPlugin::unmount() { if (d->m_mounter) { d->m_mounter->deleteLater(); d->m_mounter = nullptr; } } bool SftpPlugin::mountAndWait() { mount(); return d->m_mounter->wait(); } bool SftpPlugin::isMounted() const { return d->m_mounter && d->m_mounter->isMounted(); } +QString SftpPlugin::getMountError() +{ + if (!mountError.isEmpty()) { + return mountError; + } else { + return i18n("Could not mount device filesystem"); + } +} + + bool SftpPlugin::startBrowsing() { if (mountAndWait()) { //return new KRun(QUrl::fromLocalFile(mountPoint()), 0); return new KRun(QUrl("kdeconnect://"+deviceId), nullptr); } return false; } bool SftpPlugin::receivePacket(const NetworkPacket& np) { - if (!(fields_c - np.body().keys().toSet()).isEmpty()) { + if (!(fields_c - np.body().keys().toSet()).isEmpty() && !np.has("errorMessage")) { // packet is invalid return false; } Q_EMIT packetReceived(np); remoteDirectories.clear(); if (np.has(QStringLiteral("multiPaths"))) { QStringList paths = np.get(QStringLiteral("multiPaths"),QStringList()); QStringList names = np.get(QStringLiteral("pathNames"),QStringList()); int size = qMin(names.size(), paths.size()); for (int i = 0; i < size; i++) { remoteDirectories.insert(mountPoint() + paths.at(i), names.at(i)); } } else { remoteDirectories.insert(mountPoint(), i18n("All files")); remoteDirectories.insert(mountPoint() + "/DCIM/Camera", i18n("Camera pictures")); } + + if (np.has("errorMessage")) { + mountError = np.get("errorMessage"); + } return true; } QString SftpPlugin::mountPoint() { QString runtimePath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); if (runtimePath.isEmpty()) { runtimePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); } return QDir(runtimePath).absoluteFilePath(deviceId); } void SftpPlugin::onMounted() { qCDebug(KDECONNECT_PLUGIN_SFTP) << device()->name() << QStringLiteral("Remote filesystem mounted at %1").arg(mountPoint()); Q_EMIT mounted(); } void SftpPlugin::onUnmounted() { qCDebug(KDECONNECT_PLUGIN_SFTP) << device()->name() << "Remote filesystem unmounted"; unmount(); Q_EMIT unmounted(); } void SftpPlugin::onFailed(const QString& message) { KNotification::event(KNotification::Error, device()->name(), message); unmount(); Q_EMIT unmounted(); } QVariantMap SftpPlugin::getDirectories() { return remoteDirectories; } #include "sftpplugin.moc" diff --git a/plugins/sftp/sftpplugin.h b/plugins/sftp/sftpplugin.h index 32ca85be..c0881c68 100644 --- a/plugins/sftp/sftpplugin.h +++ b/plugins/sftp/sftpplugin.h @@ -1,77 +1,79 @@ /** * Copyright 2014 Samoilenko Yuri * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SFTPPLUGIN_H #define SFTPPLUGIN_H #include #include #define PACKET_TYPE_SFTP_REQUEST QStringLiteral("kdeconnect.sftp.request") class SftpPlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.sftp") public: explicit SftpPlugin(QObject* parent, const QVariantList& args); ~SftpPlugin() override; bool receivePacket(const NetworkPacket& np) override; void connected() override {} QString dbusPath() const override { return "/modules/kdeconnect/devices/" + deviceId + "/sftp"; } Q_SIGNALS: void packetReceived(const NetworkPacket& np); Q_SCRIPTABLE void mounted(); Q_SCRIPTABLE void unmounted(); public Q_SLOTS: Q_SCRIPTABLE void mount(); Q_SCRIPTABLE void unmount(); Q_SCRIPTABLE bool mountAndWait(); Q_SCRIPTABLE bool isMounted() const; + Q_SCRIPTABLE QString getMountError(); Q_SCRIPTABLE bool startBrowsing(); Q_SCRIPTABLE QString mountPoint(); Q_SCRIPTABLE QVariantMap getDirectories(); //Actually a QMap, but QDBus prefers this private Q_SLOTS: void onMounted(); void onUnmounted(); void onFailed(const QString& message); private: void knotify(int type, const QString& text, const QPixmap& icon) const; void addToDolphin(); void removeFromDolphin(); private: struct Pimpl; QScopedPointer d; QString deviceId; //Storing it to avoid accessing device() from the destructor which could cause a crash QVariantMap remoteDirectories; //Actually a QMap, but QDBus prefers this + QString mountError; }; #endif