db/simple: add option to hide CUE target songs
This reduces duplicates in the music database by hiding the original song file when it is referenced by a CUE sheet. Closes https://github.com/MusicPlayerDaemon/MPD/issues/1275
This commit is contained in:
parent
f7622ca332
commit
37bd6de658
1
NEWS
1
NEWS
|
@ -9,6 +9,7 @@ ver 0.23 (not yet released)
|
||||||
- proxy: require MPD 0.20 or later
|
- proxy: require MPD 0.20 or later
|
||||||
- proxy: require libmpdclient 2.11 or later
|
- proxy: require libmpdclient 2.11 or later
|
||||||
- proxy: split search into chunks to avoid exceeding the output buffer
|
- proxy: split search into chunks to avoid exceeding the output buffer
|
||||||
|
- simple: add option to hide CUE target songs
|
||||||
- upnp: support libnpupnp instead of libupnp
|
- upnp: support libnpupnp instead of libupnp
|
||||||
* archive
|
* archive
|
||||||
- zzip, iso9660: ignore file names which are invalid UTF-8
|
- zzip, iso9660: ignore file names which are invalid UTF-8
|
||||||
|
|
|
@ -23,6 +23,13 @@ The default plugin. Stores a copy of the database in memory. A file is used for
|
||||||
- The path of the cache directory for additional storages mounted at runtime. This setting is necessary for the **mount** protocol command.
|
- The path of the cache directory for additional storages mounted at runtime. This setting is necessary for the **mount** protocol command.
|
||||||
* - **compress yes|no**
|
* - **compress yes|no**
|
||||||
- Compress the database file using gzip? Enabled by default (if built with zlib).
|
- Compress the database file using gzip? Enabled by default (if built with zlib).
|
||||||
|
* - **hide_playlist_targets yes|no**
|
||||||
|
- Hide songs which are referenced by playlists? Thas is,
|
||||||
|
playlist files which are represented in the database as virtual
|
||||||
|
directories (playlist plugin setting ``as_directory``). This
|
||||||
|
option is enabled by default and avoids duplicate songs; one
|
||||||
|
copy for the original file, and another copy in the virtual
|
||||||
|
directory of a CUE file referring to it.
|
||||||
|
|
||||||
proxy
|
proxy
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -109,21 +109,21 @@ Directory::FindChild(std::string_view name) const noexcept
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
Song *
|
||||||
Directory::TargetExists(std::string_view _target) const noexcept
|
Directory::LookupTargetSong(std::string_view _target) noexcept
|
||||||
{
|
{
|
||||||
StringView target{_target};
|
StringView target{_target};
|
||||||
|
|
||||||
if (target.SkipPrefix("../")) {
|
if (target.SkipPrefix("../")) {
|
||||||
if (parent == nullptr)
|
if (parent == nullptr)
|
||||||
return false;
|
return nullptr;
|
||||||
|
|
||||||
return parent->TargetExists(target);
|
return parent->LookupTargetSong(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sorry for the const_cast ... */
|
/* sorry for the const_cast ... */
|
||||||
const auto lr = const_cast<Directory *>(this)->LookupDirectory(target);
|
const auto lr = LookupDirectory(target);
|
||||||
return lr.directory->FindSong(lr.rest) != nullptr;
|
return lr.directory->FindSong(lr.rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -229,6 +229,7 @@ Directory::Sort() noexcept
|
||||||
|
|
||||||
void
|
void
|
||||||
Directory::Walk(bool recursive, const SongFilter *filter,
|
Directory::Walk(bool recursive, const SongFilter *filter,
|
||||||
|
bool hide_playlist_targets,
|
||||||
const VisitDirectory& visit_directory, const VisitSong& visit_song,
|
const VisitDirectory& visit_directory, const VisitSong& visit_song,
|
||||||
const VisitPlaylist& visit_playlist) const
|
const VisitPlaylist& visit_playlist) const
|
||||||
{
|
{
|
||||||
|
@ -247,7 +248,10 @@ Directory::Walk(bool recursive, const SongFilter *filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visit_song) {
|
if (visit_song) {
|
||||||
for (auto &song : songs){
|
for (auto &song : songs) {
|
||||||
|
if (hide_playlist_targets && song.in_playlist)
|
||||||
|
continue;
|
||||||
|
|
||||||
const auto song2 = song.Export();
|
const auto song2 = song.Export();
|
||||||
if (filter == nullptr || filter->Match(song2))
|
if (filter == nullptr || filter->Match(song2))
|
||||||
visit_song(song2);
|
visit_song(song2);
|
||||||
|
@ -265,6 +269,7 @@ Directory::Walk(bool recursive, const SongFilter *filter,
|
||||||
|
|
||||||
if (recursive)
|
if (recursive)
|
||||||
child.Walk(recursive, filter,
|
child.Walk(recursive, filter,
|
||||||
|
hide_playlist_targets,
|
||||||
visit_directory, visit_song,
|
visit_directory, visit_song,
|
||||||
visit_playlist);
|
visit_playlist);
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,7 +214,7 @@ public:
|
||||||
LookupResult LookupDirectory(std::string_view uri) noexcept;
|
LookupResult LookupDirectory(std::string_view uri) noexcept;
|
||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
bool TargetExists(std::string_view target) const noexcept;
|
Song *LookupTargetSong(std::string_view target) noexcept;
|
||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
bool IsEmpty() const noexcept {
|
bool IsEmpty() const noexcept {
|
||||||
|
@ -303,6 +303,7 @@ public:
|
||||||
* Caller must lock #db_mutex.
|
* Caller must lock #db_mutex.
|
||||||
*/
|
*/
|
||||||
void Walk(bool recursive, const SongFilter *match,
|
void Walk(bool recursive, const SongFilter *match,
|
||||||
|
bool hide_playlist_targets,
|
||||||
const VisitDirectory& visit_directory, const VisitSong& visit_song,
|
const VisitDirectory& visit_directory, const VisitSong& visit_song,
|
||||||
const VisitPlaylist& visit_playlist) const;
|
const VisitPlaylist& visit_playlist) const;
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ inline SimpleDatabase::SimpleDatabase(const ConfigBlock &block)
|
||||||
#ifdef ENABLE_ZLIB
|
#ifdef ENABLE_ZLIB
|
||||||
compress(block.GetBlockValue("compress", true)),
|
compress(block.GetBlockValue("compress", true)),
|
||||||
#endif
|
#endif
|
||||||
|
hide_playlist_targets(block.GetBlockValue("hide_playlist_targets", true)),
|
||||||
cache_path(block.GetPath("cache_directory"))
|
cache_path(block.GetPath("cache_directory"))
|
||||||
{
|
{
|
||||||
if (path.IsNull())
|
if (path.IsNull())
|
||||||
|
@ -306,6 +307,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
||||||
visit_directory(r.directory->Export());
|
visit_directory(r.directory->Export());
|
||||||
|
|
||||||
r.directory->Walk(selection.recursive, selection.filter,
|
r.directory->Walk(selection.recursive, selection.filter,
|
||||||
|
hide_playlist_targets,
|
||||||
visit_directory, visit_song,
|
visit_directory, visit_song,
|
||||||
visit_playlist);
|
visit_playlist);
|
||||||
helper.Commit();
|
helper.Commit();
|
||||||
|
|
|
@ -44,6 +44,8 @@ class SimpleDatabase : public Database {
|
||||||
bool compress;
|
bool compress;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool hide_playlist_targets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path where cache files for Mount() are located.
|
* The path where cache files for Mount() are located.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -101,6 +101,12 @@ struct Song {
|
||||||
*/
|
*/
|
||||||
AudioFormat audio_format = AudioFormat::Undefined();
|
AudioFormat audio_format = AudioFormat::Undefined();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this song referenced by at least one playlist file that
|
||||||
|
* is part of the database?
|
||||||
|
*/
|
||||||
|
bool in_playlist = false;
|
||||||
|
|
||||||
template<typename F>
|
template<typename F>
|
||||||
Song(F &&_filename, Directory &_parent) noexcept
|
Song(F &&_filename, Directory &_parent) noexcept
|
||||||
:parent(_parent), filename(std::forward<F>(_filename)) {}
|
:parent(_parent), filename(std::forward<F>(_filename)) {}
|
||||||
|
|
|
@ -141,10 +141,18 @@ UpdateWalk::PurgeDanglingFromPlaylists(Directory &directory) noexcept
|
||||||
|
|
||||||
directory.ForEachSongSafe([&](Song &song){
|
directory.ForEachSongSafe([&](Song &song){
|
||||||
if (!song.target.empty() &&
|
if (!song.target.empty() &&
|
||||||
!PathTraitsUTF8::IsAbsoluteOrHasScheme(song.target.c_str()) &&
|
!PathTraitsUTF8::IsAbsoluteOrHasScheme(song.target.c_str())) {
|
||||||
!directory.TargetExists(song.target)) {
|
Song *target = directory.LookupTargetSong(song.target.c_str());
|
||||||
|
if (target == nullptr) {
|
||||||
|
/* the target does not exist: remove
|
||||||
|
the virtual song */
|
||||||
editor.DeleteSong(directory, &song);
|
editor.DeleteSong(directory, &song);
|
||||||
modified = true;
|
modified = true;
|
||||||
|
} else {
|
||||||
|
/* the target exists: mark it (for
|
||||||
|
option "hide_playlist_targets") */
|
||||||
|
target->in_playlist = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,9 @@ private:
|
||||||
/**
|
/**
|
||||||
* Remove all virtual songs inside playlists whose "target"
|
* Remove all virtual songs inside playlists whose "target"
|
||||||
* field points to a non-existing song file.
|
* field points to a non-existing song file.
|
||||||
|
*
|
||||||
|
* It also looks up all target songs and sets their
|
||||||
|
* "in_playlist" field.
|
||||||
*/
|
*/
|
||||||
void PurgeDanglingFromPlaylists(Directory &directory) noexcept;
|
void PurgeDanglingFromPlaylists(Directory &directory) noexcept;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue