1 /***************************************************************************
2 * Copyright (C) 2008-2012 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
35 #include "utility/comparators.h"
37 # include "tag_editor.h"
38 #endif // HAVE_TAGLIB_H
40 using Global::MainHeight
;
41 using Global::MainStartY
;
42 using Global::myScreen
;
43 using Global::RedrawHeader
;
45 using MPD::itDirectory
;
47 using MPD::itPlaylist
;
49 Browser
*myBrowser
= new Browser
;
51 std::set
<std::string
> Browser::SupportedExtensions
;
55 static Display::ScreenFormat sf
= { this, &Config
.song_list_format
};
57 w
= new Menu
<MPD::Item
>(0, MainStartY
, COLS
, MainHeight
, Config
.columns_in_browser
&& Config
.titles_visibility
? Display::Columns(COLS
) : "", Config
.main_color
, brNone
);
58 w
->HighlightColor(Config
.main_highlight_color
);
59 w
->CyclicScrolling(Config
.use_cyclic_scrolling
);
60 w
->CenteredCursor(Config
.centered_cursor
);
61 w
->SetSelectPrefix(&Config
.selected_item_prefix
);
62 w
->SetSelectSuffix(&Config
.selected_item_suffix
);
63 w
->SetItemDisplayer(Display::Items
);
64 w
->SetItemDisplayerUserData(&sf
);
65 w
->SetGetStringFunction(ItemToString
);
67 if (SupportedExtensions
.empty())
68 Mpd
.GetSupportedExtensions(SupportedExtensions
);
73 void Browser::Resize()
75 size_t x_offset
, width
;
76 GetWindowResizeParams(x_offset
, width
);
77 w
->Resize(width
, MainHeight
);
78 w
->MoveTo(x_offset
, MainStartY
);
79 w
->SetTitle(Config
.columns_in_browser
&& Config
.titles_visibility
? Display::Columns(w
->GetWidth()) : "");
83 void Browser::SwitchTo()
85 using Global::myLockedScreen
;
86 using Global::myInactiveScreen
;
91 myBrowser
->ChangeBrowseMode();
99 UpdateInactiveScreen(this);
101 if (hasToBeResized
|| myLockedScreen
)
104 if (isLocal() && Config
.browser_sort_mode
== smMTime
) // local browser doesn't support sorting by mtime
105 Config
.browser_sort_mode
= smName
;
107 w
->Empty() ? myBrowser
->GetDirectory(itsBrowsedDir
) : myBrowser
->UpdateItemList();
109 if (myScreen
!= this && myScreen
->isTabbable())
110 Global::myPrevScreen
= myScreen
;
115 std::basic_string
<my_char_t
> Browser::Title()
117 std::basic_string
<my_char_t
> result
= U("Browse: ");
118 result
+= Scroller(TO_WSTRING(itsBrowsedDir
), itsScrollBeginning
, COLS
-result
.length()-(Config
.new_design
? 2 : Global::VolumeState
.length()));
122 void Browser::EnterPressed()
127 const MPD::Item
&item
= w
->Current();
132 if (isParentDir(w
->Choice()))
134 size_t slash
= itsBrowsedDir
.rfind("/");
135 if (slash
!= std::string::npos
)
136 GetDirectory(itsBrowsedDir
.substr(0, slash
), itsBrowsedDir
);
138 GetDirectory("/", itsBrowsedDir
);
141 GetDirectory(item
.name
, itsBrowsedDir
);
147 bool res
= myPlaylist
->Add(*item
.song
, w
->isBold(), 1);
148 w
->Bold(w
->Choice(), res
);
153 if (Mpd
.LoadPlaylist(locale_to_utf_cpy(item
.name
)))
155 ShowMessage("Playlist \"%s\" loaded", item
.name
.c_str());
156 myPlaylist
->PlayNewlyAddedSongs();
162 void Browser::SpacePressed()
167 if (Config
.space_selects
&& w
->Choice() >= (itsBrowsedDir
!= "/" ? 1 : 0))
169 w
->Select(w
->Choice(), !w
->isSelected());
174 if (isParentDir(w
->Choice()))
177 const MPD::Item
&item
= w
->Current();
188 ShowMessage("Scanning directory \"%s\"...", item
.name
.c_str());
189 myBrowser
->GetLocalDirectory(items
, item
.name
, 1);
190 list
.reserve(items
.size());
191 for (MPD::ItemList::const_iterator it
= items
.begin(); it
!= items
.end(); ++it
)
192 list
.push_back(*it
->song
);
193 result
= myPlaylist
->Add(list
, 0);
197 result
= Mpd
.Add(locale_to_utf_cpy(item
.name
));
199 ShowMessage("Directory \"%s\" added", item
.name
.c_str());
204 bool res
= myPlaylist
->Add(*item
.song
, w
->isBold(), 0);
205 w
->Bold(w
->Choice(), res
);
210 if (Mpd
.LoadPlaylist(locale_to_utf_cpy(item
.name
)))
211 ShowMessage("Playlist \"%s\" loaded", item
.name
.c_str());
218 void Browser::MouseButtonPressed(MEVENT me
)
220 if (w
->Empty() || !w
->hasCoords(me
.x
, me
.y
) || size_t(me
.y
) >= w
->Size())
222 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
225 switch (w
->Current().type
)
228 if (me
.bstate
& BUTTON1_PRESSED
)
230 GetDirectory(w
->Current().name
);
235 size_t pos
= w
->Choice();
237 if (pos
< w
->Size()-1)
243 if (me
.bstate
& BUTTON1_PRESSED
)
245 size_t pos
= w
->Choice();
247 if (pos
< w
->Size()-1)
256 Screen
< Menu
<MPD::Item
> >::MouseButtonPressed(me
);
259 MPD::Song
*Browser::CurrentSong()
261 return !w
->Empty() && w
->Current().type
== itSong
? w
->Current().song
.get() : 0;
264 void Browser::ReverseSelection()
266 w
->ReverseSelection(itsBrowsedDir
== "/" ? 0 : 1);
269 void Browser::GetSelectedSongs(MPD::SongList
&v
)
273 std::vector
<size_t> selected
;
274 w
->GetSelected(selected
);
275 if (selected
.empty())
276 selected
.push_back(w
->Choice());
277 for (auto it
= selected
.begin(); it
!= selected
.end(); ++it
)
279 const MPD::Item
&item
= w
->at(*it
);
288 GetLocalDirectory(list
, item
.name
, 1);
289 for (auto j
= list
.begin(); j
!= list
.end(); ++j
)
290 v
.push_back(*j
->song
);
295 Mpd
.GetDirectoryRecursive(locale_to_utf_cpy(item
.name
), [&v
](MPD::Song
&&s
) {
303 v
.push_back(*item
.song
);
308 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), [&v
](MPD::Song
&&s
) {
317 void Browser::ApplyFilter(const std::string
&s
)
319 w
->ApplyFilter(s
, itsBrowsedDir
== "/" ? 0 : 1, REG_ICASE
| Config
.regex_type
);
322 bool Browser::hasSupportedExtension(const std::string
&file
)
324 size_t last_dot
= file
.rfind(".");
325 if (last_dot
> file
.length())
328 std::string ext
= file
.substr(last_dot
+1);
330 return SupportedExtensions
.find(ext
) != SupportedExtensions
.end();
333 void Browser::LocateSong(const MPD::Song
&s
)
335 if (s
.getDirectory().empty())
338 itsBrowseLocally
= !s
.isFromDatabase();
340 if (myScreen
!= this)
343 if (itsBrowsedDir
!= s
.getDirectory())
344 GetDirectory(s
.getDirectory());
345 for (size_t i
= 0; i
< w
->Size(); ++i
)
347 if ((*w
)[i
].type
== itSong
&& s
.getHash() == (*w
)[i
].song
->getHash())
355 void Browser::GetDirectory(std::string dir
, std::string subdir
)
360 int highlightme
= -1;
361 itsScrollBeginning
= 0;
362 if (itsBrowsedDir
!= dir
)
374 parent
.type
= itDirectory
;
375 w
->AddOption(parent
);
381 GetLocalDirectory(list
);
384 Mpd
.GetDirectory(dir
, [&list
](MPD::Item
&&i
) {
389 Mpd
.GetDirectory(dir
, [&list
](MPD::Item
&&i
) {
393 if (!isLocal()) // local directory is already sorted
394 std::sort(list
.begin(), list
.end(), CaseInsensitiveSorting());
396 for (MPD::ItemList::iterator it
= list
.begin(); it
!= list
.end(); ++it
)
402 utf_to_locale(it
->name
);
408 utf_to_locale(it
->name
);
409 if (it
->name
== subdir
)
410 highlightme
= w
->Size();
417 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
419 if (myPlaylist
->Items
->at(i
).getHash() == it
->song
->getHash())
425 w
->AddOption(*it
, bold
);
430 if (highlightme
>= 0)
431 w
->Highlight(highlightme
);
435 void Browser::GetLocalDirectory(MPD::ItemList
&v
, const std::string
&directory
, bool recursively
) const
437 DIR *dir
= opendir((directory
.empty() ? itsBrowsedDir
: directory
).c_str());
444 struct stat file_stat
;
445 std::string full_path
;
447 size_t old_size
= v
.size();
448 while ((file
= readdir(dir
)))
451 if (file
->d_name
[0] == '.' && (file
->d_name
[1] == '\0' || (file
->d_name
[1] == '.' && file
->d_name
[2] == '\0')))
454 if (!Config
.local_browser_show_hidden_files
&& file
->d_name
[0] == '.')
457 full_path
= directory
.empty() ? itsBrowsedDir
: directory
;
458 if (itsBrowsedDir
!= "/")
460 full_path
+= file
->d_name
;
461 stat(full_path
.c_str(), &file_stat
);
462 if (S_ISDIR(file_stat
.st_mode
))
466 GetLocalDirectory(v
, full_path
, 1);
471 new_item
.type
= itDirectory
;
472 new_item
.name
= full_path
;
473 v
.push_back(new_item
);
476 else if (hasSupportedExtension(file
->d_name
))
478 new_item
.type
= itSong
;
479 mpd_pair file_pair
= { "file", full_path
.c_str() };
480 MPD::MutableSong
*s
= new MPD::MutableSong(mpd_song_begin(&file_pair
));
481 new_item
.song
= std::shared_ptr
<MPD::Song
>(s
);
482 # ifdef HAVE_TAGLIB_H
485 TagEditor::ReadTags(*s
);
486 # endif // HAVE_TAGLIB_H
487 v
.push_back(new_item
);
491 std::sort(v
.begin()+old_size
, v
.end(), CaseInsensitiveSorting());
494 void Browser::ClearDirectory(const std::string
&path
) const
496 DIR *dir
= opendir(path
.c_str());
501 struct stat file_stat
;
502 std::string full_path
;
504 while ((file
= readdir(dir
)))
507 if (file
->d_name
[0] == '.' && (file
->d_name
[1] == '\0' || (file
->d_name
[1] == '.' && file
->d_name
[2] == '\0')))
511 if (*full_path
.rbegin() != '/')
513 full_path
+= file
->d_name
;
514 lstat(full_path
.c_str(), &file_stat
);
515 if (S_ISDIR(file_stat
.st_mode
))
516 ClearDirectory(full_path
);
517 if (remove(full_path
.c_str()) == 0)
519 const char msg
[] = "Deleting \"%s\"...";
520 ShowMessage(msg
, Shorten(TO_WSTRING(full_path
), COLS
-const_strlen(msg
)).c_str());
524 const char msg
[] = "Couldn't remove \"%s\": %s";
525 ShowMessage(msg
, Shorten(TO_WSTRING(full_path
), COLS
-const_strlen(msg
)-25).c_str(), strerror(errno
));
531 void Browser::ChangeBrowseMode()
533 if (Mpd
.GetHostname()[0] != '/')
535 ShowMessage("For browsing local filesystem connection to MPD via UNIX Socket is required");
539 itsBrowseLocally
= !itsBrowseLocally
;
540 ShowMessage("Browse mode: %s", itsBrowseLocally
? "Local filesystem" : "MPD database");
541 itsBrowsedDir
= itsBrowseLocally
? Config
.GetHomeDirectory() : "/";
542 if (itsBrowseLocally
&& *itsBrowsedDir
.rbegin() == '/')
543 itsBrowsedDir
.resize(itsBrowsedDir
.length()-1);
545 GetDirectory(itsBrowsedDir
);
549 bool Browser::DeleteItem(const MPD::Item
&item
)
552 if (item
.type
== itDirectory
&& item
.name
== "..")
555 // playlist created by mpd
556 if (!isLocal() && item
.type
== itPlaylist
&& CurrentDir() == "/")
557 return Mpd
.DeletePlaylist(locale_to_utf_cpy(item
.name
));
561 path
= Config
.mpd_music_dir
;
562 path
+= item
.type
== itSong
? item
.song
->getURI() : item
.name
;
564 if (item
.type
== itDirectory
)
565 ClearDirectory(path
);
567 return remove(path
.c_str()) == 0;
571 void Browser::UpdateItemList()
573 for (size_t i
= 0; i
< w
->Size(); ++i
)
574 if ((*w
)[i
].type
== itSong
)
575 w
->Bold(i
, myPlaylist
->checkForSong(*(*w
)[i
].song
));
579 std::string
Browser::ItemToString(const MPD::Item
&item
, void *)
583 case MPD::itDirectory
:
585 return "[" + getBasename(item
.name
) + "]";
589 if (!Config
.columns_in_browser
)
590 return item
.song
->toString(Config
.song_list_format_dollar_free
);
592 return Playlist::SongInColumnsToString(*item
.song
, 0);
594 case MPD::itPlaylist
:
596 return Config
.browser_playlist_prefix
.Str() + getBasename(item
.name
);