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