summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/actions.cpp105
-rw-r--r--src/browser.cpp589
-rw-r--r--src/browser.h34
-rw-r--r--src/display.cpp10
-rw-r--r--src/helpers.cpp10
-rw-r--r--src/helpers.h2
-rw-r--r--src/media_library.cpp18
-rw-r--r--src/mpdpp.cpp40
-rw-r--r--src/mpdpp.h283
-rw-r--r--src/playlist_editor.cpp16
-rw-r--r--src/playlist_editor.h2
-rw-r--r--src/search_engine.cpp13
-rw-r--r--src/status.cpp8
-rw-r--r--src/tags.cpp105
-rw-r--r--src/tags.h4
-rw-r--r--src/tiny_tag_editor.cpp2
-rw-r--r--src/utility/comparators.cpp58
-rw-r--r--src/utility/string.cpp7
-rw-r--r--src/utility/string.h2
19 files changed, 767 insertions, 541 deletions
diff --git a/src/actions.cpp b/src/actions.cpp
index 9f391424..80673806 100644
--- a/src/actions.cpp
+++ b/src/actions.cpp
@@ -521,7 +521,7 @@ void JumpToParentDirectory::run()
{
if (myScreen == myBrowser)
{
- if (myBrowser->CurrentDir() != "/")
+ if (!myBrowser->inRootDirectory())
{
myBrowser->main().reset();
myBrowser->enterPressed();
@@ -669,47 +669,59 @@ bool DeleteBrowserItems::canBeRun() const
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;
if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
question = boost::format("Delete selected items?");
else
{
- MPD::Item &item = myBrowser->main().current().value();
- std::string iname = item.type == MPD::Item::Type::Song ? item.song.getName() : item.name;
- question = boost::format("Delete %1% \"%2%\"?")
- % itemTypeToString(item.type) % wideShorten(iname, COLS-question.size()-10);
+ const auto &item = myBrowser->main().current().value();
+ // parent directories are not accepted (and they
+ // can't be selected, so in other cases it's fine).
+ 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);
- bool success = true;
- auto list = getSelectedOrCurrent(
+
+ auto items = getSelectedOrCurrent(
myBrowser->main().begin(),
myBrowser->main().end(),
myBrowser->main().currentI()
);
- for (const auto &item : list)
+ for (const auto &item : items)
{
- const MPD::Item &i = item->value();
- std::string iname = i.type == MPD::Item::Type::Song ? i.song.getName() : i.name;
- std::string errmsg;
- if (myBrowser->deleteItem(i, errmsg))
- {
- 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())
- myBrowser->GetDirectory(myBrowser->CurrentDir());
- else
- Mpd.UpdateDirectory(myBrowser->CurrentDir());
+ myBrowser->remove(item->value());
+ const char msg[] = "Deleted %1% \"%2%\"";
+ Statusbar::printf(msg,
+ itemTypeToString(item->value().type()),
+ wideShorten(get_name(item->value()), COLS-const_strlen(msg))
+ );
}
+
+ if (myBrowser->isLocal())
+ myBrowser->getDirectory(myBrowser->currentDirectory());
+ else
+ Mpd.UpdateDirectory(myBrowser->currentDirectory());
}
bool DeleteStoredPlaylist::canBeRun() const
@@ -803,10 +815,6 @@ void SavePlaylist::run()
throw e;
}
}
- if (!myBrowser->isLocal()
- && myBrowser->CurrentDir() == "/"
- && !myBrowser->main().empty())
- myBrowser->GetDirectory(myBrowser->CurrentDir());
}
void Stop::run()
@@ -1142,7 +1150,7 @@ void TogglePlayingSongCentering::run()
void UpdateDatabase::run()
{
if (myScreen == myBrowser)
- Mpd.UpdateDirectory(myBrowser->CurrentDir());
+ Mpd.UpdateDirectory(myBrowser->currentDirectory());
# ifdef HAVE_TAGLIB_H
else if (myScreen == myTagEditor)
Mpd.UpdateDirectory(myTagEditor->CurrentDir());
@@ -1169,8 +1177,7 @@ void JumpToPlayingSong::run()
}
else if (myScreen == myBrowser)
{
- myBrowser->LocateSong(s);
- drawHeader();
+ myBrowser->locateSong(s);
}
else if (myScreen == myLibrary)
{
@@ -1413,7 +1420,7 @@ bool EditDirectoryName::canBeRun() const
{
return ((myScreen == myBrowser
&& !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
|| (myScreen->activeWindow() == myTagEditor->Dirs
&& !myTagEditor->Dirs->empty()
@@ -1428,7 +1435,7 @@ void EditDirectoryName::run()
// FIXME: use boost::filesystem and better error reporting
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::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)));
if (!myBrowser->isLocal())
Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
- myBrowser->GetDirectory(myBrowser->CurrentDir());
+ myBrowser->getDirectory(myBrowser->currentDirectory());
}
else
{
@@ -1495,18 +1502,17 @@ bool EditPlaylistName::canBeRun() const
&& !myPlaylistEditor->Playlists.empty())
|| (myScreen == myBrowser
&& !myBrowser->main().empty()
- && myBrowser->main().current().value().type == MPD::Item::Type::Playlist);
+ && myBrowser->main().current().value().type() == MPD::Item::Type::Playlist);
}
void EditPlaylistName::run()
{
using Global::wFooter;
- // FIXME: support local browser more generally
std::string old_name, new_name;
if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
old_name = myPlaylistEditor->Playlists.current().value().path();
else
- old_name = myBrowser->main().current().value().name;
+ old_name = myBrowser->main().current().value().playlist().path();
{
Statusbar::ScopedLock lock;
Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
@@ -1517,8 +1523,6 @@ void EditPlaylistName::run()
Mpd.Rename(old_name, new_name);
const char msg[] = "Playlist renamed to \"%1%\"";
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()
{
auto s = currentSong(myScreen);
- myBrowser->LocateSong(*s);
+ myBrowser->locateSong(*s);
}
bool JumpToMediaLibrary::canBeRun() const
@@ -1557,12 +1561,12 @@ void JumpToMediaLibrary::run()
bool JumpToPlaylistEditor::canBeRun() const
{
return myScreen == myBrowser
- && myBrowser->main().current().value().type == MPD::Item::Type::Playlist;
+ && myBrowser->main().current().value().type() == MPD::Item::Type::Playlist;
}
void JumpToPlaylistEditor::run()
{
- myPlaylistEditor->Locate(myBrowser->main().current().value().name);
+ myPlaylistEditor->Locate(myBrowser->main().current().value().playlist());
}
void ToggleScreenLock::run()
@@ -2100,9 +2104,12 @@ void ToggleBrowserSortMode::run()
}
withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
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)
);
+ }
});
}
@@ -2400,7 +2407,7 @@ bool ChangeBrowseMode::canBeRun() const
void ChangeBrowseMode::run()
{
- myBrowser->ChangeBrowseMode();
+ myBrowser->changeBrowseMode();
}
bool ShowSearchEngine::canBeRun() const
diff --git a/src/browser.cpp b/src/browser.cpp
index 79074610..62898489 100644
--- a/src/browser.cpp
+++ b/src/browser.cpp
@@ -18,10 +18,12 @@
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
+#include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/locale/conversion.hpp>
-#include <algorithm>
+#include <time.h>
#include "browser.h"
#include "charset.h"
@@ -50,17 +52,27 @@ namespace fs = boost::filesystem;
Browser *myBrowser;
-namespace {//
+namespace {
+
+std::set<std::string> lm_supported_extensions;
-std::set<std::string> SupportedExtensions;
-bool hasSupportedExtension(const std::string &file);
+std::string realPath(bool local_browser, std::string path);
+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);
+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.setHighlightColor(Config.main_highlight_color);
@@ -97,7 +109,7 @@ void Browser::switchTo()
SwitchTo::execute(this);
if (w.empty())
- GetDirectory(itsBrowsedDir);
+ getDirectory(m_current_directory);
else
markSongsInPlaylist(proxySongList());
@@ -107,7 +119,7 @@ void Browser::switchTo()
std::wstring Browser::title()
{
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;
}
@@ -117,31 +129,29 @@ void Browser::enterPressed()
return;
const MPD::Item &item = w.current().value();
- switch (item.type)
+ switch (item.type())
{
case MPD::Item::Type::Directory:
{
- if (isParentDirectory(item))
- GetDirectory(getParentDirectory(itsBrowsedDir), itsBrowsedDir);
- else
- GetDirectory(item.name, itsBrowsedDir);
+ getDirectory(item.directory().path());
drawHeader();
break;
}
case MPD::Item::Type::Song:
{
- addSongToPlaylist(item.song, true, -1);
+ addSongToPlaylist(item.song(), true, -1);
break;
}
case MPD::Item::Type::Playlist:
{
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())
);
+ // TODO: ask on failure if we want to continue
bool success = addSongsToPlaylist(list.begin(), list.end(), true, -1);
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())
return;
- size_t i = itsBrowsedDir != "/" ? 1 : 0;
+ size_t i = inRootDirectory() ? 0 : 1;
if (Config.space_selects && w.choice() >= i)
{
i = w.choice();
- w.at(i).setSelected(!w.at(i).isSelected());
+ w[i].setSelected(!w[i].isSelected());
w.scroll(NC::Scroll::Down);
return;
}
-
+
const MPD::Item &item = w.current().value();
-
+ // ignore parent directory
if (isParentDirectory(item))
return;
-
- switch (item.type)
+
+ switch (item.type())
{
case MPD::Item::Type::Directory:
{
- bool success;
-# ifndef WIN32
- if (isLocal())
+ bool success = true;
+ if (m_local_browser)
{
- MPD::SongList list;
- MPD::ItemList items;
- Statusbar::printf("Scanning directory \"%1%\"...", item.name);
- 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);
+ MPD::SongList songs;
+ getLocalDirectoryRecursively(songs, item.directory().path());
+ success = addSongsToPlaylist(songs.begin(), songs.end(), false, -1);
}
else
-# endif // !WIN32
- {
- Mpd.Add(item.name);
- success = true;
- }
+ Mpd.Add(item.directory().path());
Statusbar::printf("Directory \"%1%\" added%2%",
- item.name, withErrors(success)
+ item.directory().path(), withErrors(success)
);
break;
}
case MPD::Item::Type::Song:
- {
- addSongToPlaylist(item.song, false);
+ addSongToPlaylist(item.song(), false);
break;
- }
case MPD::Item::Type::Playlist:
- {
- Mpd.LoadPlaylist(item.name);
- Statusbar::printf("Playlist \"%1%\" loaded", item.name);
+ Mpd.LoadPlaylist(item.playlist().path());
+ Statusbar::printf("Playlist \"%1%\" loaded", item.playlist().path());
break;
- }
}
+
w.scroll(NC::Scroll::Down);
}
@@ -216,12 +213,12 @@ void Browser::mouseButtonPressed(MEVENT me)
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
{
w.Goto(me.y);
- switch (w.current().value().type)
+ switch (w.current().value().type())
{
case MPD::Item::Type::Directory:
if (me.bstate & BUTTON1_PRESSED)
{
- GetDirectory(w.current().value().name);
+ getDirectory(w.current().value().directory().path());
drawHeader();
}
else
@@ -273,7 +270,7 @@ void Browser::applyFilter(const std::string &filter)
try
{
w.showAll();
- auto fun = boost::bind(BrowserEntryMatcher, _1, _2, true);
+ auto fun = boost::bind(browserEntryMatcher, _1, _2, true);
auto rx = RegexFilter<MPD::Item>(
boost::regex(filter, Config.regex_type), fun);
w.filter(w.begin(), w.end(), rx);
@@ -297,7 +294,7 @@ bool Browser::search(const std::string &constraint)
}
try
{
- auto fun = boost::bind(BrowserEntryMatcher, _1, _2, false);
+ auto fun = boost::bind(browserEntryMatcher, _1, _2, false);
auto rx = RegexFilter<MPD::Item>(
boost::regex(constraint, Config.regex_type), fun);
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 * {
MPD::Song *ptr = 0;
- if (item.value().type == MPD::Item::Type::Song)
- ptr = &item.value().song;
+ if (item.value().type() == MPD::Item::Type::Song)
+ ptr = const_cast<MPD::Song *>(&item.value().song());
return ptr;
});
}
@@ -337,201 +334,167 @@ bool Browser::allowsSelection()
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 result;
- auto item_handler = [this, &result](const MPD::Item &item) {
- if (item.type == MPD::Item::Type::Directory)
- {
-# ifndef WIN32
- if (isLocal())
- {
- MPD::ItemList list;
- GetLocalDirectory(list, item.name, true);
- for (auto it = list.begin(); it != list.end(); ++it)
- result.push_back(it->song);
- }
- else
-# endif // !WIN32
- {
- Mpd.GetDirectoryRecursive(item.name, vectorMoveInserter(result));
- }
- }
- else if (item.type == MPD::Item::Type::Song)
- result.push_back(item.song);
- else if (item.type == MPD::Item::Type::Playlist)
+ MPD::SongList songs;
+ auto item_handler = [this, &songs](const MPD::Item &item) {
+ switch (item.type())
{
- std::copy(
- std::make_move_iterator(Mpd.GetPlaylistContent(item.name)),
- std::make_move_iterator(MPD::SongIterator()),
- std::back_inserter(result)
- );
+ case MPD::Item::Type::Directory:
+ if (m_local_browser)
+ {
+ MPD::ItemList items;
+ getLocalDirectoryRecursively(songs, item.directory().path());
+ }
+ else
+ {
+ 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;
+ case MPD::Item::Type::Song:
+ songs.push_back(item.song());
+ break;
+ case MPD::Item::Type::Playlist:
+ std::copy(
+ std::make_move_iterator(Mpd.GetPlaylistContent(item.playlist().path())),
+ std::make_move_iterator(MPD::SongIterator()),
+ std::back_inserter(songs)
+ );
+ break;
}
};
- for (auto it = w.begin(); it != w.end(); ++it)
- if (it->isSelected())
- item_handler(it->value());
+ for (const auto &item : w)
+ if (item.isSelected())
+ item_handler(item.value());
// if no item is selected, add current one
- if (result.empty() && !w.empty())
+ if (songs.empty() && !w.empty())
item_handler(w.current().value());
- return result;
+ return songs;
}
-void Browser::fetchSupportedExtensions()
+/***********************************************************************/
+
+bool Browser::inRootDirectory()
+{
+ return isRootDirectory(m_current_directory);
+}
+
+bool Browser::isParentDirectory(const MPD::Item &item)
+{
+ return isItemParentDirectory(item);
+}
+
+const std::string& Browser::currentDirectory()
{
- SupportedExtensions.clear();
- Mpd.GetSupportedExtensions(SupportedExtensions);
+ return m_current_directory;
}
-void Browser::LocateSong(const MPD::Song &s)
+void Browser::locateSong(const MPD::Song &s)
{
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)
switchTo();
- if (itsBrowsedDir != s.getDirectory())
- GetDirectory(s.getDirectory());
- for (size_t i = 0; i < w.size(); ++i)
+ // change to relevant directory
+ if (m_current_directory != s.getDirectory())
{
- if (w[i].value().type == MPD::Item::Type::Song && s == w[i].value().song)
- {
- w.highlight(i);
- break;
- }
+ getDirectory(s.getDirectory());
+ 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())
- dir = "/";
-
- int highlightme = -1;
- itsScrollBeginning = 0;
- if (itsBrowsedDir != dir)
- w.reset();
- itsBrowsedDir = dir;
-
+ m_scroll_beginning = 0;
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;
- parent.name = "..";
- parent.type = MPD::Item::Type::Directory;
- w.addItem(parent);
+ directory.resize(directory.length()-3);
+ directory = getParentDirectory(directory);
}
-
- MPD::ItemList list;
-# ifndef WIN32
- if (isLocal())
- GetLocalDirectory(list, itsBrowsedDir, false);
+ // 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
- 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(),
+ {
+ std::copy(
+ std::make_move_iterator(Mpd.GetDirectory(directory)),
+ std::make_move_iterator(MPD::ItemIterator()),
+ std::back_inserter(items)
+ );
+ }
+
+ // sort items
+ if (Config.browser_sort_mode != SortMode::NoOp)
+ {
+ std::sort(items.begin(), items.end(),
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))
+ {
+ // make it so that display function doesn't have to handle special cases
+ w.addItem(MPD::Directory(directory + "/.."));
+ }
+
+ for (const auto &item : items)
{
- switch (it->type)
+ switch (item.type())
{
case MPD::Item::Type::Playlist:
{
- w.addItem(*it);
+ w.addItem(std::move(item));
break;
}
case MPD::Item::Type::Directory:
{
- if (it->name == subdir)
- highlightme = w.size();
- w.addItem(*it);
+ bool is_current = item.directory().path() == m_current_directory;
+ w.addItem(std::move(item));
+ if (is_current)
+ w.highlight(w.size()-1);
break;
}
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;
}
}
}
- if (highlightme >= 0)
- w.highlight(highlightme);
-}
-
-#ifndef WIN32
-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)
- );
+ m_current_directory = directory;
}
-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()
+void Browser::changeBrowseMode()
{
if (Mpd.GetHostname()[0] != '/')
{
@@ -539,120 +502,216 @@ void Browser::ChangeBrowseMode()
return;
}
- itsBrowseLocally = !itsBrowseLocally;
+ m_local_browser = !m_local_browser;
Statusbar::printf("Browse mode: %1%",
- itsBrowseLocally ? "local filesystem" : "MPD database"
+ m_local_browser ? "local filesystem" : "MPD database"
);
- if (itsBrowseLocally)
+ if (m_local_browser)
{
- itsBrowsedDir = "~";
- expand_home(itsBrowsedDir);
- if (*itsBrowsedDir.rbegin() == '/')
- itsBrowsedDir.resize(itsBrowsedDir.length()-1);
+ m_current_directory = "~";
+ expand_home(m_current_directory);
}
else
- itsBrowsedDir = "/";
+ m_current_directory = "/";
w.reset();
- GetDirectory(itsBrowsedDir);
+ getDirectory(m_current_directory);
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)
- FatalError("Browser::deleteItem invoked with allow_for_physical_item_deletion = false");
+ throw std::runtime_error("physical deletion is forbidden");
if (isParentDirectory((item)))
- FatalError("Parent directory passed to Browser::deleteItem");
-
- // playlist created by mpd
- if (!isLocal() && item.type == MPD::Item::Type::Playlist && CurrentDir() == "/")
+ throw std::runtime_error("deletion of parent directory is forbidden");
+
+ std::string path;
+ switch (item.type())
{
- 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;
- }
+ case MPD::Item::Type::Directory:
+ 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;
}
-
- std::string path;
- if (!isLocal())
- path = Config.mpd_music_dir;
- path += item.type == MPD::Item::Type::Song ? item.song.getURI() : item.name;
+}
- bool rv;
- try
+/***********************************************************************/
+
+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)
{
- 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;
- }
+ Tags::setAttribute(s, "Last-Modified",
+ timeFormat("%Y-%m-%dT%H:%M:%SZ", fs::last_write_time(entry.path()))
+ );
+ // read tags
+ Tags::read(s);
}
- catch (boost::filesystem::filesystem_error &err)
+# 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)
{
- errmsg = err.what();
- rv = false;
+ 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));
}
- return rv;
}
-#endif // !WIN32
-namespace {//
+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)
+ );
+ }
+}
-bool hasSupportedExtension(const std::string &file)
+void clearDirectory(const std::string &directory)
{
- 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();
+ 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 itemToString(const MPD::Item &item)
{
std::string result;
- switch (item.type)
+ switch (item.type())
{
case MPD::Item::Type::Directory:
- result = "[" + getBasename(item.name) + "]";
+ result = "[" + getBasename(item.directory().path()) + "]";
break;
case MPD::Item::Type::Song:
switch (Config.browser_display_mode)
{
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;
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;
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;
}
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 boost::regex_search(ItemToString(item), rx);
+ return boost::regex_search(itemToString(item), rx);
}
}
diff --git a/src/browser.h b/src/browser.h
index 13b39da2..1d06b3fa 100644
--- a/src/browser.h
+++ b/src/browser.h
@@ -63,31 +63,25 @@ struct Browser: Screen<NC::Menu<MPD::Item>>, Filterable, HasSongs, Searchable, T
virtual MPD::SongList getSelectedSongs() OVERRIDE;
// private members
- const std::string &CurrentDir() { return itsBrowsedDir; }
-
- void fetchSupportedExtensions();
-
- bool isLocal() { return itsBrowseLocally; }
- 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 == "..";
- }
+ bool inRootDirectory();
+ bool isParentDirectory(const MPD::Item &item);
+ const std::string &currentDirectory();
+ 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);
+
+ static void fetchSupportedExtensions();
+
protected:
virtual bool isLockable() OVERRIDE { return true; }
private:
- bool itsBrowseLocally;
- size_t itsScrollBeginning;
- std::string itsBrowsedDir;
+ bool m_local_browser;
+ size_t m_scroll_beginning;
+ std::string m_current_directory;
};
extern Browser *myBrowser;
diff --git a/src/display.cpp b/src/display.cpp
index b1ef8abe..99e12b9a 100644
--- a/src/display.cpp
+++ b/src/display.cpp
@@ -384,27 +384,27 @@ void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
void Display::Items(NC::Menu<MPD::Item> &menu, const ProxySongList &pl)
{
const MPD::Item &item = menu.drawn()->value();
- switch (item.type)
+ switch (item.type())
{
case MPD::Item::Type::Directory:
menu << "["
- << Charset::utf8ToLocale(getBasename(item.name))
+ << Charset::utf8ToLocale(getBasename(item.directory().path()))
<< "]";
break;
case MPD::Item::Type::Song:
switch (Config.browser_display_mode)
{
case DisplayMode::Classic:
- showSongs(menu, item.song, pl, Config.song_list_format);
+ showSongs(menu, item.song(), pl, Config.song_list_format);
break;
case DisplayMode::Columns:
- showSongsInColumns(menu, item.song, pl);
+ showSongsInColumns(menu, item.song(), pl);
break;
}
break;
case MPD::Item::Type::Playlist:
menu << Config.browser_playlist_prefix
- << Charset::utf8ToLocale(getBasename(item.name));
+ << Charset::utf8ToLocale(getBasename(item.playlist().path()));
break;
}
}
diff --git a/src/helpers.cpp b/src/helpers.cpp
index 0e93a99a..f9832376 100644
--- a/src/helpers.cpp
+++ b/src/helpers.cpp
@@ -19,6 +19,7 @@
***************************************************************************/
#include <algorithm>
+#include <time.h>
#include "helpers.h"
#include "playlist.h"
@@ -63,6 +64,15 @@ bool addSongToPlaylist(const MPD::Song &s, bool play, int position)
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)
{
char result[32];
diff --git a/src/helpers.h b/src/helpers.h
index ff8abd22..51a35308 100644
--- a/src/helpers.h
+++ b/src/helpers.h
@@ -550,6 +550,8 @@ inline const char *withErrors(bool success)
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);
void markSongsInPlaylist(ProxySongList pl);
diff --git a/src/media_library.cpp b/src/media_library.cpp
index 12d26711..07646716 100644
--- a/src/media_library.cpp
+++ b/src/media_library.cpp
@@ -259,8 +259,13 @@ void MediaLibrary::update()
Albums.clearSearchResults();
m_albums_update_request = false;
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;
+ const MPD::Song &s = item->song();
std::string tag = s.get(Config.media_lib_primary_tag, idx);
do
{
@@ -272,7 +277,7 @@ void MediaLibrary::update()
it->second = s.getMTime();
}
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
- });
+ }
withUnfilteredMenuReapplyFilter(Albums, [this, &albums]() {
size_t idx = 0;
for (auto it = albums.begin(); it != albums.end(); ++it, ++idx)
@@ -303,8 +308,13 @@ void MediaLibrary::update()
std::map<std::string, time_t> tags;
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;
+ const MPD::Song &s = item->song();
std::string tag = s.get(Config.media_lib_primary_tag, idx);
do
{
@@ -315,7 +325,7 @@ void MediaLibrary::update()
it->second = std::max(it->second, s.getMTime());
}
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
- });
+ }
}
else
{
diff --git a/src/mpdpp.cpp b/src/mpdpp.cpp
index 48241f39..1eeeed36 100644
--- a/src/mpdpp.cpp
+++ b/src/mpdpp.cpp
@@ -336,13 +336,13 @@ SongIterator Connection::GetPlaylistContentNoInfo(const std::string &path)
return result;
}
-void Connection::GetSupportedExtensions(std::set<std::string> &acc)
+void Connection::GetSupportedExtensions(StringConsumer f)
{
prechecksNoCommandsList();
mpd_send_command(m_connection.get(), "decoders", NULL);
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_response_finish(m_connection.get());
@@ -677,48 +677,20 @@ void Connection::CommitSearchTags(StringConsumer f)
checkErrors();
}
-void Connection::GetDirectory(const std::string &directory, ItemConsumer f)
+ItemIterator Connection::GetDirectory(const std::string &directory)
{
prechecksNoCommandsList();
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();
+ 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();
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();
+ return ItemIterator(m_connection.get(), mpd_recv_entity);
}
void Connection::GetDirectories(const std::string &directory, StringConsumer f)
diff --git a/src/mpdpp.h b/src/mpdpp.h
index 291983a6..70e58550 100644
--- a/src/mpdpp.h
+++ b/src/mpdpp.h
@@ -120,72 +120,172 @@ private:
std::shared_ptr<mpd_status> m_status;
};
-struct Playlist
+struct Directory
{
- Playlist() { }
- Playlist(mpd_playlist *playlist) : m_playlist(playlist, mpd_playlist_free) { }
-
- Playlist(const Playlist &rhs) : m_playlist(rhs.m_playlist) { }
- Playlist(Playlist &&rhs) : m_playlist(std::move(rhs.m_playlist)) { }
- Playlist &operator=(Playlist rhs)
+ Directory()
+ : m_last_modified(0)
+ { }
+ Directory(const mpd_directory *directory)
{
- m_playlist = std::move(rhs.m_playlist);
- return *this;
+ assert(directory != nullptr);
+ 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 true;
- else if (!empty() && !rhs.empty())
- return strcmp(path(), rhs.path()) == 0
- && lastModified() == rhs.lastModified();
- else
- return false;
+ return m_path == rhs.m_path
+ && m_last_modified == rhs.m_last_modified;
}
- bool operator!=(const Playlist &rhs)
+ bool operator!=(const Directory &rhs) const
{
return !(*this == rhs);
}
- const char *path() const
+ const std::string &path() const
{
- assert(m_playlist.get() != nullptr);
- return mpd_playlist_get_path(m_playlist.get());
+ return m_path;
}
time_t lastModified() const
{
- assert(m_playlist.get() != nullptr);
- return mpd_playlist_get_last_modified(m_playlist.get());
+ return m_last_modified;
}
- 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:
- std::shared_ptr<mpd_playlist> m_playlist;
+ std::string m_path;
+ time_t m_last_modified;
};
struct Item
{
- enum class Type { Directory, Playlist, Song };
+ enum class Type { Directory, Song, Playlist };
+
+ Item(mpd_entity *entity)
+ {
+ 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;
+ }
- Song song;
- Type type;
- std::string name;
+private:
+ Type m_type;
+ Directory m_directory;
+ Song m_song;
+ Playlist m_playlist;
};
struct Output
{
Output() { }
- 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;
- }
+ Output(mpd_output *output)
+ : m_output(output, mpd_output_free)
+ { }
bool operator==(const Output &rhs) const
{
@@ -230,34 +330,32 @@ typedef std::vector<std::string> StringList;
typedef std::vector<Output> OutputList;
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 *);
- friend class Connection;
-
- Iterator() : m_connection(nullptr), m_fetch_source(nullptr) { }
- ~Iterator()
+ Iterator()
+ : m_state(nullptr)
+ { }
+ Iterator(mpd_connection *connection, SourceFetcher fetch_source)
+ : m_state(std::make_shared<State>(connection, fetch_source))
{
- if (m_connection != nullptr)
- finish();
+ // get the first element
+ ++*this;
}
void finish()
{
- // clean up
- assert(m_connection != nullptr);
- mpd_response_finish(m_connection);
- m_object = DestT();
- m_connection = nullptr;
+ // change the iterator into end iterator
+ m_state = nullptr;
}
DestT &operator*() const
{
- assert(m_connection != nullptr);
- if (m_object.empty())
- throw std::runtime_error("empty object");
- return const_cast<DestT &>(m_object);
+ if (!m_state)
+ throw std::runtime_error("no object associated with the iterator");
+ assert(m_state->hasObject());
+ return m_state->getObject();
}
DestT *operator->() const
{
@@ -266,11 +364,10 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
Iterator &operator++()
{
- assert(m_connection != nullptr);
- assert(m_fetch_source != nullptr);
- auto src = m_fetch_source(m_connection);
+ assert(m_state);
+ auto src = m_state->fetchSource();
if (src != nullptr)
- m_object = DestT(src);
+ m_state->setObject(src);
else
finish();
return *this;
@@ -284,8 +381,7 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
bool operator==(const Iterator &rhs)
{
- return m_connection == rhs.m_connection
- && m_object == rhs.m_object;
+ return m_state == rhs.m_state;
}
bool operator!=(const Iterator &rhs)
{
@@ -293,27 +389,66 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
}
private:
- Iterator(mpd_connection *connection, SourceFetcher fetch_source)
- : m_connection(connection), m_fetch_source(fetch_source)
+ struct State
{
- // get the first element
- ++*this;
- }
+ State(mpd_connection *conn, SourceFetcher fetch_source)
+ : 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;
+ SourceFetcher m_fetch_source;
+ std::unique_ptr<DestT> m_object;
+ };
- mpd_connection *m_connection;
- SourceFetcher m_fetch_source;
- DestT m_object;
+ std::shared_ptr<State> m_state;
};
+typedef Iterator<Item, mpd_entity> ItemIterator;
typedef Iterator<Output, mpd_output> OutputIterator;
typedef Iterator<Playlist, mpd_playlist> PlaylistIterator;
typedef Iterator<Song, mpd_song> SongIterator;
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;
public:
@@ -362,7 +497,7 @@ public:
SongIterator GetPlaylistContent(const std::string &name);
SongIterator GetPlaylistContentNoInfo(const std::string &name);
- void GetSupportedExtensions(std::set<std::string> &);
+ void GetSupportedExtensions(StringConsumer f);
void SetRepeat(bool);
void SetRandom(bool);
@@ -405,8 +540,8 @@ public:
PlaylistIterator GetPlaylists();
void GetList(mpd_tag_type type, StringConsumer f);
- void GetDirectory(const std::string &directory, ItemConsumer f);
- void GetDirectoryRecursive(const std::string &directory, SongConsumer f);
+ ItemIterator GetDirectory(const std::string &directory);
+ ItemIterator GetDirectoryRecursive(const std::string &directory);
SongIterator GetSongs(const std::string &directory);
void GetDirectories(const std::string &directory, StringConsumer f);
diff --git a/src/playlist_editor.cpp b/src/playlist_editor.cpp
index d9b53ad4..6bc324b7 100644
--- a/src/playlist_editor.cpp
+++ b/src/playlist_editor.cpp
@@ -564,19 +564,17 @@ void PlaylistEditor::updateTimer()
m_timer = Global::Timer;
}
-void PlaylistEditor::Locate(const std::string &name)
+void PlaylistEditor::Locate(const MPD::Playlist &playlist)
{
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(i);
- Content.clear();
- break;
- }
+ Playlists.highlight(it-begin);
+ Content.clear();
+ switchTo();
}
- switchTo();
}
namespace {//
diff --git a/src/playlist_editor.h b/src/playlist_editor.h
index b4c8af23..0bf2191b 100644
--- a/src/playlist_editor.h
+++ b/src/playlist_editor.h
@@ -78,7 +78,7 @@ struct PlaylistEditor: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, S
void requestPlaylistsUpdate() { m_playlists_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();
ProxySongList contentProxyList();
diff --git a/src/search_engine.cpp b/src/search_engine.cpp
index 6235c4d2..c6cb637e 100644
--- a/src/search_engine.cpp
+++ b/src/search_engine.cpp
@@ -446,9 +446,18 @@ void SearchEngine::Search()
MPD::SongList list;
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
- 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 found = 1;
diff --git a/src/status.cpp b/src/status.cpp
index 1a7dc353..0cfb7a34 100644
--- a/src/status.cpp
+++ b/src/status.cpp
@@ -179,7 +179,7 @@ void Status::handleServerError(MPD::ServerError &e)
}
else if (e.code() == MPD_SERVER_ERROR_NO_EXIST && myScreen == myBrowser)
{
- myBrowser->GetDirectory(getParentDirectory(myBrowser->CurrentDir()));
+ myBrowser->getDirectory(getParentDirectory(myBrowser->currentDirectory()));
myBrowser->refresh();
}
}
@@ -438,9 +438,9 @@ void Status::Changes::storedPlaylists()
{
myPlaylistEditor->requestPlaylistsUpdate();
myPlaylistEditor->requestContentsUpdate();
- if (myBrowser->CurrentDir() == "/")
+ if (!myBrowser->isLocal() && myBrowser->inRootDirectory())
{
- myBrowser->GetDirectory("/");
+ myBrowser->getDirectory("/");
if (isVisible(myBrowser))
myBrowser->refresh();
}
@@ -449,7 +449,7 @@ void Status::Changes::storedPlaylists()
void Status::Changes::database()
{
if (isVisible(myBrowser))
- myBrowser->GetDirectory(myBrowser->CurrentDir());
+ myBrowser->getDirectory(myBrowser->currentDirectory());
else
myBrowser->main().clear();
# ifdef HAVE_TAGLIB_H
diff --git a/src/tags.cpp b/src/tags.cpp
index 03a8330e..e2bf33a1 100644
--- a/src/tags.cpp
+++ b/src/tags.cpp
@@ -39,7 +39,7 @@
#include "utility/string.h"
#include "utility/wide_string.h"
-namespace {//
+namespace {
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;
}
-void readCommonTags(MPD::MutableSong &s, TagLib::Tag *tag)
+void readCommonTags(mpd_song *s, TagLib::Tag *tag)
{
- s.setTitle(tag->title().to8Bit(true));
- s.setArtist(tag->artist().to8Bit(true));
- s.setAlbum(tag->album().to8Bit(true));
- s.setDate(boost::lexical_cast<std::string>(tag->year()));
- s.setTrack(boost::lexical_cast<std::string>(tag->track()));
- s.setGenre(tag->genre().to8Bit(true));
- s.setComment(tag->comment().to8Bit(true));
+ Tags::setAttribute(s, "Title", tag->title().to8Bit(true));
+ Tags::setAttribute(s, "Artist", tag->artist().to8Bit(true));
+ Tags::setAttribute(s, "Album", tag->album().to8Bit(true));
+ Tags::setAttribute(s, "Date", boost::lexical_cast<std::string>(tag->year()));
+ Tags::setAttribute(s, "Track", boost::lexical_cast<std::string>(tag->track()));
+ Tags::setAttribute(s, "Genre", tag->genre().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);
}
-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) {
- unsigned idx = 0;
- for (auto it = list.begin(); it != list.end(); ++it, ++idx)
+ auto readFrame = [s](const TagLib::ID3v2::FrameList &fields, const char *name) {
+ for (const auto &field : fields)
{
- if (auto textFrame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(*it))
+ if (auto textFrame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(field))
{
auto values = textFrame->fieldList();
- for (auto value = values.begin(); value != values.end(); ++value, ++idx)
- (s.*f)(value->to8Bit(true), idx);
+ for (const auto &value : values)
+ Tags::setAttribute(s, name, value.to8Bit(true));
}
else
- (s.*f)((*it)->toString().to8Bit(true), idx);
+ Tags::setAttribute(s, name, field->toString().to8Bit(true));
}
};
auto &frames = tag->frameListMap();
- readFrame(frames["TIT2"], &MPD::MutableSong::setTitle);
- readFrame(frames["TPE1"], &MPD::MutableSong::setArtist);
- readFrame(frames["TPE2"], &MPD::MutableSong::setAlbumArtist);
- readFrame(frames["TALB"], &MPD::MutableSong::setAlbum);
- readFrame(frames["TDRC"], &MPD::MutableSong::setDate);
- readFrame(frames["TRCK"], &MPD::MutableSong::setTrack);
- readFrame(frames["TCON"], &MPD::MutableSong::setGenre);
- readFrame(frames["TCOM"], &MPD::MutableSong::setComposer);
- readFrame(frames["TPE3"], &MPD::MutableSong::setPerformer);
- readFrame(frames["TPOS"], &MPD::MutableSong::setDisc);
- readFrame(frames["COMM"], &MPD::MutableSong::setComment);
+ readFrame(frames["TIT2"], "Title");
+ readFrame(frames["TPE1"], "Artist");
+ readFrame(frames["TPE2"], "AlbumArtist");
+ readFrame(frames["TALB"], "Album");
+ readFrame(frames["TDRC"], "Date");
+ readFrame(frames["TRCK"], "Track");
+ readFrame(frames["TCON"], "Genre");
+ readFrame(frames["TCOM"], "Composer");
+ readFrame(frames["TPE3"], "Performer");
+ readFrame(frames["TPOS"], "Disc");
+ 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) {
- unsigned idx = 0;
- for (auto it = list.begin(); it != list.end(); ++it, ++idx)
- (s.*f)(it->to8Bit(true), idx);
+ auto readField = [s](const TagLib::StringList &fields, const char *name) {
+ for (const auto &field : fields)
+ Tags::setAttribute(s, name, field.to8Bit(true));
};
auto &fields = tag->fieldListMap();
- readField(fields["TITLE"], &MPD::MutableSong::setTitle);
- readField(fields["ARTIST"], &MPD::MutableSong::setArtist);
- readField(fields["ALBUMARTIST"], &MPD::MutableSong::setAlbumArtist);
- readField(fields["ALBUM"], &MPD::MutableSong::setAlbum);
- readField(fields["DATE"], &MPD::MutableSong::setDate);
- readField(fields["TRACKNUMBER"], &MPD::MutableSong::setTrack);
- readField(fields["GENRE"], &MPD::MutableSong::setGenre);
- readField(fields["COMPOSER"], &MPD::MutableSong::setComposer);
- readField(fields["PERFORMER"], &MPD::MutableSong::setPerformer);
- readField(fields["DISCNUMBER"], &MPD::MutableSong::setDisc);
- readField(fields["COMMENT"], &MPD::MutableSong::setComment);
+ readField(fields["TITLE"], "Title");
+ readField(fields["ARTIST"], "Artist");
+ readField(fields["ALBUMARTIST"], "AlbumArtist");
+ readField(fields["ALBUM"], "Album");
+ readField(fields["DATE"], "Date");
+ readField(fields["TRACKNUMBER"], "Track");
+ readField(fields["GENRE"], "Genre");
+ readField(fields["COMPOSER"], "Composer");
+ readField(fields["PERFORMER"], "Performer");
+ readField(fields["DISCNUMBER"], "Disc");
+ readField(fields["COMMENT"], "Comment");
}
void clearID3v1Tags(TagLib::ID3v1::Tag *tag)
@@ -230,6 +228,12 @@ Tags::ReplayGainInfo getReplayGain(TagLib::Ogg::XiphComment *tag)
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)
{
return dynamic_cast<const TagLib::MPEG::File *>(f)
@@ -253,20 +257,21 @@ ReplayGainInfo readReplayGain(TagLib::File *f)
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())
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 id3v1 = mpeg_file->ID3v1Tag())
- readID3v1Tags(s, id3v1);
+ // prefer id3v2 only if available
if (auto id3v2 = mpeg_file->ID3v2Tag())
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()))
{
diff --git a/src/tags.h b/src/tags.h
index fbc2c89d..f69406ed 100644
--- a/src/tags.h
+++ b/src/tags.h
@@ -62,11 +62,13 @@ private:
std::string m_album_peak;
};
+void setAttribute(mpd_song *s, const char *name, const std::string &value);
+
ReplayGainInfo readReplayGain(TagLib::File *f);
bool extendedSetSupported(const TagLib::File *f);
-void read(MPD::MutableSong &);
+void read(mpd_song *s);
bool write(MPD::MutableSong &);
}
diff --git a/src/tiny_tag_editor.cpp b/src/tiny_tag_editor.cpp
index 12281a26..374b74c9 100644
--- a/src/tiny_tag_editor.cpp
+++ b/src/tiny_tag_editor.cpp
@@ -142,7 +142,7 @@ void TinyTagEditor::enterPressed()
if (m_previous_screen == myPlaylist)
myPlaylist->main().current().value() = itsEdited;
else if (m_previous_screen == myBrowser)
- myBrowser->GetDirectory(myBrowser->CurrentDir());
+ myBrowser->getDirectory(myBrowser->currentDirectory());
}
}
else
diff --git a/src/utility/comparators.cpp b/src/utility/comparators.cpp
index cc4ab321..02a81453 100644
--- a/src/utility/comparators.cpp
+++ b/src/utility/comparators.cpp
@@ -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 result = false;
- if (a.type == b.type)
+ if (a.type() == b.type())
{
- switch (a.type)
+ switch (m_sort_mode)
{
- case MPD::Item::Type::Directory:
- result = m_cmp(getBasename(a.name), getBasename(b.name));
+ case SortMode::Name:
+ switch (a.type())
+ {
+ 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(), b.song());
+ break;
+ }
break;
- case MPD::Item::Type::Playlist:
- result = m_cmp(a.name, b.name);
+ case SortMode::CustomFormat:
+ switch (a.type())
+ {
+ 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 MPD::Item::Type::Song:
- switch (m_sort_mode)
+ case SortMode::ModificationTime:
+ switch (a.type())
{
- case SortMode::Name:
- result = m_cmp(a.song, b.song);
+ case MPD::Item::Type::Directory:
+ result = a.directory().lastModified() > b.directory().lastModified();
break;
- case SortMode::ModificationTime:
- result = a.song.getMTime() > b.song.getMTime();
+ case MPD::Item::Type::Playlist:
+ result = a.playlist().lastModified() > b.playlist().lastModified();
break;
- case SortMode::CustomFormat:
- result = m_cmp(a.song.toString(Config.browser_sort_format, Config.tags_separator),
- b.song.toString(Config.browser_sort_format, Config.tags_separator));
+ case MPD::Item::Type::Song:
+ result = a.song().getMTime() > b.song().getMTime();
break;
- case SortMode::NoOp:
- throw std::logic_error("can't sort with NoOp sorting mode");
}
break;
+ case SortMode::NoOp:
+ throw std::logic_error("can't sort with NoOp sorting mode");
}
}
else
- result = a.type < b.type;
+ result = a.type() < b.type();
return result;
}
diff --git a/src/utility/string.cpp b/src/utility/string.cpp
index dc33b112..6ddc852b 100644
--- a/src/utility/string.cpp
+++ b/src/utility/string.cpp
@@ -32,13 +32,14 @@ std::string getBasename(const std::string &path)
return path.substr(slash+1);
}
-std::string getParentDirectory(const std::string &path)
+std::string getParentDirectory(std::string path)
{
size_t slash = path.rfind('/');
if (slash == std::string::npos)
- return "/";
+ path = "";
else
- return path.substr(0, slash);
+ path.resize(slash);
+ return path;
}
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2)
diff --git a/src/utility/string.h b/src/utility/string.h
index c6cd5fe2..921682fb 100644
--- a/src/utility/string.h
+++ b/src/utility/string.h
@@ -53,7 +53,7 @@ StringT join(CollectionT &&collection, StringT &&separator)
}
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 getEnclosedString(const std::string &s, char a, char b, size_t *pos);