Remove LastFM thread now that it can run from the worker thread

This commit is contained in:
ejurgensen 2015-06-27 23:24:07 +02:00
parent 0b87c04f57
commit d870b97142
4 changed files with 60 additions and 415 deletions

View File

@ -28,11 +28,9 @@
#include <unistd.h> #include <unistd.h>
#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
#include <pthread.h>
#include <gcrypt.h> #include <gcrypt.h>
#include <mxml.h> #include <mxml.h>
#include <event2/event.h>
#include <event2/buffer.h> #include <event2/buffer.h>
#include <event2/http.h> #include <event2/http.h>
#include <curl/curl.h> #include <curl/curl.h>
@ -43,28 +41,6 @@
#include "misc.h" #include "misc.h"
struct lastfm_command;
typedef int (*cmd_func)(struct lastfm_command *cmd);
struct lastfm_command
{
pthread_mutex_t lck;
pthread_cond_t cond;
cmd_func func;
int nonblock;
union {
void *noarg;
int id;
struct keyval *kv;
} arg;
int ret;
};
struct https_client_ctx struct https_client_ctx
{ {
const char *url; const char *url;
@ -72,37 +48,22 @@ struct https_client_ctx
struct evbuffer *data; struct evbuffer *data;
}; };
/* --- Globals --- */
// lastfm thread
static pthread_t tid_lastfm;
// Event base, pipes and events
struct event_base *evbase_lastfm;
static int g_exit_pipe[2];
static int g_cmd_pipe[2];
static struct event *g_exitev;
static struct event *g_cmdev;
// Tells us if the LastFM thread has been set up
static int g_initialized = 0;
// LastFM becomes disabled if we get a scrobble, try initialising session, // LastFM becomes disabled if we get a scrobble, try initialising session,
// but can't (probably no session key in db because user does not use LastFM) // but can't (probably no session key in db because user does not use LastFM)
static int g_disabled = 0; static int lastfm_disabled = 0;
/** /**
* The API key and secret (not so secret being open source) is specific to * The API key and secret (not so secret being open source) is specific to
* forked-daapd, and is used to identify forked-daapd and to sign requests * forked-daapd, and is used to identify forked-daapd and to sign requests
*/ */
const char *g_api_key = "579593f2ed3f49673c7364fd1c9c829b"; const char *lastfm_api_key = "579593f2ed3f49673c7364fd1c9c829b";
const char *g_secret = "ce45a1d275c10b3edf0ecfa27791cb2b"; const char *lastfm_secret = "ce45a1d275c10b3edf0ecfa27791cb2b";
const char *api_url = "http://ws.audioscrobbler.com/2.0/"; const char *api_url = "http://ws.audioscrobbler.com/2.0/";
const char *auth_url = "https://ws.audioscrobbler.com/2.0/"; const char *auth_url = "https://ws.audioscrobbler.com/2.0/";
// Session key // Session key
char *g_session_key = NULL; char *lastfm_session_key = NULL;
@ -285,7 +246,7 @@ param_sign(struct keyval *kv)
gcry_md_write(md_hdl, okv->value, strlen(okv->value)); gcry_md_write(md_hdl, okv->value, strlen(okv->value));
} }
gcry_md_write(md_hdl, g_secret, strlen(g_secret)); gcry_md_write(md_hdl, lastfm_secret, strlen(lastfm_secret));
hash_bytes = gcry_md_read(md_hdl, GCRY_MD_MD5); hash_bytes = gcry_md_read(md_hdl, GCRY_MD_MD5);
if (!hash_bytes) if (!hash_bytes)
@ -325,57 +286,7 @@ mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
} }
#endif #endif
/* ---------------------------- COMMAND EXECUTION -------------------------- */
static int
send_command(struct lastfm_command *cmd)
{
int ret;
if (!cmd->func)
{
DPRINTF(E_LOG, L_LASTFM, "BUG: cmd->func is NULL!\n");
return -1;
}
ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd));
if (ret != sizeof(cmd))
{
DPRINTF(E_LOG, L_LASTFM, "Could not send command: %s\n", strerror(errno));
return -1;
}
return 0;
}
static int
nonblock_command(struct lastfm_command *cmd)
{
int ret;
ret = send_command(cmd);
if (ret < 0)
return -1;
return 0;
}
/* Thread: main */
static void
thread_exit(void)
{
int dummy = 42;
DPRINTF(E_DBG, L_LASTFM, "Killing lastfm thread\n");
if (write(g_exit_pipe[1], &dummy, sizeof(dummy)) != sizeof(dummy))
DPRINTF(E_LOG, L_LASTFM, "Could not write to exit fd: %s\n", strerror(errno));
}
/* --------------------------------- MAIN --------------------------------- */ /* --------------------------------- MAIN --------------------------------- */
/* Thread: lastfm */
static size_t static size_t
request_cb(char *ptr, size_t size, size_t nmemb, void *userdata) request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
@ -460,10 +371,10 @@ response_proces(struct https_client_ctx *ctx)
DPRINTF(E_LOG, L_LASTFM, "Got session key from LastFM: %s\n", sk); DPRINTF(E_LOG, L_LASTFM, "Got session key from LastFM: %s\n", sk);
db_admin_add("lastfm_sk", sk); db_admin_add("lastfm_sk", sk);
if (g_session_key) if (lastfm_session_key)
free(g_session_key); free(lastfm_session_key);
g_session_key = sk; lastfm_session_key = sk;
} }
mxmlDelete(tree); mxmlDelete(tree);
@ -529,7 +440,7 @@ request_post(char *method, struct keyval *kv, int auth)
return -1; return -1;
if (!auth) if (!auth)
ret = keyval_add(kv, "sk", g_session_key); ret = keyval_add(kv, "sk", lastfm_session_key);
if (ret < 0) if (ret < 0)
return -1; return -1;
@ -559,17 +470,7 @@ request_post(char *method, struct keyval *kv, int auth)
} }
static int static int
login(struct lastfm_command *cmd) scrobble(int id)
{
request_post("auth.getMobileSession", cmd->arg.kv, 1);
keyval_clear(cmd->arg.kv);
return 0;
}
static int
scrobble(struct lastfm_command *cmd)
{ {
struct media_file_info *mfi; struct media_file_info *mfi;
struct keyval *kv; struct keyval *kv;
@ -578,10 +479,10 @@ scrobble(struct lastfm_command *cmd)
char timestamp[16]; char timestamp[16];
int ret; int ret;
mfi = db_file_fetch_byid(cmd->arg.id); mfi = db_file_fetch_byid(id);
if (!mfi) if (!mfi)
{ {
DPRINTF(E_LOG, L_LASTFM, "Scrobble failed, track id %d is unknown\n", cmd->arg.id); DPRINTF(E_LOG, L_LASTFM, "Scrobble failed, track id %d is unknown\n", id);
return -1; return -1;
} }
@ -605,8 +506,8 @@ scrobble(struct lastfm_command *cmd)
snprintf(trackNumber, sizeof(trackNumber), "%" PRIu32, mfi->track); snprintf(trackNumber, sizeof(trackNumber), "%" PRIu32, mfi->track);
snprintf(timestamp, sizeof(timestamp), "%" PRIi64, (int64_t)time(NULL)); snprintf(timestamp, sizeof(timestamp), "%" PRIi64, (int64_t)time(NULL));
ret = ( (keyval_add(kv, "api_key", g_api_key) == 0) && ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
(keyval_add(kv, "sk", g_session_key) == 0) && (keyval_add(kv, "sk", lastfm_session_key) == 0) &&
(keyval_add(kv, "artist", mfi->artist) == 0) && (keyval_add(kv, "artist", mfi->artist) == 0) &&
(keyval_add(kv, "track", mfi->title) == 0) && (keyval_add(kv, "track", mfi->title) == 0) &&
(keyval_add(kv, "album", mfi->album) == 0) && (keyval_add(kv, "album", mfi->album) == 0) &&
@ -621,16 +522,18 @@ scrobble(struct lastfm_command *cmd)
if (!ret) if (!ret)
{ {
keyval_clear(kv); keyval_clear(kv);
free(kv);
return -1; return -1;
} }
DPRINTF(E_INFO, L_LASTFM, "Scrobbling '%s' by '%s'\n", keyval_get(kv, "track"), keyval_get(kv, "artist")); DPRINTF(E_INFO, L_LASTFM, "Scrobbling '%s' by '%s'\n", keyval_get(kv, "track"), keyval_get(kv, "artist"));
request_post("track.scrobble", kv, 0); ret = request_post("track.scrobble", kv, 0);
keyval_clear(kv); keyval_clear(kv);
free(kv);
return 0; return ret;
noscrobble: noscrobble:
free_mfi(mfi, 0); free_mfi(mfi, 0);
@ -640,96 +543,12 @@ scrobble(struct lastfm_command *cmd)
static void *
lastfm(void *arg)
{
int ret;
DPRINTF(E_DBG, L_LASTFM, "Main loop initiating\n");
ret = db_perthread_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_LASTFM, "Error: DB init failed\n");
pthread_exit(NULL);
}
event_base_dispatch(evbase_lastfm);
if (g_initialized)
{
DPRINTF(E_LOG, L_LASTFM, "LastFM event loop terminated ahead of time!\n");
g_initialized = 0;
}
db_perthread_deinit();
DPRINTF(E_DBG, L_LASTFM, "Main loop terminating\n");
pthread_exit(NULL);
}
static void
exit_cb(int fd, short what, void *arg)
{
int dummy;
int ret;
ret = read(g_exit_pipe[0], &dummy, sizeof(dummy));
if (ret != sizeof(dummy))
DPRINTF(E_LOG, L_LASTFM, "Error reading from exit pipe\n");
event_base_loopbreak(evbase_lastfm);
g_initialized = 0;
event_add(g_exitev, NULL);
}
static void
command_cb(int fd, short what, void *arg)
{
struct lastfm_command *cmd;
int ret;
ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd));
if (ret != sizeof(cmd))
{
DPRINTF(E_LOG, L_LASTFM, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
goto readd;
}
if (cmd->nonblock)
{
cmd->func(cmd);
free(cmd);
goto readd;
}
pthread_mutex_lock(&cmd->lck);
ret = cmd->func(cmd);
cmd->ret = ret;
pthread_cond_signal(&cmd->cond);
pthread_mutex_unlock(&cmd->lck);
readd:
event_add(g_cmdev, NULL);
}
/* ---------------------------- Our lastfm API --------------------------- */ /* ---------------------------- Our lastfm API --------------------------- */
static int
lastfm_init(void);
/* Thread: filescanner */ /* Thread: filescanner */
void int
lastfm_login(char *path) lastfm_login(char *path)
{ {
struct lastfm_command *cmd;
struct keyval *kv; struct keyval *kv;
char *username; char *username;
char *password; char *password;
@ -738,252 +557,66 @@ lastfm_login(char *path)
DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n"); DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n");
// Delete any existing session key // Delete any existing session key
if (g_session_key) if (lastfm_session_key)
free(g_session_key); free(lastfm_session_key);
g_session_key = NULL; lastfm_session_key = NULL;
db_admin_delete("lastfm_sk"); db_admin_delete("lastfm_sk");
// Read the credentials file // Read the credentials file
ret = credentials_read(path, &username, &password); ret = credentials_read(path, &username, &password);
if (ret < 0) if (ret < 0)
return; return -1;
// Enable LastFM now that we got a login attempt // Enable LastFM now that we got a login attempt
g_disabled = 0; lastfm_disabled = 0;
kv = keyval_alloc(); kv = keyval_alloc();
if (!kv) if (!kv)
{ {
free(username); free(username);
free(password); free(password);
return; return -1;
} }
ret = ( (keyval_add(kv, "api_key", g_api_key) == 0) && ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
(keyval_add(kv, "username", username) == 0) && (keyval_add(kv, "username", username) == 0) &&
(keyval_add(kv, "password", password) == 0) ); (keyval_add(kv, "password", password) == 0) );
free(username); free(username);
free(password); free(password);
if (!ret) // Send the login request
{ ret = request_post("auth.getMobileSession", kv, 1);
keyval_clear(kv);
return;
}
// Spawn thread keyval_clear(kv);
ret = lastfm_init(); free(kv);
if (ret < 0)
{
g_disabled = 1;
return;
}
g_initialized = 1;
// Send login command to the thread return ret;
cmd = (struct lastfm_command *)malloc(sizeof(struct lastfm_command));
if (!cmd)
{
DPRINTF(E_LOG, L_LASTFM, "Could not allocate lastfm_command\n");
return;
}
memset(cmd, 0, sizeof(struct lastfm_command));
cmd->nonblock = 1;
cmd->func = login;
cmd->arg.kv = kv;
nonblock_command(cmd);
return;
} }
/* Thread: http and player */ /* Thread: worker */
int int
lastfm_scrobble(int id) lastfm_scrobble(int id)
{ {
struct lastfm_command *cmd;
int ret;
DPRINTF(E_DBG, L_LASTFM, "Got LastFM scrobble request\n"); DPRINTF(E_DBG, L_LASTFM, "Got LastFM scrobble request\n");
// LastFM is disabled because we already tried looking for a session key, but failed // LastFM is disabled because we already tried looking for a session key, but failed
if (g_disabled) if (lastfm_disabled)
return -1; return -1;
// No session key in mem or in db // No session key in mem or in db
if (!g_session_key) if (!lastfm_session_key)
g_session_key = db_admin_get("lastfm_sk"); lastfm_session_key = db_admin_get("lastfm_sk");
if (!g_session_key) if (!lastfm_session_key)
{ {
DPRINTF(E_INFO, L_LASTFM, "No valid LastFM session key\n"); DPRINTF(E_INFO, L_LASTFM, "No valid LastFM session key\n");
g_disabled = 1; lastfm_disabled = 1;
return -1; return -1;
} }
// Spawn LastFM thread return scrobble(id);
ret = lastfm_init();
if (ret < 0)
{
g_disabled = 1;
return -1;
}
g_initialized = 1;
// Send scrobble command to the thread
cmd = (struct lastfm_command *)malloc(sizeof(struct lastfm_command));
if (!cmd)
{
DPRINTF(E_LOG, L_LASTFM, "Could not allocate lastfm_command\n");
return -1;
}
memset(cmd, 0, sizeof(struct lastfm_command));
cmd->nonblock = 1;
cmd->func = scrobble;
cmd->arg.id = id;
nonblock_command(cmd);
return 0;
} }
static int
lastfm_init(void)
{
int ret;
if (g_initialized)
return 0;
curl_global_init(CURL_GLOBAL_DEFAULT);
# if defined(__linux__)
ret = pipe2(g_exit_pipe, O_CLOEXEC);
# else
ret = pipe(g_exit_pipe);
# endif
if (ret < 0)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create pipe: %s\n", strerror(errno));
goto exit_fail;
}
# if defined(__linux__)
ret = pipe2(g_cmd_pipe, O_CLOEXEC);
# else
ret = pipe(g_cmd_pipe);
# endif
if (ret < 0)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create command pipe: %s\n", strerror(errno));
goto cmd_fail;
}
evbase_lastfm = event_base_new();
if (!evbase_lastfm)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create an event base\n");
goto evbase_fail;
}
#ifdef HAVE_LIBEVENT2
g_exitev = event_new(evbase_lastfm, g_exit_pipe[0], EV_READ, exit_cb, NULL);
if (!g_exitev)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create exit event\n");
goto evnew_fail;
}
g_cmdev = event_new(evbase_lastfm, g_cmd_pipe[0], EV_READ, command_cb, NULL);
if (!g_cmdev)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create cmd event\n");
goto evnew_fail;
}
#else
g_exitev = (struct event *)malloc(sizeof(struct event));
if (!g_exitev)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create exit event\n");
goto evnew_fail;
}
event_set(g_exitev, g_exit_pipe[0], EV_READ, exit_cb, NULL);
event_base_set(evbase_lastfm, g_exitev);
g_cmdev = (struct event *)malloc(sizeof(struct event));
if (!g_cmdev)
{
DPRINTF(E_LOG, L_LASTFM, "Could not create cmd event\n");
goto evnew_fail;
}
event_set(g_cmdev, g_cmd_pipe[0], EV_READ, command_cb, NULL);
event_base_set(evbase_lastfm, g_cmdev);
#endif
event_add(g_exitev, NULL);
event_add(g_cmdev, NULL);
DPRINTF(E_INFO, L_LASTFM, "LastFM thread init\n");
ret = pthread_create(&tid_lastfm, NULL, lastfm, NULL);
if (ret < 0)
{
DPRINTF(E_LOG, L_LASTFM, "Could not spawn LastFM thread: %s\n", strerror(errno));
goto thread_fail;
}
return 0;
thread_fail:
evnew_fail:
event_base_free(evbase_lastfm);
evbase_lastfm = NULL;
evbase_fail:
close(g_cmd_pipe[0]);
close(g_cmd_pipe[1]);
cmd_fail:
close(g_exit_pipe[0]);
close(g_exit_pipe[1]);
exit_fail:
return -1;
}
void
lastfm_deinit(void)
{
int ret;
if (!g_initialized)
return;
curl_global_cleanup();
thread_exit();
ret = pthread_join(tid_lastfm, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_LASTFM, "Could not join lastfm thread: %s\n", strerror(errno));
return;
}
// Free event base (should free events too)
event_base_free(evbase_lastfm);
// Close pipes
close(g_cmd_pipe[0]);
close(g_cmd_pipe[1]);
close(g_exit_pipe[0]);
close(g_exit_pipe[1]);
}

View File

@ -2,13 +2,10 @@
#ifndef __LASTFM_H__ #ifndef __LASTFM_H__
#define __LASTFM_H__ #define __LASTFM_H__
void int
lastfm_login(char *path); lastfm_login(char *path);
int int
lastfm_scrobble(int id); lastfm_scrobble(int id);
void
lastfm_deinit(void);
#endif /* !__LASTFM_H__ */ #endif /* !__LASTFM_H__ */

View File

@ -68,7 +68,7 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
#include "worker.h" #include "worker.h"
#ifdef LASTFM #ifdef LASTFM
# include "lastfm.h" # include <curl/curl.h>
#endif #endif
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
# include "spotify.h" # include "spotify.h"
@ -590,6 +590,11 @@ main(int argc, char **argv)
#endif #endif
av_log_set_callback(logger_ffmpeg); av_log_set_callback(logger_ffmpeg);
#ifdef LASTFM
/* Initialize libcurl */
curl_global_init(CURL_GLOBAL_DEFAULT);
#endif
/* Initialize libgcrypt */ /* Initialize libgcrypt */
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
@ -837,10 +842,6 @@ main(int argc, char **argv)
player_deinit(); player_deinit();
player_fail: player_fail:
#ifdef LASTFM
DPRINTF(E_LOG, L_MAIN, "LastFM deinit\n");
lastfm_deinit();
#endif
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
DPRINTF(E_LOG, L_MAIN, "Spotify deinit\n"); DPRINTF(E_LOG, L_MAIN, "Spotify deinit\n");
spotify_deinit(); spotify_deinit();
@ -885,6 +886,9 @@ main(int argc, char **argv)
signal_block_fail: signal_block_fail:
gcrypt_init_fail: gcrypt_init_fail:
#ifdef LASTFM
curl_global_cleanup();
#endif
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13) #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13)
avformat_network_deinit(); avformat_network_deinit();
#endif #endif

View File

@ -642,6 +642,17 @@ playcount_inc_cb(void *arg)
db_file_inc_playcount(*id); db_file_inc_playcount(*id);
} }
#ifdef LASTFM
/* Callback from the worker thread (async operation as it may block) */
static void
scrobble_cb(void *arg)
{
int *id = arg;
lastfm_scrobble(*id);
}
#endif
/* Callback from the worker thread /* Callback from the worker thread
* This prepares metadata in the worker thread, since especially the artwork * This prepares metadata in the worker thread, since especially the artwork
* retrieval may take some time. raop_metadata_prepare() is thread safe. The * retrieval may take some time. raop_metadata_prepare() is thread safe. The
@ -1762,7 +1773,7 @@ source_check(void)
id = (int)cur_playing->id; id = (int)cur_playing->id;
worker_execute(playcount_inc_cb, &id, sizeof(int), 5); worker_execute(playcount_inc_cb, &id, sizeof(int), 5);
#ifdef LASTFM #ifdef LASTFM
lastfm_scrobble(id); worker_execute(scrobble_cb, &id, sizeof(int), 8);
#endif #endif
/* Stop playback if: /* Stop playback if: