configure: explicitly link with boost.system
[ncmpcpp/stream.git] / src / lyrics.cpp
blob7f951c9c03225c6b0ed65f256791a740347bb36e
1 /***************************************************************************
2 * Copyright (C) 2008-2013 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::msg("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::msg("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.new_design ? 2 : Global::VolumeState.length()));
137 return result;
140 void Lyrics::spacePressed()
142 Config.now_playing_lyrics = !Config.now_playing_lyrics;
143 Statusbar::msg("Reload lyrics if song changes: %s", Config.now_playing_lyrics ? "On" : "Off");
146 #ifdef HAVE_CURL_CURL_H
147 void Lyrics::DownloadInBackground(const MPD::Song &s)
149 if (s.empty() || s.getArtist().empty() || s.getTitle().empty())
150 return;
152 std::string filename = GenerateFilename(s);
153 std::ifstream f(filename.c_str());
154 if (f.is_open())
156 f.close();
157 return;
159 Statusbar::msg("Fetching lyrics for \"%s\"...", s.toString(Config.song_status_format_no_colors, Config.tags_separator).c_str());
161 MPD::Song *s_copy = new MPD::Song(s);
162 pthread_mutex_lock(&itsDIBLock);
163 if (itsWorkersNumber == itsMaxWorkersNumber)
164 itsToDownload.push(s_copy);
165 else
167 ++itsWorkersNumber;
168 pthread_t t;
169 pthread_attr_t attr;
170 pthread_attr_init(&attr);
171 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
172 pthread_create(&t, &attr, DownloadInBackgroundImpl, s_copy);
174 pthread_mutex_unlock(&itsDIBLock);
177 void *Lyrics::DownloadInBackgroundImpl(void *void_ptr)
179 MPD::Song *s = static_cast<MPD::Song *>(void_ptr);
180 DownloadInBackgroundImplHelper(*s);
181 delete s;
183 while (true)
185 pthread_mutex_lock(&itsDIBLock);
186 if (itsToDownload.empty())
188 pthread_mutex_unlock(&itsDIBLock);
189 break;
191 else
193 s = itsToDownload.front();
194 itsToDownload.pop();
195 pthread_mutex_unlock(&itsDIBLock);
197 DownloadInBackgroundImplHelper(*s);
198 delete s;
201 pthread_mutex_lock(&itsDIBLock);
202 --itsWorkersNumber;
203 pthread_mutex_unlock(&itsDIBLock);
205 pthread_exit(0);
208 void Lyrics::DownloadInBackgroundImplHelper(const MPD::Song &s)
210 std::string artist = Curl::escape(s.getArtist());
211 std::string title = Curl::escape(s.getTitle());
213 LyricsFetcher::Result result;
214 bool fetcher_defined = itsFetcher && *itsFetcher;
215 for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
217 result = (*plugin)->fetch(artist, title);
218 if (result.first)
219 break;
220 if (fetcher_defined)
221 break;
223 if (result.first == true)
224 Save(GenerateFilename(s), result.second);
227 void *Lyrics::Download()
229 std::string artist = Curl::escape(itsSong.getArtist());
230 std::string title_ = Curl::escape(itsSong.getTitle());
232 LyricsFetcher::Result result;
234 // if one of plugins is selected, try only this one,
235 // otherwise try all of them until one of them succeeds
236 bool fetcher_defined = itsFetcher && *itsFetcher;
237 for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
239 w << "Fetching lyrics from " << NC::Format::Bold << (*plugin)->name() << NC::Format::NoBold << "... ";
240 result = (*plugin)->fetch(artist, title_);
241 if (result.first == false)
242 w << NC::Color::Red << result.second << NC::Color::End << '\n';
243 else
244 break;
245 if (fetcher_defined)
246 break;
249 if (result.first == true)
251 Save(itsFilename, result.second);
252 w.clear();
253 w << Charset::utf8ToLocale(result.second);
255 else
256 w << '\n' << "Lyrics weren't found.";
258 isReadyToTake = 1;
259 pthread_exit(0);
262 void *Lyrics::DownloadWrapper(void *this_ptr)
264 return static_cast<Lyrics *>(this_ptr)->Download();
266 #endif // HAVE_CURL_CURL_H
268 std::string Lyrics::GenerateFilename(const MPD::Song &s)
270 std::string filename;
271 if (Config.store_lyrics_in_song_dir)
273 if (s.isFromDatabase())
275 filename = Config.mpd_music_dir;
276 filename += "/";
277 filename += s.getURI();
279 else
280 filename = s.getURI();
281 // replace song's extension with .txt
282 size_t dot = filename.rfind('.');
283 assert(dot != std::string::npos);
284 filename.resize(dot);
285 filename += ".txt";
287 else
289 std::string file = s.getArtist();
290 file += " - ";
291 file += s.getTitle();
292 file += ".txt";
293 removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
294 filename = Config.lyrics_directory;
295 filename += "/";
296 filename += file;
298 return filename;
301 void Lyrics::Load()
303 # ifdef HAVE_CURL_CURL_H
304 if (isDownloadInProgress)
305 return;
306 # endif // HAVE_CURL_CURL_H
308 assert(!itsSong.getArtist().empty());
309 assert(!itsSong.getTitle().empty());
311 itsFilename = GenerateFilename(itsSong);
313 CreateDir(Config.lyrics_directory);
315 w.clear();
316 w.reset();
318 std::ifstream input(itsFilename.c_str());
319 if (input.is_open())
321 bool first = 1;
322 std::string line;
323 while (std::getline(input, line))
325 if (!first)
326 w << '\n';
327 w << Charset::utf8ToLocale(line);
328 first = 0;
330 w.flush();
331 if (ReloadNP)
332 w.refresh();
334 else
336 # ifdef HAVE_CURL_CURL_H
337 pthread_create(&itsDownloader, 0, DownloadWrapper, this);
338 isDownloadInProgress = 1;
339 # else
340 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.";
341 w.flush();
342 # endif
346 void Lyrics::Edit()
348 assert(Global::myScreen == this);
350 if (Config.external_editor.empty())
352 Statusbar::msg("Proper external_editor variable has to be set in configuration file");
353 return;
356 Statusbar::msg("Opening lyrics in external editor...");
358 GNUC_UNUSED int res;
359 if (Config.use_console_editor)
361 res = system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str());
362 Load();
363 // below is needed as screen gets cleared, but apparently
364 // ncurses doesn't know about it, so we need to reload main screen
365 endwin();
366 initscr();
367 curs_set(0);
369 else
370 res = system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
373 #ifdef HAVE_CURL_CURL_H
374 void Lyrics::Save(const std::string &filename, const std::string &lyrics)
376 std::ofstream output(filename.c_str());
377 if (output.is_open())
379 output << lyrics;
380 output.close();
384 void Lyrics::Refetch()
386 if (remove(itsFilename.c_str()) && errno != ENOENT)
388 const char msg[] = "Couldn't remove \"%ls\": %s";
389 Statusbar::msg(msg, wideShorten(ToWString(itsFilename), COLS-const_strlen(msg)-25).c_str(), strerror(errno));
390 return;
392 Load();
395 void Lyrics::ToggleFetcher()
397 if (itsFetcher && *itsFetcher)
398 ++itsFetcher;
399 else
400 itsFetcher = &lyricsPlugins[0];
401 if (*itsFetcher)
402 Statusbar::msg("Using lyrics database: %s", (*itsFetcher)->name());
403 else
404 Statusbar::msg("Using all lyrics databases");
407 void Lyrics::Take()
409 assert(isReadyToTake);
410 pthread_join(itsDownloader, 0);
411 w.flush();
412 w.refresh();
413 isDownloadInProgress = 0;
414 isReadyToTake = 0;
416 #endif // HAVE_CURL_CURL_H