2573
|
1 /* Audacious - Cross-platform multimedia player
|
|
2 * Copyright (C) 2005-2008 Audacious development team
|
|
3 *
|
|
4 * Based on BMP:
|
|
5 * Copyright (C) 2003-2004 BMP development team.
|
|
6 *
|
|
7 * Based on XMMS:
|
|
8 * Copyright (C) 1998-2003 XMMS development team.
|
|
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; under version 3 of the License.
|
|
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, see <http://www.gnu.org/licenses>.
|
|
21 *
|
|
22 * The Audacious team does not consider modular code linking to
|
|
23 * Audacious or using our public API to be a derived work.
|
|
24 */
|
|
25
|
|
26 /*#define AUD_DEBUG*/
|
|
27
|
|
28 #ifdef HAVE_CONFIG_H
|
|
29 # include "config.h"
|
|
30 #endif
|
|
31
|
|
32 #include "util.h"
|
|
33
|
|
34 #include <glib.h>
|
|
35 #include <glib/gi18n.h>
|
|
36 #include <gtk/gtk.h>
|
|
37 #include <stdlib.h>
|
|
38 #include <string.h>
|
|
39 #include <ctype.h>
|
|
40 #include <math.h>
|
|
41
|
|
42 #include "platform/smartinclude.h"
|
|
43 #include <errno.h>
|
|
44
|
|
45 #ifdef HAVE_FTS_H
|
|
46 # include <sys/types.h>
|
|
47 # include <sys/stat.h>
|
|
48 # include <fts.h>
|
|
49 #endif
|
|
50
|
|
51 #include <audacious/input.h>
|
|
52 #include <audacious/playback.h>
|
|
53
|
|
54 #ifdef USE_CHARDET
|
|
55 # include "../libguess/libguess.h"
|
|
56 # ifdef HAVE_UDET
|
|
57 # include <libudet_c.h>
|
|
58 # endif
|
|
59 #endif
|
|
60
|
|
61 #include "plugin.h"
|
|
62
|
|
63 /*
|
|
64 * find <file> in directory <dirname> or subdirectories. return
|
|
65 * pointer to complete filename which has to be freed by calling
|
|
66 * "g_free()" after use. Returns NULL if file could not be found.
|
|
67 */
|
|
68
|
|
69 /* somebody tell me how to make use of those funcs from string.h that are in core... */
|
|
70 gboolean
|
|
71 str_has_suffix_nocase(const gchar * str, const gchar * suffix)
|
|
72 {
|
|
73 return (strcasecmp(str + strlen(str) - strlen(suffix), suffix) == 0);
|
|
74 }
|
|
75
|
|
76 static gchar *
|
|
77 str_replace_char(gchar * str, gchar old, gchar new)
|
|
78 {
|
|
79 gchar *match;
|
|
80
|
|
81 g_return_val_if_fail(str != NULL, NULL);
|
|
82
|
|
83 match = str;
|
|
84 while ((match = strchr(match, old)))
|
|
85 *match = new;
|
|
86
|
|
87 return str;
|
|
88 }
|
|
89
|
|
90 static gchar *
|
|
91 str_replace_drive_letter(gchar * str)
|
|
92 {
|
|
93 gchar *match, *match_end;
|
|
94
|
|
95 g_return_val_if_fail(str != NULL, NULL);
|
|
96
|
|
97 while ((match = strstr(str, ":\\"))) {
|
|
98 match--;
|
|
99 match_end = match + 3;
|
|
100 *match++ = '/';
|
|
101 while (*match_end)
|
|
102 *match++ = *match_end++;
|
|
103 *match = 0; /* the end of line */
|
|
104 }
|
|
105
|
|
106 return str;
|
|
107 }
|
|
108
|
|
109 gchar *
|
|
110 convert_dos_path(gchar * path)
|
|
111 {
|
|
112 g_return_val_if_fail(path != NULL, NULL);
|
|
113
|
|
114 /* replace drive letter with '/' */
|
|
115 str_replace_drive_letter(path);
|
|
116
|
|
117 /* replace '\' with '/' */
|
|
118 str_replace_char(path, '\\', '/');
|
|
119
|
|
120 return path;
|
|
121 }
|
|
122
|
|
123 gchar *
|
|
124 escape_shell_chars(const gchar * string)
|
|
125 {
|
|
126 const gchar *special = "$`\"\\"; /* Characters to escape */
|
|
127 const gchar *in = string;
|
|
128 gchar *out, *escaped;
|
|
129 gint num = 0;
|
|
130
|
|
131 while (*in != '\0')
|
|
132 if (strchr(special, *in++))
|
|
133 num++;
|
|
134
|
|
135 escaped = g_malloc(strlen(string) + num + 1);
|
|
136
|
|
137 in = string;
|
|
138 out = escaped;
|
|
139
|
|
140 while (*in != '\0') {
|
|
141 if (strchr(special, *in))
|
|
142 *out++ = '\\';
|
|
143 *out++ = *in++;
|
|
144 }
|
|
145 *out = '\0';
|
|
146
|
|
147 return escaped;
|
|
148 }
|
|
149
|
|
150
|
|
151 typedef struct {
|
|
152 const gchar *to_match;
|
|
153 gchar *match;
|
|
154 gboolean found;
|
|
155 } FindFileContext;
|
|
156
|
|
157 static const struct {
|
|
158 AFormat afmt;
|
|
159 SAD_sample_format sadfmt;
|
|
160 } format_table[] = {
|
|
161 {FMT_U8, SAD_SAMPLE_U8},
|
|
162 {FMT_S8, SAD_SAMPLE_S8},
|
|
163
|
|
164 {FMT_S16_LE, SAD_SAMPLE_S16_LE},
|
|
165 {FMT_S16_BE, SAD_SAMPLE_S16_BE},
|
|
166 {FMT_S16_NE, SAD_SAMPLE_S16},
|
|
167
|
|
168 {FMT_U16_LE, SAD_SAMPLE_U16_LE},
|
|
169 {FMT_U16_BE, SAD_SAMPLE_U16_BE},
|
|
170 {FMT_U16_NE, SAD_SAMPLE_U16},
|
|
171
|
|
172 {FMT_S24_LE, SAD_SAMPLE_S24_LE},
|
|
173 {FMT_S24_BE, SAD_SAMPLE_S24_BE},
|
|
174 {FMT_S24_NE, SAD_SAMPLE_S24},
|
|
175
|
|
176 {FMT_U24_LE, SAD_SAMPLE_U24_LE},
|
|
177 {FMT_U24_BE, SAD_SAMPLE_U24_BE},
|
|
178 {FMT_U24_NE, SAD_SAMPLE_U24},
|
|
179
|
|
180 {FMT_S32_LE, SAD_SAMPLE_S32_LE},
|
|
181 {FMT_S32_BE, SAD_SAMPLE_S32_BE},
|
|
182 {FMT_S32_NE, SAD_SAMPLE_S32},
|
|
183
|
|
184 {FMT_U32_LE, SAD_SAMPLE_U32_LE},
|
|
185 {FMT_U32_BE, SAD_SAMPLE_U32_BE},
|
|
186 {FMT_U32_NE, SAD_SAMPLE_U32},
|
|
187
|
|
188 {FMT_FLOAT, SAD_SAMPLE_FLOAT},
|
|
189 {FMT_FIXED32, SAD_SAMPLE_FIXED32},
|
|
190 };
|
|
191
|
|
192 SAD_sample_format
|
|
193 sadfmt_from_afmt(AFormat fmt)
|
|
194 {
|
|
195 int i;
|
|
196 for (i = 0; i < sizeof(format_table) / sizeof(format_table[0]); i++) {
|
|
197 if (format_table[i].afmt == fmt) return format_table[i].sadfmt;
|
|
198 }
|
|
199
|
|
200 return -1;
|
|
201 }
|
|
202
|
|
203
|
|
204 static gboolean
|
|
205 find_file_func(const gchar * path, const gchar * basename, gpointer data)
|
|
206 {
|
|
207 FindFileContext *context = data;
|
|
208
|
|
209 if (strlen(path) > FILENAME_MAX) {
|
|
210 AUDDBG("Ignoring path: name too long (%s)\n", path);
|
|
211 return TRUE;
|
|
212 }
|
|
213
|
|
214 if (aud_vfs_file_test(path, G_FILE_TEST_IS_REGULAR)) {
|
|
215 if (!strcasecmp(basename, context->to_match)) {
|
|
216 context->match = g_strdup(path);
|
|
217 context->found = TRUE;
|
|
218 return TRUE;
|
|
219 }
|
|
220 }
|
|
221 else if (aud_vfs_file_test(path, G_FILE_TEST_IS_DIR)) {
|
|
222 dir_foreach(path, find_file_func, context, NULL);
|
|
223 if (context->found)
|
|
224 return TRUE;
|
|
225 }
|
|
226
|
|
227 return FALSE;
|
|
228 }
|
|
229
|
|
230 gchar *
|
|
231 find_file_recursively(const gchar * path, const gchar * filename)
|
|
232 {
|
|
233 FindFileContext context;
|
|
234 gchar *out = NULL;
|
|
235
|
|
236 context.to_match = filename;
|
|
237 context.match = NULL;
|
|
238 context.found = FALSE;
|
|
239
|
|
240 dir_foreach(path, find_file_func, &context, NULL);
|
|
241
|
|
242 if (context.match)
|
|
243 {
|
|
244 out = g_filename_to_uri(context.match, NULL, NULL);
|
|
245 g_free(context.match);
|
|
246 }
|
|
247
|
|
248 return out;
|
|
249 }
|
|
250
|
|
251 gchar *
|
|
252 find_path_recursively(const gchar * path, const gchar * filename)
|
|
253 {
|
|
254 FindFileContext context;
|
|
255
|
|
256 context.to_match = filename;
|
|
257 context.match = NULL;
|
|
258 context.found = FALSE;
|
|
259
|
|
260 dir_foreach(path, find_file_func, &context, NULL);
|
|
261
|
|
262 return context.match;
|
|
263 }
|
|
264
|
|
265
|
|
266 typedef enum {
|
|
267 ARCHIVE_UNKNOWN = 0,
|
|
268 ARCHIVE_DIR,
|
|
269 ARCHIVE_TAR,
|
|
270 ARCHIVE_TGZ,
|
|
271 ARCHIVE_ZIP,
|
|
272 ARCHIVE_TBZ2
|
|
273 } ArchiveType;
|
|
274
|
|
275 typedef gchar *(*ArchiveExtractFunc) (const gchar *, const gchar *);
|
|
276
|
|
277 typedef struct {
|
|
278 ArchiveType type;
|
|
279 const gchar *ext;
|
|
280 } ArchiveExtensionType;
|
|
281
|
|
282 static ArchiveExtensionType archive_extensions[] = {
|
|
283 {ARCHIVE_TAR, ".tar"},
|
|
284 {ARCHIVE_ZIP, ".wsz"},
|
|
285 {ARCHIVE_ZIP, ".zip"},
|
|
286 {ARCHIVE_TGZ, ".tar.gz"},
|
|
287 {ARCHIVE_TGZ, ".tgz"},
|
|
288 {ARCHIVE_TBZ2, ".tar.bz2"},
|
|
289 {ARCHIVE_TBZ2, ".bz2"},
|
|
290 {ARCHIVE_UNKNOWN, NULL}
|
|
291 };
|
|
292
|
|
293 static gchar *archive_extract_tar(const gchar * archive, const gchar * dest);
|
|
294 static gchar *archive_extract_zip(const gchar * archive, const gchar * dest);
|
|
295 static gchar *archive_extract_tgz(const gchar * archive, const gchar * dest);
|
|
296 static gchar *archive_extract_tbz2(const gchar * archive, const gchar * dest);
|
|
297
|
|
298 static ArchiveExtractFunc archive_extract_funcs[] = {
|
|
299 NULL,
|
|
300 NULL,
|
|
301 archive_extract_tar,
|
|
302 archive_extract_tgz,
|
|
303 archive_extract_zip,
|
|
304 archive_extract_tbz2
|
|
305 };
|
|
306
|
|
307
|
|
308 /* FIXME: these functions can be generalised into a function using a
|
|
309 * command lookup table */
|
|
310
|
|
311 static const gchar *
|
|
312 get_tar_command(void)
|
|
313 {
|
|
314 static const gchar *command = NULL;
|
|
315
|
|
316 if (!command) {
|
|
317 if (!(command = getenv("TARCMD")))
|
|
318 command = "tar";
|
|
319 }
|
|
320
|
|
321 return command;
|
|
322 }
|
|
323
|
|
324 static const gchar *
|
|
325 get_unzip_command(void)
|
|
326 {
|
|
327 static const gchar *command = NULL;
|
|
328
|
|
329 if (!command) {
|
|
330 if (!(command = getenv("UNZIPCMD")))
|
|
331 command = "unzip";
|
|
332 }
|
|
333
|
|
334 return command;
|
|
335 }
|
|
336
|
|
337
|
|
338 static gchar *
|
|
339 archive_extract_tar(const gchar * archive, const gchar * dest)
|
|
340 {
|
|
341 return g_strdup_printf("%s >/dev/null xf \"%s\" -C %s",
|
|
342 get_tar_command(), archive, dest);
|
|
343 }
|
|
344
|
|
345 static gchar *
|
|
346 archive_extract_zip(const gchar * archive, const gchar * dest)
|
|
347 {
|
|
348 return g_strdup_printf("%s >/dev/null -o -j \"%s\" -d %s",
|
|
349 get_unzip_command(), archive, dest);
|
|
350 }
|
|
351
|
|
352 static gchar *
|
|
353 archive_extract_tgz(const gchar * archive, const gchar * dest)
|
|
354 {
|
|
355 return g_strdup_printf("%s >/dev/null xzf \"%s\" -C %s",
|
|
356 get_tar_command(), archive, dest);
|
|
357 }
|
|
358
|
|
359 static gchar *
|
|
360 archive_extract_tbz2(const gchar * archive, const gchar * dest)
|
|
361 {
|
|
362 return g_strdup_printf("bzip2 -dc \"%s\" | %s >/dev/null xf - -C %s",
|
|
363 archive, get_tar_command(), dest);
|
|
364 }
|
|
365
|
|
366
|
|
367 ArchiveType
|
|
368 archive_get_type(const gchar * filename)
|
|
369 {
|
|
370 gint i = 0;
|
|
371
|
|
372 if (g_file_test(filename, G_FILE_TEST_IS_DIR))
|
|
373 return ARCHIVE_DIR;
|
|
374
|
|
375 while (archive_extensions[i].ext) {
|
|
376 if (g_str_has_suffix(filename, archive_extensions[i].ext)) {
|
|
377 return archive_extensions[i].type;
|
|
378 }
|
|
379 i++;
|
|
380 }
|
|
381
|
|
382 return ARCHIVE_UNKNOWN;
|
|
383 }
|
|
384
|
|
385 gboolean
|
|
386 file_is_archive(const gchar * filename)
|
|
387 {
|
|
388 return (archive_get_type(filename) > ARCHIVE_DIR);
|
|
389 }
|
|
390
|
|
391 gchar *
|
|
392 archive_basename(const gchar * str)
|
|
393 {
|
|
394 gint i = 0;
|
|
395
|
|
396 while (archive_extensions[i].ext) {
|
|
397 if (str_has_suffix_nocase(str, archive_extensions[i].ext)) {
|
|
398 const gchar *end = g_strrstr(str, archive_extensions[i].ext);
|
|
399 if (end) {
|
|
400 return g_strndup(str, end - str);
|
|
401 }
|
|
402 break;
|
|
403 }
|
|
404 i++;
|
|
405 }
|
|
406
|
|
407 return NULL;
|
|
408 }
|
|
409
|
|
410 /*
|
|
411 decompress_archive
|
|
412
|
|
413 Decompresses the archive "filename" to a temporary directory,
|
|
414 returns the path to the temp dir, or NULL if failed,
|
|
415 watch out tho, doesn't actually check if the system command succeeds :-|
|
|
416 */
|
|
417
|
|
418 gchar *
|
|
419 archive_decompress(const gchar * filename)
|
|
420 {
|
|
421 gchar *tmpdir, *cmd, *escaped_filename;
|
|
422 ArchiveType type;
|
|
423 #ifndef HAVE_MKDTEMP
|
|
424 mode_t mode755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
|
425 #endif
|
|
426
|
|
427 if ((type = archive_get_type(filename)) <= ARCHIVE_DIR)
|
|
428 return NULL;
|
|
429
|
|
430 #ifdef HAVE_MKDTEMP
|
|
431 tmpdir = g_build_filename(g_get_tmp_dir(), "audacious.XXXXXXXX", NULL);
|
|
432 if (!mkdtemp(tmpdir)) {
|
|
433 g_free(tmpdir);
|
|
434 AUDDBG("Unable to load skin: Failed to create temporary "
|
|
435 "directory: %s\n", g_strerror(errno));
|
|
436 return NULL;
|
|
437 }
|
|
438 #else
|
|
439 tmpdir = g_strdup_printf("%s/audacious.%ld", g_get_tmp_dir(), (long) rand());
|
|
440 make_directory(tmpdir, mode755);
|
|
441 #endif
|
|
442
|
|
443 escaped_filename = escape_shell_chars(filename);
|
|
444 cmd = archive_extract_funcs[type] (escaped_filename, tmpdir);
|
|
445 g_free(escaped_filename);
|
|
446
|
|
447 if (!cmd) {
|
|
448 AUDDBG("extraction function is NULL!\n");
|
|
449 g_free(tmpdir);
|
|
450 return NULL;
|
|
451 }
|
|
452
|
|
453 AUDDBG("Attempt to execute \"%s\"\n", cmd);
|
|
454
|
|
455 if(system(cmd) != 0)
|
|
456 {
|
|
457 AUDDBG("could not execute cmd %s\n",cmd);
|
|
458 g_free(cmd);
|
|
459 return NULL;
|
|
460 }
|
|
461 g_free(cmd);
|
|
462
|
|
463 return tmpdir;
|
|
464 }
|
|
465
|
|
466
|
|
467 #ifdef HAVE_FTS_H
|
|
468
|
|
469 void
|
|
470 del_directory(const gchar * dirname)
|
|
471 {
|
|
472 gchar *const argv[2] = { (gchar *) dirname, NULL };
|
|
473 FTS *fts;
|
|
474 FTSENT *p;
|
|
475
|
|
476 fts = fts_open(argv, FTS_PHYSICAL, (gint(*)())NULL);
|
|
477 while ((p = fts_read(fts))) {
|
|
478 switch (p->fts_info) {
|
|
479 case FTS_D:
|
|
480 break;
|
|
481 case FTS_DNR:
|
|
482 case FTS_ERR:
|
|
483 break;
|
|
484 case FTS_DP:
|
|
485 rmdir(p->fts_accpath);
|
|
486 break;
|
|
487 default:
|
|
488 unlink(p->fts_accpath);
|
|
489 break;
|
|
490 }
|
|
491 }
|
|
492 fts_close(fts);
|
|
493 }
|
|
494
|
|
495 #else /* !HAVE_FTS */
|
|
496
|
|
497 gboolean
|
|
498 del_directory_func(const gchar * path, const gchar * basename,
|
|
499 gpointer params)
|
|
500 {
|
|
501 if (!strcmp(basename, ".") || !strcmp(path, ".."))
|
|
502 return FALSE;
|
|
503
|
|
504 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
|
|
505 dir_foreach(path, del_directory_func, NULL, NULL);
|
|
506 rmdir(path);
|
|
507 return FALSE;
|
|
508 }
|
|
509
|
|
510 unlink(path);
|
|
511
|
|
512 return FALSE;
|
|
513 }
|
|
514
|
|
515 void
|
|
516 del_directory(const gchar * path)
|
|
517 {
|
|
518 dir_foreach(path, del_directory_func, NULL, NULL);
|
|
519 rmdir(path);
|
|
520 }
|
|
521
|
|
522 #endif /* ifdef HAVE_FTS */
|
|
523
|
|
524 static void
|
|
525 strip_string(GString *string)
|
|
526 {
|
|
527 while (string->len > 0 && string->str[0] == ' ')
|
|
528 g_string_erase(string, 0, 1);
|
|
529
|
|
530 while (string->len > 0 && string->str[string->len - 1] == ' ')
|
|
531 g_string_erase(string, string->len - 1, 1);
|
|
532 }
|
|
533
|
|
534 static void
|
|
535 strip_lower_string(GString *string)
|
|
536 {
|
|
537 gchar *lower;
|
|
538 strip_string(string);
|
|
539
|
|
540 lower = g_ascii_strdown(string->str, -1);
|
|
541 g_free(string->str);
|
|
542 string->str = lower;
|
|
543 }
|
|
544
|
|
545 static void
|
|
546 close_ini_file_free_value(gpointer value)
|
|
547 {
|
|
548 g_free((gchar*)value);
|
|
549 }
|
|
550
|
|
551 static void
|
|
552 close_ini_file_free_section(gpointer section)
|
|
553 {
|
|
554 g_hash_table_destroy((GHashTable*)section);
|
|
555 }
|
|
556
|
|
557 INIFile *
|
|
558 open_ini_file(const gchar *filename)
|
|
559 {
|
|
560 GHashTable *ini_file = NULL;
|
|
561 GHashTable *section = NULL;
|
|
562 GString *section_name, *key_name, *value;
|
|
563 gpointer section_hash, key_hash;
|
|
564 gchar *buffer = NULL;
|
|
565 gsize off = 0;
|
|
566 gsize filesize = 0;
|
|
567
|
|
568 unsigned char x[] = { 0xff, 0xfe, 0x00 };
|
|
569
|
|
570 g_return_val_if_fail(filename, NULL);
|
|
571 aud_vfs_file_get_contents(filename, &buffer, &filesize);
|
|
572 if (buffer == NULL)
|
|
573 return NULL;
|
|
574
|
|
575 /*
|
|
576 * Convert UTF-16 into something useful. Original implementation
|
|
577 * by incomp@#audacious. Cleanups \nenolod
|
|
578 * FIXME: can't we use a GLib function for that? -- 01mf02
|
|
579 */
|
|
580 if (filesize > 2 && !memcmp(&buffer[0],&x,2))
|
|
581 {
|
|
582 gchar *outbuf = g_malloc (filesize); /* it's safe to waste memory. */
|
|
583 guint counter;
|
|
584
|
|
585 for (counter = 2; counter < filesize; counter += 2)
|
|
586 {
|
|
587 if (!memcmp(&buffer[counter+1], &x[2], 1)) {
|
|
588 outbuf[(counter-2)/2] = buffer[counter];
|
|
589 } else {
|
|
590 g_free(buffer);
|
|
591 g_free(outbuf);
|
|
592 return NULL;
|
|
593 }
|
|
594 }
|
|
595
|
|
596 outbuf[(counter-2)/2] = '\0';
|
|
597
|
|
598 if ((filesize - 2) / 2 == (counter - 2) / 2)
|
|
599 {
|
|
600 g_free(buffer);
|
|
601 buffer = outbuf;
|
|
602 }
|
|
603 else
|
|
604 {
|
|
605 g_free(buffer);
|
|
606 g_free(outbuf);
|
|
607 return NULL; /* XXX wrong encoding */
|
|
608 }
|
|
609 }
|
|
610
|
|
611 section_name = g_string_new("");
|
|
612 key_name = g_string_new(NULL);
|
|
613 value = g_string_new(NULL);
|
|
614
|
|
615 ini_file = g_hash_table_new_full(NULL, NULL, NULL,
|
|
616 close_ini_file_free_section);
|
|
617 section = g_hash_table_new_full(NULL, NULL, NULL,
|
|
618 close_ini_file_free_value);
|
|
619 /* make a nameless section which should store all entries that are not
|
|
620 * embedded in a section */
|
|
621 section_hash = GINT_TO_POINTER(g_string_hash(section_name));
|
|
622 g_hash_table_insert(ini_file, section_hash, section);
|
|
623
|
|
624 while (off < filesize)
|
|
625 {
|
|
626 /* ignore the following characters */
|
|
627 if (buffer[off] == '\r' || buffer[off] == '\n' ||
|
|
628 buffer[off] == ' ' || buffer[off] == '\t')
|
|
629 {
|
|
630 if (buffer[off] == '\n')
|
|
631 {
|
|
632 g_string_free(key_name, TRUE);
|
|
633 g_string_free(value, TRUE);
|
|
634 key_name = g_string_new(NULL);
|
|
635 value = g_string_new(NULL);
|
|
636 }
|
|
637
|
|
638 off++;
|
|
639 continue;
|
|
640 }
|
|
641
|
|
642 /* if we encounter a possible section statement */
|
|
643 if (buffer[off] == '[')
|
|
644 {
|
|
645 g_string_free(section_name, TRUE);
|
|
646 section_name = g_string_new(NULL);
|
|
647 off++;
|
|
648
|
|
649 if (off >= filesize)
|
|
650 goto return_sequence;
|
|
651
|
|
652 while (buffer[off] != ']')
|
|
653 {
|
|
654 /* if the section statement has not been closed before a
|
|
655 * linebreak */
|
|
656 if (buffer[off] == '\n')
|
|
657 break;
|
|
658
|
|
659 g_string_append_c(section_name, buffer[off]);
|
|
660 off++;
|
|
661 if (off >= filesize)
|
|
662 goto return_sequence;
|
|
663 }
|
|
664 if (buffer[off] == '\n')
|
|
665 continue;
|
|
666 if (buffer[off] == ']')
|
|
667 {
|
|
668 off++;
|
|
669 if (off >= filesize)
|
|
670 goto return_sequence;
|
|
671
|
|
672 strip_lower_string(section_name);
|
|
673 section_hash = GINT_TO_POINTER(g_string_hash(section_name));
|
|
674
|
|
675 /* if this section already exists, we don't make a new one,
|
|
676 * but reuse the old one */
|
|
677 if (g_hash_table_lookup(ini_file, section_hash) != NULL)
|
|
678 section = g_hash_table_lookup(ini_file, section_hash);
|
|
679 else
|
|
680 {
|
|
681 section = g_hash_table_new_full(NULL, NULL, NULL,
|
|
682 close_ini_file_free_value);
|
|
683 g_hash_table_insert(ini_file, section_hash, section);
|
|
684 }
|
|
685
|
|
686 continue;
|
|
687 }
|
|
688 }
|
|
689
|
|
690 if (buffer[off] == '=')
|
|
691 {
|
|
692 off++;
|
|
693 if (off >= filesize)
|
|
694 goto return_sequence;
|
|
695
|
|
696 while (buffer[off] != '\n' && buffer[off] != '\r')
|
|
697 {
|
|
698 g_string_append_c(value, buffer[off]);
|
|
699 off++;
|
|
700 if (off >= filesize)
|
|
701 break;
|
|
702 }
|
|
703
|
|
704 strip_lower_string(key_name);
|
|
705 key_hash = GINT_TO_POINTER(g_string_hash(key_name));
|
|
706 strip_string(value);
|
|
707
|
|
708 if (key_name->len > 0 && value->len > 0)
|
|
709 g_hash_table_insert(section, key_hash, g_strdup(value->str));
|
|
710 }
|
|
711 else
|
|
712 {
|
|
713 g_string_append_c(key_name, buffer[off]);
|
|
714 off++;
|
|
715 if (off >= filesize)
|
|
716 goto return_sequence;
|
|
717 }
|
|
718 }
|
|
719
|
|
720 return_sequence:
|
|
721 g_string_free(section_name, TRUE);
|
|
722 g_string_free(key_name, TRUE);
|
|
723 g_string_free(value, TRUE);
|
|
724 g_free(buffer);
|
|
725 return ini_file;
|
|
726 }
|
|
727
|
|
728 /**
|
|
729 * Frees the memory allocated for inifile.
|
|
730 */
|
|
731 void
|
|
732 close_ini_file(INIFile *inifile)
|
|
733 {
|
|
734 g_return_if_fail(inifile);
|
|
735 g_hash_table_destroy(inifile);
|
|
736 }
|
|
737
|
|
738 /**
|
|
739 * Returns a string that corresponds to correct section and key in inifile.
|
|
740 *
|
|
741 * Returns NULL if value was not found in inifile. Otherwise returns a copy
|
|
742 * of string pointed by "section" and "key". Returned string should be freed
|
|
743 * after use.
|
|
744 */
|
|
745 gchar *
|
|
746 read_ini_string(INIFile *inifile, const gchar *section, const gchar *key)
|
|
747 {
|
|
748 GString *section_string;
|
|
749 GString *key_string;
|
|
750 gchar *value = NULL;
|
|
751 gpointer section_hash, key_hash;
|
|
752 GHashTable *section_table;
|
|
753
|
|
754 g_return_val_if_fail(inifile, NULL);
|
|
755
|
|
756 section_string = g_string_new(section);
|
|
757 key_string = g_string_new(key);
|
|
758 value = NULL;
|
|
759
|
|
760 strip_lower_string(section_string);
|
|
761 strip_lower_string(key_string);
|
|
762 section_hash = GINT_TO_POINTER(g_string_hash(section_string));
|
|
763 key_hash = GINT_TO_POINTER(g_string_hash(key_string));
|
|
764 section_table = g_hash_table_lookup(inifile, section_hash);
|
|
765
|
|
766 if (section_table) {
|
|
767 value = g_strdup(g_hash_table_lookup(section_table,
|
|
768 GINT_TO_POINTER(key_hash)));
|
|
769 }
|
|
770
|
|
771 g_string_free(section_string, TRUE);
|
|
772 g_string_free(key_string, TRUE);
|
|
773
|
|
774 g_return_val_if_fail(value, NULL);
|
|
775 return value;
|
|
776 }
|
|
777
|
|
778 GArray *
|
|
779 read_ini_array(INIFile *inifile, const gchar *section, const gchar *key)
|
|
780 {
|
|
781 gchar *temp;
|
|
782 GArray *a;
|
|
783
|
|
784 g_return_val_if_fail((temp = read_ini_string(inifile, section, key)), NULL);
|
|
785
|
|
786 a = string_to_garray(temp);
|
|
787 g_free(temp);
|
|
788 return a;
|
|
789 }
|
|
790
|
|
791 GArray *
|
|
792 string_to_garray(const gchar * str)
|
|
793 {
|
|
794 GArray *array;
|
|
795 gint temp;
|
|
796 const gchar *ptr = str;
|
|
797 gchar *endptr;
|
|
798
|
|
799 array = g_array_new(FALSE, TRUE, sizeof(gint));
|
|
800 for (;;) {
|
|
801 temp = strtol(ptr, &endptr, 10);
|
|
802 if (ptr == endptr)
|
|
803 break;
|
|
804 g_array_append_val(array, temp);
|
|
805 ptr = endptr;
|
|
806 while (!isdigit((int) *ptr) && (*ptr) != '\0')
|
|
807 ptr++;
|
|
808 if (*ptr == '\0')
|
|
809 break;
|
|
810 }
|
|
811 return (array);
|
|
812 }
|
|
813
|
|
814 void
|
|
815 glist_movedown(GList * list)
|
|
816 {
|
|
817 gpointer temp;
|
|
818
|
|
819 if (g_list_next(list)) {
|
|
820 temp = list->data;
|
|
821 list->data = list->next->data;
|
|
822 list->next->data = temp;
|
|
823 }
|
|
824 }
|
|
825
|
|
826 void
|
|
827 glist_moveup(GList * list)
|
|
828 {
|
|
829 gpointer temp;
|
|
830
|
|
831 if (g_list_previous(list)) {
|
|
832 temp = list->data;
|
|
833 list->data = list->prev->data;
|
|
834 list->prev->data = temp;
|
|
835 }
|
|
836 }
|
|
837
|
|
838 GdkFont *
|
|
839 util_font_load(const gchar * name)
|
|
840 {
|
|
841 GdkFont *font;
|
|
842 PangoFontDescription *desc;
|
|
843
|
|
844 desc = pango_font_description_from_string(name);
|
|
845 font = gdk_font_from_description(desc);
|
|
846
|
|
847 return font;
|
|
848 }
|
2586
|
849
|
2573
|
850 /* text_get_extents() taken from The GIMP (C) Spencer Kimball, Peter
|
|
851 * Mattis et al */
|
|
852 gboolean
|
|
853 text_get_extents(const gchar * fontname,
|
|
854 const gchar * text,
|
|
855 gint * width, gint * height, gint * ascent, gint * descent)
|
|
856 {
|
|
857 PangoFontDescription *font_desc;
|
|
858 PangoLayout *layout;
|
|
859 PangoRectangle rect;
|
|
860
|
|
861 g_return_val_if_fail(fontname != NULL, FALSE);
|
|
862 g_return_val_if_fail(text != NULL, FALSE);
|
|
863
|
|
864 /* FIXME: resolution */
|
|
865 layout = gtk_widget_create_pango_layout(GTK_WIDGET(mainwin), text);
|
|
866
|
|
867 font_desc = pango_font_description_from_string(fontname);
|
|
868 pango_layout_set_font_description(layout, font_desc);
|
|
869 pango_font_description_free(font_desc);
|
|
870 pango_layout_get_pixel_extents(layout, NULL, &rect);
|
|
871
|
|
872 if (width)
|
|
873 *width = rect.width;
|
|
874 if (height)
|
|
875 *height = rect.height;
|
|
876
|
|
877 if (ascent || descent) {
|
|
878 PangoLayoutIter *iter;
|
|
879 PangoLayoutLine *line;
|
|
880
|
|
881 iter = pango_layout_get_iter(layout);
|
|
882 line = pango_layout_iter_get_line(iter);
|
|
883 pango_layout_iter_free(iter);
|
|
884
|
|
885 pango_layout_line_get_pixel_extents(line, NULL, &rect);
|
|
886
|
|
887 if (ascent)
|
|
888 *ascent = PANGO_ASCENT(rect);
|
|
889 if (descent)
|
|
890 *descent = -PANGO_DESCENT(rect);
|
|
891 }
|
|
892
|
|
893 g_object_unref(layout);
|
|
894
|
|
895 return TRUE;
|
|
896 }
|
2586
|
897
|
2573
|
898 /* counts number of digits in a gint */
|
|
899 guint
|
|
900 gint_count_digits(gint n)
|
|
901 {
|
|
902 guint count = 0;
|
|
903
|
|
904 n = ABS(n);
|
|
905 do {
|
|
906 count++;
|
|
907 n /= 10;
|
|
908 } while (n > 0);
|
|
909
|
|
910 return count;
|
|
911 }
|
|
912
|
|
913 gboolean
|
|
914 dir_foreach(const gchar * path, DirForeachFunc function,
|
|
915 gpointer user_data, GError ** error)
|
|
916 {
|
|
917 GError *error_out = NULL;
|
|
918 GDir *dir;
|
|
919 const gchar *entry;
|
|
920 gchar *entry_fullpath;
|
|
921
|
|
922 if (!(dir = g_dir_open(path, 0, &error_out))) {
|
|
923 g_propagate_error(error, error_out);
|
|
924 return FALSE;
|
|
925 }
|
|
926
|
|
927 while ((entry = g_dir_read_name(dir))) {
|
|
928 entry_fullpath = g_build_filename(path, entry, NULL);
|
|
929
|
|
930 if ((*function) (entry_fullpath, entry, user_data)) {
|
|
931 g_free(entry_fullpath);
|
|
932 break;
|
|
933 }
|
|
934
|
|
935 g_free(entry_fullpath);
|
|
936 }
|
|
937
|
|
938 g_dir_close(dir);
|
|
939
|
|
940 return TRUE;
|
|
941 }
|
|
942
|
|
943 GtkWidget *
|
|
944 make_filebrowser(const gchar *title, gboolean save)
|
|
945 {
|
|
946 GtkWidget *dialog;
|
|
947 GtkWidget *button;
|
|
948
|
|
949 g_return_val_if_fail(title != NULL, NULL);
|
|
950
|
|
951 dialog = gtk_file_chooser_dialog_new(title, GTK_WINDOW(mainwin),
|
|
952 save ?
|
|
953 GTK_FILE_CHOOSER_ACTION_SAVE :
|
|
954 GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
955 NULL, NULL);
|
|
956
|
|
957 button = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL,
|
|
958 GTK_RESPONSE_REJECT);
|
|
959
|
|
960 gtk_button_set_use_stock(GTK_BUTTON(button), TRUE);
|
|
961 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
|
|
962
|
|
963 button = gtk_dialog_add_button(GTK_DIALOG(dialog), save ?
|
|
964 GTK_STOCK_SAVE : GTK_STOCK_OPEN,
|
|
965 GTK_RESPONSE_ACCEPT);
|
|
966
|
|
967 gtk_button_set_use_stock(GTK_BUTTON(button), TRUE);
|
|
968 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
|
|
969 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); /* centering */
|
|
970
|
|
971 return dialog;
|
|
972 }
|
|
973
|
|
974 /**
|
|
975 * util_info_dialog:
|
|
976 * @title: The title of the message to show.
|
|
977 * @text: The text of the message to show.
|
|
978 * @button_text: The text of the button which will close the messagebox.
|
|
979 * @modal: Whether or not the messagebox should be modal.
|
|
980 * @button_action: Code to execute on when the messagebox is closed, or %NULL.
|
|
981 * @action_data: Optional opaque data to pass to @button_action.
|
|
982 *
|
|
983 * Displays a message box.
|
|
984 *
|
|
985 * Return value: A GTK widget handle for the message box.
|
|
986 **/
|
|
987 GtkWidget *
|
|
988 util_info_dialog(const gchar * title, const gchar * text,
|
|
989 const gchar * button_text, gboolean modal,
|
|
990 GCallback button_action, gpointer action_data)
|
|
991 {
|
|
992 GtkWidget *dialog;
|
|
993 GtkWidget *dialog_vbox, *dialog_hbox, *dialog_bbox;
|
|
994 GtkWidget *dialog_bbox_b1;
|
|
995 GtkWidget *dialog_textlabel;
|
|
996 GtkWidget *dialog_icon;
|
|
997
|
|
998 dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
999 gtk_window_set_type_hint( GTK_WINDOW(dialog) , GDK_WINDOW_TYPE_HINT_DIALOG );
|
|
1000 gtk_window_set_modal( GTK_WINDOW(dialog) , modal );
|
|
1001 gtk_window_set_title( GTK_WINDOW(dialog) , title );
|
|
1002 gtk_container_set_border_width( GTK_CONTAINER(dialog) , 10 );
|
|
1003
|
|
1004 dialog_vbox = gtk_vbox_new( FALSE , 0 );
|
|
1005 dialog_hbox = gtk_hbox_new( FALSE , 0 );
|
|
1006
|
|
1007 /* icon */
|
|
1008 dialog_icon = gtk_image_new_from_stock( GTK_STOCK_DIALOG_INFO , GTK_ICON_SIZE_DIALOG );
|
|
1009 gtk_box_pack_start( GTK_BOX(dialog_hbox) , dialog_icon , FALSE , FALSE , 2 );
|
|
1010
|
|
1011 /* label */
|
|
1012 dialog_textlabel = gtk_label_new( text );
|
|
1013 /* gtk_label_set_selectable( GTK_LABEL(dialog_textlabel) , TRUE ); */
|
|
1014 gtk_box_pack_start( GTK_BOX(dialog_hbox) , dialog_textlabel , TRUE , TRUE , 2 );
|
|
1015
|
|
1016 gtk_box_pack_start( GTK_BOX(dialog_vbox) , dialog_hbox , FALSE , FALSE , 2 );
|
|
1017 gtk_box_pack_start( GTK_BOX(dialog_vbox) , gtk_hseparator_new() , FALSE , FALSE , 4 );
|
|
1018
|
|
1019 dialog_bbox = gtk_hbutton_box_new();
|
|
1020 gtk_button_box_set_layout( GTK_BUTTON_BOX(dialog_bbox) , GTK_BUTTONBOX_END );
|
|
1021 dialog_bbox_b1 = gtk_button_new_with_label( button_text );
|
|
1022 g_signal_connect_swapped( G_OBJECT(dialog_bbox_b1) , "clicked" ,
|
|
1023 G_CALLBACK(gtk_widget_destroy) , dialog );
|
|
1024 if ( button_action )
|
|
1025 g_signal_connect( G_OBJECT(dialog_bbox_b1) , "clicked" ,
|
|
1026 button_action , action_data );
|
|
1027
|
|
1028 gtk_container_add( GTK_CONTAINER(dialog_bbox) , dialog_bbox_b1 );
|
|
1029 gtk_box_pack_start( GTK_BOX(dialog_vbox) , dialog_bbox , FALSE , FALSE , 0 );
|
|
1030
|
|
1031 gtk_container_add( GTK_CONTAINER(dialog) , dialog_vbox );
|
|
1032
|
|
1033 GTK_WIDGET_SET_FLAGS( dialog_bbox_b1 , GTK_CAN_DEFAULT);
|
|
1034 gtk_widget_grab_default( dialog_bbox_b1 );
|
|
1035
|
|
1036 gtk_widget_show_all(dialog);
|
|
1037
|
|
1038 return dialog;
|
|
1039 }
|
|
1040
|
|
1041
|
|
1042 /**
|
|
1043 * util_get_localdir:
|
|
1044 *
|
|
1045 * Returns a string with the full path of Audacious local datadir (where config files are placed).
|
|
1046 * It's useful in order to put in the right place custom config files for audacious plugins.
|
|
1047 *
|
|
1048 * Return value: a string with full path of Audacious local datadir (should be freed after use)
|
|
1049 **/
|
|
1050 gchar*
|
|
1051 util_get_localdir(void)
|
|
1052 {
|
|
1053 gchar *datadir;
|
|
1054 gchar *tmp;
|
|
1055
|
|
1056 if ( (tmp = getenv("XDG_CONFIG_HOME")) == NULL )
|
|
1057 datadir = g_build_filename( g_get_home_dir() , ".config" , "audacious" , NULL );
|
|
1058 else
|
|
1059 datadir = g_build_filename( tmp , "audacious" , NULL );
|
|
1060
|
|
1061 return datadir;
|
|
1062 }
|
|
1063
|
|
1064
|
|
1065 gchar *
|
|
1066 construct_uri(gchar *string, const gchar *playlist_name) // uri, path and anything else
|
|
1067 {
|
|
1068 gchar *filename = g_strdup(string);
|
|
1069 gchar *tmp, *path;
|
|
1070 gchar *uri = NULL;
|
|
1071
|
|
1072 /* try to translate dos path */
|
|
1073 convert_dos_path(filename); /* in place replacement */
|
|
1074
|
|
1075 // make full path uri here
|
|
1076 // case 1: filename is raw full path or uri
|
|
1077 if (filename[0] == '/' || strstr(filename, "://")) {
|
|
1078 uri = g_filename_to_uri(filename, NULL, NULL);
|
|
1079 if(!uri) {
|
|
1080 uri = g_strdup(filename);
|
|
1081 }
|
|
1082 g_free(filename);
|
|
1083 }
|
|
1084 // case 2: filename is not raw full path nor uri, playlist path is full path
|
|
1085 // make full path by replacing last part of playlist path with filename. (using g_build_filename)
|
|
1086 else if (playlist_name[0] == '/' || strstr(playlist_name, "://")) {
|
|
1087 path = g_filename_from_uri(playlist_name, NULL, NULL);
|
|
1088 if (!path) {
|
|
1089 path = g_strdup(playlist_name);
|
|
1090 }
|
|
1091 tmp = strrchr(path, '/'); *tmp = '\0';
|
|
1092 tmp = g_build_filename(path, filename, NULL);
|
|
1093 g_free(path); g_free(filename);
|
|
1094 uri = g_filename_to_uri(tmp, NULL, NULL);
|
|
1095 g_free(tmp);
|
|
1096 }
|
|
1097 // case 3: filename is not raw full path nor uri, playlist path is not full path
|
|
1098 // just abort.
|
|
1099 else {
|
|
1100 g_free(filename);
|
|
1101 return NULL;
|
|
1102 }
|
|
1103
|
|
1104 AUDDBG("uri=%s\n", uri);
|
|
1105 return uri;
|
|
1106 }
|
|
1107
|
|
1108 /*
|
|
1109 * minimize number of realloc's:
|
|
1110 * - set N to nearest power of 2 not less then N
|
|
1111 * - double it
|
|
1112 *
|
|
1113 * -- asphyx
|
|
1114 *
|
|
1115 * XXX: what's so smart about this?? seems wasteful and silly. --nenolod
|
|
1116 */
|
|
1117 gpointer
|
|
1118 smart_realloc(gpointer ptr, gsize *size)
|
|
1119 {
|
|
1120 *size = (size_t)pow(2, ceil(log(*size) / log(2)) + 1);
|
|
1121 if (ptr != NULL) free(ptr);
|
|
1122 ptr = malloc(*size);
|
|
1123 return ptr;
|
|
1124 }
|
|
1125
|
|
1126 void
|
|
1127 make_directory(const gchar * path, mode_t mode)
|
|
1128 {
|
|
1129 if (g_mkdir_with_parents(path, mode) == 0)
|
|
1130 return;
|
|
1131
|
|
1132 g_printerr(_("Could not create directory (%s): %s\n"), path,
|
|
1133 g_strerror(errno));
|
|
1134 }
|