1/*
2 Copyright (C) 2010 by Jacopo De Simoi <wilderkde@gmail.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 */
19
20#include "ksolidnotify.h"
21#include "knotify.h"
22
23//solid specific includes
24#include <Solid/DeviceNotifier>
25#include <Solid/Device>
26#include <Solid/DeviceInterface>
27#include <Solid/StorageDrive>
28#include <Solid/StorageVolume>
29#include <Solid/StorageAccess>
30#include <Solid/OpticalDrive>
31#include <Solid/OpticalDisc>
32#include <Solid/PortableMediaPlayer>
33#include <Solid/Predicate>
34
35#include <QDBusConnection>
36#include <QDBusConnectionInterface>
37#include <QDBusServiceWatcher>
38
39#include <KLocale>
40
41static const char dbusDeviceNotificationsName[] = "org.kde.DeviceNotifications";
42static const char dbusDeviceNotificationsPath[] = "/org/kde/DeviceNotifications";
43
44
45KSolidNotify::KSolidNotify(KNotify* parent):
46 QObject(parent),
47 m_kNotify(parent),
48 m_dbusServiceExists(false)
49{
50 Solid::Predicate p(Solid::DeviceInterface::StorageAccess);
51 p |= Solid::Predicate(Solid::DeviceInterface::OpticalDrive);
52 p |= Solid::Predicate(Solid::DeviceInterface::PortableMediaPlayer);
53 QList<Solid::Device> devices = Solid::Device::listFromQuery(p);
54 foreach (const Solid::Device &dev, devices)
55 {
56 m_devices.insert(dev.udi(), dev);
57 connectSignals(&m_devices[dev.udi()]);
58 }
59
60 connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(const QString &)),
61 this, SLOT(onDeviceAdded(const QString &)));
62 connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(const QString &)),
63 this, SLOT(onDeviceRemoved(const QString &)));
64
65 // check if service already exists on plugin instantiation
66 QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface();
67 m_dbusServiceExists = interface && interface->isServiceRegistered(dbusDeviceNotificationsName);
68
69 if( m_dbusServiceExists )
70 slotServiceOwnerChanged(dbusDeviceNotificationsName, QString(), "_"); //connect signals
71
72 // to catch register/unregister events from service in runtime
73 QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this);
74 watcher->setConnection(QDBusConnection::sessionBus());
75 watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
76 watcher->addWatchedService(dbusDeviceNotificationsName);
77 connect(watcher, SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
78 SLOT(slotServiceOwnerChanged(const QString&, const QString&, const QString&)));
79}
80
81void KSolidNotify::onDeviceAdded(const QString &udi)
82{
83 Solid::Device device(udi);
84 m_devices.insert(udi, device);
85 connectSignals(&m_devices[udi]);
86}
87
88void KSolidNotify::onDeviceRemoved(const QString &udi)
89{
90 if (m_devices[udi].is<Solid::StorageVolume>())
91 {
92 Solid::StorageAccess *access = m_devices[udi].as<Solid::StorageAccess>();
93 if (access)
94 disconnect(access, 0, this, 0);
95 }
96 m_devices.remove(udi);
97}
98
99bool KSolidNotify::isSafelyRemovable(const QString &udi)
100{
101 Solid::Device parent = m_devices[udi].parent();
102 if (parent.is<Solid::StorageDrive>())
103 {
104 Solid::StorageDrive *drive = parent.as<Solid::StorageDrive>();
105 return (!drive->isInUse() && (drive->isHotpluggable() || drive->isRemovable()));
106 }
107 Solid::StorageAccess* access = m_devices[udi].as<Solid::StorageAccess>();
108 if (access) {
109 return !m_devices[udi].as<Solid::StorageAccess>()->isAccessible();
110 } else {
111 // If this check fails, the device has been already physically
112 // ejected, so no need to say that it is safe to remove it
113 return false;
114 }
115}
116
117void KSolidNotify::connectSignals(Solid::Device* device)
118{
119 Solid::StorageAccess *access = device->as<Solid::StorageAccess>();
120 if (access)
121 {
122 connect(access, SIGNAL(teardownDone(Solid::ErrorType, QVariant, const QString &)),
123 this, SLOT(storageTeardownDone(Solid::ErrorType, QVariant , const QString &)));
124 connect(access, SIGNAL(setupDone(Solid::ErrorType, QVariant, const QString &)),
125 this, SLOT(storageSetupDone(Solid::ErrorType, QVariant , const QString &)));
126 }
127 if (device->is<Solid::OpticalDisc>())
128 {
129 Solid::OpticalDrive *drive = device->parent().as<Solid::OpticalDrive>();
130 connect(drive, SIGNAL(ejectDone(Solid::ErrorType, QVariant, const QString &)),
131 this, SLOT(storageEjectDone(Solid::ErrorType, QVariant , const QString &)));
132 }
133}
134
135void KSolidNotify::notifySolidEvent(QString event, Solid::ErrorType error, QVariant errorData, const QString & udi, const QString & errorMessage)
136{
137 ContextList context;
138 if (m_dbusServiceExists)
139 {
140 KNotifyConfig mountConfig("hardwarenotifications", ContextList(), event);
141 if (mountConfig.readEntry("Action").split('|').contains("Popup"))
142 {
143 QDBusMessage m = QDBusMessage::createMethodCall( dbusDeviceNotificationsName, dbusDeviceNotificationsPath, dbusDeviceNotificationsName, "notify" );
144 m << error << errorMessage << errorData.toString().simplified() << udi;
145 QDBusConnection::sessionBus().call(m);
146 }
147 context << QPair<QString, QString>("devnotifier", "present");
148 }
149
150 m_kNotify->event(event, "hardwarenotifications", context, i18n("Devices notification"), errorMessage, KNotifyImage(), QStringList(), -1);
151
152}
153
154void KSolidNotify::storageSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
155{
156 if (error)
157 {
158 Solid::Device device(udi);
159 QString errorMessage = i18n("Could not mount the following device: %1", device.description());
160 notifySolidEvent("mounterror", error, errorData, udi, errorMessage);
161 }
162}
163
164void KSolidNotify::storageTeardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
165{
166 if (error)
167 {
168 Solid::Device device(udi);
169 QString errorMessage = i18n("Could not unmount the following device: %1\nOne or more files on this device are open within an application ", device.description());
170 notifySolidEvent("mounterror", error, errorData, udi, errorMessage);
171 } else if (isSafelyRemovable(udi))
172 {
173 Solid::Device device(udi);
174 notifySolidEvent("safetoremove", error, errorData, udi, i18nc("The term \"remove\" here means \"physically disconnect the device from the computer\", whereas \"safely\" means \"without risk of data loss\"", "The following device can now be safely removed: %1", device.description()));
175 }
176}
177
178void KSolidNotify::storageEjectDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
179{
180 if (error)
181 {
182 QString discUdi;
183 foreach (Solid::Device device, m_devices) {
184 if (device.parentUdi() == udi) {
185 discUdi = device.udi();
186 }
187 }
188
189 if (discUdi.isNull()) {
190 //This should not happen, bail out
191 return;
192 }
193
194 Solid::Device discDevice(discUdi);
195 QString errorMessage = i18n("Could not eject the following device: %1\nOne or more files on this device are open within an application ", discDevice.description());
196 notifySolidEvent("mounterror", error, errorData, udi, errorMessage);
197 } else if (isSafelyRemovable(udi))
198 {
199 Solid::Device device(udi);
200 notifySolidEvent("safetoremove", error, errorData, udi, i18n("The following device can now be safely removed: %1", device.description()));
201 }
202}
203
204void KSolidNotify::slotServiceOwnerChanged( const QString & serviceName, const QString & oldOwner, const QString & newOwner )
205{
206 Q_UNUSED(serviceName);
207 if (newOwner.isEmpty())
208 m_dbusServiceExists = false;
209 else if (oldOwner.isEmpty())
210 m_dbusServiceExists = true;
211}
212
213
214#include "ksolidnotify.moc"
215