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