add support for log levels
[scrobby.git] / src / scrobby.cpp
blob17ef75461fd5dcf7926c2c9b3d30eb4d952450a0
1 /***************************************************************************
2 * Copyright (C) 2008 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 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include <csignal>
22 #include <curl/curl.h>
23 #include <pthread.h>
24 #include <iostream>
25 #include <vector>
27 #include "callback.h"
28 #include "configuration.h"
29 #include "misc.h"
30 #include "scrobby.h"
31 #include "mpdpp.h"
33 using std::string;
35 ScrobbyConfig config;
37 HandshakeResult hr;
38 SubmissionCandidate sc;
40 pthread_t mpdconnection_th;
41 pthread_t handshake_th;
43 pthread_mutex_t curl = PTHREAD_MUTEX_INITIALIZER;
44 pthread_mutex_t hr_lock = PTHREAD_MUTEX_INITIALIZER;
46 std::vector<string> queue;
48 bool exit = 0;
49 bool notify_about_now_playing = 0;
51 namespace
53 void signal_handler(int);
54 bool send_handshake();
56 void *mpdconnection_handler(void *data);
57 void *handshake_handler(void *);
60 int main(/*int argc, char **argv*/)
62 DefaultConfiguration(config);
64 if (!ReadConfiguration(config, "/etc/scrobby.conf"))
66 std::cerr << "cannot read configuration file!";
67 return 1;
69 if (config.lastfm_user.empty() || (config.lastfm_md5_password.empty() && config.lastfm_password.empty()))
71 std::cerr << "last.fm user/password is not set.\n";
72 return 1;
74 if (!CheckFiles(config))
76 return 1;
78 if (!Daemonize())
79 std::cerr << "couldn't daemonize!\n";
81 GetCachedSongs(queue);
83 MPD::Connection *Mpd = new MPD::Connection;
85 if (config.mpd_host != "localhost")
86 Mpd->SetHostname(config.mpd_host);
87 if (config.mpd_port != 6600)
88 Mpd->SetPort(config.mpd_port);
89 if (!config.mpd_password.empty())
90 Mpd->SetPassword(config.mpd_password);
92 Mpd->SetTimeout(config.mpd_timeout);
93 Mpd->SetStatusUpdater(ScrobbyStatusChanged, NULL);
94 Mpd->SetErrorHandler(ScrobbyErrorCallback, NULL);
96 signal(SIGHUP, signal_handler);
97 signal(SIGINT, signal_handler);
98 signal(SIGTERM, signal_handler);
99 signal(SIGPIPE, SIG_IGN);
101 pthread_create(&mpdconnection_th, NULL, mpdconnection_handler, Mpd);
102 pthread_create(&handshake_th, NULL, handshake_handler, NULL);
103 pthread_detach(mpdconnection_th);
104 pthread_detach(handshake_th);
106 sleep(1);
108 while (!exit && !usleep(500000))
110 if (Mpd->Connected())
111 Mpd->UpdateStatus();
114 SubmitSong(sc);
115 Log("Shutting down...", llInfo);
116 if (remove(config.file_pid.c_str()) != 0)
117 Log("Couldn't remove pid file!", llInfo);
118 delete Mpd;
120 return 0;
123 void SubmissionCandidate::Clear()
125 if (song)
126 mpd_freeSong(song);
127 song = 0;
128 started_time = 0;
129 noticed_playback = 0;
132 bool SubmissionCandidate::canBeSubmitted()
134 if (!started_time || song->time < 30 || !song->artist || !song->title)
136 if (!started_time)
138 Log("Song's start time isn't known, not submitting.", llInfo);
140 else if (song->time < 30)
142 Log("Song's length is too short, not submitting.", llInfo);
144 else if (!song->artist || !song->title)
146 Log("Song has missing tags, not submitting.", llInfo);
148 return false;
150 else if (noticed_playback < 4*60 && noticed_playback < song->time/2)
152 Log("Noticed playback was too short, not submitting.", llInfo);
153 return false;
155 return true;
158 void SubmitSong(SubmissionCandidate &sc)
160 if (!sc.song)
161 return;
163 if (sc.canBeSubmitted())
165 if (hr.status != "OK" || hr.submission_url.empty())
167 Log("Problems with handshake status, queue song at position " + IntoStr(queue.size()) + "...", llInfo);
168 goto SUBMISSION_FAILED;
171 Log("Submitting song...", llInfo);
173 string result, postdata;
174 CURLcode code;
176 pthread_mutex_lock(&curl);
177 CURL *submission = curl_easy_init();
179 char *c_artist = curl_easy_escape(submission, sc.song->artist, 0);
180 char *c_title = curl_easy_escape(submission, sc.song->title, 0);
181 char *c_album = sc.song->album ? curl_easy_escape(submission, sc.song->album, 0) : NULL;
182 char *c_track = sc.song->track ? curl_easy_escape(submission, sc.song->track, 0) : NULL;
184 postdata = "s=";
185 postdata += hr.session_id;
186 postdata += "&a[0]=";
187 postdata += c_artist;
188 postdata += "&t[0]=";
189 postdata += c_title;
190 postdata += "&i[0]=";
191 postdata += IntoStr(sc.started_time);
192 postdata += "&o[0]=P";
193 postdata += "&r[0]=";
194 postdata += "&l[0]=";
195 postdata += IntoStr(sc.song->time);
196 postdata += "&b[0]=";
197 if (c_album)
198 postdata += c_album;
199 postdata += "&n[0]=";
200 if (c_track)
201 postdata += c_track;
202 postdata += "&m[0]=";
204 curl_free(c_artist);
205 curl_free(c_title);
206 curl_free(c_album);
207 curl_free(c_track);
209 Log("URL: " + hr.submission_url, llVerbose);
210 Log("Post data: " + postdata, llVerbose);
212 curl_easy_setopt(submission, CURLOPT_URL, hr.submission_url.c_str());
213 curl_easy_setopt(submission, CURLOPT_POST, 1);
214 curl_easy_setopt(submission, CURLOPT_POSTFIELDS, postdata.c_str());
215 curl_easy_setopt(submission, CURLOPT_WRITEFUNCTION, write_data);
216 curl_easy_setopt(submission, CURLOPT_WRITEDATA, &result);
217 curl_easy_setopt(submission, CURLOPT_CONNECTTIMEOUT, 5);
218 code = curl_easy_perform(submission);
219 curl_easy_cleanup(submission);
220 pthread_mutex_unlock(&curl);
222 ignore_newlines(result);
224 if (result == "OK")
226 Log("Song submitted.", llInfo);
228 else
230 if (result.empty())
232 Log("Error while submitting song: " + string(curl_easy_strerror(code)), llInfo);
234 else
236 Log("Audioscrobbler returned status " + result, llInfo);
238 goto SUBMISSION_FAILED;
241 if (0)
243 SUBMISSION_FAILED: // so we cache not submitted song
245 pthread_mutex_lock(&hr_lock);
246 hr.Clear(); // handshake probably failed if we are here, so reset it
247 Log("Handshake status reset", llVerbose);
249 string cache;
250 string offset = IntoStr(queue.size());
252 char *c_artist = curl_easy_escape(0, sc.song->artist, 0);
253 char *c_title = curl_easy_escape(0, sc.song->title, 0);
254 char *c_album = sc.song->album ? curl_easy_escape(0, sc.song->album, 0) : NULL;
255 char *c_track = sc.song->track ? curl_easy_escape(0, sc.song->track, 0) : NULL;
257 cache = "&a[";
258 cache += offset;
259 cache += "]=";
260 cache += c_artist;
261 cache += "&t[";
262 cache += offset;
263 cache += "]=";
264 cache += c_title;
265 cache += "&i[";
266 cache += offset;
267 cache += "]=";
268 cache += IntoStr(sc.started_time);
269 cache += "&o[";
270 cache += offset;
271 cache += "]=P";
272 cache += "&r[";
273 cache += offset;
274 cache += "]=";
275 cache += "&l[";
276 cache += offset;
277 cache += "]=";
278 cache += IntoStr(sc.song->time);
279 cache += "&b[";
280 cache += offset;
281 cache += "]=";
282 if (c_album)
283 cache += c_album;
284 cache += "&n[";
285 cache += offset;
286 cache += "]=";
287 if (c_track)
288 cache += c_track;
289 cache += "&m[";
290 cache += offset;
291 cache += "]=";
293 Log("Metadata: " + cache, llVerbose);
295 curl_free(c_artist);
296 curl_free(c_title);
297 curl_free(c_album);
298 curl_free(c_track);
300 Cache(cache);
301 queue.push_back(cache);
302 Log("Song cached.", llInfo);
303 pthread_mutex_unlock(&hr_lock);
305 sc.Clear();
308 namespace
310 void signal_handler(int)
312 exit = 1;
315 bool send_handshake()
317 CURLcode code;
318 string handshake_url;
319 string result;
320 string timestamp = IntoStr(time(NULL));
322 handshake_url = "http://post.audioscrobbler.com/?hs=true&p=1.2.1&c=mpc&v="VERSION"&u=";
323 handshake_url += config.lastfm_user;
324 handshake_url += "&t=";
325 handshake_url += timestamp;
326 handshake_url += "&a=";
327 handshake_url += md5sum((config.lastfm_md5_password.empty() ? md5sum(config.lastfm_password) : config.lastfm_md5_password) + timestamp);
329 pthread_mutex_lock(&curl);
330 CURL *handshake = curl_easy_init();
331 curl_easy_setopt(handshake, CURLOPT_URL, handshake_url.c_str());
332 curl_easy_setopt(handshake, CURLOPT_WRITEFUNCTION, write_data);
333 curl_easy_setopt(handshake, CURLOPT_WRITEDATA, &result);
334 curl_easy_setopt(handshake, CURLOPT_CONNECTTIMEOUT, 5);
335 code = curl_easy_perform(handshake);
336 curl_easy_cleanup(handshake);
337 pthread_mutex_unlock(&curl);
339 if (code != CURLE_OK)
341 Log("Error while sending handshake: " + string(curl_easy_strerror(code)), llInfo);
342 return false;
345 int i = result.find("\n");
346 hr.status = result.substr(0, i);
347 if (hr.status != "OK")
348 return false;
349 result = result.substr(i+1);
350 i = result.find("\n");
351 hr.session_id = result.substr(0, i);
352 result = result.substr(i+1);
353 i = result.find("\n");
354 hr.nowplaying_url = result.substr(0, i);
355 result = result.substr(i+1);
356 ignore_newlines(result);
357 hr.submission_url = result;
358 return true;
361 void *mpdconnection_handler(void *data)
363 MPD::Connection *Mpd = static_cast<MPD::Connection *>(data);
364 while (!exit)
366 int x = 0;
367 while (!Mpd->Connected())
369 SubmitSong(sc);
370 Log("Connecting to MPD...", llVerbose);
371 Mpd->Disconnect();
372 if (Mpd->Connect())
374 Log("Connected to " + config.mpd_host + "!", llInfo);
375 x = 0;
377 else
379 x++;
380 Log("Cannot connect, retrieving in " + IntoStr(10*x) + " seconds...", llInfo);
381 sleep(10*x);
384 sleep(1);
386 pthread_exit(NULL);
389 void *handshake_handler(void *)
391 int x = 0;
392 while (!exit)
394 if (hr.status != "OK")
396 pthread_mutex_lock(&hr_lock);
397 hr.Clear();
398 if (send_handshake() && !hr.status.empty())
400 Log("Handshake returned " + hr.status, llInfo);
402 if (hr.status == "OK")
404 Log("Connected to Audioscrobbler!", llInfo);
405 if (!queue.empty())
407 Log("Queue's not empty, submitting songs...", llInfo);
409 string result, postdata;
410 CURLcode code;
412 pthread_mutex_lock(&curl);
413 CURL *submission = curl_easy_init();
415 postdata = "s=";
416 postdata += hr.session_id;
418 for (std::vector<string>::const_iterator it = queue.begin(); it != queue.end(); it++)
419 postdata += *it;
421 Log("URL: " + hr.submission_url, llVerbose);
422 Log("Post data: " + postdata, llVerbose);
424 curl_easy_setopt(submission, CURLOPT_URL, hr.submission_url.c_str());
425 curl_easy_setopt(submission, CURLOPT_POST, 1);
426 curl_easy_setopt(submission, CURLOPT_POSTFIELDS, postdata.c_str());
427 curl_easy_setopt(submission, CURLOPT_WRITEFUNCTION, write_data);
428 curl_easy_setopt(submission, CURLOPT_WRITEDATA, &result);
429 curl_easy_setopt(submission, CURLOPT_CONNECTTIMEOUT, 5);
430 code = curl_easy_perform(submission);
431 curl_easy_cleanup(submission);
432 pthread_mutex_unlock(&curl);
434 ignore_newlines(result);
436 if (result == "OK")
438 Log("Number of submitted songs: " + IntoStr(queue.size()), llInfo);
439 queue.clear();
440 ClearCache();
441 x = 0;
443 else
445 if (result.empty())
447 Log("Error while submitting songs: " + string(curl_easy_strerror(code)), llInfo);
449 else
451 Log("Audioscrobbler returned status " + result, llInfo);
455 notify_about_now_playing = 1;
457 else
459 x++;
460 Log("Connection refused, retrieving in " + IntoStr(10*x) + " seconds...", llInfo);
461 sleep(10*x);
463 pthread_mutex_unlock(&hr_lock);
465 sleep(1);
467 pthread_exit(NULL);