You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/mountpointmanager.cpp

605 lines
21 KiB

/*
* Copyright (c) 2006-2007 Maximilian Kossick <maximilian.kossick@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#define DEBUG_PREFIX "MountPointManager"
#include "debug.h"
#include "amarok.h"
#include "amarokconfig.h" //used in init()
#include "collectiondb.h"
#include "devicemanager.h"
#include "mountpointmanager.h"
#include "pluginmanager.h"
#include "statusbar.h"
#include <kglobal.h> //used in init()
#include <ktrader.h>
#include <tqfile.h>
#include <tqstringlist.h>
#include <tqtimer.h>
#include <tqvaluelist.h>
typedef Medium::List MediumList;
MountPointManager::MountPointManager()
: TQObject( 0, "MountPointManager" )
, m_noDeviceManager( false )
{
if ( !Amarok::config( "Collection" )->readBoolEntry( "DynamicCollection", true ) )
{
debug() << "Dynamic Collection deactivated in amarokrc, not loading plugins, not connecting signals" << endl;
return;
}
//we are only interested in the mounting or unmounting of mediums
//therefore it is enough to listen to DeviceManager's mediumChanged signal
if (DeviceManager::instance()->isValid() )
{
connect( DeviceManager::instance(), TQT_SIGNAL( mediumAdded( const Medium*, TQString ) ), TQT_SLOT( mediumAdded( const Medium* ) ) );
connect( DeviceManager::instance(), TQT_SIGNAL( mediumChanged( const Medium*, TQString ) ), TQT_SLOT( mediumChanged( const Medium* ) ) );
connect( DeviceManager::instance(), TQT_SIGNAL( mediumRemoved( const Medium*, TQString ) ), TQT_SLOT( mediumRemoved( const Medium* ) ) );
}
else
{
handleMissingMediaManager();
}
m_mediumFactories.setAutoDelete( true );
m_remoteFactories.setAutoDelete( true );
init();
CollectionDB *collDB = CollectionDB::instance();
if ( collDB->adminValue( "Database Stats Version" ).toInt() >= 9 && /* make sure that deviceid actually exists*/
collDB->query( "SELECT COUNT(url) FROM statistics WHERE deviceid = -2;" ).first().toInt() != 0 )
{
connect( this, TQT_SIGNAL( mediumConnected( int ) ), TQT_SLOT( migrateStatistics() ) );
TQTimer::singleShot( 0, this, TQT_SLOT( migrateStatistics() ) );
}
connect( this, TQT_SIGNAL( mediumConnected( int ) ), TQT_SLOT( updateStatisticsURLs() ) );
updateStatisticsURLs();
}
MountPointManager::~MountPointManager()
{
m_handlerMapMutex.lock();
foreachType( HandlerMap, m_handlerMap )
{
delete it.data();
}
m_handlerMapMutex.unlock();
}
MountPointManager * MountPointManager::instance( )
{
static MountPointManager instance;
return &instance;
}
void
MountPointManager::init()
{
DEBUG_BLOCK
KTrader::OfferList plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'device'" );
debug() << "Received [" << TQString::number( plugins.count() ) << "] device plugin offers" << endl;
foreachType( KTrader::OfferList, plugins )
{
Amarok::Plugin *plugin = PluginManager::createFromService( *it );
if( plugin )
{
DeviceHandlerFactory *factory = static_cast<DeviceHandlerFactory*>( plugin );
if ( factory->canCreateFromMedium() )
m_mediumFactories.append( factory );
else if (factory->canCreateFromConfig() )
m_remoteFactories.append( factory );
else
//FIXME max: better error message
debug() << "Unknown DeviceHandlerFactory" << endl;
}
else debug() << "Plugin could not be loaded" << endl;
}
//we need access to the unfiltered data
MediumList list = DeviceManager::instance()->getDeviceList();
foreachType ( MediumList, list )
{
mediumChanged( &(*it) );
}
if( !KGlobal::config()->hasGroup( "Collection Folders" ) )
{
TQStringList folders = AmarokConfig::collectionFolders();
if( !folders.isEmpty() )
setCollectionFolders( folders );
}
}
int
MountPointManager::getIdForUrl( KURL url )
{
uint mountPointLength = 0;
int id = -1;
m_handlerMapMutex.lock();
foreachType( HandlerMap, m_handlerMap )
{
if ( url.path().startsWith( it.data()->getDevicePath() ) && mountPointLength < it.data()->getDevicePath().length() )
{
id = it.key();
mountPointLength = it.data()->getDevicePath().length();
}
}
m_handlerMapMutex.unlock();
if ( mountPointLength > 0 )
{
return id;
}
else
{
//default fallback if we could not identify the mount point.
//treat -1 as mount point / in al other methods
return -1;
}
}
int
MountPointManager::getIdForUrl( const TQString &url )
{
return getIdForUrl( KURL::fromPathOrURL( url ) );
}
bool
MountPointManager::isMounted ( const int deviceId ) const {
m_handlerMapMutex.lock();
bool result = m_handlerMap.contains( deviceId );
m_handlerMapMutex.unlock();
return result;
}
TQString
MountPointManager::getMountPointForId( const int id ) const
{
TQString mountPoint;
if ( isMounted( id ) )
{
m_handlerMapMutex.lock();
mountPoint = m_handlerMap[id]->getDevicePath();
m_handlerMapMutex.unlock();
}
else
//TODO better error handling
mountPoint = "/";
return mountPoint;
}
void
MountPointManager::getAbsolutePath( const int deviceId, const KURL& relativePath, KURL& absolutePath) const
{
//debug() << "id is " << deviceId << ", relative path is " << relativePath.path() << endl;
if ( deviceId == -1 )
{
absolutePath.setPath( "/" );
absolutePath.addPath( relativePath.path() );
absolutePath.cleanPath();
//debug() << "Deviceid is -1, using relative Path as absolute Path, returning " << absolutePath.path() << endl;
return;
}
m_handlerMapMutex.lock();
if ( m_handlerMap.contains( deviceId ) )
{
m_handlerMap[deviceId]->getURL( absolutePath, relativePath );
m_handlerMapMutex.unlock();
}
else
{
m_handlerMapMutex.unlock();
TQStringList lastMountPoint = CollectionDB::instance()->query(
TQString( "SELECT lastmountpoint FROM devices WHERE id = %1" )
.arg( deviceId ) );
if ( lastMountPoint.count() == 0 )
{
//hmm, no device with that id in the DB...serious problem
absolutePath.setPath( "/" );
absolutePath.addPath( relativePath.path() );
absolutePath.cleanPath();
warning() << "Device " << deviceId << " not in database, this should never happen! Returning " << absolutePath.path() << endl;
}
else
{
absolutePath.setPath( lastMountPoint.first() );
absolutePath.addPath( relativePath.path() );
absolutePath.cleanPath();
// debug() << "Device " << deviceId << " not mounted, using last mount point and returning " << absolutePath.path() << endl;
}
}
}
TQString
MountPointManager::getAbsolutePath( const int deviceId, const TQString& relativePath ) const
{
KURL rpath;
rpath.setProtocol("file");
rpath.setPath( relativePath );
KURL url;
getAbsolutePath( deviceId, rpath, url );
return url.path();
}
void
MountPointManager::getRelativePath( const int deviceId, const KURL& absolutePath, KURL& relativePath ) const
{
m_handlerMapMutex.lock();
if ( deviceId != -1 && m_handlerMap.contains( deviceId ) )
{
//FIXME max: returns garbage if the absolute path is actually not under the device's mount point
TQString rpath = KURL::relativePath( m_handlerMap[deviceId]->getDevicePath(), absolutePath.path() );
m_handlerMapMutex.unlock();
relativePath.setPath( rpath );
}
else
{
m_handlerMapMutex.unlock();
//TODO: better error handling
TQString rpath = KURL::relativePath( "/", absolutePath.path() );
relativePath.setPath( rpath );
}
}
TQString
MountPointManager::getRelativePath( const int deviceId, const TQString& absolutePath ) const
{
KURL url;
getRelativePath( deviceId, KURL::fromPathOrURL( absolutePath ), url );
return url.path();
}
void
MountPointManager::mediumChanged( const Medium *m )
{
DEBUG_BLOCK
if ( !m ) return;
if ( m->isMounted() )
{
foreachType( FactoryList, m_mediumFactories )
{
if ( (*it)->canHandle ( m ) )
{
debug() << "found handler for " << m->id() << endl;
DeviceHandler *handler = (*it)->createHandler( m );
if( !handler )
{
debug() << "Factory " << (*it)->type() << "could not create device handler" << endl;
break;
}
int key = handler->getDeviceID();
m_handlerMapMutex.lock();
if ( m_handlerMap.contains( key ) )
{
debug() << "Key " << key << " already exists in handlerMap, replacing" << endl;
delete m_handlerMap[key];
m_handlerMap.erase( key );
}
m_handlerMap.insert( key, handler );
m_handlerMapMutex.unlock();
debug() << "added device " << key << " with mount point " << m->mountPoint() << endl;
emit mediumConnected( key );
break; //we found the added medium and don't have to check the other device handlers
}
}
}
else
{
m_handlerMapMutex.lock();
foreachType( HandlerMap, m_handlerMap )
{
if ( it.data()->deviceIsMedium( m ) )
{
delete it.data();
int key = it.key();
m_handlerMap.erase( key );
debug() << "removed device " << key << endl;
m_handlerMapMutex.unlock();
emit mediumRemoved( key );
//we found the medium which was removed, so we can abort the loop
return;
}
}
m_handlerMapMutex.unlock();
}
}
void
MountPointManager::mediumRemoved( const Medium *m )
{
DEBUG_BLOCK
if ( !m )
{
//reinit?
}
else
{
//this works for USB devices, special cases might be required for other devices
m_handlerMapMutex.lock();
foreachType( HandlerMap, m_handlerMap )
{
if ( it.data()->deviceIsMedium( m ) )
{
delete it.data();
int key = it.key();
m_handlerMap.erase( key );
debug() << "removed device " << key << endl;
m_handlerMapMutex.unlock();
emit mediumRemoved( key );
//we found the medium which was removed, so we can abort the loop
return;
}
}
m_handlerMapMutex.unlock();
}
}
void
MountPointManager::mediumAdded( const Medium *m )
{
DEBUG_BLOCK
if ( !m ) return;
if ( m->isMounted() )
{
debug() << "Device added and mounted, checking handlers" << endl;
foreachType( FactoryList, m_mediumFactories )
{
if ( (*it)->canHandle ( m ) )
{
debug() << "found handler for " << m->id() << endl;
DeviceHandler *handler = (*it)->createHandler( m );
if( !handler )
{
debug() << "Factory " << (*it)->type() << "could not create device handler" << endl;
break;
}
int key = handler->getDeviceID();
m_handlerMapMutex.lock();
if ( m_handlerMap.contains( key ) )
{
debug() << "Key " << key << " already exists in handlerMap, replacing" << endl;
delete m_handlerMap[key];
m_handlerMap.erase( key );
}
m_handlerMap.insert( key, handler );
m_handlerMapMutex.unlock();
debug() << "added device " << key << " with mount point " << m->mountPoint() << endl;
emit mediumConnected( key );
break; //we found the added medium and don't have to check the other device handlers
}
}
}
}
IdList
MountPointManager::getMountedDeviceIds() const {
m_handlerMapMutex.lock();
IdList list( m_handlerMap.keys() );
m_handlerMapMutex.unlock();
list.append( -1 );
return list;
}
TQStringList
MountPointManager::collectionFolders( )
{
//TODO max: cache data
TQStringList result;
KConfig* const folders = Amarok::config( "Collection Folders" );
IdList ids = getMountedDeviceIds();
foreachType( IdList, ids )
{
TQStringList rpaths = folders->readListEntry( TQString::number( *it ) );
for( TQStringList::ConstIterator strIt = rpaths.begin(), end = rpaths.end(); strIt != end; ++strIt )
{
TQString absPath;
if ( *strIt == "./" )
{
absPath = getMountPointForId( *it );
}
else
{
absPath = getAbsolutePath( *it, *strIt );
}
if ( !result.contains( absPath ) )
result.append( absPath );
}
}
return result;
}
void
MountPointManager::setCollectionFolders( const TQStringList &folders )
{
//TODO max: cache data
typedef TQMap<int, TQStringList> FolderMap;
KConfig* const folderConf = Amarok::config( "Collection Folders" );
FolderMap folderMap;
foreach( folders )
{
int id = getIdForUrl( *it );
TQString rpath = getRelativePath( id, *it );
if ( folderMap.contains( id ) ) {
if ( !folderMap[id].contains( rpath ) )
folderMap[id].append( rpath );
}
else
folderMap[id] = TQStringList( rpath );
}
//make sure that collection folders on devices which are not in foldermap are deleted
IdList ids = getMountedDeviceIds();
foreachType( IdList, ids )
{
if( !folderMap.contains( *it ) )
{
folderConf->deleteEntry( TQString::number( *it ) );
}
}
foreachType( FolderMap, folderMap )
{
folderConf->writeEntry( TQString::number( it.key() ), it.data() );
}
}
void
MountPointManager::migrateStatistics()
{
TQStringList urls = CollectionDB::instance()->query( "SELECT url FROM statistics WHERE deviceid = -2;" );
foreach( urls )
{
if ( TQFile::exists( *it) )
{
int deviceid = getIdForUrl( *it );
TQString rpath = getRelativePath( deviceid, *it );
TQString update = TQString( "UPDATE statistics SET deviceid = %1, url = '%2'" )
.arg( deviceid )
.arg( CollectionDB::instance()->escapeString( rpath ) );
update += TQString( " WHERE url = '%1' AND deviceid = -2;" )
.arg( CollectionDB::instance()->escapeString( *it ) );
CollectionDB::instance()->query( update );
}
}
}
void
MountPointManager::updateStatisticsURLs( bool changed )
{
if ( changed )
TQTimer::singleShot( 0, this, TQT_SLOT( startStatisticsUpdateJob() ) );
}
void
MountPointManager::startStatisticsUpdateJob()
{
ThreadManager::instance()->queueJob( new UrlUpdateJob( this ) );
}
void
MountPointManager::handleMissingMediaManager()
{
//TODO this method should activate a fallback mode which simply shows all songs and uses the
//device's last mount point to build the absolute path
m_noDeviceManager = true;
//Amarok::StatusBar::instance()->longMessage( i18n( "BlaBla" ), KDE::StatusBar::Warning );
}
void
MountPointManager::checkDeviceAvailability()
{
//code to actively scan for devices which are not supported by KDE mediamanager should go here
//method is not actually called yet
}
bool UrlUpdateJob::doJob( )
{
DEBUG_BLOCK
updateStatistics();
updateLabels();
return true;
}
void UrlUpdateJob::updateStatistics( )
{
CollectionDB *collDB = CollectionDB::instance();
MountPointManager *mpm = MountPointManager::instance();
TQStringList urls = collDB->query( "SELECT s.deviceid,s.url "
"FROM statistics AS s LEFT JOIN tags AS t ON s.deviceid = t.deviceid AND s.url = t.url "
"WHERE t.url IS NULL AND s.deviceid != -2;" );
debug() << "Trying to update " << urls.count() / 2 << " statistics rows" << endl;
foreach( urls )
{
int deviceid = (*it).toInt();
TQString rpath = *++it;
TQString realURL = mpm->getAbsolutePath( deviceid, rpath );
if( TQFile::exists( realURL ) )
{
int newDeviceid = mpm->getIdForUrl( realURL );
if( newDeviceid == deviceid )
continue;
TQString newRpath = mpm->getRelativePath( newDeviceid, realURL );
int statCount = collDB->query(
TQString( "SELECT COUNT( url ) FROM statistics WHERE deviceid = %1 AND url = '%2';" )
.arg( newDeviceid )
.arg( collDB->escapeString( newRpath ) ) ).first().toInt();
if( statCount )
continue; //statistics row with new URL/deviceid values already exists
TQString sql = TQString( "UPDATE statistics SET deviceid = %1, url = '%2'" )
.arg( newDeviceid ).arg( collDB->escapeString( newRpath ) );
sql += TQString( " WHERE deviceid = %1 AND url = '%2';" )
.arg( deviceid ).arg( collDB->escapeString( rpath ) );
collDB->query( sql );
}
}
}
void UrlUpdateJob::updateLabels( )
{
CollectionDB *collDB = CollectionDB::instance();
MountPointManager *mpm = MountPointManager::instance();
TQStringList labels = collDB->query( "SELECT l.deviceid,l.url "
"FROM tags_labels AS l LEFT JOIN tags as t ON l.deviceid = t.deviceid AND l.url = t.url "
"WHERE t.url IS NULL;" );
debug() << "Trying to update " << labels.count() / 2 << " tags_labels rows" << endl;
foreach( labels )
{
int deviceid = (*it).toInt();
TQString rpath = *++it;
TQString realUrl = mpm->getAbsolutePath( deviceid, rpath );
if( TQFile::exists( realUrl ) )
{
int newDeviceid = mpm->getIdForUrl( realUrl );
if( newDeviceid == deviceid )
continue;
TQString newRpath = mpm->getRelativePath( newDeviceid, realUrl );
//only update rows if there is not already a row with the new deviceid/rpath and the same labelid
TQStringList labelids = collDB->query(
TQString( "SELECT labelid FROM tags_labels WHERE deviceid = %1 AND url = '%2';" )
.arg( TQString::number( newDeviceid ), collDB->escapeString( newRpath ) ) );
TQString existingLabelids;
if( !labelids.isEmpty() )
{
existingLabelids = " AND labelid NOT IN (";
foreach( labelids )
{
if( it != labelids.begin() )
existingLabelids += ',';
existingLabelids += *it;
}
existingLabelids += ')';
}
TQString sql = TQString( "UPDATE tags_labels SET deviceid = %1, url = '%2' "
"WHERE deviceid = %3 AND url = '%4'%5;" )
.arg( newDeviceid )
.arg( collDB->escapeString( newRpath ),
TQString::number( deviceid ),
collDB->escapeString( rpath ),
existingLabelids );
collDB->query( sql );
}
}
}
#include "mountpointmanager.moc"