1 /****************************************************************************************
2 * Copyright (c) 2007 Jeff Mitchell <kde-dev@emailgoeshere.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify it under *
5 * the terms of the GNU General Public License as published by the Free Software *
6 * Foundation; either version 2 of the License, or (at your option) any later *
7 * version. *
8 * *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
11 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
12 * *
13 * You should have received a copy of the GNU General Public License along with *
14 * this program. If not, see <http://www.gnu.org/licenses/>. *
15 ****************************************************************************************/
16
17 #define DEBUG_PREFIX "MediaDeviceCache"
18
19 #include "MediaDeviceCache.h"
20
21 #include "core/support/Amarok.h"
22 #include "core/support/Debug.h"
23
24 #include <Solid/Block>
25 #include <Solid/Device>
26 #include <Solid/DeviceInterface>
27 #include <Solid/DeviceNotifier>
28 #include <Solid/GenericInterface>
29 #include <Solid/OpticalDisc>
30 #include <Solid/PortableMediaPlayer>
31 #include <Solid/StorageAccess>
32 #include <Solid/StorageDrive>
33 #include <Solid/StorageVolume>
34
35 #include <QDir>
36 #include <QFile>
37 #include <QList>
38
39 #include <KConfigGroup>
40
41 MediaDeviceCache* MediaDeviceCache::s_instance = nullptr;
42
MediaDeviceCache()43 MediaDeviceCache::MediaDeviceCache() : QObject()
44 , m_type()
45 , m_name()
46 , m_volumes()
47 {
48 DEBUG_BLOCK
49 s_instance = this;
50 connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded,
51 this, &MediaDeviceCache::slotAddSolidDevice );
52 connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved,
53 this, &MediaDeviceCache::slotRemoveSolidDevice );
54 }
55
~MediaDeviceCache()56 MediaDeviceCache::~MediaDeviceCache()
57 {
58 s_instance = nullptr;
59 }
60
61 void
refreshCache()62 MediaDeviceCache::refreshCache()
63 {
64 DEBUG_BLOCK
65 m_type.clear();
66 m_name.clear();
67 QList<Solid::Device> deviceList = Solid::Device::listFromType( Solid::DeviceInterface::PortableMediaPlayer );
68 foreach( const Solid::Device &device, deviceList )
69 {
70 if( device.as<Solid::StorageDrive>() )
71 {
72 debug() << "Found Solid PMP that is also a StorageDrive, skipping";
73 continue;
74 }
75 debug() << "Found Solid::DeviceInterface::PortableMediaPlayer with udi = " << device.udi();
76 debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
77 m_type[device.udi()] = MediaDeviceCache::SolidPMPType;
78 m_name[device.udi()] = device.vendor() + " - " + device.product();
79 }
80 deviceList = Solid::Device::listFromType( Solid::DeviceInterface::StorageAccess );
81 foreach( const Solid::Device &device, deviceList )
82 {
83 debug() << "Found Solid::DeviceInterface::StorageAccess with udi = " << device.udi();
84 debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
85
86 const Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
87
88 if( ssa )
89 {
90 if( !m_volumes.contains( device.udi() ) )
91 {
92 connect( ssa, &Solid::StorageAccess::accessibilityChanged,
93 this, &MediaDeviceCache::slotAccessibilityChanged );
94 m_volumes.append( device.udi() );
95 }
96 if( ssa->isAccessible() )
97 {
98 m_type[device.udi()] = MediaDeviceCache::SolidVolumeType;
99 m_name[device.udi()] = ssa->filePath();
100 m_accessibility[ device.udi() ] = true;
101 }
102 else
103 {
104 m_accessibility[ device.udi() ] = false;
105 debug() << "Solid device is not accessible, will wait until it is to consider it added.";
106 }
107 }
108 }
109 deviceList = Solid::Device::listFromType( Solid::DeviceInterface::StorageDrive );
110 foreach( const Solid::Device &device, deviceList )
111 {
112 debug() << "Found Solid::DeviceInterface::StorageDrive with udi = " << device.udi();
113 debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
114
115 if( device.as<Solid::StorageDrive>() )
116 {
117 m_type[device.udi()] = MediaDeviceCache::SolidGenericType;
118 m_name[device.udi()] = device.vendor() + " - " + device.product();
119 }
120 }
121 deviceList = Solid::Device::listFromType( Solid::DeviceInterface::OpticalDisc );
122 foreach( const Solid::Device &device, deviceList )
123 {
124 debug() << "Found Solid::DeviceInterface::OpticalDisc with udi = " << device.udi();
125 debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
126
127 const Solid::OpticalDisc * opt = device.as<Solid::OpticalDisc>();
128
129 if ( opt && opt->availableContent() & Solid::OpticalDisc::Audio )
130 {
131 debug() << "device is an Audio CD";
132 m_type[device.udi()] = MediaDeviceCache::SolidAudioCdType;
133 m_name[device.udi()] = device.vendor() + " - " + device.product();
134 }
135 }
136 KConfigGroup config = Amarok::config( "PortableDevices" );
137 const QStringList manualDeviceKeys = config.entryMap().keys();
138 foreach( const QString &udi, manualDeviceKeys )
139 {
140 if( udi.startsWith( "manual" ) )
141 {
142 debug() << "Found manual device with udi = " << udi;
143 m_type[udi] = MediaDeviceCache::ManualType;
144 m_name[udi] = udi.split( '|' )[1];
145 }
146 }
147 }
148
149 void
slotAddSolidDevice(const QString & udi)150 MediaDeviceCache::slotAddSolidDevice( const QString &udi )
151 {
152 DEBUG_BLOCK
153 Solid::Device device( udi );
154 debug() << "Found new Solid device with udi = " << device.udi();
155 debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
156 Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
157
158 Solid::OpticalDisc * opt = device.as<Solid::OpticalDisc>();
159
160 if ( opt && opt->availableContent() & Solid::OpticalDisc::Audio )
161 {
162 debug() << "device is an Audio CD";
163 m_type[udi] = MediaDeviceCache::SolidAudioCdType;
164 m_name[udi] = device.vendor() + " - " + device.product();
165 }
166 else if( ssa )
167 {
168 debug() << "volume is generic storage";
169 if( !m_volumes.contains( device.udi() ) )
170 {
171 connect( ssa, &Solid::StorageAccess::accessibilityChanged,
172 this, &MediaDeviceCache::slotAccessibilityChanged );
173 m_volumes.append( device.udi() );
174 }
175 if( ssa->isAccessible() )
176 {
177 m_type[udi] = MediaDeviceCache::SolidVolumeType;
178 m_name[udi] = ssa->filePath();
179 }
180 else
181 {
182 debug() << "storage volume is not accessible right now, not adding.";
183 return;
184 }
185 }
186 else if( device.is<Solid::StorageDrive>() )
187 {
188 debug() << "device is a Storage drive, still need a volume";
189 m_type[udi] = MediaDeviceCache::SolidGenericType;
190 m_name[udi] = device.vendor() + " - " + device.product();
191 }
192 else if( device.is<Solid::PortableMediaPlayer>() )
193 {
194 debug() << "device is a PMP";
195 m_type[udi] = MediaDeviceCache::SolidPMPType;
196 m_name[udi] = device.vendor() + " - " + device.product();
197 }
198 else if( const Solid::GenericInterface *generic = device.as<Solid::GenericInterface>() )
199 {
200 const QMap<QString, QVariant> properties = generic->allProperties();
201 /* At least iPod touch 3G and iPhone 3G do not advertise AFC (Apple File
202 * Connection) capabilities. Therefore we have to white-list them so that they are
203 * still recognised ad iPods
204 *
205 * @see IpodConnectionAssistant::identify() for a quirk that is currently also
206 * needed for proper identification of iPhone-like devices.
207 */
208 if ( !device.product().contains("iPod") && !device.product().contains("iPhone"))
209 {
210 if( !properties.contains("info.capabilities") )
211 {
212 debug() << "udi " << udi << " does not describe a portable media player or storage volume";
213 return;
214 }
215
216 const QStringList capabilities = properties["info.capabilities"].toStringList();
217 if( !capabilities.contains("afc") )
218 {
219 debug() << "udi " << udi << " does not describe a portable media player or storage volume";
220 return;
221 }
222 }
223
224 debug() << "udi" << udi << "is AFC capable (Apple mobile device)";
225 m_type[udi] = MediaDeviceCache::SolidGenericType;
226 m_name[udi] = device.vendor() + " - " + device.product();
227 }
228 else
229 {
230 debug() << "udi " << udi << " does not describe a portable media player or storage volume";
231 return;
232 }
233 Q_EMIT deviceAdded( udi );
234 }
235
236 void
slotRemoveSolidDevice(const QString & udi)237 MediaDeviceCache::slotRemoveSolidDevice( const QString &udi )
238 {
239 DEBUG_BLOCK
240 debug() << "udi is: " << udi;
241 Solid::Device device( udi );
242 if( m_volumes.contains( udi ) )
243 {
244 disconnect( device.as<Solid::StorageAccess>(), &Solid::StorageAccess::accessibilityChanged,
245 this, &MediaDeviceCache::slotAccessibilityChanged );
246 m_volumes.removeAll( udi );
247 Q_EMIT deviceRemoved( udi );
248 }
249 if( m_type.contains( udi ) )
250 {
251 m_type.remove( udi );
252 m_name.remove( udi );
253 Q_EMIT deviceRemoved( udi );
254 return;
255 }
256 debug() << "Odd, got a deviceRemoved at udi " << udi << " but it did not seem to exist in the first place...";
257 Q_EMIT deviceRemoved( udi );
258 }
259
260 void
slotAccessibilityChanged(bool accessible,const QString & udi)261 MediaDeviceCache::slotAccessibilityChanged( bool accessible, const QString &udi )
262 {
263 debug() << "accessibility of device " << udi << " has changed to accessible = " << (accessible ? "true":"false");
264 if( accessible )
265 {
266 Solid::Device device( udi );
267 m_type[udi] = MediaDeviceCache::SolidVolumeType;
268 Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
269 if( ssa )
270 m_name[udi] = ssa->filePath();
271 Q_EMIT deviceAdded( udi );
272 return;
273 }
274 else
275 {
276 if( m_type.contains( udi ) )
277 {
278 m_type.remove( udi );
279 m_name.remove( udi );
280 Q_EMIT deviceRemoved( udi );
281 return;
282 }
283 debug() << "Got accessibility changed to false but was not there in the first place...";
284 }
285
286 Q_EMIT accessibilityChanged( accessible, udi );
287 }
288
289 MediaDeviceCache::DeviceType
deviceType(const QString & udi) const290 MediaDeviceCache::deviceType( const QString &udi ) const
291 {
292 if( m_type.contains( udi ) )
293 {
294 return m_type[udi];
295 }
296 return MediaDeviceCache::InvalidType;
297 }
298
299 const QString
deviceName(const QString & udi) const300 MediaDeviceCache::deviceName( const QString &udi ) const
301 {
302 if( m_name.contains( udi ) )
303 {
304 return m_name[udi];
305 }
306 return "ERR_NO_NAME"; //Should never happen!
307 }
308
309 const QString
device(const QString & udi) const310 MediaDeviceCache::device( const QString &udi ) const
311 {
312 DEBUG_BLOCK
313 Solid::Device device( udi );
314 Solid::Device parent( device.parent() );
315 if( !parent.isValid() )
316 {
317 debug() << udi << "has no parent, returning null string.";
318 return QString();
319 }
320
321 Solid::Block* sb = parent.as<Solid::Block>();
322 if( !sb )
323 {
324 debug() << parent.udi() << "failed to convert to Block, returning null string.";
325 return QString();
326 }
327
328 return sb->device();
329 }
330
331 bool
isGenericEnabled(const QString & udi) const332 MediaDeviceCache::isGenericEnabled( const QString &udi ) const
333 {
334 DEBUG_BLOCK
335 if( m_type[udi] != MediaDeviceCache::SolidVolumeType )
336 {
337 debug() << "Not SolidVolumeType, returning false";
338 return false;
339 }
340 Solid::Device device( udi );
341 Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
342 if( !ssa || !ssa->isAccessible() )
343 {
344 debug() << "Not able to convert to StorageAccess or not accessible, returning false";
345 return false;
346 }
347 if( device.parent().as<Solid::PortableMediaPlayer>() )
348 {
349 debug() << "Could convert parent to PortableMediaPlayer, returning true";
350 return true;
351 }
352 if( QFile::exists( ssa->filePath() + QLatin1Char('/') + ".is_audio_player" ) )
353 {
354 return true;
355 }
356 return false;
357 }
358
359 const QString
volumeMountPoint(const QString & udi) const360 MediaDeviceCache::volumeMountPoint( const QString &udi ) const
361 {
362 DEBUG_BLOCK
363 Solid::Device device( udi );
364 Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
365 if( !ssa || !ssa->isAccessible() )
366 {
367 debug() << "Not able to convert to StorageAccess or not accessible, returning empty";
368 return QString();
369 }
370 return ssa->filePath();
371 }
372
373
374