1 /***************************************************************************
2 * Copyright (C) 2008-2016 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 ***************************************************************************/
21 #include <boost/algorithm/string/classification.hpp>
22 #include <boost/filesystem/operations.hpp>
23 #include <boost/range/algorithm_ext/erase.hpp>
30 #include "curses/scrollpad.h"
33 #include "curl_handle.h"
34 #include "format_impl.h"
41 #include "statusbar.h"
43 #include "screen_switcher.h"
44 #include "utility/string.h"
46 using Global::MainHeight
;
47 using Global::MainStartY
;
53 std::string
removeExtension(std::string filename
)
55 size_t dot
= filename
.rfind('.');
56 if (dot
!= std::string::npos
)
61 std::string
lyricsFilename(const MPD::Song
&s
)
64 if (Config
.store_lyrics_in_song_dir
&& !s
.isStream())
66 if (s
.isFromDatabase())
67 filename
= Config
.mpd_music_dir
+ "/";
68 filename
+= removeExtension(s
.getURI());
69 removeExtension(filename
);
73 std::string artist
= s
.getArtist();
74 std::string title
= s
.getTitle();
75 if (artist
.empty() || title
.empty())
76 filename
= removeExtension(s
.getName());
78 filename
= artist
+ " - " + title
;
79 removeInvalidCharsFromFilename(filename
, Config
.generate_win32_compatible_filenames
);
80 filename
= Config
.lyrics_directory
+ "/" + filename
;
86 bool loadLyrics(NC::Scrollpad
&w
, const std::string
&filename
)
88 std::ifstream
input(filename
);
92 bool first_line
= true;
93 while (std::getline(input
, line
))
95 // Remove carriage returns as they mess up the display.
96 boost::remove_erase(line
, '\r');
99 w
<< Charset::utf8ToLocale(line
);
108 bool saveLyrics(const std::string
&filename
, const std::string
&lyrics
)
110 std::ofstream
output(filename
);
111 if (output
.is_open())
121 boost::optional
<std::string
> downloadLyrics(
123 std::shared_ptr
<Shared
<NC::Buffer
>> shared_buffer
,
124 std::shared_ptr
<std::atomic
<bool>> download_stopper
,
125 LyricsFetcher
*current_fetcher
)
127 std::string s_artist
= Curl::escape(s
.getArtist());
128 std::string s_title
= Curl::escape(s
.getTitle());
129 // If artist or title is empty, use filename. This should give reasonable
130 // results for google search based lyrics fetchers.
131 if (s_artist
.empty() || s_title
.empty())
134 s_title
= s
.getName();
135 // Get rid of underscores to improve search results.
136 std::replace_if(s_title
.begin(), s_title
.end(), boost::is_any_of("-_"), ' ');
137 size_t dot
= s_title
.rfind('.');
138 if (dot
!= std::string::npos
)
140 s_title
= Curl::escape(s_title
);
143 auto fetch_lyrics
= [&](auto &fetcher_
) {
147 auto buf
= shared_buffer
->acquire();
148 *buf
<< "Fetching lyrics from "
151 << NC::Format::NoBold
<< "... ";
154 auto result_
= fetcher_
->fetch(s_artist
, s_title
);
155 if (result_
.first
== false)
159 auto buf
= shared_buffer
->acquire();
160 *buf
<< NC::Color::Red
169 LyricsFetcher::Result fetcher_result
;
170 if (current_fetcher
== nullptr)
172 for (auto &fetcher
: Config
.lyrics_fetchers
)
174 if (download_stopper
&& download_stopper
->load())
176 fetcher_result
= fetch_lyrics(fetcher
);
177 if (fetcher_result
.first
)
182 fetcher_result
= fetch_lyrics(current_fetcher
);
184 boost::optional
<std::string
> result
;
185 if (fetcher_result
.first
)
186 result
= std::move(fetcher_result
.second
);
193 : Screen(NC::Scrollpad(0, MainStartY
, COLS
, MainHeight
, "", Config
.main_color
, NC::Border()))
194 , m_refresh_window(false)
199 void Lyrics::resize()
201 size_t x_offset
, width
;
202 getWindowResizeParams(x_offset
, width
);
203 w
.resize(width
, MainHeight
);
204 w
.moveTo(x_offset
, MainStartY
);
208 void Lyrics::update()
210 if (m_worker
.valid())
212 auto buffer
= m_shared_buffer
->acquire();
213 if (!buffer
->empty())
217 m_refresh_window
= true;
220 if (m_worker
.is_ready())
222 auto lyrics
= m_worker
.get();
226 w
<< Charset::utf8ToLocale(*lyrics
);
227 std::string filename
= lyricsFilename(m_song
);
228 if (!saveLyrics(filename
, *lyrics
))
229 Statusbar::printf("Couldn't save lyrics as \"%1%\": %2%",
230 filename
, strerror(errno
));
233 w
<< "\nLyrics were not found.\n";
235 m_refresh_window
= true;
239 if (m_refresh_window
)
241 m_refresh_window
= false;
247 void Lyrics::switchTo()
249 using Global::myScreen
;
250 if (myScreen
!= this)
252 SwitchTo::execute(this);
257 switchToPreviousScreen();
260 std::wstring
Lyrics::title()
262 std::wstring result
= L
"Lyrics: ";
264 Format::stringify
<wchar_t>(Format::parse(L
"{%a - %t}|{%f}"), &m_song
),
266 COLS
- result
.length() - (Config
.design
== Design::Alternative
268 : Global::VolumeState
.length()));
272 void Lyrics::fetch(const MPD::Song
&s
)
274 if (!m_worker
.valid() || s
!= m_song
)
279 if (loadLyrics(w
, lyricsFilename(m_song
)))
282 m_refresh_window
= true;
286 m_download_stopper
= std::make_shared
<std::atomic
<bool>>(false);
287 m_shared_buffer
= std::make_shared
<Shared
<NC::Buffer
>>();
288 m_worker
= boost::async(
289 boost::launch::async
,
290 std::bind(downloadLyrics
,
291 m_song
, m_shared_buffer
, m_download_stopper
, m_fetcher
));
296 void Lyrics::refetchCurrent()
298 std::string filename
= lyricsFilename(m_song
);
299 if (std::remove(filename
.c_str()) == -1 && errno
!= ENOENT
)
301 const char msg
[] = "Couldn't remove \"%1%\": %2%";
302 Statusbar::printf(msg
, wideShorten(filename
, COLS
- const_strlen(msg
) - 25),
314 if (Config
.external_editor
.empty())
316 Statusbar::print("external_editor variable has to be set in configuration file");
320 Statusbar::print("Opening lyrics in external editor...");
324 std::string filename
= lyricsFilename(m_song
);
325 if (Config
.use_console_editor
)
327 command
= "/bin/sh -c \"" + Config
.external_editor
+ " \\\"" + filename
+ "\\\"\"";
328 res
= system(command
.c_str());
330 // Reset ncurses state to refresh the screen.
337 command
= "nohup " + Config
.external_editor
338 + " \"" + filename
+ "\" > /dev/null 2>&1 &";
339 res
= system(command
.c_str());
343 void Lyrics::toggleFetcher()
345 if (m_fetcher
!= nullptr)
347 auto fetcher
= std::find_if(Config
.lyrics_fetchers
.begin(),
348 Config
.lyrics_fetchers
.end(),
349 [this](auto &f
) { return f
.get() == m_fetcher
; });
350 assert(fetcher
!= Config
.lyrics_fetchers
.end());
352 if (fetcher
!= Config
.lyrics_fetchers
.end())
353 m_fetcher
= fetcher
->get();
359 assert(!Config
.lyrics_fetchers
.empty());
360 m_fetcher
= Config
.lyrics_fetchers
[0].get();
363 if (m_fetcher
!= nullptr)
364 Statusbar::printf("Using lyrics fetcher: %s", m_fetcher
->name());
366 Statusbar::print("Using all lyrics fetchers");
369 void Lyrics::fetchInBackground(const MPD::Song
&s
, bool notify_
)
371 auto consumer_impl
= [this] {
372 std::string lyrics_file
;
375 ConsumerState::Song cs
;
377 auto consumer
= m_consumer_state
.acquire();
378 assert(consumer
->running
);
379 if (consumer
->songs
.empty())
381 consumer
->running
= false;
384 lyrics_file
= lyricsFilename(consumer
->songs
.front().song());
385 if (!boost::filesystem::exists(lyrics_file
))
387 cs
= consumer
->songs
.front();
390 consumer
->message
= "Fetching lyrics for \""
391 + Format::stringify
<char>(Config
.song_status_format
, &cs
.song())
395 consumer
->songs
.pop();
397 if (!cs
.song().empty())
399 auto lyrics
= downloadLyrics(cs
.song(), nullptr, nullptr, m_fetcher
);
401 saveLyrics(lyrics_file
, *lyrics
);
406 auto consumer
= m_consumer_state
.acquire();
407 consumer
->songs
.emplace(s
, notify_
);
408 // Start the consumer if it's not running.
409 if (!consumer
->running
)
411 std::thread
t(consumer_impl
);
413 consumer
->running
= true;
417 boost::optional
<std::string
> Lyrics::tryTakeConsumerMessage()
419 boost::optional
<std::string
> result
;
420 auto consumer
= m_consumer_state
.acquire();
421 if (consumer
->message
)
423 result
= std::move(consumer
->message
);
424 consumer
->message
= boost::none
;
429 void Lyrics::clearWorker()
431 m_shared_buffer
.reset();
432 m_worker
= boost::BOOST_THREAD_FUTURE
<boost::optional
<std::string
>>();
435 void Lyrics::stopDownload()
437 if (m_download_stopper
)
438 m_download_stopper
->store(true);