version bump
[scrobby.git] / src / scrobby.cpp
blob698d4930f048bed7bb4d37c7a3eb1d82c74c04ba
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 <cstdlib>
23 #include <curl/curl.h>
24 #include <pthread.h>
25 #include <iostream>
26 #include <vector>
28 #include "callback.h"
29 #include "configuration.h"
30 #include "misc.h"
31 #include "scrobby.h"
32 #include "song.h"
33 #include "mpdpp.h"
35 using std::string;
37 ScrobbyConfig config;
39 Handshake handshake;
40 MPD::Song s;
42 pthread_t mpdconnection_th;
43 pthread_t handshake_th;
45 pthread_mutex_t curl_lock = PTHREAD_MUTEX_INITIALIZER;
46 pthread_mutex_t handshake_lock = PTHREAD_MUTEX_INITIALIZER;
48 std::vector<string> SongsQueue;
50 bool scrobby_exit = 0;
51 bool notify_about_now_playing = 0;
53 namespace {
54 void signal_handler(int);
55 bool send_handshake();
57 void *mpdconnection_handler(void *data);
58 void *handshake_handler(void *);
61 int main(int argc, char **argv)
63 DefaultConfiguration(config);
65 if (argc > 1)
67 ParseArgv(config, argc, argv);
69 if (!config.file_config.empty())
71 if (!ReadConfiguration(config, config.file_config))
73 std::cerr << "cannot read configuration file: " << config.file_config << std::endl;
74 return 1;
77 else if (!ReadConfiguration(config, string(getenv("HOME") ? getenv("HOME") : "") + "/.scrobbyconf"))
79 if (!ReadConfiguration(config, "/etc/scrobby.conf"))
81 std::cerr << "default configuration files not found!\n";
82 return 1;
85 if (config.log_level == llUndefined)
87 config.log_level = llInfo;
89 if (config.lastfm_user.empty() || (config.lastfm_md5_password.empty() && config.lastfm_password.empty()))
91 std::cerr << "last.fm user/password is not set.\n";
92 return 1;
94 ChangeToUser();
95 if (!CheckFiles(config))
97 return 1;
99 if (config.daemonize)
101 if (!Daemonize())
102 std::cerr << "couldn't daemonize!\n";
105 GetCachedSongs(SongsQueue);
107 MPD::Connection *Mpd = new MPD::Connection;
109 if (config.mpd_host != "localhost")
110 Mpd->SetHostname(config.mpd_host);
111 if (config.mpd_port != 6600)
112 Mpd->SetPort(config.mpd_port);
114 Mpd->SetTimeout(config.mpd_timeout);
115 Mpd->SetStatusUpdater(ScrobbyStatusChanged, NULL);
116 Mpd->SetErrorHandler(ScrobbyErrorCallback, NULL);
118 signal(SIGHUP, signal_handler);
119 signal(SIGINT, signal_handler);
120 signal(SIGTERM, signal_handler);
121 signal(SIGPIPE, SIG_IGN);
123 pthread_create(&mpdconnection_th, NULL, mpdconnection_handler, Mpd);
124 pthread_create(&handshake_th, NULL, handshake_handler, NULL);
125 pthread_detach(mpdconnection_th);
126 pthread_detach(handshake_th);
128 while (!scrobby_exit && !usleep(500000))
130 if (Mpd->Connected())
131 Mpd->UpdateStatus();
134 s.Submit();
135 Log(llInfo, "Shutting down...");
136 if (remove(config.file_pid.c_str()) != 0)
137 Log(llInfo, "Couldn't remove pid file!");
138 delete Mpd;
140 return 0;
143 namespace {
144 void signal_handler(int)
146 scrobby_exit = 1;
149 bool send_handshake()
151 CURLcode code;
152 string handshake_url;
153 string result;
154 string timestamp = IntoStr(time(NULL));
156 handshake_url = "http://post.audioscrobbler.com/?hs=true&p=1.2.1&c=mpc&v="VERSION"&u=";
157 handshake_url += config.lastfm_user;
158 handshake_url += "&t=";
159 handshake_url += timestamp;
160 handshake_url += "&a=";
161 handshake_url += md5sum((config.lastfm_md5_password.empty() ? md5sum(config.lastfm_password) : config.lastfm_md5_password) + timestamp);
163 pthread_mutex_lock(&curl_lock);
164 CURL *hs = curl_easy_init();
165 curl_easy_setopt(hs, CURLOPT_URL, handshake_url.c_str());
166 curl_easy_setopt(hs, CURLOPT_WRITEFUNCTION, write_data);
167 curl_easy_setopt(hs, CURLOPT_WRITEDATA, &result);
168 curl_easy_setopt(hs, CURLOPT_CONNECTTIMEOUT, curl_timeout);
169 code = curl_easy_perform(hs);
170 curl_easy_cleanup(hs);
171 pthread_mutex_unlock(&curl_lock);
173 if (code != CURLE_OK)
175 Log(llInfo, "Error while sending handshake: %s", curl_easy_strerror(code));
176 return false;
179 size_t i = result.find("\n");
180 handshake.status = result.substr(0, i);
181 if (handshake.status != "OK")
182 return false;
183 result = result.substr(i+1);
184 i = result.find("\n");
185 handshake.session_id = result.substr(0, i);
186 result = result.substr(i+1);
187 i = result.find("\n");
188 handshake.nowplaying_url = result.substr(0, i);
189 result = result.substr(i+1);
190 ignore_newlines(result);
191 handshake.submission_url = result;
192 return true;
195 void *mpdconnection_handler(void *data)
197 MPD::Connection *Mpd = static_cast<MPD::Connection *>(data);
198 while (!scrobby_exit)
200 int x = 0;
201 while (!Mpd->Connected())
203 s.Submit();
204 Log(llVerbose, "Connecting to MPD...");
205 Mpd->Disconnect();
206 if (Mpd->Connect())
208 Log(llInfo, "Connected to MPD at %s !", config.mpd_host.c_str());
209 x = 0;
211 else
213 x++;
214 Log(llInfo, "Cannot connect to MPD, retrieving in %d seconds...", 10*x);
215 sleep(10*x);
218 sleep(1);
220 pthread_exit(NULL);
223 void *handshake_handler(void *)
225 int x = 0;
226 while (!scrobby_exit)
228 if (handshake.status != "OK")
230 pthread_mutex_lock(&handshake_lock);
231 handshake.Clear();
232 if (send_handshake() && !handshake.status.empty())
234 Log(llInfo, "Handshake returned %s", handshake.status.c_str());
236 if (handshake.status == "OK")
238 Log(llInfo, "Connected to Audioscrobbler!");
239 if (!SongsQueue.empty())
241 Log(llInfo, "Queue's not empty, submitting songs...");
243 string result, postdata;
244 CURLcode code;
246 pthread_mutex_lock(&curl_lock);
247 CURL *submission = curl_easy_init();
249 postdata = "s=";
250 postdata += handshake.session_id;
252 for (std::vector<string>::const_iterator it = SongsQueue.begin(); it != SongsQueue.end(); it++)
253 postdata += *it;
255 Log(llVerbose, "URL: %s", handshake.submission_url.c_str());
256 Log(llVerbose, "Post data: %s", postdata.c_str());
258 curl_easy_setopt(submission, CURLOPT_URL, handshake.submission_url.c_str());
259 curl_easy_setopt(submission, CURLOPT_POST, 1);
260 curl_easy_setopt(submission, CURLOPT_POSTFIELDS, postdata.c_str());
261 curl_easy_setopt(submission, CURLOPT_WRITEFUNCTION, write_data);
262 curl_easy_setopt(submission, CURLOPT_WRITEDATA, &result);
263 curl_easy_setopt(submission, CURLOPT_CONNECTTIMEOUT, curl_timeout);
264 code = curl_easy_perform(submission);
265 curl_easy_cleanup(submission);
266 pthread_mutex_unlock(&curl_lock);
268 ignore_newlines(result);
270 if (result == "OK")
272 Log(llInfo, "Number of submitted songs: %d", SongsQueue.size());
273 SongsQueue.clear();
274 ClearCache();
275 x = 0;
277 else
279 if (result.empty())
281 Log(llInfo, "Error while submitting songs: %s", curl_easy_strerror(code));
283 else
285 Log(llInfo, "Audioscrobbler returned status %s", result.c_str());
289 notify_about_now_playing = !s.isStream();
291 else
293 x++;
294 Log(llInfo, "Connection to Audioscrobbler refused, retrieving in %d seconds...", 10*x);
296 pthread_mutex_unlock(&handshake_lock);
298 sleep(!x ? 1 : 10*x);
300 pthread_exit(NULL);