Add live scanning (#199)
This commit is contained in:
@@ -70,9 +70,12 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
backend_(nullptr),
|
||||
task_manager_(nullptr),
|
||||
fs_watcher_(FileSystemWatcherInterface::Create(this)),
|
||||
stop_requested_(false),
|
||||
scan_on_startup_(true),
|
||||
monitor_(true),
|
||||
live_scanning_(false),
|
||||
prevent_delete_(false),
|
||||
stop_requested_(false),
|
||||
rescan_in_progress_(false),
|
||||
rescan_timer_(new QTimer(this)),
|
||||
rescan_paused_(false),
|
||||
total_watches_(0),
|
||||
@@ -90,15 +93,17 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanPathsNow()));
|
||||
}
|
||||
|
||||
CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher, int dir, bool incremental, bool ignores_mtime)
|
||||
CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher, const int dir, const bool incremental, const bool ignores_mtime, const bool prevent_delete)
|
||||
: progress_(0),
|
||||
progress_max_(0),
|
||||
dir_(dir),
|
||||
incremental_(incremental),
|
||||
ignores_mtime_(ignores_mtime),
|
||||
prevent_delete_(prevent_delete),
|
||||
watcher_(watcher),
|
||||
cached_songs_dirty_(true),
|
||||
known_subdirs_dirty_(true) {
|
||||
known_subdirs_dirty_(true)
|
||||
{
|
||||
|
||||
QString description;
|
||||
|
||||
@@ -115,30 +120,20 @@ CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher,
|
||||
CollectionWatcher::ScanTransaction::~ScanTransaction() {
|
||||
|
||||
// If we're stopping then don't commit the transaction
|
||||
if (watcher_->stop_requested_) return;
|
||||
if (!watcher_->stop_requested_) {
|
||||
|
||||
if (!new_songs.isEmpty()) emit watcher_->NewOrUpdatedSongs(new_songs);
|
||||
CommitNewOrUpdatedSongs();
|
||||
|
||||
if (!touched_songs.isEmpty()) emit watcher_->SongsMTimeUpdated(touched_songs);
|
||||
|
||||
if (!deleted_songs.isEmpty()) emit watcher_->SongsDeleted(deleted_songs);
|
||||
|
||||
if (!readded_songs.isEmpty()) emit watcher_->SongsReadded(readded_songs);
|
||||
|
||||
if (!new_subdirs.isEmpty()) emit watcher_->SubdirsDiscovered(new_subdirs);
|
||||
|
||||
if (!touched_subdirs.isEmpty())
|
||||
emit watcher_->SubdirsMTimeUpdated(touched_subdirs);
|
||||
|
||||
watcher_->task_manager_->SetTaskFinished(task_id_);
|
||||
|
||||
if (watcher_->monitor_) {
|
||||
// Watch the new subdirectories
|
||||
for (const Subdirectory &subdir : new_subdirs) {
|
||||
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
||||
if (watcher_->monitor_) {
|
||||
// Watch the new subdirectories
|
||||
for (const Subdirectory &subdir : new_subdirs) {
|
||||
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watcher_->task_manager_->SetTaskFinished(task_id_);
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::ScanTransaction::AddToProgress(int n) {
|
||||
@@ -155,6 +150,41 @@ void CollectionWatcher::ScanTransaction::AddToProgressMax(int n) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
|
||||
if (!new_songs.isEmpty()) {
|
||||
emit watcher_->NewOrUpdatedSongs(new_songs);
|
||||
new_songs.clear();
|
||||
}
|
||||
|
||||
if (!touched_songs.isEmpty()) {
|
||||
emit watcher_->SongsMTimeUpdated(touched_songs);
|
||||
touched_songs.clear();
|
||||
}
|
||||
|
||||
if (!deleted_songs.isEmpty() && !prevent_delete_) {
|
||||
emit watcher_->SongsDeleted(deleted_songs);
|
||||
deleted_songs.clear();
|
||||
}
|
||||
|
||||
if (!readded_songs.isEmpty()) {
|
||||
emit watcher_->SongsReadded(readded_songs);
|
||||
readded_songs.clear();
|
||||
}
|
||||
|
||||
if (!new_subdirs.isEmpty()) {
|
||||
emit watcher_->SubdirsDiscovered(new_subdirs);
|
||||
new_subdirs.clear();
|
||||
}
|
||||
|
||||
if (!touched_subdirs.isEmpty()) {
|
||||
emit watcher_->SubdirsMTimeUpdated(touched_subdirs);
|
||||
touched_subdirs.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
SongList CollectionWatcher::ScanTransaction::FindSongsInSubdirectory(const QString &path) {
|
||||
|
||||
if (cached_songs_dirty_) {
|
||||
@@ -219,18 +249,18 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
||||
|
||||
if (subdirs.isEmpty()) {
|
||||
// This is a new directory that we've never seen before. Scan it fully.
|
||||
ScanTransaction transaction(this, dir.id, false);
|
||||
ScanTransaction transaction(this, dir.id, false, false, prevent_delete_);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(1);
|
||||
ScanSubdirectory(dir.path, Subdirectory(), &transaction);
|
||||
}
|
||||
else {
|
||||
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
|
||||
ScanTransaction transaction(this, dir.id, true);
|
||||
ScanTransaction transaction(this, dir.id, true, false, prevent_delete_);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(subdirs.count());
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_) break;
|
||||
|
||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction);
|
||||
|
||||
@@ -388,7 +418,6 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
QString image = ImageForSong(file, album_art);
|
||||
|
||||
for (Song song : song_list) {
|
||||
song.set_source(source_);
|
||||
song.set_directory_id(t->dir());
|
||||
if (song.art_automatic().isEmpty()) song.set_art_automatic(image);
|
||||
t->new_songs << song;
|
||||
@@ -417,6 +446,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
|
||||
t->AddToProgress(1);
|
||||
|
||||
if (live_scanning_) t->CommitNewOrUpdatedSongs();
|
||||
|
||||
// Recurse into the new subdirs that we found
|
||||
t->AddToProgressMax(my_new_subdirs.count());
|
||||
for (const Subdirectory &my_new_subdir : my_new_subdirs) {
|
||||
@@ -477,8 +508,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const So
|
||||
}
|
||||
}
|
||||
|
||||
Song song_on_disk;
|
||||
song_on_disk.set_source(source_);
|
||||
Song song_on_disk(source_);
|
||||
song_on_disk.set_directory_id(t->dir());
|
||||
TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
||||
|
||||
@@ -520,9 +550,8 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
||||
// it's a normal media file
|
||||
}
|
||||
else {
|
||||
Song song;
|
||||
Song song(source_);
|
||||
TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
||||
|
||||
if (song.is_valid()) {
|
||||
song_list << song;
|
||||
}
|
||||
@@ -636,11 +665,11 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
for (int dir : rescan_queue_.keys()) {
|
||||
if (stop_requested_) return;
|
||||
ScanTransaction transaction(this, dir, false);
|
||||
ScanTransaction transaction(this, dir, false, false, prevent_delete_);
|
||||
transaction.AddToProgressMax(rescan_queue_[dir].count());
|
||||
|
||||
for (const QString &path : rescan_queue_[dir]) {
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_) break;
|
||||
Subdirectory subdir;
|
||||
subdir.directory_id = dir;
|
||||
subdir.mtime = 0;
|
||||
@@ -685,7 +714,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
QString biggest_path;
|
||||
|
||||
for (const QString &path : filtered) {
|
||||
if (stop_requested_) return QString();
|
||||
if (stop_requested_) break;
|
||||
|
||||
QImage image(path);
|
||||
if (image.isNull()) continue;
|
||||
@@ -728,16 +757,18 @@ void CollectionWatcher::ReloadSettings() {
|
||||
|
||||
const bool was_monitoring_before = monitor_;
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||
monitor_ = s.value("monitor", true).toBool();
|
||||
live_scanning_ = s.value("live_scanning", false).toBool();
|
||||
prevent_delete_ = s.value("prevent_delete", false).toBool();
|
||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||
s.endGroup();
|
||||
|
||||
best_image_filters_.clear();
|
||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||
for (const QString &filter : filters) {
|
||||
QString s = filter.trimmed();
|
||||
if (!s.isEmpty()) best_image_filters_ << s;
|
||||
QString str = filter.trimmed();
|
||||
if (!str.isEmpty()) best_image_filters_ << str;
|
||||
}
|
||||
|
||||
if (!monitor_ && was_monitoring_before) {
|
||||
@@ -780,19 +811,61 @@ void CollectionWatcher::FullScanAsync() {
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::RescanTracksAsync(const SongList &songs) {
|
||||
|
||||
// Is List thread safe? if not, this may crash.
|
||||
song_rescan_queue_.append(songs);
|
||||
|
||||
// Call only if it's not already running
|
||||
if (!rescan_in_progress_)
|
||||
QMetaObject::invokeMethod(this, "RescanTracksNow", Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::IncrementalScanNow() { PerformScan(true, false); }
|
||||
|
||||
void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
||||
|
||||
void CollectionWatcher::RescanTracksNow() {
|
||||
|
||||
Q_ASSERT(!rescan_in_progress_);
|
||||
stop_requested_ = false;
|
||||
|
||||
// Currently we are too stupid to rescan one file at a time, so we'll just scan the full directiories
|
||||
QStringList scanned_dirs; // To avoid double scans
|
||||
while (!song_rescan_queue_.isEmpty()) {
|
||||
if (stop_requested_) break;
|
||||
Song song = song_rescan_queue_.takeFirst();
|
||||
QString songdir = song.url().toLocalFile().section('/', 0, -2);
|
||||
if (!scanned_dirs.contains(songdir)) {
|
||||
qLog(Debug) << "Song" << song.title() << "dir id" << song.directory_id() << "dir" << songdir;
|
||||
ScanTransaction transaction(this, song.directory_id(), false, false, prevent_delete_);
|
||||
ScanSubdirectory(songdir, Subdirectory(), &transaction);
|
||||
scanned_dirs << songdir;
|
||||
emit CompilationsNeedUpdating();
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Directory" << songdir << "already scanned - skipping.";
|
||||
}
|
||||
}
|
||||
Q_ASSERT(song_rescan_queue_.isEmpty());
|
||||
rescan_in_progress_ = false;
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::PerformScan(bool incremental, bool ignore_mtimes) {
|
||||
|
||||
stop_requested_ = false;
|
||||
|
||||
for (const Directory &dir : watched_dirs_.values()) {
|
||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes);
|
||||
|
||||
if (stop_requested_) break;
|
||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, prevent_delete_);
|
||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||
transaction.AddToProgressMax(subdirs.count());
|
||||
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_) break;
|
||||
|
||||
ScanSubdirectory(subdir.path, subdir, &transaction);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user