actions: make JumpToPlayingSong work also if player is stopped
[ncmpcpp.git] / src / lyrics.cpp
bloba343dbdc6f55e95064cd969389fd3d9b97ca77a2
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 , ReloadNP(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 (ReloadNP)
85 const MPD::Song s = myPlaylist->nowPlayingSong();
86 if (!s.empty() && !s.getArtist().empty() && !s.getTitle().empty())
88 drawHeader();
89 itsScrollBegin = 0;
90 itsSong = s;
91 Load();
93 ReloadNP = 0;
97 void Lyrics::switchTo()
99 using Global::myScreen;
100 if (myScreen != this)
102 # ifdef HAVE_CURL_CURL_H
103 // take lyrics if they were downloaded
104 if (isReadyToTake)
105 Take();
107 if (isDownloadInProgress || itsWorkersNumber > 0)
109 Statusbar::print("Lyrics are being downloaded...");
110 return;
112 # endif // HAVE_CURL_CURL_H
114 const MPD::Song *s = currentSong(myScreen);
115 if (!s)
116 return;
118 if (!s->getArtist().empty() && !s->getTitle().empty())
120 SwitchTo::execute(this);
121 itsScrollBegin = 0;
122 itsSong = *s;
123 Load();
124 drawHeader();
126 else
127 Statusbar::print("Song must have both artist and title tag set");
129 else
130 switchToPreviousScreen();
133 std::wstring Lyrics::title()
135 std::wstring result = L"Lyrics: ";
136 result += Scroller(ToWString(itsSong.toString("{%a - %t}", ", ")), itsScrollBegin, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
137 return result;
140 void Lyrics::spacePressed()
142 Config.now_playing_lyrics = !Config.now_playing_lyrics;
143 Statusbar::printf("Reload lyrics if song changes: %1%",
144 Config.now_playing_lyrics ? "on" : "off"
148 #ifdef HAVE_CURL_CURL_H
149 void Lyrics::DownloadInBackground(const MPD::Song &s)
151 if (s.empty() || s.getArtist().empty() || s.getTitle().empty())
152 return;
154 std::string filename = GenerateFilename(s);
155 std::ifstream f(filename.c_str());
156 if (f.is_open())
158 f.close();
159 return;
161 Statusbar::printf("Fetching lyrics for \"%1%\"...",
162 s.toString(Config.song_status_format_no_colors, Config.tags_separator)
165 MPD::Song *s_copy = new MPD::Song(s);
166 pthread_mutex_lock(&itsDIBLock);
167 if (itsWorkersNumber == itsMaxWorkersNumber)
168 itsToDownload.push(s_copy);
169 else
171 ++itsWorkersNumber;
172 pthread_t t;
173 pthread_attr_t attr;
174 pthread_attr_init(&attr);
175 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
176 pthread_create(&t, &attr, DownloadInBackgroundImpl, s_copy);
178 pthread_mutex_unlock(&itsDIBLock);
181 void *Lyrics::DownloadInBackgroundImpl(void *void_ptr)
183 MPD::Song *s = static_cast<MPD::Song *>(void_ptr);
184 DownloadInBackgroundImplHelper(*s);
185 delete s;
187 while (true)
189 pthread_mutex_lock(&itsDIBLock);
190 if (itsToDownload.empty())
192 pthread_mutex_unlock(&itsDIBLock);
193 break;
195 else
197 s = itsToDownload.front();
198 itsToDownload.pop();
199 pthread_mutex_unlock(&itsDIBLock);
201 DownloadInBackgroundImplHelper(*s);
202 delete s;
205 pthread_mutex_lock(&itsDIBLock);
206 --itsWorkersNumber;
207 pthread_mutex_unlock(&itsDIBLock);
209 pthread_exit(0);
212 void Lyrics::DownloadInBackgroundImplHelper(const MPD::Song &s)
214 std::string artist = Curl::escape(s.getArtist());
215 std::string title = Curl::escape(s.getTitle());
217 LyricsFetcher::Result result;
218 bool fetcher_defined = itsFetcher && *itsFetcher;
219 for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
221 result = (*plugin)->fetch(artist, title);
222 if (result.first)
223 break;
224 if (fetcher_defined)
225 break;
227 if (result.first == true)
228 Save(GenerateFilename(s), result.second);
231 void *Lyrics::Download()
233 std::string artist = Curl::escape(itsSong.getArtist());
234 std::string title_ = Curl::escape(itsSong.getTitle());
236 LyricsFetcher::Result result;
238 // if one of plugins is selected, try only this one,
239 // otherwise try all of them until one of them succeeds
240 bool fetcher_defined = itsFetcher && *itsFetcher;
241 for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
243 w << "Fetching lyrics from " << NC::Format::Bold << (*plugin)->name() << NC::Format::NoBold << "... ";
244 result = (*plugin)->fetch(artist, title_);
245 if (result.first == false)
246 w << NC::Color::Red << result.second << NC::Color::End << '\n';
247 else
248 break;
249 if (fetcher_defined)
250 break;
253 if (result.first == true)
255 Save(itsFilename, result.second);
256 w.clear();
257 w << Charset::utf8ToLocale(result.second);
259 else
260 w << '\n' << "Lyrics weren't found.";
262 isReadyToTake = 1;
263 pthread_exit(0);
266 void *Lyrics::DownloadWrapper(void *this_ptr)
268 return static_cast<Lyrics *>(this_ptr)->Download();
270 #endif // HAVE_CURL_CURL_H
272 std::string Lyrics::GenerateFilename(const MPD::Song &s)
274 std::string filename;
275 if (Config.store_lyrics_in_song_dir)
277 if (s.isFromDatabase())
279 filename = Config.mpd_music_dir;
280 filename += "/";
281 filename += s.getURI();
283 else
284 filename = s.getURI();
285 // replace song's extension with .txt
286 size_t dot = filename.rfind('.');
287 assert(dot != std::string::npos);
288 filename.resize(dot);
289 filename += ".txt";
291 else
293 std::string file = s.getArtist();
294 file += " - ";
295 file += s.getTitle();
296 file += ".txt";
297 removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
298 filename = Config.lyrics_directory;
299 filename += "/";
300 filename += file;
302 return filename;
305 void Lyrics::Load()
307 # ifdef HAVE_CURL_CURL_H
308 if (isDownloadInProgress)
309 return;
310 # endif // HAVE_CURL_CURL_H
312 assert(!itsSong.getArtist().empty());
313 assert(!itsSong.getTitle().empty());
315 itsFilename = GenerateFilename(itsSong);
317 w.clear();
318 w.reset();
320 std::ifstream input(itsFilename.c_str());
321 if (input.is_open())
323 bool first = 1;
324 std::string line;
325 while (std::getline(input, line))
327 if (!first)
328 w << '\n';
329 w << Charset::utf8ToLocale(line);
330 first = 0;
332 w.flush();
333 if (ReloadNP)
334 w.refresh();
336 else
338 # ifdef HAVE_CURL_CURL_H
339 pthread_create(&itsDownloader, 0, DownloadWrapper, this);
340 isDownloadInProgress = 1;
341 # else
342 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.";
343 w.flush();
344 # endif
348 void Lyrics::Edit()
350 assert(Global::myScreen == this);
352 if (Config.external_editor.empty())
354 Statusbar::print("Proper external_editor variable has to be set in configuration file");
355 return;
358 Statusbar::print("Opening lyrics in external editor...");
360 GNUC_UNUSED int res;
361 if (Config.use_console_editor)
363 res = system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str());
364 Load();
365 // below is needed as screen gets cleared, but apparently
366 // ncurses doesn't know about it, so we need to reload main screen
367 endwin();
368 initscr();
369 curs_set(0);
371 else
372 res = system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
375 #ifdef HAVE_CURL_CURL_H
376 void Lyrics::Save(const std::string &filename, const std::string &lyrics)
378 std::ofstream output(filename.c_str());
379 if (output.is_open())
381 output << lyrics;
382 output.close();
386 void Lyrics::Refetch()
388 if (remove(itsFilename.c_str()) && errno != ENOENT)
390 const char msg[] = "Couldn't remove \"%1%\": %2%";
391 Statusbar::printf(msg, wideShorten(itsFilename, COLS-const_strlen(msg)-25), strerror(errno));
392 return;
394 Load();
397 void Lyrics::ToggleFetcher()
399 if (itsFetcher && *itsFetcher)
400 ++itsFetcher;
401 else
402 itsFetcher = &lyricsPlugins[0];
403 if (*itsFetcher)
404 Statusbar::printf("Using lyrics database: %s", (*itsFetcher)->name());
405 else
406 Statusbar::print("Using all lyrics databases");
409 void Lyrics::Take()
411 assert(isReadyToTake);
412 pthread_join(itsDownloader, 0);
413 w.flush();
414 w.refresh();
415 isDownloadInProgress = 0;
416 isReadyToTake = 0;
418 #endif // HAVE_CURL_CURL_H