1 /* This file is part of Clementine.
2 Copyright 2010, David Sansome <me@davidsansome.com>
3
4 Clementine 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 3 of the License, or
7 (at your option) any later version.
8
9 Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19 #include "devicekitlister.h"
20 #include "filesystemdevice.h"
21 #include "core/logging.h"
22 #include "core/utilities.h"
23 #include "dbus/udisks.h"
24 #include "dbus/udisksdevice.h"
25
26 #ifdef HAVE_LIBGPOD
27 #include "gpoddevice.h"
28 #endif
29
30 #include <sys/statvfs.h>
31
32 #include <QtDebug>
33
DeviceKitLister()34 DeviceKitLister::DeviceKitLister() {}
35
~DeviceKitLister()36 DeviceKitLister::~DeviceKitLister() { qLog(Debug) << __PRETTY_FUNCTION__; }
37
unique_id() const38 QString DeviceKitLister::DeviceData::unique_id() const {
39 return QString("DeviceKit/%1/%2/%3/%4")
40 .arg(drive_serial, drive_vendor, drive_model)
41 .arg(device_size);
42 }
43
Init()44 void DeviceKitLister::Init() {
45 interface_.reset(new OrgFreedesktopUDisksInterface(
46 OrgFreedesktopUDisksInterface::staticInterfaceName(),
47 "/org/freedesktop/UDisks", QDBusConnection::systemBus()));
48
49 // Get all the devices currently attached
50 QDBusPendingReply<QList<QDBusObjectPath> > reply =
51 interface_->EnumerateDevices();
52 reply.waitForFinished();
53
54 if (!reply.isValid()) {
55 qLog(Warning) << "Error enumerating DeviceKit-disks devices:"
56 << reply.error().name() << reply.error().message();
57 interface_.reset();
58 return;
59 }
60
61 // Listen for changes
62 connect(interface_.get(), SIGNAL(DeviceAdded(QDBusObjectPath)),
63 SLOT(DBusDeviceAdded(QDBusObjectPath)));
64 connect(interface_.get(), SIGNAL(DeviceRemoved(QDBusObjectPath)),
65 SLOT(DBusDeviceRemoved(QDBusObjectPath)));
66 connect(interface_.get(), SIGNAL(DeviceChanged(QDBusObjectPath)),
67 SLOT(DBusDeviceChanged(QDBusObjectPath)));
68
69 // Get information about each one
70 QMap<QString, DeviceData> device_data;
71 for (const QDBusObjectPath& path : reply.value()) {
72 DeviceData data = ReadDeviceData(path);
73 if (data.suitable) device_data[data.unique_id()] = data;
74 }
75
76 // Update the internal cache
77 {
78 QMutexLocker l(&mutex_);
79 device_data_ = device_data;
80 }
81
82 // Notify about the changes
83 for (const QString& id : device_data.keys()) {
84 emit DeviceAdded(id);
85 }
86 }
87
DeviceUniqueIDs()88 QStringList DeviceKitLister::DeviceUniqueIDs() {
89 QMutexLocker l(&mutex_);
90 return device_data_.keys();
91 }
92
DeviceIcons(const QString & id)93 QVariantList DeviceKitLister::DeviceIcons(const QString& id) {
94 QString path = LockAndGetDeviceInfo(id, &DeviceData::device_mount_paths)[0];
95 return QVariantList() << GuessIconForPath(path)
96 << GuessIconForModel(DeviceManufacturer(id),
97 DeviceModel(id))
98 << LockAndGetDeviceInfo(
99 id, &DeviceData::device_presentation_icon_name);
100 }
101
DeviceManufacturer(const QString & id)102 QString DeviceKitLister::DeviceManufacturer(const QString& id) {
103 return LockAndGetDeviceInfo(id, &DeviceData::drive_vendor);
104 }
105
DeviceModel(const QString & id)106 QString DeviceKitLister::DeviceModel(const QString& id) {
107 return LockAndGetDeviceInfo(id, &DeviceData::drive_model);
108 }
109
DeviceCapacity(const QString & id)110 quint64 DeviceKitLister::DeviceCapacity(const QString& id) {
111 return LockAndGetDeviceInfo(id, &DeviceData::device_size);
112 }
113
DeviceFreeSpace(const QString & id)114 quint64 DeviceKitLister::DeviceFreeSpace(const QString& id) {
115 return LockAndGetDeviceInfo(id, &DeviceData::free_space);
116 }
117
DeviceHardwareInfo(const QString & id)118 QVariantMap DeviceKitLister::DeviceHardwareInfo(const QString& id) {
119 QVariantMap ret;
120
121 QMutexLocker l(&mutex_);
122 if (!device_data_.contains(id)) return ret;
123 const DeviceData& data = device_data_[id];
124
125 ret[QT_TR_NOOP("D-Bus path")] = data.dbus_path;
126 ret[QT_TR_NOOP("Serial number")] = data.drive_serial;
127 ret[QT_TR_NOOP("Mount points")] = data.device_mount_paths.join(", ");
128 ret[QT_TR_NOOP("Device")] = data.device_file;
129 return ret;
130 }
131
MakeFriendlyName(const QString & id)132 QString DeviceKitLister::MakeFriendlyName(const QString& id) {
133 QMutexLocker l(&mutex_);
134 if (!device_data_.contains(id)) return QString();
135 const DeviceData& data = device_data_[id];
136
137 if (!data.device_presentation_name.isEmpty())
138 return data.device_presentation_name;
139 if (!data.drive_model.isEmpty() && !data.drive_vendor.isEmpty())
140 return data.drive_vendor + " " + data.drive_model;
141 if (!data.drive_model.isEmpty()) return data.drive_model;
142 return data.drive_serial;
143 }
144
ReadDeviceData(const QDBusObjectPath & path) const145 DeviceKitLister::DeviceData DeviceKitLister::ReadDeviceData(
146 const QDBusObjectPath& path) const {
147 DeviceData ret;
148
149 OrgFreedesktopUDisksDeviceInterface device(
150 OrgFreedesktopUDisksInterface::staticInterfaceName(), path.path(),
151 QDBusConnection::systemBus());
152 if (!device.isValid()) {
153 qLog(Warning) << "Error connecting to the device interface on"
154 << path.path();
155 return ret;
156 }
157
158 // Don't do anything with internal drives, hidden drives, or partition tables
159 if (device.deviceIsSystemInternal() || device.devicePresentationHide() ||
160 device.deviceMountPaths().isEmpty() || device.deviceIsPartitionTable()) {
161 return ret;
162 }
163
164 ret.suitable = true;
165 ret.dbus_path = path.path();
166 ret.drive_serial = device.driveSerial();
167 ret.drive_model = device.driveModel();
168 ret.drive_vendor = device.driveVendor();
169 ret.device_file = device.deviceFile();
170 ret.device_presentation_name = device.devicePresentationName();
171 ret.device_presentation_icon_name = device.devicePresentationIconName();
172 ret.device_size = device.deviceSize();
173 ret.device_mount_paths = device.deviceMountPaths();
174
175 // Get free space info
176 if (!ret.device_mount_paths.isEmpty())
177 ret.free_space = Utilities::FileSystemFreeSpace(ret.device_mount_paths[0]);
178
179 return ret;
180 }
181
DBusDeviceAdded(const QDBusObjectPath & path)182 void DeviceKitLister::DBusDeviceAdded(const QDBusObjectPath& path) {
183 DeviceData data = ReadDeviceData(path);
184 if (!data.suitable) return;
185
186 {
187 QMutexLocker l(&mutex_);
188 device_data_[data.unique_id()] = data;
189 }
190
191 emit DeviceAdded(data.unique_id());
192 }
193
DBusDeviceRemoved(const QDBusObjectPath & path)194 void DeviceKitLister::DBusDeviceRemoved(const QDBusObjectPath& path) {
195 QString id;
196 {
197 QMutexLocker l(&mutex_);
198 id = FindUniqueIdByPath(path);
199 if (id.isNull()) return;
200
201 device_data_.remove(id);
202 }
203
204 emit DeviceRemoved(id);
205 }
206
DBusDeviceChanged(const QDBusObjectPath & path)207 void DeviceKitLister::DBusDeviceChanged(const QDBusObjectPath& path) {
208 bool already_known = false;
209 {
210 QMutexLocker l(&mutex_);
211 already_known = !FindUniqueIdByPath(path).isNull();
212 }
213
214 DeviceData data = ReadDeviceData(path);
215
216 if (already_known && !data.suitable)
217 DBusDeviceRemoved(path);
218 else if (!already_known && data.suitable)
219 DBusDeviceAdded(path);
220 else if (already_known && data.suitable) {
221 {
222 QMutexLocker l(&mutex_);
223 device_data_[data.unique_id()] = data;
224 }
225 emit DeviceChanged(data.unique_id());
226 }
227 }
228
FindUniqueIdByPath(const QDBusObjectPath & path) const229 QString DeviceKitLister::FindUniqueIdByPath(const QDBusObjectPath& path) const {
230 for (const DeviceData& data : device_data_) {
231 if (data.dbus_path == path.path()) return data.unique_id();
232 }
233 return QString();
234 }
235
MakeDeviceUrls(const QString & id)236 QList<QUrl> DeviceKitLister::MakeDeviceUrls(const QString& id) {
237 QString mount_point =
238 LockAndGetDeviceInfo(id, &DeviceData::device_mount_paths)[0];
239
240 return QList<QUrl>() << MakeUrlFromLocalPath(mount_point);
241 }
242
UnmountDevice(const QString & id)243 void DeviceKitLister::UnmountDevice(const QString& id) {
244 QString path = LockAndGetDeviceInfo(id, &DeviceData::dbus_path);
245
246 OrgFreedesktopUDisksDeviceInterface device(
247 OrgFreedesktopUDisksInterface::staticInterfaceName(), path,
248 QDBusConnection::systemBus());
249 if (!device.isValid()) {
250 qLog(Warning) << "Error connecting to the device interface on" << path;
251 return;
252 }
253
254 // Get the device's parent drive
255 QString drive_path = device.partitionSlave().path();
256 OrgFreedesktopUDisksDeviceInterface drive(
257 OrgFreedesktopUDisksInterface::staticInterfaceName(), drive_path,
258 QDBusConnection::systemBus());
259 if (!drive.isValid()) {
260 qLog(Warning) << "Error connecting to the drive interface on" << drive_path;
261 return;
262 }
263
264 // Unmount the filesystem
265 QDBusPendingReply<> reply = device.FilesystemUnmount(QStringList());
266 reply.waitForFinished();
267
268 // Eject the drive
269 drive.DriveEject(QStringList());
270 // Don't bother waiting for the eject to finish
271 }
272
UpdateDeviceFreeSpace(const QString & id)273 void DeviceKitLister::UpdateDeviceFreeSpace(const QString& id) {
274 {
275 QMutexLocker l(&mutex_);
276 if (!device_data_.contains(id)) return;
277
278 DeviceData& data = device_data_[id];
279 if (!data.device_mount_paths.isEmpty())
280 data.free_space =
281 Utilities::FileSystemFreeSpace(data.device_mount_paths[0]);
282 }
283
284 emit DeviceChanged(id);
285 }
286