1 /***************************************************************************
2 * Copyright (C) 2008 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 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
22 #include <curl/curl.h>
28 #include "configuration.h"
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
;
49 bool notify_about_now_playing
= 0;
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!";
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";
74 if (!CheckFiles(config
))
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
);
108 while (!exit
&& !usleep(500000))
110 if (Mpd
->Connected())
115 Log("Shutting down...", llInfo
);
116 if (remove(config
.file_pid
.c_str()) != 0)
117 Log("Couldn't remove pid file!", llInfo
);
123 void SubmissionCandidate::Clear()
129 noticed_playback
= 0;
132 bool SubmissionCandidate::canBeSubmitted()
134 if (!started_time
|| song
->time
< 30 || !song
->artist
|| !song
->title
)
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
);
150 else if (noticed_playback
< 4*60 && noticed_playback
< song
->time
/2)
152 Log("Noticed playback was too short, not submitting.", llInfo
);
158 void SubmitSong(SubmissionCandidate
&sc
)
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
;
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
;
185 postdata
+= hr
.session_id
;
186 postdata
+= "&a[0]=";
187 postdata
+= c_artist
;
188 postdata
+= "&t[0]=";
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]=";
199 postdata
+= "&n[0]=";
202 postdata
+= "&m[0]=";
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
);
226 Log("Song submitted.", llInfo
);
232 Log("Error while submitting song: " + string(curl_easy_strerror(code
)), llInfo
);
236 Log("Audioscrobbler returned status " + result
, llInfo
);
238 goto SUBMISSION_FAILED
;
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
);
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
;
268 cache
+= IntoStr(sc
.started_time
);
278 cache
+= IntoStr(sc
.song
->time
);
293 Log("Metadata: " + cache
, llVerbose
);
301 queue
.push_back(cache
);
302 Log("Song cached.", llInfo
);
303 pthread_mutex_unlock(&hr_lock
);
310 void signal_handler(int)
315 bool send_handshake()
318 string handshake_url
;
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
);
345 int i
= result
.find("\n");
346 hr
.status
= result
.substr(0, i
);
347 if (hr
.status
!= "OK")
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
;
361 void *mpdconnection_handler(void *data
)
363 MPD::Connection
*Mpd
= static_cast<MPD::Connection
*>(data
);
367 while (!Mpd
->Connected())
370 Log("Connecting to MPD...", llVerbose
);
374 Log("Connected to " + config
.mpd_host
+ "!", llInfo
);
380 Log("Cannot connect, retrieving in " + IntoStr(10*x
) + " seconds...", llInfo
);
389 void *handshake_handler(void *)
394 if (hr
.status
!= "OK")
396 pthread_mutex_lock(&hr_lock
);
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
);
407 Log("Queue's not empty, submitting songs...", llInfo
);
409 string result
, postdata
;
412 pthread_mutex_lock(&curl
);
413 CURL
*submission
= curl_easy_init();
416 postdata
+= hr
.session_id
;
418 for (std::vector
<string
>::const_iterator it
= queue
.begin(); it
!= queue
.end(); 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
);
438 Log("Number of submitted songs: " + IntoStr(queue
.size()), llInfo
);
447 Log("Error while submitting songs: " + string(curl_easy_strerror(code
)), llInfo
);
451 Log("Audioscrobbler returned status " + result
, llInfo
);
455 notify_about_now_playing
= 1;
460 Log("Connection refused, retrieving in " + IntoStr(10*x
) + " seconds...", llInfo
);
463 pthread_mutex_unlock(&hr_lock
);