From f263abb8b777b61bcce7eee88716a137d0f880c3 Mon Sep 17 00:00:00 2001 From: waker Date: Fri, 28 Aug 2009 20:30:20 +0200 Subject: lastfm multithreading WIP --- deadbeef.h | 5 + main.c | 4 +- plugins.c | 7 + plugins/lastfm/Makefile.am | 2 +- plugins/lastfm/lastfm.c | 339 +++++++++++++++++++++++++++------------------ threading.h | 7 + threading_pthread.c | 52 ++++++- 7 files changed, 277 insertions(+), 139 deletions(-) diff --git a/deadbeef.h b/deadbeef.h index 2dfefda9..cd3583be 100644 --- a/deadbeef.h +++ b/deadbeef.h @@ -128,6 +128,11 @@ typedef struct { void (*mutex_free) (uintptr_t mtx); int (*mutex_lock) (uintptr_t mtx); int (*mutex_unlock) (uintptr_t mtx); + uintptr_t (*cond_create) (void); + void (*cond_free) (uintptr_t cond); + int (*cond_wait) (uintptr_t cond, uintptr_t mutex); + int (*cond_signal) (uintptr_t cond); + int (*cond_broadcast) (uintptr_t cond); // playlist access const char *(*pl_find_meta) (DB_playItem_t *song, const char *meta); // volume control diff --git a/main.c b/main.c index 28f3ff73..885eaa9c 100644 --- a/main.c +++ b/main.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -494,6 +495,7 @@ on_trayicon_popup_menu (GtkWidget *widget, int main (int argc, char *argv[]) { + srand (time (NULL)); prctl (PR_SET_NAME, "deadbeef-main", 0, 0, 0, 0); char *homedir = getenv ("HOME"); if (!homedir) { @@ -653,7 +655,7 @@ main (int argc, char *argv[]) { p_free (); streamer_free (); codec_free_locking (); - + plug_unload_all (); session_save (sessfile); pl_save (defpl); pl_free (); diff --git a/plugins.c b/plugins.c index 7157cc8d..d5b88903 100644 --- a/plugins.c +++ b/plugins.c @@ -36,11 +36,18 @@ DB_functions_t deadbeef_api = { .playback_get_pos = plug_playback_get_pos, .playback_set_pos = plug_playback_set_pos, .quit = plug_quit, + // threading .thread_start = thread_start, .mutex_create = mutex_create, .mutex_free = mutex_free, .mutex_lock = mutex_lock, .mutex_unlock = mutex_unlock, + .cond_create = cond_create, + .cond_free = cond_free, + .cond_wait = cond_wait, + .cond_signal = cond_signal, + .cond_broadcast = cond_broadcast, + // metadata .pl_find_meta = (const char *(*) (DB_playItem_t *song, const char *meta))pl_find_meta, // volume control .volume_set_db = volume_set_db, diff --git a/plugins/lastfm/Makefile.am b/plugins/lastfm/Makefile.am index 7e26b863..24fb0f4b 100644 --- a/plugins/lastfm/Makefile.am +++ b/plugins/lastfm/Makefile.am @@ -4,4 +4,4 @@ lastfm_la_SOURCES = lastfm.c lastfm_la_LDFLAGS = -module lastfm_la_LIBADD = $(LDADD) $(LASTFM_DEPS_LIBS) -AM_CFLAGS = $(LASTFM_DEPS_CFLAGS) +AM_CFLAGS = $(LASTFM_DEPS_CFLAGS) -std=c99 diff --git a/plugins/lastfm/lastfm.c b/plugins/lastfm/lastfm.c index 2d805ff6..15c9508e 100644 --- a/plugins/lastfm/lastfm.c +++ b/plugins/lastfm/lastfm.c @@ -36,12 +36,16 @@ static DB_functions_t *deadbeef; #define LFM_API_KEY "6b33c8ae4d598a9aff8fe63e334e6e86" #define LFM_API_SECRET "a9f5e17e358377d96e96477d870b2b18" -char lfm_user[100]; -char lfm_pass[100]; +static char lfm_user[100]; +static char lfm_pass[100]; -char lfm_sess[33]; -char lfm_nowplaying_url[256]; -char lfm_submission_url[256]; +static char lfm_sess[33]; +static char lfm_nowplaying_url[256]; +static char lfm_submission_url[256]; + +static uintptr_t lfm_mutex; +static uintptr_t lfm_cond; +static int lfm_stopthread; DB_plugin_t * lastfm_load (DB_functions_t *api) { @@ -54,6 +58,10 @@ static char lfm_reply[MAX_REPLY]; static char lfm_reply_sz; static char lfm_err[CURL_ERROR_SIZE]; +#define LFM_SUBMISSION_QUEUE_SIZE 5 +static char lfm_nowplaying[2048]; // packet for nowplaying, or "" +static char lfm_subm_queue[LFM_SUBMISSION_QUEUE_SIZE][2048]; + static size_t lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream) { @@ -64,10 +72,10 @@ lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream) } memcpy (lfm_reply + lfm_reply_sz, ptr, len); lfm_reply_sz += len; - char s[size*nmemb+1]; - memcpy (s, ptr, size*nmemb); - s[size*nmemb] = 0; - fprintf (stderr, "got from net: %s\n", s); +// char s[size*nmemb+1]; +// memcpy (s, ptr, size*nmemb); +// s[size*nmemb] = 0; +// fprintf (stderr, "got from net: %s\n", s); return len; } @@ -107,95 +115,6 @@ curl_req_cleanup (void) { lfm_reply_sz = 0; } - -// {{{ lastfm v2 get session -#if 0 -int -auth_v2 (void) { - if (lfm_sess[0]) { - return 0; - } - char msg[4096]; - char sigstr[4096]; - uint8_t sig[16]; - snprintf (sigstr, sizeof (sigstr), "api_key%smethodauth.getToken%s", LASTFM_API_KEY, LASTFM_API_SECRET); - deadbeef->md5 (sig, sigstr, strlen (sigstr)); - deadbeef->md5_to_str (sigstr, sig); - snprintf (msg, sizeof (msg), "%s/?api_key=%s&method=auth.getToken&api_sig=%s", SCROBBLER_URL, LASTFM_API_KEY, sigstr); - // get token - char lfm_token[33] = ""; - int status = curl_req_send (msg, NULL); - if (!status) { - // parse output - if (strstr (lfm_reply, "")) { - char *token = strstr (lfm_reply, ""); - if (token) { - token += 7; - char *end = strstr (token, ""); - if (end) { - *end = 0; - snprintf (msg, sizeof (msg), "http://www.last.fm/api/auth/?api_key=%s&token=%s", LASTFM_API_KEY, token); - printf ("Dear user. Please visit this URL and authenticate deadbeef. Thanks.\n"); - - printf ("%s\n", msg); - strncpy (lfm_token, token, 32); - lfm_token[32] = 0; - } - } - } - } - curl_req_cleanup (); - if (!lfm_token[0]) { - // total fail, give up - return -1; - } - // get session - snprintf (sigstr, sizeof (sigstr), "api_key%smethodauth.getSessiontoken%s%s", LASTFM_API_KEY, lfm_token, LASTFM_API_SECRET); - deadbeef->md5 (sig, sigstr, strlen (sigstr)); - deadbeef->md5_to_str (sigstr, sig); - snprintf (msg, sizeof (msg), "method=auth.getSession&token=%s&api_key=%s&api_sig=%s", lfm_token, LASTFM_API_KEY, sigstr); - for (;;) { - status = curl_req_send (SCROBBLER_URL, msg); - if (!status) { - char *sess = strstr (lfm_reply, ""); - if (sess) { - sess += 5; - char *end = strstr (sess, ""); - if (end) { - *end = 0; - char config[1024]; - snprintf (config, sizeof (config), "%s/.config/deadbeef/lastfmv2", getenv ("HOME")); - fprintf (stderr, "got session key %s\n", sess); - FILE *fp = fopen (config, "w+b"); - if (!fp) { - fprintf (stderr, "lastfm: failed to write config file %s\n", config); - curl_req_cleanup (); - return -1; - } - if (fwrite (sess, 1, 32, fp) != 32) { - fclose (fp); - fprintf (stderr, "lastfm: failed to write config file %s\n", config); - curl_req_cleanup (); - return -1; - } - fclose (fp); - strcpy (lfm_sess, sess); - } - } -// printf ("reply: %s\n", lfm_reply); - } - curl_req_cleanup (); - if (lfm_sess[0]) { - break; - } - sleep (5); - } - return 0; -} -#endif -// }}} - - int auth (void) { if (lfm_sess[0]) { @@ -206,7 +125,6 @@ auth (void) { uint8_t sig[16]; char passmd5[33]; char token[100]; - printf (">> %s:%s <<\n", lfm_user, lfm_pass); deadbeef->md5 (sig, lfm_pass, strlen (lfm_pass)); deadbeef->md5_to_str (passmd5, sig); snprintf (token, sizeof (token), "%s%d", passmd5, timestamp); @@ -221,7 +139,6 @@ auth (void) { // handshake int status = curl_req_send (req, NULL); if (!status) { - printf ("%s\n", lfm_reply); // check status and extract session id, nowplaying url, submission url if (strncmp (lfm_reply, "OK", 2)) { uint8_t *p = lfm_reply; @@ -334,7 +251,7 @@ lfm_fetch_song_info (DB_playItem_t *song, const char **a, const char **t, const static int lastfm_songstarted (DB_event_song_t *ev, uintptr_t data) { - printf ("song started, info:\n"); + fprintf (stderr, "song started, info:\n"); const char *a; // artist const char *t; // title const char *b; // album @@ -342,36 +259,24 @@ lastfm_songstarted (DB_event_song_t *ev, uintptr_t data) { const char *n; // tracknum const char *m; // muzicbrainz id if (lfm_fetch_song_info (ev->song, &a, &t, &b, &l, &n, &m) == 0) { - printf ("playtime: %f\nartist: %s\ntitle: %s\nalbum: %s\nduration: %f\ntracknum: %s\n---\n", ev->song->playtime, a, t, b, l, n); + fprintf (stderr, "playtime: %f\nartist: %s\ntitle: %s\nalbum: %s\nduration: %f\ntracknum: %s\n---\n", ev->song->playtime, a, t, b, l, n); } else { - printf ("file %s doesn't have enough tags to submit to last.fm\n", ev->song->fname); + fprintf (stderr, "file %s doesn't have enough tags to submit to last.fm\n", ev->song->fname); return 0; } - // submit to nowplaying - if (auth () < 0) { - return; - } - - char req[4096]; - snprintf (req, sizeof (req), "s=%s&a=%s&t=%s&b=%s&l=%d&n=%s&m=%s", lfm_sess, a, t, b, (int)l, n, m); - fprintf (stderr, "sending nowplaying to lfm:\n%s\n", req); - int status = curl_req_send (lfm_nowplaying_url, req); - if (!status) { - if (strncmp (lfm_reply, "OK", 2)) { - fprintf (stderr, "nowplaying failed, response:\n%s\n", lfm_reply); - lfm_sess[0] = 0; - } - } - curl_req_cleanup (); + deadbeef->mutex_lock (lfm_mutex); + snprintf (lfm_nowplaying, sizeof (lfm_nowplaying), "a=%s&t=%s&b=%s&l=%d&n=%s&m=%s", a, t, b, (int)l, n, m); + deadbeef->mutex_unlock (lfm_mutex); + deadbeef->cond_signal (lfm_cond); return 0; } static int lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) { - printf ("song finished, info:\n"); + fprintf (stderr, "song finished, info:\n"); const char *a; // artist const char *t; // title const char *b; // album @@ -379,16 +284,14 @@ lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) { const char *n; // tracknum const char *m; // muzicbrainz id if (lfm_fetch_song_info (ev->song, &a, &t, &b, &l, &n, &m) == 0) { - printf ("playtime: %f\nartist: %s\ntitle: %s\nalbum: %s\nduration: %f\ntracknum: %s\n---\n", ev->song->playtime, a, t, b, l, n); + fprintf (stderr, "playtime: %f\nartist: %s\ntitle: %s\nalbum: %s\nduration: %f\ntracknum: %s\n---\n", ev->song->playtime, a, t, b, l, n); } else { - printf ("file %s doesn't have enough tags to submit to last.fm\n", ev->song->fname); + fprintf (stderr, "file %s doesn't have enough tags to submit to last.fm\n", ev->song->fname); return 0; } // check submission rules - -#if 0 // must be played for >=240sec of half the total time if (ev->song->playtime < 240 && ev->song->playtime < ev->song->duration/2) { return 0; @@ -399,27 +302,196 @@ lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) { return 0; } + deadbeef->mutex_lock (lfm_mutex); + // find free place in queue + for (int i = 0; i < LFM_SUBMISSION_QUEUE_SIZE; i++) { + if (!lfm_subm_queue[i][0]) { + snprintf (lfm_subm_queue[i], sizeof (lfm_subm_queue[i]), "a[0]=%s&t[0]=%s&i[0]=%d&o[0]=P&r[0]=&b[0]=%s&l[0]=%d&n[0]=%s&m[0]=%s", a, t, ev->song->started_timestamp, b, (int)l, n, m); + break; + } + } + deadbeef->mutex_unlock (lfm_mutex); + deadbeef->cond_signal (lfm_cond); + + return 0; +} + +static void +lfm_send_nowplaying (void) { if (auth () < 0) { + lfm_nowplaying[0] = 0; return; } -#endif - - char req[4096]; - snprintf (req, sizeof (req), "s=%s&a[0]=%s&t[0]=%s&i[0]=%d&o[0]=P&r[0]=&b[0]=%s&l[0]=%d&n[0]=%s&m[0]=%s", lfm_sess, a, t, ev->song->started_timestamp, b, (int)l, n, m); - fprintf (stderr, "sending submission to lfm:\n%s\n", req); - int status = curl_req_send (lfm_submission_url, req); + fprintf (stderr, "auth successful! setting nowplaying\n"); + char s[100]; + snprintf (s, sizeof (s), "&s=%s", lfm_sess); + strcat (lfm_nowplaying, s); + int status = curl_req_send (lfm_nowplaying_url, lfm_nowplaying); if (!status) { if (strncmp (lfm_reply, "OK", 2)) { - fprintf (stderr, "submission failed, response:\n%s\n", lfm_reply); + fprintf (stderr, "nowplaying failed, response:\n%s\n", lfm_reply); lfm_sess[0] = 0; } } curl_req_cleanup (); + lfm_nowplaying[0] = 0; +} + +static void +lfm_send_submissions (void) { + if (auth () < 0) { + return; + } + for (;;) { + int i; + deadbeef->mutex_lock (lfm_mutex); + for (i = 0; i < LFM_SUBMISSION_QUEUE_SIZE; i++) { + if (lfm_subm_queue[i][0]) { + break; + } + } + deadbeef->mutex_unlock (lfm_mutex); + if (i == LFM_SUBMISSION_QUEUE_SIZE) { + break; + } + int status = curl_req_send (lfm_submission_url, lfm_subm_queue[i]); + if (!status) { + if (strncmp (lfm_reply, "OK", 2)) { + fprintf (stderr, "submission failed, response:\n%s\n", lfm_reply); + lfm_sess[0] = 0; + } + else { + deadbeef->mutex_lock (lfm_mutex); + lfm_subm_queue[i][0] = 0; + deadbeef->mutex_unlock (lfm_mutex); + } + } + curl_req_cleanup (); + if (!lfm_sess[0]) { + break; + } + } +} + +void +lfm_thread (uintptr_t ctx) { + //fprintf (stderr, "lfm_thread started\n"); + for (;;) { + deadbeef->cond_wait (lfm_cond, lfm_mutex); + fprintf (stderr, "cond signalled!\n"); + if (lfm_stopthread) { + deadbeef->mutex_unlock (lfm_mutex); + fprintf (stderr, "lfm_thread end\n"); + deadbeef->cond_free (lfm_cond); + deadbeef->mutex_free (lfm_mutex); + return; + } + deadbeef->mutex_unlock (lfm_mutex); + + // try to send nowplaying + if (lfm_nowplaying[0]) { + lfm_send_nowplaying (); + } + + lfm_send_submissions (); + } +} + +// {{{ lastfm v2 get session +#if 0 +int +auth_v2 (void) { + if (lfm_sess[0]) { + return 0; + } + char msg[4096]; + char sigstr[4096]; + uint8_t sig[16]; + snprintf (sigstr, sizeof (sigstr), "api_key%smethodauth.getToken%s", LASTFM_API_KEY, LASTFM_API_SECRET); + deadbeef->md5 (sig, sigstr, strlen (sigstr)); + deadbeef->md5_to_str (sigstr, sig); + snprintf (msg, sizeof (msg), "%s/?api_key=%s&method=auth.getToken&api_sig=%s", SCROBBLER_URL, LASTFM_API_KEY, sigstr); + // get token + char lfm_token[33] = ""; + int status = curl_req_send (msg, NULL); + if (!status) { + // parse output + if (strstr (lfm_reply, "")) { + char *token = strstr (lfm_reply, ""); + if (token) { + token += 7; + char *end = strstr (token, ""); + if (end) { + *end = 0; + snprintf (msg, sizeof (msg), "http://www.last.fm/api/auth/?api_key=%s&token=%s", LASTFM_API_KEY, token); + fprintf (stderr, "Dear user. Please visit this URL and authenticate deadbeef. Thanks.\n"); + + fprintf (stderr, "%s\n", msg); + strncpy (lfm_token, token, 32); + lfm_token[32] = 0; + } + } + } + } + curl_req_cleanup (); + if (!lfm_token[0]) { + // total fail, give up + return -1; + } + // get session + snprintf (sigstr, sizeof (sigstr), "api_key%smethodauth.getSessiontoken%s%s", LASTFM_API_KEY, lfm_token, LASTFM_API_SECRET); + deadbeef->md5 (sig, sigstr, strlen (sigstr)); + deadbeef->md5_to_str (sigstr, sig); + snprintf (msg, sizeof (msg), "method=auth.getSession&token=%s&api_key=%s&api_sig=%s", lfm_token, LASTFM_API_KEY, sigstr); + for (;;) { + status = curl_req_send (SCROBBLER_URL, msg); + if (!status) { + char *sess = strstr (lfm_reply, ""); + if (sess) { + sess += 5; + char *end = strstr (sess, ""); + if (end) { + *end = 0; + char config[1024]; + snprintf (config, sizeof (config), "%s/.config/deadbeef/lastfmv2", getenv ("HOME")); + fprintf (stderr, "got session key %s\n", sess); + FILE *fp = fopen (config, "w+b"); + if (!fp) { + fprintf (stderr, "lastfm: failed to write config file %s\n", config); + curl_req_cleanup (); + return -1; + } + if (fwrite (sess, 1, 32, fp) != 32) { + fclose (fp); + fprintf (stderr, "lastfm: failed to write config file %s\n", config); + curl_req_cleanup (); + return -1; + } + fclose (fp); + strcpy (lfm_sess, sess); + } + } +// fprintf (stderr, "reply: %s\n", lfm_reply); + } + curl_req_cleanup (); + if (lfm_sess[0]) { + break; + } + sleep (5); + } return 0; } +#endif +// }}} + + static int lastfm_start (void) { + lfm_stopthread = 0; + lfm_mutex = deadbeef->mutex_create (); + lfm_cond = deadbeef->cond_create (); + deadbeef->thread_start (lfm_thread, 0); // subscribe to frameupdate event deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGSTARTED, DB_CALLBACK (lastfm_songstarted), 0); deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGFINISHED, DB_CALLBACK (lastfm_songfinished), 0); @@ -477,15 +549,18 @@ lastfm_start (void) { } p++; *p = 0; - auth (); return 0; } static int lastfm_stop (void) { + //fprintf (stderr, "lastfm_stop\n"); deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGSTARTED, DB_CALLBACK (lastfm_songstarted), 0); deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGFINISHED, DB_CALLBACK (lastfm_songfinished), 0); + lfm_stopthread = 1; + deadbeef->cond_signal (lfm_cond); + fprintf (stderr, "signalled to stop thread\n"); return 0; } diff --git a/threading.h b/threading.h index 824432c1..cb71b933 100644 --- a/threading.h +++ b/threading.h @@ -21,10 +21,17 @@ #include void thread_start (void (*fn)(uintptr_t ctx), uintptr_t ctx); + uintptr_t mutex_create (void); void mutex_free (uintptr_t mtx); int mutex_lock (uintptr_t mtx); int mutex_unlock (uintptr_t mtx); +uintptr_t cond_create (void); +void cond_free (uintptr_t cond); +int cond_wait (uintptr_t cond, uintptr_t mutex); +int cond_signal (uintptr_t cond); +int cond_broadcast (uintptr_t cond); + #endif diff --git a/threading_pthread.c b/threading_pthread.c index ae8bf258..7bb3d50b 100644 --- a/threading_pthread.c +++ b/threading_pthread.c @@ -20,7 +20,8 @@ #include #include "threading.h" -void thread_start (void (*fn)(uintptr_t ctx), uintptr_t ctx) { +void +thread_start (void (*fn)(uintptr_t ctx), uintptr_t ctx) { pthread_t tid; pthread_attr_t attr; int s = pthread_attr_init (&attr); @@ -39,30 +40,71 @@ void thread_start (void (*fn)(uintptr_t ctx), uintptr_t ctx) { return; } } -uintptr_t mutex_create (void) { +uintptr_t +mutex_create (void) { pthread_mutex_t *mtx = malloc (sizeof (pthread_mutex_t)); if (pthread_mutex_init (mtx, NULL)) { printf ("pthread_mutex_init failed!\n"); } return (uintptr_t)mtx; } -void mutex_free (uintptr_t _mtx) { + +void +mutex_free (uintptr_t _mtx) { pthread_mutex_t *mtx = (pthread_mutex_t *)_mtx; mutex_lock (_mtx); mutex_unlock (_mtx); pthread_mutex_destroy (mtx); free (mtx); } -int mutex_lock (uintptr_t _mtx) { + +int +mutex_lock (uintptr_t _mtx) { pthread_mutex_t *mtx = (pthread_mutex_t *)_mtx; if (pthread_mutex_lock (mtx)) { printf ("pthread_mutex_lock failed\n"); } } -int mutex_unlock (uintptr_t _mtx) { + +int +mutex_unlock (uintptr_t _mtx) { pthread_mutex_t *mtx = (pthread_mutex_t *)_mtx; if (pthread_mutex_unlock (mtx)) { printf ("pthread_mutex_unlock failed\n"); }; } +uintptr_t +cond_create (void) { + pthread_cond_t *cond = malloc (sizeof (pthread_cond_t)); + pthread_cond_init (cond, NULL); + return (uintptr_t)cond; +} + +void +cond_free (uintptr_t c) { + if (c) { + pthread_cond_t *cond = (pthread_cond_t *)c; + pthread_cond_destroy (cond); + free (cond); + } +} + +int +cond_wait (uintptr_t c, uintptr_t m) { + pthread_cond_t *cond = (pthread_cond_t *)c; + pthread_mutex_t *mutex = (pthread_mutex_t *)m; + return pthread_cond_wait (cond, mutex); +} + +int +cond_signal (uintptr_t c) { + pthread_cond_t *cond = (pthread_cond_t *)c; + return pthread_cond_signal (cond); +} + +int +cond_broadcast (uintptr_t c) { + pthread_cond_t *cond = (pthread_cond_t *)c; + return pthread_cond_broadcast (cond); +} -- cgit v1.2.3