1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-05 13:47:26 +02:00

Implement watched folders and scan-on-startup. Folders are scanned after 10 seconds without a change. Also handles deferring scans of directories if attempted during an ongoing scan, both for recursive and non-recursive scans.

Fixes TWK-30 / THK-30.
This commit is contained in:
Jeff Mitchell
2011-04-02 00:18:29 -04:00
parent d818a7f697
commit 3bc496eaaf
14 changed files with 185 additions and 50 deletions

View File

@@ -124,7 +124,7 @@ void
ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::ACL type ) ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::ACL type )
{ {
TomahawkSettings *s = TomahawkSettings::instance(); TomahawkSettings *s = TomahawkSettings::instance();
if( !s->scannerPath().contains( path ) ) if( !s->scannerPaths().contains( path ) )
{ {
qDebug() << "path selected is not in our scanner path!"; qDebug() << "path selected is not in our scanner path!";
return; return;

View File

@@ -32,6 +32,7 @@ Database::instance()
Database::Database( const QString& dbname, QObject* parent ) Database::Database( const QString& dbname, QObject* parent )
: QObject( parent ) : QObject( parent )
, m_ready( false )
, m_impl( new DatabaseImpl( dbname, this ) ) , m_impl( new DatabaseImpl( dbname, this ) )
, m_workerRW( new DatabaseWorker( m_impl, this, true ) ) , m_workerRW( new DatabaseWorker( m_impl, this, true ) )
{ {
@@ -39,6 +40,7 @@ Database::Database( const QString& dbname, QObject* parent )
connect( m_impl, SIGNAL( indexReady() ), SIGNAL( indexReady() ) ); connect( m_impl, SIGNAL( indexReady() ), SIGNAL( indexReady() ) );
connect( m_impl, SIGNAL( indexReady() ), SIGNAL( ready() ) ); connect( m_impl, SIGNAL( indexReady() ), SIGNAL( ready() ) );
connect( m_impl, SIGNAL( indexReady() ), SLOT( setIsReadyTrue() ) );
m_workerRW->start(); m_workerRW->start();
} }

View File

@@ -53,6 +53,8 @@ public:
void loadIndex(); void loadIndex();
bool isReady() const { return m_ready; }
signals: signals:
void indexReady(); // search index void indexReady(); // search index
void ready(); void ready();
@@ -63,7 +65,11 @@ signals:
public slots: public slots:
void enqueue( QSharedPointer<DatabaseCommand> lc ); void enqueue( QSharedPointer<DatabaseCommand> lc );
private slots:
void setIsReadyTrue() { m_ready = true; }
private: private:
bool m_ready;
DatabaseImpl* m_impl; DatabaseImpl* m_impl;
DatabaseWorker* m_workerRW; DatabaseWorker* m_workerRW;
QHash< QString, DatabaseWorker* > m_workers; QHash< QString, DatabaseWorker* > m_workers;

View File

@@ -84,6 +84,8 @@ signals:
public slots: public slots:
private: private:
bool m_ready;
bool updateSchema( int currentver ); bool updateSchema( int currentver );
QSqlDatabase db; QSqlDatabase db;

View File

@@ -66,29 +66,50 @@ TomahawkSettings::~TomahawkSettings()
QStringList QStringList
TomahawkSettings::scannerPath() const TomahawkSettings::scannerPaths()
{ {
//FIXME: After enough time, remove this hack (and make const)
#ifndef TOMAHAWK_HEADLESS #ifndef TOMAHAWK_HEADLESS
return value( "scannerpath", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toStringList(); if( value( "scannerpaths" ).isNull() )
setValue( "scannerpaths", value( "scannerpath" ) );
return value( "scannerpaths", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toStringList();
#else #else
return value( "scannerpath", "" ).toStringList(); if( value( "scannerpaths" ).isNull() )
setValue( "scannerpaths", value( "scannerpath" ) );
return value( "scannerpaths", "" ).toStringList();
#endif #endif
} }
void void
TomahawkSettings::setScannerPath( const QStringList& path ) TomahawkSettings::setScannerPaths( const QStringList& paths )
{ {
setValue( "scannerpath", path ); setValue( "scannerpaths", paths );
} }
bool bool
TomahawkSettings::hasScannerPath() const TomahawkSettings::hasScannerPaths() const
{ {
return contains( "scannerpath" ); //FIXME: After enough time, remove this hack
return contains( "scannerpaths" ) || contains( "scannerpath" );
} }
bool
TomahawkSettings::watchForChanges() const
{
return value( "watchForChanges", true ).toBool();
}
void
TomahawkSettings::setWatchForChanges( bool watch )
{
setValue( "watchForChanges", watch );
}
void void
TomahawkSettings::setAcceptedLegalWarning( bool accept ) TomahawkSettings::setAcceptedLegalWarning( bool accept )
{ {

View File

@@ -41,9 +41,12 @@ public:
void applyChanges() { emit changed(); } void applyChanges() { emit changed(); }
/// General settings /// General settings
QStringList scannerPath() const; /// QDesktopServices::MusicLocation by default QStringList scannerPaths(); /// QDesktopServices::MusicLocation by default
void setScannerPath( const QStringList& path ); void setScannerPaths( const QStringList& paths );
bool hasScannerPath() const; bool hasScannerPaths() const;
bool watchForChanges() const;
void setWatchForChanges( bool watch );
bool acceptedLegalWarning() const; bool acceptedLegalWarning() const;
void setAcceptedLegalWarning( bool accept ); void setAcceptedLegalWarning( bool accept );

View File

@@ -31,14 +31,16 @@ using namespace Tomahawk;
void void
DirLister::go() DirLister::go()
{ {
scanDir( m_dir, 0 ); foreach( QString dir, m_dirs )
scanDir( QDir( dir, 0 ), 0, ( m_recursive ? DirLister::Recursive : DirLister::NonRecursive ) );
emit finished( m_newdirmtimes ); emit finished( m_newdirmtimes );
} }
void void
DirLister::scanDir( QDir dir, int depth ) DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode )
{ {
qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath();
QFileInfoList dirs; QFileInfoList dirs;
const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t();
m_newdirmtimes.insert( dir.absolutePath(), mtime ); m_newdirmtimes.insert( dir.absolutePath(), mtime );
@@ -65,14 +67,18 @@ DirLister::scanDir( QDir dir, int depth )
foreach( const QFileInfo& di, dirs ) foreach( const QFileInfo& di, dirs )
{ {
scanDir( di.absoluteFilePath(), depth + 1 ); if( mode == DirLister::Recursive || !m_dirmtimes.contains( di.absolutePath() ) )
scanDir( di.absoluteFilePath(), depth + 1, DirLister::Recursive );
else //should be the non-recursive case since the second test above should only happen with a new dir
scanDir( di.absoluteFilePath(), depth + 1, DirLister::MTimeOnly );
} }
} }
MusicScanner::MusicScanner( const QStringList& dirs, quint32 bs ) MusicScanner::MusicScanner( const QStringList& dirs, bool recursive, quint32 bs )
: QObject() : QObject()
, m_dirs( dirs ) , m_dirs( dirs )
, m_recursive( recursive )
, m_batchsize( bs ) , m_batchsize( bs )
, m_dirLister( 0 ) , m_dirLister( 0 )
, m_dirListerThreadController( 0 ) , m_dirListerThreadController( 0 )
@@ -150,8 +156,7 @@ MusicScanner::scan()
m_dirListerThreadController = new QThread( this ); m_dirListerThreadController = new QThread( this );
//FIXME: MULTIPLECOLLECTIONDIRS m_dirLister = new DirLister( m_dirs, m_dirmtimes, m_recursive );
m_dirLister = new DirLister( QDir( m_dirs.first(), 0 ), m_dirmtimes );
m_dirLister->moveToThread( m_dirListerThreadController ); m_dirLister->moveToThread( m_dirListerThreadController );
connect( m_dirLister, SIGNAL( fileToScan( QFileInfo ) ), connect( m_dirLister, SIGNAL( fileToScan( QFileInfo ) ),

View File

@@ -39,8 +39,15 @@ class DirLister : public QObject
Q_OBJECT Q_OBJECT
public: public:
DirLister( QDir d, QMap<QString, unsigned int>& mtimes )
: QObject(), m_dir( d ), m_dirmtimes( mtimes ) enum Mode {
NonRecursive,
Recursive,
MTimeOnly
};
DirLister( QStringList dirs, QMap<QString, unsigned int>& mtimes, bool recursive )
: QObject(), m_dirs( dirs ), m_dirmtimes( mtimes ), m_recursive( recursive )
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
} }
@@ -56,11 +63,13 @@ signals:
private slots: private slots:
void go(); void go();
void scanDir( QDir dir, int depth ); void scanDir( QDir dir, int depth, DirLister::Mode mode );
private: private:
QDir m_dir; QStringList m_dirs;
QMap<QString, unsigned int> m_dirmtimes; QMap<QString, unsigned int> m_dirmtimes;
bool m_recursive;
QMap<QString, unsigned int> m_newdirmtimes; QMap<QString, unsigned int> m_newdirmtimes;
}; };
@@ -69,7 +78,7 @@ class MusicScanner : public QObject
Q_OBJECT Q_OBJECT
public: public:
MusicScanner( const QStringList& dirs, quint32 bs = 0 ); MusicScanner( const QStringList& dirs, bool recursive = true, quint32 bs = 0 );
~MusicScanner(); ~MusicScanner();
signals: signals:
@@ -105,6 +114,7 @@ private:
QMap<QString, unsigned int> m_newdirmtimes; QMap<QString, unsigned int> m_newdirmtimes;
QList<QVariant> m_scannedfiles; QList<QVariant> m_scannedfiles;
bool m_recursive;
quint32 m_batchsize; quint32 m_batchsize;
DirLister* m_dirLister; DirLister* m_dirLister;

View File

@@ -45,20 +45,33 @@ ScanManager::ScanManager( QObject* parent )
: QObject( parent ) : QObject( parent )
, m_scanner( 0 ) , m_scanner( 0 )
, m_musicScannerThreadController( 0 ) , m_musicScannerThreadController( 0 )
, m_currScannerPaths()
, m_dirWatcher( 0 ) , m_dirWatcher( 0 )
, m_queuedScanTimer( 0 )
, m_deferredScanTimer( 0 )
, m_queuedChangedDirs()
, m_deferredDirs()
{ {
s_instance = this; s_instance = this;
m_dirWatcher = new QFileSystemWatcher( parent ); m_queuedScanTimer = new QTimer( this );
m_queuedScanTimer->setSingleShot( true );
m_deferredScanTimer = new QTimer( this );
m_deferredScanTimer->setSingleShot( false );
m_deferredScanTimer->setInterval( 1000 );
m_dirWatcher = new QFileSystemWatcher( this );
connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) );
connect( m_queuedScanTimer, SIGNAL( timeout() ), SLOT( queuedScanTimeout() ) );
connect( m_deferredScanTimer, SIGNAL( timeout() ), SLOT( deferredScanTimeout() ) );
connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) ); connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) );
if ( TomahawkSettings::instance()->hasScannerPath() ) if ( TomahawkSettings::instance()->hasScannerPaths() )
m_currScannerPath = TomahawkSettings::instance()->scannerPath(); m_currScannerPaths = TomahawkSettings::instance()->scannerPaths();
qDebug() << "loading initial directories to watch"; qDebug() << "loading initial directories to watch";
QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) );
m_deferredScanTimer->start();
} }
@@ -91,14 +104,18 @@ ScanManager::~ScanManager()
void void
ScanManager::onSettingsChanged() ScanManager::onSettingsChanged()
{ {
if ( TomahawkSettings::instance()->hasScannerPath() && if ( TomahawkSettings::instance()->hasScannerPaths() &&
m_currScannerPath != TomahawkSettings::instance()->scannerPath() ) m_currScannerPaths != TomahawkSettings::instance()->scannerPaths() )
{ {
m_currScannerPath = TomahawkSettings::instance()->scannerPath(); m_currScannerPaths = TomahawkSettings::instance()->scannerPaths();
m_dirWatcher->removePaths( m_dirWatcher->directories() ); m_dirWatcher->removePaths( m_dirWatcher->directories() );
m_dirWatcher->addPaths( m_currScannerPath ); m_dirWatcher->addPaths( m_currScannerPaths );
runManualScan( m_currScannerPath ); runManualScan( m_currScannerPaths );
} }
if( TomahawkSettings::instance()->watchForChanges() &&
!m_queuedChangedDirs.isEmpty() )
runManualScan( m_queuedChangedDirs, false );
} }
@@ -107,21 +124,21 @@ ScanManager::startupWatchPaths()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if( !Database::instance() ) if( !Database::instance() || ( Database::instance() && !Database::instance()->isReady() ) )
{ {
QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) );
return; return;
} }
DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPath ); DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPaths );
connect( cmd, SIGNAL( done( QMap<QString, unsigned int> ) ), connect( cmd, SIGNAL( done( QMap< QString, unsigned int > ) ),
SLOT( setInitialPaths( QMap<QString, unsigned int> ) ) ); SLOT( setInitialPaths( QMap< QString, unsigned int > ) ) );
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(cmd) ); Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
} }
void void
ScanManager::setInitialPaths( QMap<QString, unsigned int> pathMap ) ScanManager::setInitialPaths( QMap< QString, unsigned int > pathMap )
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
foreach( QString path, pathMap.keys() ) foreach( QString path, pathMap.keys() )
@@ -129,27 +146,45 @@ ScanManager::setInitialPaths( QMap<QString, unsigned int> pathMap )
qDebug() << "Adding " << path << " to watcher"; qDebug() << "Adding " << path << " to watcher";
m_dirWatcher->addPath( path ); m_dirWatcher->addPath( path );
} }
runManualScan( TomahawkSettings::instance()->scannerPaths() );
} }
void void
ScanManager::runManualScan( const QStringList& path ) ScanManager::runManualScan( const QStringList& paths, bool recursive )
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero
{ {
m_musicScannerThreadController = new QThread( this ); m_musicScannerThreadController = new QThread( this );
m_scanner = new MusicScanner( path ); QStringList allPaths = paths;
foreach( QString path, m_deferredDirs[recursive] )
{
if( !allPaths.contains( path ) )
allPaths << path;
}
m_scanner = new MusicScanner( paths, recursive );
m_scanner->moveToThread( m_musicScannerThreadController ); m_scanner->moveToThread( m_musicScannerThreadController );
connect( m_scanner, SIGNAL( finished() ), SLOT( scannerFinished() ) ); connect( m_scanner, SIGNAL( finished() ), SLOT( scannerFinished() ) );
connect( m_scanner, SIGNAL( addWatchedDirs( const QStringList & ) ), SLOT( addWatchedDirs( const QStringList & ) ) ); connect( m_scanner, SIGNAL( addWatchedDirs( const QStringList & ) ), SLOT( addWatchedDirs( const QStringList & ) ) );
connect( m_scanner, SIGNAL( removeWatchedDir( const QString & ) ), SLOT( removeWatchedDir( const QString & ) ) ); connect( m_scanner, SIGNAL( removeWatchedDir( const QString & ) ), SLOT( removeWatchedDir( const QString & ) ) );
m_musicScannerThreadController->start( QThread::IdlePriority ); m_musicScannerThreadController->start( QThread::IdlePriority );
QMetaObject::invokeMethod( m_scanner, "startScan" ); QMetaObject::invokeMethod( m_scanner, "startScan" );
m_deferredDirs[recursive].clear();
} }
else else
qDebug() << "Could not run manual scan, old scan still running"; {
qDebug() << "Could not run manual scan, old scan still running; deferring paths";
foreach( QString path, paths )
{
if( !m_deferredDirs[recursive].contains( path ) )
{
qDebug() << "Deferring path " << path;
m_deferredDirs[recursive] << path;
}
}
}
} }
void void
@@ -180,6 +215,34 @@ ScanManager::handleChangedDir( const QString& path )
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
qDebug() << "Dir changed: " << path; qDebug() << "Dir changed: " << path;
m_queuedChangedDirs << path;
if( TomahawkSettings::instance()->watchForChanges() )
m_queuedScanTimer->start( 10000 );
}
void
ScanManager::queuedScanTimeout()
{
qDebug() << Q_FUNC_INFO;
runManualScan( m_queuedChangedDirs, false );
m_queuedChangedDirs.clear();
}
void
ScanManager::deferredScanTimeout()
{
if( !m_deferredDirs[true].isEmpty() )
{
qDebug() << "Running scan for deferred recursive paths";
runManualScan( m_deferredDirs[true], true );
}
else if( !m_deferredDirs[false].isEmpty() )
{
qDebug() << "Running scan for deferred non-recursive paths";
runManualScan( m_deferredDirs[false], false );
}
} }

View File

@@ -19,15 +19,17 @@
#ifndef SCANMANAGER_H #ifndef SCANMANAGER_H
#define SCANMANAGER_H #define SCANMANAGER_H
#include <QHash>
#include <QMap>
#include <QObject> #include <QObject>
#include <QStringList> #include <QStringList>
#include <QMap>
#include "dllmacro.h" #include "dllmacro.h"
class MusicScanner; class MusicScanner;
class QThread; class QThread;
class QFileSystemWatcher; class QFileSystemWatcher;
class QTimer;
class ScanManager : public QObject class ScanManager : public QObject
{ {
@@ -39,16 +41,15 @@ public:
explicit ScanManager( QObject* parent = 0 ); explicit ScanManager( QObject* parent = 0 );
virtual ~ScanManager(); virtual ~ScanManager();
void runManualScan( const QStringList& path );
signals: signals:
void finished(); void finished();
public slots: public slots:
void runManualScan( const QStringList& paths, bool recursive = true );
void handleChangedDir( const QString& path ); void handleChangedDir( const QString& path );
void addWatchedDirs( const QStringList& paths ); void addWatchedDirs( const QStringList& paths );
void removeWatchedDir( const QString& path ); void removeWatchedDir( const QString& path );
void setInitialPaths( QMap<QString, unsigned int> pathMap ); void setInitialPaths( QMap< QString, unsigned int > pathMap );
private slots: private slots:
void scannerQuit(); void scannerQuit();
@@ -56,6 +57,8 @@ private slots:
void scannerDestroyed( QObject* scanner ); void scannerDestroyed( QObject* scanner );
void startupWatchPaths(); void startupWatchPaths();
void queuedScanTimeout();
void deferredScanTimeout();
void onSettingsChanged(); void onSettingsChanged();
@@ -64,8 +67,13 @@ private:
MusicScanner* m_scanner; MusicScanner* m_scanner;
QThread* m_musicScannerThreadController; QThread* m_musicScannerThreadController;
QStringList m_currScannerPath; QStringList m_currScannerPaths;
QFileSystemWatcher* m_dirWatcher; QFileSystemWatcher* m_dirWatcher;
QTimer* m_queuedScanTimer;
QTimer* m_deferredScanTimer;
QStringList m_queuedChangedDirs;
QHash< bool, QStringList > m_deferredDirs;
}; };
#endif #endif

View File

@@ -87,7 +87,8 @@ SettingsDialog::SettingsDialog( QWidget *parent )
// MUSIC SCANNER // MUSIC SCANNER
//FIXME: MULTIPLECOLLECTIONDIRS //FIXME: MULTIPLECOLLECTIONDIRS
ui->lineEditMusicPath->setText( s->scannerPath().first() ); ui->lineEditMusicPath->setText( s->scannerPaths().first() );
ui->checkBoxWatchForChanges->setChecked( s->watchForChanges() );
// LAST FM // LAST FM
ui->checkBoxEnableLastfm->setChecked( s->scrobblingEnabled() ); ui->checkBoxEnableLastfm->setChecked( s->scrobblingEnabled() );
@@ -134,7 +135,8 @@ SettingsDialog::~SettingsDialog()
s->setExternalHostname( ui->staticHostName->text() ); s->setExternalHostname( ui->staticHostName->text() );
s->setExternalPort( ui->staticPort->value() ); s->setExternalPort( ui->staticPort->value() );
s->setScannerPath( QStringList( ui->lineEditMusicPath->text() ) ); s->setScannerPaths( QStringList( ui->lineEditMusicPath->text() ) );
s->setWatchForChanges( ui->checkBoxWatchForChanges->isChecked() );
s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() ); s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() );
s->setLastFmUsername( ui->lineEditLastfmUsername->text() ); s->setLastFmUsername( ui->lineEditLastfmUsername->text() );

View File

@@ -454,6 +454,19 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBoxWatchForChanges">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Watch for changes</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View File

@@ -285,7 +285,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] )
} }
#ifndef TOMAHAWK_HEADLESS #ifndef TOMAHAWK_HEADLESS
if ( !TomahawkSettings::instance()->hasScannerPath() ) if ( !TomahawkSettings::instance()->hasScannerPaths() )
{ {
m_mainwindow->showSettingsDialog(); m_mainwindow->showSettingsDialog();
} }

View File

@@ -319,8 +319,8 @@ TomahawkWindow::showSettingsDialog()
void void
TomahawkWindow::updateCollectionManually() TomahawkWindow::updateCollectionManually()
{ {
if ( TomahawkSettings::instance()->hasScannerPath() ) if ( TomahawkSettings::instance()->hasScannerPaths() )
ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPath() ); ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPaths() );
} }