File indexing completed on 2025-01-05 04:25:59

0001 /****************************************************************************************
0002  * Copyright (c) 2006-2007 Maximilian Kossick <maximilian.kossick@googlemail.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 "MountPointManager"
0018 
0019 #include "MountPointManager.h"
0020 
0021 #include "MediaDeviceCache.h"
0022 #include "core/support/Amarok.h"
0023 #include "core/support/Debug.h"
0024 #include <core/storage/SqlStorage.h>
0025 #include "core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h"
0026 #include "core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h"
0027 #include "core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h"
0028 
0029 #include <KConfigGroup>
0030 #include <Solid/Predicate>
0031 #include <Solid/Device>
0032 
0033 #include <QDesktopServices>
0034 #include <QDir>
0035 #include <QFile>
0036 #include <QList>
0037 #include <QStringList>
0038 #include <QTimer>
0039 
0040 MountPointManager::MountPointManager( QObject *parent, QSharedPointer<SqlStorage> storage )
0041     : QObject( parent )
0042     , m_storage( storage )
0043     , m_ready( false )
0044 {
0045     DEBUG_BLOCK
0046     setObjectName( "MountPointManager" );
0047 
0048     if ( !Amarok::config( "Collection" ).readEntry( "DynamicCollection", true ) )
0049     {
0050         debug() << "Dynamic Collection deactivated in amarokrc, not loading plugins, not connecting signals";
0051         m_ready = true;
0052         handleMusicLocation();
0053         return;
0054     }
0055 
0056     connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceAdded, this, &MountPointManager::slotDeviceAdded );
0057     connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceRemoved, this, &MountPointManager::slotDeviceRemoved );
0058 
0059     createDeviceFactories();
0060 }
0061 
0062 void
0063 MountPointManager::handleMusicLocation()
0064 {
0065     // For users who were using QDesktopServices::MusicLocation exclusively up
0066     // to v2.2.2, which did not store the location into config.
0067     // and also for versions up to 2.7-git that did write the Use MusicLocation entry
0068 
0069     KConfigGroup folders = Amarok::config( "Collection Folders" );
0070     const QString entryKey( "Use MusicLocation" );
0071     if( !folders.hasKey( entryKey ) )
0072         return; // good, already solved, nothing to do
0073 
0074     // write the music location as another collection folder in this case
0075     if( folders.readEntry( entryKey, false ) )
0076     {
0077         const QUrl musicUrl = QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::MusicLocation ) );
0078         const QString musicDir = musicUrl.adjusted( QUrl::StripTrailingSlash ).toLocalFile();
0079         const QDir dir( musicDir );
0080         if( dir.exists() && dir.isReadable() )
0081         {
0082             QStringList currentFolders = collectionFolders();
0083             if( !currentFolders.contains( musicDir ) )
0084                 setCollectionFolders( currentFolders << musicDir );
0085         }
0086     }
0087 
0088     folders.deleteEntry( entryKey ); // get rid of it for good
0089 }
0090 
0091 MountPointManager::~MountPointManager()
0092 {
0093     DEBUG_BLOCK
0094 
0095     m_handlerMapMutex.lock();
0096     foreach( DeviceHandler *dh, m_handlerMap )
0097         delete dh;
0098     m_handlerMapMutex.unlock();
0099 
0100     // DeviceHandlerFactories are memory managed using QObject parentship
0101 }
0102 
0103 
0104 void
0105 MountPointManager::createDeviceFactories()
0106 {
0107     DEBUG_BLOCK
0108     QList<DeviceHandlerFactory*> factories;
0109     factories << new MassStorageDeviceHandlerFactory( this );
0110     factories << new NfsDeviceHandlerFactory( this );
0111     factories << new SmbDeviceHandlerFactory( this );
0112     foreach( DeviceHandlerFactory *factory, factories )
0113     {
0114         debug() << "Initializing DeviceHandlerFactory of type:" << factory->type();
0115         if( factory->canCreateFromMedium() )
0116             m_mediumFactories.append( factory );
0117         else if (factory->canCreateFromConfig() )
0118             m_remoteFactories.append( factory );
0119         else //FIXME max: better error message
0120             debug() << "Unknown DeviceHandlerFactory";
0121     }
0122 
0123     Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess );
0124     QList<Solid::Device> devices = Solid::Device::listFromQuery( predicate );
0125     foreach( const Solid::Device &device, devices )
0126         createHandlerFromDevice( device, device.udi() );
0127 
0128     m_ready = true;
0129     handleMusicLocation();
0130 }
0131 
0132 int
0133 MountPointManager::getIdForUrl( const QUrl &url )
0134 {
0135     int mountPointLength = 0;
0136     int id = -1;
0137     m_handlerMapMutex.lock();
0138     foreach( DeviceHandler *dh, m_handlerMap )
0139     {
0140         if ( url.path().startsWith( dh->getDevicePath() ) && mountPointLength < dh->getDevicePath().length() )
0141         {
0142             id = m_handlerMap.key( dh );
0143             mountPointLength = dh->getDevicePath().length();
0144         }
0145     }
0146     m_handlerMapMutex.unlock();
0147     if ( mountPointLength > 0 )
0148     {
0149         return id;
0150     }
0151     else
0152     {
0153         //default fallback if we could not identify the mount point.
0154         //treat -1 as mount point / in all other methods
0155         return -1;
0156     }
0157 }
0158 
0159 bool
0160 MountPointManager::isMounted( const int deviceId ) const
0161 {
0162     m_handlerMapMutex.lock();
0163     const bool result = m_handlerMap.contains( deviceId );
0164     m_handlerMapMutex.unlock();
0165     return result;
0166 }
0167 
0168 QString
0169 MountPointManager::getMountPointForId( const int id ) const
0170 {
0171     QString mountPoint;
0172     if ( isMounted( id ) )
0173     {
0174         m_handlerMapMutex.lock();
0175         mountPoint = m_handlerMap[id]->getDevicePath();
0176         m_handlerMapMutex.unlock();
0177     }
0178     else
0179         //TODO better error handling
0180         mountPoint = '/';
0181     return mountPoint;
0182 }
0183 
0184 QString
0185 MountPointManager::getAbsolutePath( const int deviceId, const QString& relativePath ) const
0186 {
0187     if( QDir( relativePath ).isAbsolute() )
0188     {
0189         //debug() << "relativePath is already absolute";
0190         return relativePath;
0191     }
0192 
0193     QUrl rurl = QUrl::fromLocalFile( relativePath );
0194     QUrl absoluteUrl = QUrl::fromLocalFile( QDir::rootPath() );
0195 
0196     //debug() << "id is " << deviceId << ", relative path is " << relativePath;
0197     if ( deviceId == -1 )
0198     {
0199         absoluteUrl.setPath( QDir::rootPath() + relativePath );
0200         absoluteUrl.setPath( QDir::cleanPath( absoluteUrl.path() ) );
0201         //debug() << "Deviceid is -1, using relative Path as absolute Path, returning " << absoluteUrl.toLocalFile();
0202     }
0203     else
0204     {
0205         m_handlerMapMutex.lock();
0206         if ( m_handlerMap.contains( deviceId ) )
0207         {
0208             m_handlerMap[deviceId]->getURL( absoluteUrl, rurl );
0209             m_handlerMapMutex.unlock();
0210         }
0211         else
0212         {
0213             m_handlerMapMutex.unlock();
0214             const QStringList lastMountPoint = m_storage->query( QString( "SELECT lastmountpoint FROM devices WHERE id = %1" )
0215                                                                  .arg( deviceId ) );
0216             if ( lastMountPoint.isEmpty() )
0217             {
0218                 //hmm, no device with that id in the DB...serious problem
0219                 warning() << "Device " << deviceId << " not in database, this should never happen!";
0220                 return getAbsolutePath( -1, relativePath );
0221             }
0222             else
0223             {
0224                 absoluteUrl = QUrl::fromLocalFile( lastMountPoint.first() );
0225                 absoluteUrl = absoluteUrl.adjusted(QUrl::StripTrailingSlash);
0226                 absoluteUrl.setPath( absoluteUrl.path() + QLatin1Char('/') + rurl.path() );
0227                 absoluteUrl.setPath( QDir::cleanPath( absoluteUrl.path() ) );
0228                 //debug() << "Device " << deviceId << " not mounted, using last mount point and returning " << absoluteUrl.toLocalFile();
0229             }
0230         }
0231     }
0232 
0233     if( QFileInfo( absoluteUrl.toLocalFile() ).isDir() )
0234         absoluteUrl.setPath( absoluteUrl.adjusted( QUrl::StripTrailingSlash ).path() + '/' );
0235 
0236     return absoluteUrl.toLocalFile();
0237 }
0238 
0239 QString
0240 MountPointManager::getRelativePath( const int deviceId, const QString& absolutePath ) const
0241 {
0242     DEBUG_BLOCK
0243 
0244     debug() << absolutePath;
0245 
0246     QMutexLocker locker(&m_handlerMapMutex);
0247     if ( deviceId != -1 && m_handlerMap.contains( deviceId ) )
0248     {
0249         //FIXME max: returns garbage if the absolute path is actually not under the device's mount point
0250         return QDir( m_handlerMap[deviceId]->getDevicePath() ).relativeFilePath( absolutePath );
0251     }
0252     else
0253     {
0254         //TODO: better error handling
0255 #ifdef Q_OS_WIN32
0256         return QUrl( absolutePath ).toLocalFile();
0257 #else
0258         return QDir::root().relativeFilePath(absolutePath);
0259 #endif
0260     }
0261 }
0262 
0263 IdList
0264 MountPointManager::getMountedDeviceIds() const
0265 {
0266     m_handlerMapMutex.lock();
0267     IdList list( m_handlerMap.keys() );
0268     m_handlerMapMutex.unlock();
0269     list.append( -1 );
0270     return list;
0271 }
0272 
0273 QStringList
0274 MountPointManager::collectionFolders() const
0275 {
0276     if( !m_ready )
0277     {
0278         debug() << "requested collectionFolders from MountPointManager that is not yet ready";
0279         return QStringList();
0280     }
0281 
0282     //TODO max: cache data
0283     QStringList result;
0284     KConfigGroup folders = Amarok::config( "Collection Folders" );
0285     const IdList ids = getMountedDeviceIds();
0286 
0287     foreach( int id, ids )
0288     {
0289         const QStringList rpaths = folders.readEntry( QString::number( id ), QStringList() );
0290         foreach( const QString &strIt, rpaths )
0291         {
0292             const QUrl url = QUrl::fromLocalFile( ( strIt == "./" ) ? getMountPointForId( id ) : getAbsolutePath( id, strIt ) );
0293             const QString absPath = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0294             if ( !result.contains( absPath ) )
0295                 result.append( absPath );
0296         }
0297     }
0298 
0299     return result;
0300 }
0301 
0302 void
0303 MountPointManager::setCollectionFolders( const QStringList &folders )
0304 {
0305     typedef QMap<int, QStringList> FolderMap;
0306     KConfigGroup folderConf = Amarok::config( "Collection Folders" );
0307     FolderMap folderMap;
0308 
0309     for( const QString &folder : folders )
0310     {
0311         int id = getIdForUrl( QUrl::fromLocalFile(folder) );
0312         const QString rpath = getRelativePath( id, folder );
0313         if( folderMap.contains( id ) ) {
0314             if( !folderMap[id].contains( rpath ) )
0315                 folderMap[id].append( rpath );
0316         }
0317         else
0318             folderMap[id] = QStringList( rpath );
0319     }
0320     //make sure that collection folders on devices which are not in foldermap are deleted
0321     const IdList ids = getMountedDeviceIds();
0322     for ( int deviceId : ids )
0323     {
0324         if( !folderMap.contains( deviceId ) )
0325         {
0326             folderConf.deleteEntry( QString::number( deviceId ) );
0327         }
0328     }
0329     QMapIterator<int, QStringList> i( folderMap );
0330     while( i.hasNext() )
0331     {
0332         i.next();
0333         folderConf.writeEntry( QString::number( i.key() ), i.value() );
0334     }
0335 }
0336 
0337 void
0338 MountPointManager::slotDeviceAdded( const QString &udi )
0339 {
0340     DEBUG_BLOCK
0341     Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess );
0342     QList<Solid::Device> devices = Solid::Device::listFromQuery( predicate );
0343     //Looking for a specific udi in predicate seems flaky/buggy; the foreach loop barely
0344     //takes any time, so just be safe
0345     bool found = false;
0346     debug() << "looking for udi " << udi;
0347     foreach( const Solid::Device &device, devices )
0348     {
0349         if( device.udi() == udi )
0350         {
0351             createHandlerFromDevice( device, udi );
0352             found = true;
0353         }
0354     }
0355     if( !found )
0356         debug() << "Did not find device from Solid for udi " << udi;
0357 }
0358 
0359 void
0360 MountPointManager::slotDeviceRemoved( const QString &udi )
0361 {
0362     DEBUG_BLOCK
0363     m_handlerMapMutex.lock();
0364     foreach( DeviceHandler *dh, m_handlerMap )
0365     {
0366         if( dh->deviceMatchesUdi( udi ) )
0367         {
0368             int key = m_handlerMap.key( dh );
0369             m_handlerMap.remove( key );
0370             delete dh;
0371             debug() << "removed device " << key;
0372             m_handlerMapMutex.unlock();
0373             //we found the medium which was removed, so we can abort the loop
0374             Q_EMIT deviceRemoved( key );
0375             return;
0376         }
0377     }
0378     m_handlerMapMutex.unlock();
0379 }
0380 
0381 void MountPointManager::createHandlerFromDevice( const Solid::Device& device, const QString &udi )
0382 {
0383     DEBUG_BLOCK
0384     if ( device.isValid() )
0385     {
0386         debug() << "Device added and mounted, checking handlers";
0387         foreach( DeviceHandlerFactory *factory, m_mediumFactories )
0388         {
0389             if( factory->canHandle( device ) )
0390             {
0391                 debug() << "found handler for " << udi;
0392                 DeviceHandler *handler = factory->createHandler( device, udi, m_storage );
0393                 if( !handler )
0394                 {
0395                     debug() << "Factory " << factory->type() << "could not create device handler";
0396                     break;
0397                 }
0398                 int key = handler->getDeviceID();
0399                 m_handlerMapMutex.lock();
0400                 if( m_handlerMap.contains( key ) )
0401                 {
0402                     debug() << "Key " << key << " already exists in handlerMap, replacing";
0403                     delete m_handlerMap[key];
0404                     m_handlerMap.remove( key );
0405                 }
0406                 m_handlerMap.insert( key, handler );
0407                 m_handlerMapMutex.unlock();
0408 //                 debug() << "added device " << key << " with mount point " << volumeAccess->mountPoint();
0409                 Q_EMIT deviceAdded( key );
0410                 break;  //we found the added medium and don't have to check the other device handlers
0411             }
0412             else
0413                 debug() << "Factory can't handle device " << udi;
0414         }
0415     }
0416     else
0417         debug() << "Device not valid!";
0418 }