mpd: redesign Item and adjust browser

This commit is contained in:
Andrzej Rybczak
2014-11-02 00:22:02 +01:00
parent 30d57afcac
commit 0457af36fe
19 changed files with 783 additions and 557 deletions

View File

@@ -521,7 +521,7 @@ void JumpToParentDirectory::run()
{ {
if (myScreen == myBrowser) if (myScreen == myBrowser)
{ {
if (myBrowser->CurrentDir() != "/") if (!myBrowser->inRootDirectory())
{ {
myBrowser->main().reset(); myBrowser->main().reset();
myBrowser->enterPressed(); myBrowser->enterPressed();
@@ -669,47 +669,59 @@ bool DeleteBrowserItems::canBeRun() const
void DeleteBrowserItems::run() void DeleteBrowserItems::run()
{ {
auto get_name = [](const MPD::Item &item) -> std::string {
std::string name;
switch (item.type())
{
case MPD::Item::Type::Directory:
name = getBasename(item.directory().path());
break;
case MPD::Item::Type::Song:
name = item.song().getName();
break;
case MPD::Item::Type::Playlist:
name = getBasename(item.playlist().path());
break;
}
return name;
};
boost::format question; boost::format question;
if (hasSelected(myBrowser->main().begin(), myBrowser->main().end())) if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
question = boost::format("Delete selected items?"); question = boost::format("Delete selected items?");
else else
{ {
MPD::Item &item = myBrowser->main().current().value(); const auto &item = myBrowser->main().current().value();
std::string iname = item.type == MPD::Item::Type::Song ? item.song.getName() : item.name; // parent directories are not accepted (and they
question = boost::format("Delete %1% \"%2%\"?") // can't be selected, so in other cases it's fine).
% itemTypeToString(item.type) % wideShorten(iname, COLS-question.size()-10); if (myBrowser->isParentDirectory(item))
return;
const char msg[] = "Delete \"%1%\"?";
question = boost::format(msg) % wideShorten(
get_name(item), COLS-const_strlen(msg)-5
);
} }
confirmAction(question); confirmAction(question);
bool success = true;
auto list = getSelectedOrCurrent( auto items = getSelectedOrCurrent(
myBrowser->main().begin(), myBrowser->main().begin(),
myBrowser->main().end(), myBrowser->main().end(),
myBrowser->main().currentI() myBrowser->main().currentI()
); );
for (const auto &item : list) for (const auto &item : items)
{ {
const MPD::Item &i = item->value(); myBrowser->remove(item->value());
std::string iname = i.type == MPD::Item::Type::Song ? i.song.getName() : i.name; const char msg[] = "Deleted %1% \"%2%\"";
std::string errmsg; Statusbar::printf(msg,
if (myBrowser->deleteItem(i, errmsg)) itemTypeToString(item->value().type()),
{ wideShorten(get_name(item->value()), COLS-const_strlen(msg))
const char msg[] = "\"%1%\" deleted"; );
Statusbar::printf(msg, wideShorten(iname, COLS-const_strlen(msg)));
} }
else
{
Statusbar::print(errmsg);
success = false;
break;
}
}
if (success)
{
if (myBrowser->isLocal()) if (myBrowser->isLocal())
myBrowser->GetDirectory(myBrowser->CurrentDir()); myBrowser->getDirectory(myBrowser->currentDirectory());
else else
Mpd.UpdateDirectory(myBrowser->CurrentDir()); Mpd.UpdateDirectory(myBrowser->currentDirectory());
}
} }
bool DeleteStoredPlaylist::canBeRun() const bool DeleteStoredPlaylist::canBeRun() const
@@ -803,10 +815,6 @@ void SavePlaylist::run()
throw e; throw e;
} }
} }
if (!myBrowser->isLocal()
&& myBrowser->CurrentDir() == "/"
&& !myBrowser->main().empty())
myBrowser->GetDirectory(myBrowser->CurrentDir());
} }
void Stop::run() void Stop::run()
@@ -1142,7 +1150,7 @@ void TogglePlayingSongCentering::run()
void UpdateDatabase::run() void UpdateDatabase::run()
{ {
if (myScreen == myBrowser) if (myScreen == myBrowser)
Mpd.UpdateDirectory(myBrowser->CurrentDir()); Mpd.UpdateDirectory(myBrowser->currentDirectory());
# ifdef HAVE_TAGLIB_H # ifdef HAVE_TAGLIB_H
else if (myScreen == myTagEditor) else if (myScreen == myTagEditor)
Mpd.UpdateDirectory(myTagEditor->CurrentDir()); Mpd.UpdateDirectory(myTagEditor->CurrentDir());
@@ -1169,8 +1177,7 @@ void JumpToPlayingSong::run()
} }
else if (myScreen == myBrowser) else if (myScreen == myBrowser)
{ {
myBrowser->LocateSong(s); myBrowser->locateSong(s);
drawHeader();
} }
else if (myScreen == myLibrary) else if (myScreen == myLibrary)
{ {
@@ -1413,7 +1420,7 @@ bool EditDirectoryName::canBeRun() const
{ {
return ((myScreen == myBrowser return ((myScreen == myBrowser
&& !myBrowser->main().empty() && !myBrowser->main().empty()
&& myBrowser->main().current().value().type == MPD::Item::Type::Directory) && myBrowser->main().current().value().type() == MPD::Item::Type::Directory)
# ifdef HAVE_TAGLIB_H # ifdef HAVE_TAGLIB_H
|| (myScreen->activeWindow() == myTagEditor->Dirs || (myScreen->activeWindow() == myTagEditor->Dirs
&& !myTagEditor->Dirs->empty() && !myTagEditor->Dirs->empty()
@@ -1428,7 +1435,7 @@ void EditDirectoryName::run()
// FIXME: use boost::filesystem and better error reporting // FIXME: use boost::filesystem and better error reporting
if (myScreen == myBrowser) if (myScreen == myBrowser)
{ {
std::string old_dir = myBrowser->main().current().value().name, new_dir; std::string old_dir = myBrowser->main().current().value().directory().path(), new_dir;
{ {
Statusbar::ScopedLock lock; Statusbar::ScopedLock lock;
Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold; Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
@@ -1451,7 +1458,7 @@ void EditDirectoryName::run()
Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg))); Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
if (!myBrowser->isLocal()) if (!myBrowser->isLocal())
Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir)); Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
myBrowser->GetDirectory(myBrowser->CurrentDir()); myBrowser->getDirectory(myBrowser->currentDirectory());
} }
else else
{ {
@@ -1495,18 +1502,17 @@ bool EditPlaylistName::canBeRun() const
&& !myPlaylistEditor->Playlists.empty()) && !myPlaylistEditor->Playlists.empty())
|| (myScreen == myBrowser || (myScreen == myBrowser
&& !myBrowser->main().empty() && !myBrowser->main().empty()
&& myBrowser->main().current().value().type == MPD::Item::Type::Playlist); && myBrowser->main().current().value().type() == MPD::Item::Type::Playlist);
} }
void EditPlaylistName::run() void EditPlaylistName::run()
{ {
using Global::wFooter; using Global::wFooter;
// FIXME: support local browser more generally
std::string old_name, new_name; std::string old_name, new_name;
if (myScreen->isActiveWindow(myPlaylistEditor->Playlists)) if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
old_name = myPlaylistEditor->Playlists.current().value().path(); old_name = myPlaylistEditor->Playlists.current().value().path();
else else
old_name = myBrowser->main().current().value().name; old_name = myBrowser->main().current().value().playlist().path();
{ {
Statusbar::ScopedLock lock; Statusbar::ScopedLock lock;
Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold; Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
@@ -1517,8 +1523,6 @@ void EditPlaylistName::run()
Mpd.Rename(old_name, new_name); Mpd.Rename(old_name, new_name);
const char msg[] = "Playlist renamed to \"%1%\""; const char msg[] = "Playlist renamed to \"%1%\"";
Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg))); Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
if (!myBrowser->isLocal())
myBrowser->GetDirectory("/");
} }
} }
@@ -1540,7 +1544,7 @@ bool JumpToBrowser::canBeRun() const
void JumpToBrowser::run() void JumpToBrowser::run()
{ {
auto s = currentSong(myScreen); auto s = currentSong(myScreen);
myBrowser->LocateSong(*s); myBrowser->locateSong(*s);
} }
bool JumpToMediaLibrary::canBeRun() const bool JumpToMediaLibrary::canBeRun() const
@@ -1557,12 +1561,12 @@ void JumpToMediaLibrary::run()
bool JumpToPlaylistEditor::canBeRun() const bool JumpToPlaylistEditor::canBeRun() const
{ {
return myScreen == myBrowser return myScreen == myBrowser
&& myBrowser->main().current().value().type == MPD::Item::Type::Playlist; && myBrowser->main().current().value().type() == MPD::Item::Type::Playlist;
} }
void JumpToPlaylistEditor::run() void JumpToPlaylistEditor::run()
{ {
myPlaylistEditor->Locate(myBrowser->main().current().value().name); myPlaylistEditor->Locate(myBrowser->main().current().value().playlist());
} }
void ToggleScreenLock::run() void ToggleScreenLock::run()
@@ -2100,9 +2104,12 @@ void ToggleBrowserSortMode::run()
} }
withUnfilteredMenuReapplyFilter(myBrowser->main(), [] { withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
if (Config.browser_sort_mode != SortMode::NoOp) if (Config.browser_sort_mode != SortMode::NoOp)
std::sort(myBrowser->main().begin()+(myBrowser->CurrentDir() != "/"), myBrowser->main().end(), {
size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode) LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
); );
}
}); });
} }
@@ -2400,7 +2407,7 @@ bool ChangeBrowseMode::canBeRun() const
void ChangeBrowseMode::run() void ChangeBrowseMode::run()
{ {
myBrowser->ChangeBrowseMode(); myBrowser->changeBrowseMode();
} }
bool ShowSearchEngine::canBeRun() const bool ShowSearchEngine::canBeRun() const

View File

@@ -18,10 +18,12 @@
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/ ***************************************************************************/
#include <algorithm>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/locale/conversion.hpp> #include <boost/locale/conversion.hpp>
#include <algorithm> #include <time.h>
#include "browser.h" #include "browser.h"
#include "charset.h" #include "charset.h"
@@ -50,17 +52,27 @@ namespace fs = boost::filesystem;
Browser *myBrowser; Browser *myBrowser;
namespace {// namespace {
std::set<std::string> SupportedExtensions; std::set<std::string> lm_supported_extensions;
bool hasSupportedExtension(const std::string &file);
std::string ItemToString(const MPD::Item &item); std::string realPath(bool local_browser, std::string path);
bool BrowserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter); bool isStringParentDirectory(const std::string &directory);
bool isItemParentDirectory(const MPD::Item &item);
bool isRootDirectory(const std::string &directory);
bool isHidden(const fs::directory_iterator &entry);
bool hasSupportedExtension(const fs::directory_entry &entry);
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags);
void getLocalDirectory(MPD::ItemList &items, const std::string &directory);
void getLocalDirectoryRecursively(MPD::SongList &songs, const std::string &directory);
void clearDirectory(const std::string &directory);
std::string itemToString(const MPD::Item &item);
bool browserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter);
} }
Browser::Browser() : itsBrowseLocally(0), itsScrollBeginning(0), itsBrowsedDir("/") Browser::Browser() : m_local_browser(false), m_scroll_beginning(0), m_current_directory("/")
{ {
w = NC::Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.browser_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border::None); w = NC::Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.browser_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border::None);
w.setHighlightColor(Config.main_highlight_color); w.setHighlightColor(Config.main_highlight_color);
@@ -97,7 +109,7 @@ void Browser::switchTo()
SwitchTo::execute(this); SwitchTo::execute(this);
if (w.empty()) if (w.empty())
GetDirectory(itsBrowsedDir); getDirectory(m_current_directory);
else else
markSongsInPlaylist(proxySongList()); markSongsInPlaylist(proxySongList());
@@ -107,7 +119,7 @@ void Browser::switchTo()
std::wstring Browser::title() std::wstring Browser::title()
{ {
std::wstring result = L"Browse: "; std::wstring result = L"Browse: ";
result += Scroller(ToWString(itsBrowsedDir), itsScrollBeginning, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length())); result += Scroller(ToWString(m_current_directory), m_scroll_beginning, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
return result; return result;
} }
@@ -117,31 +129,29 @@ void Browser::enterPressed()
return; return;
const MPD::Item &item = w.current().value(); const MPD::Item &item = w.current().value();
switch (item.type) switch (item.type())
{ {
case MPD::Item::Type::Directory: case MPD::Item::Type::Directory:
{ {
if (isParentDirectory(item)) getDirectory(item.directory().path());
GetDirectory(getParentDirectory(itsBrowsedDir), itsBrowsedDir);
else
GetDirectory(item.name, itsBrowsedDir);
drawHeader(); drawHeader();
break; break;
} }
case MPD::Item::Type::Song: case MPD::Item::Type::Song:
{ {
addSongToPlaylist(item.song, true, -1); addSongToPlaylist(item.song(), true, -1);
break; break;
} }
case MPD::Item::Type::Playlist: case MPD::Item::Type::Playlist:
{ {
MPD::SongList list( MPD::SongList list(
std::make_move_iterator(Mpd.GetPlaylistContentNoInfo(item.name)), std::make_move_iterator(Mpd.GetPlaylistContentNoInfo(item.playlist().path())),
std::make_move_iterator(MPD::SongIterator()) std::make_move_iterator(MPD::SongIterator())
); );
// TODO: ask on failure if we want to continue
bool success = addSongsToPlaylist(list.begin(), list.end(), true, -1); bool success = addSongsToPlaylist(list.begin(), list.end(), true, -1);
Statusbar::printf("Playlist \"%1%\" loaded%2%", Statusbar::printf("Playlist \"%1%\" loaded%2%",
item.name, withErrors(success) item.playlist().path(), withErrors(success)
); );
} }
} }
@@ -152,60 +162,47 @@ void Browser::spacePressed()
if (w.empty()) if (w.empty())
return; return;
size_t i = itsBrowsedDir != "/" ? 1 : 0; size_t i = inRootDirectory() ? 0 : 1;
if (Config.space_selects && w.choice() >= i) if (Config.space_selects && w.choice() >= i)
{ {
i = w.choice(); i = w.choice();
w.at(i).setSelected(!w.at(i).isSelected()); w[i].setSelected(!w[i].isSelected());
w.scroll(NC::Scroll::Down); w.scroll(NC::Scroll::Down);
return; return;
} }
const MPD::Item &item = w.current().value(); const MPD::Item &item = w.current().value();
// ignore parent directory
if (isParentDirectory(item)) if (isParentDirectory(item))
return; return;
switch (item.type) switch (item.type())
{ {
case MPD::Item::Type::Directory: case MPD::Item::Type::Directory:
{ {
bool success; bool success = true;
# ifndef WIN32 if (m_local_browser)
if (isLocal())
{ {
MPD::SongList list; MPD::SongList songs;
MPD::ItemList items; getLocalDirectoryRecursively(songs, item.directory().path());
Statusbar::printf("Scanning directory \"%1%\"...", item.name); success = addSongsToPlaylist(songs.begin(), songs.end(), false, -1);
myBrowser->GetLocalDirectory(items, item.name, 1);
list.reserve(items.size());
for (MPD::ItemList::const_iterator it = items.begin(); it != items.end(); ++it)
list.push_back(it->song);
success = addSongsToPlaylist(list.begin(), list.end(), false, -1);
} }
else else
# endif // !WIN32 Mpd.Add(item.directory().path());
{
Mpd.Add(item.name);
success = true;
}
Statusbar::printf("Directory \"%1%\" added%2%", Statusbar::printf("Directory \"%1%\" added%2%",
item.name, withErrors(success) item.directory().path(), withErrors(success)
); );
break; break;
} }
case MPD::Item::Type::Song: case MPD::Item::Type::Song:
{ addSongToPlaylist(item.song(), false);
addSongToPlaylist(item.song, false);
break; break;
}
case MPD::Item::Type::Playlist: case MPD::Item::Type::Playlist:
{ Mpd.LoadPlaylist(item.playlist().path());
Mpd.LoadPlaylist(item.name); Statusbar::printf("Playlist \"%1%\" loaded", item.playlist().path());
Statusbar::printf("Playlist \"%1%\" loaded", item.name);
break; break;
} }
}
w.scroll(NC::Scroll::Down); w.scroll(NC::Scroll::Down);
} }
@@ -216,12 +213,12 @@ void Browser::mouseButtonPressed(MEVENT me)
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)) if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
{ {
w.Goto(me.y); w.Goto(me.y);
switch (w.current().value().type) switch (w.current().value().type())
{ {
case MPD::Item::Type::Directory: case MPD::Item::Type::Directory:
if (me.bstate & BUTTON1_PRESSED) if (me.bstate & BUTTON1_PRESSED)
{ {
GetDirectory(w.current().value().name); getDirectory(w.current().value().directory().path());
drawHeader(); drawHeader();
} }
else else
@@ -273,7 +270,7 @@ void Browser::applyFilter(const std::string &filter)
try try
{ {
w.showAll(); w.showAll();
auto fun = boost::bind(BrowserEntryMatcher, _1, _2, true); auto fun = boost::bind(browserEntryMatcher, _1, _2, true);
auto rx = RegexFilter<MPD::Item>( auto rx = RegexFilter<MPD::Item>(
boost::regex(filter, Config.regex_type), fun); boost::regex(filter, Config.regex_type), fun);
w.filter(w.begin(), w.end(), rx); w.filter(w.begin(), w.end(), rx);
@@ -297,7 +294,7 @@ bool Browser::search(const std::string &constraint)
} }
try try
{ {
auto fun = boost::bind(BrowserEntryMatcher, _1, _2, false); auto fun = boost::bind(browserEntryMatcher, _1, _2, false);
auto rx = RegexFilter<MPD::Item>( auto rx = RegexFilter<MPD::Item>(
boost::regex(constraint, Config.regex_type), fun); boost::regex(constraint, Config.regex_type), fun);
return w.search(w.begin(), w.end(), rx); return w.search(w.begin(), w.end(), rx);
@@ -324,8 +321,8 @@ ProxySongList Browser::proxySongList()
{ {
return ProxySongList(w, [](NC::Menu<MPD::Item>::Item &item) -> MPD::Song * { return ProxySongList(w, [](NC::Menu<MPD::Item>::Item &item) -> MPD::Song * {
MPD::Song *ptr = 0; MPD::Song *ptr = 0;
if (item.value().type == MPD::Item::Type::Song) if (item.value().type() == MPD::Item::Type::Song)
ptr = &item.value().song; ptr = const_cast<MPD::Song *>(&item.value().song());
return ptr; return ptr;
}); });
} }
@@ -337,201 +334,167 @@ bool Browser::allowsSelection()
void Browser::reverseSelection() void Browser::reverseSelection()
{ {
reverseSelectionHelper(w.begin()+(itsBrowsedDir == "/" ? 0 : 1), w.end()); size_t offset = inRootDirectory() ? 0 : 1;
reverseSelectionHelper(w.begin()+offset, w.end());
} }
MPD::SongList Browser::getSelectedSongs() MPD::SongList Browser::getSelectedSongs()
{ {
MPD::SongList result; MPD::SongList songs;
auto item_handler = [this, &result](const MPD::Item &item) { auto item_handler = [this, &songs](const MPD::Item &item) {
if (item.type == MPD::Item::Type::Directory) switch (item.type())
{ {
# ifndef WIN32 case MPD::Item::Type::Directory:
if (isLocal()) if (m_local_browser)
{ {
MPD::ItemList list; MPD::ItemList items;
GetLocalDirectory(list, item.name, true); getLocalDirectoryRecursively(songs, item.directory().path());
for (auto it = list.begin(); it != list.end(); ++it)
result.push_back(it->song);
} }
else else
# endif // !WIN32
{ {
Mpd.GetDirectoryRecursive(item.name, vectorMoveInserter(result)); MPD::ItemIterator it = Mpd.GetDirectoryRecursive(item.directory().path()), end;
for (; it != end; ++it)
if (it->type() == MPD::Item::Type::Song)
songs.push_back(std::move(it->song()));
} }
} break;
else if (item.type == MPD::Item::Type::Song) case MPD::Item::Type::Song:
result.push_back(item.song); songs.push_back(item.song());
else if (item.type == MPD::Item::Type::Playlist) break;
{ case MPD::Item::Type::Playlist:
std::copy( std::copy(
std::make_move_iterator(Mpd.GetPlaylistContent(item.name)), std::make_move_iterator(Mpd.GetPlaylistContent(item.playlist().path())),
std::make_move_iterator(MPD::SongIterator()), std::make_move_iterator(MPD::SongIterator()),
std::back_inserter(result) std::back_inserter(songs)
); );
break;
} }
}; };
for (auto it = w.begin(); it != w.end(); ++it) for (const auto &item : w)
if (it->isSelected()) if (item.isSelected())
item_handler(it->value()); item_handler(item.value());
// if no item is selected, add current one // if no item is selected, add current one
if (result.empty() && !w.empty()) if (songs.empty() && !w.empty())
item_handler(w.current().value()); item_handler(w.current().value());
return result; return songs;
} }
void Browser::fetchSupportedExtensions() /***********************************************************************/
bool Browser::inRootDirectory()
{ {
SupportedExtensions.clear(); return isRootDirectory(m_current_directory);
Mpd.GetSupportedExtensions(SupportedExtensions);
} }
void Browser::LocateSong(const MPD::Song &s) bool Browser::isParentDirectory(const MPD::Item &item)
{
return isItemParentDirectory(item);
}
const std::string& Browser::currentDirectory()
{
return m_current_directory;
}
void Browser::locateSong(const MPD::Song &s)
{ {
if (s.getDirectory().empty()) if (s.getDirectory().empty())
return; throw std::runtime_error("Song's directory is empty");
itsBrowseLocally = !s.isFromDatabase(); m_local_browser = !s.isFromDatabase();
if (myScreen != this) if (myScreen != this)
switchTo(); switchTo();
if (itsBrowsedDir != s.getDirectory()) // change to relevant directory
GetDirectory(s.getDirectory()); if (m_current_directory != s.getDirectory())
for (size_t i = 0; i < w.size(); ++i)
{ {
if (w[i].value().type == MPD::Item::Type::Song && s == w[i].value().song) getDirectory(s.getDirectory());
{
w.highlight(i);
break;
}
}
drawHeader(); drawHeader();
}
// highlight the item
auto begin = w.beginV(), end = w.endV();
auto it = std::find(begin, end, MPD::Item(s));
if (it != end)
w.highlight(it-begin);
} }
void Browser::GetDirectory(std::string dir, std::string subdir) void Browser::getDirectory(std::string directory)
{ {
if (dir.empty()) m_scroll_beginning = 0;
dir = "/";
int highlightme = -1;
itsScrollBeginning = 0;
if (itsBrowsedDir != dir)
w.reset();
itsBrowsedDir = dir;
w.clear(); w.clear();
if (dir != "/") // reset the position if we change directories
if (m_current_directory != directory)
w.reset();
// check if it's a parent directory
if (isStringParentDirectory(directory))
{ {
MPD::Item parent; directory.resize(directory.length()-3);
parent.name = ".."; directory = getParentDirectory(directory);
parent.type = MPD::Item::Type::Directory; }
w.addItem(parent); // when we go down to root, it can be empty
if (directory.empty())
directory = "/";
MPD::ItemList items;
if (m_local_browser)
getLocalDirectory(items, directory);
else
{
std::copy(
std::make_move_iterator(Mpd.GetDirectory(directory)),
std::make_move_iterator(MPD::ItemIterator()),
std::back_inserter(items)
);
} }
MPD::ItemList list; // sort items
# ifndef WIN32 if (Config.browser_sort_mode != SortMode::NoOp)
if (isLocal()) {
GetLocalDirectory(list, itsBrowsedDir, false); std::sort(items.begin(), items.end(),
else
Mpd.GetDirectory(dir, vectorMoveInserter(list));
# else
list = Mpd.GetDirectory(dir);
# endif // !WIN32
if (Config.browser_sort_mode != SortMode::NoOp && !isLocal()) // local directory is already sorted
std::sort(list.begin(), list.end(),
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode) LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
); );
}
for (MPD::ItemList::iterator it = list.begin(); it != list.end(); ++it) // if the requested directory is not root, add parent directory
if (!isRootDirectory(directory))
{ {
switch (it->type) // make it so that display function doesn't have to handle special cases
w.addItem(MPD::Directory(directory + "/.."));
}
for (const auto &item : items)
{
switch (item.type())
{ {
case MPD::Item::Type::Playlist: case MPD::Item::Type::Playlist:
{ {
w.addItem(*it); w.addItem(std::move(item));
break; break;
} }
case MPD::Item::Type::Directory: case MPD::Item::Type::Directory:
{ {
if (it->name == subdir) bool is_current = item.directory().path() == m_current_directory;
highlightme = w.size(); w.addItem(std::move(item));
w.addItem(*it); if (is_current)
w.highlight(w.size()-1);
break; break;
} }
case MPD::Item::Type::Song: case MPD::Item::Type::Song:
{ {
w.addItem(*it, myPlaylist->checkForSong(it->song)); bool is_bold = myPlaylist->checkForSong(item.song());
w.addItem(std::move(item), is_bold);
break; break;
} }
} }
} }
if (highlightme >= 0) m_current_directory = directory;
w.highlight(highlightme);
} }
#ifndef WIN32 void Browser::changeBrowseMode()
void Browser::GetLocalDirectory(MPD::ItemList &v, const std::string &directory, bool recursively) const
{
size_t start_size = v.size();
fs::path dir(directory);
std::for_each(fs::directory_iterator(dir), fs::directory_iterator(), [&](fs::directory_entry &e) {
if (!Config.local_browser_show_hidden_files && e.path().filename().native()[0] == '.')
return;
MPD::Item item;
if (fs::is_directory(e))
{
if (recursively)
{
GetLocalDirectory(v, e.path().native(), true);
start_size = v.size();
}
else
{
item.type = MPD::Item::Type::Directory;
item.name = e.path().native();
v.push_back(item);
}
}
else if (hasSupportedExtension(e.path().native()))
{
item.type = MPD::Item::Type::Song;
mpd_pair file_pair = { "file", e.path().native().c_str() };
item.song = mpd_song_begin(&file_pair);
// FIXME no tag reading for now
/*
# ifdef HAVE_TAGLIB_H
if (!recursively)
{
s->setMTime(fs::last_write_time(e.path()));
Tags::read(*s);
}
# endif // HAVE_TAGLIB_H
*/
v.push_back(item);
}
});
if (Config.browser_sort_mode != SortMode::NoOp)
std::sort(v.begin()+start_size, v.end(),
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
);
}
void Browser::ClearDirectory(const std::string &path) const
{
fs::path dir(path);
std::for_each(fs::directory_iterator(dir), fs::directory_iterator(), [&](fs::directory_entry &e) {
if (!fs::is_symlink(e) && fs::is_directory(e))
ClearDirectory(e.path().native());
const char msg[] = "Deleting \"%1%\"...";
Statusbar::printf(msg, wideShorten(e.path().native(), COLS-const_strlen(msg)));
fs::remove(e.path());
});
}
void Browser::ChangeBrowseMode()
{ {
if (Mpd.GetHostname()[0] != '/') if (Mpd.GetHostname()[0] != '/')
{ {
@@ -539,120 +502,216 @@ void Browser::ChangeBrowseMode()
return; return;
} }
itsBrowseLocally = !itsBrowseLocally; m_local_browser = !m_local_browser;
Statusbar::printf("Browse mode: %1%", Statusbar::printf("Browse mode: %1%",
itsBrowseLocally ? "local filesystem" : "MPD database" m_local_browser ? "local filesystem" : "MPD database"
); );
if (itsBrowseLocally) if (m_local_browser)
{ {
itsBrowsedDir = "~"; m_current_directory = "~";
expand_home(itsBrowsedDir); expand_home(m_current_directory);
if (*itsBrowsedDir.rbegin() == '/')
itsBrowsedDir.resize(itsBrowsedDir.length()-1);
} }
else else
itsBrowsedDir = "/"; m_current_directory = "/";
w.reset(); w.reset();
GetDirectory(itsBrowsedDir); getDirectory(m_current_directory);
drawHeader(); drawHeader();
} }
bool Browser::deleteItem(const MPD::Item &item, std::string &errmsg) void Browser::remove(const MPD::Item &item)
{ {
if (!Config.allow_for_physical_item_deletion) if (!Config.allow_for_physical_item_deletion)
FatalError("Browser::deleteItem invoked with allow_for_physical_item_deletion = false"); throw std::runtime_error("physical deletion is forbidden");
if (isParentDirectory((item))) if (isParentDirectory((item)))
FatalError("Parent directory passed to Browser::deleteItem"); throw std::runtime_error("deletion of parent directory is forbidden");
// playlist created by mpd
if (!isLocal() && item.type == MPD::Item::Type::Playlist && CurrentDir() == "/")
{
try
{
Mpd.DeletePlaylist(item.name);
return true;
}
catch (MPD::ServerError &e)
{
// if there is no such mpd playlist, we assume it's users's playlist.
if (e.code() != MPD_SERVER_ERROR_NO_EXIST)
throw;
}
}
std::string path; std::string path;
if (!isLocal()) switch (item.type())
path = Config.mpd_music_dir;
path += item.type == MPD::Item::Type::Song ? item.song.getURI() : item.name;
bool rv;
try
{
if (item.type == MPD::Item::Type::Directory)
ClearDirectory(path);
if (!boost::filesystem::exists(path))
{
errmsg = "No such item: " + path;
rv = false;
}
else
{
boost::filesystem::remove(path);
rv = true;
}
}
catch (boost::filesystem::filesystem_error &err)
{
errmsg = err.what();
rv = false;
}
return rv;
}
#endif // !WIN32
namespace {//
bool hasSupportedExtension(const std::string &file)
{
size_t last_dot = file.rfind(".");
if (last_dot > file.length())
return false;
std::string ext = boost::locale::to_lower(file.substr(last_dot+1));
return SupportedExtensions.find(ext) != SupportedExtensions.end();
}
std::string ItemToString(const MPD::Item &item)
{
std::string result;
switch (item.type)
{ {
case MPD::Item::Type::Directory: case MPD::Item::Type::Directory:
result = "[" + getBasename(item.name) + "]"; path = realPath(m_local_browser, item.directory().path());
clearDirectory(path);
fs::remove(path);
break;
case MPD::Item::Type::Song:
path = realPath(m_local_browser, item.song().getURI());
fs::remove(path);
break;
case MPD::Item::Type::Playlist:
path = item.playlist().path();
try {
Mpd.DeletePlaylist(path);
} catch (MPD::ServerError &e) {
// if there is no such mpd playlist, it's a local one
if (e.code() == MPD_SERVER_ERROR_NO_EXIST)
{
path = realPath(m_local_browser, std::move(path));
fs::remove(path);
}
else
throw;
}
break;
}
}
/***********************************************************************/
void Browser::fetchSupportedExtensions()
{
lm_supported_extensions.clear();
Mpd.GetSupportedExtensions([&](std::string extension) {
lm_supported_extensions.insert("." + std::move(extension));
});
}
/***********************************************************************/
namespace {
std::string realPath(bool local_browser, std::string path)
{
if (!local_browser)
path = Config.mpd_music_dir + path;
return path;
}
bool isStringParentDirectory(const std::string &directory)
{
return boost::algorithm::ends_with(directory, "/..");
}
bool isItemParentDirectory(const MPD::Item &item)
{
return item.type() == MPD::Item::Type::Directory
&& isStringParentDirectory(item.directory().path());
}
bool isRootDirectory(const std::string &directory)
{
return directory == "/";
}
bool isHidden(const fs::directory_iterator &entry)
{
return entry->path().filename().native()[0] == '.';
}
bool hasSupportedExtension(const fs::directory_entry &entry)
{
return lm_supported_extensions.find(entry.path().extension().native())
!= lm_supported_extensions.end();
}
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags)
{
mpd_pair pair = { "file", entry.path().c_str() };
mpd_song *s = mpd_song_begin(&pair);
if (s == nullptr)
throw std::runtime_error("invalid path: " + entry.path().native());
# ifdef HAVE_TAGLIB_H
if (read_tags)
{
Tags::setAttribute(s, "Last-Modified",
timeFormat("%Y-%m-%dT%H:%M:%SZ", fs::last_write_time(entry.path()))
);
// read tags
Tags::read(s);
}
# endif // HAVE_TAGLIB_H
return s;
}
void getLocalDirectory(MPD::ItemList &items, const std::string &directory)
{
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
{
if (!Config.local_browser_show_hidden_files && isHidden(entry))
continue;
if (fs::is_directory(*entry))
{
items.push_back(MPD::Directory(
entry->path().native(),
fs::last_write_time(entry->path())
));
}
else if (hasSupportedExtension(*entry))
items.push_back(getLocalSong(*entry, true));
}
}
void getLocalDirectoryRecursively(MPD::SongList &songs, const std::string &directory)
{
size_t sort_offset = songs.size();
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
{
if (!Config.local_browser_show_hidden_files && isHidden(entry))
continue;
if (fs::is_directory(*entry))
{
getLocalDirectoryRecursively(songs, entry->path().native());
sort_offset = songs.size();
}
else if (hasSupportedExtension(*entry))
songs.push_back(getLocalSong(*entry, false));
};
if (Config.browser_sort_mode != SortMode::NoOp)
{
std::sort(songs.begin()+sort_offset, songs.end(),
LocaleBasedSorting(std::locale(), Config.ignore_leading_the)
);
}
}
void clearDirectory(const std::string &directory)
{
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
{
if (!fs::is_symlink(*entry) && fs::is_directory(*entry))
clearDirectory(entry->path().native());
const char msg[] = "Deleting \"%1%\"...";
Statusbar::printf(msg, wideShorten(entry->path().native(), COLS-const_strlen(msg)));
fs::remove(entry->path());
};
}
/***********************************************************************/
std::string itemToString(const MPD::Item &item)
{
std::string result;
switch (item.type())
{
case MPD::Item::Type::Directory:
result = "[" + getBasename(item.directory().path()) + "]";
break; break;
case MPD::Item::Type::Song: case MPD::Item::Type::Song:
switch (Config.browser_display_mode) switch (Config.browser_display_mode)
{ {
case DisplayMode::Classic: case DisplayMode::Classic:
result = item.song.toString(Config.song_list_format_dollar_free, Config.tags_separator); result = item.song().toString(Config.song_list_format_dollar_free, Config.tags_separator);
break; break;
case DisplayMode::Columns: case DisplayMode::Columns:
result = item.song.toString(Config.song_in_columns_to_string_format, Config.tags_separator); result = item.song().toString(Config.song_in_columns_to_string_format, Config.tags_separator);
break; break;
} }
break; break;
case MPD::Item::Type::Playlist: case MPD::Item::Type::Playlist:
result = Config.browser_playlist_prefix.str() + getBasename(item.name); result = Config.browser_playlist_prefix.str();
result += getBasename(item.playlist().path());
break; break;
} }
return result; return result;
} }
bool BrowserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter) bool browserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter)
{ {
if (Browser::isParentDirectory(item)) if (isItemParentDirectory(item))
return filter; return filter;
return boost::regex_search(ItemToString(item), rx); return boost::regex_search(itemToString(item), rx);
} }
} }

View File

@@ -63,31 +63,25 @@ struct Browser: Screen<NC::Menu<MPD::Item>>, Filterable, HasSongs, Searchable, T
virtual MPD::SongList getSelectedSongs() OVERRIDE; virtual MPD::SongList getSelectedSongs() OVERRIDE;
// private members // private members
const std::string &CurrentDir() { return itsBrowsedDir; } bool inRootDirectory();
bool isParentDirectory(const MPD::Item &item);
const std::string &currentDirectory();
void fetchSupportedExtensions(); bool isLocal() { return m_local_browser; }
void locateSong(const MPD::Song &s);
void getDirectory(std::string directory);
void changeBrowseMode();
void remove(const MPD::Item &item);
bool isLocal() { return itsBrowseLocally; } static void fetchSupportedExtensions();
void LocateSong(const MPD::Song &);
void GetDirectory(std::string, std::string = "/");
# ifndef WIN32
void GetLocalDirectory(MPD::ItemList &, const std::string &, bool) const;
void ClearDirectory(const std::string &) const;
void ChangeBrowseMode();
bool deleteItem(const MPD::Item &, std::string &errmsg);
# endif // !WIN32
static bool isParentDirectory(const MPD::Item &item) {
return item.type == MPD::Item::Type::Directory && item.name == "..";
}
protected: protected:
virtual bool isLockable() OVERRIDE { return true; } virtual bool isLockable() OVERRIDE { return true; }
private: private:
bool itsBrowseLocally; bool m_local_browser;
size_t itsScrollBeginning; size_t m_scroll_beginning;
std::string itsBrowsedDir; std::string m_current_directory;
}; };
extern Browser *myBrowser; extern Browser *myBrowser;

View File

@@ -384,27 +384,27 @@ void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
void Display::Items(NC::Menu<MPD::Item> &menu, const ProxySongList &pl) void Display::Items(NC::Menu<MPD::Item> &menu, const ProxySongList &pl)
{ {
const MPD::Item &item = menu.drawn()->value(); const MPD::Item &item = menu.drawn()->value();
switch (item.type) switch (item.type())
{ {
case MPD::Item::Type::Directory: case MPD::Item::Type::Directory:
menu << "[" menu << "["
<< Charset::utf8ToLocale(getBasename(item.name)) << Charset::utf8ToLocale(getBasename(item.directory().path()))
<< "]"; << "]";
break; break;
case MPD::Item::Type::Song: case MPD::Item::Type::Song:
switch (Config.browser_display_mode) switch (Config.browser_display_mode)
{ {
case DisplayMode::Classic: case DisplayMode::Classic:
showSongs(menu, item.song, pl, Config.song_list_format); showSongs(menu, item.song(), pl, Config.song_list_format);
break; break;
case DisplayMode::Columns: case DisplayMode::Columns:
showSongsInColumns(menu, item.song, pl); showSongsInColumns(menu, item.song(), pl);
break; break;
} }
break; break;
case MPD::Item::Type::Playlist: case MPD::Item::Type::Playlist:
menu << Config.browser_playlist_prefix menu << Config.browser_playlist_prefix
<< Charset::utf8ToLocale(getBasename(item.name)); << Charset::utf8ToLocale(getBasename(item.playlist().path()));
break; break;
} }
} }

View File

@@ -19,6 +19,7 @@
***************************************************************************/ ***************************************************************************/
#include <algorithm> #include <algorithm>
#include <time.h>
#include "helpers.h" #include "helpers.h"
#include "playlist.h" #include "playlist.h"
@@ -63,6 +64,15 @@ bool addSongToPlaylist(const MPD::Song &s, bool play, int position)
return result; return result;
} }
std::string timeFormat(const char *format, time_t t)
{
char result[32];
tm tinfo;
localtime_r(&t, &tinfo);
strftime(result, sizeof(result), format, &tinfo);
return result;
}
std::string Timestamp(time_t t) std::string Timestamp(time_t t)
{ {
char result[32]; char result[32];

View File

@@ -550,6 +550,8 @@ inline const char *withErrors(bool success)
bool addSongToPlaylist(const MPD::Song &s, bool play, int position = -1); bool addSongToPlaylist(const MPD::Song &s, bool play, int position = -1);
std::string timeFormat(const char *format, time_t t);
std::string Timestamp(time_t t); std::string Timestamp(time_t t);
void markSongsInPlaylist(ProxySongList pl); void markSongsInPlaylist(ProxySongList pl);

View File

@@ -259,8 +259,13 @@ void MediaLibrary::update()
Albums.clearSearchResults(); Albums.clearSearchResults();
m_albums_update_request = false; m_albums_update_request = false;
std::map<std::tuple<std::string, std::string, std::string>, time_t> albums; std::map<std::tuple<std::string, std::string, std::string>, time_t> albums;
Mpd.GetDirectoryRecursive("/", [&albums](MPD::Song s) { MPD::ItemIterator item = Mpd.GetDirectoryRecursive("/"), end;
for (; item != end; ++item)
{
if (item->type() != MPD::Item::Type::Song)
continue;
unsigned idx = 0; unsigned idx = 0;
const MPD::Song &s = item->song();
std::string tag = s.get(Config.media_lib_primary_tag, idx); std::string tag = s.get(Config.media_lib_primary_tag, idx);
do do
{ {
@@ -272,7 +277,7 @@ void MediaLibrary::update()
it->second = s.getMTime(); it->second = s.getMTime();
} }
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty()); while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
}); }
withUnfilteredMenuReapplyFilter(Albums, [this, &albums]() { withUnfilteredMenuReapplyFilter(Albums, [this, &albums]() {
size_t idx = 0; size_t idx = 0;
for (auto it = albums.begin(); it != albums.end(); ++it, ++idx) for (auto it = albums.begin(); it != albums.end(); ++it, ++idx)
@@ -303,8 +308,13 @@ void MediaLibrary::update()
std::map<std::string, time_t> tags; std::map<std::string, time_t> tags;
if (Config.media_library_sort_by_mtime) if (Config.media_library_sort_by_mtime)
{ {
Mpd.GetDirectoryRecursive("/", [&tags](MPD::Song s) { MPD::ItemIterator item = Mpd.GetDirectoryRecursive("/"), end;
for (; item != end; ++item)
{
if (item->type() != MPD::Item::Type::Song)
continue;
unsigned idx = 0; unsigned idx = 0;
const MPD::Song &s = item->song();
std::string tag = s.get(Config.media_lib_primary_tag, idx); std::string tag = s.get(Config.media_lib_primary_tag, idx);
do do
{ {
@@ -315,7 +325,7 @@ void MediaLibrary::update()
it->second = std::max(it->second, s.getMTime()); it->second = std::max(it->second, s.getMTime());
} }
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty()); while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
}); }
} }
else else
{ {

View File

@@ -336,13 +336,13 @@ SongIterator Connection::GetPlaylistContentNoInfo(const std::string &path)
return result; return result;
} }
void Connection::GetSupportedExtensions(std::set<std::string> &acc) void Connection::GetSupportedExtensions(StringConsumer f)
{ {
prechecksNoCommandsList(); prechecksNoCommandsList();
mpd_send_command(m_connection.get(), "decoders", NULL); mpd_send_command(m_connection.get(), "decoders", NULL);
while (mpd_pair *pair = mpd_recv_pair_named(m_connection.get(), "suffix")) while (mpd_pair *pair = mpd_recv_pair_named(m_connection.get(), "suffix"))
{ {
acc.insert(pair->value); f(pair->value);
mpd_return_pair(m_connection.get(), pair); mpd_return_pair(m_connection.get(), pair);
} }
mpd_response_finish(m_connection.get()); mpd_response_finish(m_connection.get());
@@ -677,48 +677,20 @@ void Connection::CommitSearchTags(StringConsumer f)
checkErrors(); checkErrors();
} }
void Connection::GetDirectory(const std::string &directory, ItemConsumer f) ItemIterator Connection::GetDirectory(const std::string &directory)
{ {
prechecksNoCommandsList(); prechecksNoCommandsList();
mpd_send_list_meta(m_connection.get(), directory.c_str()); mpd_send_list_meta(m_connection.get(), directory.c_str());
while (mpd_entity *item = mpd_recv_entity(m_connection.get()))
{
Item it;
switch (mpd_entity_get_type(item))
{
case MPD_ENTITY_TYPE_DIRECTORY:
it.name = mpd_directory_get_path(mpd_entity_get_directory(item));
it.type = MPD::Item::Type::Directory;
break;
case MPD_ENTITY_TYPE_SONG:
it.song = Song(mpd_song_dup(mpd_entity_get_song(item)));
it.type = MPD::Item::Type::Song;
break;
case MPD_ENTITY_TYPE_PLAYLIST:
it.name = mpd_playlist_get_path(mpd_entity_get_playlist(item));
it.type = MPD::Item::Type::Playlist;
break;
default:
assert(false);
}
mpd_entity_free(item);
f(std::move(it));
}
mpd_response_finish(m_connection.get());
checkErrors(); checkErrors();
return ItemIterator(m_connection.get(), mpd_recv_entity);
} }
void Connection::GetDirectoryRecursive(const std::string &directory, SongConsumer f) ItemIterator Connection::GetDirectoryRecursive(const std::string &directory)
{ {
prechecksNoCommandsList(); prechecksNoCommandsList();
mpd_send_list_all_meta(m_connection.get(), directory.c_str()); mpd_send_list_all_meta(m_connection.get(), directory.c_str());
while (mpd_entity *e = mpd_recv_entity(m_connection.get())) {
if (mpd_entity_get_type(e) == MPD_ENTITY_TYPE_SONG)
f(Song(mpd_song_dup(mpd_entity_get_song(e))));
mpd_entity_free(e);
}
mpd_response_finish(m_connection.get());
checkErrors(); checkErrors();
return ItemIterator(m_connection.get(), mpd_recv_entity);
} }
void Connection::GetDirectories(const std::string &directory, StringConsumer f) void Connection::GetDirectories(const std::string &directory, StringConsumer f)

View File

@@ -120,72 +120,172 @@ private:
std::shared_ptr<mpd_status> m_status; std::shared_ptr<mpd_status> m_status;
}; };
struct Playlist struct Directory
{ {
Playlist() { } Directory()
Playlist(mpd_playlist *playlist) : m_playlist(playlist, mpd_playlist_free) { } : m_last_modified(0)
{ }
Playlist(const Playlist &rhs) : m_playlist(rhs.m_playlist) { } Directory(const mpd_directory *directory)
Playlist(Playlist &&rhs) : m_playlist(std::move(rhs.m_playlist)) { }
Playlist &operator=(Playlist rhs)
{ {
m_playlist = std::move(rhs.m_playlist); assert(directory != nullptr);
return *this; m_path = mpd_directory_get_path(directory);
m_last_modified = mpd_directory_get_last_modified(directory);
} }
Directory(std::string path, time_t last_modified = 0)
: m_path(std::move(path))
, m_last_modified(last_modified)
{ }
bool operator==(const Playlist &rhs) bool operator==(const Directory &rhs) const
{ {
if (empty() && rhs.empty()) return m_path == rhs.m_path
return true; && m_last_modified == rhs.m_last_modified;
else if (!empty() && !rhs.empty())
return strcmp(path(), rhs.path()) == 0
&& lastModified() == rhs.lastModified();
else
return false;
} }
bool operator!=(const Playlist &rhs) bool operator!=(const Directory &rhs) const
{ {
return !(*this == rhs); return !(*this == rhs);
} }
const char *path() const const std::string &path() const
{ {
assert(m_playlist.get() != nullptr); return m_path;
return mpd_playlist_get_path(m_playlist.get());
} }
time_t lastModified() const time_t lastModified() const
{ {
assert(m_playlist.get() != nullptr); return m_last_modified;
return mpd_playlist_get_last_modified(m_playlist.get());
} }
bool empty() const { return m_playlist.get() == nullptr; } private:
std::string m_path;
time_t m_last_modified;
};
struct Playlist
{
Playlist()
: m_last_modified(0)
{ }
Playlist(const mpd_playlist *playlist)
{
assert(playlist != nullptr);
m_path = mpd_playlist_get_path(playlist);
m_last_modified = mpd_playlist_get_last_modified(playlist);
}
Playlist(std::string path, time_t last_modified = 0)
: m_path(std::move(path))
, m_last_modified(last_modified)
{
if (m_path.empty())
throw std::runtime_error("empty path");
}
bool operator==(const Playlist &rhs) const
{
return m_path == rhs.m_path
&& m_last_modified == rhs.m_last_modified;
}
bool operator!=(const Playlist &rhs) const
{
return !(*this == rhs);
}
const std::string &path() const
{
return m_path;
}
time_t lastModified() const
{
return m_last_modified;
}
private: private:
std::shared_ptr<mpd_playlist> m_playlist; std::string m_path;
time_t m_last_modified;
}; };
struct Item struct Item
{ {
enum class Type { Directory, Playlist, Song }; enum class Type { Directory, Song, Playlist };
Song song; Item(mpd_entity *entity)
Type type; {
std::string name; assert(entity != nullptr);
switch (mpd_entity_get_type(entity))
{
case MPD_ENTITY_TYPE_DIRECTORY:
m_type = Type::Directory;
m_directory = Directory(mpd_entity_get_directory(entity));
break;
case MPD_ENTITY_TYPE_SONG:
m_type = Type::Song;
m_song = Song(mpd_song_dup(mpd_entity_get_song(entity)));
break;
case MPD_ENTITY_TYPE_PLAYLIST:
m_type = Type::Playlist;
m_playlist = Playlist(mpd_entity_get_playlist(entity));
break;
default:
throw std::runtime_error("unknown mpd_entity type");
}
mpd_entity_free(entity);
}
Item(Directory directory_)
: m_type(Type::Directory)
, m_directory(std::move(directory_))
{ }
Item(Song song_)
: m_type(Type::Song)
, m_song(std::move(song_))
{ }
Item(Playlist playlist_)
: m_type(Type::Playlist)
, m_playlist(std::move(playlist_))
{ }
bool operator==(const Item &rhs) const
{
return m_directory == rhs.m_directory
&& m_song == rhs.m_song
&& m_playlist == rhs.m_playlist;
}
bool operator!=(const Item &rhs) const
{
return !(*this == rhs);
}
Type type() const
{
return m_type;
}
const Directory &directory() const
{
assert(m_type == Type::Directory);
return m_directory;
}
const Song &song() const
{
assert(m_type == Type::Song);
return m_song;
}
const Playlist &playlist() const
{
assert(m_type == Type::Playlist);
return m_playlist;
}
private:
Type m_type;
Directory m_directory;
Song m_song;
Playlist m_playlist;
}; };
struct Output struct Output
{ {
Output() { } Output() { }
Output(mpd_output *output) : m_output(output, mpd_output_free) { } Output(mpd_output *output)
: m_output(output, mpd_output_free)
Output(const Output &rhs) : m_output(rhs.m_output) { } { }
Output(Output &&rhs) : m_output(std::move(rhs.m_output)) { }
Output &operator=(Output rhs)
{
m_output = std::move(rhs.m_output);
return *this;
}
bool operator==(const Output &rhs) const bool operator==(const Output &rhs) const
{ {
@@ -230,34 +330,32 @@ typedef std::vector<std::string> StringList;
typedef std::vector<Output> OutputList; typedef std::vector<Output> OutputList;
template <typename DestT, typename SourceT> template <typename DestT, typename SourceT>
struct Iterator : std::iterator<std::forward_iterator_tag, DestT> struct Iterator: std::iterator<std::input_iterator_tag, DestT>
{ {
typedef SourceT *(*SourceFetcher)(mpd_connection *); typedef SourceT *(*SourceFetcher)(mpd_connection *);
friend class Connection; Iterator()
: m_state(nullptr)
Iterator() : m_connection(nullptr), m_fetch_source(nullptr) { } { }
~Iterator() Iterator(mpd_connection *connection, SourceFetcher fetch_source)
: m_state(std::make_shared<State>(connection, fetch_source))
{ {
if (m_connection != nullptr) // get the first element
finish(); ++*this;
} }
void finish() void finish()
{ {
// clean up // change the iterator into end iterator
assert(m_connection != nullptr); m_state = nullptr;
mpd_response_finish(m_connection);
m_object = DestT();
m_connection = nullptr;
} }
DestT &operator*() const DestT &operator*() const
{ {
assert(m_connection != nullptr); if (!m_state)
if (m_object.empty()) throw std::runtime_error("no object associated with the iterator");
throw std::runtime_error("empty object"); assert(m_state->hasObject());
return const_cast<DestT &>(m_object); return m_state->getObject();
} }
DestT *operator->() const DestT *operator->() const
{ {
@@ -266,11 +364,10 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
Iterator &operator++() Iterator &operator++()
{ {
assert(m_connection != nullptr); assert(m_state);
assert(m_fetch_source != nullptr); auto src = m_state->fetchSource();
auto src = m_fetch_source(m_connection);
if (src != nullptr) if (src != nullptr)
m_object = DestT(src); m_state->setObject(src);
else else
finish(); finish();
return *this; return *this;
@@ -284,8 +381,7 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
bool operator==(const Iterator &rhs) bool operator==(const Iterator &rhs)
{ {
return m_connection == rhs.m_connection return m_state == rhs.m_state;
&& m_object == rhs.m_object;
} }
bool operator!=(const Iterator &rhs) bool operator!=(const Iterator &rhs)
{ {
@@ -293,27 +389,66 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
} }
private: private:
Iterator(mpd_connection *connection, SourceFetcher fetch_source) struct State
: m_connection(connection), m_fetch_source(fetch_source)
{ {
// get the first element State(mpd_connection *conn, SourceFetcher fetch_source)
++*this; : m_connection(conn)
, m_fetch_source(fetch_source)
{
assert(m_connection != nullptr);
assert(m_fetch_source != nullptr);
}
~State()
{
mpd_response_finish(m_connection);
} }
bool operator==(const State &rhs) const
{
return m_connection == rhs.m_connection
&& m_object == m_object;
}
bool operator!=(const State &rhs) const
{
return !(*this == rhs);
}
SourceT *fetchSource() const
{
return m_fetch_source(m_connection);
}
DestT &getObject() const
{
return *m_object;
}
bool hasObject() const
{
return m_object.get() != nullptr;
}
void setObject(DestT object)
{
if (hasObject())
*m_object = std::move(object);
else
m_object.reset(new DestT(std::move(object)));
}
private:
mpd_connection *m_connection; mpd_connection *m_connection;
SourceFetcher m_fetch_source; SourceFetcher m_fetch_source;
DestT m_object; std::unique_ptr<DestT> m_object;
};
std::shared_ptr<State> m_state;
}; };
typedef Iterator<Item, mpd_entity> ItemIterator;
typedef Iterator<Output, mpd_output> OutputIterator; typedef Iterator<Output, mpd_output> OutputIterator;
typedef Iterator<Playlist, mpd_playlist> PlaylistIterator; typedef Iterator<Playlist, mpd_playlist> PlaylistIterator;
typedef Iterator<Song, mpd_song> SongIterator; typedef Iterator<Song, mpd_song> SongIterator;
class Connection class Connection
{ {
typedef std::function<void(Item)> ItemConsumer;
typedef std::function<void(Output)> OutputConsumer;
typedef std::function<void(Song)> SongConsumer;
typedef std::function<void(std::string)> StringConsumer; typedef std::function<void(std::string)> StringConsumer;
public: public:
@@ -362,7 +497,7 @@ public:
SongIterator GetPlaylistContent(const std::string &name); SongIterator GetPlaylistContent(const std::string &name);
SongIterator GetPlaylistContentNoInfo(const std::string &name); SongIterator GetPlaylistContentNoInfo(const std::string &name);
void GetSupportedExtensions(std::set<std::string> &); void GetSupportedExtensions(StringConsumer f);
void SetRepeat(bool); void SetRepeat(bool);
void SetRandom(bool); void SetRandom(bool);
@@ -405,8 +540,8 @@ public:
PlaylistIterator GetPlaylists(); PlaylistIterator GetPlaylists();
void GetList(mpd_tag_type type, StringConsumer f); void GetList(mpd_tag_type type, StringConsumer f);
void GetDirectory(const std::string &directory, ItemConsumer f); ItemIterator GetDirectory(const std::string &directory);
void GetDirectoryRecursive(const std::string &directory, SongConsumer f); ItemIterator GetDirectoryRecursive(const std::string &directory);
SongIterator GetSongs(const std::string &directory); SongIterator GetSongs(const std::string &directory);
void GetDirectories(const std::string &directory, StringConsumer f); void GetDirectories(const std::string &directory, StringConsumer f);

View File

@@ -564,19 +564,17 @@ void PlaylistEditor::updateTimer()
m_timer = Global::Timer; m_timer = Global::Timer;
} }
void PlaylistEditor::Locate(const std::string &name) void PlaylistEditor::Locate(const MPD::Playlist &playlist)
{ {
update(); update();
for (size_t i = 0; i < Playlists.size(); ++i) auto begin = Playlists.beginV(), end = Playlists.endV();
auto it = std::find(begin, end, playlist);
if (it != end)
{ {
if (name == Playlists[i].value().path()) Playlists.highlight(it-begin);
{
Playlists.highlight(i);
Content.clear(); Content.clear();
break;
}
}
switchTo(); switchTo();
}
} }
namespace {// namespace {//

View File

@@ -78,7 +78,7 @@ struct PlaylistEditor: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, S
void requestPlaylistsUpdate() { m_playlists_update_requested = true; } void requestPlaylistsUpdate() { m_playlists_update_requested = true; }
void requestContentsUpdate() { m_content_update_requested = true; } void requestContentsUpdate() { m_content_update_requested = true; }
virtual void Locate(const std::string &); virtual void Locate(const MPD::Playlist &playlist);
bool isContentFiltered(); bool isContentFiltered();
ProxySongList contentProxyList(); ProxySongList contentProxyList();

View File

@@ -446,9 +446,18 @@ void SearchEngine::Search()
MPD::SongList list; MPD::SongList list;
if (Config.search_in_db) if (Config.search_in_db)
Mpd.GetDirectoryRecursive("/", vectorMoveInserter(list)); {
MPD::ItemIterator item = Mpd.GetDirectoryRecursive("/"), end;
for (; item != end; ++item)
if (item->type() != MPD::Item::Type::Song)
list.push_back(std::move(item->song()));
}
else else
list.insert(list.end(), myPlaylist->main().beginV(), myPlaylist->main().endV()); std::copy(
myPlaylist->main().beginV(),
myPlaylist->main().endV(),
std::back_inserter(list)
);
bool any_found = 1; bool any_found = 1;
bool found = 1; bool found = 1;

View File

@@ -179,7 +179,7 @@ void Status::handleServerError(MPD::ServerError &e)
} }
else if (e.code() == MPD_SERVER_ERROR_NO_EXIST && myScreen == myBrowser) else if (e.code() == MPD_SERVER_ERROR_NO_EXIST && myScreen == myBrowser)
{ {
myBrowser->GetDirectory(getParentDirectory(myBrowser->CurrentDir())); myBrowser->getDirectory(getParentDirectory(myBrowser->currentDirectory()));
myBrowser->refresh(); myBrowser->refresh();
} }
} }
@@ -438,9 +438,9 @@ void Status::Changes::storedPlaylists()
{ {
myPlaylistEditor->requestPlaylistsUpdate(); myPlaylistEditor->requestPlaylistsUpdate();
myPlaylistEditor->requestContentsUpdate(); myPlaylistEditor->requestContentsUpdate();
if (myBrowser->CurrentDir() == "/") if (!myBrowser->isLocal() && myBrowser->inRootDirectory())
{ {
myBrowser->GetDirectory("/"); myBrowser->getDirectory("/");
if (isVisible(myBrowser)) if (isVisible(myBrowser))
myBrowser->refresh(); myBrowser->refresh();
} }
@@ -449,7 +449,7 @@ void Status::Changes::storedPlaylists()
void Status::Changes::database() void Status::Changes::database()
{ {
if (isVisible(myBrowser)) if (isVisible(myBrowser))
myBrowser->GetDirectory(myBrowser->CurrentDir()); myBrowser->getDirectory(myBrowser->currentDirectory());
else else
myBrowser->main().clear(); myBrowser->main().clear();
# ifdef HAVE_TAGLIB_H # ifdef HAVE_TAGLIB_H

View File

@@ -39,7 +39,7 @@
#include "utility/string.h" #include "utility/string.h"
#include "utility/wide_string.h" #include "utility/wide_string.h"
namespace {// namespace {
TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f) TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f)
{ {
@@ -50,71 +50,69 @@ TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f)
return result; return result;
} }
void readCommonTags(MPD::MutableSong &s, TagLib::Tag *tag) void readCommonTags(mpd_song *s, TagLib::Tag *tag)
{ {
s.setTitle(tag->title().to8Bit(true)); Tags::setAttribute(s, "Title", tag->title().to8Bit(true));
s.setArtist(tag->artist().to8Bit(true)); Tags::setAttribute(s, "Artist", tag->artist().to8Bit(true));
s.setAlbum(tag->album().to8Bit(true)); Tags::setAttribute(s, "Album", tag->album().to8Bit(true));
s.setDate(boost::lexical_cast<std::string>(tag->year())); Tags::setAttribute(s, "Date", boost::lexical_cast<std::string>(tag->year()));
s.setTrack(boost::lexical_cast<std::string>(tag->track())); Tags::setAttribute(s, "Track", boost::lexical_cast<std::string>(tag->track()));
s.setGenre(tag->genre().to8Bit(true)); Tags::setAttribute(s, "Genre", tag->genre().to8Bit(true));
s.setComment(tag->comment().to8Bit(true)); Tags::setAttribute(s, "Comment", tag->comment().to8Bit(true));
} }
void readID3v1Tags(MPD::MutableSong &s, TagLib::ID3v1::Tag *tag) void readID3v1Tags(mpd_song *s, TagLib::ID3v1::Tag *tag)
{ {
readCommonTags(s, tag); readCommonTags(s, tag);
} }
void readID3v2Tags(MPD::MutableSong &s, TagLib::ID3v2::Tag *tag) void readID3v2Tags(mpd_song *s, TagLib::ID3v2::Tag *tag)
{ {
auto readFrame = [&s](const TagLib::ID3v2::FrameList &list, MPD::MutableSong::SetFunction f) { auto readFrame = [s](const TagLib::ID3v2::FrameList &fields, const char *name) {
unsigned idx = 0; for (const auto &field : fields)
for (auto it = list.begin(); it != list.end(); ++it, ++idx)
{ {
if (auto textFrame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(*it)) if (auto textFrame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(field))
{ {
auto values = textFrame->fieldList(); auto values = textFrame->fieldList();
for (auto value = values.begin(); value != values.end(); ++value, ++idx) for (const auto &value : values)
(s.*f)(value->to8Bit(true), idx); Tags::setAttribute(s, name, value.to8Bit(true));
} }
else else
(s.*f)((*it)->toString().to8Bit(true), idx); Tags::setAttribute(s, name, field->toString().to8Bit(true));
} }
}; };
auto &frames = tag->frameListMap(); auto &frames = tag->frameListMap();
readFrame(frames["TIT2"], &MPD::MutableSong::setTitle); readFrame(frames["TIT2"], "Title");
readFrame(frames["TPE1"], &MPD::MutableSong::setArtist); readFrame(frames["TPE1"], "Artist");
readFrame(frames["TPE2"], &MPD::MutableSong::setAlbumArtist); readFrame(frames["TPE2"], "AlbumArtist");
readFrame(frames["TALB"], &MPD::MutableSong::setAlbum); readFrame(frames["TALB"], "Album");
readFrame(frames["TDRC"], &MPD::MutableSong::setDate); readFrame(frames["TDRC"], "Date");
readFrame(frames["TRCK"], &MPD::MutableSong::setTrack); readFrame(frames["TRCK"], "Track");
readFrame(frames["TCON"], &MPD::MutableSong::setGenre); readFrame(frames["TCON"], "Genre");
readFrame(frames["TCOM"], &MPD::MutableSong::setComposer); readFrame(frames["TCOM"], "Composer");
readFrame(frames["TPE3"], &MPD::MutableSong::setPerformer); readFrame(frames["TPE3"], "Performer");
readFrame(frames["TPOS"], &MPD::MutableSong::setDisc); readFrame(frames["TPOS"], "Disc");
readFrame(frames["COMM"], &MPD::MutableSong::setComment); readFrame(frames["COMM"], "Comment");
} }
void readXiphComments(MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag) void readXiphComments(mpd_song *s, TagLib::Ogg::XiphComment *tag)
{ {
auto readField = [&s](const TagLib::StringList &list, MPD::MutableSong::SetFunction f) { auto readField = [s](const TagLib::StringList &fields, const char *name) {
unsigned idx = 0; for (const auto &field : fields)
for (auto it = list.begin(); it != list.end(); ++it, ++idx) Tags::setAttribute(s, name, field.to8Bit(true));
(s.*f)(it->to8Bit(true), idx);
}; };
auto &fields = tag->fieldListMap(); auto &fields = tag->fieldListMap();
readField(fields["TITLE"], &MPD::MutableSong::setTitle); readField(fields["TITLE"], "Title");
readField(fields["ARTIST"], &MPD::MutableSong::setArtist); readField(fields["ARTIST"], "Artist");
readField(fields["ALBUMARTIST"], &MPD::MutableSong::setAlbumArtist); readField(fields["ALBUMARTIST"], "AlbumArtist");
readField(fields["ALBUM"], &MPD::MutableSong::setAlbum); readField(fields["ALBUM"], "Album");
readField(fields["DATE"], &MPD::MutableSong::setDate); readField(fields["DATE"], "Date");
readField(fields["TRACKNUMBER"], &MPD::MutableSong::setTrack); readField(fields["TRACKNUMBER"], "Track");
readField(fields["GENRE"], &MPD::MutableSong::setGenre); readField(fields["GENRE"], "Genre");
readField(fields["COMPOSER"], &MPD::MutableSong::setComposer); readField(fields["COMPOSER"], "Composer");
readField(fields["PERFORMER"], &MPD::MutableSong::setPerformer); readField(fields["PERFORMER"], "Performer");
readField(fields["DISCNUMBER"], &MPD::MutableSong::setDisc); readField(fields["DISCNUMBER"], "Disc");
readField(fields["COMMENT"], &MPD::MutableSong::setComment); readField(fields["COMMENT"], "Comment");
} }
void clearID3v1Tags(TagLib::ID3v1::Tag *tag) void clearID3v1Tags(TagLib::ID3v1::Tag *tag)
@@ -230,6 +228,12 @@ Tags::ReplayGainInfo getReplayGain(TagLib::Ogg::XiphComment *tag)
namespace Tags {// namespace Tags {//
void setAttribute(mpd_song *s, const char *name, const std::string &value)
{
mpd_pair pair = { name, value.c_str() };
mpd_song_feed(s, &pair);
}
bool extendedSetSupported(const TagLib::File *f) bool extendedSetSupported(const TagLib::File *f)
{ {
return dynamic_cast<const TagLib::MPEG::File *>(f) return dynamic_cast<const TagLib::MPEG::File *>(f)
@@ -253,20 +257,21 @@ ReplayGainInfo readReplayGain(TagLib::File *f)
return result; return result;
} }
void read(MPD::MutableSong &s) void read(mpd_song *s)
{ {
TagLib::FileRef f(s.getURI().c_str()); TagLib::FileRef f(mpd_song_get_uri(s));
if (f.isNull()) if (f.isNull())
return; return;
s.setDuration(f.audioProperties()->length()); setAttribute(s, "Time", boost::lexical_cast<std::string>(f.audioProperties()->length()));
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file())) if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file()))
{ {
if (auto id3v1 = mpeg_file->ID3v1Tag()) // prefer id3v2 only if available
readID3v1Tags(s, id3v1);
if (auto id3v2 = mpeg_file->ID3v2Tag()) if (auto id3v2 = mpeg_file->ID3v2Tag())
readID3v2Tags(s, id3v2); readID3v2Tags(s, id3v2);
else if (auto id3v1 = mpeg_file->ID3v1Tag())
readID3v1Tags(s, id3v1);
} }
else if (auto ogg_file = dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file())) else if (auto ogg_file = dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file()))
{ {

View File

@@ -62,11 +62,13 @@ private:
std::string m_album_peak; std::string m_album_peak;
}; };
void setAttribute(mpd_song *s, const char *name, const std::string &value);
ReplayGainInfo readReplayGain(TagLib::File *f); ReplayGainInfo readReplayGain(TagLib::File *f);
bool extendedSetSupported(const TagLib::File *f); bool extendedSetSupported(const TagLib::File *f);
void read(MPD::MutableSong &); void read(mpd_song *s);
bool write(MPD::MutableSong &); bool write(MPD::MutableSong &);
} }

View File

@@ -142,7 +142,7 @@ void TinyTagEditor::enterPressed()
if (m_previous_screen == myPlaylist) if (m_previous_screen == myPlaylist)
myPlaylist->main().current().value() = itsEdited; myPlaylist->main().current().value() = itsEdited;
else if (m_previous_screen == myBrowser) else if (m_previous_screen == myBrowser)
myBrowser->GetDirectory(myBrowser->CurrentDir()); myBrowser->getDirectory(myBrowser->currentDirectory());
} }
} }
else else

View File

@@ -53,36 +53,58 @@ int LocaleStringComparison::compare(const char *a, size_t a_len, const char *b,
bool LocaleBasedItemSorting::operator()(const MPD::Item &a, const MPD::Item &b) const bool LocaleBasedItemSorting::operator()(const MPD::Item &a, const MPD::Item &b) const
{ {
bool result = false; bool result = false;
if (a.type == b.type) if (a.type() == b.type())
{ {
switch (a.type)
{
case MPD::Item::Type::Directory:
result = m_cmp(getBasename(a.name), getBasename(b.name));
break;
case MPD::Item::Type::Playlist:
result = m_cmp(a.name, b.name);
break;
case MPD::Item::Type::Song:
switch (m_sort_mode) switch (m_sort_mode)
{ {
case SortMode::Name: case SortMode::Name:
result = m_cmp(a.song, b.song); switch (a.type())
{
case MPD::Item::Type::Directory:
result = m_cmp(a.directory().path(), b.directory().path());
break; break;
case SortMode::ModificationTime: case MPD::Item::Type::Playlist:
result = a.song.getMTime() > b.song.getMTime(); result = m_cmp(a.playlist().path(), b.playlist().path());
break;
case MPD::Item::Type::Song:
result = m_cmp(a.song(), b.song());
break;
}
break; break;
case SortMode::CustomFormat: case SortMode::CustomFormat:
result = m_cmp(a.song.toString(Config.browser_sort_format, Config.tags_separator), switch (a.type())
b.song.toString(Config.browser_sort_format, Config.tags_separator)); {
case MPD::Item::Type::Directory:
result = m_cmp(a.directory().path(), b.directory().path());
break;
case MPD::Item::Type::Playlist:
result = m_cmp(a.playlist().path(), b.playlist().path());
break;
case MPD::Item::Type::Song:
result = m_cmp(a.song().toString(Config.browser_sort_format, Config.tags_separator),
b.song().toString(Config.browser_sort_format, Config.tags_separator));
break;
}
break;
case SortMode::ModificationTime:
switch (a.type())
{
case MPD::Item::Type::Directory:
result = a.directory().lastModified() > b.directory().lastModified();
break;
case MPD::Item::Type::Playlist:
result = a.playlist().lastModified() > b.playlist().lastModified();
break;
case MPD::Item::Type::Song:
result = a.song().getMTime() > b.song().getMTime();
break;
}
break; break;
case SortMode::NoOp: case SortMode::NoOp:
throw std::logic_error("can't sort with NoOp sorting mode"); throw std::logic_error("can't sort with NoOp sorting mode");
} }
break;
}
} }
else else
result = a.type < b.type; result = a.type() < b.type();
return result; return result;
} }

View File

@@ -32,13 +32,14 @@ std::string getBasename(const std::string &path)
return path.substr(slash+1); return path.substr(slash+1);
} }
std::string getParentDirectory(const std::string &path) std::string getParentDirectory(std::string path)
{ {
size_t slash = path.rfind('/'); size_t slash = path.rfind('/');
if (slash == std::string::npos) if (slash == std::string::npos)
return "/"; path = "";
else else
return path.substr(0, slash); path.resize(slash);
return path;
} }
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2) std::string getSharedDirectory(const std::string &dir1, const std::string &dir2)

View File

@@ -53,7 +53,7 @@ StringT join(CollectionT &&collection, StringT &&separator)
} }
std::string getBasename(const std::string &path); std::string getBasename(const std::string &path);
std::string getParentDirectory(const std::string &path); std::string getParentDirectory(std::string path);
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2); std::string getSharedDirectory(const std::string &dir1, const std::string &dir2);
std::string getEnclosedString(const std::string &s, char a, char b, size_t *pos); std::string getEnclosedString(const std::string &s, char a, char b, size_t *pos);