diff options
Diffstat (limited to 'src/db/Helpers.cxx')
-rw-r--r-- | src/db/Helpers.cxx | 121 |
1 files changed, 108 insertions, 13 deletions
diff --git a/src/db/Helpers.cxx b/src/db/Helpers.cxx index 43b0e8159..6259d4f40 100644 --- a/src/db/Helpers.cxx +++ b/src/db/Helpers.cxx @@ -22,6 +22,7 @@ #include "Interface.hxx" #include "LightSong.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include <functional> #include <set> @@ -38,13 +39,100 @@ struct StringLess { typedef std::set<const char *, StringLess> StringSet; +struct TagLess { + gcc_pure + bool operator()(const Tag &a, const Tag &b) const { + if (a.num_items != b.num_items) + return a.num_items < b.num_items; + + const unsigned n = a.num_items; + for (unsigned i = 0; i < n; ++i) { + const TagItem &ai = *a.items[i]; + const TagItem &bi = *b.items[i]; + if (ai.type != bi.type) + return unsigned(ai.type) < unsigned(bi.type); + + const int cmp = strcmp(ai.value, bi.value); + if (cmp != 0) + return cmp < 0; + } + + return false; + } +}; + +typedef std::set<Tag, TagLess> TagSet; + +/** + * Copy all tag items of the specified type. + */ static bool -CheckUniqueTag(StringSet &set, const Tag &tag, TagType type) +CopyTagItem(TagBuilder &dest, TagType dest_type, + const Tag &src, TagType src_type) +{ + bool found = false; + const unsigned n = src.num_items; + for (unsigned i = 0; i < n; ++i) { + if (src.items[i]->type == src_type) { + dest.AddItem(dest_type, src.items[i]->value); + found = true; + } + } + + return found; +} + +/** + * Copy all tag items of the specified type. Fall back to "Artist" if + * there is no "AlbumArtist". + */ +static void +CopyTagItem(TagBuilder &dest, const Tag &src, TagType type) +{ + if (!CopyTagItem(dest, type, src, type) && + type == TAG_ALBUM_ARTIST) + CopyTagItem(dest, type, src, TAG_ARTIST); +} + +/** + * Copy all tag items of the types in the mask. + */ +static void +CopyTagMask(TagBuilder &dest, const Tag &src, uint32_t mask) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if ((mask & (1u << i)) != 0) + CopyTagItem(dest, src, TagType(i)); +} + +static void +InsertUniqueTag(TagSet &set, const Tag &src, TagType type, const char *value, + uint32_t group_mask) +{ + TagBuilder builder; + if (value == nullptr) + builder.AddEmptyItem(type); + else + builder.AddItem(type, value); + CopyTagMask(builder, src, group_mask); +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + set.emplace(builder.Commit()); +#else + set.insert(builder.Commit()); +#endif +} + +static bool +CheckUniqueTag(TagSet &set, TagType dest_type, + const Tag &tag, TagType src_type, + uint32_t group_mask) { bool found = false; for (unsigned i = 0; i < tag.num_items; ++i) { - if (tag.items[i]->type == type) { - set.insert(tag.items[i]->value); + if (tag.items[i]->type == src_type) { + InsertUniqueTag(set, tag, dest_type, + tag.items[i]->value, + group_mask); found = true; } } @@ -53,35 +141,42 @@ CheckUniqueTag(StringSet &set, const Tag &tag, TagType type) } static bool -CollectTags(StringSet &set, TagType tag_type, const LightSong &song) +CollectTags(TagSet &set, TagType tag_type, uint32_t group_mask, + const LightSong &song) { + static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES, + "Mask is too small"); + + assert((group_mask & (1u << unsigned(tag_type))) == 0); + assert(song.tag != nullptr); const Tag &tag = *song.tag; - if (!CheckUniqueTag(set, tag, tag_type) && + if (!CheckUniqueTag(set, tag_type, tag, tag_type, group_mask) && (tag_type != TAG_ALBUM_ARTIST || /* fall back to "Artist" if no "AlbumArtist" was found */ - !CheckUniqueTag(set, tag, TAG_ARTIST))) - set.insert(""); + !CheckUniqueTag(set, tag_type, tag, TAG_ARTIST, group_mask))) + InsertUniqueTag(set, tag, tag_type, nullptr, group_mask); return true; } bool VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, Error &error) { - StringSet set; + TagSet set; using namespace std::placeholders; - const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1); + const auto f = std::bind(CollectTags, std::ref(set), + tag_type, group_mask, _1); if (!db.Visit(selection, f, error)) return false; - for (auto value : set) - if (!visit_string(value, error)) + for (const auto &value : set) + if (!visit_tag(value, error)) return false; return true; |