2009-12-24 20:16:07 +01:00
|
|
|
#include "librarywatcher.h"
|
|
|
|
#include "librarybackend.h"
|
|
|
|
#include "enginebase.h"
|
|
|
|
|
|
|
|
#include <QFileSystemWatcher>
|
|
|
|
#include <QDirIterator>
|
|
|
|
#include <QtDebug>
|
|
|
|
#include <QThread>
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QTimer>
|
|
|
|
|
|
|
|
#include <taglib/fileref.h>
|
|
|
|
#include <taglib/tag.h>
|
|
|
|
|
|
|
|
|
|
|
|
LibraryWatcher::LibraryWatcher(QObject* parent)
|
|
|
|
: QObject(parent),
|
|
|
|
fs_watcher_(new QFileSystemWatcher(this)),
|
2010-02-14 01:37:44 +01:00
|
|
|
rescan_timer_(new QTimer(this)),
|
2010-02-15 11:23:29 +01:00
|
|
|
total_watches_(0)
|
2009-12-24 20:16:07 +01:00
|
|
|
{
|
|
|
|
rescan_timer_->setInterval(1000);
|
|
|
|
rescan_timer_->setSingleShot(true);
|
|
|
|
|
|
|
|
connect(fs_watcher_, SIGNAL(directoryChanged(QString)), SLOT(DirectoryChanged(QString)));
|
|
|
|
connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanPathsNow()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void LibraryWatcher::AddDirectories(const DirectoryList& directories) {
|
|
|
|
// Iterate through each directory to find a list of files that look like they
|
|
|
|
// could be music.
|
|
|
|
|
|
|
|
foreach (const Directory& dir, directories) {
|
|
|
|
paths_watched_[dir.path] = dir;
|
|
|
|
ScanDirectory(dir.path);
|
|
|
|
|
|
|
|
// Start monitoring this directory for more changes
|
|
|
|
fs_watcher_->addPath(dir.path);
|
2010-02-15 11:23:29 +01:00
|
|
|
++total_watches_;
|
2010-01-16 18:17:00 +01:00
|
|
|
|
|
|
|
// And all the subdirectories
|
|
|
|
QDirIterator it(dir.path,
|
|
|
|
QDir::NoDotAndDotDot | QDir::Dirs,
|
|
|
|
QDirIterator::Subdirectories);
|
|
|
|
while (it.hasNext()) {
|
|
|
|
QString subdir(it.next());
|
|
|
|
fs_watcher_->addPath(subdir);
|
|
|
|
paths_watched_[subdir] = dir;
|
2010-02-15 11:24:20 +01:00
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
if (++total_watches_ > kMaxWatches) {
|
|
|
|
qWarning() << "Trying to watch more files than we can manage";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-16 18:17:00 +01:00
|
|
|
}
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LibraryWatcher::RemoveDirectories(const DirectoryList &directories) {
|
|
|
|
foreach (const Directory& dir, directories) {
|
|
|
|
fs_watcher_->removePath(dir.path);
|
|
|
|
paths_watched_.remove(dir.path);
|
|
|
|
paths_needing_rescan_.removeAll(dir.path);
|
2010-01-16 18:17:00 +01:00
|
|
|
|
|
|
|
// And all the subdirectories
|
|
|
|
QDirIterator it(dir.path,
|
|
|
|
QDir::NoDotAndDotDot | QDir::Dirs,
|
|
|
|
QDirIterator::Subdirectories);
|
|
|
|
while (it.hasNext()) {
|
|
|
|
QString subdir(it.next());
|
|
|
|
fs_watcher_->removePath(subdir);
|
|
|
|
paths_watched_.remove(subdir);
|
|
|
|
}
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LibraryWatcher::ScanDirectory(const QString& path) {
|
|
|
|
const Directory& dir = paths_watched_[path];
|
|
|
|
qDebug() << "Scanning" << path;
|
|
|
|
emit ScanStarted();
|
|
|
|
|
|
|
|
QStringList files_on_disk;
|
|
|
|
QDirIterator it(dir.path,
|
|
|
|
QDir::Files | QDir::NoDotAndDotDot | QDir::Readable,
|
|
|
|
QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
|
|
|
while (it.hasNext()) {
|
|
|
|
QString path(it.next());
|
|
|
|
|
|
|
|
// Don't bother if the engine can't decode it
|
|
|
|
if (!engine_->canDecode(QUrl::fromLocalFile(path)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
files_on_disk << path;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ask the database for a list of files in this directory
|
|
|
|
SongList songs_in_db = backend_->FindSongsInDirectory(dir.id);
|
|
|
|
|
|
|
|
// Now compare the list from the database with the list of files on disk
|
|
|
|
SongList new_songs;
|
|
|
|
SongList touched_songs;
|
|
|
|
foreach (const QString& file, files_on_disk) {
|
|
|
|
Song matching_song;
|
|
|
|
if (FindSongByPath(songs_in_db, file, &matching_song)) {
|
|
|
|
// The song is in the database and still on disk.
|
|
|
|
// Check the mtime to see if it's been changed since it was added.
|
|
|
|
|
|
|
|
if (matching_song.mtime() != QFileInfo(file).lastModified().toTime_t()) {
|
|
|
|
qDebug() << file << "changed";
|
|
|
|
|
|
|
|
// It's changed - reread the metadata from the file
|
|
|
|
Song song_on_disk;
|
|
|
|
song_on_disk.InitFromFile(file, dir.id);
|
|
|
|
song_on_disk.set_id(matching_song.id());
|
|
|
|
|
|
|
|
if (!matching_song.IsMetadataEqual(song_on_disk)) {
|
|
|
|
qDebug() << file << "metadata changed";
|
|
|
|
// Update the song in the DB
|
|
|
|
new_songs << song_on_disk;
|
|
|
|
} else {
|
|
|
|
// Only the metadata changed
|
|
|
|
touched_songs << song_on_disk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The song is on disk but not in the DB
|
|
|
|
|
|
|
|
Song song;
|
|
|
|
song.InitFromFile(file, dir.id);
|
|
|
|
if (!song.is_valid())
|
|
|
|
continue;
|
|
|
|
qDebug() << file << "created";
|
|
|
|
|
|
|
|
new_songs << song;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!new_songs.isEmpty())
|
|
|
|
emit NewOrUpdatedSongs(new_songs);
|
|
|
|
|
|
|
|
if (!touched_songs.isEmpty())
|
|
|
|
emit SongsMTimeUpdated(touched_songs);
|
|
|
|
|
|
|
|
// Look for deleted songs
|
|
|
|
SongList deleted_songs;
|
|
|
|
foreach (const Song& song, songs_in_db) {
|
|
|
|
if (!files_on_disk.contains(song.filename())) {
|
|
|
|
qDebug() << "Song deleted from disk:" << song.filename();
|
|
|
|
deleted_songs << song;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!deleted_songs.isEmpty())
|
|
|
|
emit SongsDeleted(deleted_songs);
|
|
|
|
|
|
|
|
qDebug() << "Finished scanning" << path;
|
|
|
|
emit ScanFinished();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LibraryWatcher::FindSongByPath(const SongList& list, const QString& path, Song* out) {
|
|
|
|
foreach (const Song& song, list) {
|
|
|
|
if (song.filename() == path) {
|
|
|
|
*out = song;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LibraryWatcher::DirectoryChanged(const QString &path) {
|
2010-01-16 18:17:00 +01:00
|
|
|
qDebug() << path;
|
2009-12-24 20:16:07 +01:00
|
|
|
if (!paths_needing_rescan_.contains(path))
|
|
|
|
paths_needing_rescan_ << path;
|
|
|
|
|
|
|
|
rescan_timer_->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LibraryWatcher::RescanPathsNow() {
|
|
|
|
foreach (const QString& path, paths_needing_rescan_)
|
|
|
|
ScanDirectory(path);
|
|
|
|
paths_needing_rescan_.clear();
|
|
|
|
}
|