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