/* This file is part of Clementine. Copyright 2010, David Sansome 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 . */ #ifndef LIBRARYWATCHER_H #define LIBRARYWATCHER_H #include #include #include #include #include #include "core/song.h" #include "directory.h" class QFileSystemWatcher; class QTimer; class CueParser; class FileSystemWatcherInterface; class LibraryBackend; class TaskManager; class LibraryWatcher : public QObject { Q_OBJECT public: LibraryWatcher(QObject* parent = nullptr); static const char* kSettingsGroup; void set_backend(LibraryBackend* backend) { backend_ = backend; } void set_task_manager(TaskManager* task_manager) { task_manager_ = task_manager; } void set_device_name(const QString& device_name) { device_name_ = device_name; } void IncrementalScanAsync(); void FullScanAsync(); void SetRescanPausedAsync(bool pause); void ReloadSettingsAsync(); // 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. void RemoveDirectory(int dir_id); void Stop() { watched_dirs_.StopAll(); } signals: void NewOrUpdatedSongs(const SongList& songs); void SongsMTimeUpdated(const SongList& songs); void SongsDeleted(const SongList& songs); void SongsReadded(const SongList& songs, bool unavailable = false); void SubdirsDiscovered(const SubdirectoryList& subdirs); void SubdirsMTimeUpdated(const SubdirectoryList& subdirs); void CompilationsNeedUpdating(); void ScanStarted(int task_id); void Error(const QString& message); public slots: void ReloadSettings(); void AddDirectory(const Directory& dir, const SubdirectoryList& subdirs); 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() // 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: ScanTransaction(LibraryWatcher* watcher, const LibraryWatcher::WatchedDir& dir, bool incremental, bool ignores_mtime = false); ~ScanTransaction(); SongList FindSongsInSubdirectory(const QString& path); bool HasSeenSubdir(const QString& path); void SetKnownSubdirs(const SubdirectoryList& subdirs); SubdirectoryList GetImmediateSubdirs(const QString& path); SubdirectoryList GetAllSubdirs(); void AddToProgress(int n = 1); void AddToProgressMax(int n); int dir_id() const { return dir_.id; } 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; SongList touched_songs; SubdirectoryList new_subdirs; SubdirectoryList touched_subdirs; SubdirectoryList deleted_subdirs; private: ScanTransaction& operator=(const ScanTransaction&) { return *this; } int task_id_; int progress_; int progress_max_; const WatchedDir& dir_; // Incremental scan enters a directory only if it has changed since the // last scan. bool incremental_; // 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. bool ignores_mtime_; LibraryWatcher* watcher_; SongList cached_songs_; bool cached_songs_dirty_; SubdirectoryList known_subdirs_; bool known_subdirs_dirty_; }; private slots: void DirectoryChanged(const QString& path); void IncrementalScanNow(); void FullScanNow(); void RescanPathsNow(); void ScanSubdirectory(const QString& path, const Subdirectory& subdir, ScanTransaction* t, bool force_noincremental = false); void DoRemoveDirectory(int dir_id); private: 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); QString PickBestImage(const QStringList& images, ScanTransaction* t); QString ImageForSong(const QString& path, QMap* 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); void PerformScan(bool incremental, bool ignore_mtimes); // 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, const QString& matching_cue, const QString& image, ScanTransaction* t); // Updates a single non-cue associated and altered (according to mtime) song // during a scan. void UpdateNonCueAssociatedSong(const QString& file, const Song& matching_song, const QString& image, bool cue_deleted, ScanTransaction* t); // Updates a new song with some metadata taken from it's equivalent old // song (for example rating and score). void PreserveUserSetData(const QString& file, const QString& image, 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. // 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, const QString& matching_cue, QSet* cues_processed); private: LibraryBackend* backend_; TaskManager* task_manager_; QString device_name_; FileSystemWatcherInterface* fs_watcher_; QHash subdir_mapping_; /* A list of words use to try to identify the (likely) best image * found in an directory to use as cover artwork. * e.g. using ["front", "cover"] would identify front.jpg and * exclude back.jpg. */ QStringList best_image_filters_; // List of file extensions that should be ingored during a scan. QStringList skip_file_extensions_; bool scan_on_startup_; bool monitor_; // 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(int dir_id); // This should only be called on the watcher thread. void Remove(int id); void Add(const Directory& dir); QMap list_; private: QMutex mutex_; }; WatchList watched_dirs_; QTimer* rescan_timer_; QMap rescan_queue_; // dir id -> list of subdirs to be scanned bool rescan_paused_; int total_watches_; CueParser* cue_parser_; static QStringList sValidImages; }; inline QString LibraryWatcher::NoExtensionPart(const QString& fileName) { return fileName.contains('.') ? fileName.section('.', 0, -2) : ""; } // Thanks Amarok inline QString LibraryWatcher::ExtensionPart(const QString& fileName) { return fileName.contains('.') ? fileName.mid(fileName.lastIndexOf('.') + 1).toLower() : ""; } inline QString LibraryWatcher::DirectoryPart(const QString& fileName) { return fileName.section('/', 0, -2); } #endif // LIBRARYWATCHER_H