strawberry-audio-player-win.../src/collection/collectionwatcher.h

265 lines
10 KiB
C
Raw Normal View History

2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
2023-05-14 11:34:55 +02:00
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
2018-02-27 18:06:05 +01:00
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
#ifndef COLLECTIONWATCHER_H
#define COLLECTIONWATCHER_H
#include "config.h"
#include <QtGlobal>
2018-02-27 18:06:05 +01:00
#include <QObject>
#include <QHash>
2018-02-27 18:06:05 +01:00
#include <QMap>
#include <QMultiMap>
#include <QSet>
#include <QString>
#include <QStringList>
2020-02-08 03:40:30 +01:00
#include <QUrl>
2018-02-27 18:06:05 +01:00
#include "collectiondirectory.h"
#include "core/shared_ptr.h"
2018-02-27 18:06:05 +01:00
#include "core/song.h"
2020-02-08 03:40:30 +01:00
class QThread;
class QTimer;
2018-02-27 18:06:05 +01:00
class CollectionBackend;
class FileSystemWatcherInterface;
2018-02-27 18:06:05 +01:00
class TaskManager;
class CueParser;
2018-02-27 18:06:05 +01:00
class CollectionWatcher : public QObject {
Q_OBJECT
public:
2020-04-07 16:49:15 +02:00
explicit CollectionWatcher(Song::Source source, QObject *parent = nullptr);
~CollectionWatcher();
2018-02-27 18:06:05 +01:00
Song::Source source() { return source_; }
void set_backend(SharedPtr<CollectionBackend> backend) { backend_ = backend; }
void set_task_manager(SharedPtr<TaskManager> task_manager) { task_manager_ = task_manager; }
void set_device_name(const QString &device_name) { device_name_ = device_name; }
2018-02-27 18:06:05 +01:00
void IncrementalScanAsync();
void FullScanAsync();
void SetRescanPausedAsync(const bool pause);
2018-02-27 18:06:05 +01:00
void ReloadSettingsAsync();
void Stop() { stop_requested_ = true; }
void Abort() { abort_requested_ = true; }
2018-02-27 18:06:05 +01:00
void ExitAsync();
void RescanSongsAsync(const SongList &songs);
2020-04-07 16:49:15 +02:00
signals:
void NewOrUpdatedSongs(const SongList &songs);
void SongsMTimeUpdated(const SongList &songs);
void SongsDeleted(const SongList &songs);
void SongsUnavailable(const SongList &songs, const bool unavailable = true);
void SongsReadded(const SongList &songs, const bool unavailable = false);
void SubdirsDiscovered(const CollectionSubdirectoryList &subdirs);
void SubdirsMTimeUpdated(const CollectionSubdirectoryList &subdirs);
2018-02-27 18:06:05 +01:00
void CompilationsNeedUpdating();
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
void ExitFinished();
2018-02-27 18:06:05 +01:00
void ScanStarted(const int task_id);
2018-02-27 18:06:05 +01:00
public slots:
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
void RemoveDirectory(const CollectionDirectory &dir);
2018-02-27 18:06:05 +01:00
void SetRescanPaused(bool pause);
private:
// 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 CollectionBackend in the transaction's dtor.
// The transaction also caches the list of songs in this directory according to the collection.
// Multiple calls to FindSongsInSubdirectory during one transaction will only result in one call to CollectionBackend::FindSongsInDirectory.
2018-02-27 18:06:05 +01:00
class ScanTransaction {
public:
ScanTransaction(CollectionWatcher *watcher, const int dir, const bool incremental, const bool ignores_mtime, const bool mark_songs_unavailable);
2018-02-27 18:06:05 +01:00
~ScanTransaction();
SongList FindSongsInSubdirectory(const QString &path);
bool HasSongsWithMissingFingerprint(const QString &path);
bool HasSongsWithMissingLoudnessCharacteristics(const QString &path);
2018-02-27 18:06:05 +01:00
bool HasSeenSubdir(const QString &path);
void SetKnownSubdirs(const CollectionSubdirectoryList &subdirs);
CollectionSubdirectoryList GetImmediateSubdirs(const QString &path);
CollectionSubdirectoryList GetAllSubdirs();
2018-02-27 18:06:05 +01:00
void AddToProgress(const quint64 n = 1);
void AddToProgressMax(const quint64 n);
2018-02-27 18:06:05 +01:00
2019-06-30 21:06:07 +02:00
// Emits the signals for new & deleted songs etc and clears the lists. This causes the new stuff to be updated on UI.
void CommitNewOrUpdatedSongs();
2018-02-27 18:06:05 +01:00
int dir() const { return dir_; }
bool is_incremental() const { return incremental_; }
bool ignores_mtime() const { return ignores_mtime_; }
SongList deleted_songs;
SongList readded_songs;
SongList new_songs;
SongList touched_songs;
CollectionSubdirectoryList new_subdirs;
CollectionSubdirectoryList touched_subdirs;
CollectionSubdirectoryList deleted_subdirs;
2018-02-27 18:06:05 +01:00
QStringList files_changed_path_;
2018-02-27 18:06:05 +01:00
private:
ScanTransaction(const ScanTransaction&) {}
ScanTransaction &operator=(const ScanTransaction&) { return *this; }
2018-02-27 18:06:05 +01:00
int task_id_;
quint64 progress_;
quint64 progress_max_;
2018-02-27 18:06:05 +01:00
int 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.
2018-02-27 18:06:05 +01:00
bool ignores_mtime_;
2019-06-30 21:06:07 +02:00
// Set this to true to prevent deleting missing files from database.
// Useful for unstable network connections.
bool mark_songs_unavailable_;
int expire_unavailable_songs_days_;
2019-06-30 21:06:07 +02:00
2018-02-27 18:06:05 +01:00
CollectionWatcher *watcher_;
QMultiMap<QString, Song> cached_songs_;
2018-02-27 18:06:05 +01:00
bool cached_songs_dirty_;
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
bool cached_songs_missing_fingerprint_dirty_;
QMultiMap<QString, Song> cached_songs_missing_loudness_characteristics_;
bool cached_songs_missing_loudness_characteristics_dirty_;
CollectionSubdirectoryList known_subdirs_;
2018-02-27 18:06:05 +01:00
bool known_subdirs_dirty_;
};
private slots:
void ReloadSettings();
void Exit();
2020-06-14 18:58:24 +02:00
void DirectoryChanged(const QString &subdir);
void IncrementalScanCheck();
2018-02-27 18:06:05 +01:00
void IncrementalScanNow();
void FullScanNow();
void RescanPathsNow();
void ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, CollectionWatcher::ScanTransaction *t, const bool force_noincremental = false);
void RescanSongs(const SongList &songs);
2018-02-27 18:06:05 +01:00
private:
2021-06-22 13:41:38 +02:00
static bool FindSongsByPath(const SongList &songs, const QString &path, SongList *out);
bool FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out);
static bool FindSongsByFingerprint(const QString &file, const SongList &songs, const QString &fingerprint, SongList *out);
2018-02-27 18:06:05 +01:00
inline static QString NoExtensionPart(const QString &fileName);
inline static QString ExtensionPart(const QString &fileName);
inline static QString DirectoryPart(const QString &fileName);
2023-05-14 11:34:55 +02:00
QString PickBestArt(const QStringList &art_automatic_list);
QUrl ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list);
void AddWatch(const CollectionDirectory &dir, const QString &path);
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
2021-06-22 13:41:38 +02:00
static quint64 GetMtimeForCue(const QString &cue_path);
void PerformScan(const bool incremental, const bool ignore_mtimes);
2018-02-27 18:06:05 +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, const QString &fingerprint, const QString &matching_cue, const QUrl &art_automatic, const SongList &old_cue_songs, ScanTransaction *t);
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &art_automatic, const bool cue_deleted, ScanTransaction *t);
2018-02-27 18:06:05 +01:00
// Scans a single media file that's present on the disk but not yet in the collection.
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
SongList ScanNewFile(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, QSet<QString> *cues_processed);
2021-06-22 13:41:38 +02:00
static void AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t);
void PerformEBUR128Analysis(Song &song) const;
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
quint64 FilesCountForSubdirs(ScanTransaction *t, const CollectionSubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
2018-02-27 18:06:05 +01:00
2021-11-27 20:28:00 +01:00
QString FindCueFilename(const QString &filename);
2018-02-27 18:06:05 +01:00
private:
2019-04-08 23:00:07 +02:00
Song::Source source_;
SharedPtr<CollectionBackend> backend_;
SharedPtr<TaskManager> task_manager_;
2018-02-27 18:06:05 +01:00
QString device_name_;
FileSystemWatcherInterface *fs_watcher_;
QThread *original_thread_;
QHash<QString, CollectionDirectory> subdir_mapping_;
2018-02-27 18:06:05 +01:00
2023-05-14 11:34:55 +02:00
// A list of words use to try to identify the (likely) best album cover art found in an directory to use as cover artwork.
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
2023-05-14 11:34:55 +02:00
QStringList best_art_filters_;
2018-02-27 18:06:05 +01:00
bool scan_on_startup_;
bool monitor_;
bool song_tracking_;
bool song_ebur128_loudness_analysis_;
bool mark_songs_unavailable_;
int expire_unavailable_songs_days_;
bool overwrite_playcount_;
bool overwrite_rating_;
2019-06-30 21:06:07 +02:00
bool stop_requested_;
bool abort_requested_;
2018-02-27 18:06:05 +01:00
QMap<int, CollectionDirectory> watched_dirs_;
2018-02-27 18:06:05 +01:00
QTimer *rescan_timer_;
QTimer *periodic_scan_timer_;
2021-07-11 09:49:38 +02:00
QMap<int, QStringList> rescan_queue_; // dir id -> list of subdirs to be scanned
2018-02-27 18:06:05 +01:00
bool rescan_paused_;
int total_watches_;
CueParser *cue_parser_;
static QStringList sValidImages;
2019-06-30 21:06:07 +02:00
qint64 last_scan_time_;
2018-02-27 18:06:05 +01:00
};
2021-06-12 20:53:23 +02:00
inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) {
2018-02-27 18:06:05 +01:00
return fileName.contains('.') ? fileName.section('.', 0, -2) : "";
}
// Thanks Amarok
2021-06-12 20:53:23 +02:00
inline QString CollectionWatcher::ExtensionPart(const QString &fileName) {
2018-02-27 18:06:05 +01:00
return fileName.contains( '.' ) ? fileName.mid( fileName.lastIndexOf('.') + 1 ).toLower() : "";
}
2021-06-12 20:53:23 +02:00
inline QString CollectionWatcher::DirectoryPart(const QString &fileName) {
2018-02-27 18:06:05 +01:00
return fileName.section('/', 0, -2);
}
#endif // COLLECTIONWATCHER_H