2010-11-20 14:27:10 +01:00
|
|
|
/* This file is part of Clementine.
|
|
|
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
2010-03-24 00:11:46 +01:00
|
|
|
|
|
|
|
Clementine is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Clementine is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2009-12-24 20:16:07 +01:00
|
|
|
#ifndef LIBRARYWATCHER_H
|
|
|
|
#define LIBRARYWATCHER_H
|
|
|
|
|
2012-01-05 16:56:46 +01:00
|
|
|
#include <QHash>
|
2020-01-21 09:32:55 +01:00
|
|
|
#include <QMap>
|
|
|
|
#include <QMutex>
|
2009-12-24 20:16:07 +01:00
|
|
|
#include <QObject>
|
|
|
|
#include <QStringList>
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
#include "core/song.h"
|
|
|
|
#include "directory.h"
|
|
|
|
|
2009-12-24 20:16:07 +01:00
|
|
|
class QFileSystemWatcher;
|
|
|
|
class QTimer;
|
|
|
|
|
2010-12-28 16:36:01 +01:00
|
|
|
class CueParser;
|
2012-01-05 15:51:23 +01:00
|
|
|
class FileSystemWatcherInterface;
|
2010-05-09 02:10:26 +02:00
|
|
|
class LibraryBackend;
|
2010-06-23 15:21:30 +02:00
|
|
|
class TaskManager;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
class LibraryWatcher : public QObject {
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
2014-02-10 16:03:54 +01:00
|
|
|
LibraryWatcher(QObject* parent = nullptr);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2010-05-25 22:40:45 +02:00
|
|
|
static const char* kSettingsGroup;
|
|
|
|
|
2010-06-26 14:41:18 +02:00
|
|
|
void set_backend(LibraryBackend* backend) { backend_ = backend; }
|
2014-02-07 16:34:20 +01:00
|
|
|
void set_task_manager(TaskManager* task_manager) {
|
|
|
|
task_manager_ = task_manager;
|
|
|
|
}
|
|
|
|
void set_device_name(const QString& device_name) {
|
|
|
|
device_name_ = device_name;
|
|
|
|
}
|
2012-01-05 15:51:23 +01:00
|
|
|
|
2010-05-25 22:40:45 +02:00
|
|
|
void IncrementalScanAsync();
|
2011-02-17 20:57:14 +01:00
|
|
|
void FullScanAsync();
|
2010-06-24 23:46:18 +02:00
|
|
|
void SetRescanPausedAsync(bool pause);
|
2010-07-10 19:03:28 +02:00
|
|
|
void ReloadSettingsAsync();
|
2020-01-21 09:32:55 +01:00
|
|
|
// This thread-safe method will cause a scan of this directory to cancel to
|
|
|
|
// unblock the watcher thread. It will then invoke the DoRemoveDirectory on
|
|
|
|
// the watcher's thread to complete the removal.
|
2020-01-25 20:59:33 +01:00
|
|
|
void RemoveDirectory(int dir_id);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2020-01-21 09:32:55 +01:00
|
|
|
void Stop() { watched_dirs_.StopAll(); }
|
2010-03-25 15:33:09 +01:00
|
|
|
|
2020-01-21 09:32:55 +01:00
|
|
|
signals:
|
2009-12-24 20:16:07 +01:00
|
|
|
void NewOrUpdatedSongs(const SongList& songs);
|
|
|
|
void SongsMTimeUpdated(const SongList& songs);
|
|
|
|
void SongsDeleted(const SongList& songs);
|
2014-02-01 20:21:28 +01:00
|
|
|
void SongsReadded(const SongList& songs, bool unavailable = false);
|
2010-04-01 18:59:32 +02:00
|
|
|
void SubdirsDiscovered(const SubdirectoryList& subdirs);
|
|
|
|
void SubdirsMTimeUpdated(const SubdirectoryList& subdirs);
|
2010-06-02 18:22:20 +02:00
|
|
|
void CompilationsNeedUpdating();
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2010-07-04 17:56:08 +02:00
|
|
|
void ScanStarted(int task_id);
|
|
|
|
|
2020-01-13 10:02:40 +01:00
|
|
|
void Error(const QString& message);
|
|
|
|
|
2009-12-24 20:16:07 +01:00
|
|
|
public slots:
|
2010-05-25 22:40:45 +02:00
|
|
|
void ReloadSettings();
|
2010-04-01 18:59:32 +02:00
|
|
|
void AddDirectory(const Directory& dir, const SubdirectoryList& subdirs);
|
2010-06-24 23:46:18 +02:00
|
|
|
void SetRescanPaused(bool pause);
|
2010-04-01 18:59:32 +02:00
|
|
|
|
|
|
|
private:
|
2020-01-21 09:32:55 +01:00
|
|
|
class WatchedDir : public Directory {
|
|
|
|
public:
|
|
|
|
WatchedDir() : active_(true) {}
|
|
|
|
WatchedDir(const Directory& dir) : Directory(dir), active_(true) {}
|
|
|
|
|
|
|
|
bool active_;
|
|
|
|
};
|
|
|
|
|
2010-04-01 18:59:32 +02:00
|
|
|
// 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()
|
|
|
|
// adds its results to the members of this transaction class, and they are
|
|
|
|
// "committed" through calls to the LibraryBackend in the transaction's dtor.
|
|
|
|
// The transaction also caches the list of songs in this directory according
|
|
|
|
// to the library. Multiple calls to FindSongsInSubdirectory during one
|
|
|
|
// transaction will only result in one call to
|
|
|
|
// LibraryBackend::FindSongsInDirectory.
|
|
|
|
class ScanTransaction {
|
|
|
|
public:
|
2020-01-21 09:32:55 +01:00
|
|
|
ScanTransaction(LibraryWatcher* watcher,
|
|
|
|
const LibraryWatcher::WatchedDir& dir, bool incremental,
|
|
|
|
bool ignores_mtime = false);
|
2010-04-01 18:59:32 +02:00
|
|
|
~ScanTransaction();
|
|
|
|
|
|
|
|
SongList FindSongsInSubdirectory(const QString& path);
|
2010-04-04 16:59:55 +02:00
|
|
|
bool HasSeenSubdir(const QString& path);
|
|
|
|
void SetKnownSubdirs(const SubdirectoryList& subdirs);
|
2010-04-23 15:29:11 +02:00
|
|
|
SubdirectoryList GetImmediateSubdirs(const QString& path);
|
2010-05-25 22:40:45 +02:00
|
|
|
SubdirectoryList GetAllSubdirs();
|
2010-04-01 18:59:32 +02:00
|
|
|
|
2010-06-23 16:00:18 +02:00
|
|
|
void AddToProgress(int n = 1);
|
|
|
|
void AddToProgressMax(int n);
|
|
|
|
|
2020-01-21 05:12:56 +01:00
|
|
|
int dir_id() const { return dir_.id; }
|
2010-04-01 18:59:32 +02:00
|
|
|
bool is_incremental() const { return incremental_; }
|
2011-02-17 20:57:14 +01:00
|
|
|
bool ignores_mtime() const { return ignores_mtime_; }
|
2010-04-01 18:59:32 +02:00
|
|
|
|
2020-01-21 09:32:55 +01:00
|
|
|
bool aborted() { return !dir_.active_; }
|
|
|
|
|
2010-04-01 18:59:32 +02:00
|
|
|
SongList deleted_songs;
|
2014-02-01 20:21:28 +01:00
|
|
|
SongList readded_songs;
|
2010-04-01 18:59:32 +02:00
|
|
|
SongList new_songs;
|
|
|
|
SongList touched_songs;
|
|
|
|
SubdirectoryList new_subdirs;
|
|
|
|
SubdirectoryList touched_subdirs;
|
2019-11-12 19:55:48 +01:00
|
|
|
SubdirectoryList deleted_subdirs;
|
2010-04-01 18:59:32 +02:00
|
|
|
|
|
|
|
private:
|
2014-02-07 16:34:20 +01:00
|
|
|
ScanTransaction& operator=(const ScanTransaction&) { return *this; }
|
2010-04-01 18:59:32 +02:00
|
|
|
|
2010-06-23 15:21:30 +02:00
|
|
|
int task_id_;
|
2010-06-23 16:00:18 +02:00
|
|
|
int progress_;
|
|
|
|
int progress_max_;
|
2010-06-23 15:21:30 +02:00
|
|
|
|
2020-01-21 09:32:55 +01:00
|
|
|
const WatchedDir& dir_;
|
2011-02-17 21:04:58 +01:00
|
|
|
// Incremental scan enters a directory only if it has changed since the
|
|
|
|
// last scan.
|
2010-04-01 18:59:32 +02:00
|
|
|
bool incremental_;
|
2011-02-17 21:04:58 +01:00
|
|
|
// This type of scan updates every file in a folder that's
|
|
|
|
// being scanned. Even if it detects the file hasn't changed since
|
|
|
|
// the last scan. Also, since it's ignoring mtimes on folders too,
|
|
|
|
// it will go as deep in the folder hierarchy as it's possible.
|
2011-02-17 20:57:14 +01:00
|
|
|
bool ignores_mtime_;
|
|
|
|
|
2010-04-01 18:59:32 +02:00
|
|
|
LibraryWatcher* watcher_;
|
2010-04-04 16:59:55 +02:00
|
|
|
|
2010-04-01 18:59:32 +02:00
|
|
|
SongList cached_songs_;
|
|
|
|
bool cached_songs_dirty_;
|
2010-04-04 16:59:55 +02:00
|
|
|
|
|
|
|
SubdirectoryList known_subdirs_;
|
|
|
|
bool known_subdirs_dirty_;
|
2010-04-01 18:59:32 +02:00
|
|
|
};
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
private slots:
|
|
|
|
void DirectoryChanged(const QString& path);
|
2010-05-25 22:40:45 +02:00
|
|
|
void IncrementalScanNow();
|
2011-02-17 20:57:14 +01:00
|
|
|
void FullScanNow();
|
2009-12-24 20:16:07 +01:00
|
|
|
void RescanPathsNow();
|
2010-04-01 18:59:32 +02:00
|
|
|
void ScanSubdirectory(const QString& path, const Subdirectory& subdir,
|
2010-04-04 16:09:07 +02:00
|
|
|
ScanTransaction* t, bool force_noincremental = false);
|
2020-01-25 20:59:33 +01:00
|
|
|
void DoRemoveDirectory(int dir_id);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
private:
|
2014-02-07 16:34:20 +01:00
|
|
|
static bool FindSongByPath(const SongList& list, const QString& path,
|
|
|
|
Song* out);
|
|
|
|
inline static QString NoExtensionPart(const QString& fileName);
|
|
|
|
inline static QString ExtensionPart(const QString& fileName);
|
|
|
|
inline static QString DirectoryPart(const QString& fileName);
|
2020-01-21 09:32:55 +01:00
|
|
|
QString PickBestImage(const QStringList& images, ScanTransaction* t);
|
2014-02-07 16:34:20 +01:00
|
|
|
QString ImageForSong(const QString& path,
|
2020-01-23 01:02:08 +01:00
|
|
|
QMap<QString, QStringList>* album_art,
|
2020-01-21 09:32:55 +01:00
|
|
|
ScanTransaction* t);
|
2012-01-29 18:39:28 +01:00
|
|
|
void AddWatch(const Directory& dir, const QString& path);
|
2019-11-12 20:05:53 +01:00
|
|
|
void RemoveWatch(const Directory& dir, const Subdirectory& subdir);
|
2011-01-15 19:46:23 +01:00
|
|
|
uint GetMtimeForCue(const QString& cue_path);
|
2011-02-17 20:57:14 +01:00
|
|
|
void PerformScan(bool incremental, bool ignore_mtimes);
|
2011-01-15 19:46:23 +01:00
|
|
|
|
|
|
|
// Updates the sections of a cue associated and altered (according to mtime)
|
|
|
|
// media file during a scan.
|
|
|
|
void UpdateCueAssociatedSongs(const QString& file, const QString& path,
|
2014-02-07 16:34:20 +01:00
|
|
|
const QString& matching_cue,
|
|
|
|
const QString& image, ScanTransaction* t);
|
2011-01-15 19:46:23 +01:00
|
|
|
// Updates a single non-cue associated and altered (according to mtime) song
|
|
|
|
// during a scan.
|
2014-02-07 16:34:20 +01:00
|
|
|
void UpdateNonCueAssociatedSong(const QString& file,
|
|
|
|
const Song& matching_song,
|
2011-01-15 19:46:23 +01:00
|
|
|
const QString& image, bool cue_deleted,
|
2014-02-07 16:34:20 +01:00
|
|
|
ScanTransaction* t);
|
2014-02-01 20:21:28 +01:00
|
|
|
// Updates a new song with some metadata taken from it's equivalent old
|
2011-01-15 19:46:23 +01:00
|
|
|
// song (for example rating and score).
|
2011-01-12 00:09:59 +01:00
|
|
|
void PreserveUserSetData(const QString& file, const QString& image,
|
2014-02-07 16:34:20 +01:00
|
|
|
const Song& matching_song, Song* out,
|
|
|
|
ScanTransaction* t);
|
|
|
|
// Scans a single media file that's present on the disk but not yet in the
|
|
|
|
// library.
|
2011-01-15 19:46:23 +01:00
|
|
|
// It may result in a multiple files added to the library when the media file
|
|
|
|
// has many sections (like a CUE related media file).
|
|
|
|
SongList ScanNewFile(const QString& file, const QString& path,
|
2014-02-07 16:34:20 +01:00
|
|
|
const QString& matching_cue,
|
|
|
|
QSet<QString>* cues_processed);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
private:
|
2010-05-09 02:10:26 +02:00
|
|
|
LibraryBackend* backend_;
|
2010-06-23 15:21:30 +02:00
|
|
|
TaskManager* task_manager_;
|
2010-06-26 14:41:18 +02:00
|
|
|
QString device_name_;
|
2012-01-05 15:51:23 +01:00
|
|
|
|
|
|
|
FileSystemWatcherInterface* fs_watcher_;
|
2012-01-29 18:39:28 +01:00
|
|
|
QHash<QString, Directory> subdir_mapping_;
|
2012-01-05 15:51:23 +01:00
|
|
|
|
2014-02-01 20:21:28 +01:00
|
|
|
/* A list of words use to try to identify the (likely) best image
|
2010-12-28 13:52:58 +01:00
|
|
|
* found in an directory to use as cover artwork.
|
|
|
|
* e.g. using ["front", "cover"] would identify front.jpg and
|
|
|
|
* exclude back.jpg.
|
|
|
|
*/
|
2014-02-01 20:21:28 +01:00
|
|
|
QStringList best_image_filters_;
|
2012-01-05 15:51:23 +01:00
|
|
|
|
2010-05-25 22:40:45 +02:00
|
|
|
bool scan_on_startup_;
|
2010-07-10 19:03:28 +02:00
|
|
|
bool monitor_;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2020-01-21 09:32:55 +01:00
|
|
|
// 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();
|
2020-01-25 20:59:33 +01:00
|
|
|
void Stop(int dir_id);
|
2020-01-21 09:32:55 +01:00
|
|
|
|
|
|
|
// 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_;
|
2009-12-24 20:16:07 +01:00
|
|
|
QTimer* rescan_timer_;
|
2014-02-07 16:34:20 +01:00
|
|
|
QMap<int, QStringList>
|
|
|
|
rescan_queue_; // dir id -> list of subdirs to be scanned
|
2010-06-24 23:46:18 +02:00
|
|
|
bool rescan_paused_;
|
2010-02-14 01:37:44 +01:00
|
|
|
|
|
|
|
int total_watches_;
|
|
|
|
|
2010-12-28 16:36:01 +01:00
|
|
|
CueParser* cue_parser_;
|
|
|
|
|
2010-04-01 18:59:32 +02:00
|
|
|
static QStringList sValidImages;
|
2009-12-24 20:16:07 +01:00
|
|
|
};
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
inline QString LibraryWatcher::NoExtensionPart(const QString& fileName) {
|
|
|
|
return fileName.contains('.') ? fileName.section('.', 0, -2) : "";
|
2010-12-28 16:36:01 +01:00
|
|
|
}
|
2010-02-28 01:35:20 +01:00
|
|
|
// Thanks Amarok
|
2014-02-07 16:34:20 +01:00
|
|
|
inline QString LibraryWatcher::ExtensionPart(const QString& fileName) {
|
|
|
|
return fileName.contains('.')
|
|
|
|
? fileName.mid(fileName.lastIndexOf('.') + 1).toLower()
|
|
|
|
: "";
|
2010-02-28 01:35:20 +01:00
|
|
|
}
|
2014-02-07 16:34:20 +01:00
|
|
|
inline QString LibraryWatcher::DirectoryPart(const QString& fileName) {
|
|
|
|
return fileName.section('/', 0, -2);
|
2010-02-28 01:35:20 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
#endif // LIBRARYWATCHER_H
|