Add ability to cancel scan per directory.
Store a boolean along with watched directories to indicate active. Use this flag to provide a mechanism to halt scans on a per-directory basis.
This commit is contained in:
parent
97ffae2e70
commit
4ebd3c8c0a
@ -27,13 +27,13 @@
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDirIterator>
|
||||
#include <QtDebug>
|
||||
#include <QThread>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QMutexLocker>
|
||||
#include <QSet>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <fileref.h>
|
||||
#include <tag.h>
|
||||
@ -59,7 +59,6 @@ LibraryWatcher::LibraryWatcher(QObject* parent)
|
||||
backend_(nullptr),
|
||||
task_manager_(nullptr),
|
||||
fs_watcher_(FileSystemWatcherInterface::Create(this)),
|
||||
stop_requested_(false),
|
||||
scan_on_startup_(true),
|
||||
monitor_(true),
|
||||
rescan_timer_(new QTimer(this)),
|
||||
@ -85,10 +84,9 @@ LibraryWatcher::LibraryWatcher(QObject* parent)
|
||||
// is only created on a stack and the removal of a directory from the watch
|
||||
// list only occurs as a result of a signal and happens on the watcher's
|
||||
// thread. So the Directory object will not be deleted out from under us.
|
||||
LibraryWatcher::ScanTransaction::ScanTransaction(LibraryWatcher* watcher,
|
||||
const Directory& dir,
|
||||
bool incremental,
|
||||
bool ignores_mtime)
|
||||
LibraryWatcher::ScanTransaction::ScanTransaction(
|
||||
LibraryWatcher* watcher, const LibraryWatcher::WatchedDir& dir,
|
||||
bool incremental, bool ignores_mtime)
|
||||
: progress_(0),
|
||||
progress_max_(0),
|
||||
dir_(dir),
|
||||
@ -109,7 +107,7 @@ LibraryWatcher::ScanTransaction::ScanTransaction(LibraryWatcher* watcher,
|
||||
|
||||
LibraryWatcher::ScanTransaction::~ScanTransaction() {
|
||||
// If we're stopping then don't commit the transaction
|
||||
if (watcher_->stop_requested_) {
|
||||
if (aborted()) {
|
||||
watcher_->task_manager_->SetTaskFinished(task_id_);
|
||||
return;
|
||||
}
|
||||
@ -204,12 +202,36 @@ SubdirectoryList LibraryWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
return known_subdirs_;
|
||||
}
|
||||
|
||||
void LibraryWatcher::WatchList::StopAll() {
|
||||
QMutexLocker l(&mutex_);
|
||||
for (WatchedDir& wdir : list_) {
|
||||
wdir.active_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryWatcher::WatchList::Stop(const Directory& dir) {
|
||||
QMutexLocker l(&mutex_);
|
||||
if (list_.contains(dir.id)) {
|
||||
list_[dir.id].active_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryWatcher::WatchList::Remove(int id) {
|
||||
QMutexLocker l(&mutex_);
|
||||
list_.remove(id);
|
||||
}
|
||||
|
||||
void LibraryWatcher::WatchList::Add(const Directory& dir) {
|
||||
// Lock is not really necessary here, but it's also not going to block
|
||||
// anything, so include it for sanity and completeness.
|
||||
QMutexLocker l(&mutex_);
|
||||
list_[dir.id] = WatchedDir(dir);
|
||||
}
|
||||
|
||||
void LibraryWatcher::AddDirectory(const Directory& dir,
|
||||
const SubdirectoryList& subdirs) {
|
||||
watched_dirs_[dir.id] = dir;
|
||||
// Use a reference to our copy of the directory, not the reference to the
|
||||
// caller's instance.
|
||||
Directory& new_dir = watched_dirs_[dir.id];
|
||||
watched_dirs_.Add(dir);
|
||||
const WatchedDir& new_dir = watched_dirs_.list_[dir.id];
|
||||
|
||||
if (subdirs.isEmpty()) {
|
||||
// This is a new directory that we've never seen before.
|
||||
@ -225,7 +247,7 @@ void LibraryWatcher::AddDirectory(const Directory& dir,
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(subdirs.count());
|
||||
for (const Subdirectory& subdir : subdirs) {
|
||||
if (stop_requested_) return;
|
||||
if (transaction.aborted()) return;
|
||||
|
||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction);
|
||||
|
||||
@ -246,7 +268,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
// Do not scan symlinked dirs that are already in collection
|
||||
if (path_info.isSymLink()) {
|
||||
QString real_path = path_info.symLinkTarget();
|
||||
for (const Directory& dir : watched_dirs_) {
|
||||
for (const Directory& dir : watched_dirs_.list_) {
|
||||
if (real_path.startsWith(dir.path)) {
|
||||
t->AddToProgress(1);
|
||||
return;
|
||||
@ -290,7 +312,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
QDirIterator it(
|
||||
path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
|
||||
while (it.hasNext()) {
|
||||
if (stop_requested_) return;
|
||||
if (t->aborted()) return;
|
||||
|
||||
QString child(it.next());
|
||||
QFileInfo child_info(child);
|
||||
@ -316,7 +338,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
}
|
||||
}
|
||||
|
||||
if (stop_requested_) return;
|
||||
if (t->aborted()) return;
|
||||
|
||||
// Ask the database for a list of files in this directory
|
||||
SongList songs_in_db = t->FindSongsInSubdirectory(path);
|
||||
@ -325,7 +347,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
|
||||
// Now compare the list from the database with the list of files on disk
|
||||
for (const QString& file : files_on_disk) {
|
||||
if (stop_requested_) return;
|
||||
if (t->aborted()) return;
|
||||
|
||||
// associated cue
|
||||
QString matching_cue = NoExtensionPart(file) + ".cue";
|
||||
@ -360,7 +382,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
cue_deleted || cue_added;
|
||||
|
||||
// Also want to look to see whether the album art has changed
|
||||
QString image = ImageForSong(file, album_art);
|
||||
QString image = ImageForSong(file, album_art, t);
|
||||
if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) ||
|
||||
(!matching_song.art_automatic().isEmpty() &&
|
||||
!matching_song.has_embedded_cover() &&
|
||||
@ -396,7 +418,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
|
||||
qLog(Debug) << file << "created";
|
||||
// choose an image for the song(s)
|
||||
QString image = ImageForSong(file, album_art);
|
||||
QString image = ImageForSong(file, album_art, t);
|
||||
|
||||
for (Song song : song_list) {
|
||||
song.set_directory_id(t->dir_id());
|
||||
@ -438,7 +460,7 @@ void LibraryWatcher::ScanSubdirectory(const QString& path,
|
||||
// Recurse into the new subdirs that we found
|
||||
t->AddToProgressMax(my_new_subdirs.count());
|
||||
for (const Subdirectory& my_new_subdir : my_new_subdirs) {
|
||||
if (stop_requested_) return;
|
||||
if (t->aborted()) return;
|
||||
ScanSubdirectory(my_new_subdir.path, my_new_subdir, t, true);
|
||||
}
|
||||
}
|
||||
@ -642,7 +664,7 @@ void LibraryWatcher::RemoveWatch(const Directory& dir,
|
||||
|
||||
void LibraryWatcher::RemoveDirectory(const Directory& dir) {
|
||||
rescan_queue_.remove(dir.id);
|
||||
watched_dirs_.remove(dir.id);
|
||||
watched_dirs_.Remove(dir.id);
|
||||
|
||||
// Stop watching the directory's subdirectories
|
||||
for (const QString& subdir_path : subdir_mapping_.keys(dir)) {
|
||||
@ -682,21 +704,22 @@ void LibraryWatcher::DirectoryChanged(const QString& subdir) {
|
||||
}
|
||||
|
||||
void LibraryWatcher::RescanPathsNow() {
|
||||
for (int dir : rescan_queue_.keys()) {
|
||||
if (stop_requested_) return;
|
||||
|
||||
if (!watched_dirs_.contains(dir)) {
|
||||
qLog(Warning) << "Rescan id" << dir << "not in watch list.";
|
||||
for (int id : rescan_queue_.keys()) {
|
||||
if (!watched_dirs_.list_.contains(id)) {
|
||||
qLog(Warning) << "Rescan id" << id << "not in watch list.";
|
||||
continue;
|
||||
}
|
||||
const WatchedDir& dir = watched_dirs_.list_[id];
|
||||
|
||||
ScanTransaction transaction(this, watched_dirs_[dir], false);
|
||||
transaction.AddToProgressMax(rescan_queue_[dir].count());
|
||||
if (!dir.active_) continue;
|
||||
|
||||
for (const QString& path : rescan_queue_[dir]) {
|
||||
if (stop_requested_) return;
|
||||
ScanTransaction transaction(this, dir, false);
|
||||
transaction.AddToProgressMax(rescan_queue_[id].count());
|
||||
|
||||
for (const QString& path : rescan_queue_[id]) {
|
||||
if (transaction.aborted()) return;
|
||||
Subdirectory subdir;
|
||||
subdir.directory_id = dir;
|
||||
subdir.directory_id = id;
|
||||
subdir.mtime = 0;
|
||||
subdir.path = path;
|
||||
ScanSubdirectory(path, subdir, &transaction);
|
||||
@ -708,7 +731,8 @@ void LibraryWatcher::RescanPathsNow() {
|
||||
emit CompilationsNeedUpdating();
|
||||
}
|
||||
|
||||
QString LibraryWatcher::PickBestImage(const QStringList& images) {
|
||||
QString LibraryWatcher::PickBestImage(const QStringList& images,
|
||||
ScanTransaction* t) {
|
||||
// This is used when there is more than one image in a directory.
|
||||
// Pick the biggest image that matches the most important filter
|
||||
|
||||
@ -748,7 +772,7 @@ QString LibraryWatcher::PickBestImage(const QStringList& images) {
|
||||
QString biggest_path;
|
||||
|
||||
for (const QString& path : filtered) {
|
||||
if (stop_requested_) return "";
|
||||
if (t->aborted()) return "";
|
||||
|
||||
QImage image(path);
|
||||
if (image.isNull()) continue;
|
||||
@ -764,14 +788,15 @@ QString LibraryWatcher::PickBestImage(const QStringList& images) {
|
||||
}
|
||||
|
||||
QString LibraryWatcher::ImageForSong(const QString& path,
|
||||
QMap<QString, QStringList>& album_art) {
|
||||
QMap<QString, QStringList>& album_art,
|
||||
ScanTransaction* t) {
|
||||
QString dir(DirectoryPart(path));
|
||||
|
||||
if (album_art.contains(dir)) {
|
||||
if (album_art[dir].count() == 1)
|
||||
return album_art[dir][0];
|
||||
else {
|
||||
QString best_image = PickBestImage(album_art[dir]);
|
||||
QString best_image = PickBestImage(album_art[dir], t);
|
||||
album_art[dir] = QStringList() << best_image;
|
||||
return best_image;
|
||||
}
|
||||
@ -804,7 +829,7 @@ void LibraryWatcher::ReloadSettings() {
|
||||
fs_watcher_->Clear();
|
||||
} else if (monitor_ && !was_monitoring_before) {
|
||||
// Add all directories to all QFileSystemWatchers again
|
||||
for (const Directory& dir : watched_dirs_.values()) {
|
||||
for (const Directory& dir : watched_dirs_.list_.values()) {
|
||||
SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||
for (const Subdirectory& subdir : subdirs) {
|
||||
AddWatch(dir, subdir.path);
|
||||
@ -836,13 +861,13 @@ void LibraryWatcher::IncrementalScanNow() { PerformScan(true, false); }
|
||||
void LibraryWatcher::FullScanNow() { PerformScan(false, true); }
|
||||
|
||||
void LibraryWatcher::PerformScan(bool incremental, bool ignore_mtimes) {
|
||||
for (const Directory& dir : watched_dirs_.values()) {
|
||||
for (const WatchedDir& dir : watched_dirs_.list_.values()) {
|
||||
ScanTransaction transaction(this, dir, incremental, ignore_mtimes);
|
||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||
transaction.AddToProgressMax(subdirs.count());
|
||||
|
||||
for (const Subdirectory& subdir : subdirs) {
|
||||
if (stop_requested_) return;
|
||||
if (transaction.aborted()) return;
|
||||
|
||||
ScanSubdirectory(subdir.path, subdir, &transaction);
|
||||
}
|
||||
|
@ -22,9 +22,10 @@
|
||||
#include "core/song.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
|
||||
class QFileSystemWatcher;
|
||||
class QTimer;
|
||||
@ -55,9 +56,9 @@ class LibraryWatcher : public QObject {
|
||||
void SetRescanPausedAsync(bool pause);
|
||||
void ReloadSettingsAsync();
|
||||
|
||||
void Stop() { stop_requested_ = true; }
|
||||
void Stop() { watched_dirs_.StopAll(); }
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void NewOrUpdatedSongs(const SongList& songs);
|
||||
void SongsMTimeUpdated(const SongList& songs);
|
||||
void SongsDeleted(const SongList& songs);
|
||||
@ -77,6 +78,14 @@ signals:
|
||||
void SetRescanPaused(bool pause);
|
||||
|
||||
private:
|
||||
class WatchedDir : public Directory {
|
||||
public:
|
||||
WatchedDir() : active_(true) {}
|
||||
WatchedDir(const Directory& dir) : Directory(dir), active_(true) {}
|
||||
|
||||
bool active_;
|
||||
};
|
||||
|
||||
// This class encapsulates a full or partial scan of a directory.
|
||||
// Each directory has one or more subdirectories, and any number of
|
||||
// subdirectories can be scanned during one transaction. ScanSubdirectory()
|
||||
@ -88,8 +97,9 @@ signals:
|
||||
// LibraryBackend::FindSongsInDirectory.
|
||||
class ScanTransaction {
|
||||
public:
|
||||
ScanTransaction(LibraryWatcher* watcher, const Directory& dir,
|
||||
bool incremental, bool ignores_mtime = false);
|
||||
ScanTransaction(LibraryWatcher* watcher,
|
||||
const LibraryWatcher::WatchedDir& dir, bool incremental,
|
||||
bool ignores_mtime = false);
|
||||
~ScanTransaction();
|
||||
|
||||
SongList FindSongsInSubdirectory(const QString& path);
|
||||
@ -105,6 +115,8 @@ signals:
|
||||
bool is_incremental() const { return incremental_; }
|
||||
bool ignores_mtime() const { return ignores_mtime_; }
|
||||
|
||||
bool aborted() { return !dir_.active_; }
|
||||
|
||||
SongList deleted_songs;
|
||||
SongList readded_songs;
|
||||
SongList new_songs;
|
||||
@ -120,7 +132,7 @@ signals:
|
||||
int progress_;
|
||||
int progress_max_;
|
||||
|
||||
const Directory& dir_;
|
||||
const WatchedDir& dir_;
|
||||
// Incremental scan enters a directory only if it has changed since the
|
||||
// last scan.
|
||||
bool incremental_;
|
||||
@ -153,9 +165,10 @@ signals:
|
||||
inline static QString NoExtensionPart(const QString& fileName);
|
||||
inline static QString ExtensionPart(const QString& fileName);
|
||||
inline static QString DirectoryPart(const QString& fileName);
|
||||
QString PickBestImage(const QStringList& images);
|
||||
QString PickBestImage(const QStringList& images, ScanTransaction* t);
|
||||
QString ImageForSong(const QString& path,
|
||||
QMap<QString, QStringList>& album_art);
|
||||
QMap<QString, QStringList>& album_art,
|
||||
ScanTransaction* t);
|
||||
void AddWatch(const Directory& dir, const QString& path);
|
||||
void RemoveWatch(const Directory& dir, const Subdirectory& subdir);
|
||||
uint GetMtimeForCue(const QString& cue_path);
|
||||
@ -200,11 +213,30 @@ signals:
|
||||
*/
|
||||
QStringList best_image_filters_;
|
||||
|
||||
bool stop_requested_;
|
||||
bool scan_on_startup_;
|
||||
bool monitor_;
|
||||
|
||||
QMap<int, Directory> watched_dirs_;
|
||||
// All methods of QMap are reentrant We should only need to worry about
|
||||
// syncronizing methods that remove directories on the watcher thread and
|
||||
// methods that modify directories called from other threads.
|
||||
class WatchList {
|
||||
public:
|
||||
// These may be called from a different thread.
|
||||
void StopAll();
|
||||
void Stop(const Directory& dir);
|
||||
|
||||
// This should only be called on the watcher thread.
|
||||
void Remove(int id);
|
||||
|
||||
void Add(const Directory& dir);
|
||||
|
||||
QMap<int, WatchedDir> list_;
|
||||
|
||||
private:
|
||||
QMutex mutex_;
|
||||
};
|
||||
|
||||
WatchList watched_dirs_;
|
||||
QTimer* rescan_timer_;
|
||||
QMap<int, QStringList>
|
||||
rescan_queue_; // dir id -> list of subdirs to be scanned
|
||||
|
Loading…
x
Reference in New Issue
Block a user