media library: optimize update
This commit is contained in:
@@ -1453,9 +1453,9 @@ void EditLibraryAlbum::Run()
|
|||||||
|
|
||||||
Statusbar::lock();
|
Statusbar::lock();
|
||||||
Statusbar::put() << NC::fmtBold << "Album: " << NC::fmtBoldEnd;
|
Statusbar::put() << NC::fmtBold << "Album: " << NC::fmtBoldEnd;
|
||||||
std::string new_album = wFooter->getString(myLibrary->Albums.current().value().Album);
|
std::string new_album = wFooter->getString(myLibrary->Albums.current().value().entry().album());
|
||||||
Statusbar::unlock();
|
Statusbar::unlock();
|
||||||
if (!new_album.empty() && new_album != myLibrary->Albums.current().value().Album)
|
if (!new_album.empty() && new_album != myLibrary->Albums.current().value().entry().album())
|
||||||
{
|
{
|
||||||
bool success = 1;
|
bool success = 1;
|
||||||
Statusbar::msg("Updating tags...");
|
Statusbar::msg("Updating tags...");
|
||||||
@@ -2193,7 +2193,7 @@ bool ToggleMediaLibrarySortMode::canBeRun() const
|
|||||||
|
|
||||||
void ToggleMediaLibrarySortMode::Run()
|
void ToggleMediaLibrarySortMode::Run()
|
||||||
{
|
{
|
||||||
myLibrary->toggleMTimeSort();
|
myLibrary->toggleSortMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RefetchLyrics::canBeRun() const
|
bool RefetchLyrics::canBeRun() const
|
||||||
|
|||||||
@@ -55,31 +55,28 @@ size_t itsMiddleColStartX;
|
|||||||
size_t itsRightColWidth;
|
size_t itsRightColWidth;
|
||||||
size_t itsRightColStartX;
|
size_t itsRightColStartX;
|
||||||
|
|
||||||
// this string marks the position in middle column that works as "All tracks" option. it's
|
typedef MediaLibrary::AlbumEntry AlbumEntry;
|
||||||
// assigned to Date in SearchConstraint class since date normally cannot contain other chars
|
|
||||||
// than ciphers and -'s (0x7f is interpreted as backspace keycode, so it's quite safe to assume
|
|
||||||
// that it won't appear in any tag, let alone date).
|
|
||||||
const std::string AllTracksMarker = "\x7f_\x7f_\x7f";
|
|
||||||
|
|
||||||
typedef MediaLibrary::SearchConstraints SearchConstraints;
|
std::string AlbumToString(const AlbumEntry &ae);
|
||||||
|
|
||||||
std::string AlbumToString(const SearchConstraints &sc);
|
|
||||||
std::string SongToString(const MPD::Song &s);
|
std::string SongToString(const MPD::Song &s);
|
||||||
|
|
||||||
bool TagEntryMatcher(const Regex &rx, const MPD::TagMTime &tagmtime);
|
bool TagEntryMatcher(const Regex &rx, const MediaLibrary::PrimaryTag &tagmtime);
|
||||||
bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<SearchConstraints>::Item &item, bool filter);
|
bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<AlbumEntry>::Item &item, bool filter);
|
||||||
bool SongEntryMatcher(const Regex &rx, const MPD::Song &s);
|
bool SongEntryMatcher(const Regex &rx, const MPD::Song &s);
|
||||||
|
|
||||||
void DisplayAlbums(NC::Menu<SearchConstraints> &menu);
|
void DisplayAlbums(NC::Menu<AlbumEntry> &menu);
|
||||||
void DisplayPrimaryTags(NC::Menu<MPD::TagMTime> &menu);
|
void DisplayPrimaryTags(NC::Menu<MediaLibrary::PrimaryTag> &menu);
|
||||||
|
|
||||||
bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b);
|
bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b);
|
||||||
|
|
||||||
struct SortAllTracks {
|
struct SortAllTracks {
|
||||||
static const std::array<MPD::Song::GetFunction, 3> GetFuns;
|
static const std::array<MPD::Song::GetFunction, 3> GetFuns;
|
||||||
|
|
||||||
LocaleStringComparison m_cmp;
|
LocaleStringComparison m_cmp;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SortAllTracks() : m_cmp(std::locale(), Config.ignore_leading_the) { }
|
SortAllTracks() : m_cmp(std::locale(), Config.ignore_leading_the) { }
|
||||||
|
|
||||||
bool operator()(const MPD::Song &a, const MPD::Song &b) {
|
bool operator()(const MPD::Song &a, const MPD::Song &b) {
|
||||||
for (auto get = GetFuns.begin(); get != GetFuns.end(); ++get) {
|
for (auto get = GetFuns.begin(); get != GetFuns.end(); ++get) {
|
||||||
int ret = m_cmp(a.getTags(*get, Config.tags_separator),
|
int ret = m_cmp(a.getTags(*get, Config.tags_separator),
|
||||||
@@ -90,41 +87,51 @@ public:
|
|||||||
return a.getTrack() < b.getTrack();
|
return a.getTrack() < b.getTrack();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::array<MPD::Song::GetFunction, 3> SortAllTracks::GetFuns = {{
|
const std::array<MPD::Song::GetFunction, 3> SortAllTracks::GetFuns = {{
|
||||||
&MPD::Song::getDate,
|
&MPD::Song::getDate,
|
||||||
&MPD::Song::getAlbum,
|
&MPD::Song::getAlbum,
|
||||||
&MPD::Song::getDisc
|
&MPD::Song::getDisc
|
||||||
}};
|
}};
|
||||||
|
|
||||||
class SortSearchConstraints {
|
class SortAlbumEntries {
|
||||||
|
typedef MediaLibrary::Album Album;
|
||||||
|
|
||||||
LocaleStringComparison m_cmp;
|
LocaleStringComparison m_cmp;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SortSearchConstraints() : m_cmp(std::locale(), Config.ignore_leading_the) { }
|
SortAlbumEntries() : m_cmp(std::locale(), Config.ignore_leading_the) { }
|
||||||
bool operator()(const SearchConstraints &a, const SearchConstraints &b) const {
|
|
||||||
|
bool operator()(const AlbumEntry &a, const AlbumEntry &b) const {
|
||||||
|
return (*this)(a.entry(), b.entry());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const Album &a, const Album &b) const {
|
||||||
if (Config.media_library_sort_by_mtime)
|
if (Config.media_library_sort_by_mtime)
|
||||||
{
|
return a.mtime() > b.mtime();
|
||||||
return a.MTime > b.MTime;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
result = m_cmp(a.PrimaryTag, b.PrimaryTag);
|
result = m_cmp(a.tag(), b.tag());
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
return result < 0;
|
return result < 0;
|
||||||
result = m_cmp(a.Date, b.Date);
|
result = m_cmp(a.date(), b.date());
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
return result < 0;
|
return result < 0;
|
||||||
return m_cmp(a.Album, b.Album) < 0;
|
return m_cmp(a.album(), b.album()) < 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ArtistSorting {
|
class SortPrimaryTags {
|
||||||
|
typedef MediaLibrary::PrimaryTag PrimaryTag;
|
||||||
|
|
||||||
LocaleStringComparison m_cmp;
|
LocaleStringComparison m_cmp;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ArtistSorting() : m_cmp(std::locale(), Config.ignore_leading_the) { }
|
SortPrimaryTags() : m_cmp(std::locale(), Config.ignore_leading_the) { }
|
||||||
bool operator()(const MPD::TagMTime &a,
|
|
||||||
const MPD::TagMTime &b) const {
|
bool operator()(const PrimaryTag &a, const PrimaryTag &b) const {
|
||||||
if (Config.media_library_sort_by_mtime)
|
if (Config.media_library_sort_by_mtime)
|
||||||
return a.mtime() > b.mtime();
|
return a.mtime() > b.mtime();
|
||||||
else
|
else
|
||||||
@@ -143,7 +150,7 @@ MediaLibrary::MediaLibrary()
|
|||||||
itsRightColWidth = COLS-COLS/3*2-1;
|
itsRightColWidth = COLS-COLS/3*2-1;
|
||||||
itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
|
itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
|
||||||
|
|
||||||
Tags = NC::Menu<MPD::TagMTime>(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, NC::brNone);
|
Tags = NC::Menu<PrimaryTag>(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, NC::brNone);
|
||||||
Tags.setHighlightColor(Config.active_column_color);
|
Tags.setHighlightColor(Config.active_column_color);
|
||||||
Tags.cyclicScrolling(Config.use_cyclic_scrolling);
|
Tags.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||||
Tags.centeredCursor(Config.centered_cursor);
|
Tags.centeredCursor(Config.centered_cursor);
|
||||||
@@ -151,7 +158,7 @@ MediaLibrary::MediaLibrary()
|
|||||||
Tags.setSelectedSuffix(Config.selected_item_suffix);
|
Tags.setSelectedSuffix(Config.selected_item_suffix);
|
||||||
Tags.setItemDisplayer(DisplayPrimaryTags);
|
Tags.setItemDisplayer(DisplayPrimaryTags);
|
||||||
|
|
||||||
Albums = NC::Menu<SearchConstraints>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, Config.titles_visibility ? "Albums" : "", Config.main_color, NC::brNone);
|
Albums = NC::Menu<AlbumEntry>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, Config.titles_visibility ? "Albums" : "", Config.main_color, NC::brNone);
|
||||||
Albums.setHighlightColor(Config.main_highlight_color);
|
Albums.setHighlightColor(Config.main_highlight_color);
|
||||||
Albums.cyclicScrolling(Config.use_cyclic_scrolling);
|
Albums.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||||
Albums.centeredCursor(Config.centered_cursor);
|
Albums.centeredCursor(Config.centered_cursor);
|
||||||
@@ -229,194 +236,119 @@ std::wstring MediaLibrary::title()
|
|||||||
return L"Media library";
|
return L"Media library";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaLibrary::hasMTimes()
|
|
||||||
{
|
|
||||||
bool has = false;
|
|
||||||
if (hasTwoColumns && !Albums.empty())
|
|
||||||
has = Albums.current().value().hasMTime();
|
|
||||||
else if (!hasTwoColumns && !Tags.empty())
|
|
||||||
has = Tags.current().value().hasMTime();
|
|
||||||
return has;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaLibrary::toggleMTimeSort()
|
|
||||||
{
|
|
||||||
Config.media_library_sort_by_mtime = !Config.media_library_sort_by_mtime;
|
|
||||||
if (Config.media_library_sort_by_mtime)
|
|
||||||
Statusbar::msg("Sorting library by: Modification time");
|
|
||||||
else
|
|
||||||
Statusbar::msg("Sorting library by: Name");
|
|
||||||
|
|
||||||
if (!hasMTimes() && Config.media_library_sort_by_mtime)
|
|
||||||
{
|
|
||||||
Tags.clear();
|
|
||||||
Albums.clear();
|
|
||||||
Songs.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!hasTwoColumns)
|
|
||||||
{
|
|
||||||
std::sort(Tags.beginV(), Tags.endV(), ArtistSorting());
|
|
||||||
Tags.refresh();
|
|
||||||
Albums.clear();
|
|
||||||
Songs.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints());
|
|
||||||
Albums.refresh();
|
|
||||||
Songs.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasTwoColumns)
|
|
||||||
{
|
|
||||||
if (Config.titles_visibility)
|
|
||||||
{
|
|
||||||
std::string item_type = lowercase(tagTypeToString(Config.media_lib_primary_tag));
|
|
||||||
std::string and_mtime = Config.media_library_sort_by_mtime ?
|
|
||||||
" and mtime" :
|
|
||||||
"";
|
|
||||||
Albums.setTitle("Albums (sorted by " + item_type + and_mtime + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaLibrary::update()
|
void MediaLibrary::update()
|
||||||
{
|
{
|
||||||
if (!hasTwoColumns && Tags.reallyEmpty())
|
Mpd.BlockIdle(true);
|
||||||
{
|
|
||||||
Albums.clear();
|
|
||||||
Songs.clear();
|
|
||||||
auto list = Mpd.GetListMTime(Config.media_lib_primary_tag,
|
|
||||||
Config.media_library_sort_by_mtime);
|
|
||||||
|
|
||||||
std::sort(list.begin(), list.end(), ArtistSorting());
|
|
||||||
for (auto it = list.begin(); it != list.end(); ++it)
|
|
||||||
{
|
|
||||||
if (it->tag().empty() && !Config.media_library_display_empty_tag)
|
|
||||||
continue;
|
|
||||||
Tags.addItem(*it);
|
|
||||||
}
|
|
||||||
Tags.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasTwoColumns && !Tags.empty() && Albums.reallyEmpty() && Songs.reallyEmpty())
|
if (hasTwoColumns)
|
||||||
{
|
{
|
||||||
// idle has to be blocked for now since it would be enabled and
|
if (Albums.reallyEmpty())
|
||||||
// disabled a few times by each mpd command, which makes no sense
|
|
||||||
// and slows down the whole process.
|
|
||||||
Mpd.BlockIdle(true);
|
|
||||||
Albums.reset();
|
|
||||||
Mpd.StartFieldSearchMTime(MPD_TAG_ALBUM, Config.media_library_sort_by_mtime);
|
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, Tags.current().value().tag());
|
|
||||||
auto albums = Mpd.CommitSearchTagsMTime();
|
|
||||||
for (auto tagmtime = albums.begin(); tagmtime != albums.end(); ++tagmtime)
|
|
||||||
{
|
{
|
||||||
const std::string &album = tagmtime->tag();
|
Songs.clear();
|
||||||
time_t mtime = tagmtime->mtime();
|
std::map<std::tuple<std::string, std::string, std::string>, time_t> albums;
|
||||||
if (Config.media_library_display_date)
|
Mpd.GetDirectoryRecursive("/", [&albums](MPD::Song &&s) {
|
||||||
{
|
unsigned idx = 0;
|
||||||
Mpd.StartFieldSearch(MPD_TAG_DATE);
|
std::string tag = s.get(Config.media_lib_primary_tag, idx);
|
||||||
|
do
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag,
|
|
||||||
Tags.current().value().tag());
|
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, album);
|
|
||||||
Mpd.CommitSearchTags([this, &album, &mtime](std::string &&date) {
|
|
||||||
Albums.addItem(SearchConstraints(album, date, mtime));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Albums.addItem(SearchConstraints(album, "", mtime));
|
|
||||||
}
|
|
||||||
if (!Albums.empty())
|
|
||||||
std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints());
|
|
||||||
if (Albums.size() > 1)
|
|
||||||
{
|
|
||||||
Albums.addSeparator();
|
|
||||||
Albums.addItem(SearchConstraints("", AllTracksMarker));
|
|
||||||
}
|
|
||||||
Albums.refresh();
|
|
||||||
Mpd.BlockIdle(false);
|
|
||||||
}
|
|
||||||
else if (hasTwoColumns && Albums.reallyEmpty())
|
|
||||||
{
|
|
||||||
Songs.clear();
|
|
||||||
Albums << NC::XY(0, 0) << "Fetching albums...";
|
|
||||||
Albums.Window::refresh();
|
|
||||||
Mpd.BlockIdle(true);
|
|
||||||
MPD::StringList artists;
|
|
||||||
Mpd.GetList(Config.media_lib_primary_tag, [&artists](std::string &&artist) {
|
|
||||||
artists.push_back(artist);
|
|
||||||
});
|
|
||||||
for (auto artist = artists.begin(); artist != artists.end(); ++artist)
|
|
||||||
{
|
|
||||||
Mpd.StartFieldSearchMTime(MPD_TAG_ALBUM, Config.media_library_sort_by_mtime);
|
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, *artist);
|
|
||||||
auto albums = Mpd.CommitSearchTagsMTime();
|
|
||||||
for (auto am = albums.begin(); am != albums.end(); ++am)
|
|
||||||
{
|
|
||||||
const std::string &album = am->tag();
|
|
||||||
time_t mtime = am->mtime();
|
|
||||||
if (Config.media_library_display_date)
|
|
||||||
{
|
{
|
||||||
if (Config.media_lib_primary_tag != MPD_TAG_DATE)
|
auto key = std::make_tuple(tag, s.getAlbum(), s.getDate());
|
||||||
{
|
auto it = albums.find(key);
|
||||||
Mpd.StartFieldSearch(MPD_TAG_DATE);
|
if (it == albums.end())
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, *artist);
|
albums[key] = s.getMTime();
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, album);
|
|
||||||
Mpd.CommitSearchTags([this, &artist, &album, &mtime](std::string &&date) {
|
|
||||||
Albums.addItem(SearchConstraints(*artist, album, date, mtime));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
Albums.addItem(SearchConstraints(*artist, album, *artist, mtime));
|
it->second = s.getMTime();
|
||||||
}
|
}
|
||||||
else
|
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
|
||||||
Albums.addItem(SearchConstraints(*artist, album, "", mtime));
|
});
|
||||||
}
|
for (auto it = albums.begin(); it != albums.end(); ++it)
|
||||||
|
Albums.addItem(AlbumEntry(Album(
|
||||||
|
std::move(std::get<0>(it->first)),
|
||||||
|
std::move(std::get<1>(it->first)),
|
||||||
|
std::move(std::get<2>(it->first)),
|
||||||
|
it->second)));
|
||||||
|
std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries());
|
||||||
|
Albums.refresh();
|
||||||
}
|
}
|
||||||
Mpd.BlockIdle(0);
|
|
||||||
|
|
||||||
if (!Albums.empty())
|
|
||||||
std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints());
|
|
||||||
Albums.refresh();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!hasTwoColumns && !Tags.empty() && isActiveWindow(Albums) && Albums.reallyEmpty())
|
|
||||||
{
|
{
|
||||||
Albums.setHighlightColor(Config.main_highlight_color);
|
if (Tags.reallyEmpty())
|
||||||
Tags.setHighlightColor(Config.active_column_color);
|
{
|
||||||
w = &Tags;
|
Albums.clear();
|
||||||
|
Songs.clear();
|
||||||
|
std::map<std::string, time_t> tags;
|
||||||
|
Mpd.GetDirectoryRecursive("/", [&tags](MPD::Song &&s) {
|
||||||
|
unsigned idx = 0;
|
||||||
|
std::string tag = s.get(Config.media_lib_primary_tag, idx);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto it = tags.find(tag);
|
||||||
|
if (it == tags.end())
|
||||||
|
tags[tag] = s.getMTime();
|
||||||
|
else
|
||||||
|
it->second = std::max(it->second, s.getMTime());
|
||||||
|
}
|
||||||
|
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
|
||||||
|
});
|
||||||
|
for (auto it = tags.begin(); it != tags.end(); ++it)
|
||||||
|
Tags.addItem(PrimaryTag(std::move(it->first), it->second));
|
||||||
|
Tags.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Tags.empty() && Albums.reallyEmpty())
|
||||||
|
{
|
||||||
|
auto &primary_tag = Tags.current().value().tag();
|
||||||
|
Mpd.StartSearch(true);
|
||||||
|
Mpd.AddSearch(Config.media_lib_primary_tag, primary_tag);
|
||||||
|
std::map<std::tuple<std::string, std::string>, time_t> albums;
|
||||||
|
Mpd.CommitSearchSongs([&albums](MPD::Song &&s) {
|
||||||
|
auto key = std::make_tuple(s.getAlbum(), s.getDate());
|
||||||
|
auto it = albums.find(key);
|
||||||
|
if (it == albums.end())
|
||||||
|
albums[key] = s.getMTime();
|
||||||
|
else
|
||||||
|
it->second = s.getMTime();
|
||||||
|
});
|
||||||
|
for (auto it = albums.begin(); it != albums.end(); ++it)
|
||||||
|
Albums.addItem(AlbumEntry(Album(
|
||||||
|
primary_tag,
|
||||||
|
std::move(std::get<0>(it->first)),
|
||||||
|
std::move(std::get<1>(it->first)),
|
||||||
|
it->second)));
|
||||||
|
std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries());
|
||||||
|
if (albums.size() > 1)
|
||||||
|
{
|
||||||
|
Albums.addSeparator();
|
||||||
|
Albums.addItem(AlbumEntry::mkAllTracksEntry(primary_tag));
|
||||||
|
}
|
||||||
|
Albums.refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(hasTwoColumns ? Albums.empty() : Tags.empty()) && Songs.reallyEmpty())
|
if (!Albums.empty() && Songs.reallyEmpty())
|
||||||
{
|
{
|
||||||
Songs.reset();
|
Songs.reset();
|
||||||
|
auto &album = Albums.current().value();
|
||||||
Mpd.StartSearch(1);
|
Mpd.StartSearch(true);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag,
|
Mpd.AddSearch(Config.media_lib_primary_tag, album.entry().tag());
|
||||||
hasTwoColumns ? Albums.current().value().PrimaryTag :
|
if (!album.isAllTracksEntry())
|
||||||
Tags.current().value().tag());
|
|
||||||
if (Albums.current().value().Date != AllTracksMarker)
|
|
||||||
{
|
{
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, Albums.current().value().Album);
|
Mpd.AddSearch(MPD_TAG_ALBUM, album.entry().album());
|
||||||
if (Config.media_library_display_date)
|
Mpd.AddSearch(MPD_TAG_DATE, album.entry().date());
|
||||||
Mpd.AddSearch(MPD_TAG_DATE, Albums.current().value().Date);
|
|
||||||
}
|
}
|
||||||
Mpd.CommitSearchSongs([this](MPD::Song &&s) {
|
Mpd.CommitSearchSongs([this](MPD::Song &&s) {
|
||||||
Songs.addItem(s, myPlaylist->checkForSong(s));
|
Songs.addItem(s, myPlaylist->checkForSong(s));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Albums.current().value().Date == AllTracksMarker)
|
if (album.isAllTracksEntry())
|
||||||
std::sort(Songs.beginV(), Songs.endV(), SortAllTracks());
|
std::sort(Songs.beginV(), Songs.endV(), SortAllTracks());
|
||||||
else
|
else
|
||||||
std::sort(Songs.beginV(), Songs.endV(), SortSongsByTrack);
|
std::sort(Songs.beginV(), Songs.endV(), SortSongsByTrack);
|
||||||
|
|
||||||
Songs.refresh();
|
Songs.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mpd.BlockIdle(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaLibrary::enterPressed()
|
void MediaLibrary::enterPressed()
|
||||||
@@ -437,7 +369,7 @@ void MediaLibrary::spacePressed()
|
|||||||
}
|
}
|
||||||
else if (isActiveWindow(Albums))
|
else if (isActiveWindow(Albums))
|
||||||
{
|
{
|
||||||
if (Albums.current().value().Date != AllTracksMarker)
|
if (Albums.current().value().isAllTracksEntry())
|
||||||
{
|
{
|
||||||
size_t i = Albums.choice();
|
size_t i = Albums.choice();
|
||||||
Albums.at(i).setSelected(!Albums.at(i).isSelected());
|
Albums.at(i).setSelected(!Albums.at(i).isSelected());
|
||||||
@@ -559,9 +491,9 @@ std::string MediaLibrary::currentFilter()
|
|||||||
{
|
{
|
||||||
std::string filter;
|
std::string filter;
|
||||||
if (isActiveWindow(Tags))
|
if (isActiveWindow(Tags))
|
||||||
filter = RegexFilter<MPD::TagMTime>::currentFilter(Tags);
|
filter = RegexFilter<PrimaryTag>::currentFilter(Tags);
|
||||||
else if (isActiveWindow(Albums))
|
else if (isActiveWindow(Albums))
|
||||||
filter = RegexItemFilter<SearchConstraints>::currentFilter(Albums);
|
filter = RegexItemFilter<AlbumEntry>::currentFilter(Albums);
|
||||||
else if (isActiveWindow(Songs))
|
else if (isActiveWindow(Songs))
|
||||||
filter = RegexFilter<MPD::Song>::currentFilter(Songs);
|
filter = RegexFilter<MPD::Song>::currentFilter(Songs);
|
||||||
return filter;
|
return filter;
|
||||||
@@ -572,14 +504,14 @@ void MediaLibrary::applyFilter(const std::string &filter)
|
|||||||
if (isActiveWindow(Tags))
|
if (isActiveWindow(Tags))
|
||||||
{
|
{
|
||||||
Tags.showAll();
|
Tags.showAll();
|
||||||
auto rx = RegexFilter<MPD::TagMTime>(filter, Config.regex_type, TagEntryMatcher);
|
auto rx = RegexFilter<PrimaryTag>(filter, Config.regex_type, TagEntryMatcher);
|
||||||
Tags.filter(Tags.begin(), Tags.end(), rx);
|
Tags.filter(Tags.begin(), Tags.end(), rx);
|
||||||
}
|
}
|
||||||
else if (isActiveWindow(Albums))
|
else if (isActiveWindow(Albums))
|
||||||
{
|
{
|
||||||
Albums.showAll();
|
Albums.showAll();
|
||||||
auto fun = std::bind(AlbumEntryMatcher, _1, _2, true);
|
auto fun = std::bind(AlbumEntryMatcher, _1, _2, true);
|
||||||
auto rx = RegexItemFilter<SearchConstraints>(filter, Config.regex_type, fun);
|
auto rx = RegexItemFilter<AlbumEntry>(filter, Config.regex_type, fun);
|
||||||
Albums.filter(Albums.begin(), Albums.end(), rx);
|
Albums.filter(Albums.begin(), Albums.end(), rx);
|
||||||
}
|
}
|
||||||
else if (isActiveWindow(Songs))
|
else if (isActiveWindow(Songs))
|
||||||
@@ -602,13 +534,13 @@ bool MediaLibrary::search(const std::string &constraint)
|
|||||||
bool result = false;
|
bool result = false;
|
||||||
if (isActiveWindow(Tags))
|
if (isActiveWindow(Tags))
|
||||||
{
|
{
|
||||||
auto rx = RegexFilter<MPD::TagMTime>(constraint, Config.regex_type, TagEntryMatcher);
|
auto rx = RegexFilter<PrimaryTag>(constraint, Config.regex_type, TagEntryMatcher);
|
||||||
result = Tags.search(Tags.begin(), Tags.end(), rx);
|
result = Tags.search(Tags.begin(), Tags.end(), rx);
|
||||||
}
|
}
|
||||||
else if (isActiveWindow(Albums))
|
else if (isActiveWindow(Albums))
|
||||||
{
|
{
|
||||||
auto fun = std::bind(AlbumEntryMatcher, _1, _2, false);
|
auto fun = std::bind(AlbumEntryMatcher, _1, _2, false);
|
||||||
auto rx = RegexItemFilter<SearchConstraints>(constraint, Config.regex_type, fun);
|
auto rx = RegexItemFilter<AlbumEntry>(constraint, Config.regex_type, fun);
|
||||||
result = Albums.search(Albums.begin(), Albums.end(), rx);
|
result = Albums.search(Albums.begin(), Albums.end(), rx);
|
||||||
}
|
}
|
||||||
else if (isActiveWindow(Songs))
|
else if (isActiveWindow(Songs))
|
||||||
@@ -698,12 +630,12 @@ MPD::SongList MediaLibrary::getSelectedSongs()
|
|||||||
auto &sc = it->value();
|
auto &sc = it->value();
|
||||||
Mpd.StartSearch(true);
|
Mpd.StartSearch(true);
|
||||||
if (hasTwoColumns)
|
if (hasTwoColumns)
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, sc.PrimaryTag);
|
Mpd.AddSearch(Config.media_lib_primary_tag, sc.entry().tag());
|
||||||
else
|
else
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag,
|
Mpd.AddSearch(Config.media_lib_primary_tag,
|
||||||
Tags.current().value().tag());
|
Tags.current().value().tag());
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, sc.Album);
|
Mpd.AddSearch(MPD_TAG_ALBUM, sc.entry().album());
|
||||||
Mpd.AddSearch(MPD_TAG_DATE, sc.Date);
|
Mpd.AddSearch(MPD_TAG_DATE, sc.entry().date());
|
||||||
size_t begin = result.size();
|
size_t begin = result.size();
|
||||||
Mpd.CommitSearchSongs([&result](MPD::Song &&s) {
|
Mpd.CommitSearchSongs([&result](MPD::Song &&s) {
|
||||||
result.push_back(s);
|
result.push_back(s);
|
||||||
@@ -817,13 +749,9 @@ void MediaLibrary::toggleColumnsMode()
|
|||||||
if (Config.titles_visibility)
|
if (Config.titles_visibility)
|
||||||
{
|
{
|
||||||
std::string item_type = lowercase(tagTypeToString(Config.media_lib_primary_tag));
|
std::string item_type = lowercase(tagTypeToString(Config.media_lib_primary_tag));
|
||||||
std::string and_mtime = Config.media_library_sort_by_mtime ?
|
std::string and_mtime = Config.media_library_sort_by_mtime ? " and mtime" : "";
|
||||||
" and mtime" :
|
|
||||||
"";
|
|
||||||
Albums.setTitle("Albums (sorted by " + item_type + and_mtime + ")");
|
Albums.setTitle("Albums (sorted by " + item_type + and_mtime + ")");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
Albums.setTitle("");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Albums.setTitle(Config.titles_visibility ? "Albums" : "");
|
Albums.setTitle(Config.titles_visibility ? "Albums" : "");
|
||||||
@@ -845,6 +773,33 @@ ProxySongList MediaLibrary::songsProxyList()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaLibrary::toggleSortMode()
|
||||||
|
{
|
||||||
|
Config.media_library_sort_by_mtime = !Config.media_library_sort_by_mtime;
|
||||||
|
Statusbar::msg("Sorting library by: %s",
|
||||||
|
Config.media_library_sort_by_mtime ? "Modification time" : "Name");
|
||||||
|
if (hasTwoColumns)
|
||||||
|
{
|
||||||
|
std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries());
|
||||||
|
Albums.refresh();
|
||||||
|
Songs.clear();
|
||||||
|
if (Config.titles_visibility)
|
||||||
|
{
|
||||||
|
std::string item_type = lowercase(tagTypeToString(Config.media_lib_primary_tag));
|
||||||
|
std::string and_mtime = Config.media_library_sort_by_mtime ? " and mtime" : "";
|
||||||
|
Albums.setTitle("Albums (sorted by " + item_type + and_mtime + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::sort(Tags.beginV(), Tags.endV(), SortPrimaryTags());
|
||||||
|
Tags.refresh();
|
||||||
|
Albums.clear();
|
||||||
|
Songs.clear();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
void MediaLibrary::LocateSong(const MPD::Song &s)
|
void MediaLibrary::LocateSong(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
std::string primary_tag;
|
std::string primary_tag;
|
||||||
@@ -907,15 +862,15 @@ void MediaLibrary::LocateSong(const MPD::Song &s)
|
|||||||
|
|
||||||
std::string album = s.getAlbum();
|
std::string album = s.getAlbum();
|
||||||
std::string date = s.getDate();
|
std::string date = s.getDate();
|
||||||
if ((hasTwoColumns && Albums.current().value().PrimaryTag != primary_tag)
|
if ((hasTwoColumns && Albums.current().value().entry().tag() != primary_tag)
|
||||||
|| album != Albums.current().value().Album
|
|| album != Albums.current().value().entry().album()
|
||||||
|| date != Albums.current().value().Date)
|
|| date != Albums.current().value().entry().date())
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < Albums.size(); ++i)
|
for (size_t i = 0; i < Albums.size(); ++i)
|
||||||
{
|
{
|
||||||
if ((!hasTwoColumns || Albums[i].value().PrimaryTag == primary_tag)
|
if ((!hasTwoColumns || Albums[i].value().entry().tag() == primary_tag)
|
||||||
&& album == Albums[i].value().Album
|
&& album == Albums[i].value().entry().album()
|
||||||
&& date == Albums[i].value().Date)
|
&& date == Albums[i].value().entry().date())
|
||||||
{
|
{
|
||||||
Albums.highlight(i);
|
Albums.highlight(i);
|
||||||
Songs.clear();
|
Songs.clear();
|
||||||
@@ -957,13 +912,14 @@ void MediaLibrary::AddToPlaylist(bool add_n_play)
|
|||||||
if (addSongsToPlaylist(list, add_n_play))
|
if (addSongsToPlaylist(list, add_n_play))
|
||||||
{
|
{
|
||||||
if ((!Tags.empty() && isActiveWindow(Tags))
|
if ((!Tags.empty() && isActiveWindow(Tags))
|
||||||
|| (isActiveWindow(Albums) && Albums.current().value().Date == AllTracksMarker))
|
|| (isActiveWindow(Albums) && Albums.current().value().isAllTracksEntry()))
|
||||||
{
|
{
|
||||||
std::string tag_type = lowercase(tagTypeToString(Config.media_lib_primary_tag));
|
std::string tag_type = lowercase(tagTypeToString(Config.media_lib_primary_tag));
|
||||||
Statusbar::msg("Songs with %s = \"%s\" added", tag_type.c_str(), Tags.current().value().tag().c_str());
|
Statusbar::msg("Songs with %s = \"%s\" added", tag_type.c_str(), Tags.current().value().tag().c_str());
|
||||||
}
|
}
|
||||||
else if (isActiveWindow(Albums))
|
else if (isActiveWindow(Albums))
|
||||||
Statusbar::msg("Songs from album \"%s\" added", Albums.current().value().Album.c_str());
|
Statusbar::msg("Songs from album \"%s\" added",
|
||||||
|
Albums.current().value().entry().album().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -982,24 +938,24 @@ void MediaLibrary::AddToPlaylist(bool add_n_play)
|
|||||||
|
|
||||||
namespace {//
|
namespace {//
|
||||||
|
|
||||||
std::string AlbumToString(const SearchConstraints &sc)
|
std::string AlbumToString(const AlbumEntry &ae)
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
if (sc.Date == AllTracksMarker)
|
if (ae.isAllTracksEntry())
|
||||||
result = "All tracks";
|
result = "All tracks";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (hasTwoColumns)
|
if (hasTwoColumns)
|
||||||
{
|
{
|
||||||
if (sc.PrimaryTag.empty())
|
if (ae.entry().tag().empty())
|
||||||
result += Config.empty_tag;
|
result += Config.empty_tag;
|
||||||
else
|
else
|
||||||
result += sc.PrimaryTag;
|
result += ae.entry().tag();
|
||||||
result += " - ";
|
result += " - ";
|
||||||
}
|
}
|
||||||
if (Config.media_lib_primary_tag != MPD_TAG_DATE && !sc.Date.empty())
|
if (Config.media_lib_primary_tag != MPD_TAG_DATE && !ae.entry().date().empty())
|
||||||
result += "(" + sc.Date + ") ";
|
result += "(" + ae.entry().date() + ") ";
|
||||||
result += sc.Album.empty() ? "<no album>" : sc.Album;
|
result += ae.entry().album().empty() ? "<no album>" : ae.entry().album();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1009,14 +965,14 @@ std::string SongToString(const MPD::Song &s)
|
|||||||
return s.toString(Config.song_library_format, Config.tags_separator);
|
return s.toString(Config.song_library_format, Config.tags_separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagEntryMatcher(const Regex &rx, const MPD::TagMTime &tagmtime)
|
bool TagEntryMatcher(const Regex &rx, const MediaLibrary::PrimaryTag &pt)
|
||||||
{
|
{
|
||||||
return rx.match(tagmtime.tag());
|
return rx.match(pt.tag());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<SearchConstraints>::Item &item, bool filter)
|
bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<AlbumEntry>::Item &item, bool filter)
|
||||||
{
|
{
|
||||||
if (item.isSeparator() || item.value().Date == AllTracksMarker)
|
if (item.isSeparator() || item.value().isAllTracksEntry())
|
||||||
return filter;
|
return filter;
|
||||||
return rx.match(AlbumToString(item.value()));
|
return rx.match(AlbumToString(item.value()));
|
||||||
}
|
}
|
||||||
@@ -1028,12 +984,12 @@ bool SongEntryMatcher(const Regex &rx, const MPD::Song &s)
|
|||||||
|
|
||||||
/***********************************************************************/
|
/***********************************************************************/
|
||||||
|
|
||||||
void DisplayAlbums(NC::Menu<SearchConstraints> &menu)
|
void DisplayAlbums(NC::Menu<AlbumEntry> &menu)
|
||||||
{
|
{
|
||||||
menu << AlbumToString(menu.drawn()->value());
|
menu << AlbumToString(menu.drawn()->value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayPrimaryTags(NC::Menu<MPD::TagMTime> &menu)
|
void DisplayPrimaryTags(NC::Menu<MediaLibrary::PrimaryTag> &menu)
|
||||||
{
|
{
|
||||||
const std::string &tag = menu.drawn()->value().tag();
|
const std::string &tag = menu.drawn()->value().tag();
|
||||||
if (tag.empty())
|
if (tag.empty())
|
||||||
@@ -1046,7 +1002,7 @@ void DisplayPrimaryTags(NC::Menu<MPD::TagMTime> &menu)
|
|||||||
|
|
||||||
bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b)
|
bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b)
|
||||||
{
|
{
|
||||||
int cmp = a.getDisc().compare(a.getDisc());
|
int cmp = a.getDisc().compare(b.getDisc());
|
||||||
if (cmp != 0)
|
if (cmp != 0)
|
||||||
return cmp < 0;
|
return cmp < 0;
|
||||||
return a.getTrack() < b.getTrack();
|
return a.getTrack() < b.getTrack();
|
||||||
|
|||||||
@@ -73,31 +73,60 @@ struct MediaLibrary: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, Sea
|
|||||||
int Columns();
|
int Columns();
|
||||||
void LocateSong(const MPD::Song &);
|
void LocateSong(const MPD::Song &);
|
||||||
ProxySongList songsProxyList();
|
ProxySongList songsProxyList();
|
||||||
|
void toggleSortMode();
|
||||||
|
|
||||||
// mtimes
|
struct PrimaryTag
|
||||||
bool hasMTimes();
|
|
||||||
void toggleMTimeSort();
|
|
||||||
|
|
||||||
struct SearchConstraints
|
|
||||||
{
|
{
|
||||||
SearchConstraints() { }
|
PrimaryTag(std::string tag_, time_t mtime_)
|
||||||
SearchConstraints(const std::string &tag, const std::string &album, const std::string &date) : PrimaryTag(tag), Album(album), Date(date), MTime(0) { }
|
: m_tag(std::move(tag_)), m_mtime(mtime_) { }
|
||||||
SearchConstraints(const std::string &album, const std::string &date) : Album(album), Date(date), MTime(0) { }
|
|
||||||
SearchConstraints(const std::string &tag, const std::string &album, const std::string &date, time_t mtime) : PrimaryTag(tag), Album(album), Date(date), MTime(mtime) { }
|
const std::string &tag() const { return m_tag; }
|
||||||
SearchConstraints(const std::string &album, const std::string &date, time_t mtime) : Album(album), Date(date), MTime(mtime) { }
|
time_t mtime() const { return m_mtime; }
|
||||||
|
|
||||||
std::string PrimaryTag;
|
private:
|
||||||
std::string Album;
|
std::string m_tag;
|
||||||
std::string Date;
|
time_t m_mtime;
|
||||||
time_t MTime;
|
|
||||||
|
|
||||||
bool operator<(const SearchConstraints &a) const;
|
|
||||||
|
|
||||||
bool hasMTime() { return MTime != 0; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NC::Menu<MPD::TagMTime> Tags;
|
struct Album
|
||||||
NC::Menu<SearchConstraints> Albums;
|
{
|
||||||
|
Album(std::string tag_, std::string album_, std::string date_, time_t mtime_)
|
||||||
|
: m_tag(std::move(tag_)), m_album(std::move(album_))
|
||||||
|
, m_date(std::move(date_)), m_mtime(mtime_) { }
|
||||||
|
|
||||||
|
const std::string &tag() const { return m_tag; }
|
||||||
|
const std::string &album() const { return m_album; }
|
||||||
|
const std::string &date() const { return m_date; }
|
||||||
|
time_t mtime() const { return m_mtime; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_tag;
|
||||||
|
std::string m_album;
|
||||||
|
std::string m_date;
|
||||||
|
time_t m_mtime;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AlbumEntry
|
||||||
|
{
|
||||||
|
AlbumEntry() : m_all_tracks_entry(false), m_album("", "", "", 0) { }
|
||||||
|
AlbumEntry(Album album_) : m_all_tracks_entry(false), m_album(album_) { }
|
||||||
|
|
||||||
|
const Album &entry() const { return m_album; }
|
||||||
|
bool isAllTracksEntry() const { return m_all_tracks_entry; }
|
||||||
|
|
||||||
|
static AlbumEntry mkAllTracksEntry(std::string tag) {
|
||||||
|
auto result = AlbumEntry(Album(tag, "", "", 0));
|
||||||
|
result.m_all_tracks_entry = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_all_tracks_entry;
|
||||||
|
Album m_album;
|
||||||
|
};
|
||||||
|
|
||||||
|
NC::Menu<PrimaryTag> Tags;
|
||||||
|
NC::Menu<AlbumEntry> Albums;
|
||||||
NC::Menu<MPD::Song> Songs;
|
NC::Menu<MPD::Song> Songs;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
102
src/mpdpp.cpp
102
src/mpdpp.cpp
@@ -1139,52 +1139,6 @@ void Connection::GetList(mpd_tag_type type, StringConsumer f)
|
|||||||
GoIdle();
|
GoIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
TagMTimeList Connection::GetListMTime(mpd_tag_type type, bool get_mtime)
|
|
||||||
{
|
|
||||||
TagMTimeList result;
|
|
||||||
if (!itsConnection)
|
|
||||||
return result;
|
|
||||||
assert(!isCommandsListEnabled);
|
|
||||||
GoBusy();
|
|
||||||
|
|
||||||
if (!get_mtime)
|
|
||||||
{
|
|
||||||
mpd_search_db_tags(itsConnection, type);
|
|
||||||
mpd_search_commit(itsConnection);
|
|
||||||
while (mpd_pair *item = mpd_recv_pair_tag(itsConnection, type))
|
|
||||||
{
|
|
||||||
result.push_back(TagMTime(item->value));
|
|
||||||
mpd_return_pair(itsConnection, item);
|
|
||||||
}
|
|
||||||
mpd_response_finish(itsConnection);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mpd_send_list_all_meta(itsConnection, "/");
|
|
||||||
std::map<std::string, time_t> max_mtimes;
|
|
||||||
while (mpd_song *s = mpd_recv_song(itsConnection))
|
|
||||||
{
|
|
||||||
Song song(s);
|
|
||||||
const std::string &tag = song.getTag(type);
|
|
||||||
time_t mtime = song.getMTime();
|
|
||||||
auto mt = max_mtimes.find(tag);
|
|
||||||
if (mt == max_mtimes.end())
|
|
||||||
max_mtimes.insert(std::make_pair(tag, mtime));
|
|
||||||
else
|
|
||||||
mt->second = std::max(mt->second, mtime);
|
|
||||||
}
|
|
||||||
mpd_response_finish(itsConnection);
|
|
||||||
|
|
||||||
for (auto it = max_mtimes.begin(); it != max_mtimes.end(); ++it)
|
|
||||||
{
|
|
||||||
result.push_back(TagMTime(it->first, it->second));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GoIdle();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::StartSearch(bool exact_match)
|
void Connection::StartSearch(bool exact_match)
|
||||||
{
|
{
|
||||||
if (itsConnection)
|
if (itsConnection)
|
||||||
@@ -1199,18 +1153,6 @@ void Connection::StartFieldSearch(mpd_tag_type item)
|
|||||||
mpd_search_db_tags(itsConnection, item);
|
mpd_search_db_tags(itsConnection, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Connection::StartFieldSearchMTime(mpd_tag_type item, bool get_mtime)
|
|
||||||
{
|
|
||||||
if (itsConnection)
|
|
||||||
{
|
|
||||||
itsSearchedField = item;
|
|
||||||
itsSearchFieldMTime = get_mtime;
|
|
||||||
if (!get_mtime)
|
|
||||||
mpd_search_db_tags(itsConnection, item);
|
|
||||||
else
|
|
||||||
mpd_search_db_songs(itsConnection, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::AddSearch(mpd_tag_type item, const std::string &str) const
|
void Connection::AddSearch(mpd_tag_type item, const std::string &str) const
|
||||||
{
|
{
|
||||||
@@ -1264,50 +1206,6 @@ void Connection::CommitSearchTags(StringConsumer f)
|
|||||||
GoIdle();
|
GoIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
TagMTimeList Connection::CommitSearchTagsMTime()
|
|
||||||
{
|
|
||||||
TagMTimeList result;
|
|
||||||
if (!itsConnection)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
assert(!isCommandsListEnabled);
|
|
||||||
GoBusy();
|
|
||||||
mpd_search_commit(itsConnection);
|
|
||||||
|
|
||||||
if (!itsSearchFieldMTime)
|
|
||||||
{
|
|
||||||
while (mpd_pair *tag = mpd_recv_pair_tag(itsConnection, itsSearchedField))
|
|
||||||
{
|
|
||||||
result.push_back(TagMTime(tag->value));
|
|
||||||
mpd_return_pair(itsConnection, tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::map<std::string, time_t> max_mtimes;
|
|
||||||
while (mpd_song *s = mpd_recv_song(itsConnection))
|
|
||||||
{
|
|
||||||
Song song(s);
|
|
||||||
const std::string &tag = song.getTag(itsSearchedField);
|
|
||||||
time_t mtime = song.getMTime();
|
|
||||||
auto mt = max_mtimes.find(tag);
|
|
||||||
if (mt == max_mtimes.end())
|
|
||||||
max_mtimes.insert(std::make_pair(tag, mtime));
|
|
||||||
else
|
|
||||||
mt->second = std::max(mt->second, mtime);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = max_mtimes.begin(); it != max_mtimes.end(); ++it)
|
|
||||||
{
|
|
||||||
result.push_back(TagMTime(it->first, it->second));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mpd_response_finish(itsConnection);
|
|
||||||
GoIdle();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::GetDirectory(const std::string &directory, ItemConsumer f)
|
void Connection::GetDirectory(const std::string &directory, ItemConsumer f)
|
||||||
{
|
{
|
||||||
if (!itsConnection)
|
if (!itsConnection)
|
||||||
|
|||||||
32
src/mpdpp.h
32
src/mpdpp.h
@@ -93,35 +93,6 @@ private:
|
|||||||
bool m_enabled;
|
bool m_enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TagMTime
|
|
||||||
{
|
|
||||||
TagMTime(const std::string &tag_) : m_tag(tag_), m_mtime(0) { }
|
|
||||||
TagMTime(const std::string &tag_, time_t mtime_) : m_tag(tag_), m_mtime(mtime_) { }
|
|
||||||
|
|
||||||
const std::string &tag() const { return m_tag; }
|
|
||||||
time_t mtime() const { return m_mtime; }
|
|
||||||
|
|
||||||
void set_mtime(time_t mtime_)
|
|
||||||
{
|
|
||||||
m_mtime = mtime_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_tag(std::string tag_)
|
|
||||||
{
|
|
||||||
m_tag = tag_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasMTime()
|
|
||||||
{
|
|
||||||
return (m_mtime != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_tag;
|
|
||||||
time_t m_mtime;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::vector<TagMTime> TagMTimeList;
|
|
||||||
typedef std::vector<Item> ItemList;
|
typedef std::vector<Item> ItemList;
|
||||||
typedef std::vector<std::string> StringList;
|
typedef std::vector<std::string> StringList;
|
||||||
typedef std::vector<Output> OutputList;
|
typedef std::vector<Output> OutputList;
|
||||||
@@ -245,17 +216,14 @@ public:
|
|||||||
|
|
||||||
void StartSearch(bool);
|
void StartSearch(bool);
|
||||||
void StartFieldSearch(mpd_tag_type);
|
void StartFieldSearch(mpd_tag_type);
|
||||||
void StartFieldSearchMTime(mpd_tag_type, bool);
|
|
||||||
void AddSearch(mpd_tag_type, const std::string &) const;
|
void AddSearch(mpd_tag_type, const std::string &) const;
|
||||||
void AddSearchAny(const std::string &str) const;
|
void AddSearchAny(const std::string &str) const;
|
||||||
void AddSearchURI(const std::string &str) const;
|
void AddSearchURI(const std::string &str) const;
|
||||||
void CommitSearchSongs(SongConsumer f);
|
void CommitSearchSongs(SongConsumer f);
|
||||||
void CommitSearchTags(StringConsumer f);
|
void CommitSearchTags(StringConsumer f);
|
||||||
TagMTimeList CommitSearchTagsMTime();
|
|
||||||
|
|
||||||
void GetPlaylists(StringConsumer f);
|
void GetPlaylists(StringConsumer f);
|
||||||
void GetList(mpd_tag_type type, StringConsumer f);
|
void GetList(mpd_tag_type type, StringConsumer f);
|
||||||
TagMTimeList GetListMTime(mpd_tag_type, bool);
|
|
||||||
void GetDirectory(const std::string &directory, ItemConsumer f);
|
void GetDirectory(const std::string &directory, ItemConsumer f);
|
||||||
void GetDirectoryRecursive(const std::string &directory, SongConsumer f);
|
void GetDirectoryRecursive(const std::string &directory, SongConsumer f);
|
||||||
void GetSongs(const std::string &directory, SongConsumer f);
|
void GetSongs(const std::string &directory, SongConsumer f);
|
||||||
|
|||||||
41
src/song.cpp
41
src/song.cpp
@@ -43,10 +43,13 @@ size_t calc_hash(const char* s, unsigned seed = 0)
|
|||||||
|
|
||||||
namespace MPD {//
|
namespace MPD {//
|
||||||
|
|
||||||
const char *Song::getTag(mpd_tag_type type, unsigned idx) const
|
std::string Song::get(mpd_tag_type type, unsigned idx) const
|
||||||
{
|
{
|
||||||
|
std::string result;
|
||||||
const char *tag = mpd_song_get_tag(m_song.get(), type, idx);
|
const char *tag = mpd_song_get_tag(m_song.get(), type, idx);
|
||||||
return tag ? tag : "";
|
if (tag)
|
||||||
|
result = tag;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Song::Song(mpd_song *s)
|
Song::Song(mpd_song *s)
|
||||||
@@ -96,31 +99,31 @@ std::string Song::getDirectory(unsigned idx) const
|
|||||||
std::string Song::getArtist(unsigned idx) const
|
std::string Song::getArtist(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_ARTIST, idx);
|
return get(MPD_TAG_ARTIST, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getTitle(unsigned idx) const
|
std::string Song::getTitle(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_TITLE, idx);
|
return get(MPD_TAG_TITLE, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getAlbum(unsigned idx) const
|
std::string Song::getAlbum(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_ALBUM, idx);
|
return get(MPD_TAG_ALBUM, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getAlbumArtist(unsigned idx) const
|
std::string Song::getAlbumArtist(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_ALBUM_ARTIST, idx);
|
return get(MPD_TAG_ALBUM_ARTIST, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getTrack(unsigned idx) const
|
std::string Song::getTrack(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
std::string track = getTag(MPD_TAG_TRACK, idx);
|
std::string track = get(MPD_TAG_TRACK, idx);
|
||||||
if ((track.length() == 1 && track[0] != '0')
|
if ((track.length() == 1 && track[0] != '0')
|
||||||
|| (track.length() > 3 && track[1] == '/'))
|
|| (track.length() > 3 && track[1] == '/'))
|
||||||
track = "0"+track;
|
track = "0"+track;
|
||||||
@@ -140,37 +143,37 @@ std::string Song::getTrackNumber(unsigned idx) const
|
|||||||
std::string Song::getDate(unsigned idx) const
|
std::string Song::getDate(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_DATE, idx);
|
return get(MPD_TAG_DATE, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getGenre(unsigned idx) const
|
std::string Song::getGenre(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_GENRE, idx);
|
return get(MPD_TAG_GENRE, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getComposer(unsigned idx) const
|
std::string Song::getComposer(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_COMPOSER, idx);
|
return get(MPD_TAG_COMPOSER, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getPerformer(unsigned idx) const
|
std::string Song::getPerformer(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_PERFORMER, idx);
|
return get(MPD_TAG_PERFORMER, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getDisc(unsigned idx) const
|
std::string Song::getDisc(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_DISC, idx);
|
return get(MPD_TAG_DISC, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getComment(unsigned idx) const
|
std::string Song::getComment(unsigned idx) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
return getTag(MPD_TAG_COMMENT, idx);
|
return get(MPD_TAG_COMMENT, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::getLength(unsigned idx) const
|
std::string Song::getLength(unsigned idx) const
|
||||||
@@ -318,7 +321,7 @@ std::string Song::ParseFormat(std::string::const_iterator &it, const std::string
|
|||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
bool has_some_tags = 0;
|
bool has_some_tags = 0;
|
||||||
MPD::Song::GetFunction get = 0;
|
MPD::Song::GetFunction get_fun = 0;
|
||||||
while (*++it != '}')
|
while (*++it != '}')
|
||||||
{
|
{
|
||||||
while (*it == '{')
|
while (*it == '{')
|
||||||
@@ -345,21 +348,21 @@ std::string Song::ParseFormat(std::string::const_iterator &it, const std::string
|
|||||||
if (*it == '%')
|
if (*it == '%')
|
||||||
{
|
{
|
||||||
result += *it;
|
result += *it;
|
||||||
get = 0;
|
get_fun = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
get = charToGetFunction(*it);
|
get_fun = charToGetFunction(*it);
|
||||||
|
|
||||||
if (get)
|
if (get_fun)
|
||||||
{
|
{
|
||||||
std::string tag = getTags(get, tags_separator);
|
std::string tag = getTags(get_fun, tags_separator);
|
||||||
if (!escape_chars.empty()) // prepend format escape character to all given chars to escape
|
if (!escape_chars.empty()) // prepend format escape character to all given chars to escape
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < escape_chars.length(); ++i)
|
for (size_t i = 0; i < escape_chars.length(); ++i)
|
||||||
for (size_t j = 0; (j = tag.find(escape_chars[i], j)) != std::string::npos; j += 2)
|
for (size_t j = 0; (j = tag.find(escape_chars[i], j)) != std::string::npos; j += 2)
|
||||||
tag.replace(j, 1, std::string(1, FormatEscapeCharacter) + escape_chars[i]);
|
tag.replace(j, 1, std::string(1, FormatEscapeCharacter) + escape_chars[i]);
|
||||||
}
|
}
|
||||||
if (!tag.empty() && (get != &MPD::Song::getLength || getDuration() > 0))
|
if (!tag.empty() && (get_fun != &MPD::Song::getLength || getDuration() > 0))
|
||||||
{
|
{
|
||||||
if (delimiter && tag.size() > delimiter)
|
if (delimiter && tag.size() > delimiter)
|
||||||
tag = ToString(wideShorten(ToWString(tag), delimiter));
|
tag = ToString(wideShorten(ToWString(tag), delimiter));
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ struct Song
|
|||||||
|
|
||||||
Song(mpd_song *s);
|
Song(mpd_song *s);
|
||||||
|
|
||||||
|
std::string get(mpd_tag_type type, unsigned idx = 0) const;
|
||||||
|
|
||||||
virtual std::string getURI(unsigned idx = 0) const;
|
virtual std::string getURI(unsigned idx = 0) const;
|
||||||
virtual std::string getName(unsigned idx = 0) const;
|
virtual std::string getName(unsigned idx = 0) const;
|
||||||
virtual std::string getDirectory(unsigned idx = 0) const;
|
virtual std::string getDirectory(unsigned idx = 0) const;
|
||||||
@@ -81,8 +83,6 @@ struct Song
|
|||||||
static bool isFormatOk(const std::string &type, const std::string &fmt);
|
static bool isFormatOk(const std::string &type, const std::string &fmt);
|
||||||
|
|
||||||
static const char FormatEscapeCharacter = 1;
|
static const char FormatEscapeCharacter = 1;
|
||||||
|
|
||||||
const char *getTag(mpd_tag_type type, unsigned idx = 0) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string ParseFormat(std::string::const_iterator &it, const std::string &tags_separator,
|
std::string ParseFormat(std::string::const_iterator &it, const std::string &tags_separator,
|
||||||
|
|||||||
Reference in New Issue
Block a user