parse argv and improve dealing with config files
[scrobby.git] / src / scrobby.cpp
blobb30576e49341bb43273b51179759e7464784e160
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
55 void signal_handler(int);
56 bool send_handshake();
58 void *mpdconnection_handler(void *data);
59 void *handshake_handler(void *);
62 int main(int argc, char **argv)
64 DefaultConfiguration(config);
66 if (argc > 1)
68 ParseArgv(config, argc, argv);
70 if (!config.file_config.empty())
72 if (!ReadConfiguration(config, config.file_config))
74 std::cerr << "cannot read configuration file: " << config.file_config << std::endl;
75 return 1;
78 else if (!ReadConfiguration(config, string(getenv("HOME") ? getenv("HOME") : "") + "/.scrobbyconf"))
80 if (!ReadConfiguration(config, "/etc/scrobby.conf"))
82 std::cerr << "default configuration files not found!\n";
83 return 1;
86 if (config.log_level == llUndefined)
88 config.log_level = llInfo;
90 if (config.lastfm_user.empty() || (config.lastfm_md5_password.empty() && config.lastfm_password.empty()))
92 std::cerr << "last.fm user/password is not set.\n";
93 return 1;
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);
113 if (!config.mpd_password.empty())
114 Mpd->SetPassword(config.mpd_password);
116 Mpd->SetTimeout(config.mpd_timeout);
117 Mpd->SetStatusUpdater(ScrobbyStatusChanged, NULL);
118 Mpd->SetErrorHandler(ScrobbyErrorCallback, NULL);
120 signal(SIGHUP, signal_handler);
121 signal(SIGINT, signal_handler);
122 signal(SIGTERM, signal_handler);
123 signal(SIGPIPE, SIG_IGN);
125 pthread_create(&mpdconnection_th, NULL, mpdconnection_handler, Mpd);
126 pthread_create(&handshake_th, NULL, handshake_handler, NULL);
127 pthread_detach(mpdconnection_th);
128 pthread_detach(handshake_th);
130 while (!scrobby_exit && !usleep(500000))
132 if (Mpd->Connected())
133 Mpd->UpdateStatus();
136 s.Submit();
137 Log(llInfo, "Shutting down...");
138 if (remove(config.file_pid.c_str()) != 0)
139 Log(llInfo, "Couldn't remove pid file!");
140 delete Mpd;
142 return 0;
145 namespace
147 void signal_handler(int)
149 scrobby_exit = 1;
152 bool send_handshake()
154 CURLcode code;
155 string handshake_url;
156 string result;
157 string timestamp = IntoStr(time(NULL));
159 handshake_url = "http://post.audioscrobbler.com/?hs=true&p=1.2.1&c=mpc&v="VERSION"&u=";
160 handshake_url += config.lastfm_user;
161 handshake_url += "&t=";
162 handshake_url += timestamp;
163 handshake_url += "&a=";
164 handshake_url += md5sum((config.lastfm_md5_password.empty() ? md5sum(config.lastfm_password) : config.lastfm_md5_password) + timestamp);
166 pthread_mutex_lock(&curl_lock);
167 CURL *hs = curl_easy_init();
168 curl_easy_setopt(hs, CURLOPT_URL, handshake_url.c_str());
169 curl_easy_setopt(hs, CURLOPT_WRITEFUNCTION, write_data);
170 curl_easy_setopt(hs, CURLOPT_WRITEDATA, &result);
171 curl_easy_setopt(hs, CURLOPT_CONNECTTIMEOUT, curl_timeout);
172 code = curl_easy_perform(hs);
173 curl_easy_cleanup(hs);
174 pthread_mutex_unlock(&curl_lock);
176 if (code != CURLE_OK)
178 Log(llInfo, "Error while sending handshake: %s", curl_easy_strerror(code));
179 return false;
182 size_t i = result.find("\n");
183 handshake.status = result.substr(0, i);
184 if (handshake.status != "OK")
185 return false;
186 result = result.substr(i+1);
187 i = result.find("\n");
188 handshake.session_id = result.substr(0, i);
189 result = result.substr(i+1);
190 i = result.find("\n");
191 handshake.nowplaying_url = result.substr(0, i);
192 result = result.substr(i+1);
193 ignore_newlines(result);
194 handshake.submission_url = result;
195 return true;
198 void *mpdconnection_handler(void *data)
200 MPD::Connection *Mpd = static_cast<MPD::Connection *>(data);
201 while (!scrobby_exit)
203 int x = 0;
204 while (!Mpd->Connected())
206 s.Submit();
207 Log(llVerbose, "Connecting to MPD...");
208 Mpd->Disconnect();
209 if (Mpd->Connect())
211 Log(llInfo, "Connected to %s !", config.mpd_host.c_str());
212 x = 0;
214 else
216 x++;
217 Log(llInfo, "Cannot connect, retrieving in %d seconds...", 10*x);
218 sleep(10*x);
221 sleep(1);
223 pthread_exit(NULL);
226 void *handshake_handler(void *)
228 int x = 0;
229 while (!scrobby_exit)
231 if (handshake.status != "OK")
233 pthread_mutex_lock(&handshake_lock);
234 handshake.Clear();
235 if (send_handshake() && !handshake.status.empty())
237 Log(llInfo, "Handshake returned %s", handshake.status.c_str());
239 if (handshake.status == "OK")
241 Log(llInfo, "Connected to Audioscrobbler!");
242 if (!SongsQueue.empty())
244 Log(llInfo, "Queue's not empty, submitting songs...");
246 string result, postdata;
247 CURLcode code;
249 pthread_mutex_lock(&curl_lock);
250 CURL *submission = curl_easy_init();
252 postdata = "s=";
253 postdata += handshake.session_id;
255 for (std::vector<string>::const_iterator it = SongsQueue.begin(); it != SongsQueue.end(); it++)
256 postdata += *it;
258 Log(llVerbose, "URL: %s", handshake.submission_url.c_str());
259 Log(llVerbose, "Post data: %s", postdata.c_str());
261 curl_easy_setopt(submission, CURLOPT_URL, handshake.submission_url.c_str());
262 curl_easy_setopt(submission, CURLOPT_POST, 1);
263 curl_easy_setopt(submission, CURLOPT_POSTFIELDS, postdata.c_str());
264 curl_easy_setopt(submission, CURLOPT_WRITEFUNCTION, write_data);
265 curl_easy_setopt(submission, CURLOPT_WRITEDATA, &result);
266 curl_easy_setopt(submission, CURLOPT_CONNECTTIMEOUT, curl_timeout);
267 code = curl_easy_perform(submission);
268 curl_easy_cleanup(submission);
269 pthread_mutex_unlock(&curl_lock);
271 ignore_newlines(result);
273 if (result == "OK")
275 Log(llInfo, "Number of submitted songs: %d", SongsQueue.size());
276 SongsQueue.clear();
277 ClearCache();
278 x = 0;
280 else
282 if (result.empty())
284 Log(llInfo, "Error while submitting songs: %s", curl_easy_strerror(code));
286 else
288 Log(llInfo, "Audioscrobbler returned status %s", result.c_str());
292 notify_about_now_playing = !s.isStream();
294 else
296 x++;
297 Log(llInfo, "Connection refused, retrieving in %d seconds...", 10*x);
298 sleep(10*x);
300 pthread_mutex_unlock(&handshake_lock);
302 sleep(1);
304 pthread_exit(NULL);