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>
31 #include "curl_handle.h"
32 #include "format_impl.h"
37 #include "scrollpad.h"
40 #include "statusbar.h"
42 #include "screen_switcher.h"
43 #include "utility/string.h"
45 using Global::MainHeight
;
46 using Global::MainStartY
;
52 std::string
removeExtension(std::string filename
)
54 size_t dot
= filename
.rfind('.');
55 if (dot
!= std::string::npos
)
60 std::string
lyricsFilename(const MPD::Song
&s
)
63 if (Config
.store_lyrics_in_song_dir
&& !s
.isStream())
65 if (s
.isFromDatabase())
66 filename
= Config
.mpd_music_dir
+ "/";
67 filename
+= removeExtension(s
.getURI());
68 removeExtension(filename
);
72 std::string artist
= s
.getArtist();
73 std::string title
= s
.getTitle();
74 if (artist
.empty() || title
.empty())
75 filename
= removeExtension(s
.getName());
77 filename
= artist
+ " - " + title
;
78 removeInvalidCharsFromFilename(filename
, Config
.generate_win32_compatible_filenames
);
79 filename
= Config
.lyrics_directory
+ "/" + filename
;
85 bool loadLyrics(NC::Scrollpad
&w
, const std::string
&filename
)
87 std::ifstream
input(filename
);
91 bool first_line
= true;
92 while (std::getline(input
, line
))
94 // Remove carriage returns as they mess up the display.
95 boost::remove_erase(line
, '\r');
98 w
<< Charset::utf8ToLocale(line
);
107 bool saveLyrics(const std::string
&filename
, const std::string
&lyrics
)
109 std::ofstream
output(filename
);
110 if (output
.is_open())
120 boost::optional
<std::string
> downloadLyrics(
122 std::shared_ptr
<Shared
<NC::Buffer
>> shared_buffer
,
123 LyricsFetcher
*current_fetcher
)
125 std::string s_artist
= Curl::escape(s
.getArtist());
126 std::string s_title
= Curl::escape(s
.getTitle());
127 // If artist or title is empty, use filename. This should give reasonable
128 // results for google search based lyrics fetchers.
129 if (s_artist
.empty() || s_title
.empty())
132 s_title
= s
.getName();
133 // Get rid of underscores to improve search results.
134 std::replace_if(s_title
.begin(), s_title
.end(), boost::is_any_of("-_"), ' ');
135 size_t dot
= s_title
.rfind('.');
136 if (dot
!= std::string::npos
)
138 s_title
= Curl::escape(s_title
);
141 auto fetch_lyrics
= [&](auto &fetcher_
) {
145 auto buf
= shared_buffer
->acquire();
146 *buf
<< "Fetching lyrics from "
149 << NC::Format::NoBold
<< "... ";
152 auto result_
= fetcher_
->fetch(s_artist
, s_title
);
153 if (result_
.first
== false)
157 auto buf
= shared_buffer
->acquire();
158 *buf
<< NC::Color::Red
167 LyricsFetcher::Result fetcher_result
;
168 if (current_fetcher
== nullptr)
170 for (auto &fetcher
: Config
.lyrics_fetchers
)
172 fetcher_result
= fetch_lyrics(fetcher
);
173 if (fetcher_result
.first
)
178 fetcher_result
= fetch_lyrics(current_fetcher
);
180 boost::optional
<std::string
> result
;
181 if (fetcher_result
.first
)
182 result
= std::move(fetcher_result
.second
);
189 : Screen(NC::Scrollpad(0, MainStartY
, COLS
, MainHeight
, "", Config
.main_color
, NC::Border()))
190 , m_refresh_window(false)
195 void Lyrics::resize()
197 size_t x_offset
, width
;
198 getWindowResizeParams(x_offset
, width
);
199 w
.resize(width
, MainHeight
);
200 w
.moveTo(x_offset
, MainStartY
);
204 void Lyrics::update()
206 if (m_worker
.valid())
208 auto buffer
= m_shared_buffer
->acquire();
209 if (!buffer
->empty())
213 m_refresh_window
= true;
216 if (m_worker
.wait_for(std::chrono::seconds(0)) == std::future_status::ready
)
218 auto lyrics
= m_worker
.get();
222 w
<< Charset::utf8ToLocale(*lyrics
);
223 std::string filename
= lyricsFilename(m_song
);
224 if (!saveLyrics(filename
, *lyrics
))
225 Statusbar::printf("Couldn't save lyrics as \"%1%\": %2%",
226 filename
, strerror(errno
));
229 w
<< "\nLyrics were not found.\n";
231 m_refresh_window
= true;
235 if (m_refresh_window
)
237 m_refresh_window
= false;
243 void Lyrics::switchTo()
245 using Global::myScreen
;
246 if (myScreen
!= this)
248 SwitchTo::execute(this);
253 switchToPreviousScreen();
256 std::wstring
Lyrics::title()
258 std::wstring result
= L
"Lyrics: ";
260 Format::stringify
<wchar_t>(Format::parse(L
"{%a - %t}|{%f}"), &m_song
),
262 COLS
- result
.length() - (Config
.design
== Design::Alternative
264 : Global::VolumeState
.length()));
268 void Lyrics::fetch(const MPD::Song
&s
)
270 if (!m_worker
.valid() || s
!= m_song
)
274 if (loadLyrics(w
, lyricsFilename(m_song
)))
277 m_refresh_window
= true;
281 m_shared_buffer
= std::make_shared
<Shared
<NC::Buffer
>>();
282 m_worker
= std::async(
284 std::bind(downloadLyrics
, m_song
, m_shared_buffer
, m_fetcher
));
289 void Lyrics::refetchCurrent()
291 std::string filename
= lyricsFilename(m_song
);
292 if (std::remove(filename
.c_str()) == -1 && errno
!= ENOENT
)
294 const char msg
[] = "Couldn't remove \"%1%\": %2%";
295 Statusbar::printf(msg
, wideShorten(filename
, COLS
- const_strlen(msg
) - 25),
307 if (Config
.external_editor
.empty())
309 Statusbar::print("external_editor variable has to be set in configuration file");
313 Statusbar::print("Opening lyrics in external editor...");
317 std::string filename
= lyricsFilename(m_song
);
318 if (Config
.use_console_editor
)
320 command
= "/bin/sh -c \"" + Config
.external_editor
+ " \\\"" + filename
+ "\\\"\"";
321 res
= system(command
.c_str());
323 // Reset ncurses state to refresh the screen.
330 command
= "nohup " + Config
.external_editor
331 + " \"" + filename
+ "\" > /dev/null 2>&1 &";
332 res
= system(command
.c_str());
336 void Lyrics::toggleFetcher()
338 if (m_fetcher
!= nullptr)
340 auto fetcher
= std::find_if(Config
.lyrics_fetchers
.begin(),
341 Config
.lyrics_fetchers
.end(),
342 [this](auto &f
) { return f
.get() == m_fetcher
; });
343 assert(fetcher
!= Config
.lyrics_fetchers
.end());
345 if (fetcher
!= Config
.lyrics_fetchers
.end())
346 m_fetcher
= fetcher
->get();
352 assert(!Config
.lyrics_fetchers
.empty());
353 m_fetcher
= Config
.lyrics_fetchers
[0].get();
356 if (m_fetcher
!= nullptr)
357 Statusbar::printf("Using lyrics fetcher: %s", m_fetcher
->name());
359 Statusbar::print("Using all lyrics fetchers");
362 void Lyrics::fetchInBackground(const MPD::Song
&s
, bool notify_
)
364 auto consumer_impl
= [this] {
365 std::string lyrics_file
;
368 ConsumerState::Song cs
;
370 auto consumer
= m_consumer_state
.acquire();
371 assert(consumer
->running
);
372 if (consumer
->songs
.empty())
374 consumer
->running
= false;
377 lyrics_file
= lyricsFilename(consumer
->songs
.front().song());
378 if (!boost::filesystem::exists(lyrics_file
))
380 cs
= consumer
->songs
.front();
383 consumer
->message
= "Fetching lyrics for \""
384 + Format::stringify
<char>(Config
.song_status_format
, &cs
.song())
388 consumer
->songs
.pop();
390 if (!cs
.song().empty())
392 auto lyrics
= downloadLyrics(cs
.song(), nullptr, m_fetcher
);
394 saveLyrics(lyrics_file
, *lyrics
);
399 auto consumer
= m_consumer_state
.acquire();
400 consumer
->songs
.emplace(s
, notify_
);
401 // Start the consumer if it's not running.
402 if (!consumer
->running
)
404 std::thread
t(consumer_impl
);
406 consumer
->running
= true;
410 boost::optional
<std::string
> Lyrics::tryTakeConsumerMessage()
412 boost::optional
<std::string
> result
;
413 auto consumer
= m_consumer_state
.acquire();
414 if (consumer
->message
)
416 result
= std::move(consumer
->message
);
417 consumer
->message
= boost::none
;
422 void Lyrics::clearWorker()
424 m_shared_buffer
.reset();
425 m_worker
= std::future
<boost::optional
<std::string
>>();