1 /*
2 |  Copyright (C) 2002-2007 Jorg Schuler <jcsjcs at users sourceforge net>
3 |  Part of the gtkpod project.
4 |
5 |  URL: http://www.gtkpod.org/
6 |  URL: http://gtkpod.sourceforge.net/
7 |
8 |  This program is free software; you can redistribute it and/or modify
9 |  it under the terms of the GNU General Public License as published by
10 |  the Free Software Foundation; either version 2 of the License, or
11 |  (at your option) any later version.
12 |
13 |  This program is distributed in the hope that it will be useful,
14 |  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 |  GNU General Public License for more details.
17 |
18 |  You should have received a copy of the GNU General Public License
19 |  along with this program; if not, write to the Free Software
20 |  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 |
22 |  iTunes and iPod are trademarks of Apple
23 |
24 |  This product is not supported/written/published by Apple!
25 |
26 |  $Id$
27 */
28 
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32 
33 #define __USE_XOPEN     /* needed for strptime() with glibc2 */
34 #define _XOPEN_SOURCE   /* needed for strptime() with glibc2 */
35 
36 #include <gtk/gtk.h>
37 #include <math.h>
38 #include <string.h>
39 
40 #include "charset.h"
41 #include "itdb.h"
42 #include "misc.h"
43 #include "display.h"
44 #include "prefs.h"
45 #include <time.h>
46 
47 
48 #define DEBUG_MISC 0
49 
50 /* Note: the toggle buttons for tag_autoset and display_col in the
51  * prefs_window are named after the the TM_COLUM_* numbers defined in
52  * display.h (Title: tag_autoset0, Artist: tag_autoset1
53  * etc.). ign_field is named after T_*. Since the labels to the
54  * buttons are set in prefs_window.c when creating the window, you
55  * only need to name the buttons in the intended order using
56  * glade-2. There is no need to label them. */
57 /* Strings associated to the column headers */
58 static const gchar *t_strings[] = {
59     N_("All"),               /*  0 */
60     N_("Album"),
61     N_("Artist"),
62     N_("Title"),
63     N_("Genre"),
64     N_("Comment"),           /*  5 */
65     N_("Composer"),
66     N_("File type"),
67     N_("PC File"),
68     N_("iPod File"),
69     N_("iPod ID"),           /* 10 */
70     N_("Track Nr (#)"),
71     N_("Transferred"),
72     N_("File Size"),
73     N_("Play Time"),
74     N_("Bitrate"),           /* 15 */
75     N_("Samplerate"),
76     N_("BPM"),
77     N_("Playcount"),
78     N_("Rating"),
79     N_("Date added"),        /* 20 */
80     N_("Date played"),
81     N_("Date modified"),
82     N_("Volume"),
83     N_("Soundcheck"),
84     N_("Year"),              /* 25 */
85     N_("CD Nr"),
86     N_("Grouping"),
87     N_("Compilation"),
88     N_("Category"),
89     N_("Description"),       /* 30 */
90     N_("Podcast URL"),
91     N_("Podcast RSS"),
92     N_("Subtitle"),
93     N_("Date released"),
94     N_("Checked"),           /* 35 */
95     N_("Start time"),
96     N_("Stop time"),
97     N_("Remember Playback Position"),
98     N_("Skip when Shuffling"),
99     N_("Artwork Path"),      /* 40 */
100     N_("Media Type"),
101     N_("TV Show"),
102     N_("TV Episode"),
103     N_("TV Network"),
104     N_("Season Nr"),         /* 45 */
105     N_("Episode Nr"),
106     N_("Album Artist"),
107     N_("Sort Artist"),
108     N_("Sort Title"),
109     N_("Sort Album"),        /* 50 */
110     N_("Sort Album Artist"),
111     N_("Sort Composer"),
112     N_("Sort TV Show"),
113     N_("Gapless Track Flag"),
114     N_("Lyrics"),
115     NULL };
116 
117 /* Tooltips for prefs window */
118 static const gchar *t_tooltips[] = {
119     NULL,                                              /*  0 */
120     NULL,
121     NULL,
122     NULL,
123     NULL,
124     NULL,                                              /*  5 */
125     NULL,
126     NULL,
127     N_("Name of file on PC, if available"),
128     N_("Name of file on the iPod"),
129     NULL,                                              /* 10 */
130     N_("Track Nr. and total number of tracks on CD"),
131     N_("Whether the file has already been "
132        "transferred to the iPod or not"),
133     NULL,
134     NULL,
135     NULL,                                              /* 15 */
136     NULL,
137     N_("Beats per minute"),
138     N_("Number of times the track has been played"),
139     N_("Star rating from 0 to 5"),
140     N_("Date and time track has been added"),          /* 20 */
141     N_("Date and time track has last been played"),
142     N_("Date and time track has last been modified"),
143     N_("Manual volume adjust"),
144     N_("Volume adjust in dB (replay gain) -- "
145        "you need to activate 'soundcheck' on the iPod"),
146     NULL,                                              /* 25 */
147     N_("CD Nr. and total number of CDS in set"),
148     NULL,
149     NULL,
150     N_("The category (e.g. 'Technology' or 'Music') where the podcast was located."),
151     N_("Accessible by selecting the center button on the iPod."), /* 30 */
152     NULL,
153     NULL,
154     NULL,
155     N_("Release date (for podcasts displayed next to the title on the iPod)"),
156     NULL,   /* 35 */
157     NULL,
158     NULL,
159     NULL,
160     NULL,
161     NULL,   /* 40 */
162     NULL,
163     NULL,
164     NULL,
165     NULL,
166     NULL,   /* 45 */
167     NULL,
168     NULL,
169     N_("Used for sorting on the iPod"),
170     N_("Used for sorting on the iPod"),
171     N_("Used for sorting on the iPod"),  /* 50 */
172     N_("Used for sorting on the iPod"),
173     N_("Used for sorting on the iPod"),
174     N_("Used for sorting on the iPod"),
175     NULL,
176     NULL
177  };
178 
179 
180 /* translates a TM_COLUMN_... (defined in display.h) into a
181  * T_... (defined in display.h). Returns -1 in case a translation is not
182  * possible */
TM_to_T(TM_item tm)183 T_item TM_to_T (TM_item tm)
184 {
185     switch (tm)
186     {
187     case TM_COLUMN_TITLE:         return T_TITLE;
188     case TM_COLUMN_ARTIST:        return T_ARTIST;
189     case TM_COLUMN_ALBUM:         return T_ALBUM;
190     case TM_COLUMN_GENRE:         return T_GENRE;
191     case TM_COLUMN_COMPOSER:      return T_COMPOSER;
192     case TM_COLUMN_FILETYPE:      return T_FILETYPE;
193     case TM_COLUMN_GROUPING:      return T_GROUPING;
194     case TM_COLUMN_TRACK_NR:      return T_TRACK_NR;
195     case TM_COLUMN_CD_NR:         return T_CD_NR;
196     case TM_COLUMN_IPOD_ID:       return T_IPOD_ID;
197     case TM_COLUMN_PC_PATH:       return T_PC_PATH;
198     case TM_COLUMN_IPOD_PATH:     return T_IPOD_PATH;
199     case TM_COLUMN_TRANSFERRED:   return T_TRANSFERRED;
200     case TM_COLUMN_SIZE:          return T_SIZE;
201     case TM_COLUMN_TRACKLEN:      return T_TRACKLEN;
202     case TM_COLUMN_BITRATE:       return T_BITRATE;
203     case TM_COLUMN_SAMPLERATE:    return T_SAMPLERATE;
204     case TM_COLUMN_BPM:           return T_BPM;
205     case TM_COLUMN_PLAYCOUNT:     return T_PLAYCOUNT;
206     case TM_COLUMN_RATING:        return T_RATING;
207     case TM_COLUMN_TIME_ADDED:    return T_TIME_ADDED;
208     case TM_COLUMN_TIME_PLAYED:   return T_TIME_PLAYED;
209     case TM_COLUMN_TIME_MODIFIED: return T_TIME_MODIFIED;
210     case TM_COLUMN_VOLUME:        return T_VOLUME;
211     case TM_COLUMN_SOUNDCHECK:    return T_SOUNDCHECK;
212     case TM_COLUMN_YEAR:          return T_YEAR;
213     case TM_COLUMN_COMPILATION:   return T_COMPILATION;
214     case TM_COLUMN_COMMENT:       return T_COMMENT;
215     case TM_COLUMN_CATEGORY:      return T_CATEGORY;
216     case TM_COLUMN_DESCRIPTION:   return T_DESCRIPTION;
217     case TM_COLUMN_PODCASTURL:    return T_PODCASTURL;
218     case TM_COLUMN_PODCASTRSS:    return T_PODCASTRSS;
219     case TM_COLUMN_SUBTITLE:      return T_SUBTITLE;
220     case TM_COLUMN_TIME_RELEASED: return T_TIME_RELEASED;
221     case TM_COLUMN_THUMB_PATH:    return T_THUMB_PATH;
222     case TM_COLUMN_MEDIA_TYPE:    return T_MEDIA_TYPE;
223     case TM_COLUMN_TV_SHOW:       return T_TV_SHOW;
224     case TM_COLUMN_TV_EPISODE:    return T_TV_EPISODE;
225     case TM_COLUMN_TV_NETWORK:    return T_TV_NETWORK;
226     case TM_COLUMN_SEASON_NR:     return T_SEASON_NR;
227     case TM_COLUMN_EPISODE_NR:    return T_EPISODE_NR;
228     case TM_COLUMN_ALBUMARTIST:   return T_ALBUMARTIST;
229     case TM_COLUMN_SORT_ARTIST:   return T_SORT_ARTIST;
230     case TM_COLUMN_SORT_TITLE:    return T_SORT_TITLE;
231     case TM_COLUMN_SORT_ALBUM:    return T_SORT_ALBUM;
232     case TM_COLUMN_SORT_ALBUMARTIST: return T_SORT_ALBUMARTIST;
233     case TM_COLUMN_SORT_COMPOSER: return T_SORT_COMPOSER;
234     case TM_COLUMN_SORT_TVSHOW:   return T_SORT_TVSHOW;
235     case TM_COLUMN_LYRICS:        return T_LYRICS;
236     case TM_NUM_COLUMNS:          g_return_val_if_reached (-1);
237     }
238     return -1;
239 }
240 
241 
242 /* See track_get_item() / track_get_item_pointer()  */
get_allowed_percent_char(void)243 gchar *get_allowed_percent_char(void )
244 {
245     return g_strdup("latgcoysSny");
246 }
247 /* translates a char into a T_... (defined in display.h) */
char_to_T(gchar c)248 T_item char_to_T(gchar c){
249     switch (c) {
250         case 'l': return T_ALBUM;
251         case 'a': return T_ARTIST;
252         case 't': return T_TITLE;
253         case 'g': return T_GENRE;
254         case 'c': return T_COMMENT;
255         case 'o': return T_COMPOSER;
256         case 'f': return T_FILETYPE;
257         case 's': return T_PC_PATH;
258         case 'S': return T_IPOD_PATH;
259 	    /* case 'i': return T_IPOD_ID; */
260         case 'n': return T_TRACK_NR;
261 	    /* case 'f': return T_TRANSFERRED; */
262 	    /* case 'z': return T_SIZE; */
263 	    /* case 'L': return T_TRACKLEN; */
264 	    /* case 'b': return T_BITRATE; */
265 	    /* case 'r': return T_SAMPLERATE; */
266 	    /* case 'b': return T_BPM; */
267 	    /* case 'C': return T_PLAYCOUNT; */
268 	    /* case 'i': return T_RATING; */
269 	    /* case '': return T_TIME_ADDED; */
270 	    /* case '': return T_TIME_PLAYED; */
271 	    /* case '': return T_TIME_MODIFIED; */
272 	    /* case '': return T_VOLUME; */
273 	    /* case '': return T_SOUNDCHECK; */
274         case 'y': return T_YEAR;
275 	    /* case 'd': return T_CD_NR; */
276 	    /* case '': return T_GROUPING; */
277 	    /* case '': return T_COMPILATION; */
278 	    /* case '': return T_CATEGORY; */
279 	    /* case '': return T_DESCRIPTION; */
280 	    /* case '': return T_PODCASTURL; */
281 	    /* case '': return T_PODCASTRSS; */
282 	    /* case '': return T_SUBTITLE; */
283 	    /* case '': return T_TIME_RELEASED; */
284 	    /* case '': return T_CHECKED; */
285     }
286     return -1;
287 }
288 
289 /* translates a ST_CAT_... (defined in display.h) into a
290  * T_... (defined in display.h). Returns -1 in case a translation is not
291  * possible */
ST_to_T(ST_CAT_item st)292 T_item ST_to_T (ST_CAT_item st)
293 {
294     switch (st)
295     {
296     case ST_CAT_ARTIST:      return T_ARTIST;
297     case ST_CAT_ALBUM:       return T_ALBUM;
298     case ST_CAT_GENRE:       return T_GENRE;
299     case ST_CAT_COMPOSER:    return T_COMPOSER;
300     case ST_CAT_TITLE:       return T_TITLE;
301     case ST_CAT_YEAR:        return T_YEAR;
302     case ST_CAT_SPECIAL:     g_return_val_if_reached (-1);
303     default:                 g_return_val_if_reached (-1);
304     }
305 }
306 
307 
308 /* return descriptive string (non-localized -- pass through gettext()
309  * for the localized version) for tm_item (usually used to name
310  * buttons or column headers). */
get_tm_string(TM_item tm)311 const gchar *get_tm_string (TM_item tm)
312 {
313     T_item t = TM_to_T (tm);
314 
315     g_return_val_if_fail (t != -1, "");
316 
317     return t_strings[t];
318 }
319 
320 
321 /* return string (non-localized -- pass through gettext()
322  * for the localized version) for tm_item that can be used as a
323  * tooltip */
get_tm_tooltip(TM_item tm)324 const gchar *get_tm_tooltip (TM_item tm)
325 {
326     T_item t = TM_to_T (tm);
327 
328     g_return_val_if_fail (t != -1, "");
329 
330     return t_tooltips[t];
331 }
332 
333 
334 /* return descriptive string (non-localized -- pass through gettext()
335  * for the localized version) for tm_item (usually used to name
336  * buttons or column headers). */
get_t_string(T_item t)337 const gchar *get_t_string (T_item t)
338 {
339     g_return_val_if_fail (t>=0 && t<T_ITEM_NUM, "");
340 
341     return t_strings[t];
342 }
343 
344 
345 /* return string (non-localized -- pass through gettext()
346  * for the localized version) for tm_item that can be used as a
347  * tooltip */
get_t_tooltip(T_item t)348 const gchar *get_t_tooltip (T_item t)
349 {
350     if ((t >= 0) && (t<T_ITEM_NUM))   return t_tooltips[t];
351     else                              return ("");
352 }
353 
354 
355 
356 /*------------------------------------------------------------------*\
357  *                                                                  *
358  *                       Timestamp stuff                            *
359  *                                                                  *
360 \*------------------------------------------------------------------*/
361 
362 
363 	/* NOTE:
364 	 *
365 	 * The iPod (firmware 1.3, 2.0, ...?) doesn't seem to use the
366 	 * timezone information correctly -- no matter what you set
367 	 * iPod's timezone to, it will always record as if it were set
368 	 * to UTC -- we need to subtract the difference between
369 	 * current timezone and UTC to get a correct
370 	 * display. 'timezone' (initialized above) contains the
371 	 * difference in seconds.
372 	 */
373 /*	if (playcount->time_played)
374 	playcount->time_played += timezone;*/
375 /* FIXME: THIS IS NOT IMPLEMENTED CORRECTLY */
376 
377 
378 #define DATE_FORMAT_LONG "%x %X"
379 #define DATE_FORMAT_SHORT "%x"
380 
381 
time_to_string_format(time_t t,const gchar * format)382 static gchar *time_to_string_format (time_t t, const gchar *format)
383 {
384     gchar buf[PATH_MAX+1];
385     struct tm tm;
386     size_t size;
387 
388     g_return_val_if_fail (format, NULL);
389 
390     if (t)
391     {
392 	localtime_r (&t, &tm);
393 	size = strftime (buf, PATH_MAX, format, &tm);
394 	buf[size] = 0;
395 	return g_locale_to_utf8 (buf, -1, NULL, NULL, NULL);
396     }
397     return g_strdup ("--");
398 }
399 
400 
401 
402 /* converts the time stamp @t to a string (max. length:
403  * PATH_MAX). You must g_free the return value */
time_time_to_string(time_t t)404 gchar *time_time_to_string (time_t t)
405 {
406     return time_to_string_format (t, DATE_FORMAT_LONG);
407 }
408 
409 
410 /* converts the time stamp @t to a string (max. length PATH_MAX)
411    assuming that no time should be shown if the time is 0:00:00 */
time_fromtime_to_string(time_t t)412 gchar *time_fromtime_to_string (time_t t)
413 {
414     struct tm tm;
415 
416     localtime_r (&t, &tm);
417     if ((tm.tm_sec == 0) && (tm.tm_min == 0) && (tm.tm_hour == 0))
418 	 return time_to_string_format (t, DATE_FORMAT_SHORT);
419     else return time_to_string_format (t, DATE_FORMAT_LONG);
420 }
421 
422 
423 /* converts the time stamp @t to a string (max. length PATH_MAX)
424    assuming that no time should be shown if the time is 23:59:59 */
time_totime_to_string(time_t t)425 gchar *time_totime_to_string (time_t t)
426 {
427     struct tm tm;
428 
429     localtime_r (&t, &tm);
430     if ((tm.tm_sec == 59) && (tm.tm_min == 59) && (tm.tm_hour == 23))
431 	 return time_to_string_format (t, DATE_FORMAT_SHORT);
432     else return time_to_string_format (t, DATE_FORMAT_LONG);
433 }
434 
435 
436 /* convert the string @str to a time stamp */
time_string_to_time(const gchar * str)437 time_t time_string_to_time (const gchar *str)
438 {
439     return time_string_to_fromtime (str);
440 }
441 
442 
443 /* convert the string @str to a time stamp, assuming 0:00:00 if no
444  * time is given. Returns 0 if @str is "--" */
time_string_to_fromtime(const gchar * str)445 time_t time_string_to_fromtime (const gchar *str)
446 {
447     time_t t;
448     struct tm tm;
449 
450     g_return_val_if_fail (str, -1);
451 
452     if (strcmp (str, "--") == 0)  return 0;
453 
454     t = time (NULL);
455     localtime_r (&t, &tm);
456     tm.tm_sec = 0;
457     tm.tm_min = 0;
458     tm.tm_hour = 0;
459     strptime (str, DATE_FORMAT_LONG, &tm);
460     t = mktime (&tm);
461     return t;
462 }
463 
464 
465 /* convert the string @str to a time stamp, assuming 23:59:59 if only
466  * date is specified */
time_string_to_totime(const gchar * str)467 time_t time_string_to_totime (const gchar *str)
468 {
469     time_t t;
470     struct tm tm;
471 
472     g_return_val_if_fail (str, -1);
473 
474     t = time (NULL);
475     localtime_r (&t, &tm);
476     tm.tm_sec = 59;
477     tm.tm_min = 59;
478     tm.tm_hour = 23;
479     strptime (str, DATE_FORMAT_LONG, &tm);
480     t = mktime (&tm);
481     return t;
482 }
483 
484 
485 /* get the timestamp TM_COLUMN_TIME_CREATE/PLAYED/MODIFIED/RELEASED */
time_get_time(Track * track,T_item t_item)486 time_t time_get_time (Track *track, T_item t_item)
487 {
488     guint32 mactime = 0;
489 
490     if (track) switch (t_item)
491     {
492     case T_TIME_ADDED:
493 	mactime = track->time_added;
494 	break;
495     case T_TIME_PLAYED:
496 	mactime = track->time_played;
497 	break;
498     case T_TIME_MODIFIED:
499 	mactime = track->time_modified;
500 	break;
501     case T_TIME_RELEASED:
502 	mactime = track->time_released;
503 	break;
504     default:
505 	mactime = 0;
506 	break;
507     }
508     return mactime;
509 }
510 
511 
512 /* hopefully obvious */
time_field_to_string(Track * track,T_item t_item)513 gchar *time_field_to_string (Track *track, T_item t_item)
514 {
515     return (time_time_to_string (time_get_time (track, t_item)));
516 }
517 
518 
519 /* get the timestamp TM_COLUMN_TIME_CREATE/PLAYED/MODIFIED/RELEASED */
time_set_time(Track * track,time_t timet,T_item t_item)520 void time_set_time (Track *track, time_t timet, T_item t_item)
521 {
522     g_return_if_fail (track);
523 
524     switch (t_item)
525     {
526     case T_TIME_ADDED:
527 	track->time_added = timet;
528 	break;
529     case T_TIME_PLAYED:
530 	track->time_played = timet;
531 	break;
532     case T_TIME_MODIFIED:
533 	track->time_modified = timet;
534 	break;
535     case T_TIME_RELEASED:
536 	track->time_released = timet;
537 	break;
538     default:
539 	break;
540     }
541 }
542 
543 
544 
545 /* -------------------------------------------------------------------
546  * The following is taken straight out of glib2.0.6 (gconvert.c):
547  * g_filename_from_uri uses g_filename_from_utf8() to convert from
548  * utf8. However, the user might have selected a different charset
549  * inside gtkpod -- we must use gtkpod's charset_from_utf8()
550  * instead. That's the only line changed...
551  * -------------------------------------------------------------------*/
552 
553 /* Test of haystack has the needle prefix, comparing case
554  * insensitive. haystack may be UTF-8, but needle must
555  * contain only ascii. */
556 static gboolean
has_case_prefix(const gchar * haystack,const gchar * needle)557 has_case_prefix (const gchar *haystack, const gchar *needle)
558 {
559   const gchar *h, *n;
560 
561   /* Eat one character at a time. */
562   h = haystack;
563   n = needle;
564 
565   while (*n && *h &&
566 	 g_ascii_tolower (*n) == g_ascii_tolower (*h))
567     {
568       n++;
569       h++;
570     }
571 
572   return *n == '\0';
573 }
574 
575 static int
unescape_character(const char * scanner)576 unescape_character (const char *scanner)
577 {
578   int first_digit;
579   int second_digit;
580 
581   first_digit = g_ascii_xdigit_value (scanner[0]);
582   if (first_digit < 0)
583     return -1;
584 
585   second_digit = g_ascii_xdigit_value (scanner[1]);
586   if (second_digit < 0)
587     return -1;
588 
589   return (first_digit << 4) | second_digit;
590 }
591 
592 static gchar *
g_unescape_uri_string(const char * escaped,int len,const char * illegal_escaped_characters,gboolean ascii_must_not_be_escaped)593 g_unescape_uri_string (const char *escaped,
594 		       int         len,
595 		       const char *illegal_escaped_characters,
596 		       gboolean    ascii_must_not_be_escaped)
597 {
598   const gchar *in, *in_end;
599   gchar *out, *result;
600   int c;
601 
602   if (escaped == NULL)
603     return NULL;
604 
605   if (len < 0)
606     len = strlen (escaped);
607 
608   result = g_malloc (len + 1);
609 
610   out = result;
611   for (in = escaped, in_end = escaped + len; in < in_end; in++)
612     {
613       c = *in;
614 
615       if (c == '%')
616 	{
617 	  /* catch partial escape sequences past the end of the substring */
618 	  if (in + 3 > in_end)
619 	    break;
620 
621 	  c = unescape_character (in + 1);
622 
623 	  /* catch bad escape sequences and NUL characters */
624 	  if (c <= 0)
625 	    break;
626 
627 	  /* catch escaped ASCII */
628 	  if (ascii_must_not_be_escaped && c <= 0x7F)
629 	    break;
630 
631 	  /* catch other illegal escaped characters */
632 	  if (strchr (illegal_escaped_characters, c) != NULL)
633 	    break;
634 
635 	  in += 2;
636 	}
637 
638       *out++ = c;
639     }
640 
641   g_return_val_if_fail (out - result <= len, NULL);
642   *out = '\0';
643 
644   if (in != in_end || !g_utf8_validate (result, -1, NULL))
645     {
646       g_free (result);
647       return NULL;
648     }
649 
650   return result;
651 }
652 
653 static gboolean
is_escalphanum(gunichar c)654 is_escalphanum (gunichar c)
655 {
656   return c > 0x7F || g_ascii_isalnum (c);
657 }
658 
659 static gboolean
is_escalpha(gunichar c)660 is_escalpha (gunichar c)
661 {
662   return c > 0x7F || g_ascii_isalpha (c);
663 }
664 
665 /* allows an empty string */
666 static gboolean
hostname_validate(const char * hostname)667 hostname_validate (const char *hostname)
668 {
669   const char *p;
670   gunichar c, first_char, last_char;
671 
672   p = hostname;
673   if (*p == '\0')
674     return TRUE;
675   do
676     {
677       /* read in a label */
678       c = g_utf8_get_char (p);
679       p = g_utf8_next_char (p);
680       if (!is_escalphanum (c))
681 	return FALSE;
682       first_char = c;
683       do
684 	{
685 	  last_char = c;
686 	  c = g_utf8_get_char (p);
687 	  p = g_utf8_next_char (p);
688 	}
689       while (is_escalphanum (c) || c == '-');
690       if (last_char == '-')
691 	return FALSE;
692 
693       /* if that was the last label, check that it was a toplabel */
694       if (c == '\0' || (c == '.' && *p == '\0'))
695 	return is_escalpha (first_char);
696     }
697   while (c == '.');
698   return FALSE;
699 }
700 
701 /**
702  * g_filename_from_uri:
703  * @uri: a uri describing a filename (escaped, encoded in UTF-8).
704  * @hostname: Location to store hostname for the URI, or %NULL.
705  *            If there is no hostname in the URI, %NULL will be
706  *            stored in this location.
707  * @error: location to store the error occuring, or %NULL to ignore
708  *         errors. Any of the errors in #GConvertError may occur.
709  *
710  * Converts an escaped UTF-8 encoded URI to a local filename in the
711  * encoding used for filenames.
712  *
713  * Return value: a newly-allocated string holding the resulting
714  *               filename, or %NULL on an error.
715  **/
716 gchar *
filename_from_uri(const char * uri,char ** hostname,GError ** error)717 filename_from_uri (const char *uri,
718 		   char      **hostname,
719 		   GError    **error)
720 {
721   const char *path_part;
722   const char *host_part;
723   char *unescaped_hostname;
724   char *result;
725   char *filename;
726   int offs;
727 #ifdef G_OS_WIN32
728   char *p, *slash;
729 #endif
730 
731   if (hostname)
732     *hostname = NULL;
733 
734   if (!has_case_prefix (uri, "file:/"))
735     {
736       g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI,
737 		   _("The URI '%s' is not an absolute URI using the file scheme"),
738 		   uri);
739       return NULL;
740     }
741 
742   path_part = uri + strlen ("file:");
743 
744   if (strchr (path_part, '#') != NULL)
745     {
746       g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI,
747 		   _("The local file URI '%s' may not include a '#'"),
748 		   uri);
749       return NULL;
750     }
751 
752   if (has_case_prefix (path_part, "///"))
753     path_part += 2;
754   else if (has_case_prefix (path_part, "//"))
755     {
756       path_part += 2;
757       host_part = path_part;
758 
759       path_part = strchr (path_part, '/');
760 
761       if (path_part == NULL)
762 	{
763 	  g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI,
764 		       _("The URI '%s' is invalid"),
765 		       uri);
766 	  return NULL;
767 	}
768 
769       unescaped_hostname = g_unescape_uri_string (host_part, path_part - host_part, "", TRUE);
770 
771       if (unescaped_hostname == NULL ||
772 	  !hostname_validate (unescaped_hostname))
773 	{
774 	  g_free (unescaped_hostname);
775 	  g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI,
776 		       _("The hostname of the URI '%s' is invalid"),
777 		       uri);
778 	  return NULL;
779 	}
780 
781       if (hostname)
782 	*hostname = unescaped_hostname;
783       else
784 	g_free (unescaped_hostname);
785     }
786 
787   filename = g_unescape_uri_string (path_part, -1, "/", FALSE);
788 
789   if (filename == NULL)
790     {
791       g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI,
792 		   _("The URI '%s' contains invalidly escaped characters"),
793 		   uri);
794       return NULL;
795     }
796 
797   offs = 0;
798 #ifdef G_OS_WIN32
799   /* Drop localhost */
800   if (hostname && *hostname != NULL &&
801       g_ascii_strcasecmp (*hostname, "localhost") == 0)
802     {
803       g_free (*hostname);
804       *hostname = NULL;
805     }
806 
807   /* Turn slashes into backslashes, because that's the canonical spelling */
808   p = filename;
809   while ((slash = strchr (p, '/')) != NULL)
810     {
811       *slash = '\\';
812       p = slash + 1;
813     }
814 
815   /* Windows URIs with a drive letter can be like "file://host/c:/foo"
816    * or "file://host/c|/foo" (some Netscape versions). In those cases, start
817    * the filename from the drive letter.
818    */
819   if (g_ascii_isalpha (filename[1]))
820     {
821       if (filename[2] == ':')
822 	offs = 1;
823       else if (filename[2] == '|')
824 	{
825 	  filename[2] = ':';
826 	  offs = 1;
827 	}
828     }
829 #endif
830 
831   /* This is where we differ from glib2.0.6: we use
832      gtkpod's charset_from_utf8() instead of glib's
833      g_filename_from_utf8() */
834   result = charset_from_utf8 (filename + offs);
835   g_free (filename);
836 
837   return result;
838 }
839 
840 
841 /* exp10() and log10() are gnu extensions */
842 #ifndef exp10
843 #define exp10(x) (exp((x)*log(10)))
844 #endif
845 
846 #ifndef log10
847 #define log10(x) (log(x)/log(10))
848 #endif
849 
replaygain_to_soundcheck(gdouble replaygain)850 guint32 replaygain_to_soundcheck(gdouble replaygain)
851 {
852     /* according to Samuel Wood -- thanks! */
853     return floor (1000. * exp10 (-replaygain * 0.1) + 0.5);
854 }
855 
soundcheck_to_replaygain(guint32 soundcheck)856 gdouble soundcheck_to_replaygain(guint32 soundcheck)
857 {
858     if (soundcheck == 0) return 0;  /* unset should be 0 dB */
859     return (-10. * log10 (soundcheck/1000.));
860 }
861