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 }