diff options
-rw-r--r-- | src/actions.cpp | 105 | ||||
-rw-r--r-- | src/browser.cpp | 589 | ||||
-rw-r--r-- | src/browser.h | 34 | ||||
-rw-r--r-- | src/display.cpp | 10 | ||||
-rw-r--r-- | src/helpers.cpp | 10 | ||||
-rw-r--r-- | src/helpers.h | 2 | ||||
-rw-r--r-- | src/media_library.cpp | 18 | ||||
-rw-r--r-- | src/mpdpp.cpp | 40 | ||||
-rw-r--r-- | src/mpdpp.h | 283 | ||||
-rw-r--r-- | src/playlist_editor.cpp | 16 | ||||
-rw-r--r-- | src/playlist_editor.h | 2 | ||||
-rw-r--r-- | src/search_engine.cpp | 13 | ||||
-rw-r--r-- | src/status.cpp | 8 | ||||
-rw-r--r-- | src/tags.cpp | 105 | ||||
-rw-r--r-- | src/tags.h | 4 | ||||
-rw-r--r-- | src/tiny_tag_editor.cpp | 2 | ||||
-rw-r--r-- | src/utility/comparators.cpp | 58 | ||||
-rw-r--r-- | src/utility/string.cpp | 7 | ||||
-rw-r--r-- | src/utility/string.h | 2 |
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 ¤tDirectory(); + 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())) { @@ -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); |