media library: add support for sorting by mtime

This commit is contained in:
Matthew Hague
2012-09-20 12:32:48 +02:00
committed by Andrzej Rybczak
parent add40d542d
commit b487f10f10
15 changed files with 336 additions and 65 deletions

View File

@@ -431,6 +431,9 @@
#def_key "m" #def_key "m"
# move_selected_items_up # move_selected_items_up
# #
#def_key "m"
# toggle_media_library_sort_mode
#
#def_key "n" #def_key "n"
# move_sort_order_down # move_sort_order_down
# #

View File

@@ -437,6 +437,8 @@
# #
#media_library_display_empty_tag = "yes" #media_library_display_empty_tag = "yes"
# #
#media_library_sort_by_mtime = "no"
#
#enable_window_title = "yes" #enable_window_title = "yes"
# #
## ##

View File

@@ -1397,13 +1397,13 @@ void EditLibraryTag::Run()
Statusbar::lock(); Statusbar::lock();
Statusbar::put() << NC::fmtBold << tagTypeToString(Config.media_lib_primary_tag) << NC::fmtBoldEnd << ": "; Statusbar::put() << NC::fmtBold << tagTypeToString(Config.media_lib_primary_tag) << NC::fmtBoldEnd << ": ";
std::string new_tag = wFooter->getString(myLibrary->Tags.current().value()); std::string new_tag = wFooter->getString(myLibrary->Tags.current().value().tag());
Statusbar::unlock(); Statusbar::unlock();
if (!new_tag.empty() && new_tag != myLibrary->Tags.current().value()) if (!new_tag.empty() && new_tag != myLibrary->Tags.current().value().tag())
{ {
Statusbar::msg("Updating tags..."); Statusbar::msg("Updating tags...");
Mpd.StartSearch(1); Mpd.StartSearch(1);
Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current().value()); Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current().value().tag());
MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag); MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
assert(set); assert(set);
bool success = true; bool success = true;
@@ -2162,12 +2162,15 @@ void ToggleLibraryTagType::Run()
myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : ""); myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
myLibrary->Tags.reset(); myLibrary->Tags.reset();
item_type = lowercase(item_type); item_type = lowercase(item_type);
std::string and_mtime = Config.media_library_sort_by_mtime ?
" and mtime" :
"";
if (myLibrary->Columns() == 2) if (myLibrary->Columns() == 2)
{ {
myLibrary->Songs.clear(); myLibrary->Songs.clear();
myLibrary->Albums.reset(); myLibrary->Albums.reset();
myLibrary->Albums.clear(); myLibrary->Albums.clear();
myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + ")" : ""); myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
myLibrary->Albums.display(); myLibrary->Albums.display();
} }
else else
@@ -2179,6 +2182,16 @@ void ToggleLibraryTagType::Run()
} }
} }
bool ToggleMediaLibrarySortMode::canBeRun() const
{
return myScreen == myLibrary;
}
void ToggleMediaLibrarySortMode::Run()
{
myLibrary->toggleMTimeSort();
}
bool RefetchLyrics::canBeRun() const bool RefetchLyrics::canBeRun() const
{ {
# ifdef HAVE_CURL_CURL_H # ifdef HAVE_CURL_CURL_H
@@ -2295,7 +2308,7 @@ void ShowArtistInfo::Run()
{ {
assert(!myLibrary->Tags.empty()); assert(!myLibrary->Tags.empty());
assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST); assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
artist = myLibrary->Tags.current().value(); artist = myLibrary->Tags.current().value().tag();
} }
else else
{ {
@@ -2660,6 +2673,7 @@ void populateActions()
insertAction(new AddRandomItems()); insertAction(new AddRandomItems());
insertAction(new ToggleBrowserSortMode()); insertAction(new ToggleBrowserSortMode());
insertAction(new ToggleLibraryTagType()); insertAction(new ToggleLibraryTagType());
insertAction(new ToggleMediaLibrarySortMode());
insertAction(new RefetchLyrics()); insertAction(new RefetchLyrics());
insertAction(new RefetchArtistInfo()); insertAction(new RefetchArtistInfo());
insertAction(new SetSelectedItemsPriority()); insertAction(new SetSelectedItemsPriority());

View File

@@ -43,7 +43,7 @@ enum ActionType
aCropMainPlaylist, aCropPlaylist, aClearMainPlaylist, aClearPlaylist, aSortPlaylist, aReversePlaylist, aCropMainPlaylist, aCropPlaylist, aClearMainPlaylist, aClearPlaylist, aSortPlaylist, aReversePlaylist,
aApplyFilter, aFind, aFindItemForward, aFindItemBackward, aNextFoundItem, aApplyFilter, aFind, aFindItemForward, aFindItemBackward, aNextFoundItem,
aPreviousFoundItem, aToggleFindMode, aToggleReplayGainMode, aToggleSpaceMode, aToggleAddMode, aPreviousFoundItem, aToggleFindMode, aToggleReplayGainMode, aToggleSpaceMode, aToggleAddMode,
aToggleMouse, aToggleBitrateVisibility, aAddRandomItems, aToggleBrowserSortMode, aToggleLibraryTagType, aToggleMouse, aToggleBitrateVisibility, aAddRandomItems, aToggleBrowserSortMode, aToggleLibraryTagType, aToggleMediaLibrarySortMode,
aRefetchLyrics, aRefetchArtistInfo, aSetSelectedItemsPriority, aFilterPlaylistOnPriorities, aRefetchLyrics, aRefetchArtistInfo, aSetSelectedItemsPriority, aFilterPlaylistOnPriorities,
aShowSongInfo, aShowArtistInfo, aShowSongInfo, aShowArtistInfo,
aShowLyrics, aQuit, aNextScreen, aPreviousScreen, aShowHelp, aShowPlaylist, aShowBrowser, aChangeBrowseMode, aShowLyrics, aQuit, aNextScreen, aPreviousScreen, aShowHelp, aShowPlaylist, aShowBrowser, aChangeBrowseMode,
@@ -914,6 +914,16 @@ protected:
virtual void Run(); virtual void Run();
}; };
struct ToggleMediaLibrarySortMode : public Action
{
ToggleMediaLibrarySortMode()
: Action(aToggleMediaLibrarySortMode, "toggle_media_library_sort_mode") { }
protected:
virtual bool canBeRun() const;
virtual void Run();
};
struct RefetchLyrics : public Action struct RefetchLyrics : public Action
{ {
RefetchLyrics() : Action(aRefetchLyrics, "refetch_lyrics") { } RefetchLyrics() : Action(aRefetchLyrics, "refetch_lyrics") { }

View File

@@ -529,6 +529,7 @@ void BindingsConfiguration::generateDefaults()
{ {
bind(k, aMoveSortOrderUp); bind(k, aMoveSortOrderUp);
bind(k, aMoveSelectedItemsUp); bind(k, aMoveSelectedItemsUp);
bind(k, aToggleMediaLibrarySortMode);
} }
if (notBound(k = stringToKey("n"))) if (notBound(k = stringToKey("n")))
{ {

View File

@@ -325,6 +325,7 @@ void Help::GetKeybindings()
# endif // HAVE_TAGLIB_H # endif // HAVE_TAGLIB_H
KeyDesc(aEditLibraryTag, "Edit tag (left column)/album (middle/right column)"); KeyDesc(aEditLibraryTag, "Edit tag (left column)/album (middle/right column)");
KeyDesc(aToggleLibraryTagType, "Toggle type of tag used in left column"); KeyDesc(aToggleLibraryTagType, "Toggle type of tag used in left column");
KeyDesc(aToggleMediaLibrarySortMode, "Toggle sort mode");
KeysSection("Playlist editor"); KeysSection("Playlist editor");
KeyDesc(aPreviousColumn, "Previous column"); KeyDesc(aPreviousColumn, "Previous column");

View File

@@ -66,12 +66,12 @@ typedef MediaLibrary::SearchConstraints SearchConstraints;
std::string AlbumToString(const SearchConstraints &sc); 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 std::string &tag); bool TagEntryMatcher(const Regex &rx, const MPD::TagMTime &tagmtime);
bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<SearchConstraints>::Item &item, bool filter); bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<SearchConstraints>::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<SearchConstraints> &menu);
void DisplayPrimaryTags(NC::Menu<std::string> &menu); 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);
@@ -101,14 +101,34 @@ class SortSearchConstraints {
public: public:
SortSearchConstraints() : m_cmp(std::locale(), Config.ignore_leading_the) { } SortSearchConstraints() : m_cmp(std::locale(), Config.ignore_leading_the) { }
bool operator()(const SearchConstraints &a, const SearchConstraints &b) const { bool operator()(const SearchConstraints &a, const SearchConstraints &b) const {
int result; if (Config.media_library_sort_by_mtime)
result = m_cmp(a.PrimaryTag, b.PrimaryTag); {
if (result != 0) return a.MTime > b.MTime;
return result < 0; }
result = m_cmp(a.Date, b.Date); else
if (result != 0) {
return result < 0; int result;
return m_cmp(a.Album, b.Album) < 0; result = m_cmp(a.PrimaryTag, b.PrimaryTag);
if (result != 0)
return result < 0;
result = m_cmp(a.Date, b.Date);
if (result != 0)
return result < 0;
return m_cmp(a.Album, b.Album) < 0;
}
}
};
class ArtistSorting {
LocaleStringComparison m_cmp;
public:
ArtistSorting() : m_cmp(std::locale(), Config.ignore_leading_the) { }
bool operator()(const MPD::TagMTime &a,
const MPD::TagMTime &b) const {
if (Config.media_library_sort_by_mtime)
return a.mtime() > b.mtime();
else
return m_cmp(a.tag(), b.tag()) < 0;
} }
}; };
@@ -123,7 +143,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<std::string>(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, NC::brNone); 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.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);
@@ -209,18 +229,74 @@ 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()) if (!hasTwoColumns && Tags.reallyEmpty())
{ {
Albums.clear(); Albums.clear();
Songs.clear(); Songs.clear();
auto list = Mpd.GetList(Config.media_lib_primary_tag); auto list = Mpd.GetListMTime(Config.media_lib_primary_tag,
std::sort(list.begin(), list.end(), Config.media_library_sort_by_mtime);
LocaleBasedSorting(std::locale(), Config.ignore_leading_the));
std::sort(list.begin(), list.end(), ArtistSorting());
for (auto it = list.begin(); it != list.end(); ++it) for (auto it = list.begin(); it != list.end(); ++it)
{ {
if (it->empty() && !Config.media_library_display_empty_tag) if (it->tag().empty() && !Config.media_library_display_empty_tag)
continue; continue;
Tags.addItem(*it); Tags.addItem(*it);
} }
@@ -234,22 +310,26 @@ void MediaLibrary::update()
// and slows down the whole process. // and slows down the whole process.
Mpd.BlockIdle(true); Mpd.BlockIdle(true);
Albums.reset(); Albums.reset();
Mpd.StartFieldSearch(MPD_TAG_ALBUM); Mpd.StartFieldSearchMTime(MPD_TAG_ALBUM, Config.media_library_sort_by_mtime);
Mpd.AddSearch(Config.media_lib_primary_tag, Tags.current().value()); Mpd.AddSearch(Config.media_lib_primary_tag, Tags.current().value().tag());
auto albums = Mpd.CommitSearchTags(); auto albums = Mpd.CommitSearchTagsMTime();
for (auto album = albums.begin(); album != albums.end(); ++album) for (auto tagmtime = albums.begin(); tagmtime != albums.end(); ++tagmtime)
{ {
const std::string &album = tagmtime->tag();
time_t mtime = tagmtime->mtime();
if (Config.media_library_display_date) if (Config.media_library_display_date)
{ {
Mpd.StartFieldSearch(MPD_TAG_DATE); Mpd.StartFieldSearch(MPD_TAG_DATE);
Mpd.AddSearch(Config.media_lib_primary_tag, Tags.current().value());
Mpd.AddSearch(MPD_TAG_ALBUM, *album); Mpd.AddSearch(Config.media_lib_primary_tag,
Tags.current().value().tag());
Mpd.AddSearch(MPD_TAG_ALBUM, album);
auto dates = Mpd.CommitSearchTags(); auto dates = Mpd.CommitSearchTags();
for (auto date = dates.begin(); date != dates.end(); ++date) for (auto date = dates.begin(); date != dates.end(); ++date)
Albums.addItem(SearchConstraints(*album, *date)); Albums.addItem(SearchConstraints(album, *date, mtime));
} }
else else
Albums.addItem(SearchConstraints(*album, "")); Albums.addItem(SearchConstraints(album, "", mtime));
} }
if (!Albums.empty()) if (!Albums.empty())
std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints()); std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints());
@@ -270,30 +350,33 @@ void MediaLibrary::update()
auto artists = Mpd.GetList(Config.media_lib_primary_tag); auto artists = Mpd.GetList(Config.media_lib_primary_tag);
for (auto artist = artists.begin(); artist != artists.end(); ++artist) for (auto artist = artists.begin(); artist != artists.end(); ++artist)
{ {
Mpd.StartFieldSearch(MPD_TAG_ALBUM); Mpd.StartFieldSearchMTime(MPD_TAG_ALBUM, Config.media_library_sort_by_mtime);
Mpd.AddSearch(Config.media_lib_primary_tag, *artist); Mpd.AddSearch(Config.media_lib_primary_tag, *artist);
auto albums = Mpd.CommitSearchTags(); auto albums = Mpd.CommitSearchTagsMTime();
for (auto album = albums.begin(); album != albums.end(); ++album) 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_library_display_date)
{ {
if (Config.media_lib_primary_tag != MPD_TAG_DATE) if (Config.media_lib_primary_tag != MPD_TAG_DATE)
{ {
Mpd.StartFieldSearch(MPD_TAG_DATE); Mpd.StartFieldSearch(MPD_TAG_DATE);
Mpd.AddSearch(Config.media_lib_primary_tag, *artist); Mpd.AddSearch(Config.media_lib_primary_tag, *artist);
Mpd.AddSearch(MPD_TAG_ALBUM, *album); Mpd.AddSearch(MPD_TAG_ALBUM, album);
auto dates = Mpd.CommitSearchTags(); auto dates = Mpd.CommitSearchTags();
for (auto date = dates.begin(); date != dates.end(); ++date) for (auto date = dates.begin(); date != dates.end(); ++date)
Albums.addItem(SearchConstraints(*artist, *album, *date)); Albums.addItem(SearchConstraints(*artist, album, *date, mtime));
} }
else else
Albums.addItem(SearchConstraints(*artist, *album, *artist)); Albums.addItem(SearchConstraints(*artist, album, *artist, mtime));
} }
else else
Albums.addItem(SearchConstraints(*artist, *album, "")); Albums.addItem(SearchConstraints(*artist, album, "", mtime));
} }
} }
Mpd.BlockIdle(0); Mpd.BlockIdle(0);
if (!Albums.empty()) if (!Albums.empty())
std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints()); std::sort(Albums.beginV(), Albums.endV(), SortSearchConstraints());
Albums.refresh(); Albums.refresh();
@@ -311,7 +394,9 @@ void MediaLibrary::update()
Songs.reset(); Songs.reset();
Mpd.StartSearch(1); Mpd.StartSearch(1);
Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns ? Albums.current().value().PrimaryTag : Tags.current().value()); Mpd.AddSearch(Config.media_lib_primary_tag,
hasTwoColumns ? Albums.current().value().PrimaryTag :
Tags.current().value().tag());
if (Albums.current().value().Date != AllTracksMarker) if (Albums.current().value().Date != AllTracksMarker)
{ {
Mpd.AddSearch(MPD_TAG_ALBUM, Albums.current().value().Album); Mpd.AddSearch(MPD_TAG_ALBUM, Albums.current().value().Album);
@@ -471,7 +556,7 @@ std::string MediaLibrary::currentFilter()
{ {
std::string filter; std::string filter;
if (isActiveWindow(Tags)) if (isActiveWindow(Tags))
filter = RegexFilter<std::string>::currentFilter(Tags); filter = RegexFilter<MPD::TagMTime>::currentFilter(Tags);
else if (isActiveWindow(Albums)) else if (isActiveWindow(Albums))
filter = RegexItemFilter<SearchConstraints>::currentFilter(Albums); filter = RegexItemFilter<SearchConstraints>::currentFilter(Albums);
else if (isActiveWindow(Songs)) else if (isActiveWindow(Songs))
@@ -484,7 +569,7 @@ void MediaLibrary::applyFilter(const std::string &filter)
if (isActiveWindow(Tags)) if (isActiveWindow(Tags))
{ {
Tags.showAll(); Tags.showAll();
auto rx = RegexFilter<std::string>(filter, Config.regex_type, TagEntryMatcher); auto rx = RegexFilter<MPD::TagMTime>(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))
@@ -514,7 +599,7 @@ bool MediaLibrary::search(const std::string &constraint)
bool result = false; bool result = false;
if (isActiveWindow(Tags)) if (isActiveWindow(Tags))
{ {
auto rx = RegexFilter<std::string>(constraint, Config.regex_type, TagEntryMatcher); auto rx = RegexFilter<MPD::TagMTime>(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))
@@ -596,10 +681,10 @@ MPD::SongList MediaLibrary::getSelectedSongs()
}; };
for (auto it = Tags.begin(); it != Tags.end(); ++it) for (auto it = Tags.begin(); it != Tags.end(); ++it)
if (it->isSelected()) if (it->isSelected())
tag_handler(it->value()); tag_handler(it->value().tag());
// if no item is selected, add current one // if no item is selected, add current one
if (result.empty() && !Tags.empty()) if (result.empty() && !Tags.empty())
tag_handler(Tags.current().value()); tag_handler(Tags.current().value().tag());
} }
else if (isActiveWindow(Albums)) else if (isActiveWindow(Albums))
{ {
@@ -612,7 +697,8 @@ MPD::SongList MediaLibrary::getSelectedSongs()
if (hasTwoColumns) if (hasTwoColumns)
Mpd.AddSearch(Config.media_lib_primary_tag, sc.PrimaryTag); Mpd.AddSearch(Config.media_lib_primary_tag, sc.PrimaryTag);
else else
Mpd.AddSearch(Config.media_lib_primary_tag, Tags.current().value()); Mpd.AddSearch(Config.media_lib_primary_tag,
Tags.current().value().tag());
Mpd.AddSearch(MPD_TAG_ALBUM, sc.Album); Mpd.AddSearch(MPD_TAG_ALBUM, sc.Album);
Mpd.AddSearch(MPD_TAG_DATE, sc.Date); Mpd.AddSearch(MPD_TAG_DATE, sc.Date);
auto songs = Mpd.CommitSearchSongs(); auto songs = Mpd.CommitSearchSongs();
@@ -726,7 +812,10 @@ 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));
Albums.setTitle("Albums (sorted by " + item_type + ")"); std::string and_mtime = Config.media_library_sort_by_mtime ?
" and mtime" :
"";
Albums.setTitle("Albums (sorted by " + item_type + and_mtime + ")");
} }
else else
Albums.setTitle(""); Albums.setTitle("");
@@ -792,11 +881,11 @@ void MediaLibrary::LocateSong(const MPD::Song &s)
Tags.showAll(); Tags.showAll();
if (Tags.empty()) if (Tags.empty())
update(); update();
if (primary_tag != Tags.current().value()) if (primary_tag != Tags.current().value().tag())
{ {
for (size_t i = 0; i < Tags.size(); ++i) for (size_t i = 0; i < Tags.size(); ++i)
{ {
if (primary_tag == Tags[i].value()) if (primary_tag == Tags[i].value().tag())
{ {
Tags.highlight(i); Tags.highlight(i);
Albums.clear(); Albums.clear();
@@ -866,7 +955,7 @@ void MediaLibrary::AddToPlaylist(bool add_n_play)
|| (isActiveWindow(Albums) && Albums.current().value().Date == AllTracksMarker)) || (isActiveWindow(Albums) && Albums.current().value().Date == AllTracksMarker))
{ {
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().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().Album.c_str());
@@ -915,9 +1004,9 @@ 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 std::string &tag) bool TagEntryMatcher(const Regex &rx, const MPD::TagMTime &tagmtime)
{ {
return rx.match(tag); return rx.match(tagmtime.tag());
} }
bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<SearchConstraints>::Item &item, bool filter) bool AlbumEntryMatcher(const Regex &rx, const NC::Menu<SearchConstraints>::Item &item, bool filter)
@@ -939,9 +1028,9 @@ void DisplayAlbums(NC::Menu<SearchConstraints> &menu)
menu << AlbumToString(menu.drawn()->value()); menu << AlbumToString(menu.drawn()->value());
} }
void DisplayPrimaryTags(NC::Menu<std::string> &menu) void DisplayPrimaryTags(NC::Menu<MPD::TagMTime> &menu)
{ {
const std::string &tag = menu.drawn()->value(); const std::string &tag = menu.drawn()->value().tag();
if (tag.empty()) if (tag.empty())
menu << Config.empty_tag; menu << Config.empty_tag;
else else
@@ -952,7 +1041,7 @@ void DisplayPrimaryTags(NC::Menu<std::string> &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(b.getDisc()); int cmp = a.getDisc().compare(a.getDisc());
if (cmp != 0) if (cmp != 0)
return cmp < 0; return cmp < 0;
return a.getTrack() < b.getTrack(); return a.getTrack() < b.getTrack();

View File

@@ -74,20 +74,29 @@ struct MediaLibrary: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, Sea
void LocateSong(const MPD::Song &); void LocateSong(const MPD::Song &);
ProxySongList songsProxyList(); ProxySongList songsProxyList();
// mtimes
bool hasMTimes();
void toggleMTimeSort();
struct SearchConstraints struct SearchConstraints
{ {
SearchConstraints() { } SearchConstraints() { }
SearchConstraints(const std::string &tag, const std::string &album, const std::string &date) SearchConstraints(const std::string &tag, const std::string &album, const std::string &date) : PrimaryTag(tag), Album(album), Date(date), MTime(0) { }
: PrimaryTag(tag), Album(album), Date(date) { } SearchConstraints(const std::string &album, const std::string &date) : Album(album), Date(date), MTime(0) { }
SearchConstraints(const std::string &album, const std::string &date) SearchConstraints(const std::string &tag, const std::string &album, const std::string &date, time_t mtime) : PrimaryTag(tag), Album(album), Date(date), MTime(mtime) { }
: Album(album), Date(date) { } SearchConstraints(const std::string &album, const std::string &date, time_t mtime) : Album(album), Date(date), MTime(mtime) { }
std::string PrimaryTag; std::string PrimaryTag;
std::string Album; std::string Album;
std::string Date; std::string Date;
time_t MTime;
bool operator<(const SearchConstraints &a) const;
bool hasMTime() { return MTime != 0; }
}; };
NC::Menu<std::string> Tags; NC::Menu<MPD::TagMTime> Tags;
NC::Menu<SearchConstraints> Albums; NC::Menu<SearchConstraints> Albums;
NC::Menu<MPD::Song> Songs; NC::Menu<MPD::Song> Songs;

View File

@@ -21,6 +21,7 @@
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <algorithm> #include <algorithm>
#include <map>
#include "charset.h" #include "charset.h"
#include "error.h" #include "error.h"
@@ -1140,6 +1141,52 @@ StringList Connection::GetList(mpd_tag_type type)
return result; return result;
} }
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)
@@ -1154,6 +1201,18 @@ 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
{ {
@@ -1211,6 +1270,50 @@ StringList Connection::CommitSearchTags()
return result; return result;
} }
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;
}
ItemList Connection::GetDirectory(const std::string &path) ItemList Connection::GetDirectory(const std::string &path)
{ {
ItemList result; ItemList result;

View File

@@ -93,6 +93,35 @@ 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;
@@ -213,14 +242,17 @@ 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;
SongList CommitSearchSongs(); SongList CommitSearchSongs();
StringList CommitSearchTags(); StringList CommitSearchTags();
TagMTimeList CommitSearchTagsMTime();
StringList GetPlaylists(); StringList GetPlaylists();
StringList GetList(mpd_tag_type); StringList GetList(mpd_tag_type);
TagMTimeList GetListMTime(mpd_tag_type, bool);
ItemList GetDirectory(const std::string &); ItemList GetDirectory(const std::string &);
SongList GetDirectoryRecursive(const std::string &); SongList GetDirectoryRecursive(const std::string &);
SongList GetSongs(const std::string &); SongList GetSongs(const std::string &);
@@ -272,6 +304,7 @@ private:
void *itsErrorHandlerUserdata; void *itsErrorHandlerUserdata;
mpd_tag_type itsSearchedField; mpd_tag_type itsSearchedField;
bool itsSearchFieldMTime;
}; };
} }
@@ -279,4 +312,3 @@ private:
extern MPD::Connection Mpd; extern MPD::Connection Mpd;
#endif #endif

View File

@@ -89,7 +89,7 @@ private:
}; };
void replaceTag(mpd_tag_type tag_type, std::string &&orig_value, void replaceTag(mpd_tag_type tag_type, std::string &&orig_value,
const std::string &value, unsigned idx); const std::string &value, unsigned idx);
template <typename F> template <typename F>
std::string getTag(mpd_tag_type tag_type, F orig_value, unsigned idx) const { std::string getTag(mpd_tag_type tag_type, F orig_value, unsigned idx) const {

View File

@@ -239,6 +239,7 @@ void Configuration::SetDefaults()
new_design = false; new_design = false;
visualizer_use_wave = true; visualizer_use_wave = true;
visualizer_in_stereo = false; visualizer_in_stereo = false;
media_library_sort_by_mtime = false;
tag_editor_extended_numeration = false; tag_editor_extended_numeration = false;
media_library_display_date = true; media_library_display_date = true;
media_library_display_empty_tag = true; media_library_display_empty_tag = true;
@@ -953,6 +954,10 @@ void Configuration::Read()
if (!v.empty()) if (!v.empty())
media_lib_primary_tag = charToTagType(v[0]); media_lib_primary_tag = charToTagType(v[0]);
} }
else if (name == "media_library_sort_by_mtime")
{
media_library_sort_by_mtime = v == "yes";
}
else else
std::cout << "Unknown option: " << name << ", ignoring.\n"; std::cout << "Unknown option: " << name << ", ignoring.\n";
} }

View File

@@ -177,6 +177,7 @@ struct Configuration
bool new_design; bool new_design;
bool visualizer_use_wave; bool visualizer_use_wave;
bool visualizer_in_stereo; bool visualizer_in_stereo;
bool media_library_sort_by_mtime;
bool tag_editor_extended_numeration; bool tag_editor_extended_numeration;
bool media_library_display_date; bool media_library_display_date;
bool media_library_display_empty_tag; bool media_library_display_empty_tag;

View File

@@ -82,8 +82,9 @@ struct Song
static const char FormatEscapeCharacter = 1; static const char FormatEscapeCharacter = 1;
const char *getTag(mpd_tag_type type, unsigned idx = 0) const;
private: private:
const char *getTag(mpd_tag_type type, unsigned idx) const;
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,
const std::string &escape_chars) const; const std::string &escape_chars) const;