make code more object oriented
[scrobby.git] / src / scrobby.cpp
blob29003a69d9ba4e295358d3d0cc018c75c1fdd4fb
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 "song.h"
32 #include "mpdpp.h"
34 using std::string;
36 ScrobbyConfig config;
38 Handshake handshake;
39 MPD::Song s;
41 pthread_t mpdconnection_th;
42 pthread_t handshake_th;
44 pthread_mutex_t curl_lock = PTHREAD_MUTEX_INITIALIZER;
45 pthread_mutex_t handshake_lock = PTHREAD_MUTEX_INITIALIZER;
47 std::vector<string> SongsQueue;
49 bool scrobby_exit = 0;
50 bool notify_about_now_playing = 0;
52 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 const string config_file = argc > 1 ? argv[1] : "/etc/scrobby.conf";
65 DefaultConfiguration(config);
67 if (!ReadConfiguration(config, config_file))
69 std::cerr << "cannot read configuration file: " << config_file << std::endl;
70 return 1;
72 if (config.lastfm_user.empty() || (config.lastfm_md5_password.empty() && config.lastfm_password.empty()))
74 std::cerr << "last.fm user/password is not set.\n";
75 return 1;
77 if (!CheckFiles(config))
79 return 1;
81 if (!Daemonize())
82 std::cerr << "couldn't daemonize!\n";
84 GetCachedSongs(SongsQueue);
86 MPD::Connection *Mpd = new MPD::Connection;
88 if (config.mpd_host != "localhost")
89 Mpd->SetHostname(config.mpd_host);
90 if (config.mpd_port != 6600)
91 Mpd->SetPort(config.mpd_port);
92 if (!config.mpd_password.empty())
93 Mpd->SetPassword(config.mpd_password);
95 Mpd->SetTimeout(config.mpd_timeout);
96 Mpd->SetStatusUpdater(ScrobbyStatusChanged, NULL);
97 Mpd->SetErrorHandler(ScrobbyErrorCallback, NULL);
99 signal(SIGHUP, signal_handler);
100 signal(SIGINT, signal_handler);
101 signal(SIGTERM, signal_handler);
102 signal(SIGPIPE, SIG_IGN);
104 pthread_create(&mpdconnection_th, NULL, mpdconnection_handler, Mpd);
105 pthread_create(&handshake_th, NULL, handshake_handler, NULL);
106 pthread_detach(mpdconnection_th);
107 pthread_detach(handshake_th);
109 while (!scrobby_exit && !usleep(500000))
111 if (Mpd->Connected())
112 Mpd->UpdateStatus();
115 s.Submit();
116 Log("Shutting down...", llInfo);
117 if (remove(config.file_pid.c_str()) != 0)
118 Log("Couldn't remove pid file!", llInfo);
119 delete Mpd;
121 return 0;
124 namespace
126 void signal_handler(int)
128 scrobby_exit = 1;
131 bool send_handshake()
133 CURLcode code;
134 string handshake_url;
135 string result;
136 string timestamp = IntoStr(time(NULL));
138 handshake_url = "http://post.audioscrobbler.com/?hs=true&p=1.2.1&c=mpc&v="VERSION"&u=";
139 handshake_url += config.lastfm_user;
140 handshake_url += "&t=";
141 handshake_url += timestamp;
142 handshake_url += "&a=";
143 handshake_url += md5sum((config.lastfm_md5_password.empty() ? md5sum(config.lastfm_password) : config.lastfm_md5_password) + timestamp);
145 pthread_mutex_lock(&curl_lock);
146 CURL *hs = curl_easy_init();
147 curl_easy_setopt(hs, CURLOPT_URL, handshake_url.c_str());
148 curl_easy_setopt(hs, CURLOPT_WRITEFUNCTION, write_data);
149 curl_easy_setopt(hs, CURLOPT_WRITEDATA, &result);
150 curl_easy_setopt(hs, CURLOPT_CONNECTTIMEOUT, curl_timeout);
151 code = curl_easy_perform(hs);
152 curl_easy_cleanup(hs);
153 pthread_mutex_unlock(&curl_lock);
155 if (code != CURLE_OK)
157 Log("Error while sending handshake: " + string(curl_easy_strerror(code)), llInfo);
158 return false;
161 size_t i = result.find("\n");
162 handshake.status = result.substr(0, i);
163 if (handshake.status != "OK")
164 return false;
165 result = result.substr(i+1);
166 i = result.find("\n");
167 handshake.session_id = result.substr(0, i);
168 result = result.substr(i+1);
169 i = result.find("\n");
170 handshake.nowplaying_url = result.substr(0, i);
171 result = result.substr(i+1);
172 ignore_newlines(result);
173 handshake.submission_url = result;
174 return true;
177 void *mpdconnection_handler(void *data)
179 MPD::Connection *Mpd = static_cast<MPD::Connection *>(data);
180 while (!scrobby_exit)
182 int x = 0;
183 while (!Mpd->Connected())
185 s.Submit();
186 Log("Connecting to MPD...", llVerbose);
187 Mpd->Disconnect();
188 if (Mpd->Connect())
190 Log("Connected to " + config.mpd_host + "!", llInfo);
191 x = 0;
193 else
195 x++;
196 Log("Cannot connect, retrieving in " + IntoStr(10*x) + " seconds...", llInfo);
197 sleep(10*x);
200 sleep(1);
202 pthread_exit(NULL);
205 void *handshake_handler(void *)
207 int x = 0;
208 while (!scrobby_exit)
210 if (handshake.status != "OK")
212 pthread_mutex_lock(&handshake_lock);
213 handshake.Clear();
214 if (send_handshake() && !handshake.status.empty())
216 Log("Handshake returned " + handshake.status, llInfo);
218 if (handshake.status == "OK")
220 Log("Connected to Audioscrobbler!", llInfo);
221 if (!SongsQueue.empty())
223 Log("Queue's not empty, submitting songs...", llInfo);
225 string result, postdata;
226 CURLcode code;
228 pthread_mutex_lock(&curl_lock);
229 CURL *submission = curl_easy_init();
231 postdata = "s=";
232 postdata += handshake.session_id;
234 for (std::vector<string>::const_iterator it = SongsQueue.begin(); it != SongsQueue.end(); it++)
235 postdata += *it;
237 Log("URL: " + handshake.submission_url, llVerbose);
238 Log("Post data: " + postdata, llVerbose);
240 curl_easy_setopt(submission, CURLOPT_URL, handshake.submission_url.c_str());
241 curl_easy_setopt(submission, CURLOPT_POST, 1);
242 curl_easy_setopt(submission, CURLOPT_POSTFIELDS, postdata.c_str());
243 curl_easy_setopt(submission, CURLOPT_WRITEFUNCTION, write_data);
244 curl_easy_setopt(submission, CURLOPT_WRITEDATA, &result);
245 curl_easy_setopt(submission, CURLOPT_CONNECTTIMEOUT, curl_timeout);
246 code = curl_easy_perform(submission);
247 curl_easy_cleanup(submission);
248 pthread_mutex_unlock(&curl_lock);
250 ignore_newlines(result);
252 if (result == "OK")
254 Log("Number of submitted songs: " + IntoStr(SongsQueue.size()), llInfo);
255 SongsQueue.clear();
256 ClearCache();
257 x = 0;
259 else
261 if (result.empty())
263 Log("Error while submitting songs: " + string(curl_easy_strerror(code)), llInfo);
265 else
267 Log("Audioscrobbler returned status " + result, llInfo);
271 notify_about_now_playing = !s.isStream();
273 else
275 x++;
276 Log("Connection refused, retrieving in " + IntoStr(10*x) + " seconds...", llInfo);
277 sleep(10*x);
279 pthread_mutex_unlock(&handshake_lock);
281 sleep(1);
283 pthread_exit(NULL);