2018-02-27 18:06:05 +01:00
|
|
|
/*
|
|
|
|
* Strawberry Music Player
|
|
|
|
* This file was part of Clementine.
|
|
|
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
2021-03-20 21:14:47 +01:00
|
|
|
* Copyright 2018-2021, 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"
|
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QtGlobal>
|
2018-02-27 18:06:05 +01:00
|
|
|
#include <QObject>
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QHash>
|
2018-02-27 18:06:05 +01:00
|
|
|
#include <QMap>
|
2021-04-25 21:16:44 +02:00
|
|
|
#include <QMultiMap>
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QSet>
|
|
|
|
#include <QString>
|
|
|
|
#include <QStringList>
|
2020-02-08 03:40:30 +01:00
|
|
|
#include <QUrl>
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
#include "directory.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;
|
2018-05-01 00:41:33 +02:00
|
|
|
class FileSystemWatcherInterface;
|
2018-02-27 18:06:05 +01:00
|
|
|
class TaskManager;
|
2018-05-01 00:41:33 +02:00
|
|
|
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);
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2022-01-28 23:32:49 +01:00
|
|
|
Song::Source source() { return source_; }
|
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
void set_backend(CollectionBackend *backend) { backend_ = backend; }
|
|
|
|
void set_task_manager(TaskManager *task_manager) { task_manager_ = task_manager; }
|
2021-04-25 21:16:44 +02:00
|
|
|
void set_device_name(const QString &device_name) { device_name_ = device_name; }
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
void IncrementalScanAsync();
|
|
|
|
void FullScanAsync();
|
2019-06-30 21:06:07 +02:00
|
|
|
void RescanTracksAsync(const SongList &songs);
|
2021-04-25 21:16:44 +02:00
|
|
|
void SetRescanPausedAsync(const bool pause);
|
2018-02-27 18:06:05 +01:00
|
|
|
void ReloadSettingsAsync();
|
|
|
|
|
|
|
|
void Stop() { stop_requested_ = true; }
|
2022-01-06 23:14:10 +01:00
|
|
|
void Abort() { abort_requested_ = true; }
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2019-07-24 19:16:51 +02:00
|
|
|
void ExitAsync();
|
|
|
|
|
2020-04-07 16:49:15 +02:00
|
|
|
signals:
|
2021-01-26 16:48:04 +01:00
|
|
|
void NewOrUpdatedSongs(SongList);
|
|
|
|
void SongsMTimeUpdated(SongList);
|
|
|
|
void SongsDeleted(SongList);
|
|
|
|
void SongsUnavailable(SongList songs, bool unavailable = true);
|
|
|
|
void SongsReadded(SongList songs, bool unavailable = false);
|
|
|
|
void SubdirsDiscovered(SubdirectoryList subdirs);
|
|
|
|
void SubdirsMTimeUpdated(SubdirectoryList subdirs);
|
2018-02-27 18:06:05 +01:00
|
|
|
void CompilationsNeedUpdating();
|
2021-04-25 21:16:44 +02:00
|
|
|
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
2019-07-24 19:16:51 +02:00
|
|
|
void ExitFinished();
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
void ScanStarted(int task_id);
|
|
|
|
|
|
|
|
public slots:
|
|
|
|
void AddDirectory(const Directory &dir, const SubdirectoryList &subdirs);
|
|
|
|
void RemoveDirectory(const Directory &dir);
|
|
|
|
void SetRescanPaused(bool pause);
|
|
|
|
|
|
|
|
private:
|
|
|
|
// This class encapsulates a full or partial scan of a directory.
|
2018-05-01 00:41:33 +02:00
|
|
|
// 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:
|
2019-08-11 22:30:28 +02:00
|
|
|
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);
|
2021-04-25 21:16:44 +02:00
|
|
|
bool HasSongsWithMissingFingerprint(const QString &path);
|
2018-02-27 18:06:05 +01:00
|
|
|
bool HasSeenSubdir(const QString &path);
|
|
|
|
void SetKnownSubdirs(const SubdirectoryList &subdirs);
|
|
|
|
SubdirectoryList GetImmediateSubdirs(const QString &path);
|
|
|
|
SubdirectoryList GetAllSubdirs();
|
|
|
|
|
2021-04-25 21:16:44 +02: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;
|
|
|
|
SubdirectoryList new_subdirs;
|
|
|
|
SubdirectoryList touched_subdirs;
|
2019-08-05 18:40:47 +02:00
|
|
|
SubdirectoryList deleted_subdirs;
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2021-04-25 21:16:44 +02:00
|
|
|
QStringList files_changed_path_;
|
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
private:
|
|
|
|
ScanTransaction(const ScanTransaction&) {}
|
2021-04-25 21:16:44 +02:00
|
|
|
ScanTransaction &operator=(const ScanTransaction&) { return *this; }
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
int task_id_;
|
2021-04-25 21:16:44 +02:00
|
|
|
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_;
|
2018-05-01 00:41:33 +02: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.
|
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.
|
2019-08-11 22:30:28 +02:00
|
|
|
bool mark_songs_unavailable_;
|
2021-04-25 21:16:44 +02:00
|
|
|
int expire_unavailable_songs_days_;
|
2019-06-30 21:06:07 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
CollectionWatcher *watcher_;
|
|
|
|
|
2021-04-25 21:16:44 +02:00
|
|
|
QMultiMap<QString, Song> cached_songs_;
|
2018-02-27 18:06:05 +01:00
|
|
|
bool cached_songs_dirty_;
|
|
|
|
|
2021-04-25 21:16:44 +02:00
|
|
|
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
|
|
|
|
bool cached_songs_missing_fingerprint_dirty_;
|
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
SubdirectoryList known_subdirs_;
|
|
|
|
bool known_subdirs_dirty_;
|
|
|
|
};
|
|
|
|
|
|
|
|
private slots:
|
2021-04-25 21:16:44 +02:00
|
|
|
void ReloadSettings();
|
2019-07-24 19:16:51 +02:00
|
|
|
void Exit();
|
2020-06-14 18:58:24 +02:00
|
|
|
void DirectoryChanged(const QString &subdir);
|
2021-04-25 21:16:44 +02:00
|
|
|
void IncrementalScanCheck();
|
2018-02-27 18:06:05 +01:00
|
|
|
void IncrementalScanNow();
|
|
|
|
void FullScanNow();
|
2019-06-30 21:06:07 +02:00
|
|
|
void RescanTracksNow();
|
2018-02-27 18:06:05 +01:00
|
|
|
void RescanPathsNow();
|
2021-04-25 21:16:44 +02:00
|
|
|
void ScanSubdirectory(const QString &path, const Subdirectory &subdir, const quint64 files_count, CollectionWatcher::ScanTransaction *t, const bool force_noincremental = false);
|
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);
|
2021-04-25 21:16:44 +02:00
|
|
|
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);
|
|
|
|
QString PickBestImage(const QStringList &images);
|
2019-07-07 21:14:24 +02:00
|
|
|
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
2018-02-27 18:06:05 +01:00
|
|
|
void AddWatch(const Directory &dir, const QString &path);
|
2019-08-05 18:40:47 +02:00
|
|
|
void RemoveWatch(const Directory &dir, const Subdirectory &subdir);
|
2021-06-22 13:41:38 +02:00
|
|
|
static quint64 GetMtimeForCue(const QString &cue_path);
|
2021-04-25 21:16:44 +02:00
|
|
|
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
|
2021-04-25 21:16:44 +02:00
|
|
|
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &image, const SongList &old_cue_songs, ScanTransaction *t);
|
2018-05-01 00:41:33 +02:00
|
|
|
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
|
2021-04-25 21:16:44 +02:00
|
|
|
void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &image, 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.
|
2018-05-01 00:41:33 +02:00
|
|
|
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
|
2021-04-25 21:16:44 +02:00
|
|
|
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);
|
2021-04-25 21:16:44 +02:00
|
|
|
|
|
|
|
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
|
|
|
|
quint64 FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &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_;
|
2018-02-27 18:06:05 +01:00
|
|
|
CollectionBackend *backend_;
|
|
|
|
TaskManager *task_manager_;
|
|
|
|
QString device_name_;
|
|
|
|
|
|
|
|
FileSystemWatcherInterface *fs_watcher_;
|
2021-04-25 21:16:44 +02:00
|
|
|
QThread *original_thread_;
|
2018-02-27 18:06:05 +01:00
|
|
|
QHash<QString, Directory> subdir_mapping_;
|
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
// 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.
|
2018-02-27 18:06:05 +01:00
|
|
|
QStringList best_image_filters_;
|
|
|
|
|
|
|
|
bool scan_on_startup_;
|
|
|
|
bool monitor_;
|
2021-04-25 21:16:44 +02:00
|
|
|
bool song_tracking_;
|
2019-08-11 22:30:28 +02:00
|
|
|
bool mark_songs_unavailable_;
|
2021-04-25 21:16:44 +02:00
|
|
|
int expire_unavailable_songs_days_;
|
2022-06-05 04:59:50 +02:00
|
|
|
bool overwrite_playcount_;
|
2021-10-31 23:24:32 +01:00
|
|
|
bool overwrite_rating_;
|
2019-06-30 21:06:07 +02:00
|
|
|
|
|
|
|
bool stop_requested_;
|
2022-01-06 23:14:10 +01:00
|
|
|
bool abort_requested_;
|
2021-07-11 09:49:38 +02:00
|
|
|
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
QMap<int, Directory> watched_dirs_;
|
|
|
|
QTimer *rescan_timer_;
|
2021-04-25 21:16:44 +02:00
|
|
|
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
|
|
|
|
2021-07-11 09:49:38 +02:00
|
|
|
SongList song_rescan_queue_; // Set by UI thread
|
2019-06-30 21:06:07 +02:00
|
|
|
|
2021-04-25 21:16:44 +02:00
|
|
|
qint64 last_scan_time_;
|
2019-07-24 19:16:51 +02:00
|
|
|
|
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
|