File indexing completed on 2024-04-21 04:47:52

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Jeff Mitchell <kde-dev@emailgoeshere.com>                         *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "MediaDeviceCache"
0018 
0019 #include "MediaDeviceCache.h"
0020 
0021 #include "core/support/Amarok.h"
0022 #include "core/support/Debug.h"
0023 
0024 #include <Solid/Block>
0025 #include <Solid/Device>
0026 #include <Solid/DeviceInterface>
0027 #include <Solid/DeviceNotifier>
0028 #include <Solid/GenericInterface>
0029 #include <Solid/OpticalDisc>
0030 #include <Solid/PortableMediaPlayer>
0031 #include <Solid/StorageAccess>
0032 #include <Solid/StorageDrive>
0033 #include <Solid/StorageVolume>
0034 
0035 #include <QDir>
0036 #include <QFile>
0037 #include <QList>
0038 
0039 #include <KConfigGroup>
0040 
0041 MediaDeviceCache* MediaDeviceCache::s_instance = nullptr;
0042 
0043 MediaDeviceCache::MediaDeviceCache() : QObject()
0044                              , m_type()
0045                              , m_name()
0046                              , m_volumes()
0047 {
0048     DEBUG_BLOCK
0049     s_instance = this;
0050     connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded,
0051              this, &MediaDeviceCache::slotAddSolidDevice );
0052     connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved,
0053              this, &MediaDeviceCache::slotRemoveSolidDevice );
0054 }
0055 
0056 MediaDeviceCache::~MediaDeviceCache()
0057 {
0058     s_instance = nullptr;
0059 }
0060 
0061 void
0062 MediaDeviceCache::refreshCache()
0063 {
0064     DEBUG_BLOCK
0065     m_type.clear();
0066     m_name.clear();
0067     QList<Solid::Device> deviceList = Solid::Device::listFromType( Solid::DeviceInterface::PortableMediaPlayer );
0068     foreach( const Solid::Device &device, deviceList )
0069     {
0070         if( device.as<Solid::StorageDrive>() )
0071         {
0072             debug() << "Found Solid PMP that is also a StorageDrive, skipping";
0073             continue;
0074         }
0075         debug() << "Found Solid::DeviceInterface::PortableMediaPlayer with udi = " << device.udi();
0076         debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
0077         m_type[device.udi()] = MediaDeviceCache::SolidPMPType;
0078         m_name[device.udi()] = device.vendor() + " - " + device.product();
0079     }
0080     deviceList = Solid::Device::listFromType( Solid::DeviceInterface::StorageAccess );
0081     foreach( const Solid::Device &device, deviceList )
0082     {
0083         debug() << "Found Solid::DeviceInterface::StorageAccess with udi = " << device.udi();
0084         debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
0085 
0086         const Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
0087 
0088         if( ssa )
0089         {
0090             if( !m_volumes.contains( device.udi() ) )
0091             {
0092                 connect( ssa, &Solid::StorageAccess::accessibilityChanged,
0093                     this, &MediaDeviceCache::slotAccessibilityChanged );
0094                 m_volumes.append( device.udi() );
0095             }
0096             if( ssa->isAccessible() )
0097             {
0098                 m_type[device.udi()] = MediaDeviceCache::SolidVolumeType;
0099                 m_name[device.udi()] = ssa->filePath();
0100                 m_accessibility[ device.udi() ] = true;
0101             }
0102             else
0103             {
0104                 m_accessibility[ device.udi() ] = false;
0105                 debug() << "Solid device is not accessible, will wait until it is to consider it added.";
0106             }
0107         }
0108     }
0109     deviceList = Solid::Device::listFromType( Solid::DeviceInterface::StorageDrive );
0110     foreach( const Solid::Device &device, deviceList )
0111     {
0112         debug() << "Found Solid::DeviceInterface::StorageDrive with udi = " << device.udi();
0113         debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
0114 
0115         if( device.as<Solid::StorageDrive>() )
0116         {
0117             m_type[device.udi()] = MediaDeviceCache::SolidGenericType;
0118             m_name[device.udi()] = device.vendor() + " - " + device.product();
0119         }
0120     }
0121     deviceList = Solid::Device::listFromType( Solid::DeviceInterface::OpticalDisc );
0122     foreach( const Solid::Device &device, deviceList )
0123     {
0124         debug() << "Found Solid::DeviceInterface::OpticalDisc with udi = " << device.udi();
0125         debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
0126 
0127         const Solid::OpticalDisc * opt = device.as<Solid::OpticalDisc>();
0128 
0129         if ( opt && opt->availableContent() & Solid::OpticalDisc::Audio )
0130         {
0131             debug() << "device is an Audio CD";
0132             m_type[device.udi()] = MediaDeviceCache::SolidAudioCdType;
0133             m_name[device.udi()] = device.vendor() + " - " + device.product();
0134         }
0135     }
0136     KConfigGroup config = Amarok::config( "PortableDevices" );
0137     const QStringList manualDeviceKeys = config.entryMap().keys();
0138     foreach( const QString &udi, manualDeviceKeys )
0139     {
0140         if( udi.startsWith( "manual" ) )
0141         {
0142             debug() << "Found manual device with udi = " << udi;
0143             m_type[udi] = MediaDeviceCache::ManualType;
0144             m_name[udi] = udi.split( '|' )[1];
0145         }
0146     }
0147 }
0148 
0149 void
0150 MediaDeviceCache::slotAddSolidDevice( const QString &udi )
0151 {
0152     DEBUG_BLOCK
0153     Solid::Device device( udi );
0154     debug() << "Found new Solid device with udi = " << device.udi();
0155     debug() << "Device name is = " << device.product() << " and was made by " << device.vendor();
0156     Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
0157 
0158     Solid::OpticalDisc * opt = device.as<Solid::OpticalDisc>();
0159 
0160     if ( opt && opt->availableContent() & Solid::OpticalDisc::Audio )
0161     {
0162         debug() << "device is an Audio CD";
0163         m_type[udi] = MediaDeviceCache::SolidAudioCdType;
0164         m_name[udi] = device.vendor() + " - " + device.product();
0165     }
0166     else if( ssa )
0167     {
0168         debug() << "volume is generic storage";
0169         if( !m_volumes.contains( device.udi() ) )
0170         {
0171             connect( ssa, &Solid::StorageAccess::accessibilityChanged,
0172                 this, &MediaDeviceCache::slotAccessibilityChanged );
0173             m_volumes.append( device.udi() );
0174         }
0175         if( ssa->isAccessible() )
0176         {
0177             m_type[udi] = MediaDeviceCache::SolidVolumeType;
0178             m_name[udi] = ssa->filePath();
0179         }
0180         else
0181         {
0182             debug() << "storage volume is not accessible right now, not adding.";
0183             return;
0184         }
0185     }
0186     else if( device.is<Solid::StorageDrive>() )
0187     {
0188         debug() << "device is a Storage drive, still need a volume";
0189         m_type[udi] = MediaDeviceCache::SolidGenericType;
0190         m_name[udi] = device.vendor() + " - " + device.product();
0191     }
0192     else if( device.is<Solid::PortableMediaPlayer>() )
0193     {
0194         debug() << "device is a PMP";
0195         m_type[udi] = MediaDeviceCache::SolidPMPType;
0196         m_name[udi] = device.vendor() + " - " + device.product();
0197     }
0198     else if( const Solid::GenericInterface *generic = device.as<Solid::GenericInterface>() )
0199     {
0200         const QMap<QString, QVariant> properties = generic->allProperties();
0201         /* At least iPod touch 3G and iPhone 3G do not advertise AFC (Apple File
0202          * Connection) capabilities. Therefore we have to white-list them so that they are
0203          * still recognised ad iPods
0204          *
0205          * @see IpodConnectionAssistant::identify() for a quirk that is currently also
0206          * needed for proper identification of iPhone-like devices.
0207          */
0208         if ( !device.product().contains("iPod") && !device.product().contains("iPhone"))
0209         {
0210             if( !properties.contains("info.capabilities") )
0211             {
0212                 debug() << "udi " << udi << " does not describe a portable media player or storage volume";
0213                 return;
0214             }
0215     
0216             const QStringList capabilities = properties["info.capabilities"].toStringList();
0217             if( !capabilities.contains("afc") )
0218             {
0219                 debug() << "udi " << udi << " does not describe a portable media player or storage volume";
0220                 return;
0221             }
0222         }
0223 
0224         debug() << "udi" << udi << "is AFC capable (Apple mobile device)";
0225         m_type[udi] = MediaDeviceCache::SolidGenericType;
0226         m_name[udi] = device.vendor() + " - " + device.product();
0227     }
0228     else
0229     {
0230         debug() << "udi " << udi << " does not describe a portable media player or storage volume";
0231         return;
0232     }
0233     Q_EMIT deviceAdded( udi );
0234 }
0235 
0236 void
0237 MediaDeviceCache::slotRemoveSolidDevice( const QString &udi )
0238 {
0239     DEBUG_BLOCK
0240     debug() << "udi is: " << udi;
0241     Solid::Device device( udi );
0242     if( m_volumes.contains( udi ) )
0243     {
0244         disconnect( device.as<Solid::StorageAccess>(), &Solid::StorageAccess::accessibilityChanged,
0245                     this, &MediaDeviceCache::slotAccessibilityChanged );
0246         m_volumes.removeAll( udi );
0247         Q_EMIT deviceRemoved( udi );
0248     }
0249     if( m_type.contains( udi ) )
0250     {
0251         m_type.remove( udi );
0252         m_name.remove( udi );
0253         Q_EMIT deviceRemoved( udi );
0254         return;
0255     }
0256     debug() << "Odd, got a deviceRemoved at udi " << udi << " but it did not seem to exist in the first place...";
0257     Q_EMIT deviceRemoved( udi );
0258 }
0259 
0260 void
0261 MediaDeviceCache::slotAccessibilityChanged( bool accessible, const QString &udi )
0262 {
0263     debug() << "accessibility of device " << udi << " has changed to accessible = " << (accessible ? "true":"false");
0264     if( accessible )
0265     {
0266         Solid::Device device( udi );
0267         m_type[udi] = MediaDeviceCache::SolidVolumeType;
0268         Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>();
0269         if( ssa )
0270             m_name[udi] = ssa->filePath();
0271         Q_EMIT deviceAdded( udi );
0272         return;
0273     }
0274     else
0275     {
0276         if( m_type.contains( udi ) )
0277         {
0278             m_type.remove( udi );
0279             m_name.remove( udi );
0280             Q_EMIT deviceRemoved( udi );
0281             return;
0282         }
0283         debug() << "Got accessibility changed to false but was not there in the first place...";
0284     }
0285 
0286     Q_EMIT accessibilityChanged( accessible, udi );
0287 }
0288 
0289 MediaDeviceCache::DeviceType
0290 MediaDeviceCache::deviceType( const QString &udi ) const
0291 {
0292     if( m_type.contains( udi ) )
0293     {
0294         return m_type[udi];
0295     }
0296     return MediaDeviceCache::InvalidType;
0297 }
0298 
0299 const QString
0300 MediaDeviceCache::deviceName( const QString &udi ) const
0301 {
0302     if( m_name.contains( udi ) )
0303     {
0304         return m_name[udi];
0305     }
0306     return "ERR_NO_NAME"; //Should never happen!
0307 }
0308 
0309 const QString
0310 MediaDeviceCache::device( const QString &udi ) const
0311 {
0312     DEBUG_BLOCK
0313     Solid::Device device( udi );
0314     Solid::Device parent( device.parent() );
0315     if( !parent.isValid() )
0316     {
0317         debug() << udi << "has no parent, returning null string.";
0318         return QString();
0319     }
0320 
0321     Solid::Block* sb = parent.as<Solid::Block>();
0322     if( !sb  )
0323     {
0324         debug() << parent.udi() << "failed to convert to Block, returning null string.";
0325         return QString();
0326     }
0327 
0328     return sb->device();
0329 }
0330 
0331 bool
0332 MediaDeviceCache::isGenericEnabled( const QString &udi ) const
0333 {
0334     DEBUG_BLOCK
0335     if( m_type[udi] != MediaDeviceCache::SolidVolumeType )
0336     {
0337         debug() << "Not SolidVolumeType, returning false";
0338         return false;
0339     }
0340     Solid::Device device( udi );
0341     Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
0342     if( !ssa || !ssa->isAccessible() )
0343     {
0344         debug() << "Not able to convert to StorageAccess or not accessible, returning false";
0345         return false;
0346     }
0347     if( device.parent().as<Solid::PortableMediaPlayer>() )
0348     {
0349         debug() << "Could convert parent to PortableMediaPlayer, returning true";
0350         return true;
0351     }
0352     if( QFile::exists( ssa->filePath() + QLatin1Char('/') + ".is_audio_player" ) )
0353     {
0354         return true;
0355     }
0356     return false;
0357 }
0358 
0359 const QString
0360 MediaDeviceCache::volumeMountPoint( const QString &udi ) const
0361 {
0362     DEBUG_BLOCK
0363     Solid::Device device( udi );
0364     Solid::StorageAccess* ssa = device.as<Solid::StorageAccess>();
0365     if( !ssa || !ssa->isAccessible() )
0366     {
0367         debug() << "Not able to convert to StorageAccess or not accessible, returning empty";
0368         return QString();
0369     }
0370     return ssa->filePath();
0371 }
0372 
0373