set SIGWINCH handler before initializing ncurses to avoid races
[ncmpcpp.git] / src / lyrics.cpp
blob000c3c451e0e6a3713e30a25fd74f08089350145
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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 <cassert>
22 #include <cerrno>
23 #include <cstring>
24 #include <fstream>
26 #include "browser.h"
27 #include "charset.h"
28 #include "curl_handle.h"
29 #include "global.h"
30 #include "helpers.h"
31 #include "lyrics.h"
32 #include "playlist.h"
33 #include "scrollpad.h"
34 #include "settings.h"
35 #include "song.h"
36 #include "statusbar.h"
37 #include "title.h"
38 #include "screen_switcher.h"
39 #include "utility/string.h"
41 using Global::MainHeight;
42 using Global::MainStartY;
44 #ifdef HAVE_CURL_CURL_H
45 LyricsFetcher **Lyrics::itsFetcher = 0;
46 std::queue<MPD::Song *> Lyrics::itsToDownload;
47 pthread_mutex_t Lyrics::itsDIBLock = PTHREAD_MUTEX_INITIALIZER;
48 size_t Lyrics::itsWorkersNumber = 0;
49 #endif // HAVE_CURL_CURL_H
51 Lyrics *myLyrics;
53 Lyrics::Lyrics()
54 : Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border::None))
55 , Reload(0),
56 #ifdef HAVE_CURL_CURL_H
57 isReadyToTake(0), isDownloadInProgress(0),
58 #endif // HAVE_CURL_CURL_H
59 itsScrollBegin(0)
60 { }
62 void Lyrics::resize()
64 size_t x_offset, width;
65 getWindowResizeParams(x_offset, width);
66 w.resize(width, MainHeight);
67 w.moveTo(x_offset, MainStartY);
68 hasToBeResized = 0;
71 void Lyrics::update()
73 # ifdef HAVE_CURL_CURL_H
74 if (isReadyToTake)
75 Take();
77 if (isDownloadInProgress)
79 w.flush();
80 w.refresh();
82 # endif // HAVE_CURL_CURL_H
83 if (Reload)
85 drawHeader();
86 itsScrollBegin = 0;
87 Load();
88 Reload = 0;
92 void Lyrics::switchTo()
94 using Global::myScreen;
95 if (myScreen != this)
97 # ifdef HAVE_CURL_CURL_H
98 // take lyrics if they were downloaded
99 if (isReadyToTake)
100 Take();
102 if (isDownloadInProgress || itsWorkersNumber > 0)
104 Statusbar::print("Lyrics are being downloaded...");
105 return;
107 # endif // HAVE_CURL_CURL_H
109 auto s = currentSong(myScreen);
110 if (!s)
111 return;
113 if (SetSong(*s))
115 SwitchTo::execute(this);
116 itsScrollBegin = 0;
117 Load();
118 drawHeader();
120 else
121 Statusbar::print("Song must have both artist and title tag set");
123 else
124 switchToPreviousScreen();
127 std::wstring Lyrics::title()
129 std::wstring result = L"Lyrics: ";
130 result += Scroller(ToWString(itsSong.toString("{%a - %t}", ", ")), itsScrollBegin, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
131 return result;
134 void Lyrics::spacePressed()
136 Config.now_playing_lyrics = !Config.now_playing_lyrics;
137 Statusbar::printf("Reload lyrics if song changes: %1%",
138 Config.now_playing_lyrics ? "on" : "off"
142 #ifdef HAVE_CURL_CURL_H
143 void Lyrics::DownloadInBackground(const MPD::Song &s)
145 if (s.empty() || s.getArtist().empty() || s.getTitle().empty())
146 return;
148 std::string filename = GenerateFilename(s);
149 std::ifstream f(filename.c_str());
150 if (f.is_open())
152 f.close();
153 return;
155 Statusbar::printf("Fetching lyrics for \"%1%\"...",
156 s.toString(Config.song_status_format_no_colors, Config.tags_separator)
159 MPD::Song *s_copy = new MPD::Song(s);
160 pthread_mutex_lock(&itsDIBLock);
161 if (itsWorkersNumber == itsMaxWorkersNumber)
162 itsToDownload.push(s_copy);
163 else
165 ++itsWorkersNumber;
166 pthread_t t;
167 pthread_attr_t attr;
168 pthread_attr_init(&attr);
169 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
170 pthread_create(&t, &attr, DownloadInBackgroundImpl, s_copy);
172 pthread_mutex_unlock(&itsDIBLock);
175 void *Lyrics::DownloadInBackgroundImpl(void *void_ptr)
177 MPD::Song *s = static_cast<MPD::Song *>(void_ptr);
178 DownloadInBackgroundImplHelper(*s);
179 delete s;
181 while (true)
183 pthread_mutex_lock(&itsDIBLock);
184 if (itsToDownload.empty())
186 pthread_mutex_unlock(&itsDIBLock);
187 break;
189 else
191 s = itsToDownload.front();
192 itsToDownload.pop();
193 pthread_mutex_unlock(&itsDIBLock);
195 DownloadInBackgroundImplHelper(*s);
196 delete s;
199 pthread_mutex_lock(&itsDIBLock);
200 --itsWorkersNumber;
201 pthread_mutex_unlock(&itsDIBLock);
203 pthread_exit(0);
206 void Lyrics::DownloadInBackgroundImplHelper(const MPD::Song &s)
208 std::string artist = Curl::escape(s.getArtist());
209 std::string title = Curl::escape(s.getTitle());
211 LyricsFetcher::Result result;
212 bool fetcher_defined = itsFetcher && *itsFetcher;
213 for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
215 result = (*plugin)->fetch(artist, title);
216 if (result.first)
217 break;
218 if (fetcher_defined)
219 break;
221 if (result.first == true)
222 Save(GenerateFilename(s), result.second);
225 void *Lyrics::Download()
227 std::string artist = Curl::escape(itsSong.getArtist());
228 std::string title_ = Curl::escape(itsSong.getTitle());
230 LyricsFetcher::Result result;
232 // if one of plugins is selected, try only this one,
233 // otherwise try all of them until one of them succeeds
234 bool fetcher_defined = itsFetcher && *itsFetcher;
235 for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
237 w << "Fetching lyrics from " << NC::Format::Bold << (*plugin)->name() << NC::Format::NoBold << "... ";
238 result = (*plugin)->fetch(artist, title_);
239 if (result.first == false)
240 w << NC::Color::Red << result.second << NC::Color::End << '\n';
241 else
242 break;
243 if (fetcher_defined)
244 break;
247 if (result.first == true)
249 Save(itsFilename, result.second);
250 w.clear();
251 w << Charset::utf8ToLocale(result.second);
253 else
254 w << '\n' << "Lyrics weren't found.";
256 isReadyToTake = 1;
257 pthread_exit(0);
260 void *Lyrics::DownloadWrapper(void *this_ptr)
262 return static_cast<Lyrics *>(this_ptr)->Download();
264 #endif // HAVE_CURL_CURL_H
266 std::string Lyrics::GenerateFilename(const MPD::Song &s)
268 std::string filename;
269 if (Config.store_lyrics_in_song_dir)
271 if (s.isFromDatabase())
273 filename = Config.mpd_music_dir;
274 filename += "/";
275 filename += s.getURI();
277 else
278 filename = s.getURI();
279 // replace song's extension with .txt
280 size_t dot = filename.rfind('.');
281 assert(dot != std::string::npos);
282 filename.resize(dot);
283 filename += ".txt";
285 else
287 std::string file = s.getArtist();
288 file += " - ";
289 file += s.getTitle();
290 file += ".txt";
291 removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
292 filename = Config.lyrics_directory;
293 filename += "/";
294 filename += file;
296 return filename;
299 void Lyrics::Load()
301 # ifdef HAVE_CURL_CURL_H
302 if (isDownloadInProgress)
303 return;
304 # endif // HAVE_CURL_CURL_H
306 assert(!itsSong.getArtist().empty());
307 assert(!itsSong.getTitle().empty());
309 itsFilename = GenerateFilename(itsSong);
311 w.clear();
312 w.reset();
314 std::ifstream input(itsFilename.c_str());
315 if (input.is_open())
317 bool first = 1;
318 std::string line;
319 while (std::getline(input, line))
321 if (!first)
322 w << '\n';
323 w << Charset::utf8ToLocale(line);
324 first = 0;
326 w.flush();
327 if (Reload)
328 w.refresh();
330 else
332 # ifdef HAVE_CURL_CURL_H
333 pthread_create(&itsDownloader, 0, DownloadWrapper, this);
334 isDownloadInProgress = 1;
335 # else
336 w << "Local lyrics not found. As ncmpcpp has been compiled without curl support, you can put appropriate lyrics into " << Config.lyrics_directory << " directory (file syntax is \"$ARTIST - $TITLE.txt\") or recompile ncmpcpp with curl support.";
337 w.flush();
338 # endif
342 void Lyrics::Edit()
344 assert(Global::myScreen == this);
346 if (Config.external_editor.empty())
348 Statusbar::print("Proper external_editor variable has to be set in configuration file");
349 return;
352 Statusbar::print("Opening lyrics in external editor...");
354 GNUC_UNUSED int res;
355 if (Config.use_console_editor)
357 res = system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str());
358 Load();
359 // below is needed as screen gets cleared, but apparently
360 // ncurses doesn't know about it, so we need to reload main screen
361 endwin();
362 initscr();
363 curs_set(0);
365 else
366 res = system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
369 bool Lyrics::SetSong(const MPD::Song &s)
371 if (!s.getArtist().empty() && !s.getTitle().empty())
373 itsSong = s;
374 return true;
376 else
377 return false;
380 #ifdef HAVE_CURL_CURL_H
381 void Lyrics::Save(const std::string &filename, const std::string &lyrics)
383 std::ofstream output(filename.c_str());
384 if (output.is_open())
386 output << lyrics;
387 output.close();
391 void Lyrics::Refetch()
393 if (remove(itsFilename.c_str()) && errno != ENOENT)
395 const char msg[] = "Couldn't remove \"%1%\": %2%";
396 Statusbar::printf(msg, wideShorten(itsFilename, COLS-const_strlen(msg)-25), strerror(errno));
397 return;
399 Load();
402 void Lyrics::ToggleFetcher()
404 if (itsFetcher && *itsFetcher)
405 ++itsFetcher;
406 else
407 itsFetcher = &lyricsPlugins[0];
408 if (*itsFetcher)
409 Statusbar::printf("Using lyrics database: %s", (*itsFetcher)->name());
410 else
411 Statusbar::print("Using all lyrics databases");
414 void Lyrics::Take()
416 assert(isReadyToTake);
417 pthread_join(itsDownloader, 0);
418 w.flush();
419 w.refresh();
420 isDownloadInProgress = 0;
421 isReadyToTake = 0;
423 #endif // HAVE_CURL_CURL_H