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