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:
Jim Broadus 2020-01-21 00:32:55 -08:00
parent 97ffae2e70
commit 4ebd3c8c0a
2 changed files with 106 additions and 49 deletions

View File

@ -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);
}

View File

@ -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