* ./src/playlist/playlist.c: lots of playlist behaviour enhancements.
[vlc.git] / plugins / gtk / gtk_playlist.c
1 /*****************************************************************************
2  * gtk_playlist.c : Interface for the playlist dialog
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: gtk_playlist.c,v 1.33 2002/06/07 14:30:41 sam Exp $
6  *
7  * Authors: Pierre Baillet <oct@zoy.org>
8  *          St├ęphane Borel <stef@via.ecp.fr>
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/intf.h>
33
34 #include <sys/types.h>          /* for readdir  and stat stuff */
35
36 #if (!defined( WIN32 ) || defined(__MINGW32__))
37 /* Mingw has its own version of dirent */
38 #   include <dirent.h>
39 #endif
40
41 #include <sys/stat.h>
42 #include <unistd.h>
43
44 #ifdef MODULE_NAME_IS_gnome
45 #   include <gnome.h>
46 #else
47 #   include <gtk/gtk.h>
48 #endif
49
50 #include "gtk_callbacks.h"
51 #include "gtk_interface.h"
52 #include "gtk_support.h"
53 #include "gtk_playlist.h"
54 #include "gtk_common.h"
55
56 /****************************************************************************
57  * Local prototypes
58  ****************************************************************************/
59 static void UrlDecode( char *encoded_path );
60
61 /****************************************************************************
62  * Playlist window management
63  ****************************************************************************/
64 gboolean GtkPlaylistShow( GtkWidget       *widget,
65                           gpointer         user_data )
66 {
67     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
68     playlist_t *p_playlist;
69
70     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
71     if( p_playlist == NULL )
72     {
73         return FALSE;
74     }
75
76     if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playwin ) )
77     {
78         gtk_widget_hide( p_intf->p_sys->p_playwin );
79     } 
80     else 
81     {        
82         GtkCList * p_clist;
83
84         p_clist = GTK_CLIST( gtk_object_get_data(
85             GTK_OBJECT( p_intf->p_sys->p_playwin ), "playlist_clist" ) );
86         GtkRebuildCList( p_clist , p_playlist );
87         gtk_widget_show( p_intf->p_sys->p_playwin );
88         gdk_window_raise( p_intf->p_sys->p_playwin->window );
89     }
90
91     vlc_object_release( p_playlist );
92
93     return TRUE;
94 }
95
96
97 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
98 {
99      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
100 }
101
102
103 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
104 {
105      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
106 }
107
108
109
110 gboolean GtkPlaylistPrev( GtkWidget       *widget,
111                           gpointer         user_data )
112 {
113     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
114     playlist_t *p_playlist;
115
116     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
117
118     if( p_playlist )
119     {
120         playlist_Prev( p_playlist );
121         vlc_object_release( p_playlist );
122     }
123
124     return TRUE;
125 }
126
127
128 gboolean GtkPlaylistNext( GtkWidget       *widget,
129                           gpointer         user_data)
130 {
131     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
132     playlist_t *p_playlist;
133
134     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
135
136     if( p_playlist )
137     {
138         playlist_Next( p_playlist );
139         vlc_object_release( p_playlist );
140     }
141
142     return TRUE;
143 }
144
145 /****************************************************************************
146  * Playlist core functions
147  ****************************************************************************/
148 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
149 {
150
151 }
152
153
154 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
155 {
156
157 }
158
159
160 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
161 {
162 #if 0 /* PLAYLIST TARASS */
163     /* user wants to delete a file in the queue */
164     GList *     p_selection;
165     GtkCList *  p_clist;
166     playlist_t *p_playlist;
167     
168     /* catch the thread back */
169     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), /*(char*)user_data*/"intf_playlist" );
170
171     p_playlist = p_intf->p_vlc->p_playlist;
172     
173     /* lock the struct */
174     vlc_mutex_lock( &p_intf->change_lock );
175
176     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
177         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
178     
179     /* I use UNDOCUMENTED features to retrieve the selection... */
180     p_selection = p_clist->selection;
181     
182     if( g_list_length( p_selection ) > 0 )
183     {
184         /* reverse-sort so that we can delete from the furthest
185          * to the closest item to delete...
186          */
187         p_selection = g_list_sort( p_selection, GtkCompareItems );
188         g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
189         /* rebuild the CList */
190         GtkRebuildCList( p_clist, p_playlist );
191     }
192     
193     vlc_mutex_unlock( &p_intf->change_lock );
194 #endif
195 }
196
197 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
198 {
199     /* Ok, this is a really small thing, but, hey, it works and
200        might be useful, who knows ? */
201     GtkPlaylistInvert( menuitem, user_data );
202     GtkPlaylistDeleteSelected( menuitem, user_data );
203 }
204
205 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
206 {
207 #if 0 /* PLAYLIST TARASS */
208     playlist_t *p_playlist;
209     GtkCList *  p_clist;
210     int *       pi_selected;
211     int         i_sel_l;
212     int         i_dummy;
213     
214     /* catch the thread back */
215     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
216
217     p_playlist = p_intf->p_vlc->p_playlist;
218     
219     /* lock the struct */
220     vlc_mutex_lock( &p_intf->change_lock );
221
222     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
223         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
224     
225     /* have to copy the selection to an int *
226        I wasn't able to copy the g_list to another g_list
227        glib only does pointer copies, not real copies :( */
228     
229     pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
230     i_sel_l = g_list_length( p_clist->selection );
231
232     for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
233     {
234         pi_selected[i_dummy] = (long)g_list_nth_data( p_clist->selection,
235                                                       i_dummy );
236     }
237     
238     gtk_clist_freeze( p_clist );
239     gtk_clist_select_all( p_clist );
240
241     for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
242     {
243         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
244         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
245     }
246
247     free( pi_selected );
248     gtk_clist_thaw( p_clist );
249
250     vlc_mutex_unlock( &p_intf->change_lock );
251 #endif
252 }
253
254 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
255 {
256
257 }
258
259 gboolean GtkPlaylistEvent( GtkWidget * widget,
260                            GdkEvent  * event,
261                            gpointer    user_data)
262 {
263     intf_thread_t *  p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
264     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
265                                                        FIND_ANYWHERE );
266     if( p_playlist == NULL )
267     {
268         return FALSE;
269     }
270
271     if( ( event->button ).type == GDK_2BUTTON_PRESS )
272     {
273         GtkCList *  p_clist;
274         gint        i_row;
275         gint        i_col;
276
277         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
278             p_intf->p_sys->p_playwin ), "playlist_clist" ) );
279         
280         if( gtk_clist_get_selection_info( p_clist, (event->button).x, 
281                     (event->button).y, &i_row, &i_col ) == 1 )
282         {
283             playlist_Goto( p_playlist, i_row );
284         }
285
286         vlc_object_release( p_playlist );
287         return TRUE;
288     }
289
290     vlc_object_release( p_playlist );
291     return FALSE;
292 }
293
294 void GtkPlaylistDragData( GtkWidget       *widget,
295                           GdkDragContext  *drag_context,
296                           gint             x,
297                           gint             y,
298                           GtkSelectionData *data,
299                           guint            info,
300                           guint            time,
301                           gpointer         user_data )
302 {
303 #if 0 /* PLAYLIST TARASS */
304     intf_thread_t * p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
305     GtkCList *      p_clist;
306     gint            i_row;
307     gint            i_col;
308     int             i_end = p_intf->p_vlc->p_playlist->i_size;
309
310     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
311         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
312    
313     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
314     {
315         /* we are dropping somewhere into the clist items */
316         GtkDropDataReceived( p_intf, data, info, i_row );
317     } 
318     else 
319     {
320         /* else, put that at the end of the playlist */
321         GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
322     }
323
324     intf_PlaylistJumpto( p_intf->p_vlc->p_playlist, i_end - 1 );
325 #endif
326 }
327
328
329 gboolean GtkPlaylistDragMotion( GtkWidget       *widget,
330                                 GdkDragContext  *drag_context,
331                                 gint             x,
332                                 gint             y,
333                                 guint            time,
334                                 gpointer         user_data )
335 {
336 #if 0 /* PLAYLIST TARASS */
337     intf_thread_t *p_intf;
338     GtkCList *  p_clist;
339     gint        i_row;
340     gint        i_col;
341     int         i_dummy;
342     GdkColor    color;
343
344     p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
345
346     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
347         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
348
349     if( !GTK_WIDGET_TOPLEVEL(widget) )
350     {
351         gdk_window_raise( p_intf->p_sys->p_playwin->window );
352     }
353
354     color.red =   0xffff;
355     color.blue =  0xffff;
356     color.green = 0xffff;
357
358     gtk_clist_freeze( p_clist );
359     
360     for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
361     {
362        gtk_clist_set_background ( p_clist, i_dummy , &color);
363     }
364
365     color.red = 0xffff;
366     color.blue = 0;
367     color.green = 0;
368     i_row = p_intf->p_vlc->p_playlist->i_index;
369     gtk_clist_set_background( p_clist, i_row, &color );
370         
371     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
372     {
373         color.red = 0;
374         color.blue = 0xf000;
375         color.green = 0x9000;
376         gtk_clist_set_background ( p_clist, i_row - 1, &color);
377         gtk_clist_set_background ( p_clist, i_row, &color);
378     }
379
380     gtk_clist_thaw( p_clist );
381 #endif
382     
383     return TRUE;
384 }
385
386 void GtkDropDataReceived( intf_thread_t * p_intf,
387         GtkSelectionData * p_data, guint i_info, int i_position)
388 {
389 #if 0 /* PLAYLIST TARASS */
390     /* first we'll have to split against all the '\n' we have */
391     gchar *     p_protocol;
392     gchar *     p_temp;
393     gchar *     p_next;
394     gchar *     p_string = p_data->data ;
395     GList *     p_files = NULL;
396     GtkCList *  p_clist;
397
398     
399     /* catch the playlist back */
400     playlist_t * p_playlist = p_intf->p_vlc->p_playlist;
401    
402
403     /* if this has been URLencoded, decode it
404      * 
405      * Is it a good thing to do it in place ?
406      * probably not... 
407      */
408     if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
409     {
410         UrlDecode( p_string );
411     }
412     
413     /* this cuts string into single file drops */
414     /* this code was borrowed from xmms, thx guys :) */
415     while( *p_string)
416     {
417         p_next = strchr( p_string, '\n' );
418         if( p_next )
419         {
420             if( *( p_next - 1 ) == '\r' )
421             {
422                 *( p_next - 1) = '\0';
423             }
424             *p_next = '\0';
425         }
426
427         /* do we have a protocol or something ? */
428         p_temp = strstr( p_string, ":" );
429         if( p_temp != NULL && p_temp[0] != '\0' )
430         {
431             char i_save;
432
433             i_save = p_temp[0];
434             p_temp[0] = '\0';
435             p_protocol = strdup( p_string );
436             p_temp[0] = i_save;
437             p_temp++;
438
439             /* Allowed things are proto: or proto:// */
440             if( p_temp[0] == '/' && p_temp[1] == '/')
441             {
442                 /* eat two '/'s */
443                 p_temp += 2;
444             }
445             msg_Dbg( p_intf, "playlist protocol '%s', target '%s'",
446                              p_protocol, p_temp );
447         } 
448         else 
449         {
450             p_protocol = strdup( "" );
451         }
452          
453         /* if it uses the file protocol we can do something, else, sorry :( 
454          * I think this is a good choice for now, as we don't have any
455          * ability to read http:// or ftp:// files
456          * what about adding dvd:// to the list of authorized proto ? */
457         
458         if( strcmp( p_protocol, "file:" ) == 0 )
459         {
460             p_files = g_list_concat( p_files, GtkReadFiles( p_string ) ); 
461         }
462         else
463         {
464             p_files = g_list_concat( p_files,
465                       g_list_append( NULL, g_strdup( p_string ) ) );
466         }
467        
468         /* free the malloc and go on... */
469         free( p_protocol );
470
471         if( p_next == NULL )
472         {
473             break;
474         }
475         p_string = p_next + 1;
476     }
477    
478     /* At this point, we have a nice big list maybe NULL */
479     if( p_files != NULL )
480     {
481         /* lock the interface */
482         vlc_mutex_lock( &p_intf->change_lock );
483
484         msg_Dbg( p_intf, "list has %d elements", g_list_length( p_files ) ); 
485         GtkAppendList( p_playlist, i_position, p_files );
486
487         /* get the CList  and rebuild it. */
488         p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playwin,
489                                             "playlist_clist" ) ); 
490         GtkRebuildCList( p_clist , p_playlist );
491         
492         /* unlock the interface */
493         vlc_mutex_unlock( &p_intf->change_lock );
494     }
495 #endif
496 }
497
498
499 void GtkDeleteGListItem( gpointer data, gpointer param )
500 {
501 #if 0 /* PLAYLIST TARASS */
502     int i_cur_row = (long)data;
503     intf_thread_t * p_intf = param;    
504     
505     intf_PlaylistDelete( p_intf->p_vlc->p_playlist, i_cur_row );
506
507     /* are we deleting the current played stream */
508     if( p_intf->p_sys->i_playing == i_cur_row )
509     {
510         /* next ! */
511         p_intf->p_sys->p_input->b_eof = 1;
512         /* this has to set the slider to 0 */
513         
514         /* step minus one */
515         p_intf->p_sys->i_playing-- ;
516
517         vlc_mutex_lock( &p_intf->p_vlc->p_playlist->change_lock );
518         p_intf->p_vlc->p_playlist->i_index-- ;
519         vlc_mutex_unlock( &p_intf->p_vlc->p_playlist->change_lock );
520     }
521 #endif
522 }
523
524
525 gint GtkCompareItems( gconstpointer a, gconstpointer b )
526 {
527     return b - a;
528 }
529
530
531 /* check a file (string) against supposed valid extension */
532 int GtkHasValidExtension( gchar * psz_filename )
533 {
534     char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
535     int  i_ext = 6;
536     int  i_dummy;
537
538     gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
539
540     for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
541     {
542         if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
543         {
544             return 1;
545         }
546     }
547
548     return 0;
549 }
550
551 /* recursive function: descend into folders and build a list of
552  * valid filenames */
553 GList * GtkReadFiles( gchar * psz_fsname )
554 {
555     struct stat statbuf;
556     GList  *    p_current = NULL;
557
558     /* get the attributes of this file */
559     stat( psz_fsname, &statbuf );
560     
561     /* is it a regular file ? */
562     if( S_ISREG( statbuf.st_mode ) )
563     {
564         if( GtkHasValidExtension( psz_fsname ) )
565         {
566 //X            msg_Warn( "%s is a valid file. Stacking on the playlist",
567 //X                      psz_fsname );
568             return g_list_append( NULL, g_strdup( psz_fsname ) );
569         } 
570         else
571         {
572             return NULL;
573         }
574     } 
575     /* is it a directory (should we check for symlinks ?) */
576     else if( S_ISDIR( statbuf.st_mode ) ) 
577     {
578         /* have to cd into this dir */
579         DIR *           p_current_dir = opendir( psz_fsname );
580         struct dirent * p_dir_content; 
581         
582 //X        msg_Warn( "%s is a folder.", psz_fsname );
583         
584         if( p_current_dir == NULL )
585         {
586             /* something went bad, get out of here ! */
587             return p_current;
588         }
589         p_dir_content = readdir( p_current_dir );
590
591         /* while we still have entries in the directory */
592         while( p_dir_content != NULL )
593         {
594             /* if it is "." or "..", forget it */
595             if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
596                 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
597             {
598                 /* else build the new directory by adding
599                    fsname "/" and the current entry name 
600                    (kludgy :()
601                   */
602                 char *  psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
603                             strlen( p_dir_content->d_name ) * sizeof(char) );
604                 strcpy( psz_newfs, psz_fsname );
605                 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
606                         p_dir_content->d_name );
607                 psz_newfs[strlen( psz_fsname )] = '/';
608                 
609                 p_current = g_list_concat( p_current,
610                                            GtkReadFiles( psz_newfs ) );
611                     
612                 g_free( psz_newfs );
613             }
614             p_dir_content = readdir( p_current_dir );
615         }
616         return p_current;
617     }
618     return NULL;
619 }
620
621 /* add items in a playlist 
622  * when i_pos==-1 add to the end of the list... 
623  */
624 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
625 {
626 #if 0 /* PLAYLIST TARASS */
627     guint i_dummy;
628     guint i_length;
629
630     i_length = g_list_length( p_list );
631
632     for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
633     {
634         intf_PlaylistAdd( p_playlist, 
635                 /* ok; this is a really nasty trick to insert
636                    the item where they are suppose to go but, hey
637                    this works :P (btw, you are really nasty too) */
638                 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ), 
639                 g_list_nth_data( p_list, i_dummy ) );
640     }
641 #endif
642     return 0;
643 }
644
645 /* statis timeouted function */
646 void GtkPlayListManage( intf_thread_t * p_intf )
647 {
648     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
649                                                        FIND_ANYWHERE );
650     GtkCList *   p_clist;
651
652     if( p_playlist == NULL )
653     {
654         return;
655     }
656
657     /* this thing really sucks for now :( */
658
659     /* TODO speak more with interface/intf_playlist.c */
660     if( GTK_IS_WIDGET( p_intf->p_sys->p_playwin ) )
661     {
662         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
663                        p_intf->p_sys->p_playwin ), "playlist_clist" ) );
664     
665         vlc_mutex_lock( &p_playlist->object_lock );
666     
667         if( p_intf->p_sys->i_playing != p_playlist->i_index )
668         {
669             GdkColor color;
670     
671             color.red = 0xffff;
672             color.blue = 0;
673             color.green = 0;
674     
675             gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
676     
677             if( p_intf->p_sys->i_playing != -1 )
678             {
679                 color.red = 0xffff;
680                 color.blue = 0xffff;
681                 color.green = 0xffff;
682                 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
683                                           &color);
684             }
685             p_intf->p_sys->i_playing = p_playlist->i_index;
686         }
687     
688         vlc_mutex_unlock( &p_playlist->object_lock );
689     }
690
691     vlc_object_release( p_playlist );
692 }
693
694 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
695 {
696     int         i_dummy;
697     gchar *     ppsz_text[2];
698     GdkColor    red;
699     red.red     = 65535;
700     red.blue    = 0;
701     red.green   = 0;
702     
703     gtk_clist_freeze( p_clist );
704     gtk_clist_clear( p_clist );
705    
706     vlc_mutex_lock( &p_playlist->object_lock );
707     for( i_dummy = p_playlist->i_size ; i_dummy-- ; )
708     {
709         ppsz_text[0] = p_playlist->pp_items[i_dummy]->psz_name;
710         ppsz_text[1] = "no info";
711         gtk_clist_insert( p_clist, 0, ppsz_text );
712     }
713     vlc_mutex_unlock( &p_playlist->object_lock );
714
715     gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
716     gtk_clist_thaw( p_clist );
717 }
718
719 /* URL-decode a file: URL path, return NULL if it's not what we expect */
720 static void UrlDecode( char *encoded_path )
721 {
722     char *tmp = NULL, *cur = NULL, *ext = NULL;
723     int realchar;
724
725     if( !encoded_path || *encoded_path == '\0' )
726     {
727         return;
728     }
729
730     cur = encoded_path ;
731
732     tmp = calloc(strlen(encoded_path) + 1,  sizeof(char) );
733
734     while ( ( ext = strchr(cur, '%') ) != NULL)
735     {
736         strncat(tmp, cur, (ext - cur) / sizeof(char));
737         ext++;
738
739         if (!sscanf(ext, "%2x", &realchar))
740         {
741             free(tmp);
742             return;
743         }
744
745         tmp[strlen(tmp)] = (char)realchar;
746
747         cur = ext + 2;
748     }
749
750     strcat(tmp, cur);
751     strcpy(encoded_path,tmp);
752 }
753