1 /****************************************************************************************
2 * Copyright (c) 2006-2007 Maximilian Kossick <maximilian.kossick@googlemail.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 "MountPointManager"
18
19 #include "MountPointManager.h"
20
21 #include "MediaDeviceCache.h"
22 #include "core/support/Amarok.h"
23 #include "core/support/Debug.h"
24 #include <core/storage/SqlStorage.h>
25 #include "core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h"
26 #include "core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h"
27 #include "core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h"
28
29 #include <KConfigGroup>
30 #include <Solid/Predicate>
31 #include <Solid/Device>
32
33 #include <QDesktopServices>
34 #include <QDir>
35 #include <QFile>
36 #include <QList>
37 #include <QStringList>
38 #include <QTimer>
39
MountPointManager(QObject * parent,QSharedPointer<SqlStorage> storage)40 MountPointManager::MountPointManager( QObject *parent, QSharedPointer<SqlStorage> storage )
41 : QObject( parent )
42 , m_storage( storage )
43 , m_ready( false )
44 {
45 DEBUG_BLOCK
46 setObjectName( "MountPointManager" );
47
48 if ( !Amarok::config( "Collection" ).readEntry( "DynamicCollection", true ) )
49 {
50 debug() << "Dynamic Collection deactivated in amarokrc, not loading plugins, not connecting signals";
51 m_ready = true;
52 handleMusicLocation();
53 return;
54 }
55
56 connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceAdded, this, &MountPointManager::slotDeviceAdded );
57 connect( MediaDeviceCache::instance(), &MediaDeviceCache::deviceRemoved, this, &MountPointManager::slotDeviceRemoved );
58
59 createDeviceFactories();
60 }
61
62 void
handleMusicLocation()63 MountPointManager::handleMusicLocation()
64 {
65 // For users who were using QDesktopServices::MusicLocation exclusively up
66 // to v2.2.2, which did not store the location into config.
67 // and also for versions up to 2.7-git that did write the Use MusicLocation entry
68
69 KConfigGroup folders = Amarok::config( "Collection Folders" );
70 const QString entryKey( "Use MusicLocation" );
71 if( !folders.hasKey( entryKey ) )
72 return; // good, already solved, nothing to do
73
74 // write the music location as another collection folder in this case
75 if( folders.readEntry( entryKey, false ) )
76 {
77 const QUrl musicUrl = QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::MusicLocation ) );
78 const QString musicDir = musicUrl.adjusted( QUrl::StripTrailingSlash ).toLocalFile();
79 const QDir dir( musicDir );
80 if( dir.exists() && dir.isReadable() )
81 {
82 QStringList currentFolders = collectionFolders();
83 if( !currentFolders.contains( musicDir ) )
84 setCollectionFolders( currentFolders << musicDir );
85 }
86 }
87
88 folders.deleteEntry( entryKey ); // get rid of it for good
89 }
90
~MountPointManager()91 MountPointManager::~MountPointManager()
92 {
93 DEBUG_BLOCK
94
95 m_handlerMapMutex.lock();
96 foreach( DeviceHandler *dh, m_handlerMap )
97 delete dh;
98 m_handlerMapMutex.unlock();
99
100 // DeviceHandlerFactories are memory managed using QObject parentship
101 }
102
103
104 void
createDeviceFactories()105 MountPointManager::createDeviceFactories()
106 {
107 DEBUG_BLOCK
108 QList<DeviceHandlerFactory*> factories;
109 factories << new MassStorageDeviceHandlerFactory( this );
110 factories << new NfsDeviceHandlerFactory( this );
111 factories << new SmbDeviceHandlerFactory( this );
112 foreach( DeviceHandlerFactory *factory, factories )
113 {
114 debug() << "Initializing DeviceHandlerFactory of type:" << factory->type();
115 if( factory->canCreateFromMedium() )
116 m_mediumFactories.append( factory );
117 else if (factory->canCreateFromConfig() )
118 m_remoteFactories.append( factory );
119 else //FIXME max: better error message
120 debug() << "Unknown DeviceHandlerFactory";
121 }
122
123 Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess );
124 QList<Solid::Device> devices = Solid::Device::listFromQuery( predicate );
125 foreach( const Solid::Device &device, devices )
126 createHandlerFromDevice( device, device.udi() );
127
128 m_ready = true;
129 handleMusicLocation();
130 }
131
132 int
getIdForUrl(const QUrl & url)133 MountPointManager::getIdForUrl( const QUrl &url )
134 {
135 int mountPointLength = 0;
136 int id = -1;
137 m_handlerMapMutex.lock();
138 foreach( DeviceHandler *dh, m_handlerMap )
139 {
140 if ( url.path().startsWith( dh->getDevicePath() ) && mountPointLength < dh->getDevicePath().length() )
141 {
142 id = m_handlerMap.key( dh );
143 mountPointLength = dh->getDevicePath().length();
144 }
145 }
146 m_handlerMapMutex.unlock();
147 if ( mountPointLength > 0 )
148 {
149 return id;
150 }
151 else
152 {
153 //default fallback if we could not identify the mount point.
154 //treat -1 as mount point / in all other methods
155 return -1;
156 }
157 }
158
159 bool
isMounted(const int deviceId) const160 MountPointManager::isMounted( const int deviceId ) const
161 {
162 m_handlerMapMutex.lock();
163 const bool result = m_handlerMap.contains( deviceId );
164 m_handlerMapMutex.unlock();
165 return result;
166 }
167
168 QString
getMountPointForId(const int id) const169 MountPointManager::getMountPointForId( const int id ) const
170 {
171 QString mountPoint;
172 if ( isMounted( id ) )
173 {
174 m_handlerMapMutex.lock();
175 mountPoint = m_handlerMap[id]->getDevicePath();
176 m_handlerMapMutex.unlock();
177 }
178 else
179 //TODO better error handling
180 mountPoint = '/';
181 return mountPoint;
182 }
183
184 QString
getAbsolutePath(const int deviceId,const QString & relativePath) const185 MountPointManager::getAbsolutePath( const int deviceId, const QString& relativePath ) const
186 {
187 if( QDir( relativePath ).isAbsolute() )
188 {
189 //debug() << "relativePath is already absolute";
190 return relativePath;
191 }
192
193 QUrl rurl = QUrl::fromLocalFile( relativePath );
194 QUrl absoluteUrl = QUrl::fromLocalFile( QDir::rootPath() );
195
196 //debug() << "id is " << deviceId << ", relative path is " << relativePath;
197 if ( deviceId == -1 )
198 {
199 absoluteUrl.setPath( QDir::rootPath() + relativePath );
200 absoluteUrl.setPath( QDir::cleanPath( absoluteUrl.path() ) );
201 //debug() << "Deviceid is -1, using relative Path as absolute Path, returning " << absoluteUrl.toLocalFile();
202 }
203 else
204 {
205 m_handlerMapMutex.lock();
206 if ( m_handlerMap.contains( deviceId ) )
207 {
208 m_handlerMap[deviceId]->getURL( absoluteUrl, rurl );
209 m_handlerMapMutex.unlock();
210 }
211 else
212 {
213 m_handlerMapMutex.unlock();
214 const QStringList lastMountPoint = m_storage->query( QString( "SELECT lastmountpoint FROM devices WHERE id = %1" )
215 .arg( deviceId ) );
216 if ( lastMountPoint.isEmpty() )
217 {
218 //hmm, no device with that id in the DB...serious problem
219 warning() << "Device " << deviceId << " not in database, this should never happen!";
220 return getAbsolutePath( -1, relativePath );
221 }
222 else
223 {
224 absoluteUrl = QUrl::fromLocalFile( lastMountPoint.first() );
225 absoluteUrl = absoluteUrl.adjusted(QUrl::StripTrailingSlash);
226 absoluteUrl.setPath( absoluteUrl.path() + QLatin1Char('/') + rurl.path() );
227 absoluteUrl.setPath( QDir::cleanPath( absoluteUrl.path() ) );
228 //debug() << "Device " << deviceId << " not mounted, using last mount point and returning " << absoluteUrl.toLocalFile();
229 }
230 }
231 }
232
233 if( QFileInfo( absoluteUrl.toLocalFile() ).isDir() )
234 absoluteUrl.setPath( absoluteUrl.adjusted( QUrl::StripTrailingSlash ).path() + '/' );
235
236 return absoluteUrl.toLocalFile();
237 }
238
239 QString
getRelativePath(const int deviceId,const QString & absolutePath) const240 MountPointManager::getRelativePath( const int deviceId, const QString& absolutePath ) const
241 {
242 DEBUG_BLOCK
243
244 debug() << absolutePath;
245
246 QMutexLocker locker(&m_handlerMapMutex);
247 if ( deviceId != -1 && m_handlerMap.contains( deviceId ) )
248 {
249 //FIXME max: returns garbage if the absolute path is actually not under the device's mount point
250 return QDir( m_handlerMap[deviceId]->getDevicePath() ).relativeFilePath( absolutePath );
251 }
252 else
253 {
254 //TODO: better error handling
255 #ifdef Q_OS_WIN32
256 return QUrl( absolutePath ).toLocalFile();
257 #else
258 return QDir::root().relativeFilePath(absolutePath);
259 #endif
260 }
261 }
262
263 IdList
getMountedDeviceIds() const264 MountPointManager::getMountedDeviceIds() const
265 {
266 m_handlerMapMutex.lock();
267 IdList list( m_handlerMap.keys() );
268 m_handlerMapMutex.unlock();
269 list.append( -1 );
270 return list;
271 }
272
273 QStringList
collectionFolders() const274 MountPointManager::collectionFolders() const
275 {
276 if( !m_ready )
277 {
278 debug() << "requested collectionFolders from MountPointManager that is not yet ready";
279 return QStringList();
280 }
281
282 //TODO max: cache data
283 QStringList result;
284 KConfigGroup folders = Amarok::config( "Collection Folders" );
285 const IdList ids = getMountedDeviceIds();
286
287 foreach( int id, ids )
288 {
289 const QStringList rpaths = folders.readEntry( QString::number( id ), QStringList() );
290 foreach( const QString &strIt, rpaths )
291 {
292 const QUrl url = QUrl::fromLocalFile( ( strIt == "./" ) ? getMountPointForId( id ) : getAbsolutePath( id, strIt ) );
293 const QString absPath = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
294 if ( !result.contains( absPath ) )
295 result.append( absPath );
296 }
297 }
298
299 return result;
300 }
301
302 void
setCollectionFolders(const QStringList & folders)303 MountPointManager::setCollectionFolders( const QStringList &folders )
304 {
305 typedef QMap<int, QStringList> FolderMap;
306 KConfigGroup folderConf = Amarok::config( "Collection Folders" );
307 FolderMap folderMap;
308
309 for( const QString &folder : folders )
310 {
311 int id = getIdForUrl( QUrl::fromLocalFile(folder) );
312 const QString rpath = getRelativePath( id, folder );
313 if( folderMap.contains( id ) ) {
314 if( !folderMap[id].contains( rpath ) )
315 folderMap[id].append( rpath );
316 }
317 else
318 folderMap[id] = QStringList( rpath );
319 }
320 //make sure that collection folders on devices which are not in foldermap are deleted
321 const IdList ids = getMountedDeviceIds();
322 for ( int deviceId : ids )
323 {
324 if( !folderMap.contains( deviceId ) )
325 {
326 folderConf.deleteEntry( QString::number( deviceId ) );
327 }
328 }
329 QMapIterator<int, QStringList> i( folderMap );
330 while( i.hasNext() )
331 {
332 i.next();
333 folderConf.writeEntry( QString::number( i.key() ), i.value() );
334 }
335 }
336
337 void
slotDeviceAdded(const QString & udi)338 MountPointManager::slotDeviceAdded( const QString &udi )
339 {
340 DEBUG_BLOCK
341 Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess );
342 QList<Solid::Device> devices = Solid::Device::listFromQuery( predicate );
343 //Looking for a specific udi in predicate seems flaky/buggy; the foreach loop barely
344 //takes any time, so just be safe
345 bool found = false;
346 debug() << "looking for udi " << udi;
347 foreach( const Solid::Device &device, devices )
348 {
349 if( device.udi() == udi )
350 {
351 createHandlerFromDevice( device, udi );
352 found = true;
353 }
354 }
355 if( !found )
356 debug() << "Did not find device from Solid for udi " << udi;
357 }
358
359 void
slotDeviceRemoved(const QString & udi)360 MountPointManager::slotDeviceRemoved( const QString &udi )
361 {
362 DEBUG_BLOCK
363 m_handlerMapMutex.lock();
364 foreach( DeviceHandler *dh, m_handlerMap )
365 {
366 if( dh->deviceMatchesUdi( udi ) )
367 {
368 int key = m_handlerMap.key( dh );
369 m_handlerMap.remove( key );
370 delete dh;
371 debug() << "removed device " << key;
372 m_handlerMapMutex.unlock();
373 //we found the medium which was removed, so we can abort the loop
374 Q_EMIT deviceRemoved( key );
375 return;
376 }
377 }
378 m_handlerMapMutex.unlock();
379 }
380
createHandlerFromDevice(const Solid::Device & device,const QString & udi)381 void MountPointManager::createHandlerFromDevice( const Solid::Device& device, const QString &udi )
382 {
383 DEBUG_BLOCK
384 if ( device.isValid() )
385 {
386 debug() << "Device added and mounted, checking handlers";
387 foreach( DeviceHandlerFactory *factory, m_mediumFactories )
388 {
389 if( factory->canHandle( device ) )
390 {
391 debug() << "found handler for " << udi;
392 DeviceHandler *handler = factory->createHandler( device, udi, m_storage );
393 if( !handler )
394 {
395 debug() << "Factory " << factory->type() << "could not create device handler";
396 break;
397 }
398 int key = handler->getDeviceID();
399 m_handlerMapMutex.lock();
400 if( m_handlerMap.contains( key ) )
401 {
402 debug() << "Key " << key << " already exists in handlerMap, replacing";
403 delete m_handlerMap[key];
404 m_handlerMap.remove( key );
405 }
406 m_handlerMap.insert( key, handler );
407 m_handlerMapMutex.unlock();
408 // debug() << "added device " << key << " with mount point " << volumeAccess->mountPoint();
409 Q_EMIT deviceAdded( key );
410 break; //we found the added medium and don't have to check the other device handlers
411 }
412 else
413 debug() << "Factory can't handle device " << udi;
414 }
415 }
416 else
417 debug() << "Device not valid!";
418 }
419