mpd: redesign Item and adjust browser
This commit is contained in:
105
src/actions.cpp
105
src/actions.cpp
@@ -521,7 +521,7 @@ void JumpToParentDirectory::run()
|
|||||||
{
|
{
|
||||||
if (myScreen == myBrowser)
|
if (myScreen == myBrowser)
|
||||||
{
|
{
|
||||||
if (myBrowser->CurrentDir() != "/")
|
if (!myBrowser->inRootDirectory())
|
||||||
{
|
{
|
||||||
myBrowser->main().reset();
|
myBrowser->main().reset();
|
||||||
myBrowser->enterPressed();
|
myBrowser->enterPressed();
|
||||||
@@ -669,47 +669,59 @@ bool DeleteBrowserItems::canBeRun() const
|
|||||||
|
|
||||||
void DeleteBrowserItems::run()
|
void DeleteBrowserItems::run()
|
||||||
{
|
{
|
||||||
|
auto get_name = [](const MPD::Item &item) -> std::string {
|
||||||
|
std::string name;
|
||||||
|
switch (item.type())
|
||||||
|
{
|
||||||
|
case MPD::Item::Type::Directory:
|
||||||
|
name = getBasename(item.directory().path());
|
||||||
|
break;
|
||||||
|
case MPD::Item::Type::Song:
|
||||||
|
name = item.song().getName();
|
||||||
|
break;
|
||||||
|
case MPD::Item::Type::Playlist:
|
||||||
|
name = getBasename(item.playlist().path());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
boost::format question;
|
boost::format question;
|
||||||
if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
|
if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
|
||||||
question = boost::format("Delete selected items?");
|
question = boost::format("Delete selected items?");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MPD::Item &item = myBrowser->main().current().value();
|
const auto &item = myBrowser->main().current().value();
|
||||||
std::string iname = item.type == MPD::Item::Type::Song ? item.song.getName() : item.name;
|
// parent directories are not accepted (and they
|
||||||
question = boost::format("Delete %1% \"%2%\"?")
|
// can't be selected, so in other cases it's fine).
|
||||||
% itemTypeToString(item.type) % wideShorten(iname, COLS-question.size()-10);
|
if (myBrowser->isParentDirectory(item))
|
||||||
|
return;
|
||||||
|
const char msg[] = "Delete \"%1%\"?";
|
||||||
|
question = boost::format(msg) % wideShorten(
|
||||||
|
get_name(item), COLS-const_strlen(msg)-5
|
||||||
|
);
|
||||||
}
|
}
|
||||||
confirmAction(question);
|
confirmAction(question);
|
||||||
bool success = true;
|
|
||||||
auto list = getSelectedOrCurrent(
|
auto items = getSelectedOrCurrent(
|
||||||
myBrowser->main().begin(),
|
myBrowser->main().begin(),
|
||||||
myBrowser->main().end(),
|
myBrowser->main().end(),
|
||||||
myBrowser->main().currentI()
|
myBrowser->main().currentI()
|
||||||
);
|
);
|
||||||
for (const auto &item : list)
|
for (const auto &item : items)
|
||||||
{
|
{
|
||||||
const MPD::Item &i = item->value();
|
myBrowser->remove(item->value());
|
||||||
std::string iname = i.type == MPD::Item::Type::Song ? i.song.getName() : i.name;
|
const char msg[] = "Deleted %1% \"%2%\"";
|
||||||
std::string errmsg;
|
Statusbar::printf(msg,
|
||||||
if (myBrowser->deleteItem(i, errmsg))
|
itemTypeToString(item->value().type()),
|
||||||
{
|
wideShorten(get_name(item->value()), COLS-const_strlen(msg))
|
||||||
const char msg[] = "\"%1%\" deleted";
|
);
|
||||||
Statusbar::printf(msg, wideShorten(iname, COLS-const_strlen(msg)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Statusbar::print(errmsg);
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
if (myBrowser->isLocal())
|
|
||||||
myBrowser->GetDirectory(myBrowser->CurrentDir());
|
|
||||||
else
|
|
||||||
Mpd.UpdateDirectory(myBrowser->CurrentDir());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (myBrowser->isLocal())
|
||||||
|
myBrowser->getDirectory(myBrowser->currentDirectory());
|
||||||
|
else
|
||||||
|
Mpd.UpdateDirectory(myBrowser->currentDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeleteStoredPlaylist::canBeRun() const
|
bool DeleteStoredPlaylist::canBeRun() const
|
||||||
@@ -803,10 +815,6 @@ void SavePlaylist::run()
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!myBrowser->isLocal()
|
|
||||||
&& myBrowser->CurrentDir() == "/"
|
|
||||||
&& !myBrowser->main().empty())
|
|
||||||
myBrowser->GetDirectory(myBrowser->CurrentDir());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stop::run()
|
void Stop::run()
|
||||||
@@ -1142,7 +1150,7 @@ void TogglePlayingSongCentering::run()
|
|||||||
void UpdateDatabase::run()
|
void UpdateDatabase::run()
|
||||||
{
|
{
|
||||||
if (myScreen == myBrowser)
|
if (myScreen == myBrowser)
|
||||||
Mpd.UpdateDirectory(myBrowser->CurrentDir());
|
Mpd.UpdateDirectory(myBrowser->currentDirectory());
|
||||||
# ifdef HAVE_TAGLIB_H
|
# ifdef HAVE_TAGLIB_H
|
||||||
else if (myScreen == myTagEditor)
|
else if (myScreen == myTagEditor)
|
||||||
Mpd.UpdateDirectory(myTagEditor->CurrentDir());
|
Mpd.UpdateDirectory(myTagEditor->CurrentDir());
|
||||||
@@ -1169,8 +1177,7 @@ void JumpToPlayingSong::run()
|
|||||||
}
|
}
|
||||||
else if (myScreen == myBrowser)
|
else if (myScreen == myBrowser)
|
||||||
{
|
{
|
||||||
myBrowser->LocateSong(s);
|
myBrowser->locateSong(s);
|
||||||
drawHeader();
|
|
||||||
}
|
}
|
||||||
else if (myScreen == myLibrary)
|
else if (myScreen == myLibrary)
|
||||||
{
|
{
|
||||||
@@ -1413,7 +1420,7 @@ bool EditDirectoryName::canBeRun() const
|
|||||||
{
|
{
|
||||||
return ((myScreen == myBrowser
|
return ((myScreen == myBrowser
|
||||||
&& !myBrowser->main().empty()
|
&& !myBrowser->main().empty()
|
||||||
&& myBrowser->main().current().value().type == MPD::Item::Type::Directory)
|
&& myBrowser->main().current().value().type() == MPD::Item::Type::Directory)
|
||||||
# ifdef HAVE_TAGLIB_H
|
# ifdef HAVE_TAGLIB_H
|
||||||
|| (myScreen->activeWindow() == myTagEditor->Dirs
|
|| (myScreen->activeWindow() == myTagEditor->Dirs
|
||||||
&& !myTagEditor->Dirs->empty()
|
&& !myTagEditor->Dirs->empty()
|
||||||
@@ -1428,7 +1435,7 @@ void EditDirectoryName::run()
|
|||||||
// FIXME: use boost::filesystem and better error reporting
|
// FIXME: use boost::filesystem and better error reporting
|
||||||
if (myScreen == myBrowser)
|
if (myScreen == myBrowser)
|
||||||
{
|
{
|
||||||
std::string old_dir = myBrowser->main().current().value().name, new_dir;
|
std::string old_dir = myBrowser->main().current().value().directory().path(), new_dir;
|
||||||
{
|
{
|
||||||
Statusbar::ScopedLock lock;
|
Statusbar::ScopedLock lock;
|
||||||
Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
|
Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
|
||||||
@@ -1451,7 +1458,7 @@ void EditDirectoryName::run()
|
|||||||
Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
|
Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
|
||||||
if (!myBrowser->isLocal())
|
if (!myBrowser->isLocal())
|
||||||
Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
|
Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
|
||||||
myBrowser->GetDirectory(myBrowser->CurrentDir());
|
myBrowser->getDirectory(myBrowser->currentDirectory());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1495,18 +1502,17 @@ bool EditPlaylistName::canBeRun() const
|
|||||||
&& !myPlaylistEditor->Playlists.empty())
|
&& !myPlaylistEditor->Playlists.empty())
|
||||||
|| (myScreen == myBrowser
|
|| (myScreen == myBrowser
|
||||||
&& !myBrowser->main().empty()
|
&& !myBrowser->main().empty()
|
||||||
&& myBrowser->main().current().value().type == MPD::Item::Type::Playlist);
|
&& myBrowser->main().current().value().type() == MPD::Item::Type::Playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditPlaylistName::run()
|
void EditPlaylistName::run()
|
||||||
{
|
{
|
||||||
using Global::wFooter;
|
using Global::wFooter;
|
||||||
// FIXME: support local browser more generally
|
|
||||||
std::string old_name, new_name;
|
std::string old_name, new_name;
|
||||||
if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
|
if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
|
||||||
old_name = myPlaylistEditor->Playlists.current().value().path();
|
old_name = myPlaylistEditor->Playlists.current().value().path();
|
||||||
else
|
else
|
||||||
old_name = myBrowser->main().current().value().name;
|
old_name = myBrowser->main().current().value().playlist().path();
|
||||||
{
|
{
|
||||||
Statusbar::ScopedLock lock;
|
Statusbar::ScopedLock lock;
|
||||||
Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
|
Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
|
||||||
@@ -1517,8 +1523,6 @@ void EditPlaylistName::run()
|
|||||||
Mpd.Rename(old_name, new_name);
|
Mpd.Rename(old_name, new_name);
|
||||||
const char msg[] = "Playlist renamed to \"%1%\"";
|
const char msg[] = "Playlist renamed to \"%1%\"";
|
||||||
Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
|
Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
|
||||||
if (!myBrowser->isLocal())
|
|
||||||
myBrowser->GetDirectory("/");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1540,7 +1544,7 @@ bool JumpToBrowser::canBeRun() const
|
|||||||
void JumpToBrowser::run()
|
void JumpToBrowser::run()
|
||||||
{
|
{
|
||||||
auto s = currentSong(myScreen);
|
auto s = currentSong(myScreen);
|
||||||
myBrowser->LocateSong(*s);
|
myBrowser->locateSong(*s);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JumpToMediaLibrary::canBeRun() const
|
bool JumpToMediaLibrary::canBeRun() const
|
||||||
@@ -1557,12 +1561,12 @@ void JumpToMediaLibrary::run()
|
|||||||
bool JumpToPlaylistEditor::canBeRun() const
|
bool JumpToPlaylistEditor::canBeRun() const
|
||||||
{
|
{
|
||||||
return myScreen == myBrowser
|
return myScreen == myBrowser
|
||||||
&& myBrowser->main().current().value().type == MPD::Item::Type::Playlist;
|
&& myBrowser->main().current().value().type() == MPD::Item::Type::Playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JumpToPlaylistEditor::run()
|
void JumpToPlaylistEditor::run()
|
||||||
{
|
{
|
||||||
myPlaylistEditor->Locate(myBrowser->main().current().value().name);
|
myPlaylistEditor->Locate(myBrowser->main().current().value().playlist());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ToggleScreenLock::run()
|
void ToggleScreenLock::run()
|
||||||
@@ -2100,9 +2104,12 @@ void ToggleBrowserSortMode::run()
|
|||||||
}
|
}
|
||||||
withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
|
withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
|
||||||
if (Config.browser_sort_mode != SortMode::NoOp)
|
if (Config.browser_sort_mode != SortMode::NoOp)
|
||||||
std::sort(myBrowser->main().begin()+(myBrowser->CurrentDir() != "/"), myBrowser->main().end(),
|
{
|
||||||
|
size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
|
||||||
|
std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
|
||||||
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
|
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2400,7 +2407,7 @@ bool ChangeBrowseMode::canBeRun() const
|
|||||||
|
|
||||||
void ChangeBrowseMode::run()
|
void ChangeBrowseMode::run()
|
||||||
{
|
{
|
||||||
myBrowser->ChangeBrowseMode();
|
myBrowser->changeBrowseMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShowSearchEngine::canBeRun() const
|
bool ShowSearchEngine::canBeRun() const
|
||||||
|
|||||||
607
src/browser.cpp
607
src/browser.cpp
@@ -18,10 +18,12 @@
|
|||||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/locale/conversion.hpp>
|
#include <boost/locale/conversion.hpp>
|
||||||
#include <algorithm>
|
#include <time.h>
|
||||||
|
|
||||||
#include "browser.h"
|
#include "browser.h"
|
||||||
#include "charset.h"
|
#include "charset.h"
|
||||||
@@ -50,17 +52,27 @@ namespace fs = boost::filesystem;
|
|||||||
|
|
||||||
Browser *myBrowser;
|
Browser *myBrowser;
|
||||||
|
|
||||||
namespace {//
|
namespace {
|
||||||
|
|
||||||
std::set<std::string> SupportedExtensions;
|
std::set<std::string> lm_supported_extensions;
|
||||||
bool hasSupportedExtension(const std::string &file);
|
|
||||||
|
|
||||||
std::string ItemToString(const MPD::Item &item);
|
std::string realPath(bool local_browser, std::string path);
|
||||||
bool BrowserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter);
|
bool isStringParentDirectory(const std::string &directory);
|
||||||
|
bool isItemParentDirectory(const MPD::Item &item);
|
||||||
|
bool isRootDirectory(const std::string &directory);
|
||||||
|
bool isHidden(const fs::directory_iterator &entry);
|
||||||
|
bool hasSupportedExtension(const fs::directory_entry &entry);
|
||||||
|
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags);
|
||||||
|
void getLocalDirectory(MPD::ItemList &items, const std::string &directory);
|
||||||
|
void getLocalDirectoryRecursively(MPD::SongList &songs, const std::string &directory);
|
||||||
|
void clearDirectory(const std::string &directory);
|
||||||
|
|
||||||
|
std::string itemToString(const MPD::Item &item);
|
||||||
|
bool browserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Browser::Browser() : itsBrowseLocally(0), itsScrollBeginning(0), itsBrowsedDir("/")
|
Browser::Browser() : m_local_browser(false), m_scroll_beginning(0), m_current_directory("/")
|
||||||
{
|
{
|
||||||
w = NC::Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.browser_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border::None);
|
w = NC::Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.browser_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border::None);
|
||||||
w.setHighlightColor(Config.main_highlight_color);
|
w.setHighlightColor(Config.main_highlight_color);
|
||||||
@@ -97,7 +109,7 @@ void Browser::switchTo()
|
|||||||
SwitchTo::execute(this);
|
SwitchTo::execute(this);
|
||||||
|
|
||||||
if (w.empty())
|
if (w.empty())
|
||||||
GetDirectory(itsBrowsedDir);
|
getDirectory(m_current_directory);
|
||||||
else
|
else
|
||||||
markSongsInPlaylist(proxySongList());
|
markSongsInPlaylist(proxySongList());
|
||||||
|
|
||||||
@@ -107,7 +119,7 @@ void Browser::switchTo()
|
|||||||
std::wstring Browser::title()
|
std::wstring Browser::title()
|
||||||
{
|
{
|
||||||
std::wstring result = L"Browse: ";
|
std::wstring result = L"Browse: ";
|
||||||
result += Scroller(ToWString(itsBrowsedDir), itsScrollBeginning, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
|
result += Scroller(ToWString(m_current_directory), m_scroll_beginning, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,31 +129,29 @@ void Browser::enterPressed()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const MPD::Item &item = w.current().value();
|
const MPD::Item &item = w.current().value();
|
||||||
switch (item.type)
|
switch (item.type())
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Directory:
|
case MPD::Item::Type::Directory:
|
||||||
{
|
{
|
||||||
if (isParentDirectory(item))
|
getDirectory(item.directory().path());
|
||||||
GetDirectory(getParentDirectory(itsBrowsedDir), itsBrowsedDir);
|
|
||||||
else
|
|
||||||
GetDirectory(item.name, itsBrowsedDir);
|
|
||||||
drawHeader();
|
drawHeader();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPD::Item::Type::Song:
|
case MPD::Item::Type::Song:
|
||||||
{
|
{
|
||||||
addSongToPlaylist(item.song, true, -1);
|
addSongToPlaylist(item.song(), true, -1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPD::Item::Type::Playlist:
|
case MPD::Item::Type::Playlist:
|
||||||
{
|
{
|
||||||
MPD::SongList list(
|
MPD::SongList list(
|
||||||
std::make_move_iterator(Mpd.GetPlaylistContentNoInfo(item.name)),
|
std::make_move_iterator(Mpd.GetPlaylistContentNoInfo(item.playlist().path())),
|
||||||
std::make_move_iterator(MPD::SongIterator())
|
std::make_move_iterator(MPD::SongIterator())
|
||||||
);
|
);
|
||||||
|
// TODO: ask on failure if we want to continue
|
||||||
bool success = addSongsToPlaylist(list.begin(), list.end(), true, -1);
|
bool success = addSongsToPlaylist(list.begin(), list.end(), true, -1);
|
||||||
Statusbar::printf("Playlist \"%1%\" loaded%2%",
|
Statusbar::printf("Playlist \"%1%\" loaded%2%",
|
||||||
item.name, withErrors(success)
|
item.playlist().path(), withErrors(success)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,60 +162,47 @@ void Browser::spacePressed()
|
|||||||
if (w.empty())
|
if (w.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
size_t i = itsBrowsedDir != "/" ? 1 : 0;
|
size_t i = inRootDirectory() ? 0 : 1;
|
||||||
if (Config.space_selects && w.choice() >= i)
|
if (Config.space_selects && w.choice() >= i)
|
||||||
{
|
{
|
||||||
i = w.choice();
|
i = w.choice();
|
||||||
w.at(i).setSelected(!w.at(i).isSelected());
|
w[i].setSelected(!w[i].isSelected());
|
||||||
w.scroll(NC::Scroll::Down);
|
w.scroll(NC::Scroll::Down);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MPD::Item &item = w.current().value();
|
const MPD::Item &item = w.current().value();
|
||||||
|
// ignore parent directory
|
||||||
if (isParentDirectory(item))
|
if (isParentDirectory(item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (item.type)
|
switch (item.type())
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Directory:
|
case MPD::Item::Type::Directory:
|
||||||
{
|
{
|
||||||
bool success;
|
bool success = true;
|
||||||
# ifndef WIN32
|
if (m_local_browser)
|
||||||
if (isLocal())
|
|
||||||
{
|
{
|
||||||
MPD::SongList list;
|
MPD::SongList songs;
|
||||||
MPD::ItemList items;
|
getLocalDirectoryRecursively(songs, item.directory().path());
|
||||||
Statusbar::printf("Scanning directory \"%1%\"...", item.name);
|
success = addSongsToPlaylist(songs.begin(), songs.end(), false, -1);
|
||||||
myBrowser->GetLocalDirectory(items, item.name, 1);
|
|
||||||
list.reserve(items.size());
|
|
||||||
for (MPD::ItemList::const_iterator it = items.begin(); it != items.end(); ++it)
|
|
||||||
list.push_back(it->song);
|
|
||||||
success = addSongsToPlaylist(list.begin(), list.end(), false, -1);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
# endif // !WIN32
|
Mpd.Add(item.directory().path());
|
||||||
{
|
|
||||||
Mpd.Add(item.name);
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
Statusbar::printf("Directory \"%1%\" added%2%",
|
Statusbar::printf("Directory \"%1%\" added%2%",
|
||||||
item.name, withErrors(success)
|
item.directory().path(), withErrors(success)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPD::Item::Type::Song:
|
case MPD::Item::Type::Song:
|
||||||
{
|
addSongToPlaylist(item.song(), false);
|
||||||
addSongToPlaylist(item.song, false);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case MPD::Item::Type::Playlist:
|
case MPD::Item::Type::Playlist:
|
||||||
{
|
Mpd.LoadPlaylist(item.playlist().path());
|
||||||
Mpd.LoadPlaylist(item.name);
|
Statusbar::printf("Playlist \"%1%\" loaded", item.playlist().path());
|
||||||
Statusbar::printf("Playlist \"%1%\" loaded", item.name);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.scroll(NC::Scroll::Down);
|
w.scroll(NC::Scroll::Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,12 +213,12 @@ void Browser::mouseButtonPressed(MEVENT me)
|
|||||||
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
||||||
{
|
{
|
||||||
w.Goto(me.y);
|
w.Goto(me.y);
|
||||||
switch (w.current().value().type)
|
switch (w.current().value().type())
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Directory:
|
case MPD::Item::Type::Directory:
|
||||||
if (me.bstate & BUTTON1_PRESSED)
|
if (me.bstate & BUTTON1_PRESSED)
|
||||||
{
|
{
|
||||||
GetDirectory(w.current().value().name);
|
getDirectory(w.current().value().directory().path());
|
||||||
drawHeader();
|
drawHeader();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -273,7 +270,7 @@ void Browser::applyFilter(const std::string &filter)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
w.showAll();
|
w.showAll();
|
||||||
auto fun = boost::bind(BrowserEntryMatcher, _1, _2, true);
|
auto fun = boost::bind(browserEntryMatcher, _1, _2, true);
|
||||||
auto rx = RegexFilter<MPD::Item>(
|
auto rx = RegexFilter<MPD::Item>(
|
||||||
boost::regex(filter, Config.regex_type), fun);
|
boost::regex(filter, Config.regex_type), fun);
|
||||||
w.filter(w.begin(), w.end(), rx);
|
w.filter(w.begin(), w.end(), rx);
|
||||||
@@ -297,7 +294,7 @@ bool Browser::search(const std::string &constraint)
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto fun = boost::bind(BrowserEntryMatcher, _1, _2, false);
|
auto fun = boost::bind(browserEntryMatcher, _1, _2, false);
|
||||||
auto rx = RegexFilter<MPD::Item>(
|
auto rx = RegexFilter<MPD::Item>(
|
||||||
boost::regex(constraint, Config.regex_type), fun);
|
boost::regex(constraint, Config.regex_type), fun);
|
||||||
return w.search(w.begin(), w.end(), rx);
|
return w.search(w.begin(), w.end(), rx);
|
||||||
@@ -324,8 +321,8 @@ ProxySongList Browser::proxySongList()
|
|||||||
{
|
{
|
||||||
return ProxySongList(w, [](NC::Menu<MPD::Item>::Item &item) -> MPD::Song * {
|
return ProxySongList(w, [](NC::Menu<MPD::Item>::Item &item) -> MPD::Song * {
|
||||||
MPD::Song *ptr = 0;
|
MPD::Song *ptr = 0;
|
||||||
if (item.value().type == MPD::Item::Type::Song)
|
if (item.value().type() == MPD::Item::Type::Song)
|
||||||
ptr = &item.value().song;
|
ptr = const_cast<MPD::Song *>(&item.value().song());
|
||||||
return ptr;
|
return ptr;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -337,201 +334,167 @@ bool Browser::allowsSelection()
|
|||||||
|
|
||||||
void Browser::reverseSelection()
|
void Browser::reverseSelection()
|
||||||
{
|
{
|
||||||
reverseSelectionHelper(w.begin()+(itsBrowsedDir == "/" ? 0 : 1), w.end());
|
size_t offset = inRootDirectory() ? 0 : 1;
|
||||||
|
reverseSelectionHelper(w.begin()+offset, w.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
MPD::SongList Browser::getSelectedSongs()
|
MPD::SongList Browser::getSelectedSongs()
|
||||||
{
|
{
|
||||||
MPD::SongList result;
|
MPD::SongList songs;
|
||||||
auto item_handler = [this, &result](const MPD::Item &item) {
|
auto item_handler = [this, &songs](const MPD::Item &item) {
|
||||||
if (item.type == MPD::Item::Type::Directory)
|
switch (item.type())
|
||||||
{
|
{
|
||||||
# ifndef WIN32
|
case MPD::Item::Type::Directory:
|
||||||
if (isLocal())
|
if (m_local_browser)
|
||||||
{
|
{
|
||||||
MPD::ItemList list;
|
MPD::ItemList items;
|
||||||
GetLocalDirectory(list, item.name, true);
|
getLocalDirectoryRecursively(songs, item.directory().path());
|
||||||
for (auto it = list.begin(); it != list.end(); ++it)
|
}
|
||||||
result.push_back(it->song);
|
else
|
||||||
}
|
{
|
||||||
else
|
MPD::ItemIterator it = Mpd.GetDirectoryRecursive(item.directory().path()), end;
|
||||||
# endif // !WIN32
|
for (; it != end; ++it)
|
||||||
{
|
if (it->type() == MPD::Item::Type::Song)
|
||||||
Mpd.GetDirectoryRecursive(item.name, vectorMoveInserter(result));
|
songs.push_back(std::move(it->song()));
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else if (item.type == MPD::Item::Type::Song)
|
case MPD::Item::Type::Song:
|
||||||
result.push_back(item.song);
|
songs.push_back(item.song());
|
||||||
else if (item.type == MPD::Item::Type::Playlist)
|
break;
|
||||||
{
|
case MPD::Item::Type::Playlist:
|
||||||
std::copy(
|
std::copy(
|
||||||
std::make_move_iterator(Mpd.GetPlaylistContent(item.name)),
|
std::make_move_iterator(Mpd.GetPlaylistContent(item.playlist().path())),
|
||||||
std::make_move_iterator(MPD::SongIterator()),
|
std::make_move_iterator(MPD::SongIterator()),
|
||||||
std::back_inserter(result)
|
std::back_inserter(songs)
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (auto it = w.begin(); it != w.end(); ++it)
|
for (const auto &item : w)
|
||||||
if (it->isSelected())
|
if (item.isSelected())
|
||||||
item_handler(it->value());
|
item_handler(item.value());
|
||||||
// if no item is selected, add current one
|
// if no item is selected, add current one
|
||||||
if (result.empty() && !w.empty())
|
if (songs.empty() && !w.empty())
|
||||||
item_handler(w.current().value());
|
item_handler(w.current().value());
|
||||||
return result;
|
return songs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::fetchSupportedExtensions()
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool Browser::inRootDirectory()
|
||||||
{
|
{
|
||||||
SupportedExtensions.clear();
|
return isRootDirectory(m_current_directory);
|
||||||
Mpd.GetSupportedExtensions(SupportedExtensions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::LocateSong(const MPD::Song &s)
|
bool Browser::isParentDirectory(const MPD::Item &item)
|
||||||
|
{
|
||||||
|
return isItemParentDirectory(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Browser::currentDirectory()
|
||||||
|
{
|
||||||
|
return m_current_directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Browser::locateSong(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
if (s.getDirectory().empty())
|
if (s.getDirectory().empty())
|
||||||
return;
|
throw std::runtime_error("Song's directory is empty");
|
||||||
|
|
||||||
itsBrowseLocally = !s.isFromDatabase();
|
m_local_browser = !s.isFromDatabase();
|
||||||
|
|
||||||
if (myScreen != this)
|
if (myScreen != this)
|
||||||
switchTo();
|
switchTo();
|
||||||
|
|
||||||
if (itsBrowsedDir != s.getDirectory())
|
// change to relevant directory
|
||||||
GetDirectory(s.getDirectory());
|
if (m_current_directory != s.getDirectory())
|
||||||
for (size_t i = 0; i < w.size(); ++i)
|
|
||||||
{
|
{
|
||||||
if (w[i].value().type == MPD::Item::Type::Song && s == w[i].value().song)
|
getDirectory(s.getDirectory());
|
||||||
{
|
drawHeader();
|
||||||
w.highlight(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
drawHeader();
|
|
||||||
|
// highlight the item
|
||||||
|
auto begin = w.beginV(), end = w.endV();
|
||||||
|
auto it = std::find(begin, end, MPD::Item(s));
|
||||||
|
if (it != end)
|
||||||
|
w.highlight(it-begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::GetDirectory(std::string dir, std::string subdir)
|
void Browser::getDirectory(std::string directory)
|
||||||
{
|
{
|
||||||
if (dir.empty())
|
m_scroll_beginning = 0;
|
||||||
dir = "/";
|
|
||||||
|
|
||||||
int highlightme = -1;
|
|
||||||
itsScrollBeginning = 0;
|
|
||||||
if (itsBrowsedDir != dir)
|
|
||||||
w.reset();
|
|
||||||
itsBrowsedDir = dir;
|
|
||||||
|
|
||||||
w.clear();
|
w.clear();
|
||||||
|
|
||||||
if (dir != "/")
|
// reset the position if we change directories
|
||||||
|
if (m_current_directory != directory)
|
||||||
|
w.reset();
|
||||||
|
|
||||||
|
// check if it's a parent directory
|
||||||
|
if (isStringParentDirectory(directory))
|
||||||
{
|
{
|
||||||
MPD::Item parent;
|
directory.resize(directory.length()-3);
|
||||||
parent.name = "..";
|
directory = getParentDirectory(directory);
|
||||||
parent.type = MPD::Item::Type::Directory;
|
}
|
||||||
w.addItem(parent);
|
// when we go down to root, it can be empty
|
||||||
|
if (directory.empty())
|
||||||
|
directory = "/";
|
||||||
|
|
||||||
|
MPD::ItemList items;
|
||||||
|
if (m_local_browser)
|
||||||
|
getLocalDirectory(items, directory);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::copy(
|
||||||
|
std::make_move_iterator(Mpd.GetDirectory(directory)),
|
||||||
|
std::make_move_iterator(MPD::ItemIterator()),
|
||||||
|
std::back_inserter(items)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MPD::ItemList list;
|
// sort items
|
||||||
# ifndef WIN32
|
if (Config.browser_sort_mode != SortMode::NoOp)
|
||||||
if (isLocal())
|
{
|
||||||
GetLocalDirectory(list, itsBrowsedDir, false);
|
std::sort(items.begin(), items.end(),
|
||||||
else
|
|
||||||
Mpd.GetDirectory(dir, vectorMoveInserter(list));
|
|
||||||
# else
|
|
||||||
list = Mpd.GetDirectory(dir);
|
|
||||||
# endif // !WIN32
|
|
||||||
if (Config.browser_sort_mode != SortMode::NoOp && !isLocal()) // local directory is already sorted
|
|
||||||
std::sort(list.begin(), list.end(),
|
|
||||||
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
|
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (MPD::ItemList::iterator it = list.begin(); it != list.end(); ++it)
|
// if the requested directory is not root, add parent directory
|
||||||
|
if (!isRootDirectory(directory))
|
||||||
{
|
{
|
||||||
switch (it->type)
|
// make it so that display function doesn't have to handle special cases
|
||||||
|
w.addItem(MPD::Directory(directory + "/.."));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &item : items)
|
||||||
|
{
|
||||||
|
switch (item.type())
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Playlist:
|
case MPD::Item::Type::Playlist:
|
||||||
{
|
{
|
||||||
w.addItem(*it);
|
w.addItem(std::move(item));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPD::Item::Type::Directory:
|
case MPD::Item::Type::Directory:
|
||||||
{
|
{
|
||||||
if (it->name == subdir)
|
bool is_current = item.directory().path() == m_current_directory;
|
||||||
highlightme = w.size();
|
w.addItem(std::move(item));
|
||||||
w.addItem(*it);
|
if (is_current)
|
||||||
|
w.highlight(w.size()-1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPD::Item::Type::Song:
|
case MPD::Item::Type::Song:
|
||||||
{
|
{
|
||||||
w.addItem(*it, myPlaylist->checkForSong(it->song));
|
bool is_bold = myPlaylist->checkForSong(item.song());
|
||||||
|
w.addItem(std::move(item), is_bold);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (highlightme >= 0)
|
m_current_directory = directory;
|
||||||
w.highlight(highlightme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef WIN32
|
void Browser::changeBrowseMode()
|
||||||
void Browser::GetLocalDirectory(MPD::ItemList &v, const std::string &directory, bool recursively) const
|
|
||||||
{
|
|
||||||
size_t start_size = v.size();
|
|
||||||
fs::path dir(directory);
|
|
||||||
std::for_each(fs::directory_iterator(dir), fs::directory_iterator(), [&](fs::directory_entry &e) {
|
|
||||||
if (!Config.local_browser_show_hidden_files && e.path().filename().native()[0] == '.')
|
|
||||||
return;
|
|
||||||
MPD::Item item;
|
|
||||||
if (fs::is_directory(e))
|
|
||||||
{
|
|
||||||
if (recursively)
|
|
||||||
{
|
|
||||||
GetLocalDirectory(v, e.path().native(), true);
|
|
||||||
start_size = v.size();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.type = MPD::Item::Type::Directory;
|
|
||||||
item.name = e.path().native();
|
|
||||||
v.push_back(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (hasSupportedExtension(e.path().native()))
|
|
||||||
{
|
|
||||||
item.type = MPD::Item::Type::Song;
|
|
||||||
mpd_pair file_pair = { "file", e.path().native().c_str() };
|
|
||||||
item.song = mpd_song_begin(&file_pair);
|
|
||||||
// FIXME no tag reading for now
|
|
||||||
/*
|
|
||||||
# ifdef HAVE_TAGLIB_H
|
|
||||||
if (!recursively)
|
|
||||||
{
|
|
||||||
s->setMTime(fs::last_write_time(e.path()));
|
|
||||||
Tags::read(*s);
|
|
||||||
}
|
|
||||||
# endif // HAVE_TAGLIB_H
|
|
||||||
*/
|
|
||||||
v.push_back(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Config.browser_sort_mode != SortMode::NoOp)
|
|
||||||
std::sort(v.begin()+start_size, v.end(),
|
|
||||||
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Browser::ClearDirectory(const std::string &path) const
|
|
||||||
{
|
|
||||||
fs::path dir(path);
|
|
||||||
std::for_each(fs::directory_iterator(dir), fs::directory_iterator(), [&](fs::directory_entry &e) {
|
|
||||||
if (!fs::is_symlink(e) && fs::is_directory(e))
|
|
||||||
ClearDirectory(e.path().native());
|
|
||||||
const char msg[] = "Deleting \"%1%\"...";
|
|
||||||
Statusbar::printf(msg, wideShorten(e.path().native(), COLS-const_strlen(msg)));
|
|
||||||
fs::remove(e.path());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Browser::ChangeBrowseMode()
|
|
||||||
{
|
{
|
||||||
if (Mpd.GetHostname()[0] != '/')
|
if (Mpd.GetHostname()[0] != '/')
|
||||||
{
|
{
|
||||||
@@ -539,120 +502,216 @@ void Browser::ChangeBrowseMode()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
itsBrowseLocally = !itsBrowseLocally;
|
m_local_browser = !m_local_browser;
|
||||||
Statusbar::printf("Browse mode: %1%",
|
Statusbar::printf("Browse mode: %1%",
|
||||||
itsBrowseLocally ? "local filesystem" : "MPD database"
|
m_local_browser ? "local filesystem" : "MPD database"
|
||||||
);
|
);
|
||||||
if (itsBrowseLocally)
|
if (m_local_browser)
|
||||||
{
|
{
|
||||||
itsBrowsedDir = "~";
|
m_current_directory = "~";
|
||||||
expand_home(itsBrowsedDir);
|
expand_home(m_current_directory);
|
||||||
if (*itsBrowsedDir.rbegin() == '/')
|
|
||||||
itsBrowsedDir.resize(itsBrowsedDir.length()-1);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
itsBrowsedDir = "/";
|
m_current_directory = "/";
|
||||||
w.reset();
|
w.reset();
|
||||||
GetDirectory(itsBrowsedDir);
|
getDirectory(m_current_directory);
|
||||||
drawHeader();
|
drawHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Browser::deleteItem(const MPD::Item &item, std::string &errmsg)
|
void Browser::remove(const MPD::Item &item)
|
||||||
{
|
{
|
||||||
if (!Config.allow_for_physical_item_deletion)
|
if (!Config.allow_for_physical_item_deletion)
|
||||||
FatalError("Browser::deleteItem invoked with allow_for_physical_item_deletion = false");
|
throw std::runtime_error("physical deletion is forbidden");
|
||||||
if (isParentDirectory((item)))
|
if (isParentDirectory((item)))
|
||||||
FatalError("Parent directory passed to Browser::deleteItem");
|
throw std::runtime_error("deletion of parent directory is forbidden");
|
||||||
|
|
||||||
// playlist created by mpd
|
|
||||||
if (!isLocal() && item.type == MPD::Item::Type::Playlist && CurrentDir() == "/")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Mpd.DeletePlaylist(item.name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (MPD::ServerError &e)
|
|
||||||
{
|
|
||||||
// if there is no such mpd playlist, we assume it's users's playlist.
|
|
||||||
if (e.code() != MPD_SERVER_ERROR_NO_EXIST)
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string path;
|
std::string path;
|
||||||
if (!isLocal())
|
switch (item.type())
|
||||||
path = Config.mpd_music_dir;
|
|
||||||
path += item.type == MPD::Item::Type::Song ? item.song.getURI() : item.name;
|
|
||||||
|
|
||||||
bool rv;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (item.type == MPD::Item::Type::Directory)
|
|
||||||
ClearDirectory(path);
|
|
||||||
if (!boost::filesystem::exists(path))
|
|
||||||
{
|
|
||||||
errmsg = "No such item: " + path;
|
|
||||||
rv = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
boost::filesystem::remove(path);
|
|
||||||
rv = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (boost::filesystem::filesystem_error &err)
|
|
||||||
{
|
|
||||||
errmsg = err.what();
|
|
||||||
rv = false;
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
#endif // !WIN32
|
|
||||||
|
|
||||||
namespace {//
|
|
||||||
|
|
||||||
bool hasSupportedExtension(const std::string &file)
|
|
||||||
{
|
|
||||||
size_t last_dot = file.rfind(".");
|
|
||||||
if (last_dot > file.length())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::string ext = boost::locale::to_lower(file.substr(last_dot+1));
|
|
||||||
return SupportedExtensions.find(ext) != SupportedExtensions.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ItemToString(const MPD::Item &item)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
switch (item.type)
|
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Directory:
|
case MPD::Item::Type::Directory:
|
||||||
result = "[" + getBasename(item.name) + "]";
|
path = realPath(m_local_browser, item.directory().path());
|
||||||
|
clearDirectory(path);
|
||||||
|
fs::remove(path);
|
||||||
|
break;
|
||||||
|
case MPD::Item::Type::Song:
|
||||||
|
path = realPath(m_local_browser, item.song().getURI());
|
||||||
|
fs::remove(path);
|
||||||
|
break;
|
||||||
|
case MPD::Item::Type::Playlist:
|
||||||
|
path = item.playlist().path();
|
||||||
|
try {
|
||||||
|
Mpd.DeletePlaylist(path);
|
||||||
|
} catch (MPD::ServerError &e) {
|
||||||
|
// if there is no such mpd playlist, it's a local one
|
||||||
|
if (e.code() == MPD_SERVER_ERROR_NO_EXIST)
|
||||||
|
{
|
||||||
|
path = realPath(m_local_browser, std::move(path));
|
||||||
|
fs::remove(path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
void Browser::fetchSupportedExtensions()
|
||||||
|
{
|
||||||
|
lm_supported_extensions.clear();
|
||||||
|
Mpd.GetSupportedExtensions([&](std::string extension) {
|
||||||
|
lm_supported_extensions.insert("." + std::move(extension));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string realPath(bool local_browser, std::string path)
|
||||||
|
{
|
||||||
|
if (!local_browser)
|
||||||
|
path = Config.mpd_music_dir + path;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isStringParentDirectory(const std::string &directory)
|
||||||
|
{
|
||||||
|
return boost::algorithm::ends_with(directory, "/..");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isItemParentDirectory(const MPD::Item &item)
|
||||||
|
{
|
||||||
|
return item.type() == MPD::Item::Type::Directory
|
||||||
|
&& isStringParentDirectory(item.directory().path());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRootDirectory(const std::string &directory)
|
||||||
|
{
|
||||||
|
return directory == "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHidden(const fs::directory_iterator &entry)
|
||||||
|
{
|
||||||
|
return entry->path().filename().native()[0] == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasSupportedExtension(const fs::directory_entry &entry)
|
||||||
|
{
|
||||||
|
return lm_supported_extensions.find(entry.path().extension().native())
|
||||||
|
!= lm_supported_extensions.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags)
|
||||||
|
{
|
||||||
|
mpd_pair pair = { "file", entry.path().c_str() };
|
||||||
|
mpd_song *s = mpd_song_begin(&pair);
|
||||||
|
if (s == nullptr)
|
||||||
|
throw std::runtime_error("invalid path: " + entry.path().native());
|
||||||
|
# ifdef HAVE_TAGLIB_H
|
||||||
|
if (read_tags)
|
||||||
|
{
|
||||||
|
Tags::setAttribute(s, "Last-Modified",
|
||||||
|
timeFormat("%Y-%m-%dT%H:%M:%SZ", fs::last_write_time(entry.path()))
|
||||||
|
);
|
||||||
|
// read tags
|
||||||
|
Tags::read(s);
|
||||||
|
}
|
||||||
|
# endif // HAVE_TAGLIB_H
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getLocalDirectory(MPD::ItemList &items, const std::string &directory)
|
||||||
|
{
|
||||||
|
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
|
||||||
|
{
|
||||||
|
if (!Config.local_browser_show_hidden_files && isHidden(entry))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (fs::is_directory(*entry))
|
||||||
|
{
|
||||||
|
items.push_back(MPD::Directory(
|
||||||
|
entry->path().native(),
|
||||||
|
fs::last_write_time(entry->path())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else if (hasSupportedExtension(*entry))
|
||||||
|
items.push_back(getLocalSong(*entry, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getLocalDirectoryRecursively(MPD::SongList &songs, const std::string &directory)
|
||||||
|
{
|
||||||
|
size_t sort_offset = songs.size();
|
||||||
|
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
|
||||||
|
{
|
||||||
|
if (!Config.local_browser_show_hidden_files && isHidden(entry))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (fs::is_directory(*entry))
|
||||||
|
{
|
||||||
|
getLocalDirectoryRecursively(songs, entry->path().native());
|
||||||
|
sort_offset = songs.size();
|
||||||
|
}
|
||||||
|
else if (hasSupportedExtension(*entry))
|
||||||
|
songs.push_back(getLocalSong(*entry, false));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Config.browser_sort_mode != SortMode::NoOp)
|
||||||
|
{
|
||||||
|
std::sort(songs.begin()+sort_offset, songs.end(),
|
||||||
|
LocaleBasedSorting(std::locale(), Config.ignore_leading_the)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearDirectory(const std::string &directory)
|
||||||
|
{
|
||||||
|
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
|
||||||
|
{
|
||||||
|
if (!fs::is_symlink(*entry) && fs::is_directory(*entry))
|
||||||
|
clearDirectory(entry->path().native());
|
||||||
|
const char msg[] = "Deleting \"%1%\"...";
|
||||||
|
Statusbar::printf(msg, wideShorten(entry->path().native(), COLS-const_strlen(msg)));
|
||||||
|
fs::remove(entry->path());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
std::string itemToString(const MPD::Item &item)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
switch (item.type())
|
||||||
|
{
|
||||||
|
case MPD::Item::Type::Directory:
|
||||||
|
result = "[" + getBasename(item.directory().path()) + "]";
|
||||||
break;
|
break;
|
||||||
case MPD::Item::Type::Song:
|
case MPD::Item::Type::Song:
|
||||||
switch (Config.browser_display_mode)
|
switch (Config.browser_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
result = item.song.toString(Config.song_list_format_dollar_free, Config.tags_separator);
|
result = item.song().toString(Config.song_list_format_dollar_free, Config.tags_separator);
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
result = item.song.toString(Config.song_in_columns_to_string_format, Config.tags_separator);
|
result = item.song().toString(Config.song_in_columns_to_string_format, Config.tags_separator);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MPD::Item::Type::Playlist:
|
case MPD::Item::Type::Playlist:
|
||||||
result = Config.browser_playlist_prefix.str() + getBasename(item.name);
|
result = Config.browser_playlist_prefix.str();
|
||||||
|
result += getBasename(item.playlist().path());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter)
|
bool browserEntryMatcher(const boost::regex &rx, const MPD::Item &item, bool filter)
|
||||||
{
|
{
|
||||||
if (Browser::isParentDirectory(item))
|
if (isItemParentDirectory(item))
|
||||||
return filter;
|
return filter;
|
||||||
return boost::regex_search(ItemToString(item), rx);
|
return boost::regex_search(itemToString(item), rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,31 +63,25 @@ struct Browser: Screen<NC::Menu<MPD::Item>>, Filterable, HasSongs, Searchable, T
|
|||||||
virtual MPD::SongList getSelectedSongs() OVERRIDE;
|
virtual MPD::SongList getSelectedSongs() OVERRIDE;
|
||||||
|
|
||||||
// private members
|
// private members
|
||||||
const std::string &CurrentDir() { return itsBrowsedDir; }
|
bool inRootDirectory();
|
||||||
|
bool isParentDirectory(const MPD::Item &item);
|
||||||
|
const std::string ¤tDirectory();
|
||||||
|
|
||||||
void fetchSupportedExtensions();
|
bool isLocal() { return m_local_browser; }
|
||||||
|
void locateSong(const MPD::Song &s);
|
||||||
|
void getDirectory(std::string directory);
|
||||||
|
void changeBrowseMode();
|
||||||
|
void remove(const MPD::Item &item);
|
||||||
|
|
||||||
bool isLocal() { return itsBrowseLocally; }
|
static void fetchSupportedExtensions();
|
||||||
void LocateSong(const MPD::Song &);
|
|
||||||
void GetDirectory(std::string, std::string = "/");
|
|
||||||
# ifndef WIN32
|
|
||||||
void GetLocalDirectory(MPD::ItemList &, const std::string &, bool) const;
|
|
||||||
void ClearDirectory(const std::string &) const;
|
|
||||||
void ChangeBrowseMode();
|
|
||||||
bool deleteItem(const MPD::Item &, std::string &errmsg);
|
|
||||||
# endif // !WIN32
|
|
||||||
|
|
||||||
static bool isParentDirectory(const MPD::Item &item) {
|
|
||||||
return item.type == MPD::Item::Type::Directory && item.name == "..";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool isLockable() OVERRIDE { return true; }
|
virtual bool isLockable() OVERRIDE { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool itsBrowseLocally;
|
bool m_local_browser;
|
||||||
size_t itsScrollBeginning;
|
size_t m_scroll_beginning;
|
||||||
std::string itsBrowsedDir;
|
std::string m_current_directory;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Browser *myBrowser;
|
extern Browser *myBrowser;
|
||||||
|
|||||||
@@ -384,27 +384,27 @@ void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
|
|||||||
void Display::Items(NC::Menu<MPD::Item> &menu, const ProxySongList &pl)
|
void Display::Items(NC::Menu<MPD::Item> &menu, const ProxySongList &pl)
|
||||||
{
|
{
|
||||||
const MPD::Item &item = menu.drawn()->value();
|
const MPD::Item &item = menu.drawn()->value();
|
||||||
switch (item.type)
|
switch (item.type())
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Directory:
|
case MPD::Item::Type::Directory:
|
||||||
menu << "["
|
menu << "["
|
||||||
<< Charset::utf8ToLocale(getBasename(item.name))
|
<< Charset::utf8ToLocale(getBasename(item.directory().path()))
|
||||||
<< "]";
|
<< "]";
|
||||||
break;
|
break;
|
||||||
case MPD::Item::Type::Song:
|
case MPD::Item::Type::Song:
|
||||||
switch (Config.browser_display_mode)
|
switch (Config.browser_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
showSongs(menu, item.song, pl, Config.song_list_format);
|
showSongs(menu, item.song(), pl, Config.song_list_format);
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
showSongsInColumns(menu, item.song, pl);
|
showSongsInColumns(menu, item.song(), pl);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MPD::Item::Type::Playlist:
|
case MPD::Item::Type::Playlist:
|
||||||
menu << Config.browser_playlist_prefix
|
menu << Config.browser_playlist_prefix
|
||||||
<< Charset::utf8ToLocale(getBasename(item.name));
|
<< Charset::utf8ToLocale(getBasename(item.playlist().path()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
@@ -63,6 +64,15 @@ bool addSongToPlaylist(const MPD::Song &s, bool play, int position)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string timeFormat(const char *format, time_t t)
|
||||||
|
{
|
||||||
|
char result[32];
|
||||||
|
tm tinfo;
|
||||||
|
localtime_r(&t, &tinfo);
|
||||||
|
strftime(result, sizeof(result), format, &tinfo);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Timestamp(time_t t)
|
std::string Timestamp(time_t t)
|
||||||
{
|
{
|
||||||
char result[32];
|
char result[32];
|
||||||
|
|||||||
@@ -550,6 +550,8 @@ inline const char *withErrors(bool success)
|
|||||||
|
|
||||||
bool addSongToPlaylist(const MPD::Song &s, bool play, int position = -1);
|
bool addSongToPlaylist(const MPD::Song &s, bool play, int position = -1);
|
||||||
|
|
||||||
|
std::string timeFormat(const char *format, time_t t);
|
||||||
|
|
||||||
std::string Timestamp(time_t t);
|
std::string Timestamp(time_t t);
|
||||||
|
|
||||||
void markSongsInPlaylist(ProxySongList pl);
|
void markSongsInPlaylist(ProxySongList pl);
|
||||||
|
|||||||
@@ -259,8 +259,13 @@ void MediaLibrary::update()
|
|||||||
Albums.clearSearchResults();
|
Albums.clearSearchResults();
|
||||||
m_albums_update_request = false;
|
m_albums_update_request = false;
|
||||||
std::map<std::tuple<std::string, std::string, std::string>, time_t> albums;
|
std::map<std::tuple<std::string, std::string, std::string>, time_t> albums;
|
||||||
Mpd.GetDirectoryRecursive("/", [&albums](MPD::Song s) {
|
MPD::ItemIterator item = Mpd.GetDirectoryRecursive("/"), end;
|
||||||
|
for (; item != end; ++item)
|
||||||
|
{
|
||||||
|
if (item->type() != MPD::Item::Type::Song)
|
||||||
|
continue;
|
||||||
unsigned idx = 0;
|
unsigned idx = 0;
|
||||||
|
const MPD::Song &s = item->song();
|
||||||
std::string tag = s.get(Config.media_lib_primary_tag, idx);
|
std::string tag = s.get(Config.media_lib_primary_tag, idx);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -272,7 +277,7 @@ void MediaLibrary::update()
|
|||||||
it->second = s.getMTime();
|
it->second = s.getMTime();
|
||||||
}
|
}
|
||||||
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
|
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
|
||||||
});
|
}
|
||||||
withUnfilteredMenuReapplyFilter(Albums, [this, &albums]() {
|
withUnfilteredMenuReapplyFilter(Albums, [this, &albums]() {
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
for (auto it = albums.begin(); it != albums.end(); ++it, ++idx)
|
for (auto it = albums.begin(); it != albums.end(); ++it, ++idx)
|
||||||
@@ -303,8 +308,13 @@ void MediaLibrary::update()
|
|||||||
std::map<std::string, time_t> tags;
|
std::map<std::string, time_t> tags;
|
||||||
if (Config.media_library_sort_by_mtime)
|
if (Config.media_library_sort_by_mtime)
|
||||||
{
|
{
|
||||||
Mpd.GetDirectoryRecursive("/", [&tags](MPD::Song s) {
|
MPD::ItemIterator item = Mpd.GetDirectoryRecursive("/"), end;
|
||||||
|
for (; item != end; ++item)
|
||||||
|
{
|
||||||
|
if (item->type() != MPD::Item::Type::Song)
|
||||||
|
continue;
|
||||||
unsigned idx = 0;
|
unsigned idx = 0;
|
||||||
|
const MPD::Song &s = item->song();
|
||||||
std::string tag = s.get(Config.media_lib_primary_tag, idx);
|
std::string tag = s.get(Config.media_lib_primary_tag, idx);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -315,7 +325,7 @@ void MediaLibrary::update()
|
|||||||
it->second = std::max(it->second, s.getMTime());
|
it->second = std::max(it->second, s.getMTime());
|
||||||
}
|
}
|
||||||
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
|
while (!(tag = s.get(Config.media_lib_primary_tag, ++idx)).empty());
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -336,13 +336,13 @@ SongIterator Connection::GetPlaylistContentNoInfo(const std::string &path)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::GetSupportedExtensions(std::set<std::string> &acc)
|
void Connection::GetSupportedExtensions(StringConsumer f)
|
||||||
{
|
{
|
||||||
prechecksNoCommandsList();
|
prechecksNoCommandsList();
|
||||||
mpd_send_command(m_connection.get(), "decoders", NULL);
|
mpd_send_command(m_connection.get(), "decoders", NULL);
|
||||||
while (mpd_pair *pair = mpd_recv_pair_named(m_connection.get(), "suffix"))
|
while (mpd_pair *pair = mpd_recv_pair_named(m_connection.get(), "suffix"))
|
||||||
{
|
{
|
||||||
acc.insert(pair->value);
|
f(pair->value);
|
||||||
mpd_return_pair(m_connection.get(), pair);
|
mpd_return_pair(m_connection.get(), pair);
|
||||||
}
|
}
|
||||||
mpd_response_finish(m_connection.get());
|
mpd_response_finish(m_connection.get());
|
||||||
@@ -677,48 +677,20 @@ void Connection::CommitSearchTags(StringConsumer f)
|
|||||||
checkErrors();
|
checkErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::GetDirectory(const std::string &directory, ItemConsumer f)
|
ItemIterator Connection::GetDirectory(const std::string &directory)
|
||||||
{
|
{
|
||||||
prechecksNoCommandsList();
|
prechecksNoCommandsList();
|
||||||
mpd_send_list_meta(m_connection.get(), directory.c_str());
|
mpd_send_list_meta(m_connection.get(), directory.c_str());
|
||||||
while (mpd_entity *item = mpd_recv_entity(m_connection.get()))
|
|
||||||
{
|
|
||||||
Item it;
|
|
||||||
switch (mpd_entity_get_type(item))
|
|
||||||
{
|
|
||||||
case MPD_ENTITY_TYPE_DIRECTORY:
|
|
||||||
it.name = mpd_directory_get_path(mpd_entity_get_directory(item));
|
|
||||||
it.type = MPD::Item::Type::Directory;
|
|
||||||
break;
|
|
||||||
case MPD_ENTITY_TYPE_SONG:
|
|
||||||
it.song = Song(mpd_song_dup(mpd_entity_get_song(item)));
|
|
||||||
it.type = MPD::Item::Type::Song;
|
|
||||||
break;
|
|
||||||
case MPD_ENTITY_TYPE_PLAYLIST:
|
|
||||||
it.name = mpd_playlist_get_path(mpd_entity_get_playlist(item));
|
|
||||||
it.type = MPD::Item::Type::Playlist;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
mpd_entity_free(item);
|
|
||||||
f(std::move(it));
|
|
||||||
}
|
|
||||||
mpd_response_finish(m_connection.get());
|
|
||||||
checkErrors();
|
checkErrors();
|
||||||
|
return ItemIterator(m_connection.get(), mpd_recv_entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::GetDirectoryRecursive(const std::string &directory, SongConsumer f)
|
ItemIterator Connection::GetDirectoryRecursive(const std::string &directory)
|
||||||
{
|
{
|
||||||
prechecksNoCommandsList();
|
prechecksNoCommandsList();
|
||||||
mpd_send_list_all_meta(m_connection.get(), directory.c_str());
|
mpd_send_list_all_meta(m_connection.get(), directory.c_str());
|
||||||
while (mpd_entity *e = mpd_recv_entity(m_connection.get())) {
|
|
||||||
if (mpd_entity_get_type(e) == MPD_ENTITY_TYPE_SONG)
|
|
||||||
f(Song(mpd_song_dup(mpd_entity_get_song(e))));
|
|
||||||
mpd_entity_free(e);
|
|
||||||
}
|
|
||||||
mpd_response_finish(m_connection.get());
|
|
||||||
checkErrors();
|
checkErrors();
|
||||||
|
return ItemIterator(m_connection.get(), mpd_recv_entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::GetDirectories(const std::string &directory, StringConsumer f)
|
void Connection::GetDirectories(const std::string &directory, StringConsumer f)
|
||||||
|
|||||||
283
src/mpdpp.h
283
src/mpdpp.h
@@ -120,72 +120,172 @@ private:
|
|||||||
std::shared_ptr<mpd_status> m_status;
|
std::shared_ptr<mpd_status> m_status;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Playlist
|
struct Directory
|
||||||
{
|
{
|
||||||
Playlist() { }
|
Directory()
|
||||||
Playlist(mpd_playlist *playlist) : m_playlist(playlist, mpd_playlist_free) { }
|
: m_last_modified(0)
|
||||||
|
{ }
|
||||||
Playlist(const Playlist &rhs) : m_playlist(rhs.m_playlist) { }
|
Directory(const mpd_directory *directory)
|
||||||
Playlist(Playlist &&rhs) : m_playlist(std::move(rhs.m_playlist)) { }
|
|
||||||
Playlist &operator=(Playlist rhs)
|
|
||||||
{
|
{
|
||||||
m_playlist = std::move(rhs.m_playlist);
|
assert(directory != nullptr);
|
||||||
return *this;
|
m_path = mpd_directory_get_path(directory);
|
||||||
|
m_last_modified = mpd_directory_get_last_modified(directory);
|
||||||
}
|
}
|
||||||
|
Directory(std::string path, time_t last_modified = 0)
|
||||||
|
: m_path(std::move(path))
|
||||||
|
, m_last_modified(last_modified)
|
||||||
|
{ }
|
||||||
|
|
||||||
bool operator==(const Playlist &rhs)
|
bool operator==(const Directory &rhs) const
|
||||||
{
|
{
|
||||||
if (empty() && rhs.empty())
|
return m_path == rhs.m_path
|
||||||
return true;
|
&& m_last_modified == rhs.m_last_modified;
|
||||||
else if (!empty() && !rhs.empty())
|
|
||||||
return strcmp(path(), rhs.path()) == 0
|
|
||||||
&& lastModified() == rhs.lastModified();
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
bool operator!=(const Playlist &rhs)
|
bool operator!=(const Directory &rhs) const
|
||||||
{
|
{
|
||||||
return !(*this == rhs);
|
return !(*this == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *path() const
|
const std::string &path() const
|
||||||
{
|
{
|
||||||
assert(m_playlist.get() != nullptr);
|
return m_path;
|
||||||
return mpd_playlist_get_path(m_playlist.get());
|
|
||||||
}
|
}
|
||||||
time_t lastModified() const
|
time_t lastModified() const
|
||||||
{
|
{
|
||||||
assert(m_playlist.get() != nullptr);
|
return m_last_modified;
|
||||||
return mpd_playlist_get_last_modified(m_playlist.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const { return m_playlist.get() == nullptr; }
|
private:
|
||||||
|
std::string m_path;
|
||||||
|
time_t m_last_modified;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Playlist
|
||||||
|
{
|
||||||
|
Playlist()
|
||||||
|
: m_last_modified(0)
|
||||||
|
{ }
|
||||||
|
Playlist(const mpd_playlist *playlist)
|
||||||
|
{
|
||||||
|
assert(playlist != nullptr);
|
||||||
|
m_path = mpd_playlist_get_path(playlist);
|
||||||
|
m_last_modified = mpd_playlist_get_last_modified(playlist);
|
||||||
|
}
|
||||||
|
Playlist(std::string path, time_t last_modified = 0)
|
||||||
|
: m_path(std::move(path))
|
||||||
|
, m_last_modified(last_modified)
|
||||||
|
{
|
||||||
|
if (m_path.empty())
|
||||||
|
throw std::runtime_error("empty path");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Playlist &rhs) const
|
||||||
|
{
|
||||||
|
return m_path == rhs.m_path
|
||||||
|
&& m_last_modified == rhs.m_last_modified;
|
||||||
|
}
|
||||||
|
bool operator!=(const Playlist &rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &path() const
|
||||||
|
{
|
||||||
|
return m_path;
|
||||||
|
}
|
||||||
|
time_t lastModified() const
|
||||||
|
{
|
||||||
|
return m_last_modified;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<mpd_playlist> m_playlist;
|
std::string m_path;
|
||||||
|
time_t m_last_modified;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Item
|
struct Item
|
||||||
{
|
{
|
||||||
enum class Type { Directory, Playlist, Song };
|
enum class Type { Directory, Song, Playlist };
|
||||||
|
|
||||||
Song song;
|
Item(mpd_entity *entity)
|
||||||
Type type;
|
{
|
||||||
std::string name;
|
assert(entity != nullptr);
|
||||||
|
switch (mpd_entity_get_type(entity))
|
||||||
|
{
|
||||||
|
case MPD_ENTITY_TYPE_DIRECTORY:
|
||||||
|
m_type = Type::Directory;
|
||||||
|
m_directory = Directory(mpd_entity_get_directory(entity));
|
||||||
|
break;
|
||||||
|
case MPD_ENTITY_TYPE_SONG:
|
||||||
|
m_type = Type::Song;
|
||||||
|
m_song = Song(mpd_song_dup(mpd_entity_get_song(entity)));
|
||||||
|
break;
|
||||||
|
case MPD_ENTITY_TYPE_PLAYLIST:
|
||||||
|
m_type = Type::Playlist;
|
||||||
|
m_playlist = Playlist(mpd_entity_get_playlist(entity));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("unknown mpd_entity type");
|
||||||
|
}
|
||||||
|
mpd_entity_free(entity);
|
||||||
|
}
|
||||||
|
Item(Directory directory_)
|
||||||
|
: m_type(Type::Directory)
|
||||||
|
, m_directory(std::move(directory_))
|
||||||
|
{ }
|
||||||
|
Item(Song song_)
|
||||||
|
: m_type(Type::Song)
|
||||||
|
, m_song(std::move(song_))
|
||||||
|
{ }
|
||||||
|
Item(Playlist playlist_)
|
||||||
|
: m_type(Type::Playlist)
|
||||||
|
, m_playlist(std::move(playlist_))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
bool operator==(const Item &rhs) const
|
||||||
|
{
|
||||||
|
return m_directory == rhs.m_directory
|
||||||
|
&& m_song == rhs.m_song
|
||||||
|
&& m_playlist == rhs.m_playlist;
|
||||||
|
}
|
||||||
|
bool operator!=(const Item &rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type() const
|
||||||
|
{
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
const Directory &directory() const
|
||||||
|
{
|
||||||
|
assert(m_type == Type::Directory);
|
||||||
|
return m_directory;
|
||||||
|
}
|
||||||
|
const Song &song() const
|
||||||
|
{
|
||||||
|
assert(m_type == Type::Song);
|
||||||
|
return m_song;
|
||||||
|
}
|
||||||
|
const Playlist &playlist() const
|
||||||
|
{
|
||||||
|
assert(m_type == Type::Playlist);
|
||||||
|
return m_playlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Type m_type;
|
||||||
|
Directory m_directory;
|
||||||
|
Song m_song;
|
||||||
|
Playlist m_playlist;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Output
|
struct Output
|
||||||
{
|
{
|
||||||
Output() { }
|
Output() { }
|
||||||
Output(mpd_output *output) : m_output(output, mpd_output_free) { }
|
Output(mpd_output *output)
|
||||||
|
: m_output(output, mpd_output_free)
|
||||||
Output(const Output &rhs) : m_output(rhs.m_output) { }
|
{ }
|
||||||
Output(Output &&rhs) : m_output(std::move(rhs.m_output)) { }
|
|
||||||
Output &operator=(Output rhs)
|
|
||||||
{
|
|
||||||
m_output = std::move(rhs.m_output);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Output &rhs) const
|
bool operator==(const Output &rhs) const
|
||||||
{
|
{
|
||||||
@@ -230,34 +330,32 @@ typedef std::vector<std::string> StringList;
|
|||||||
typedef std::vector<Output> OutputList;
|
typedef std::vector<Output> OutputList;
|
||||||
|
|
||||||
template <typename DestT, typename SourceT>
|
template <typename DestT, typename SourceT>
|
||||||
struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
|
struct Iterator: std::iterator<std::input_iterator_tag, DestT>
|
||||||
{
|
{
|
||||||
typedef SourceT *(*SourceFetcher)(mpd_connection *);
|
typedef SourceT *(*SourceFetcher)(mpd_connection *);
|
||||||
|
|
||||||
friend class Connection;
|
Iterator()
|
||||||
|
: m_state(nullptr)
|
||||||
Iterator() : m_connection(nullptr), m_fetch_source(nullptr) { }
|
{ }
|
||||||
~Iterator()
|
Iterator(mpd_connection *connection, SourceFetcher fetch_source)
|
||||||
|
: m_state(std::make_shared<State>(connection, fetch_source))
|
||||||
{
|
{
|
||||||
if (m_connection != nullptr)
|
// get the first element
|
||||||
finish();
|
++*this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void finish()
|
void finish()
|
||||||
{
|
{
|
||||||
// clean up
|
// change the iterator into end iterator
|
||||||
assert(m_connection != nullptr);
|
m_state = nullptr;
|
||||||
mpd_response_finish(m_connection);
|
|
||||||
m_object = DestT();
|
|
||||||
m_connection = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DestT &operator*() const
|
DestT &operator*() const
|
||||||
{
|
{
|
||||||
assert(m_connection != nullptr);
|
if (!m_state)
|
||||||
if (m_object.empty())
|
throw std::runtime_error("no object associated with the iterator");
|
||||||
throw std::runtime_error("empty object");
|
assert(m_state->hasObject());
|
||||||
return const_cast<DestT &>(m_object);
|
return m_state->getObject();
|
||||||
}
|
}
|
||||||
DestT *operator->() const
|
DestT *operator->() const
|
||||||
{
|
{
|
||||||
@@ -266,11 +364,10 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
|
|||||||
|
|
||||||
Iterator &operator++()
|
Iterator &operator++()
|
||||||
{
|
{
|
||||||
assert(m_connection != nullptr);
|
assert(m_state);
|
||||||
assert(m_fetch_source != nullptr);
|
auto src = m_state->fetchSource();
|
||||||
auto src = m_fetch_source(m_connection);
|
|
||||||
if (src != nullptr)
|
if (src != nullptr)
|
||||||
m_object = DestT(src);
|
m_state->setObject(src);
|
||||||
else
|
else
|
||||||
finish();
|
finish();
|
||||||
return *this;
|
return *this;
|
||||||
@@ -284,8 +381,7 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
|
|||||||
|
|
||||||
bool operator==(const Iterator &rhs)
|
bool operator==(const Iterator &rhs)
|
||||||
{
|
{
|
||||||
return m_connection == rhs.m_connection
|
return m_state == rhs.m_state;
|
||||||
&& m_object == rhs.m_object;
|
|
||||||
}
|
}
|
||||||
bool operator!=(const Iterator &rhs)
|
bool operator!=(const Iterator &rhs)
|
||||||
{
|
{
|
||||||
@@ -293,27 +389,66 @@ struct Iterator : std::iterator<std::forward_iterator_tag, DestT>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Iterator(mpd_connection *connection, SourceFetcher fetch_source)
|
struct State
|
||||||
: m_connection(connection), m_fetch_source(fetch_source)
|
|
||||||
{
|
{
|
||||||
// get the first element
|
State(mpd_connection *conn, SourceFetcher fetch_source)
|
||||||
++*this;
|
: m_connection(conn)
|
||||||
}
|
, m_fetch_source(fetch_source)
|
||||||
|
{
|
||||||
|
assert(m_connection != nullptr);
|
||||||
|
assert(m_fetch_source != nullptr);
|
||||||
|
}
|
||||||
|
~State()
|
||||||
|
{
|
||||||
|
mpd_response_finish(m_connection);
|
||||||
|
}
|
||||||
|
|
||||||
mpd_connection *m_connection;
|
bool operator==(const State &rhs) const
|
||||||
SourceFetcher m_fetch_source;
|
{
|
||||||
DestT m_object;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<State> m_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef Iterator<Item, mpd_entity> ItemIterator;
|
||||||
typedef Iterator<Output, mpd_output> OutputIterator;
|
typedef Iterator<Output, mpd_output> OutputIterator;
|
||||||
typedef Iterator<Playlist, mpd_playlist> PlaylistIterator;
|
typedef Iterator<Playlist, mpd_playlist> PlaylistIterator;
|
||||||
typedef Iterator<Song, mpd_song> SongIterator;
|
typedef Iterator<Song, mpd_song> SongIterator;
|
||||||
|
|
||||||
class Connection
|
class Connection
|
||||||
{
|
{
|
||||||
typedef std::function<void(Item)> ItemConsumer;
|
|
||||||
typedef std::function<void(Output)> OutputConsumer;
|
|
||||||
typedef std::function<void(Song)> SongConsumer;
|
|
||||||
typedef std::function<void(std::string)> StringConsumer;
|
typedef std::function<void(std::string)> StringConsumer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -362,7 +497,7 @@ public:
|
|||||||
SongIterator GetPlaylistContent(const std::string &name);
|
SongIterator GetPlaylistContent(const std::string &name);
|
||||||
SongIterator GetPlaylistContentNoInfo(const std::string &name);
|
SongIterator GetPlaylistContentNoInfo(const std::string &name);
|
||||||
|
|
||||||
void GetSupportedExtensions(std::set<std::string> &);
|
void GetSupportedExtensions(StringConsumer f);
|
||||||
|
|
||||||
void SetRepeat(bool);
|
void SetRepeat(bool);
|
||||||
void SetRandom(bool);
|
void SetRandom(bool);
|
||||||
@@ -405,8 +540,8 @@ public:
|
|||||||
|
|
||||||
PlaylistIterator GetPlaylists();
|
PlaylistIterator GetPlaylists();
|
||||||
void GetList(mpd_tag_type type, StringConsumer f);
|
void GetList(mpd_tag_type type, StringConsumer f);
|
||||||
void GetDirectory(const std::string &directory, ItemConsumer f);
|
ItemIterator GetDirectory(const std::string &directory);
|
||||||
void GetDirectoryRecursive(const std::string &directory, SongConsumer f);
|
ItemIterator GetDirectoryRecursive(const std::string &directory);
|
||||||
SongIterator GetSongs(const std::string &directory);
|
SongIterator GetSongs(const std::string &directory);
|
||||||
void GetDirectories(const std::string &directory, StringConsumer f);
|
void GetDirectories(const std::string &directory, StringConsumer f);
|
||||||
|
|
||||||
|
|||||||
@@ -564,19 +564,17 @@ void PlaylistEditor::updateTimer()
|
|||||||
m_timer = Global::Timer;
|
m_timer = Global::Timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistEditor::Locate(const std::string &name)
|
void PlaylistEditor::Locate(const MPD::Playlist &playlist)
|
||||||
{
|
{
|
||||||
update();
|
update();
|
||||||
for (size_t i = 0; i < Playlists.size(); ++i)
|
auto begin = Playlists.beginV(), end = Playlists.endV();
|
||||||
|
auto it = std::find(begin, end, playlist);
|
||||||
|
if (it != end)
|
||||||
{
|
{
|
||||||
if (name == Playlists[i].value().path())
|
Playlists.highlight(it-begin);
|
||||||
{
|
Content.clear();
|
||||||
Playlists.highlight(i);
|
switchTo();
|
||||||
Content.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switchTo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {//
|
namespace {//
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ struct PlaylistEditor: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, S
|
|||||||
void requestPlaylistsUpdate() { m_playlists_update_requested = true; }
|
void requestPlaylistsUpdate() { m_playlists_update_requested = true; }
|
||||||
void requestContentsUpdate() { m_content_update_requested = true; }
|
void requestContentsUpdate() { m_content_update_requested = true; }
|
||||||
|
|
||||||
virtual void Locate(const std::string &);
|
virtual void Locate(const MPD::Playlist &playlist);
|
||||||
bool isContentFiltered();
|
bool isContentFiltered();
|
||||||
ProxySongList contentProxyList();
|
ProxySongList contentProxyList();
|
||||||
|
|
||||||
|
|||||||
@@ -446,9 +446,18 @@ void SearchEngine::Search()
|
|||||||
|
|
||||||
MPD::SongList list;
|
MPD::SongList list;
|
||||||
if (Config.search_in_db)
|
if (Config.search_in_db)
|
||||||
Mpd.GetDirectoryRecursive("/", vectorMoveInserter(list));
|
{
|
||||||
|
MPD::ItemIterator item = Mpd.GetDirectoryRecursive("/"), end;
|
||||||
|
for (; item != end; ++item)
|
||||||
|
if (item->type() != MPD::Item::Type::Song)
|
||||||
|
list.push_back(std::move(item->song()));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
list.insert(list.end(), myPlaylist->main().beginV(), myPlaylist->main().endV());
|
std::copy(
|
||||||
|
myPlaylist->main().beginV(),
|
||||||
|
myPlaylist->main().endV(),
|
||||||
|
std::back_inserter(list)
|
||||||
|
);
|
||||||
|
|
||||||
bool any_found = 1;
|
bool any_found = 1;
|
||||||
bool found = 1;
|
bool found = 1;
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ void Status::handleServerError(MPD::ServerError &e)
|
|||||||
}
|
}
|
||||||
else if (e.code() == MPD_SERVER_ERROR_NO_EXIST && myScreen == myBrowser)
|
else if (e.code() == MPD_SERVER_ERROR_NO_EXIST && myScreen == myBrowser)
|
||||||
{
|
{
|
||||||
myBrowser->GetDirectory(getParentDirectory(myBrowser->CurrentDir()));
|
myBrowser->getDirectory(getParentDirectory(myBrowser->currentDirectory()));
|
||||||
myBrowser->refresh();
|
myBrowser->refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,9 +438,9 @@ void Status::Changes::storedPlaylists()
|
|||||||
{
|
{
|
||||||
myPlaylistEditor->requestPlaylistsUpdate();
|
myPlaylistEditor->requestPlaylistsUpdate();
|
||||||
myPlaylistEditor->requestContentsUpdate();
|
myPlaylistEditor->requestContentsUpdate();
|
||||||
if (myBrowser->CurrentDir() == "/")
|
if (!myBrowser->isLocal() && myBrowser->inRootDirectory())
|
||||||
{
|
{
|
||||||
myBrowser->GetDirectory("/");
|
myBrowser->getDirectory("/");
|
||||||
if (isVisible(myBrowser))
|
if (isVisible(myBrowser))
|
||||||
myBrowser->refresh();
|
myBrowser->refresh();
|
||||||
}
|
}
|
||||||
@@ -449,7 +449,7 @@ void Status::Changes::storedPlaylists()
|
|||||||
void Status::Changes::database()
|
void Status::Changes::database()
|
||||||
{
|
{
|
||||||
if (isVisible(myBrowser))
|
if (isVisible(myBrowser))
|
||||||
myBrowser->GetDirectory(myBrowser->CurrentDir());
|
myBrowser->getDirectory(myBrowser->currentDirectory());
|
||||||
else
|
else
|
||||||
myBrowser->main().clear();
|
myBrowser->main().clear();
|
||||||
# ifdef HAVE_TAGLIB_H
|
# ifdef HAVE_TAGLIB_H
|
||||||
|
|||||||
105
src/tags.cpp
105
src/tags.cpp
@@ -39,7 +39,7 @@
|
|||||||
#include "utility/string.h"
|
#include "utility/string.h"
|
||||||
#include "utility/wide_string.h"
|
#include "utility/wide_string.h"
|
||||||
|
|
||||||
namespace {//
|
namespace {
|
||||||
|
|
||||||
TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f)
|
TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f)
|
||||||
{
|
{
|
||||||
@@ -50,71 +50,69 @@ TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void readCommonTags(MPD::MutableSong &s, TagLib::Tag *tag)
|
void readCommonTags(mpd_song *s, TagLib::Tag *tag)
|
||||||
{
|
{
|
||||||
s.setTitle(tag->title().to8Bit(true));
|
Tags::setAttribute(s, "Title", tag->title().to8Bit(true));
|
||||||
s.setArtist(tag->artist().to8Bit(true));
|
Tags::setAttribute(s, "Artist", tag->artist().to8Bit(true));
|
||||||
s.setAlbum(tag->album().to8Bit(true));
|
Tags::setAttribute(s, "Album", tag->album().to8Bit(true));
|
||||||
s.setDate(boost::lexical_cast<std::string>(tag->year()));
|
Tags::setAttribute(s, "Date", boost::lexical_cast<std::string>(tag->year()));
|
||||||
s.setTrack(boost::lexical_cast<std::string>(tag->track()));
|
Tags::setAttribute(s, "Track", boost::lexical_cast<std::string>(tag->track()));
|
||||||
s.setGenre(tag->genre().to8Bit(true));
|
Tags::setAttribute(s, "Genre", tag->genre().to8Bit(true));
|
||||||
s.setComment(tag->comment().to8Bit(true));
|
Tags::setAttribute(s, "Comment", tag->comment().to8Bit(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
void readID3v1Tags(MPD::MutableSong &s, TagLib::ID3v1::Tag *tag)
|
void readID3v1Tags(mpd_song *s, TagLib::ID3v1::Tag *tag)
|
||||||
{
|
{
|
||||||
readCommonTags(s, tag);
|
readCommonTags(s, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void readID3v2Tags(MPD::MutableSong &s, TagLib::ID3v2::Tag *tag)
|
void readID3v2Tags(mpd_song *s, TagLib::ID3v2::Tag *tag)
|
||||||
{
|
{
|
||||||
auto readFrame = [&s](const TagLib::ID3v2::FrameList &list, MPD::MutableSong::SetFunction f) {
|
auto readFrame = [s](const TagLib::ID3v2::FrameList &fields, const char *name) {
|
||||||
unsigned idx = 0;
|
for (const auto &field : fields)
|
||||||
for (auto it = list.begin(); it != list.end(); ++it, ++idx)
|
|
||||||
{
|
{
|
||||||
if (auto textFrame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(*it))
|
if (auto textFrame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(field))
|
||||||
{
|
{
|
||||||
auto values = textFrame->fieldList();
|
auto values = textFrame->fieldList();
|
||||||
for (auto value = values.begin(); value != values.end(); ++value, ++idx)
|
for (const auto &value : values)
|
||||||
(s.*f)(value->to8Bit(true), idx);
|
Tags::setAttribute(s, name, value.to8Bit(true));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
(s.*f)((*it)->toString().to8Bit(true), idx);
|
Tags::setAttribute(s, name, field->toString().to8Bit(true));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
auto &frames = tag->frameListMap();
|
auto &frames = tag->frameListMap();
|
||||||
readFrame(frames["TIT2"], &MPD::MutableSong::setTitle);
|
readFrame(frames["TIT2"], "Title");
|
||||||
readFrame(frames["TPE1"], &MPD::MutableSong::setArtist);
|
readFrame(frames["TPE1"], "Artist");
|
||||||
readFrame(frames["TPE2"], &MPD::MutableSong::setAlbumArtist);
|
readFrame(frames["TPE2"], "AlbumArtist");
|
||||||
readFrame(frames["TALB"], &MPD::MutableSong::setAlbum);
|
readFrame(frames["TALB"], "Album");
|
||||||
readFrame(frames["TDRC"], &MPD::MutableSong::setDate);
|
readFrame(frames["TDRC"], "Date");
|
||||||
readFrame(frames["TRCK"], &MPD::MutableSong::setTrack);
|
readFrame(frames["TRCK"], "Track");
|
||||||
readFrame(frames["TCON"], &MPD::MutableSong::setGenre);
|
readFrame(frames["TCON"], "Genre");
|
||||||
readFrame(frames["TCOM"], &MPD::MutableSong::setComposer);
|
readFrame(frames["TCOM"], "Composer");
|
||||||
readFrame(frames["TPE3"], &MPD::MutableSong::setPerformer);
|
readFrame(frames["TPE3"], "Performer");
|
||||||
readFrame(frames["TPOS"], &MPD::MutableSong::setDisc);
|
readFrame(frames["TPOS"], "Disc");
|
||||||
readFrame(frames["COMM"], &MPD::MutableSong::setComment);
|
readFrame(frames["COMM"], "Comment");
|
||||||
}
|
}
|
||||||
|
|
||||||
void readXiphComments(MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag)
|
void readXiphComments(mpd_song *s, TagLib::Ogg::XiphComment *tag)
|
||||||
{
|
{
|
||||||
auto readField = [&s](const TagLib::StringList &list, MPD::MutableSong::SetFunction f) {
|
auto readField = [s](const TagLib::StringList &fields, const char *name) {
|
||||||
unsigned idx = 0;
|
for (const auto &field : fields)
|
||||||
for (auto it = list.begin(); it != list.end(); ++it, ++idx)
|
Tags::setAttribute(s, name, field.to8Bit(true));
|
||||||
(s.*f)(it->to8Bit(true), idx);
|
|
||||||
};
|
};
|
||||||
auto &fields = tag->fieldListMap();
|
auto &fields = tag->fieldListMap();
|
||||||
readField(fields["TITLE"], &MPD::MutableSong::setTitle);
|
readField(fields["TITLE"], "Title");
|
||||||
readField(fields["ARTIST"], &MPD::MutableSong::setArtist);
|
readField(fields["ARTIST"], "Artist");
|
||||||
readField(fields["ALBUMARTIST"], &MPD::MutableSong::setAlbumArtist);
|
readField(fields["ALBUMARTIST"], "AlbumArtist");
|
||||||
readField(fields["ALBUM"], &MPD::MutableSong::setAlbum);
|
readField(fields["ALBUM"], "Album");
|
||||||
readField(fields["DATE"], &MPD::MutableSong::setDate);
|
readField(fields["DATE"], "Date");
|
||||||
readField(fields["TRACKNUMBER"], &MPD::MutableSong::setTrack);
|
readField(fields["TRACKNUMBER"], "Track");
|
||||||
readField(fields["GENRE"], &MPD::MutableSong::setGenre);
|
readField(fields["GENRE"], "Genre");
|
||||||
readField(fields["COMPOSER"], &MPD::MutableSong::setComposer);
|
readField(fields["COMPOSER"], "Composer");
|
||||||
readField(fields["PERFORMER"], &MPD::MutableSong::setPerformer);
|
readField(fields["PERFORMER"], "Performer");
|
||||||
readField(fields["DISCNUMBER"], &MPD::MutableSong::setDisc);
|
readField(fields["DISCNUMBER"], "Disc");
|
||||||
readField(fields["COMMENT"], &MPD::MutableSong::setComment);
|
readField(fields["COMMENT"], "Comment");
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearID3v1Tags(TagLib::ID3v1::Tag *tag)
|
void clearID3v1Tags(TagLib::ID3v1::Tag *tag)
|
||||||
@@ -230,6 +228,12 @@ Tags::ReplayGainInfo getReplayGain(TagLib::Ogg::XiphComment *tag)
|
|||||||
|
|
||||||
namespace Tags {//
|
namespace Tags {//
|
||||||
|
|
||||||
|
void setAttribute(mpd_song *s, const char *name, const std::string &value)
|
||||||
|
{
|
||||||
|
mpd_pair pair = { name, value.c_str() };
|
||||||
|
mpd_song_feed(s, &pair);
|
||||||
|
}
|
||||||
|
|
||||||
bool extendedSetSupported(const TagLib::File *f)
|
bool extendedSetSupported(const TagLib::File *f)
|
||||||
{
|
{
|
||||||
return dynamic_cast<const TagLib::MPEG::File *>(f)
|
return dynamic_cast<const TagLib::MPEG::File *>(f)
|
||||||
@@ -253,20 +257,21 @@ ReplayGainInfo readReplayGain(TagLib::File *f)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void read(MPD::MutableSong &s)
|
void read(mpd_song *s)
|
||||||
{
|
{
|
||||||
TagLib::FileRef f(s.getURI().c_str());
|
TagLib::FileRef f(mpd_song_get_uri(s));
|
||||||
if (f.isNull())
|
if (f.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s.setDuration(f.audioProperties()->length());
|
setAttribute(s, "Time", boost::lexical_cast<std::string>(f.audioProperties()->length()));
|
||||||
|
|
||||||
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file()))
|
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file()))
|
||||||
{
|
{
|
||||||
if (auto id3v1 = mpeg_file->ID3v1Tag())
|
// prefer id3v2 only if available
|
||||||
readID3v1Tags(s, id3v1);
|
|
||||||
if (auto id3v2 = mpeg_file->ID3v2Tag())
|
if (auto id3v2 = mpeg_file->ID3v2Tag())
|
||||||
readID3v2Tags(s, id3v2);
|
readID3v2Tags(s, id3v2);
|
||||||
|
else if (auto id3v1 = mpeg_file->ID3v1Tag())
|
||||||
|
readID3v1Tags(s, id3v1);
|
||||||
}
|
}
|
||||||
else if (auto ogg_file = dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file()))
|
else if (auto ogg_file = dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,11 +62,13 @@ private:
|
|||||||
std::string m_album_peak;
|
std::string m_album_peak;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void setAttribute(mpd_song *s, const char *name, const std::string &value);
|
||||||
|
|
||||||
ReplayGainInfo readReplayGain(TagLib::File *f);
|
ReplayGainInfo readReplayGain(TagLib::File *f);
|
||||||
|
|
||||||
bool extendedSetSupported(const TagLib::File *f);
|
bool extendedSetSupported(const TagLib::File *f);
|
||||||
|
|
||||||
void read(MPD::MutableSong &);
|
void read(mpd_song *s);
|
||||||
bool write(MPD::MutableSong &);
|
bool write(MPD::MutableSong &);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ void TinyTagEditor::enterPressed()
|
|||||||
if (m_previous_screen == myPlaylist)
|
if (m_previous_screen == myPlaylist)
|
||||||
myPlaylist->main().current().value() = itsEdited;
|
myPlaylist->main().current().value() = itsEdited;
|
||||||
else if (m_previous_screen == myBrowser)
|
else if (m_previous_screen == myBrowser)
|
||||||
myBrowser->GetDirectory(myBrowser->CurrentDir());
|
myBrowser->getDirectory(myBrowser->currentDirectory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -53,36 +53,58 @@ int LocaleStringComparison::compare(const char *a, size_t a_len, const char *b,
|
|||||||
bool LocaleBasedItemSorting::operator()(const MPD::Item &a, const MPD::Item &b) const
|
bool LocaleBasedItemSorting::operator()(const MPD::Item &a, const MPD::Item &b) const
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
if (a.type == b.type)
|
if (a.type() == b.type())
|
||||||
{
|
{
|
||||||
switch (a.type)
|
switch (m_sort_mode)
|
||||||
{
|
{
|
||||||
case MPD::Item::Type::Directory:
|
case SortMode::Name:
|
||||||
result = m_cmp(getBasename(a.name), getBasename(b.name));
|
switch (a.type())
|
||||||
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:
|
case MPD::Item::Type::Directory:
|
||||||
result = m_cmp(a.song, b.song);
|
result = m_cmp(a.directory().path(), b.directory().path());
|
||||||
break;
|
break;
|
||||||
case SortMode::ModificationTime:
|
case MPD::Item::Type::Playlist:
|
||||||
result = a.song.getMTime() > b.song.getMTime();
|
result = m_cmp(a.playlist().path(), b.playlist().path());
|
||||||
break;
|
break;
|
||||||
case SortMode::CustomFormat:
|
case MPD::Item::Type::Song:
|
||||||
result = m_cmp(a.song.toString(Config.browser_sort_format, Config.tags_separator),
|
result = m_cmp(a.song(), b.song());
|
||||||
b.song.toString(Config.browser_sort_format, Config.tags_separator));
|
|
||||||
break;
|
break;
|
||||||
case SortMode::NoOp:
|
|
||||||
throw std::logic_error("can't sort with NoOp sorting mode");
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
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 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
result = a.type < b.type;
|
result = a.type() < b.type();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,14 @@ std::string getBasename(const std::string &path)
|
|||||||
return path.substr(slash+1);
|
return path.substr(slash+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getParentDirectory(const std::string &path)
|
std::string getParentDirectory(std::string path)
|
||||||
{
|
{
|
||||||
size_t slash = path.rfind('/');
|
size_t slash = path.rfind('/');
|
||||||
if (slash == std::string::npos)
|
if (slash == std::string::npos)
|
||||||
return "/";
|
path = "";
|
||||||
else
|
else
|
||||||
return path.substr(0, slash);
|
path.resize(slash);
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2)
|
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ StringT join(CollectionT &&collection, StringT &&separator)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string getBasename(const std::string &path);
|
std::string getBasename(const std::string &path);
|
||||||
std::string getParentDirectory(const std::string &path);
|
std::string getParentDirectory(std::string path);
|
||||||
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2);
|
std::string getSharedDirectory(const std::string &dir1, const std::string &dir2);
|
||||||
|
|
||||||
std::string getEnclosedString(const std::string &s, char a, char b, size_t *pos);
|
std::string getEnclosedString(const std::string &s, char a, char b, size_t *pos);
|
||||||
|
|||||||
Reference in New Issue
Block a user