From 3bc496eaafbcd5a3417150fb707ebd0d564b6825 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Apr 2011 00:18:29 -0400 Subject: [PATCH] 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. --- src/libtomahawk/aclsystem.cpp | 2 +- src/libtomahawk/database/database.cpp | 2 + src/libtomahawk/database/database.h | 6 ++ src/libtomahawk/database/databaseimpl.h | 2 + src/libtomahawk/tomahawksettings.cpp | 35 +++++++-- src/libtomahawk/tomahawksettings.h | 9 ++- src/musicscanner.cpp | 17 +++-- src/musicscanner.h | 20 +++-- src/scanmanager.cpp | 97 ++++++++++++++++++++----- src/scanmanager.h | 18 +++-- src/settingsdialog.cpp | 6 +- src/settingsdialog.ui | 15 +++- src/tomahawkapp.cpp | 2 +- src/tomahawkwindow.cpp | 4 +- 14 files changed, 185 insertions(+), 50 deletions(-) diff --git a/src/libtomahawk/aclsystem.cpp b/src/libtomahawk/aclsystem.cpp index 7b7d70e1f..15c75a407 100644 --- a/src/libtomahawk/aclsystem.cpp +++ b/src/libtomahawk/aclsystem.cpp @@ -124,7 +124,7 @@ void ACLSystem::authorizePath( const QString& dbid, const QString& path, ACLSystem::ACL type ) { TomahawkSettings *s = TomahawkSettings::instance(); - if( !s->scannerPath().contains( path ) ) + if( !s->scannerPaths().contains( path ) ) { qDebug() << "path selected is not in our scanner path!"; return; diff --git a/src/libtomahawk/database/database.cpp b/src/libtomahawk/database/database.cpp index 691a9db3b..4436f6cfa 100644 --- a/src/libtomahawk/database/database.cpp +++ b/src/libtomahawk/database/database.cpp @@ -32,6 +32,7 @@ Database::instance() Database::Database( const QString& dbname, QObject* parent ) : QObject( parent ) + , m_ready( false ) , m_impl( new DatabaseImpl( dbname, this ) ) , 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( ready() ) ); + connect( m_impl, SIGNAL( indexReady() ), SLOT( setIsReadyTrue() ) ); m_workerRW->start(); } diff --git a/src/libtomahawk/database/database.h b/src/libtomahawk/database/database.h index 4dad4882e..a6890fbb6 100644 --- a/src/libtomahawk/database/database.h +++ b/src/libtomahawk/database/database.h @@ -52,6 +52,8 @@ public: const bool indexReady() const { return m_indexReady; } void loadIndex(); + + bool isReady() const { return m_ready; } signals: void indexReady(); // search index @@ -63,7 +65,11 @@ signals: public slots: void enqueue( QSharedPointer lc ); +private slots: + void setIsReadyTrue() { m_ready = true; } + private: + bool m_ready; DatabaseImpl* m_impl; DatabaseWorker* m_workerRW; QHash< QString, DatabaseWorker* > m_workers; diff --git a/src/libtomahawk/database/databaseimpl.h b/src/libtomahawk/database/databaseimpl.h index 579850b7e..e2965f553 100644 --- a/src/libtomahawk/database/databaseimpl.h +++ b/src/libtomahawk/database/databaseimpl.h @@ -84,6 +84,8 @@ signals: public slots: private: + bool m_ready; + bool updateSchema( int currentver ); QSqlDatabase db; diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index 82d04d75c..bdbe3c171 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -66,29 +66,50 @@ TomahawkSettings::~TomahawkSettings() QStringList -TomahawkSettings::scannerPath() const +TomahawkSettings::scannerPaths() { + //FIXME: After enough time, remove this hack (and make const) #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 - return value( "scannerpath", "" ).toStringList(); + if( value( "scannerpaths" ).isNull() ) + setValue( "scannerpaths", value( "scannerpath" ) ); + return value( "scannerpaths", "" ).toStringList(); #endif } void -TomahawkSettings::setScannerPath( const QStringList& path ) +TomahawkSettings::setScannerPaths( const QStringList& paths ) { - setValue( "scannerpath", path ); + setValue( "scannerpaths", paths ); } 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 TomahawkSettings::setAcceptedLegalWarning( bool accept ) { diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index cc2b2923f..42daa91e3 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -41,9 +41,12 @@ public: void applyChanges() { emit changed(); } /// General settings - QStringList scannerPath() const; /// QDesktopServices::MusicLocation by default - void setScannerPath( const QStringList& path ); - bool hasScannerPath() const; + QStringList scannerPaths(); /// QDesktopServices::MusicLocation by default + void setScannerPaths( const QStringList& paths ); + bool hasScannerPaths() const; + + bool watchForChanges() const; + void setWatchForChanges( bool watch ); bool acceptedLegalWarning() const; void setAcceptedLegalWarning( bool accept ); diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index fe8eb29f6..1662b452c 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -31,14 +31,16 @@ using namespace Tomahawk; void 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 ); } void -DirLister::scanDir( QDir dir, int depth ) +DirLister::scanDir( QDir dir, int depth, DirLister::Mode mode ) { + qDebug() << "DirLister::scanDir scanning: " << dir.absolutePath(); QFileInfoList dirs; const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); m_newdirmtimes.insert( dir.absolutePath(), mtime ); @@ -65,14 +67,18 @@ DirLister::scanDir( QDir dir, int depth ) 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() , m_dirs( dirs ) + , m_recursive( recursive ) , m_batchsize( bs ) , m_dirLister( 0 ) , m_dirListerThreadController( 0 ) @@ -150,8 +156,7 @@ MusicScanner::scan() m_dirListerThreadController = new QThread( this ); - //FIXME: MULTIPLECOLLECTIONDIRS - m_dirLister = new DirLister( QDir( m_dirs.first(), 0 ), m_dirmtimes ); + m_dirLister = new DirLister( m_dirs, m_dirmtimes, m_recursive ); m_dirLister->moveToThread( m_dirListerThreadController ); connect( m_dirLister, SIGNAL( fileToScan( QFileInfo ) ), diff --git a/src/musicscanner.h b/src/musicscanner.h index dd2725334..77fb62071 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -39,8 +39,15 @@ class DirLister : public QObject Q_OBJECT public: - DirLister( QDir d, QMap& mtimes ) - : QObject(), m_dir( d ), m_dirmtimes( mtimes ) + + enum Mode { + NonRecursive, + Recursive, + MTimeOnly + }; + + DirLister( QStringList dirs, QMap& mtimes, bool recursive ) + : QObject(), m_dirs( dirs ), m_dirmtimes( mtimes ), m_recursive( recursive ) { qDebug() << Q_FUNC_INFO; } @@ -56,11 +63,13 @@ signals: private slots: void go(); - void scanDir( QDir dir, int depth ); + void scanDir( QDir dir, int depth, DirLister::Mode mode ); private: - QDir m_dir; + QStringList m_dirs; QMap m_dirmtimes; + bool m_recursive; + QMap m_newdirmtimes; }; @@ -69,7 +78,7 @@ class MusicScanner : public QObject Q_OBJECT public: - MusicScanner( const QStringList& dirs, quint32 bs = 0 ); + MusicScanner( const QStringList& dirs, bool recursive = true, quint32 bs = 0 ); ~MusicScanner(); signals: @@ -105,6 +114,7 @@ private: QMap m_newdirmtimes; QList m_scannedfiles; + bool m_recursive; quint32 m_batchsize; DirLister* m_dirLister; diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index e1d5f028c..5fda0a7ec 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -45,20 +45,33 @@ ScanManager::ScanManager( QObject* parent ) : QObject( parent ) , m_scanner( 0 ) , m_musicScannerThreadController( 0 ) + , m_currScannerPaths() , m_dirWatcher( 0 ) + , m_queuedScanTimer( 0 ) + , m_deferredScanTimer( 0 ) + , m_queuedChangedDirs() + , m_deferredDirs() { 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( m_queuedScanTimer, SIGNAL( timeout() ), SLOT( queuedScanTimeout() ) ); + connect( m_deferredScanTimer, SIGNAL( timeout() ), SLOT( deferredScanTimeout() ) ); connect( m_dirWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( handleChangedDir( const QString & ) ) ); - if ( TomahawkSettings::instance()->hasScannerPath() ) - m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + if ( TomahawkSettings::instance()->hasScannerPaths() ) + m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); qDebug() << "loading initial directories to watch"; QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); + m_deferredScanTimer->start(); } @@ -91,14 +104,18 @@ ScanManager::~ScanManager() void ScanManager::onSettingsChanged() { - if ( TomahawkSettings::instance()->hasScannerPath() && - m_currScannerPath != TomahawkSettings::instance()->scannerPath() ) + if ( TomahawkSettings::instance()->hasScannerPaths() && + m_currScannerPaths != TomahawkSettings::instance()->scannerPaths() ) { - m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + m_currScannerPaths = TomahawkSettings::instance()->scannerPaths(); m_dirWatcher->removePaths( m_dirWatcher->directories() ); - m_dirWatcher->addPaths( m_currScannerPath ); - runManualScan( m_currScannerPath ); + m_dirWatcher->addPaths( m_currScannerPaths ); + runManualScan( m_currScannerPaths ); } + + if( TomahawkSettings::instance()->watchForChanges() && + !m_queuedChangedDirs.isEmpty() ) + runManualScan( m_queuedChangedDirs, false ); } @@ -107,21 +124,21 @@ ScanManager::startupWatchPaths() { qDebug() << Q_FUNC_INFO; - if( !Database::instance() ) + if( !Database::instance() || ( Database::instance() && !Database::instance()->isReady() ) ) { QTimer::singleShot( 1000, this, SLOT( startupWatchPaths() ) ); return; } - DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPath ); - connect( cmd, SIGNAL( done( QMap ) ), - SLOT( setInitialPaths( QMap ) ) ); - Database::instance()->enqueue( QSharedPointer(cmd) ); + DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_currScannerPaths ); + connect( cmd, SIGNAL( done( QMap< QString, unsigned int > ) ), + SLOT( setInitialPaths( QMap< QString, unsigned int > ) ) ); + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); } void -ScanManager::setInitialPaths( QMap pathMap ) +ScanManager::setInitialPaths( QMap< QString, unsigned int > pathMap ) { qDebug() << Q_FUNC_INFO; foreach( QString path, pathMap.keys() ) @@ -129,27 +146,45 @@ ScanManager::setInitialPaths( QMap pathMap ) qDebug() << "Adding " << path << " to watcher"; m_dirWatcher->addPath( path ); } + runManualScan( TomahawkSettings::instance()->scannerPaths() ); } void -ScanManager::runManualScan( const QStringList& path ) +ScanManager::runManualScan( const QStringList& paths, bool recursive ) { qDebug() << Q_FUNC_INFO; if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero { 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 ); connect( m_scanner, SIGNAL( finished() ), SLOT( scannerFinished() ) ); connect( m_scanner, SIGNAL( addWatchedDirs( const QStringList & ) ), SLOT( addWatchedDirs( const QStringList & ) ) ); connect( m_scanner, SIGNAL( removeWatchedDir( const QString & ) ), SLOT( removeWatchedDir( const QString & ) ) ); m_musicScannerThreadController->start( QThread::IdlePriority ); QMetaObject::invokeMethod( m_scanner, "startScan" ); + m_deferredDirs[recursive].clear(); } 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 @@ -180,6 +215,34 @@ ScanManager::handleChangedDir( const QString& path ) { qDebug() << Q_FUNC_INFO; 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 ); + } } diff --git a/src/scanmanager.h b/src/scanmanager.h index 232627058..fdec0cf15 100644 --- a/src/scanmanager.h +++ b/src/scanmanager.h @@ -19,15 +19,17 @@ #ifndef SCANMANAGER_H #define SCANMANAGER_H +#include +#include #include #include -#include #include "dllmacro.h" class MusicScanner; class QThread; class QFileSystemWatcher; +class QTimer; class ScanManager : public QObject { @@ -38,17 +40,16 @@ public: explicit ScanManager( QObject* parent = 0 ); virtual ~ScanManager(); - - void runManualScan( const QStringList& path ); signals: void finished(); public slots: + void runManualScan( const QStringList& paths, bool recursive = true ); void handleChangedDir( const QString& path ); void addWatchedDirs( const QStringList& paths ); void removeWatchedDir( const QString& path ); - void setInitialPaths( QMap pathMap ); + void setInitialPaths( QMap< QString, unsigned int > pathMap ); private slots: void scannerQuit(); @@ -56,6 +57,8 @@ private slots: void scannerDestroyed( QObject* scanner ); void startupWatchPaths(); + void queuedScanTimeout(); + void deferredScanTimeout(); void onSettingsChanged(); @@ -64,8 +67,13 @@ private: MusicScanner* m_scanner; QThread* m_musicScannerThreadController; - QStringList m_currScannerPath; + QStringList m_currScannerPaths; QFileSystemWatcher* m_dirWatcher; + + QTimer* m_queuedScanTimer; + QTimer* m_deferredScanTimer; + QStringList m_queuedChangedDirs; + QHash< bool, QStringList > m_deferredDirs; }; #endif diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 58fbd1f96..b8808b621 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -87,7 +87,8 @@ SettingsDialog::SettingsDialog( QWidget *parent ) // MUSIC SCANNER //FIXME: MULTIPLECOLLECTIONDIRS - ui->lineEditMusicPath->setText( s->scannerPath().first() ); + ui->lineEditMusicPath->setText( s->scannerPaths().first() ); + ui->checkBoxWatchForChanges->setChecked( s->watchForChanges() ); // LAST FM ui->checkBoxEnableLastfm->setChecked( s->scrobblingEnabled() ); @@ -134,7 +135,8 @@ SettingsDialog::~SettingsDialog() s->setExternalHostname( ui->staticHostName->text() ); 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->setLastFmUsername( ui->lineEditLastfmUsername->text() ); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index c3f302da8..0558568b6 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -66,7 +66,7 @@ - e.g. user@example.com + e.g. user@example.com @@ -454,6 +454,19 @@ + + + + + 0 + 0 + + + + Watch for changes + + + diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 46c002c4a..a148d9a02 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -285,7 +285,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) } #ifndef TOMAHAWK_HEADLESS - if ( !TomahawkSettings::instance()->hasScannerPath() ) + if ( !TomahawkSettings::instance()->hasScannerPaths() ) { m_mainwindow->showSettingsDialog(); } diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 843d8b525..15b355d28 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -319,8 +319,8 @@ TomahawkWindow::showSettingsDialog() void TomahawkWindow::updateCollectionManually() { - if ( TomahawkSettings::instance()->hasScannerPath() ) - ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPath() ); + if ( TomahawkSettings::instance()->hasScannerPaths() ) + ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPaths() ); }