mpd: redesign Item and adjust browser

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

View File

@@ -521,7 +521,7 @@ void JumpToParentDirectory::run()
{
if (myScreen == myBrowser)
{
if (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)));
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))
);
}
else
{
Statusbar::print(errmsg);
success = false;
break;
}
}
if (success)
{
if (myBrowser->isLocal())
myBrowser->GetDirectory(myBrowser->CurrentDir());
myBrowser->getDirectory(myBrowser->currentDirectory());
else
Mpd.UpdateDirectory(myBrowser->CurrentDir());
}
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

View File

@@ -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> SupportedExtensions;
bool hasSupportedExtension(const std::string &file);
std::set<std::string> lm_supported_extensions;
std::string ItemToString(const MPD::Item &item);
bool BrowserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter);
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);
}
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)
MPD::SongList songs;
auto item_handler = [this, &songs](const MPD::Item &item) {
switch (item.type())
{
# ifndef WIN32
if (isLocal())
case MPD::Item::Type::Directory:
if (m_local_browser)
{
MPD::ItemList list;
GetLocalDirectory(list, item.name, true);
for (auto it = list.begin(); it != list.end(); ++it)
result.push_back(it->song);
MPD::ItemList items;
getLocalDirectoryRecursively(songs, item.directory().path());
}
else
# endif // !WIN32
{
Mpd.GetDirectoryRecursive(item.name, vectorMoveInserter(result));
MPD::ItemIterator it = Mpd.GetDirectoryRecursive(item.directory().path()), end;
for (; it != end; ++it)
if (it->type() == MPD::Item::Type::Song)
songs.push_back(std::move(it->song()));
}
}
else if (item.type == MPD::Item::Type::Song)
result.push_back(item.song);
else if (item.type == MPD::Item::Type::Playlist)
{
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.name)),
std::make_move_iterator(Mpd.GetPlaylistContent(item.playlist().path())),
std::make_move_iterator(MPD::SongIterator()),
std::back_inserter(result)
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()
{
SupportedExtensions.clear();
Mpd.GetSupportedExtensions(SupportedExtensions);
return isRootDirectory(m_current_directory);
}
void Browser::LocateSong(const MPD::Song &s)
bool Browser::isParentDirectory(const MPD::Item &item)
{
return isItemParentDirectory(item);
}
const std::string& Browser::currentDirectory()
{
return m_current_directory;
}
void Browser::locateSong(const MPD::Song &s)
{
if (s.getDirectory().empty())
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();
}
// 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);
}
// when we go down to root, it can be empty
if (directory.empty())
directory = "/";
MPD::ItemList items;
if (m_local_browser)
getLocalDirectory(items, directory);
else
{
std::copy(
std::make_move_iterator(Mpd.GetDirectory(directory)),
std::make_move_iterator(MPD::ItemIterator()),
std::back_inserter(items)
);
}
MPD::ItemList list;
# ifndef WIN32
if (isLocal())
GetLocalDirectory(list, itsBrowsedDir, false);
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(),
// 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))
{
switch (it->type)
// make it so that display function doesn't have to handle special cases
w.addItem(MPD::Directory(directory + "/.."));
}
for (const auto &item : items)
{
switch (item.type())
{
case MPD::Item::Type::Playlist:
{
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);
m_current_directory = directory;
}
#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)
);
}
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() == "/")
{
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;
}
}
throw std::runtime_error("deletion of parent directory is forbidden");
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
{
if (item.type == MPD::Item::Type::Directory)
ClearDirectory(path);
if (!boost::filesystem::exists(path))
{
errmsg = "No such item: " + path;
rv = false;
}
else
{
boost::filesystem::remove(path);
rv = true;
}
}
catch (boost::filesystem::filesystem_error &err)
{
errmsg = err.what();
rv = false;
}
return rv;
}
#endif // !WIN32
namespace {//
bool hasSupportedExtension(const std::string &file)
{
size_t last_dot = file.rfind(".");
if (last_dot > file.length())
return false;
std::string ext = boost::locale::to_lower(file.substr(last_dot+1));
return SupportedExtensions.find(ext) != SupportedExtensions.end();
}
std::string ItemToString(const MPD::Item &item)
{
std::string result;
switch (item.type)
switch (item.type())
{
case MPD::Item::Type::Directory:
result = "[" + getBasename(item.name) + "]";
path = realPath(m_local_browser, item.directory().path());
clearDirectory(path);
fs::remove(path);
break;
case MPD::Item::Type::Song:
path = realPath(m_local_browser, item.song().getURI());
fs::remove(path);
break;
case MPD::Item::Type::Playlist:
path = item.playlist().path();
try {
Mpd.DeletePlaylist(path);
} catch (MPD::ServerError &e) {
// if there is no such mpd playlist, it's a local one
if (e.code() == MPD_SERVER_ERROR_NO_EXIST)
{
path = realPath(m_local_browser, std::move(path));
fs::remove(path);
}
else
throw;
}
break;
}
}
/***********************************************************************/
void Browser::fetchSupportedExtensions()
{
lm_supported_extensions.clear();
Mpd.GetSupportedExtensions([&](std::string extension) {
lm_supported_extensions.insert("." + std::move(extension));
});
}
/***********************************************************************/
namespace {
std::string realPath(bool local_browser, std::string path)
{
if (!local_browser)
path = Config.mpd_music_dir + path;
return path;
}
bool isStringParentDirectory(const std::string &directory)
{
return boost::algorithm::ends_with(directory, "/..");
}
bool isItemParentDirectory(const MPD::Item &item)
{
return item.type() == MPD::Item::Type::Directory
&& isStringParentDirectory(item.directory().path());
}
bool isRootDirectory(const std::string &directory)
{
return directory == "/";
}
bool isHidden(const fs::directory_iterator &entry)
{
return entry->path().filename().native()[0] == '.';
}
bool hasSupportedExtension(const fs::directory_entry &entry)
{
return lm_supported_extensions.find(entry.path().extension().native())
!= lm_supported_extensions.end();
}
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags)
{
mpd_pair pair = { "file", entry.path().c_str() };
mpd_song *s = mpd_song_begin(&pair);
if (s == nullptr)
throw std::runtime_error("invalid path: " + entry.path().native());
# ifdef HAVE_TAGLIB_H
if (read_tags)
{
Tags::setAttribute(s, "Last-Modified",
timeFormat("%Y-%m-%dT%H:%M:%SZ", fs::last_write_time(entry.path()))
);
// read tags
Tags::read(s);
}
# endif // HAVE_TAGLIB_H
return s;
}
void getLocalDirectory(MPD::ItemList &items, const std::string &directory)
{
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
{
if (!Config.local_browser_show_hidden_files && isHidden(entry))
continue;
if (fs::is_directory(*entry))
{
items.push_back(MPD::Directory(
entry->path().native(),
fs::last_write_time(entry->path())
));
}
else if (hasSupportedExtension(*entry))
items.push_back(getLocalSong(*entry, true));
}
}
void getLocalDirectoryRecursively(MPD::SongList &songs, const std::string &directory)
{
size_t sort_offset = songs.size();
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
{
if (!Config.local_browser_show_hidden_files && isHidden(entry))
continue;
if (fs::is_directory(*entry))
{
getLocalDirectoryRecursively(songs, entry->path().native());
sort_offset = songs.size();
}
else if (hasSupportedExtension(*entry))
songs.push_back(getLocalSong(*entry, false));
};
if (Config.browser_sort_mode != SortMode::NoOp)
{
std::sort(songs.begin()+sort_offset, songs.end(),
LocaleBasedSorting(std::locale(), Config.ignore_leading_the)
);
}
}
void clearDirectory(const std::string &directory)
{
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
{
if (!fs::is_symlink(*entry) && fs::is_directory(*entry))
clearDirectory(entry->path().native());
const char msg[] = "Deleting \"%1%\"...";
Statusbar::printf(msg, wideShorten(entry->path().native(), COLS-const_strlen(msg)));
fs::remove(entry->path());
};
}
/***********************************************************************/
std::string itemToString(const MPD::Item &item)
{
std::string result;
switch (item.type())
{
case MPD::Item::Type::Directory:
result = "[" + getBasename(item.directory().path()) + "]";
break;
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);
}
}

View File

@@ -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; }
bool inRootDirectory();
bool isParentDirectory(const MPD::Item &item);
const std::string &currentDirectory();
void fetchSupportedExtensions();
bool isLocal() { return m_local_browser; }
void locateSong(const MPD::Song &s);
void getDirectory(std::string directory);
void changeBrowseMode();
void remove(const MPD::Item &item);
bool isLocal() { return itsBrowseLocally; }
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 == "..";
}
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;

View File

@@ -384,27 +384,27 @@ void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
void Display::Items(NC::Menu<MPD::Item> &menu, const ProxySongList &pl)
{
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;
}
}

View File

@@ -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];

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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)

View File

@@ -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 };
Song song;
Type type;
std::string name;
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;
}
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;
DestT m_object;
std::unique_ptr<DestT> m_object;
};
std::shared_ptr<State> m_state;
};
typedef Iterator<Item, mpd_entity> ItemIterator;
typedef Iterator<Output, mpd_output> OutputIterator;
typedef Iterator<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);

View File

@@ -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);
Playlists.highlight(it-begin);
Content.clear();
break;
}
}
switchTo();
}
}
namespace {//

View File

@@ -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();

View File

@@ -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;

View File

@@ -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

View File

@@ -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()))
{

View 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 &);
}

View File

@@ -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

View File

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

View File

@@ -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)

View File

@@ -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);