config: remove advanced from add_password()
[vlc.git] / modules / misc / audioscrobbler.c
1 /*****************************************************************************
2  * audioscrobbler.c : audioscrobbler submission plugin
3  *****************************************************************************
4  * Copyright © 2006-2011 the VideoLAN team
5  * $Id$
6  *
7  * Author: Rafaël Carré <funman at videolanorg>
8  *         Ilkka Ollakka <ileoo at videolan org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /* Last.fm Submissions protocol version: 1.2
26  * http://www.last.fm/api/submissions
27  *
28  * TODO:    Update to new API? http://www.last.fm/api/scrobbling
29  */
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33
34 #ifdef HAVE_CONFIG_H
35 # include "config.h"
36 #endif
37
38 #include <assert.h>
39 #include <time.h>
40 #ifdef HAVE_POLL
41 # include <poll.h>
42 #endif
43
44 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
47 #include <vlc_interface.h>
48 #include <vlc_input.h>
49 #include <vlc_dialog.h>
50 #include <vlc_meta.h>
51 #include <vlc_md5.h>
52 #include <vlc_stream.h>
53 #include <vlc_url.h>
54 #include <vlc_network.h>
55 #include <vlc_playlist.h>
56
57 /*****************************************************************************
58  * Local prototypes
59  *****************************************************************************/
60
61 #define QUEUE_MAX 50
62
63 /* Keeps track of metadata to be submitted */
64 typedef struct audioscrobbler_song_t
65 {
66     char        *psz_a;             /**< track artist     */
67     char        *psz_t;             /**< track title      */
68     char        *psz_b;             /**< track album      */
69     char        *psz_n;             /**< track number     */
70     int         i_l;                /**< track length     */
71     char        *psz_m;             /**< musicbrainz id   */
72     time_t      date;               /**< date since epoch */
73     mtime_t     i_start;            /**< playing start    */
74 } audioscrobbler_song_t;
75
76 struct intf_sys_t
77 {
78     audioscrobbler_song_t   p_queue[QUEUE_MAX]; /**< songs not submitted yet*/
79     int                     i_songs;            /**< number of songs        */
80
81     input_thread_t         *p_input;            /**< current input thread   */
82     vlc_mutex_t             lock;               /**< p_sys mutex            */
83     vlc_cond_t              wait;               /**< song to submit event   */
84     vlc_thread_t            thread;             /**< thread to submit song  */
85
86     /* submission of played songs */
87     vlc_url_t               p_submit_url;       /**< where to submit data   */
88
89     /* submission of playing song */
90     vlc_url_t               p_nowp_url;         /**< where to submit data   */
91
92     char                    psz_auth_token[33]; /**< Authentication token */
93
94     /* data about song currently playing */
95     audioscrobbler_song_t   p_current_song;     /**< song being played      */
96
97     mtime_t                 time_pause;         /**< time when vlc paused   */
98     mtime_t                 time_total_pauses;  /**< total time in pause    */
99
100     bool                    b_submit_nowp;      /**< do we have to submit ? */
101
102     bool                    b_meta_read;        /**< if we read the song's
103                                                  * metadata already         */
104 };
105
106 static int  Open            (vlc_object_t *);
107 static void Close           (vlc_object_t *);
108 static void *Run            (void *);
109
110 /*****************************************************************************
111  * Module descriptor
112  ****************************************************************************/
113
114 #define USERNAME_TEXT       N_("Username")
115 #define USERNAME_LONGTEXT   N_("The username of your last.fm account")
116 #define PASSWORD_TEXT       N_("Password")
117 #define PASSWORD_LONGTEXT   N_("The password of your last.fm account")
118 #define URL_TEXT            N_("Scrobbler URL")
119 #define URL_LONGTEXT        N_("The URL set for an alternative scrobbler engine")
120
121 /* This error value is used when last.fm plugin has to be unloaded. */
122 #define VLC_AUDIOSCROBBLER_EFATAL -69
123
124 /* last.fm client identifier */
125 #define CLIENT_NAME     PACKAGE
126 #define CLIENT_VERSION  VERSION
127
128 vlc_module_begin ()
129     set_category(CAT_INTERFACE)
130     set_subcategory(SUBCAT_INTERFACE_CONTROL)
131     set_shortname(N_("Audioscrobbler"))
132     set_description(N_("Submission of played songs to last.fm"))
133     add_string("lastfm-username", "",
134                 USERNAME_TEXT, USERNAME_LONGTEXT, false)
135     add_password("lastfm-password", "", PASSWORD_TEXT, PASSWORD_LONGTEXT)
136     add_string("scrobbler-url", "post.audioscrobbler.com",
137                 URL_TEXT, URL_LONGTEXT, false)
138     set_capability("interface", 0)
139     set_callbacks(Open, Close)
140 vlc_module_end ()
141
142 /*****************************************************************************
143  * DeleteSong : Delete the char pointers in a song
144  *****************************************************************************/
145 static void DeleteSong(audioscrobbler_song_t* p_song)
146 {
147     FREENULL(p_song->psz_a);
148     FREENULL(p_song->psz_b);
149     FREENULL(p_song->psz_t);
150     FREENULL(p_song->psz_m);
151     FREENULL(p_song->psz_n);
152 }
153
154 /*****************************************************************************
155  * ReadMetaData : Read meta data when parsed by vlc
156  *****************************************************************************/
157 static void ReadMetaData(intf_thread_t *p_this, input_thread_t *p_input)
158 {
159     intf_sys_t *p_sys = p_this->p_sys;
160
161     assert(p_input != NULL);
162
163     input_item_t *p_item = input_GetItem(p_input);
164     if (p_item == NULL)
165         return;
166
167 #define ALLOC_ITEM_META(a, b) do { \
168         char *psz_meta = input_item_Get##b(p_item); \
169         if (psz_meta && *psz_meta) \
170             a = vlc_uri_encode(psz_meta); \
171         free(psz_meta); \
172     } while (0)
173
174     vlc_mutex_lock(&p_sys->lock);
175
176     p_sys->b_meta_read = true;
177
178     ALLOC_ITEM_META(p_sys->p_current_song.psz_a, Artist);
179     if (!p_sys->p_current_song.psz_a)
180     {
181         msg_Dbg(p_this, "No artist..");
182         DeleteSong(&p_sys->p_current_song);
183         goto end;
184     }
185
186     ALLOC_ITEM_META(p_sys->p_current_song.psz_t, Title);
187     if (!p_sys->p_current_song.psz_t)
188     {
189         msg_Dbg(p_this, "No track name..");
190         DeleteSong(&p_sys->p_current_song);
191         goto end;
192     }
193
194     /* Now we have read the mandatory meta data, so we can submit that info */
195     p_sys->b_submit_nowp = true;
196
197     ALLOC_ITEM_META(p_sys->p_current_song.psz_b, Album);
198     ALLOC_ITEM_META(p_sys->p_current_song.psz_m, TrackID);
199     ALLOC_ITEM_META(p_sys->p_current_song.psz_n, TrackNum);
200
201     p_sys->p_current_song.i_l = input_item_GetDuration(p_item) / 1000000;
202
203 #undef ALLOC_ITEM_META
204
205     msg_Dbg(p_this, "Meta data registered");
206
207     vlc_cond_signal(&p_sys->wait);
208
209 end:
210     vlc_mutex_unlock(&p_sys->lock);
211 }
212
213 /*****************************************************************************
214  * AddToQueue: Add the played song to the queue to be submitted
215  *****************************************************************************/
216 static void AddToQueue (intf_thread_t *p_this)
217 {
218     mtime_t                     played_time;
219     intf_sys_t                  *p_sys = p_this->p_sys;
220
221     vlc_mutex_lock(&p_sys->lock);
222
223     /* Check that we have the mandatory meta data */
224     if (!p_sys->p_current_song.psz_t || !p_sys->p_current_song.psz_a)
225         goto end;
226
227     /* wait for the user to listen enough before submitting */
228     played_time = mdate() - p_sys->p_current_song.i_start -
229                             p_sys->time_total_pauses;
230     played_time /= 1000000; /* µs → s */
231
232     /*HACK: it seam that the preparsing sometime fail,
233             so use the playing time as the song length */
234     if (p_sys->p_current_song.i_l == 0)
235         p_sys->p_current_song.i_l = played_time;
236
237     /* Don't send song shorter than 30s */
238     if (p_sys->p_current_song.i_l < 30)
239     {
240         msg_Dbg(p_this, "Song too short (< 30s), not submitting");
241         goto end;
242     }
243
244     /* Send if the user had listen more than 240s OR half the track length */
245     if ((played_time < 240) &&
246         (played_time < (p_sys->p_current_song.i_l / 2)))
247     {
248         msg_Dbg(p_this, "Song not listened long enough, not submitting");
249         goto end;
250     }
251
252     /* Check that all meta are present */
253     if (!p_sys->p_current_song.psz_a || !*p_sys->p_current_song.psz_a ||
254         !p_sys->p_current_song.psz_t || !*p_sys->p_current_song.psz_t)
255     {
256         msg_Dbg(p_this, "Missing artist or title, not submitting");
257         goto end;
258     }
259
260     if (p_sys->i_songs >= QUEUE_MAX)
261     {
262         msg_Warn(p_this, "Submission queue is full, not submitting");
263         goto end;
264     }
265
266     msg_Dbg(p_this, "Song will be submitted.");
267
268 #define QUEUE_COPY(a) \
269     p_sys->p_queue[p_sys->i_songs].a = p_sys->p_current_song.a
270
271 #define QUEUE_COPY_NULL(a) \
272     QUEUE_COPY(a); \
273     p_sys->p_current_song.a = NULL
274
275     QUEUE_COPY(i_l);
276     QUEUE_COPY_NULL(psz_n);
277     QUEUE_COPY_NULL(psz_a);
278     QUEUE_COPY_NULL(psz_t);
279     QUEUE_COPY_NULL(psz_b);
280     QUEUE_COPY_NULL(psz_m);
281     QUEUE_COPY(date);
282 #undef QUEUE_COPY_NULL
283 #undef QUEUE_COPY
284
285     p_sys->i_songs++;
286
287     /* signal the main loop we have something to submit */
288     vlc_cond_signal(&p_sys->wait);
289
290 end:
291     DeleteSong(&p_sys->p_current_song);
292     vlc_mutex_unlock(&p_sys->lock);
293 }
294
295 /*****************************************************************************
296  * PlayingChange: Playing status change callback
297  *****************************************************************************/
298 static int PlayingChange(vlc_object_t *p_this, const char *psz_var,
299                        vlc_value_t oldval, vlc_value_t newval, void *p_data)
300 {
301     VLC_UNUSED(oldval);
302
303     intf_thread_t   *p_intf = (intf_thread_t*) p_data;
304     intf_sys_t      *p_sys  = p_intf->p_sys;
305     input_thread_t  *p_input = (input_thread_t*)p_this;
306     int             state;
307
308     VLC_UNUSED(psz_var);
309
310     if (newval.i_int != INPUT_EVENT_STATE) return VLC_SUCCESS;
311
312     if (var_CountChoices(p_input, "video-es"))
313     {
314         msg_Dbg(p_this, "Not an audio-only input, not submitting");
315         return VLC_SUCCESS;
316     }
317
318     state = var_GetInteger(p_input, "state");
319
320     if (!p_sys->b_meta_read && state >= PLAYING_S)
321     {
322         ReadMetaData(p_intf, p_input);
323         return VLC_SUCCESS;
324     }
325
326
327     if (state >= END_S)
328         AddToQueue(p_intf);
329     else if (state == PAUSE_S)
330         p_sys->time_pause = mdate();
331     else if (p_sys->time_pause > 0 && state == PLAYING_S)
332     {
333         p_sys->time_total_pauses += (mdate() - p_sys->time_pause);
334         p_sys->time_pause = 0;
335     }
336
337     return VLC_SUCCESS;
338 }
339
340 /*****************************************************************************
341  * ItemChange: Playlist item change callback
342  *****************************************************************************/
343 static int ItemChange(vlc_object_t *p_this, const char *psz_var,
344                        vlc_value_t oldval, vlc_value_t newval, void *p_data)
345 {
346     intf_thread_t  *p_intf  = p_data;
347     intf_sys_t     *p_sys   = p_intf->p_sys;
348     input_thread_t *p_input = newval.p_address;
349
350     VLC_UNUSED(psz_var);
351     VLC_UNUSED(oldval);
352
353     p_sys->b_meta_read      = false;
354
355     if (p_sys->p_input != NULL)
356     {
357         var_DelCallback(p_sys->p_input, "intf-event", PlayingChange, p_intf);
358         vlc_object_release(p_sys->p_input);
359         p_sys->p_input = NULL;
360     }
361
362     if (p_input == NULL)
363         return VLC_SUCCESS;
364
365     input_item_t *p_item = input_GetItem(p_input);
366     if (p_item == NULL)
367         return VLC_SUCCESS;
368
369     if (var_CountChoices(p_input, "video-es"))
370     {
371         msg_Dbg(p_this, "Not an audio-only input, not submitting");
372         return VLC_SUCCESS;
373     }
374
375     p_sys->time_total_pauses = 0;
376     time(&p_sys->p_current_song.date);        /* to be sent to last.fm */
377     p_sys->p_current_song.i_start = mdate();    /* only used locally */
378
379     p_sys->p_input = vlc_object_hold(p_input);
380     var_AddCallback(p_input, "intf-event", PlayingChange, p_intf);
381
382     if (input_item_IsPreparsed(p_item))
383         ReadMetaData(p_intf, p_input);
384     /* if the input item was not preparsed, we'll do it in PlayingChange()
385      * callback, when "state" == PLAYING_S */
386
387     return VLC_SUCCESS;
388 }
389
390 /*****************************************************************************
391  * Open: initialize and create stuff
392  *****************************************************************************/
393 static int Open(vlc_object_t *p_this)
394 {
395     intf_thread_t   *p_intf     = (intf_thread_t*) p_this;
396     intf_sys_t      *p_sys      = calloc(1, sizeof(intf_sys_t));
397
398     if (!p_sys)
399         return VLC_ENOMEM;
400
401     p_intf->p_sys = p_sys;
402
403     vlc_mutex_init(&p_sys->lock);
404     vlc_cond_init(&p_sys->wait);
405
406     if (vlc_clone(&p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW))
407     {
408         vlc_cond_destroy(&p_sys->wait);
409         vlc_mutex_destroy(&p_sys->lock);
410         free(p_sys);
411         return VLC_ENOMEM;
412     }
413
414     var_AddCallback(pl_Get(p_intf), "input-current", ItemChange, p_intf);
415
416     return VLC_SUCCESS;
417 }
418
419 /*****************************************************************************
420  * Close: destroy interface stuff
421  *****************************************************************************/
422 static void Close(vlc_object_t *p_this)
423 {
424     intf_thread_t               *p_intf = (intf_thread_t*) p_this;
425     intf_sys_t                  *p_sys  = p_intf->p_sys;
426
427     vlc_cancel(p_sys->thread);
428     vlc_join(p_sys->thread, NULL);
429
430     var_DelCallback(pl_Get(p_intf), "input-current", ItemChange, p_intf);
431
432     if (p_sys->p_input != NULL)
433     {
434         var_DelCallback(p_sys->p_input, "intf-event", PlayingChange, p_intf);
435         vlc_object_release(p_sys->p_input);
436     }
437
438     int i;
439     for (i = 0; i < p_sys->i_songs; i++)
440         DeleteSong(&p_sys->p_queue[i]);
441     vlc_UrlClean(&p_sys->p_submit_url);
442     vlc_UrlClean(&p_sys->p_nowp_url);
443     vlc_cond_destroy(&p_sys->wait);
444     vlc_mutex_destroy(&p_sys->lock);
445     free(p_sys);
446 }
447
448 /*****************************************************************************
449  * Handshake : Init audioscrobbler connection
450  *****************************************************************************/
451 static int Handshake(intf_thread_t *p_this)
452 {
453     char                *psz_username, *psz_password;
454     char                *psz_scrobbler_url;
455     time_t              timestamp;
456     char                psz_timestamp[21];
457
458     struct md5_s        p_struct_md5;
459
460     stream_t            *p_stream;
461     char                *psz_handshake_url;
462     uint8_t             p_buffer[1024];
463     char                *p_buffer_pos;
464
465     int                 i_ret;
466     char                *psz_url;
467
468     intf_thread_t       *p_intf                 = (intf_thread_t*) p_this;
469     intf_sys_t          *p_sys                  = p_this->p_sys;
470
471     psz_username = var_InheritString(p_this, "lastfm-username");
472     psz_password = var_InheritString(p_this, "lastfm-password");
473
474     /* username or password have not been setup */
475     if (EMPTY_STR(psz_username) || EMPTY_STR(psz_password))
476     {
477         free(psz_username);
478         free(psz_password);
479         return VLC_ENOVAR;
480     }
481
482     time(&timestamp);
483
484     /* generates a md5 hash of the password */
485     InitMD5(&p_struct_md5);
486     AddMD5(&p_struct_md5, (uint8_t*) psz_password, strlen(psz_password));
487     EndMD5(&p_struct_md5);
488
489     free(psz_password);
490
491     char *psz_password_md5 = psz_md5_hash(&p_struct_md5);
492     if (!psz_password_md5)
493     {
494         free(psz_username);
495         return VLC_ENOMEM;
496     }
497
498     snprintf(psz_timestamp, sizeof(psz_timestamp), "%"PRIu64,
499               (uint64_t)timestamp);
500
501     /* generates a md5 hash of :
502      * - md5 hash of the password, plus
503      * - timestamp in clear text
504      */
505     InitMD5(&p_struct_md5);
506     AddMD5(&p_struct_md5, (uint8_t*) psz_password_md5, 32);
507     AddMD5(&p_struct_md5, (uint8_t*) psz_timestamp, strlen(psz_timestamp));
508     EndMD5(&p_struct_md5);
509     free(psz_password_md5);
510
511     char *psz_auth_token = psz_md5_hash(&p_struct_md5);
512     if (!psz_auth_token)
513     {
514         free(psz_username);
515         return VLC_ENOMEM;
516     }
517
518     psz_scrobbler_url = var_InheritString(p_this, "scrobbler-url");
519     if (!psz_scrobbler_url)
520     {
521         free(psz_auth_token);
522         free(psz_username);
523         return VLC_ENOMEM;
524     }
525
526     i_ret = asprintf(&psz_handshake_url,
527     "http://%s/?hs=true&p=1.2&c="CLIENT_NAME"&v="CLIENT_VERSION"&u=%s&t=%s&a=%s"
528     , psz_scrobbler_url, psz_username, psz_timestamp, psz_auth_token);
529
530     free(psz_auth_token);
531     free(psz_scrobbler_url);
532     free(psz_username);
533     if (i_ret == -1)
534         return VLC_ENOMEM;
535
536     /* send the http handshake request */
537     p_stream = vlc_stream_NewURL(p_intf, psz_handshake_url);
538     free(psz_handshake_url);
539
540     if (!p_stream)
541         return VLC_EGENERIC;
542
543     /* read answer */
544     i_ret = vlc_stream_Read(p_stream, p_buffer, sizeof(p_buffer) - 1);
545     if (i_ret <= 0)
546     {
547         vlc_stream_Delete(p_stream);
548         return VLC_EGENERIC;
549     }
550     p_buffer[i_ret] = '\0';
551     vlc_stream_Delete(p_stream);
552
553     p_buffer_pos = strstr((char*) p_buffer, "FAILED ");
554     if (p_buffer_pos)
555     {
556         /* handshake request failed, sorry */
557         msg_Err(p_this, "last.fm handshake failed: %s", p_buffer_pos + 7);
558         return VLC_EGENERIC;
559     }
560
561     if (strstr((char*) p_buffer, "BADAUTH"))
562     {
563         /* authentication failed, bad username/password combination */
564         vlc_dialog_display_error(p_this,
565             _("last.fm: Authentication failed"),
566             "%s", _("last.fm username or password is incorrect. "
567               "Please verify your settings and relaunch VLC."));
568         return VLC_AUDIOSCROBBLER_EFATAL;
569     }
570
571     if (strstr((char*) p_buffer, "BANNED"))
572     {
573         /* oops, our version of vlc has been banned by last.fm servers */
574         msg_Err(p_intf, "This version of VLC has been banned by last.fm. "
575                          "You should upgrade VLC, or disable the last.fm plugin.");
576         return VLC_AUDIOSCROBBLER_EFATAL;
577     }
578
579     if (strstr((char*) p_buffer, "BADTIME"))
580     {
581         /* The system clock isn't good */
582         msg_Err(p_intf, "last.fm handshake failed because your clock is too "
583                          "much shifted. Please correct it, and relaunch VLC.");
584         return VLC_AUDIOSCROBBLER_EFATAL;
585     }
586
587     p_buffer_pos = strstr((char*) p_buffer, "OK");
588     if (!p_buffer_pos)
589         goto proto;
590
591     p_buffer_pos = strstr(p_buffer_pos, "\n");
592     if (!p_buffer_pos || strlen(p_buffer_pos) < 33)
593         goto proto;
594     p_buffer_pos++; /* we skip the '\n' */
595
596     /* save the session ID */
597     memcpy(p_sys->psz_auth_token, p_buffer_pos, 32);
598     p_sys->psz_auth_token[32] = '\0';
599
600     p_buffer_pos = strstr(p_buffer_pos, "http://");
601     if (!p_buffer_pos || strlen(p_buffer_pos) == 7)
602         goto proto;
603
604     /* We need to read the nowplaying url */
605     p_buffer_pos += 7; /* we skip "http://" */
606     psz_url = strndup(p_buffer_pos, strcspn(p_buffer_pos, "\n"));
607     if (!psz_url)
608         goto oom;
609
610     vlc_UrlParse(&p_sys->p_nowp_url, psz_url);
611     free(psz_url);
612     if (p_sys->p_nowp_url.psz_host == NULL ||
613         p_sys->p_nowp_url.i_port == 0)
614     {
615         vlc_UrlClean(&p_sys->p_nowp_url);
616         goto proto;
617     }
618
619     p_buffer_pos = strstr(p_buffer_pos, "http://");
620     if (!p_buffer_pos || strlen(p_buffer_pos) == 7)
621         goto proto;
622
623     /* We need to read the submission url */
624     p_buffer_pos += 7; /* we skip "http://" */
625     psz_url = strndup(p_buffer_pos, strcspn(p_buffer_pos, "\n"));
626     if (!psz_url)
627         goto oom;
628
629     /* parse the submission url */
630     vlc_UrlParse(&p_sys->p_submit_url, psz_url);
631     free(psz_url);
632     if (p_sys->p_submit_url.psz_host == NULL ||
633         p_sys->p_submit_url.i_port == 0)
634     {
635         vlc_UrlClean(&p_sys->p_nowp_url);
636         vlc_UrlClean(&p_sys->p_submit_url);
637         goto proto;
638     }
639
640     return VLC_SUCCESS;
641
642 oom:
643     return VLC_ENOMEM;
644
645 proto:
646     msg_Err(p_intf, "Handshake: can't recognize server protocol");
647     return VLC_EGENERIC;
648 }
649
650 static void HandleInterval(mtime_t *next, unsigned int *i_interval)
651 {
652     if (*i_interval == 0)
653     {
654         /* first interval is 1 minute */
655         *i_interval = 1;
656     }
657     else
658     {
659         /* else we double the previous interval, up to 120 minutes */
660         *i_interval <<= 1;
661         if (*i_interval > 120)
662             *i_interval = 120;
663     }
664     *next = mdate() + (*i_interval * 1000000 * 60);
665 }
666
667 /*****************************************************************************
668  * Run : call Handshake() then submit songs
669  *****************************************************************************/
670 static void *Run(void *data)
671 {
672     intf_thread_t          *p_intf = data;
673     uint8_t                 p_buffer[1024];
674     int                     canc = vlc_savecancel();
675     bool                    b_handshaked = false;
676     bool                    b_nowp_submission_ongoing = false;
677
678     /* data about audioscrobbler session */
679     mtime_t                 next_exchange = 0; /**< when can we send data  */
680     unsigned int            i_interval = 0;     /**< waiting interval (secs)*/
681
682     intf_sys_t *p_sys = p_intf->p_sys;
683
684     /* main loop */
685     for (;;)
686     {
687         vlc_restorecancel(canc);
688         mwait(next_exchange);
689
690         vlc_mutex_lock(&p_sys->lock);
691         mutex_cleanup_push(&p_sys->lock);
692
693         while (p_sys->i_songs == 0 && p_sys->b_submit_nowp == false)
694             vlc_cond_wait(&p_sys->wait, &p_sys->lock);
695
696         vlc_cleanup_pop();
697         vlc_mutex_unlock(&p_sys->lock);
698         canc = vlc_savecancel();
699
700         /* handshake if needed */
701         if (!b_handshaked)
702         {
703             msg_Dbg(p_intf, "Handshaking with last.fm ...");
704
705             switch(Handshake(p_intf))
706             {
707                 case VLC_ENOMEM:
708                     goto out;
709
710                 case VLC_ENOVAR:
711                     /* username not set */
712                     vlc_dialog_display_error(p_intf,
713                         _("Last.fm username not set"),
714                         "%s", _("Please set a username or disable the "
715                         "audioscrobbler plugin, and restart VLC.\n"
716                         "Visit http://www.last.fm/join/ to get an account."));
717                     goto out;
718
719                 case VLC_SUCCESS:
720                     msg_Dbg(p_intf, "Handshake successful :)");
721                     b_handshaked = true;
722                     i_interval = 0;
723                     next_exchange = 0;
724                     break;
725
726                 case VLC_AUDIOSCROBBLER_EFATAL:
727                     msg_Warn(p_intf, "Exiting...");
728                     goto out;
729
730                 case VLC_EGENERIC:
731                 default:
732                     /* protocol error : we'll try later */
733                     HandleInterval(&next_exchange, &i_interval);
734                     break;
735             }
736             /* if handshake failed let's restart the loop */
737             if (!b_handshaked)
738                 continue;
739         }
740
741         msg_Dbg(p_intf, "Going to submit some data...");
742         char *psz_submit;
743         vlc_url_t *url;
744         char *psz_submit_song, *psz_submit_tmp;
745
746         if (asprintf(&psz_submit, "s=%s", p_sys->psz_auth_token) == -1)
747             break;
748
749         /* forge the HTTP POST request */
750         vlc_mutex_lock(&p_sys->lock);
751
752         if (p_sys->b_submit_nowp)
753         {
754             b_nowp_submission_ongoing = true;
755             url = &p_sys->p_nowp_url;
756             if (asprintf(&psz_submit_song,
757                 "&a=%s"
758                 "&t=%s"
759                 "&b=%s"
760                 "&l=%d"
761                 "&n=%s"
762                 "&m=%s",
763                 p_sys->p_current_song.psz_a,
764                 p_sys->p_current_song.psz_t,
765                 p_sys->p_current_song.psz_b ? p_sys->p_current_song.psz_b : "",
766                 p_sys->p_current_song.i_l,
767                 p_sys->p_current_song.psz_n ? p_sys->p_current_song.psz_n : "",
768                 p_sys->p_current_song.psz_m ? p_sys->p_current_song.psz_m : ""
769                 ) == -1)
770             {   /* Out of memory */
771                 vlc_mutex_unlock(&p_sys->lock);
772                 goto out;
773             }
774
775         }
776         else
777         {
778             url = &p_sys->p_submit_url;
779             audioscrobbler_song_t *p_song;
780             for (int i_song = 0 ; i_song < p_sys->i_songs ; i_song++)
781             {
782                 p_song = &p_sys->p_queue[i_song];
783                 if (asprintf(&psz_submit_song,
784                         "&a%%5B%d%%5D=%s"
785                         "&t%%5B%d%%5D=%s"
786                         "&i%%5B%d%%5D=%u"
787                         "&o%%5B%d%%5D=P"
788                         "&r%%5B%d%%5D="
789                         "&l%%5B%d%%5D=%d"
790                         "&b%%5B%d%%5D=%s"
791                         "&n%%5B%d%%5D=%s"
792                         "&m%%5B%d%%5D=%s",
793                         i_song, p_song->psz_a,
794                         i_song, p_song->psz_t,
795                         i_song, (unsigned)p_song->date, /* HACK: %ju (uintmax_t) unsupported on Windows */
796                         i_song,
797                         i_song,
798                         i_song, p_song->i_l,
799                         i_song, p_song->psz_b ? p_song->psz_b : "",
800                         i_song, p_song->psz_n ? p_song->psz_n : "",
801                         i_song, p_song->psz_m ? p_song->psz_m : ""
802                        ) == -1)
803                 {   /* Out of memory */
804                         vlc_mutex_unlock(&p_sys->lock);
805                         goto out;
806                 }
807             }
808         }
809
810         psz_submit_tmp = psz_submit;
811         int print_ret = asprintf(&psz_submit, "%s%s",
812                                  psz_submit_tmp, psz_submit_song);
813         free(psz_submit_tmp);
814         free(psz_submit_song);
815         vlc_mutex_unlock(&p_sys->lock);
816
817         if (print_ret == -1)
818         {   /* Out of memory */
819             goto out;
820         }
821
822         int i_post_socket = net_ConnectTCP(p_intf, url->psz_host,
823                                         url->i_port);
824
825         if (i_post_socket == -1)
826         {
827             /* If connection fails, we assume we must handshake again */
828             HandleInterval(&next_exchange, &i_interval);
829             b_handshaked = false;
830             free(psz_submit);
831             continue;
832         }
833
834         /* we transmit the data */
835         int i_net_ret = net_Printf(p_intf, i_post_socket,
836             "POST %s HTTP/1.1\r\n"
837             "Host: %s\r\n"
838             "User-Agent: "PACKAGE_NAME"/"PACKAGE_VERSION"\r\n"
839             "Connection: close\r\n"
840             "Accept-Encoding: identity\r\n"
841             "Content-Type: application/x-www-form-urlencoded\r\n"
842             "Content-Length: %zu\r\n"
843             "\r\n"
844             "%s\r\n"
845             "\r\n",
846             url->psz_path, url->psz_host, strlen(psz_submit), psz_submit);
847
848         free(psz_submit);
849         if (i_net_ret == -1)
850         {
851             /* If connection fails, we assume we must handshake again */
852             HandleInterval(&next_exchange, &i_interval);
853             b_handshaked = false;
854             net_Close(i_post_socket);
855             continue;
856         }
857
858         /* FIXME: this might wait forever */
859         struct pollfd ufd = { .fd = i_post_socket, .events = POLLIN };
860         while( poll( &ufd, 1, -1 ) == -1 );
861
862         /* FIXME: With TCP, you should never assume that a single read will
863          * return the entire response... */
864         i_net_ret = recv(i_post_socket, p_buffer, sizeof(p_buffer) - 1, 0);
865         if (i_net_ret <= 0)
866         {
867             /* if we get no answer, something went wrong : try again */
868             net_Close(i_post_socket);
869             continue;
870         }
871
872         net_Close(i_post_socket);
873         p_buffer[i_net_ret] = '\0';
874
875         char *failed = strstr((char *) p_buffer, "FAILED");
876         if (failed)
877         {
878             msg_Warn(p_intf, "%s", failed);
879             HandleInterval(&next_exchange, &i_interval);
880             continue;
881         }
882
883         if (strstr((char *) p_buffer, "BADSESSION"))
884         {
885             msg_Err(p_intf, "Authentication failed (BADSESSION), are you connected to last.fm with another program ?");
886             b_handshaked = false;
887             HandleInterval(&next_exchange, &i_interval);
888             continue;
889         }
890
891         if (strstr((char *) p_buffer, "OK"))
892         {
893             if (b_nowp_submission_ongoing)
894             {
895                 b_nowp_submission_ongoing = false;
896                 p_sys->b_submit_nowp = false;
897             }
898             else
899             {
900                 for (int i = 0; i < p_sys->i_songs; i++)
901                     DeleteSong(&p_sys->p_queue[i]);
902                 p_sys->i_songs = 0;
903             }
904
905             i_interval = 0;
906             next_exchange = 0;
907             msg_Dbg(p_intf, "Submission successful!");
908         }
909         else
910         {
911             msg_Err(p_intf, "Authentication failed, handshaking again (%s)",
912                              p_buffer);
913             b_handshaked = false;
914             HandleInterval(&next_exchange, &i_interval);
915         }
916     }
917 out:
918     vlc_restorecancel(canc);
919     return NULL;
920 }