db/simple/Song: Export() merges tags with "target"

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1048
This commit is contained in:
Max Kellermann 2021-01-21 13:14:27 +01:00
parent 3a7c9c7c84
commit 1afa33c3c7
3 changed files with 80 additions and 7 deletions

2
NEWS
View File

@ -1,7 +1,7 @@
ver 0.22.4 (not yet released) ver 0.22.4 (not yet released)
* protocol * protocol
- fix "readpicture" on 32 bit machines - fix "readpicture" on 32 bit machines
- show duration of songs in virtual playlist (CUE) folders - show duration and tags of songs in virtual playlist (CUE) folders
* storage * storage
- curl: fix several WebDAV protocol bugs - curl: fix several WebDAV protocol bugs
* decoder * decoder

View File

@ -233,12 +233,12 @@ SimpleDatabase::GetSong(std::string_view uri) const
"No such song"); "No such song");
const Song *song = r.directory->FindSong(r.rest); const Song *song = r.directory->FindSong(r.rest);
protect.unlock();
if (song == nullptr) if (song == nullptr)
throw DatabaseError(DatabaseErrorCode::NOT_FOUND, throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
"No such song"); "No such song");
exported_song.Construct(song->Export()); exported_song.Construct(song->Export());
protect.unlock();
#ifndef NDEBUG #ifndef NDEBUG
++borrowed_song_count; ++borrowed_song_count;

View File

@ -21,9 +21,12 @@
#include "ExportedSong.hxx" #include "ExportedSong.hxx"
#include "Directory.hxx" #include "Directory.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "tag/Builder.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "song/LightSong.hxx" #include "song/LightSong.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "time/ChronoUtil.hxx"
#include "util/IterableSplitString.hxx"
Song::Song(DetachedSong &&other, Directory &_parent) noexcept Song::Song(DetachedSong &&other, Directory &_parent) noexcept
:tag(std::move(other.WritableTag())), :tag(std::move(other.WritableTag())),
@ -54,17 +57,87 @@ Song::GetURI() const noexcept
} }
} }
/**
* Path name traversal of a #Directory.
*/
gcc_pure
static const Directory *
FindTargetDirectory(const Directory &base, StringView path) noexcept
{
const auto *directory = &base;
for (const StringView name : IterableSplitString(path, '/')) {
if (name.empty() || name.Equals("."))
continue;
directory = name.Equals("..")
? directory->parent
: directory->FindChild(name);
if (directory == nullptr)
break;
}
return directory;
}
/**
* Path name traversal of a #Song.
*/
gcc_pure
static const Song *
FindTargetSong(const Directory &_directory, StringView target) noexcept
{
auto [path, last] = target.SplitLast('/');
if (last == nullptr) {
last = path;
path = nullptr;
}
if (last.empty())
return nullptr;
const auto *directory = FindTargetDirectory(_directory, path);
if (directory == nullptr)
return nullptr;
return directory->FindSong(last);
}
ExportedSong ExportedSong
Song::Export() const noexcept Song::Export() const noexcept
{ {
ExportedSong dest(filename.c_str(), tag); const auto *target_song = !target.empty()
? FindTargetSong(parent, (std::string_view)target)
: nullptr;
Tag merged_tag;
if (target_song != nullptr) {
/* if we found the target song (which may be the
underlying song file of a CUE file), merge the tags
from that song with this song's tags (from the CUE
file) */
TagBuilder builder(tag);
builder.Complement(target_song->tag);
merged_tag = builder.Commit();
}
ExportedSong dest = merged_tag.IsDefined()
? ExportedSong(filename.c_str(), std::move(merged_tag))
: ExportedSong(filename.c_str(), tag);
if (!parent.IsRoot()) if (!parent.IsRoot())
dest.directory = parent.GetPath(); dest.directory = parent.GetPath();
if (!target.empty()) if (!target.empty())
dest.real_uri = target.c_str(); dest.real_uri = target.c_str();
dest.mtime = mtime; dest.mtime = IsNegative(mtime) && target_song != nullptr
dest.start_time = start_time; ? target_song->mtime
dest.end_time = end_time; : mtime;
dest.audio_format = audio_format; dest.start_time = start_time.IsZero() && target_song != nullptr
? target_song->start_time
: start_time;
dest.end_time = end_time.IsZero() && target_song != nullptr
? target_song->end_time
: end_time;
dest.audio_format = audio_format.IsDefined() || target_song == nullptr
? audio_format
: target_song->audio_format;
return dest; return dest;
} }