diff options
author | Alessio Treglia <alessio@debian.org> | 2014-06-12 12:30:32 +0100 |
---|---|---|
committer | Alessio Treglia <alessio@debian.org> | 2014-06-12 12:30:32 +0100 |
commit | 260690bc6b904d1ca0025e539b9663ddb9fd2424 (patch) | |
tree | 6638a0254217dc8443bdd4ff00a5d78c5a3da556 /src/libaudcore | |
parent | b380f841f764543f477b658af25194c4d9f8a1eb (diff) |
Imported Upstream version 3.5
Diffstat (limited to 'src/libaudcore')
30 files changed, 2144 insertions, 1089 deletions
diff --git a/src/libaudcore/Makefile b/src/libaudcore/Makefile index c823ae7..8f380f7 100644 --- a/src/libaudcore/Makefile +++ b/src/libaudcore/Makefile @@ -1,25 +1,33 @@ SHARED_LIB = ${LIB_PREFIX}audcore${LIB_SUFFIX} -LIB_MAJOR = 1 +LIB_MAJOR = 2 LIB_MINOR = 0 SRCS = audio.c \ audstrings.c \ + charset.c \ eventqueue.c \ hook.c \ index.c \ + inifile.c \ + multihash.c \ strpool.c \ + tinylock.c \ tuple.c \ tuple_compiler.c \ tuple_formatter.c \ vfs.c \ vfs_async.c \ - vfs_common.c + vfs_common.c \ + vfs_local.c INCLUDES = audio.h \ audstrings.h \ core.h \ hook.h \ index.h \ + inifile.h \ + multihash.h \ + tinylock.h \ tuple.h \ vfs.h \ vfs_async.h @@ -32,7 +40,10 @@ includesubdir = libaudcore CPPFLAGS := -I.. -I../.. \ ${CPPFLAGS} \ ${GLIB_CFLAGS} \ + ${LIBGUESS_CFLAGS} CFLAGS += ${LIB_CFLAGS} -LIBS += ${GLIB_LIBS} -lm +LIBS += -lm \ + ${GLIB_LIBS} \ + ${LIBGUESS_LIBS} diff --git a/src/libaudcore/audio.c b/src/libaudcore/audio.c index f866509..25246be 100644 --- a/src/libaudcore/audio.c +++ b/src/libaudcore/audio.c @@ -1,6 +1,6 @@ /* * audio.c - * Copyright 2009-2012 John Lindgren, Michał Lipski, and Anders Johansson + * Copyright 2009-2013 John Lindgren, Michał Lipski, and Anders Johansson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -22,6 +22,7 @@ #include <math.h> #include "audio.h" +#include "core.h" #define INTERLACE_LOOP(TYPE) \ for (int c = 0; c < channels; c ++) \ @@ -173,7 +174,7 @@ EXPORT void audio_from_int (const void * in, int format, float * out, int sample { int entry; - for (entry = 0; entry < G_N_ELEMENTS (convert_table); entry ++) + for (entry = 0; entry < ARRAY_LEN (convert_table); entry ++) { if (convert_table[entry].format == format) { @@ -187,7 +188,7 @@ EXPORT void audio_to_int (const float * in, void * out, int format, int samples) { int entry; - for (entry = 0; entry < G_N_ELEMENTS (convert_table); entry ++) + for (entry = 0; entry < ARRAY_LEN (convert_table); entry ++) { if (convert_table[entry].format == format) { diff --git a/src/libaudcore/audio.h.in b/src/libaudcore/audio.h.in index 19cfef2..2e9dd5a 100644 --- a/src/libaudcore/audio.h.in +++ b/src/libaudcore/audio.h.in @@ -1,6 +1,6 @@ /* * audio.h - * Copyright 2009-2011 John Lindgren + * Copyright 2009-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/libaudcore/audstrings.c b/src/libaudcore/audstrings.c index eb4b8f1..c1d878b 100644 --- a/src/libaudcore/audstrings.c +++ b/src/libaudcore/audstrings.c @@ -1,7 +1,6 @@ /* * audstrings.c - * Copyright 2009-2011 John Lindgren - * Copyright 2010 William Pitcock + * Copyright 2009-2012 John Lindgren and William Pitcock * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -22,19 +21,67 @@ #include <math.h> #include <stdio.h> #include <stdlib.h> -#include <glib.h> #include <string.h> -#include <ctype.h> -#include <locale.h> + +#include <glib.h> #include <audacious/i18n.h> #include "audstrings.h" +#include "index.h" + +static const char ascii_to_hex[256] = { + ['0'] = 0x0, ['1'] = 0x1, ['2'] = 0x2, ['3'] = 0x3, ['4'] = 0x4, + ['5'] = 0x5, ['6'] = 0x6, ['7'] = 0x7, ['8'] = 0x8, ['9'] = 0x9, + ['a'] = 0xa, ['b'] = 0xb, ['c'] = 0xc, ['d'] = 0xd, ['e'] = 0xe, ['f'] = 0xf, + ['A'] = 0xa, ['B'] = 0xb, ['C'] = 0xc, ['D'] = 0xd, ['E'] = 0xe, ['F'] = 0xf +}; + +static const char hex_to_ascii[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static const char uri_legal_table[256] = { + ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1, + ['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1, + ['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1, ['g'] = 1, + ['h'] = 1, ['i'] = 1, ['j'] = 1, ['k'] = 1, ['l'] = 1, ['m'] = 1, ['n'] = 1, + ['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1, ['s'] = 1, ['t'] = 1, ['u'] = 1, + ['v'] = 1, ['w'] = 1, ['x'] = 1, ['y'] = 1, ['z'] = 1, + ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1, ['G'] = 1, + ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1, ['M'] = 1, ['N'] = 1, + ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1, ['U'] = 1, + ['V'] = 1, ['W'] = 1, ['X'] = 1, ['Y'] = 1, ['Z'] = 1, + ['-'] = 1, ['_'] = 1, ['.'] = 1, ['~'] = 1, ['/'] = 1 +}; + +static const char swap_case[256] = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" + "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ\0\0\0\0\0"; + +#define FROM_HEX(c) (ascii_to_hex[(unsigned char) (c)]) +#define TO_HEX(i) (hex_to_ascii[(i) & 15]) +#define IS_LEGAL(c) (uri_legal_table[(unsigned char) (c)]) +#define SWAP_CASE(c) (swap_case[(unsigned char) (c)]) + +EXPORT char * str_printf (const char * format, ...) +{ + va_list args; + va_start (args, format); + + char * str = str_vprintf (format, args); -#define FROM_HEX(c) ((c) < 'A' ? (c) - '0' : (c) < 'a' ? 10 + (c) - 'A' : 10 + (c) - 'a') -#define TO_HEX(i) ((i) < 10 ? '0' + (i) : 'A' + (i) - 10) -#define IS_LEGAL(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z') \ - || ((c) >= '0' && (c) <= '9') || (strchr ("-_.~/", (c)))) + va_end (args); + return str; +} + +EXPORT char * str_vprintf (const char * format, va_list args) +{ + VSPRINTF (buf, format, args); + return str_get (buf); +} EXPORT bool_t str_has_prefix_nocase (const char * str, const char * prefix) { @@ -52,60 +99,154 @@ EXPORT bool_t str_has_suffix_nocase (const char * str, const char * suffix) return ! g_ascii_strcasecmp (str + len1 - len2, suffix); } -static char * (* str_to_utf8_impl) (const char *) = NULL; -static char * (* str_to_utf8_full_impl) (const char *, int, int *, int *) = NULL; - -EXPORT void str_set_utf8_impl (char * (* stu_impl) (const char *), - char * (* stuf_impl) (const char *, int, int *, int *)) +EXPORT char * strstr_nocase (const char * haystack, const char * needle) { - str_to_utf8_impl = stu_impl; - str_to_utf8_full_impl = stuf_impl; + while (1) + { + const char * ap = haystack; + const char * bp = needle; + + while (1) + { + char a = * ap ++; + char b = * bp ++; + + if (! b) /* all of needle matched */ + return (char *) haystack; + if (! a) /* end of haystack reached */ + return NULL; + + if (a != b && a != SWAP_CASE (b)) + break; + } + + haystack ++; + } } -EXPORT char * str_to_utf8 (const char * str) +EXPORT char * strstr_nocase_utf8 (const char * haystack, const char * needle) { - g_return_val_if_fail (str_to_utf8_impl, NULL); - return str_to_utf8_impl (str); + while (1) + { + const char * ap = haystack; + const char * bp = needle; + + while (1) + { + gunichar a = g_utf8_get_char (ap); + gunichar b = g_utf8_get_char (bp); + + if (! b) /* all of needle matched */ + return (char *) haystack; + if (! a) /* end of haystack reached */ + return NULL; + + if (a != b && (a < 128 ? SWAP_CASE (a) != b : + g_unichar_tolower (a) != g_unichar_tolower (b))) + break; + + ap = g_utf8_next_char (ap); + bp = g_utf8_next_char (bp); + } + + haystack = g_utf8_next_char (haystack); + } } -EXPORT char * str_to_utf8_full (const char * str, int len, int * bytes_read, int * bytes_written) +EXPORT char * str_tolower_utf8 (const char * str) { - g_return_val_if_fail (str_to_utf8_full_impl, NULL); - return str_to_utf8_full_impl (str, len, bytes_read, bytes_written); + char buf[6 * strlen (str) + 1]; + const char * get = str; + char * set = buf; + gunichar c; + + while ((c = g_utf8_get_char (get))) + { + if (c < 128) + * set ++ = g_ascii_tolower (c); + else + set += g_unichar_to_utf8 (g_unichar_tolower (c), set); + + get = g_utf8_next_char (get); + } + + * set = 0; + + return str_get (buf); } -EXPORT void string_replace_char (char * string, char old_c, char new_c) +EXPORT void str_replace_char (char * string, char old_c, char new_c) { while ((string = strchr (string, old_c))) * string ++ = new_c; } +EXPORT void str_itoa (int x, char * buf, int bufsize) +{ + if (! bufsize) + return; + + if (x < 0) + { + if (bufsize > 1) + { + * buf ++ = '-'; + bufsize --; + } + + x = -x; + } + + char * rev = buf + bufsize - 1; + * rev = 0; + + while (rev > buf) + { + * (-- rev) = '0' + x % 10; + if (! (x /= 10)) + break; + } + + while ((* buf ++ = * rev ++)); +} + /* Percent-decodes up to <len> bytes of <str> to <out>, which must be large * enough to hold the decoded string (i.e., (len + 1) bytes). If <len> is * negative, decodes all of <str>. */ EXPORT void str_decode_percent (const char * str, int len, char * out) { + const char * nul; + if (len < 0) - len = INT_MAX; + len = strlen (str); + else if ((nul = memchr (str, 0, len))) + len = nul - str; - while (len --) + while (1) { - char c = * str ++; - if (! c) + const char * p = memchr (str, '%', len); + if (! p) break; - if (c == '%' && len >= 2 && str[0] && str[1]) - { - c = (FROM_HEX (str[0]) << 4) | FROM_HEX (str[1]); - str += 2; - len -= 2; - } + int block = p - str; + memmove (out, str, block); + + str += block; + out += block; + len -= block; + + if (len < 3) + break; + + * out ++ = (FROM_HEX (str[1]) << 4) | FROM_HEX (str[2]); - * out ++ = c; + str += 3; + len -= 3; } - * out = 0; + memmove (out, str, len); + out[len] = 0; } /* Percent-encodes up to <len> bytes of <str> to <out>, which must be large @@ -114,14 +255,16 @@ EXPORT void str_decode_percent (const char * str, int len, char * out) EXPORT void str_encode_percent (const char * str, int len, char * out) { + const char * nul; + if (len < 0) - len = INT_MAX; + len = strlen (str); + else if ((nul = memchr (str, 0, len))) + len = nul - str; while (len --) { char c = * str ++; - if (! c) - break; if (IS_LEGAL (c)) * out ++ = c; @@ -136,62 +279,101 @@ EXPORT void str_encode_percent (const char * str, int len, char * out) * out = 0; } -/* Like g_filename_to_uri, but converts the filename from the system locale to - * UTF-8 before percent-encoding. On Windows, replaces '\' with '/' and adds a - * leading '/'. */ +EXPORT void filename_normalize (char * filename) +{ +#ifdef _WIN32 + /* convert slash to backslash on Windows */ + str_replace_char (filename, '/', '\\'); +#endif -EXPORT char * filename_to_uri (const char * name) + /* remove trailing slash */ + int len = strlen (filename); +#ifdef _WIN32 + if (len > 3 && filename[len - 1] == '\\') /* leave "C:\" */ +#else + if (len > 1 && filename[len - 1] == '/') /* leave leading "/" */ +#endif + filename[len - 1] = 0; +} + +EXPORT char * filename_build (const char * path, const char * name) { - char * utf8 = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); - if (! utf8) + int len = strlen (path); + +#ifdef _WIN32 + if (! len || path[len - 1] == '/' || path[len - 1] == '\\') { - const char * locale = setlocale (LC_ALL, NULL); - fprintf (stderr, "Cannot convert filename from system locale (%s): %s\n", locale, name); - return NULL; + SCONCAT2 (filename, path, name); + return str_get (filename); + } + + SCONCAT3 (filename, path, "\\", name); + return str_get (filename); +#else + if (! len || path[len - 1] == '/') + { + SCONCAT2 (filename, path, name); + return str_get (filename); } + SCONCAT3 (filename, path, "/", name); + return str_get (filename); +#endif +} + #ifdef _WIN32 - string_replace_char (utf8, '\\', '/'); +#define URI_PREFIX "file:///" +#define URI_PREFIX_LEN 8 +#else +#define URI_PREFIX "file://" +#define URI_PREFIX_LEN 7 #endif - char enc[3 * strlen (utf8) + 1]; - str_encode_percent (utf8, -1, enc); - g_free (utf8); +/* Like g_filename_to_uri, but converts the filename from the system locale to + * UTF-8 before percent-encoding (except on Windows, where filenames are assumed + * to be UTF-8). On Windows, replaces '\' with '/' and adds a leading '/'. */ +EXPORT char * filename_to_uri (const char * name) +{ #ifdef _WIN32 - return g_strdup_printf ("file:///%s", enc); + SCOPY (utf8, name); + str_replace_char (utf8, '\\', '/'); #else - return g_strdup_printf ("file://%s", enc); + char * utf8 = str_from_locale (name, -1); + if (! utf8) + return NULL; #endif + + char enc[URI_PREFIX_LEN + 3 * strlen (utf8) + 1]; + strcpy (enc, URI_PREFIX); + str_encode_percent (utf8, -1, enc + URI_PREFIX_LEN); + +#ifndef _WIN32 + str_unref (utf8); +#endif + + return str_get (enc); } /* Like g_filename_from_uri, but converts the filename from UTF-8 to the system - * locale after percent-decoding. On Windows, strips the leading '/' and - * replaces '/' with '\'. */ + * locale after percent-decoding (except on Windows, where filenames are assumed + * to be UTF-8). On Windows, strips the leading '/' and replaces '/' with '\'. */ EXPORT char * uri_to_filename (const char * uri) { + if (strncmp (uri, URI_PREFIX, URI_PREFIX_LEN)) + return NULL; + + char buf[strlen (uri + URI_PREFIX_LEN) + 1]; + str_decode_percent (uri + URI_PREFIX_LEN, -1, buf); + + filename_normalize (buf); + #ifdef _WIN32 - g_return_val_if_fail (! strncmp (uri, "file:///", 8), NULL); - char buf[strlen (uri + 8) + 1]; - str_decode_percent (uri + 8, -1, buf); + return str_get (buf); #else - g_return_val_if_fail (! strncmp (uri, "file://", 7), NULL); - char buf[strlen (uri + 7) + 1]; - str_decode_percent (uri + 7, -1, buf); -#endif -#ifdef _WIN32 - string_replace_char (buf, '/', '\\'); + return str_to_locale (buf, -1); #endif - - char * name = g_locale_from_utf8 (buf, -1, NULL, NULL, NULL); - if (! name) - { - const char * locale = setlocale (LC_ALL, NULL); - fprintf (stderr, "Cannot convert filename to system locale (%s): %s\n", locale, buf); - } - - return name; } /* Formats a URI for human-readable display. Percent-decodes and, for file:// @@ -200,26 +382,26 @@ EXPORT char * uri_to_filename (const char * uri) EXPORT char * uri_to_display (const char * uri) { if (! strncmp (uri, "cdda://?", 8)) - return g_strdup_printf (_("Audio CD, track %s"), uri + 8); + return str_printf (_("Audio CD, track %s"), uri + 8); char buf[strlen (uri) + 1]; -#ifdef _WIN32 - if (! strncmp (uri, "file:///", 8)) + if (! strncmp (uri, URI_PREFIX, URI_PREFIX_LEN)) { - str_decode_percent (uri + 8, -1, buf); - string_replace_char (buf, '/', '\\'); - } -#else - if (! strncmp (uri, "file://", 7)) - str_decode_percent (uri + 7, -1, buf); + str_decode_percent (uri + URI_PREFIX_LEN, -1, buf); +#ifdef _WIN32 + str_replace_char (buf, '/', '\\'); #endif + } else str_decode_percent (uri, -1, buf); - return g_strdup (buf); + return str_get (buf); } +#undef URI_PREFIX +#undef URI_PREFIX_LEN + EXPORT void uri_parse (const char * uri, const char * * base_p, const char * * ext_p, const char * * sub_p, int * isub_p) { @@ -238,9 +420,7 @@ EXPORT void uri_parse (const char * uri, const char * * base_p, const char * * e else sub = end; - char buf[sub - base + 1]; - memcpy (buf, base, sub - base); - buf[sub - base] = 0; + SNCOPY (buf, base, sub - base); if ((c = strrchr (buf, '.'))) ext = base + (c - buf); @@ -265,7 +445,8 @@ EXPORT bool_t uri_get_extension (const char * uri, char * buf, int buflen) if (ext[0] != '.') return FALSE; - g_strlcpy (buf, ext + 1, buflen); + strncpy (buf, ext + 1, buflen - 1); + buf[buflen - 1] = 0; /* remove subtunes and HTTP query strings */ char * qmark; @@ -279,7 +460,7 @@ EXPORT bool_t uri_get_extension (const char * uri, char * buf, int buflen) /* Non-ASCII characters are treated exactly as is. */ /* Handles NULL gracefully. */ -EXPORT int string_compare (const char * ap, const char * bp) +EXPORT int str_compare (const char * ap, const char * bp) { if (ap == NULL) return (bp == NULL) ? 0 : -1; @@ -321,9 +502,9 @@ EXPORT int string_compare (const char * ap, const char * bp) return 0; } -/* Decodes percent-encoded strings, then compares then with string_compare. */ +/* Decodes percent-encoded strings, then compares then with str_compare. */ -EXPORT int string_compare_encoded (const char * ap, const char * bp) +EXPORT int str_compare_encoded (const char * ap, const char * bp) { if (ap == NULL) return (bp == NULL) ? 0 : -1; @@ -376,37 +557,72 @@ EXPORT int string_compare_encoded (const char * ap, const char * bp) return 0; } -EXPORT char * -str_replace_fragment(char *s, int size, const char *old, const char *new) +EXPORT Index * str_list_to_index (const char * list, const char * delims) { - char *ptr = s; - int left = strlen(s); - int avail = size - (left + 1); - int oldlen = strlen(old); - int newlen = strlen(new); - int diff = newlen - oldlen; - - while (left >= oldlen) + char dmap[256] = {0}; + + for (; * delims; delims ++) + dmap[(unsigned char) (* delims)] = 1; + + Index * index = index_new (); + const char * word = NULL; + + for (; * list; list ++) { - if (strncmp(ptr, old, oldlen)) + if (dmap[(unsigned char) (* list)]) { - left--; - ptr++; - continue; + if (word) + { + index_insert (index, -1, str_nget (word, list - word)); + word = NULL; + } } + else + { + if (! word) + { + word = list; + } + } + } - if (diff > avail) - break; + if (word) + index_insert (index, -1, str_get (word)); - if (diff != 0) - memmove(ptr + oldlen + diff, ptr + oldlen, left + 1 - oldlen); + return index; +} + +EXPORT char * index_to_str_list (Index * index, const char * sep) +{ + int count = index_count (index); + int seplen = strlen (sep); + int total = count ? seplen * (count - 1) : 0; + int lengths[count]; - memcpy(ptr, new, newlen); - ptr += newlen; - left -= oldlen; + for (int i = 0; i < count; i ++) + { + lengths[i] = strlen (index_get (index, i)); + total += lengths[i]; } - return s; + char buf[total + 1]; + int pos = 0; + + for (int i = 0; i < count; i ++) + { + if (i) + { + strcpy (buf + pos, sep); + pos += seplen; + } + + strcpy (buf + pos, index_get (index, i)); + pos += lengths[i]; + } + + buf[pos] = 0; + + return str_get (buf); } /* @@ -420,11 +636,11 @@ str_replace_fragment(char *s, int size, const char *old, const char *new) * architecture or locale we have to deal with. * - Readability, meaning that the number one is rendered "1", not "1.000". * - * Values are limited between -1,000,000,000 and 1,000,000,000 (inclusive) and + * Values between -1,000,000,000 and 1,000,000,000 (inclusive) are guaranteed to * have an accuracy of 6 decimal places. */ -EXPORT bool_t string_to_int (const char * string, int * addr) +EXPORT int str_to_int (const char * string) { bool_t neg = (string[0] == '-'); if (neg) @@ -433,88 +649,41 @@ EXPORT bool_t string_to_int (const char * string, int * addr) int val = 0; char c; - while ((c = * string ++)) - { - if (c < '0' || c > '9' || val > 100000000) - goto ERR; - + while ((c = * string ++) && c >= '0' && c <= '9') val = val * 10 + (c - '0'); - } - - if (val > 1000000000) - goto ERR; - * addr = neg ? -val : val; - return TRUE; - -ERR: - return FALSE; + return neg ? -val : val; } -EXPORT bool_t string_to_double (const char * string, double * addr) +EXPORT double str_to_double (const char * string) { bool_t neg = (string[0] == '-'); if (neg) string ++; + double val = str_to_int (string); const char * p = strchr (string, '.'); - int i, f; if (p) { - char buf[11]; - int len; - - len = p - string; - if (len > 10) - goto ERR; - - memcpy (buf, string, len); - buf[len] = 0; - - if (! string_to_int (buf, & i)) - goto ERR; - - len = strlen (p + 1); - if (len > 6) - goto ERR; - - memcpy (buf, p + 1, len); - memset (buf + len, '0', 6 - len); - buf[6] = 0; - - if (! string_to_int (buf, & f)) - goto ERR; + char buf[7] = "000000"; + const char * nul = memchr (p + 1, 0, 6); + memcpy (buf, p + 1, nul ? nul - (p + 1) : 6); + val += (double) str_to_int (buf) / 1000000; } - else - { - if (! string_to_int (string, & i)) - goto ERR; - - f = 0; - } - - double val = i + (double) f / 1000000; - if (val > 1000000000) - goto ERR; - - * addr = neg ? -val : val; - return TRUE; -ERR: - return FALSE; + return neg ? -val : val; } -EXPORT char * int_to_string (int val) +EXPORT char * int_to_str (int val) { - g_return_val_if_fail (val >= -1000000000 && val <= 1000000000, NULL); - return g_strdup_printf ("%d", val); + char buf[16]; + str_itoa (val, buf, sizeof buf); + return str_get (buf); } -EXPORT char * double_to_string (double val) +EXPORT char * double_to_str (double val) { - g_return_val_if_fail (val >= -1000000000 && val <= 1000000000, NULL); - bool_t neg = (val < 0); if (neg) val = -val; @@ -528,94 +697,88 @@ EXPORT char * double_to_string (double val) f = 0; } - char * s = neg ? g_strdup_printf ("-%d.%06d", i, f) : g_strdup_printf ("%d.%06d", i, f); + SPRINTF (buf, "%s%d.%06d", neg ? "-" : "", i, f); - char * c = s + strlen (s); + char * c = buf + strlen (buf); while (* (c - 1) == '0') c --; if (* (c - 1) == '.') c --; * c = 0; - return s; + return str_get (buf); } -EXPORT bool_t string_to_int_array (const char * string, int * array, int count) +EXPORT bool_t str_to_int_array (const char * string, int * array, int count) { - char * * split = g_strsplit (string, ",", -1); - if (g_strv_length (split) != count) - goto ERR; + Index * index = str_list_to_index (string, ", "); + bool_t okay = (index_count (index) == count); - for (int i = 0; i < count; i ++) + if (okay) { - if (! string_to_int (split[i], & array[i])) - goto ERR; + for (int i = 0; i < count; i ++) + array[i] = str_to_int (index_get (index, i)); } - g_strfreev (split); - return TRUE; - -ERR: - g_strfreev (split); - return FALSE; + index_free_full (index, (IndexFreeFunc) str_unref); + return okay; } -EXPORT char * int_array_to_string (const int * array, int count) +EXPORT char * int_array_to_str (const int * array, int count) { - char * * split = g_malloc0 (sizeof (char *) * (count + 1)); + Index * index = index_new (); for (int i = 0; i < count; i ++) { - split[i] = int_to_string (array[i]); - if (! split[i]) + char * value = int_to_str (array[i]); + if (! value) goto ERR; + + index_insert (index, -1, value); } - char * string = g_strjoinv (",", split); - g_strfreev (split); + char * string = index_to_str_list (index, ","); + index_free_full (index, (IndexFreeFunc) str_unref); return string; ERR: - g_strfreev (split); + index_free_full (index, (IndexFreeFunc) str_unref); return NULL; } -EXPORT bool_t string_to_double_array (const char * string, double * array, int count) +EXPORT bool_t str_to_double_array (const char * string, double * array, int count) { - char * * split = g_strsplit (string, ",", -1); - if (g_strv_length (split) != count) - goto ERR; + Index * index = str_list_to_index (string, ", "); + bool_t okay = (index_count (index) == count); - for (int i = 0; i < count; i ++) + if (okay) { - if (! string_to_double (split[i], & array[i])) - goto ERR; + for (int i = 0; i < count; i ++) + array[i] = str_to_double (index_get (index, i)); } - g_strfreev (split); - return TRUE; - -ERR: - g_strfreev (split); - return FALSE; + index_free_full (index, (IndexFreeFunc) str_unref); + return okay; } -EXPORT char * double_array_to_string (const double * array, int count) +EXPORT char * double_array_to_str (const double * array, int count) { - char * * split = g_malloc0 (sizeof (char *) * (count + 1)); + Index * index = index_new (); for (int i = 0; i < count; i ++) { - split[i] = double_to_string (array[i]); - if (! split[i]) + char * value = double_to_str (array[i]); + if (! value) goto ERR; + + index_insert (index, -1, value); } - char * string = g_strjoinv (",", split); - g_strfreev (split); + char * string = index_to_str_list (index, ","); + index_free_full (index, (IndexFreeFunc) str_unref); return string; ERR: - g_strfreev (split); + index_free_full (index, (IndexFreeFunc) str_unref); return NULL; } diff --git a/src/libaudcore/audstrings.h b/src/libaudcore/audstrings.h index c8e1035..9f6cae7 100644 --- a/src/libaudcore/audstrings.h +++ b/src/libaudcore/audstrings.h @@ -1,7 +1,6 @@ /* * audstrings.h - * Copyright 2009-2011 John Lindgren - * Copyright 2010 William Pitcock + * Copyright 2009-2012 John Lindgren and William Pitcock * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -21,21 +20,86 @@ #ifndef LIBAUDCORE_STRINGS_H #define LIBAUDCORE_STRINGS_H +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + #include <libaudcore/core.h> +#define SPRINTF(s, ...) \ + char s[snprintf (NULL, 0, __VA_ARGS__) + 1]; \ + snprintf (s, sizeof s, __VA_ARGS__) + +#define VSPRINTF(s, f, v) \ + va_list v##2; \ + va_copy (v##2, v); \ + char s[vsnprintf (NULL, 0, f, v##2) + 1]; \ + va_end (v##2); \ + vsnprintf (s, sizeof s, f, v) + +#define SCOPY(s, a) \ + char s[strlen (a) + 1]; \ + strcpy (s, a) + +#define SNCOPY(s, a, x) \ + char s[(x) + 1]; \ + strncpy (s, a, sizeof s - 1); \ + s[sizeof s - 1] = 0 + +#define SCONCAT2(s, a, b) \ + int s##_1 = strlen (a), s##_2 = strlen (b); \ + char s[s##_1 + s##_2 + 1]; \ + memcpy (s, (a), s##_1); \ + strcpy (s + s##_1, (b)) + +#define SCONCAT3(s, a, b, c) \ + int s##_1 = strlen (a), s##_2 = strlen (b), s##_3 = strlen (c); \ + char s[s##_1 + s##_2 + s##_3 + 1]; \ + memcpy (s, (a), s##_1); \ + memcpy (s + s##_1, (b), s##_2); \ + strcpy (s + s##_1 + s##_2, (c)) + +#define SCONCAT4(s, a, b, c, d) \ + int s##_1 = strlen (a), s##_2 = strlen (b), s##_3 = strlen (c), s##_4 = strlen (d); \ + char s[s##_1 + s##_2 + s##_3 + s##_4 + 1]; \ + memcpy (s, (a), s##_1); \ + memcpy (s + s##_1, (b), s##_2); \ + memcpy (s + s##_1 + s##_2, (c), s##_3); \ + strcpy (s + s##_1 + s##_2 + s##_3, (d)) + +struct _Index; + +/* all (char *) return values must be freed with str_unref() */ + +char * str_printf (const char * format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); +char * str_vprintf (const char * format, va_list args); + bool_t str_has_prefix_nocase(const char * str, const char * prefix); bool_t str_has_suffix_nocase(const char * str, const char * suffix); -void str_set_utf8_impl (char * (* stu_impl) (const char *), - char * (* stuf_impl) (const char *, int, int *, int *)); -char * str_to_utf8 (const char * str); -char * str_to_utf8_full (const char * str, int len, int * bytes_read, int * bytes_written); +char * strstr_nocase (const char * haystack, const char * needle); +char * strstr_nocase_utf8 (const char * haystack, const char * needle); + +char * str_tolower_utf8 (const char * str); -void string_replace_char (char * string, char old_c, char new_c); +void str_replace_char (char * string, char old_c, char new_c); + +void str_itoa (int x, char * buf, int bufsize); void str_decode_percent (const char * str, int len, char * out); void str_encode_percent (const char * str, int len, char * out); +char * str_convert (const char * str, int len, const char * from_charset, const char * to_charset); +char * str_from_locale (const char * str, int len); +char * str_to_locale (const char * str, int len); +char * str_to_utf8 (const char * str, int len); + +/* takes ownership of <fallbacks> and the pooled strings in it */ +void str_set_charsets (const char * region, struct _Index * fallbacks); + +void filename_normalize (char * filename); + +char * filename_build (const char * path, const char * name); char * filename_to_uri (const char * filename); char * uri_to_filename (const char * uri); char * uri_to_display (const char * uri); @@ -44,19 +108,20 @@ void uri_parse (const char * uri, const char * * base_p, const char * * ext_p, const char * * sub_p, int * isub_p); bool_t uri_get_extension (const char * uri, char * buf, int buflen); -int string_compare (const char * a, const char * b); -int string_compare_encoded (const char * a, const char * b); +int str_compare (const char * a, const char * b); +int str_compare_encoded (const char * a, const char * b); -char *str_replace_fragment(char *s, int size, const char *old_str, const char *new_str); +struct _Index * str_list_to_index (const char * list, const char * delims); +char * index_to_str_list (struct _Index * index, const char * sep); -bool_t string_to_int (const char * string, int * addr); -bool_t string_to_double (const char * string, double * addr); -char * int_to_string (int val); -char * double_to_string (double val); +int str_to_int (const char * string); +double str_to_double (const char * string); +char * int_to_str (int val); +char * double_to_str (double val); -bool_t string_to_int_array (const char * string, int * array, int count); -char * int_array_to_string (const int * array, int count); -bool_t string_to_double_array (const char * string, double * array, int count); -char * double_array_to_string (const double * array, int count); +bool_t str_to_int_array (const char * string, int * array, int count); +char * int_array_to_str (const int * array, int count); +bool_t str_to_double_array (const char * string, double * array, int count); +char * double_array_to_str (const double * array, int count); #endif /* LIBAUDCORE_STRINGS_H */ diff --git a/src/libaudcore/charset.c b/src/libaudcore/charset.c new file mode 100644 index 0000000..d3c6d5f --- /dev/null +++ b/src/libaudcore/charset.c @@ -0,0 +1,180 @@ +/* + * charset.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <iconv.h> +#include <stdio.h> +#include <string.h> + +#include <glib.h> + +#ifdef USE_CHARDET +#include <libguess/libguess.h> +#endif + +#include "audstrings.h" +#include "index.h" +#include "tinylock.h" + +EXPORT char * str_convert (const char * str, int len, const char * from_charset, + const char * to_charset) +{ + iconv_t conv = iconv_open (to_charset, from_charset); + if (conv == (iconv_t) -1) + return NULL; + + if (len < 0) + len = strlen (str); + + // liberal estimate of how much space we will need + // are there obscure cases that require even more? + int maxlen = 4 * len; + + char buf[maxlen + 1]; + char * result = NULL; + + size_t inbytes = len; + size_t outbytes = maxlen; + char * in = (char *) str; + char * out = buf; + + if (iconv (conv, & in, & inbytes, & out, & outbytes) != (size_t) -1 && ! inbytes) + { + buf[maxlen - outbytes] = 0; + result = str_get (buf); + } + + iconv_close (conv); + return result; +} + +static void whine_locale (const char * str, int len, const char * dir, const char * charset) +{ + if (len < 0) + fprintf (stderr, "Cannot convert %s locale (%s): %s\n", dir, charset, str); + else + fprintf (stderr, "Cannot convert %s locale (%s): %.*s\n", dir, charset, len, str); +} + +EXPORT char * str_from_locale (const char * str, int len) +{ + const char * charset; + + if (g_get_charset (& charset)) + { + /* locale is UTF-8 */ + if (! g_utf8_validate (str, len, NULL)) + { + whine_locale (str, len, "from", "UTF-8"); + return NULL; + } + + return (len < 0) ? str_get (str) : str_nget (str, len); + } + else + { + char * utf8 = str_convert (str, len, charset, "UTF-8"); + if (! utf8) + whine_locale (str, len, "from", charset); + + return utf8; + } +} + +EXPORT char * str_to_locale (const char * str, int len) +{ + const char * charset; + + if (g_get_charset (& charset)) + { + /* locale is UTF-8 */ + return (len < 0) ? str_get (str) : str_nget (str, len); + } + else + { + char * local = str_convert (str, len, "UTF-8", charset); + if (! local) + whine_locale (str, len, "to", charset); + + return local; + } +} + +static TinyRWLock settings_lock; +static char * detect_region; +static Index * fallback_charsets; + +EXPORT void str_set_charsets (const char * region, Index * fallbacks) +{ + tiny_lock_write (& settings_lock); + + str_unref (detect_region); + detect_region = str_get (region); + +#ifdef USE_CHARDET + if (detect_region) + libguess_init (); +#endif + + if (fallback_charsets) + index_free_full (fallback_charsets, (IndexFreeFunc) str_unref); + + fallback_charsets = fallbacks; + + tiny_unlock_write (& settings_lock); +} + +EXPORT char * str_to_utf8 (const char * str, int len) +{ + /* check whether already UTF-8 */ + if (g_utf8_validate (str, len, NULL)) + return (len < 0) ? str_get (str) : str_nget (str, len); + + if (len < 0) + len = strlen (str); + + char * utf8 = NULL; + tiny_lock_read (& settings_lock); + +#ifdef USE_CHARDET + if (detect_region) + { + /* prefer libguess-detected charset */ + const char * detected = libguess_determine_encoding (str, len, detect_region); + if (detected && (utf8 = str_convert (str, len, detected, "UTF-8"))) + goto DONE; + } +#endif + + if (fallback_charsets) + { + /* try user-configured fallbacks */ + for (int i = 0; i < index_count (fallback_charsets); i ++) + { + if ((utf8 = str_convert (str, len, index_get (fallback_charsets, i), "UTF-8"))) + goto DONE; + } + } + + /* try system locale last (this one will print a warning if it fails) */ + utf8 = str_from_locale (str, len); + +DONE: + tiny_unlock_read (& settings_lock); + return utf8; +} diff --git a/src/libaudcore/core.h b/src/libaudcore/core.h index b92c500..f3c2615 100644 --- a/src/libaudcore/core.h +++ b/src/libaudcore/core.h @@ -1,6 +1,6 @@ /* * core.h - * Copyright 2011 John Lindgren + * Copyright 2011-2012 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,14 +43,7 @@ #undef CLAMP #define CLAMP(a,min,max) ((a) < (min) ? (min) : (a) > (max) ? (max) : (a)) -#define SPRINTF(s,...) \ - char s[snprintf (NULL, 0, __VA_ARGS__) + 1]; \ - snprintf (s, sizeof s, __VA_ARGS__); - -/* Simple sanity check to catch (1) strings that are still in use after their - * reference count has dropped to zero and (2) strings that should have been - * pooled but never were. If the check fails, the program is aborted. */ -#define STR_CHECK(str) do {if ((str) && (str)[-1] != '@') strpool_abort (str);} while (0) +#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) /* If the pool contains a copy of <str>, increments its reference count. * Otherwise, adds a copy of <str> to the pool with a reference count of one. @@ -63,7 +56,7 @@ char * str_get (const char * str); * string already in the pool. Faster than calling str_get() a second time. * Returns <str> for convenience. If <str> is NULL, simply returns NULL with no * side effects. */ -char * str_ref (char * str); +char * str_ref (const char * str); /* Decrements the reference count of <str>, where <str> is the address of a * string in the pool. If the reference count drops to zero, releases the @@ -71,16 +64,18 @@ char * str_ref (char * str); * effects. */ void str_unref (char * str); +/* Returns the cached hash value of a pooled string (or 0 for NULL). */ +unsigned str_hash (const char * str); + +/* Checks whether two pooled strings are equal. Since the pool never contains + * duplicate strings, this is a simple pointer comparison and thus much faster + * than strcmp(). NULL is considered equal to NULL but not equal to any string. */ +bool_t str_equal (const char * str1, const char * str2); + /* Calls str_get() on the first <len> characters of <str>. If <str> has less * than or equal to <len> characters, equivalent to str_get(). */ char * str_nget (const char * str, int len); -/* Calls sprintf() internally, then pools the produced string with str_get(). */ -char * str_printf (const char * format, ...); - -/* Used by STR_CHECK; should not be called directly. */ -void strpool_abort (char * str); - /* Releases all memory used by the string pool. If strings remain in the pool, * a warning may be printed to stderr in order to reveal memory leaks. */ void strpool_shutdown (void); diff --git a/src/libaudcore/eventqueue.c b/src/libaudcore/eventqueue.c index c2648b9..ee92c03 100644 --- a/src/libaudcore/eventqueue.c +++ b/src/libaudcore/eventqueue.c @@ -45,7 +45,7 @@ static bool_t event_execute (Event * event) hook_call (event->name, event->data); - g_free (event->name); + str_unref (event->name); if (event->destroy) event->destroy (event->data); @@ -56,7 +56,7 @@ static bool_t event_execute (Event * event) EXPORT void event_queue_full (int time, const char * name, void * data, void (* destroy) (void *)) { Event * event = g_slice_new (Event); - event->name = g_strdup (name); + event->name = str_get (name); event->data = data; event->destroy = destroy; @@ -83,7 +83,7 @@ EXPORT void event_queue_cancel (const char * name, void * data) g_source_remove (event->source); events = g_list_delete_link (events, node); - g_free (event->name); + str_unref (event->name); if (event->destroy) event->destroy (event->data); @@ -95,3 +95,28 @@ EXPORT void event_queue_cancel (const char * name, void * data) pthread_mutex_unlock (& mutex); } + +EXPORT void event_queue_cancel_all (void) +{ + pthread_mutex_lock (& mutex); + + GList * node = events; + while (node) + { + Event * event = node->data; + GList * next = node->next; + + g_source_remove (event->source); + events = g_list_delete_link (events, node); + + str_unref (event->name); + if (event->destroy) + event->destroy (event->data); + + g_slice_free (Event, event); + + node = next; + } + + pthread_mutex_unlock (& mutex); +} diff --git a/src/libaudcore/hook.c b/src/libaudcore/hook.c index 00d69c8..e9f4e39 100644 --- a/src/libaudcore/hook.c +++ b/src/libaudcore/hook.c @@ -1,6 +1,6 @@ /* * hook.c - * Copyright 2011 John Lindgren + * Copyright 2011-2012 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/libaudcore/hook.h b/src/libaudcore/hook.h index 79fe8ac..646359b 100644 --- a/src/libaudcore/hook.h +++ b/src/libaudcore/hook.h @@ -47,4 +47,7 @@ void event_queue_full (int time, const char * name, void * data, void (* destroy * all hook calls matching <name> are canceled. */ void event_queue_cancel (const char * name, void * data); +/* Cancels all pending hook calls. */ +void event_queue_cancel_all (void); + #endif /* LIBAUDCORE_HOOK_H */ diff --git a/src/libaudcore/index.c b/src/libaudcore/index.c index 160a97d..c2f7f39 100644 --- a/src/libaudcore/index.c +++ b/src/libaudcore/index.c @@ -1,6 +1,6 @@ /* * index.c - * Copyright 2009-2011 John Lindgren + * Copyright 2009-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,16 +17,19 @@ * the use of this software. */ +#include <assert.h> #include <stdlib.h> #include <string.h> #include <glib.h> +#include "core.h" #include "index.h" struct _Index { void * * data; int count, size; + void * sdata[16]; }; typedef struct { @@ -40,19 +43,26 @@ typedef struct { EXPORT Index * index_new (void) { - Index * index = g_slice_new (Index); - - index->data = NULL; - index->count = 0; - index->size = 0; - + Index * index = g_new0 (Index, 1); + index->data = index->sdata; + index->size = ARRAY_LEN (index->sdata); return index; } EXPORT void index_free (Index * index) { - g_free (index->data); - g_slice_free (Index, index); + if (index->data != index->sdata) + g_free (index->data); + + g_free (index); +} + +EXPORT void index_free_full (Index * index, IndexFreeFunc func) +{ + for (int i = 0; i < index->count; i ++) + func (index->data[i]); + + index_free (index); } EXPORT int index_count (Index * index) @@ -62,26 +72,38 @@ EXPORT int index_count (Index * index) EXPORT void index_allocate (Index * index, int size) { - if (size <= index->size) + assert (size >= 0); + + if (index->size >= size) return; - if (! index->size) + if (index->size < 64) index->size = 64; - while (size > index->size) + while (index->size < size) index->size <<= 1; - index->data = g_realloc (index->data, sizeof (void *) * index->size); + if (index->data == index->sdata) + { + index->data = g_new (void *, index->size); + memcpy (index->data, index->sdata, sizeof index->sdata); + } + else + index->data = g_renew (void *, index->data, index->size); } -EXPORT void index_set (Index * index, int at, void * value) +EXPORT void * index_get (Index * index, int at) { - index->data[at] = value; + assert (at >= 0 && at < index->count); + + return index->data[at]; } -EXPORT void * index_get (Index * index, int at) +EXPORT void index_set (Index * index, int at, void * value) { - return index->data[at]; + assert (at >= 0 && at < index->count); + + index->data[at] = value; } static void make_room (Index * index, int at, int count) @@ -89,62 +111,80 @@ static void make_room (Index * index, int at, int count) index_allocate (index, index->count + count); if (at < index->count) - memmove (index->data + at + count, index->data + at, sizeof (void *) * - (index->count - at)); + memmove (index->data + at + count, index->data + at, sizeof (void *) * (index->count - at)); index->count += count; } EXPORT void index_insert (Index * index, int at, void * value) { + if (at == -1) + at = index->count; + + assert (at >= 0 && at <= index->count); + make_room (index, at, 1); index->data[at] = value; } -EXPORT void index_append (Index * index, void * value) +EXPORT void index_copy_set (Index * source, int from, Index * target, int to, int count) { - index_insert (index, index->count, value); -} + assert (count >= 0); + assert (from >= 0 && from + count <= source->count); + assert (to >= 0 && to + count <= target->count); -EXPORT void index_copy_set (Index * source, int from, Index * target, - int to, int count) -{ - memcpy (target->data + to, source->data + from, sizeof (void *) * count); + memmove (target->data + to, source->data + from, sizeof (void *) * count); } -EXPORT void index_copy_insert (Index * source, int from, Index * target, - int to, int count) +EXPORT void index_copy_insert (Index * source, int from, Index * target, int to, int count) { + if (to == -1) + to = target->count; + if (count == -1) + count = source->count - from; + + assert (count >= 0); + assert (from >= 0 && from + count <= source->count); + assert (to >= 0 && to <= target->count); + make_room (target, to, count); - memcpy (target->data + to, source->data + from, sizeof (void *) * count); -} -EXPORT void index_copy_append (Index * source, int from, Index * target, - int count) -{ - index_copy_insert (source, from, target, target->count, count); + if (source == target && to <= from) + index_copy_set (source, from + count, target, to, count); + else if (source == target && to <= from + count) + { + index_copy_set (source, from, target, to, to - from); + index_copy_set (source, to + count, target, to + (to - from), count - (to - from)); + } + else + index_copy_set (source, from, target, to, count); } -EXPORT void index_merge_insert (Index * first, int at, Index * second) +EXPORT void index_delete (Index * index, int at, int count) { - index_copy_insert (second, 0, first, at, second->count); -} + if (count == -1) + count = index->count - at; -EXPORT void index_merge_append (Index * first, Index * second) -{ - index_copy_insert (second, 0, first, first->count, second->count); -} + assert (count >= 0); + assert (at >= 0 && at + count <= index->count); -EXPORT void index_move (Index * index, int from, int to, int count) -{ - memmove (index->data + to, index->data + from, sizeof (void *) * count); + index_copy_set (index, at + count, index, at, index->count - (at + count)); + + index->count -= count; } -EXPORT void index_delete (Index * index, int at, int count) +EXPORT void index_delete_full (Index * index, int at, int count, IndexFreeFunc func) { - index->count -= count; - memmove (index->data + at, index->data + at + count, sizeof (void *) * - (index->count - at)); + if (count == -1) + count = index->count - at; + + assert (count >= 0); + assert (at >= 0 && at + count <= index->count); + + for (int i = at; i < at + count; i ++) + func (index->data[i]); + + index_delete (index, at, count); } static int index_compare (const void * ap, const void * bp, void * _wrapper) @@ -156,8 +196,7 @@ static int index_compare (const void * ap, const void * bp, void * _wrapper) EXPORT void index_sort (Index * index, int (* compare) (const void *, const void *)) { CompareWrapper wrapper = {compare}; - g_qsort_with_data (index->data, index->count, sizeof (void *), - index_compare, & wrapper); + g_qsort_with_data (index->data, index->count, sizeof (void *), index_compare, & wrapper); } static int index_compare2 (const void * ap, const void * bp, void * _wrapper) @@ -170,6 +209,5 @@ EXPORT void index_sort_with_data (Index * index, int (* compare) (const void * a, const void * b, void * data), void * data) { CompareWrapper2 wrapper = {compare, data}; - g_qsort_with_data (index->data, index->count, sizeof (void *), - index_compare2, & wrapper); + g_qsort_with_data (index->data, index->count, sizeof (void *), index_compare2, & wrapper); } diff --git a/src/libaudcore/index.h b/src/libaudcore/index.h index 68d21a6..3990ff8 100644 --- a/src/libaudcore/index.h +++ b/src/libaudcore/index.h @@ -1,6 +1,6 @@ /* * index.h - * Copyright 2009-2011 John Lindgren + * Copyright 2009-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,25 +20,77 @@ #ifndef LIBAUDCORE_INDEX_H #define LIBAUDCORE_INDEX_H +/* An "index" is an opaque structure representing a list of pointers. It is + * used primarily to store Audacious playlists, but can be useful for other + * purposes as well. */ + struct _Index; typedef struct _Index Index; +typedef void (* IndexFreeFunc) (void * value); + +/* Returns a new, empty index. */ Index * index_new (void); + +/* Destroys <index>. */ void index_free (Index * index); + +/* Destroys <index>, first calling <func> on each pointer stored in it. */ +void index_free_full (Index * index, IndexFreeFunc func); + +/* Returns the number of pointers stored in <index>. */ int index_count (Index * index); + +/* Preallocates space to store <size> pointers in <index>. This can be used to + * avoid repeated memory allocations when adding pointers to <index> one by one. + * The value returned by index_count() does not changed until the pointers are + * actually added. */ void index_allocate (Index * index, int size); -void index_set (Index * index, int at, void * value); + +/* Returns the value stored in <index> at position <at>. */ void * index_get (Index * index, int at); + +/* Stores <value> in <index> at position <at>, overwriting the previous value. */ +void index_set (Index * index, int at, void * value); + +/* Stores <value> in <index> at position <at>, shifting the previous value (if + * any) and those following it forward to make room. If <at> is -1, <value> is + * added to the end of <index>. */ void index_insert (Index * index, int at, void * value); -void index_append (Index * index, void * value); + +/* Copies <count> pointers from <source>, starting at position <from>, to + * <target>, starting at position <to>. Existing pointers in <target> are + * overwritten. Overlapping regions are handled correctly if <source> and + * <target> are the same index. */ void index_copy_set (Index * source, int from, Index * target, int to, int count); + +/* Copies <count> pointers from <source>, starting at position <from>, to + * <target>, starting at position <to>. Existing pointers in <target> are + * shifted forward to make room. All cases are handled correctly if <source> + * and <target> are the same index, including the case where <to> is between + * <from> and <from + count>. If <to> is -1, the pointers are added to the end + * of <target>. If <count> is -1, copying continues until the end of <source> + * is reached. */ void index_copy_insert (Index * source, int from, Index * target, int to, int count); -void index_copy_append (Index * source, int from, Index * target, int count); -void index_merge_insert (Index * first, int at, Index * second); -void index_merge_append (Index * first, Index * second); -void index_move (Index * index, int from, int to, int count); + +/* Removes <count> pointers from <index>, start at position <at>. Any following + * pointers are shifted backward to close the gap. If <count> is -1, all + * pointers from <at> to the end of <index> are removed. */ void index_delete (Index * index, int at, int count); + +/* Like index_delete(), but first calls <func> on any pointer that is being + * removed. */ +void index_delete_full (Index * index, int at, int count, IndexFreeFunc func); + +/* Sort the entries in <index> using the quick-sort algorithm with the given + * comparison function. The return value of <compare> should follow the + * convention used by strcmp(): negative if (a < b), zero if (a = b), positive + * if (a > b). */ void index_sort (Index * index, int (* compare) (const void * a, const void * b)); + +/* Exactly like index_sort() except that <data> is forwarded to the comparison + * function. This allows comparison functions whose behavior changes depending + * on context. */ void index_sort_with_data (Index * index, int (* compare) (const void * a, const void * b, void * data), void * data); diff --git a/src/libaudcore/inifile.c b/src/libaudcore/inifile.c new file mode 100644 index 0000000..6e13a48 --- /dev/null +++ b/src/libaudcore/inifile.c @@ -0,0 +1,134 @@ +/* + * inifile.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "audstrings.h" +#include "inifile.h" + +#include <glib.h> + +#include <string.h> + +static char * strskip (char * str) +{ + while (g_ascii_isspace (* str)) + str ++; + + return str; +} + +static char * strtrim (char * str) +{ + int len = strlen (str); + + while (len && g_ascii_isspace(str[len - 1])) + str[-- len] = 0; + + return str; +} + +EXPORT void inifile_parse (VFSFile * file, + void (* handle_heading) (const char * heading, void * data), + void (* handle_entry) (const char * key, const char * value, void * data), + void * data) +{ + int size = 512; + char * buf = g_new (char, size); + + char * pos = buf; + int len = 0; + bool_t eof = FALSE; + + while (1) + { + char * newline = memchr (pos, '\n', len); + + while (! newline && ! eof) + { + memmove (buf, pos, len); + pos = buf; + + if (len >= size - 1) + { + size <<= 1; + buf = g_renew (char, buf, size); + pos = buf; + } + + len += vfs_fread (buf + len, 1, size - 1 - len, file); + + if (len < size - 1) + eof = TRUE; + + newline = memchr (pos, '\n', len); + } + + if (newline) + * newline = 0; + else + pos[len] = 0; + + char * start = strskip (pos); + + switch (* start) + { + case 0: + case '#': + case ';': + break; + + case '[':; + char * end = strchr (start + 1, ']'); + if (! end) + break; + + * end = 0; + handle_heading (strtrim (strskip (start + 1)), data); + break; + + default:; + char * sep = strchr (start, '='); + if (! sep) + break; + + * sep = 0; + handle_entry (strtrim (start), strtrim (strskip (sep + 1)), data); + break; + } + + if (! newline) + break; + + len -= newline + 1 - pos; + pos = newline + 1; + } + + g_free (buf); +} + +EXPORT bool_t inifile_write_heading (VFSFile * file, const char * heading) +{ + SCONCAT3 (buf, "\n[", heading, "]\n"); + return (vfs_fwrite (buf, 1, sizeof buf - 1, file) == sizeof buf - 1); +} + +EXPORT bool_t inifile_write_entry (VFSFile * file, const char * key, const char * value) +{ + SCONCAT4 (buf, key, "=", value, "\n"); + return (vfs_fwrite (buf, 1, sizeof buf - 1, file) == sizeof buf - 1); +} diff --git a/src/libaudcore/inifile.h b/src/libaudcore/inifile.h new file mode 100644 index 0000000..87189c2 --- /dev/null +++ b/src/libaudcore/inifile.h @@ -0,0 +1,33 @@ +/* + * inifile.h + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_INIFILE_H +#define LIBAUDCORE_INIFILE_H + +#include "vfs.h" + +void inifile_parse (VFSFile * file, + void (* handle_heading) (const char * heading, void * data), + void (* handle_entry) (const char * key, const char * value, void * data), + void * data); + +bool_t inifile_write_heading (VFSFile * file, const char * heading); +bool_t inifile_write_entry (VFSFile * file, const char * key, const char * value); + +#endif /* LIBAUDCORE_INIFILE_H */ diff --git a/src/libaudcore/multihash.c b/src/libaudcore/multihash.c new file mode 100644 index 0000000..2be8839 --- /dev/null +++ b/src/libaudcore/multihash.c @@ -0,0 +1,153 @@ +/* + * multihash.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "multihash.h" + +#include <glib.h> + +#define INITIAL_SIZE 256 /* must be a power of two */ + +static void resize_channel (MultihashTable * table, MultihashChannel * channel, unsigned size) +{ + MultihashNode * * buckets = g_new0 (MultihashNode *, size); + + for (int b1 = 0; b1 < channel->size; b1 ++) + { + MultihashNode * node = channel->buckets[b1]; + + while (node) + { + MultihashNode * next = node->next; + + unsigned hash = table->hash_func (node); + unsigned b2 = (hash >> MULTIHASH_SHIFT) & (size - 1); + MultihashNode * * node_ptr = & buckets[b2]; + + node->next = * node_ptr; + * node_ptr = node; + + node = next; + } + } + + g_free (channel->buckets); + channel->buckets = buckets; + channel->size = size; +} + +EXPORT int multihash_lookup (MultihashTable * table, const void * data, + unsigned hash, MultihashAddFunc add, MultihashActionFunc action, void * state) +{ + unsigned c = hash & (MULTIHASH_CHANNELS - 1); + MultihashChannel * channel = & table->channels[c]; + + int status = 0; + tiny_lock (& channel->lock); + + if (! channel->buckets) + { + if (! add) + goto DONE; + + channel->buckets = g_new0 (MultihashNode *, INITIAL_SIZE); + channel->size = INITIAL_SIZE; + channel->used = 0; + } + + unsigned b = (hash >> MULTIHASH_SHIFT) & (channel->size - 1); + MultihashNode * * node_ptr = & channel->buckets[b]; + MultihashNode * node = * node_ptr; + + while (node && ! table->match_func (node, data, hash)) + { + node_ptr = & node->next; + node = * node_ptr; + } + + if (node) + { + status |= MULTIHASH_FOUND; + + MultihashNode * next = node->next; + + if (action && action (node, state)) + { + status |= MULTIHASH_REMOVED; + + * node_ptr = next; + + channel->used --; + if (channel->used < channel->size >> 2 && channel->size > INITIAL_SIZE) + resize_channel (table, channel, channel->size >> 1); + } + } + else if (add && (node = add (data, hash, state))) + { + status |= MULTIHASH_ADDED; + + * node_ptr = node; + node->next = NULL; + + channel->used ++; + if (channel->used > channel->size) + resize_channel (table, channel, channel->size << 1); + } + +DONE: + tiny_unlock (& channel->lock); + return status; +} + +EXPORT void multihash_iterate (MultihashTable * table, MultihashActionFunc action, void * state) +{ + for (int c = 0; c < MULTIHASH_CHANNELS; c ++) + tiny_lock (& table->channels[c].lock); + + for (int c = 0; c < MULTIHASH_CHANNELS; c ++) + { + MultihashChannel * channel = & table->channels[c]; + + for (int b = 0; b < channel->size; b ++) + { + MultihashNode * * node_ptr = & channel->buckets[b]; + MultihashNode * node = * node_ptr; + + while (node) + { + MultihashNode * next = node->next; + + if (action (node, state)) + { + * node_ptr = next; + channel->used --; + } + else + node_ptr = & node->next; + + node = next; + } + } + + if (channel->used < channel->size >> 2 && channel->size > INITIAL_SIZE) + resize_channel (table, channel, channel->size >> 1); + } + + for (int c = 0; c < MULTIHASH_CHANNELS; c ++) + tiny_unlock (& table->channels[c].lock); +} diff --git a/src/libaudcore/multihash.h b/src/libaudcore/multihash.h new file mode 100644 index 0000000..1e66b21 --- /dev/null +++ b/src/libaudcore/multihash.h @@ -0,0 +1,95 @@ +/* + * multihash.h + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_MULTIHASH_H +#define LIBAUDCORE_MULTIHASH_H + +#include <libaudcore/core.h> +#include <libaudcore/tinylock.h> + +/* Multihash is a generic, thread-safe hash table. It scales well to multiple + * processors by the use of multiple channels, each with a separate lock. The + * hash value of a given node decides what channel it is stored in. Hence, + * different processors will tend to hit different channels, keeping lock + * contention to a minimum. The data structures are public in order to allow + * static initialization (setting all bytes to zero gives the initial state). + * The all-purpose lookup function enables a variety of atomic operations, such + * as allocating and adding a node only if not already present. */ + +#define MULTIHASH_CHANNELS 16 /* must be a power of two */ +#define MULTIHASH_SHIFT 4 /* log (base 2) of MULTIHASH_CHANNELS */ + +#define MULTIHASH_FOUND (1 << 0) +#define MULTIHASH_ADDED (1 << 1) +#define MULTIHASH_REMOVED (1 << 2) + +/* Skeleton structure containing internal member(s) of a multihash node. Actual + * node structures should be defined with MultihashNode as the first member. */ +typedef struct _MultihashNode { + struct _MultihashNode * next; +} MultihashNode; + +/* Single channel of a multihash table. For internal use only. */ +typedef struct { + TinyLock lock; + MultihashNode * * buckets; + unsigned size, used; +} MultihashChannel; + +/* Callback. Calculates (or retrieves) the hash value of <node>. */ +typedef unsigned (* MultihashFunc) (const MultihashNode * node); + +/* Callback. Returns TRUE if <node> matches <data>, otherwise FALSE. */ +typedef bool_t (* MultihashMatchFunc) (const MultihashNode * node, + const void * data, unsigned hash); + +/* Multihash table. <hash_func> and <match_func> should be initialized to + * functions appropriate for the type of data to be stored in the table. */ +typedef struct { + MultihashFunc hash_func; + MultihashMatchFunc match_func; + MultihashChannel channels[MULTIHASH_CHANNELS]; +} MultihashTable; + +/* Callback. May create a new node representing <data> to be added to the + * table. Returns the new node or NULL. */ +typedef MultihashNode * (* MultihashAddFunc) (const void * data, unsigned hash, void * state); + +/* Callback. Performs a user-defined action when a matching node is found. + * Doubles as a node removal function. Returns TRUE if <node> was freed and is + * to be removed, otherwise FALSE. */ +typedef bool_t (* MultihashActionFunc) (MultihashNode * node, void * state); + +/* All-purpose lookup function. The caller passes in the data to be looked up + * along with its hash value. The two callbacks are optional. <add> (if not + * NULL) is called if no matching node is found, and may return a new node to + * add to the table. <action> (if not NULL) is called if a matching node is + * found, and may return TRUE to remove the node from the table. <state> is + * forwarded to either callback. Returns the status of the lookup as a bitmask + * of MULTIHASH_FOUND, MULTIHASH_ADDED, and MULTIHASH_REMOVED. */ +int multihash_lookup (MultihashTable * table, const void * data, unsigned hash, + MultihashAddFunc add, MultihashActionFunc action, void * state); + +/* All-purpose iteration function. All channels of the table are locked + * simultaneously during the iteration to freeze the table in a consistent + * state. <action> is called on each node in order, and may return TRUE to + * remove the node from the table. <state> is forwarded to <action>. */ +void multihash_iterate (MultihashTable * table, MultihashActionFunc action, void * state); + +#endif /* LIBAUDCORE_MULTIHASH_H */ diff --git a/src/libaudcore/strpool.c b/src/libaudcore/strpool.c index 634a84d..71e99b3 100644 --- a/src/libaudcore/strpool.c +++ b/src/libaudcore/strpool.c @@ -1,6 +1,6 @@ /* * strpool.c - * Copyright 2011 John Lindgren + * Copyright 2011-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,136 +17,237 @@ * the use of this software. */ -#include <glib.h> -#include <pthread.h> -#include <stdarg.h> +#include <assert.h> #include <stdio.h> -#include <stdint.h> #include <stdlib.h> #include <string.h> -#include "core.h" +#include <glib.h> -/* Each string in the pool is allocated with five leading bytes: a 32-bit - * reference count and a one-byte signature, the '@' character. */ +#include "audstrings.h" +#include "multihash.h" -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static GHashTable * table; +#ifdef VALGRIND_FRIENDLY -static void str_destroy (void * str) -{ - * ((char *) str - 1) = 0; - free ((char *) str - 5); -} +typedef struct { + unsigned hash; + char magic; + char str[]; +} StrNode; + +#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1) +#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str))) EXPORT char * str_get (const char * str) { if (! str) return NULL; - char * copy; - pthread_mutex_lock (& mutex); + StrNode * node = g_malloc (NODE_SIZE_FOR (str)); + node->magic = '@'; + node->hash = g_str_hash (str); - if (! table) - table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, str_destroy); + strcpy (node->str, str); + return node->str; +} - if ((copy = g_hash_table_lookup (table, str))) - { - void * mem = copy - 5; - (* (int32_t *) mem) ++; - } - else - { - void * mem = malloc (6 + strlen (str)); - (* (int32_t *) mem) = 1; +EXPORT char * str_ref (const char * str) +{ + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + assert (g_str_hash (str) == node->hash); - copy = (char *) mem + 5; - copy[-1] = '@'; - strcpy (copy, str); + return str_get (str); +} - g_hash_table_insert (table, copy, copy); - } +EXPORT void str_unref (char * str) +{ + if (! str) + return; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + assert (g_str_hash (str) == node->hash); - pthread_mutex_unlock (& mutex); - return copy; + node->magic = 0; + g_free (node); } -EXPORT char * str_ref (char * str) +EXPORT unsigned str_hash (const char * str) { if (! str) - return NULL; + return 0; - pthread_mutex_lock (& mutex); - STR_CHECK (str); + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + return g_str_hash (str); +} - void * mem = str - 5; - (* (int32_t *) mem) ++; +EXPORT bool_t str_equal (const char * str1, const char * str2) +{ + assert (! str1 || NODE_OF (str1)->magic == '@'); + assert (! str2 || NODE_OF (str2)->magic == '@'); - pthread_mutex_unlock (& mutex); - return str; + return ! g_strcmp0 (str1, str2); } -EXPORT void str_unref (char * str) +EXPORT void strpool_shutdown (void) { - if (! str) - return; +} + +#else /* ! VALGRIND_FRIENDLY */ - pthread_mutex_lock (& mutex); - STR_CHECK (str); +typedef struct { + MultihashNode node; + unsigned hash, refs; + char magic; + char str[]; +} StrNode; - void * mem = str - 5; - if (! -- (* (int32_t *) mem)) - g_hash_table_remove (table, str); +#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1) +#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str))) - pthread_mutex_unlock (& mutex); +static unsigned hash_cb (const MultihashNode * node) +{ + return ((const StrNode *) node)->hash; } -EXPORT char * str_nget (const char * str, int len) +static bool_t match_cb (const MultihashNode * node_, const void * data, unsigned hash) { - if (memchr (str, 0, len)) - return str_get (str); + const StrNode * node = (const StrNode *) node_; + return data == node->str || (hash == node->hash && ! strcmp (data, node->str)); +} - char buf[len + 1]; - memcpy (buf, str, len); - buf[len] = 0; +static MultihashTable strpool_table = { + .hash_func = hash_cb, + .match_func = match_cb +}; - return str_get (buf); +static MultihashNode * add_cb (const void * data, unsigned hash, void * state) +{ + StrNode * node = g_malloc (NODE_SIZE_FOR (data)); + node->hash = hash; + node->refs = 1; + node->magic = '@'; + strcpy (node->str, data); + + * ((char * *) state) = node->str; + return (MultihashNode *) node; } -EXPORT char * str_printf (const char * format, ...) +static bool_t ref_cb (MultihashNode * node_, void * state) { - va_list args; + StrNode * node = (StrNode *) node_; - va_start (args, format); - int len = vsnprintf (NULL, 0, format, args); - va_end (args); + __sync_fetch_and_add (& node->refs, 1); - char buf[len + 1]; + * ((char * *) state) = node->str; + return FALSE; +} - va_start (args, format); - vsnprintf (buf, sizeof buf, format, args); - va_end (args); +EXPORT char * str_get (const char * str) +{ + if (! str) + return NULL; - return str_get (buf); + char * ret = NULL; + multihash_lookup (& strpool_table, str, g_str_hash (str), add_cb, ref_cb, & ret); + return ret; } -EXPORT void strpool_abort (char * str) +EXPORT char * str_ref (const char * str) { - fprintf (stderr, "String not in pool: %s\n", str); - abort (); + if (! str) + return NULL; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + __sync_fetch_and_add (& node->refs, 1); + + return (char *) str; } -static void str_leaked (void * key, void * str, void * unused) +static bool_t remove_cb (MultihashNode * node_, void * state) { - fprintf (stderr, "String not freed: %s\n", (char *) str); + StrNode * node = (StrNode *) node_; + + if (! __sync_bool_compare_and_swap (& node->refs, 1, 0)) + return FALSE; + + node->magic = 0; + g_free (node); + return TRUE; } -EXPORT void strpool_shutdown (void) +EXPORT void str_unref (char * str) { - if (! table) + if (! str) return; - g_hash_table_foreach (table, str_leaked, NULL); - g_hash_table_destroy (table); - table = NULL; + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + while (1) + { + int refs = __sync_fetch_and_add (& node->refs, 0); + + if (refs > 1) + { + if (__sync_bool_compare_and_swap (& node->refs, refs, refs - 1)) + break; + } + else + { + int status = multihash_lookup (& strpool_table, node->str, + node->hash, NULL, remove_cb, NULL); + + assert (status & MULTIHASH_FOUND); + if (status & MULTIHASH_REMOVED) + break; + } + } +} + +static bool_t leak_cb (MultihashNode * node, void * state) +{ + fprintf (stderr, "String leaked: %s\n", ((StrNode *) node)->str); + return FALSE; +} + +EXPORT void strpool_shutdown (void) +{ + multihash_iterate (& strpool_table, leak_cb, NULL); +} + +EXPORT unsigned str_hash (const char * str) +{ + if (! str) + return 0; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + return node->hash; +} + + +EXPORT bool_t str_equal (const char * str1, const char * str2) +{ + assert (! str1 || NODE_OF (str1)->magic == '@'); + assert (! str2 || NODE_OF (str2)->magic == '@'); + + return str1 == str2; +} + +#endif /* ! VALGRIND_FRIENDLY */ + +EXPORT char * str_nget (const char * str, int len) +{ + if (memchr (str, 0, len)) + return str_get (str); + + SNCOPY (buf, str, len); + return str_get (buf); } diff --git a/src/libaudcore/tinylock.c b/src/libaudcore/tinylock.c new file mode 100644 index 0000000..873aff1 --- /dev/null +++ b/src/libaudcore/tinylock.c @@ -0,0 +1,65 @@ +/* + * tinylock.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "tinylock.h" + +#ifndef VALGRIND_FRIENDLY + +#include <limits.h> +#include <sched.h> + +#define WRITE_BIT (SHRT_MAX + 1) + +EXPORT void tiny_lock (TinyLock * lock) +{ + while (__builtin_expect (__sync_lock_test_and_set (lock, 1), 0)) + sched_yield (); +} + +EXPORT void tiny_unlock (TinyLock * lock) +{ + __sync_lock_release (lock); +} + +EXPORT void tiny_lock_read (TinyRWLock * lock) +{ + while (__builtin_expect (__sync_fetch_and_add (lock, 1) & WRITE_BIT, 0)) + { + __sync_fetch_and_sub (lock, 1); + sched_yield (); + } +} + +EXPORT void tiny_unlock_read (TinyRWLock * lock) +{ + __sync_fetch_and_sub (lock, 1); +} + +EXPORT void tiny_lock_write (TinyRWLock * lock) +{ + while (! __builtin_expect (__sync_bool_compare_and_swap (lock, 0, WRITE_BIT), 1)) + sched_yield (); +} + +EXPORT void tiny_unlock_write (TinyRWLock * lock) +{ + __sync_fetch_and_sub (lock, WRITE_BIT); +} + +#endif /* ! VALGRIND_FRIENDLY */ diff --git a/src/libaudcore/tinylock.h.in b/src/libaudcore/tinylock.h.in new file mode 100644 index 0000000..873af1b --- /dev/null +++ b/src/libaudcore/tinylock.h.in @@ -0,0 +1,56 @@ +/* + * tinylock.h + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_TINYLOCK_H +#define LIBAUDCORE_TINYLOCK_H + +#if @VALGRIND_FRIENDLY@ /* VALGRIND_FRIENDLY */ + +#include <pthread.h> + +typedef pthread_mutex_t TinyLock; +typedef pthread_rwlock_t TinyRWLock; + +#define tiny_lock pthread_mutex_lock +#define tiny_unlock pthread_mutex_unlock +#define tiny_lock_read pthread_rwlock_rdlock +#define tiny_unlock_read pthread_rwlock_unlock +#define tiny_lock_write pthread_rwlock_wrlock +#define tiny_unlock_write pthread_rwlock_unlock + +#else /* ! VALGRIND_FRIENDLY */ + +/* TinyLock is an extremely low-overhead lock object (in terms of speed and + * memory usage). It makes no guarantees of fair scheduling, however. */ + +typedef char TinyLock; + +void tiny_lock (TinyLock * lock); +void tiny_unlock (TinyLock * lock); + +typedef unsigned short TinyRWLock; + +void tiny_lock_read (TinyRWLock * lock); +void tiny_unlock_read (TinyRWLock * lock); +void tiny_lock_write (TinyRWLock * lock); +void tiny_unlock_write (TinyRWLock * lock); + +#endif /* ! VALGRIND_FRIENDLY */ + +#endif /* LIBAUDCORE_TINYLOCK_H */ diff --git a/src/libaudcore/tuple.c b/src/libaudcore/tuple.c index f9a65dc..7974c8b 100644 --- a/src/libaudcore/tuple.c +++ b/src/libaudcore/tuple.c @@ -1,6 +1,6 @@ /* * tuple.c - * Copyright 2007-2011 William Pitcock, Christian Birchinger, Matti Hämäläinen, + * Copyright 2007-2013 William Pitcock, Christian Birchinger, Matti Hämäläinen, * Giacomo Lozito, Eugene Zagidullin, and John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -18,13 +18,7 @@ * the use of this software. */ -/** - * @file tuple.c - * @brief Basic Tuple handling API. - */ - #include <glib.h> -#include <pthread.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> @@ -33,8 +27,12 @@ #include <audacious/i18n.h> #include "audstrings.h" +#include "tinylock.h" #include "tuple.h" -#include "tuple_formatter.h" + +#if TUPLE_FIELDS > 64 +#error The current tuple implementation is limited to 64 fields +#endif #define BLOCK_VALS 4 @@ -61,20 +59,25 @@ struct _TupleBlock { * metadata. This is not the same as a playlist entry, though. */ struct _Tuple { - int refcount; int64_t setmask; TupleBlock * blocks; - int nsubtunes; /**< Number of subtunes, if any. Values greater than 0 - mean that there are subtunes and #subtunes array - may be set. */ int *subtunes; /**< Array of int containing subtune index numbers. Can be NULL if indexing is linear or if there are no subtunes. */ + int nsubtunes; /**< Number of subtunes, if any. Values greater than 0 + mean that there are subtunes and #subtunes array + may be set. */ + + int refcount; + TinyLock lock; }; #define BIT(i) ((int64_t) 1 << (i)) +#define LOCK(t) tiny_lock ((TinyLock *) & t->lock) +#define UNLOCK(t) tiny_unlock ((TinyLock *) & t->lock) + /** Ordered table of basic #Tuple field names and their #TupleValueType. */ static const TupleBasicType tuple_fields[TUPLE_FIELDS] = { @@ -154,8 +157,8 @@ static const FieldDictEntry field_dict[TUPLE_FIELDS] = { {"track-number", FIELD_TRACK_NUMBER}, {"year", FIELD_YEAR}}; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - +#define VALID_FIELD(f) ((f) >= 0 && (f) < TUPLE_FIELDS) +#define FIELD_TYPE(f) (tuple_fields[f].type) static int field_dict_compare (const void * a, const void * b) { @@ -168,26 +171,18 @@ EXPORT int tuple_field_by_name (const char * name) FieldDictEntry * found = bsearch (& find, field_dict, TUPLE_FIELDS, sizeof (FieldDictEntry), field_dict_compare); - if (found) - return found->field; - - fprintf (stderr, "Unknown tuple field name \"%s\".\n", name); - return -1; + return found ? found->field : -1; } EXPORT const char * tuple_field_get_name (int field) { - if (field < 0 || field >= TUPLE_FIELDS) - return NULL; - + g_return_val_if_fail (VALID_FIELD (field), NULL); return tuple_fields[field].name; } EXPORT TupleValueType tuple_field_get_type (int field) { - if (field < 0 || field >= TUPLE_FIELDS) - return TUPLE_UNKNOWN; - + g_return_val_if_fail (VALID_FIELD (field), TUPLE_UNKNOWN); return tuple_fields[field].type; } @@ -240,7 +235,7 @@ static TupleVal * lookup_val (Tuple * tuple, int field, bool_t add, bool_t remov return & block->vals[0]; } -static void tuple_destroy_unlocked (Tuple * tuple) +static void tuple_destroy (Tuple * tuple) { TupleBlock * next; for (TupleBlock * block = tuple->blocks; block; block = next) @@ -254,13 +249,10 @@ static void tuple_destroy_unlocked (Tuple * tuple) str_unref (block->vals[i].str); } - memset (block, 0, sizeof (TupleBlock)); g_slice_free (TupleBlock, block); } - g_free(tuple->subtunes); - - memset (tuple, 0, sizeof (Tuple)); + g_free (tuple->subtunes); g_slice_free (Tuple, tuple); } @@ -273,11 +265,8 @@ EXPORT Tuple * tuple_new (void) EXPORT Tuple * tuple_ref (Tuple * tuple) { - pthread_mutex_lock (& mutex); + __sync_fetch_and_add (& tuple->refcount, 1); - tuple->refcount ++; - - pthread_mutex_unlock (& mutex); return tuple; } @@ -286,22 +275,10 @@ EXPORT void tuple_unref (Tuple * tuple) if (! tuple) return; - pthread_mutex_lock (& mutex); - - if (! -- tuple->refcount) - tuple_destroy_unlocked (tuple); - - pthread_mutex_unlock (& mutex); + if (! __sync_sub_and_fetch (& tuple->refcount, 1)) + tuple_destroy (tuple); } -/** - * Sets filename/URI related fields of a #Tuple structure, based - * on the given filename argument. The fields set are: - * #FIELD_FILE_PATH, #FIELD_FILE_NAME and #FIELD_FILE_EXT. - * - * @param[in] filename Filename URI. - * @param[in,out] tuple Tuple structure to manipulate. - */ EXPORT void tuple_set_filename (Tuple * tuple, const char * filename) { const char * base, * ext, * sub; @@ -311,34 +288,27 @@ EXPORT void tuple_set_filename (Tuple * tuple, const char * filename) char path[base - filename + 1]; str_decode_percent (filename, base - filename, path); - tuple_set_str (tuple, FIELD_FILE_PATH, NULL, path); + tuple_set_str (tuple, FIELD_FILE_PATH, path); char name[ext - base + 1]; str_decode_percent (base, ext - base, name); - tuple_set_str (tuple, FIELD_FILE_NAME, NULL, name); + tuple_set_str (tuple, FIELD_FILE_NAME, name); if (ext < sub) { char extbuf[sub - ext]; str_decode_percent (ext + 1, sub - ext - 1, extbuf); - tuple_set_str (tuple, FIELD_FILE_EXT, NULL, extbuf); + tuple_set_str (tuple, FIELD_FILE_EXT, extbuf); } if (sub[0]) - tuple_set_int (tuple, FIELD_SUBSONG_ID, NULL, isub); + tuple_set_int (tuple, FIELD_SUBSONG_ID, isub); } -/** - * Creates a copy of given Tuple structure, with copied data. - * - * @param[in] src Tuple structure to be made a copy of. - * @return Pointer to newly allocated Tuple. - */ EXPORT Tuple * tuple_copy (const Tuple * old) { - pthread_mutex_lock (& mutex); - Tuple * new = tuple_new (); + LOCK (old); for (int f = 0; f < TUPLE_FIELDS; f ++) { @@ -358,83 +328,56 @@ EXPORT Tuple * tuple_copy (const Tuple * old) if (old->subtunes) new->subtunes = g_memdup (old->subtunes, sizeof (int) * old->nsubtunes); - pthread_mutex_unlock (& mutex); + UNLOCK (old); return new; } -/** - * Allocates a new #Tuple structure, setting filename/URI related - * fields based on the given filename argument by calling #tuple_set_filename. - * - * @param[in] filename Filename URI. - * @return Pointer to newly allocated Tuple. - */ -EXPORT Tuple * -tuple_new_from_filename(const char *filename) +EXPORT Tuple * tuple_new_from_filename (const char * filename) { - Tuple *tuple = tuple_new(); - - tuple_set_filename(tuple, filename); + Tuple * tuple = tuple_new (); + tuple_set_filename (tuple, filename); return tuple; } -EXPORT void tuple_set_int (Tuple * tuple, int nfield, const char * field, int x) +EXPORT void tuple_set_int (Tuple * tuple, int field, int x) { - if (nfield < 0) - nfield = tuple_field_by_name (field); - if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_INT) - return; + g_return_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_INT); + LOCK (tuple); - pthread_mutex_lock (& mutex); - - TupleVal * val = lookup_val (tuple, nfield, TRUE, FALSE); + TupleVal * val = lookup_val (tuple, field, TRUE, FALSE); val->x = x; - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); } -EXPORT void tuple_set_str (Tuple * tuple, int nfield, const char * field, const char * str) +EXPORT void tuple_set_str (Tuple * tuple, int field, const char * str) { - if (! str) - { - tuple_unset (tuple, nfield, field); - return; - } + g_return_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_STRING); - if (! g_utf8_validate (str, -1, NULL)) + if (! str) { - fprintf (stderr, "Invalid UTF-8: %s\n", str); + tuple_unset (tuple, field); return; } - if (nfield < 0) - nfield = tuple_field_by_name (field); - if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_STRING) - return; - - pthread_mutex_lock (& mutex); + LOCK (tuple); - TupleVal * val = lookup_val (tuple, nfield, TRUE, FALSE); - if (val->str) - str_unref (val->str); - val->str = str_get (str); + TupleVal * val = lookup_val (tuple, field, TRUE, FALSE); + str_unref (val->str); + val->str = str_to_utf8 (str, -1); - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); } -EXPORT void tuple_unset (Tuple * tuple, int nfield, const char * field) +EXPORT void tuple_unset (Tuple * tuple, int field) { - if (nfield < 0) - nfield = tuple_field_by_name (field); - if (nfield < 0 || nfield >= TUPLE_FIELDS) - return; + g_return_if_fail (VALID_FIELD (field)); + LOCK (tuple); - pthread_mutex_lock (& mutex); - - TupleVal * val = lookup_val (tuple, nfield, FALSE, TRUE); + TupleVal * val = lookup_val (tuple, field, FALSE, TRUE); if (val) { - if (tuple_fields[nfield].type == TUPLE_STRING) + if (tuple_fields[field].type == TUPLE_STRING) { str_unref (val->str); val->str = NULL; @@ -443,96 +386,52 @@ EXPORT void tuple_unset (Tuple * tuple, int nfield, const char * field) val->x = 0; } - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); } -/** - * Returns #TupleValueType of given #Tuple field. - * Desired field can be specified either by key name or if it is - * one of basic fields, by #TupleBasicType index. - * - * @param[in] tuple #Tuple structure pointer. - * @param[in] cnfield #TupleBasicType index or -1 if key name is to be used instead. - * @param[in] field String acting as key name or NULL if nfield is used. - * @return #TupleValueType of the field or TUPLE_UNKNOWN if there was an error. - */ -EXPORT TupleValueType tuple_get_value_type (const Tuple * tuple, int nfield, const char * field) +EXPORT TupleValueType tuple_get_value_type (const Tuple * tuple, int field) { - if (nfield < 0) - nfield = tuple_field_by_name (field); - if (nfield < 0 || nfield >= TUPLE_FIELDS) - return TUPLE_UNKNOWN; - - pthread_mutex_lock (& mutex); + g_return_val_if_fail (VALID_FIELD (field), TUPLE_UNKNOWN); + LOCK (tuple); - TupleValueType type = TUPLE_UNKNOWN; + TupleVal * val = lookup_val ((Tuple *) tuple, field, FALSE, FALSE); + TupleValueType type = val ? FIELD_TYPE (field) : TUPLE_UNKNOWN; - TupleVal * val = lookup_val ((Tuple *) tuple, nfield, FALSE, FALSE); - if (val) - type = tuple_fields[nfield].type; - - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); return type; } -EXPORT char * tuple_get_str (const Tuple * tuple, int nfield, const char * field) +EXPORT char * tuple_get_str (const Tuple * tuple, int field) { - if (nfield < 0) - nfield = tuple_field_by_name (field); - if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_STRING) - return NULL; - - pthread_mutex_lock (& mutex); + g_return_val_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_STRING, NULL); + LOCK (tuple); - char * str = NULL; + TupleVal * val = lookup_val ((Tuple *) tuple, field, FALSE, FALSE); + char * str = val ? str_ref (val->str) : NULL; - TupleVal * val = lookup_val ((Tuple *) tuple, nfield, FALSE, FALSE); - if (val) - str = str_ref (val->str); - - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); return str; } -/** - * Returns integer associated to #Tuple field. - * Desired field can be specified either by key name or if it is - * one of basic fields, by #TupleBasicType index. - * - * @param[in] tuple #Tuple structure pointer. - * @param[in] cnfield #TupleBasicType index or -1 if key name is to be used instead. - * @param[in] field String acting as key name or NULL if nfield is used. - * @return Integer value or 0 if the field/key did not exist. - * - * @bug There is no way to distinguish error situations if the associated value is zero. - */ -EXPORT int tuple_get_int (const Tuple * tuple, int nfield, const char * field) +EXPORT int tuple_get_int (const Tuple * tuple, int field) { - if (nfield < 0) - nfield = tuple_field_by_name (field); - if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_INT) - return 0; - - pthread_mutex_lock (& mutex); + g_return_val_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_INT, -1); + LOCK (tuple); - int x = 0; - - TupleVal * val = lookup_val ((Tuple *) tuple, nfield, FALSE, FALSE); - if (val) - x = val->x; + TupleVal * val = lookup_val ((Tuple *) tuple, field, FALSE, FALSE); + int x = val ? val->x : -1; - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); return x; } -#define APPEND(b, ...) snprintf (b + strlen (b), sizeof b - strlen (b), \ - __VA_ARGS__) +#define APPEND(b, ...) snprintf (b + strlen (b), sizeof b - strlen (b), __VA_ARGS__) EXPORT void tuple_set_format (Tuple * t, const char * format, int chans, int rate, int brate) { if (format) - tuple_set_str (t, FIELD_CODEC, NULL, format); + tuple_set_str (t, FIELD_CODEC, format); char buf[32]; buf[0] = 0; @@ -544,8 +443,7 @@ EXPORT void tuple_set_format (Tuple * t, const char * format, int chans, int rat else if (chans == 2) APPEND (buf, _("Stereo")); else - APPEND (buf, dngettext (PACKAGE, "%d channel", "%d channels", - chans), chans); + APPEND (buf, dngettext (PACKAGE, "%d channel", "%d channels", chans), chans); if (rate > 0) APPEND (buf, ", "); @@ -555,15 +453,15 @@ EXPORT void tuple_set_format (Tuple * t, const char * format, int chans, int rat APPEND (buf, "%d kHz", rate / 1000); if (buf[0]) - tuple_set_str (t, FIELD_QUALITY, NULL, buf); + tuple_set_str (t, FIELD_QUALITY, buf); if (brate > 0) - tuple_set_int (t, FIELD_BITRATE, NULL, brate); + tuple_set_int (t, FIELD_BITRATE, brate); } EXPORT void tuple_set_subtunes (Tuple * tuple, int n_subtunes, const int * subtunes) { - pthread_mutex_lock (& mutex); + LOCK (tuple); g_free (tuple->subtunes); tuple->subtunes = NULL; @@ -572,45 +470,27 @@ EXPORT void tuple_set_subtunes (Tuple * tuple, int n_subtunes, const int * subtu if (subtunes) tuple->subtunes = g_memdup (subtunes, sizeof (int) * n_subtunes); - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); } EXPORT int tuple_get_n_subtunes (Tuple * tuple) { - pthread_mutex_lock (& mutex); + LOCK (tuple); int n_subtunes = tuple->nsubtunes; - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); return n_subtunes; } EXPORT int tuple_get_nth_subtune (Tuple * tuple, int n) { - pthread_mutex_lock (& mutex); + LOCK (tuple); int subtune = -1; if (n >= 0 && n < tuple->nsubtunes) subtune = tuple->subtunes ? tuple->subtunes[n] : 1 + n; - pthread_mutex_unlock (& mutex); + UNLOCK (tuple); return subtune; } - -EXPORT char * tuple_format_title (Tuple * tuple, const char * format) -{ - static const gint fallbacks[] = {FIELD_TITLE, FIELD_FILE_NAME, FIELD_FILE_PATH}; - - char * title = tuple_formatter_process_string (tuple, format); - - for (int i = 0; i < G_N_ELEMENTS (fallbacks); i ++) - { - if (title && title[0]) - break; - - str_unref (title); - title = tuple_get_str (tuple, fallbacks[i], NULL); - } - - return title ? title : str_get (""); -} diff --git a/src/libaudcore/tuple.h b/src/libaudcore/tuple.h index 55f3e2e..873f699 100644 --- a/src/libaudcore/tuple.h +++ b/src/libaudcore/tuple.h @@ -1,6 +1,6 @@ /* * tuple.h - * Copyright 2007-2011 William Pitcock, Christian Birchinger, Matti Hämäläinen, + * Copyright 2007-2013 William Pitcock, Christian Birchinger, Matti Hämäläinen, * Giacomo Lozito, Eugene Zagidullin, and John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -90,6 +90,7 @@ const char * tuple_field_get_name (int field); TupleValueType tuple_field_get_type (int field); typedef struct _Tuple Tuple; +typedef struct _TupleFormatter TupleFormatter; /* Creates a new, blank tuple with a reference count of one. */ Tuple * tuple_new (void); @@ -105,45 +106,41 @@ void tuple_unref (Tuple * tuple); /* Makes a copy of <tuple>. Only use tuple_copy() if you need to modify one * copy of the tuple while not modifying the other. In most cases, tuple_ref() * is more appropriate. */ -Tuple *tuple_copy(const Tuple *); +Tuple * tuple_copy (const Tuple * tuple); /* Parses the URI <filename> and sets FIELD_FILE_NAME, FIELD_FILE_PATH, * FIELD_FILE_EXT, and FIELD_SUBSONG_ID accordingly. */ -void tuple_set_filename(Tuple *tuple, const char *filename); +void tuple_set_filename (Tuple * tuple, const char * filename); /* Convenience function, equivalent to calling tuple_new() and then * tuple_set_filename(). */ -Tuple *tuple_new_from_filename(const char *filename); +Tuple * tuple_new_from_filename (const char * filename); /* Sets a field to the integer value <x>. */ -void tuple_set_int (Tuple * tuple, int nfield, const char * field, int x); +void tuple_set_int (Tuple * tuple, int field, int x); -/* Sets the field specified by <nfield> (one of the FIELD_* constants) or - * <field> (one of the names returned by tuple_field_get_name() to the string - * value <str>. Only one of <nfield> or <field> may be set. If <nfield> is - * set, <field> must be NULL; if <field> is set, <nfield> must be -1. As a +/* Sets a field to the string value <str>. If <str> is not valid UTF-8, it will + * be converted according to the user's character set detection rules. As a * special case, if <str> is NULL, the result is equivalent to calling * tuple_unset(). */ -void tuple_set_str (Tuple * tuple, int nfield, const char * field, const char * str); +void tuple_set_str (Tuple * tuple, int field, const char * str); /* Clears any value that a field is currently set to. */ -void tuple_unset (Tuple * tuple, int nfield, const char * field); +void tuple_unset (Tuple * tuple, int field); /* Returns the value type of a field, or TUPLE_UNKNOWN if the field has not been * set to any value. */ -TupleValueType tuple_get_value_type (const Tuple * tuple, int nfield, - const char * field); +TupleValueType tuple_get_value_type (const Tuple * tuple, int field); /* Returns the string value of a field. The returned string is pooled and must * be released with str_unref() when no longer needed. If the field has not * been set to any value, returns NULL. */ -char * tuple_get_str (const Tuple * tuple, int nfield, const char * field); +char * tuple_get_str (const Tuple * tuple, int field); /* Returns the integer value of a field. If the field has not been set to any - * value, returns 0. (In hindsight, it would have been better to return -1 in - * this case. If you need to distinguish between a value of 0 and a field not - * set to any value, use tuple_get_value_type().) */ -int tuple_get_int (const Tuple * tuple, int nfield, const char * field); + * value, returns -1. If you need to distinguish between a value of -1 and a + * field not set to any value, use tuple_get_value_type(). */ +int tuple_get_int (const Tuple * tuple, int field); /* Fills in format-related fields (specifically FIELD_CODEC, FIELD_QUALITY, and * FIELD_BITRATE). Plugins should use this function instead of setting these @@ -168,10 +165,16 @@ int tuple_get_n_subtunes (Tuple * tuple); /* Returns the <n>th member of the subtune array. */ int tuple_get_nth_subtune (Tuple * tuple, int n); -/* Generates a formatted title string for <tuple> according to <format>. The - * syntax of <format> is documented in tuple_formatter.c. The returned string - * is pooled and must be released with str_unref() when no longer need. The - * returned string is never NULL, though it may be the empty string. */ -char * tuple_format_title (Tuple * tuple, const char * format); +/* Creates a tuple formatter object for the given format. The syntax of + * <format> is documented in tuple_formatter.c. */ +TupleFormatter * tuple_formatter_new (const char * format); + +/* Destroys a tuple formatter object. */ +void tuple_formatter_free (TupleFormatter * formatter); + +/* Generates a title string for <tuple> using the given formatter object. The + * returned string is pooled and must be released with str_unref() when no + * longer needed. Never returns NULL, but may return an empty string. */ +char * tuple_format_title (TupleFormatter * formatter, const Tuple * tuple); #endif /* LIBAUDCORE_TUPLE_H */ diff --git a/src/libaudcore/tuple_compiler.c b/src/libaudcore/tuple_compiler.c index 4834e17..74584c1 100644 --- a/src/libaudcore/tuple_compiler.c +++ b/src/libaudcore/tuple_compiler.c @@ -1,7 +1,7 @@ /* * tuple_compiler.c * Copyright (c) 2007 Matti 'ccr' Hämäläinen - * Copyright (c) 2011 John Lindgren + * Copyright (c) 2011-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,7 +31,6 @@ * currently there is just a single context, is a "global" context needed? */ -#include <ctype.h> #include <stdarg.h> #include <stdlib.h> #include <stdio.h> @@ -39,18 +38,19 @@ #include <glib.h> +#include "audstrings.h" #include "tuple_compiler.h" -#define MAX_STR (256) -#define MIN_ALLOC_NODES (8) -#define MIN_ALLOC_BUF (64) +#define MAX_STR (256) #define TUPLEZ_MAX_VARS (4) +#define GET_VAR(c, i) (& g_array_index ((c), TupleEvalVar, (i))) + #define tuple_error(ctx, ...) fprintf (stderr, "Tuple compiler: " __VA_ARGS__) enum { - OP_RAW = 0, /* plain text */ - OP_FIELD, /* a field/variable */ + OP_RAW = 0, /* plain text */ + OP_FIELD, /* a field/variable */ OP_EXISTS, OP_EQUALS, OP_NOT_EQUALS, @@ -67,42 +67,28 @@ enum { }; struct _TupleEvalNode { - int opcode; /* operator, see OP_ enums */ - int var[TUPLEZ_MAX_VARS]; /* tuple variable references */ - char *text; /* raw text, if any (OP_RAW) */ + int opcode; /* operator, see OP_ enums */ + int var[TUPLEZ_MAX_VARS]; /* tuple variable references */ + char *text; /* raw text, if any (OP_RAW) */ struct _TupleEvalNode *children, *next, *prev; /* children of this struct, and pointer to next node. */ }; typedef struct { char *name; - int type; /* Type of variable, see VAR_* */ + int type; /* Type of variable, see VAR_* */ int defvali; - TupleValueType ctype; /* Type of constant/def value */ + TupleValueType ctype; /* Type of constant/def value */ - int fieldidx; /* if >= 0: Index # of "pre-defined" Tuple fields */ + int fieldidx; /* if >= 0: Index # of "pre-defined" Tuple fields */ bool_t fieldread, fieldvalid; char * fieldstr; } TupleEvalVar; -struct _TupleEvalContext { - int nvariables; - TupleEvalVar **variables; -}; - - -static void tuple_evalctx_free_var(TupleEvalVar *var) -{ - g_free(var->name); - str_unref (var->fieldstr); - g_free(var); -} - - /* Initialize an evaluation context */ TupleEvalContext * tuple_evalctx_new(void) { - return g_new0(TupleEvalContext, 1); + return g_array_new (FALSE, TRUE, sizeof (TupleEvalVar)); } @@ -110,15 +96,15 @@ TupleEvalContext * tuple_evalctx_new(void) */ void tuple_evalctx_reset(TupleEvalContext *ctx) { - int i; - - for (i = 0; i < ctx->nvariables; i++) - if (ctx->variables[i]) { - ctx->variables[i]->fieldread = FALSE; - ctx->variables[i]->fieldvalid = FALSE; - str_unref (ctx->variables[i]->fieldstr); - ctx->variables[i]->fieldstr = NULL; - } + for (int i = 0; i < ctx->len; i ++) + { + TupleEvalVar * var = GET_VAR (ctx, i); + + var->fieldread = FALSE; + var->fieldvalid = FALSE; + str_unref (var->fieldstr); + var->fieldstr = NULL; + } } @@ -126,57 +112,52 @@ void tuple_evalctx_reset(TupleEvalContext *ctx) */ void tuple_evalctx_free(TupleEvalContext *ctx) { - int i; - - if (!ctx) return; + for (int i = 0; i < ctx->len; i ++) + { + TupleEvalVar * var = GET_VAR (ctx, i); - /* Deallocate variables */ - for (i = 0; i < ctx->nvariables; i++) - if (ctx->variables[i]) - tuple_evalctx_free_var(ctx->variables[i]); + str_unref (var->name); + str_unref (var->fieldstr); + } - g_free(ctx->variables); - g_free(ctx); + g_array_free (ctx, TRUE); } +/* note: may invalidate TupleEvalVar pointers due to reallocation */ static int tuple_evalctx_add_var (TupleEvalContext * ctx, const char * name, const int type, const TupleValueType ctype) { - int i; - TupleEvalVar *tmp = g_new0(TupleEvalVar, 1); + int field = -1; + + if (type == TUPLE_VAR_FIELD) + { + field = tuple_field_by_name (name); + if (field < 0) + return -1; + } + + int i = ctx->len; + g_array_set_size (ctx, i + 1); - tmp->name = g_strdup(name); - tmp->type = type; - tmp->fieldidx = -1; - tmp->ctype = ctype; + TupleEvalVar * var = GET_VAR (ctx, i); + + var->name = str_get (name); + var->type = type; + var->fieldidx = field; + var->ctype = ctype; - /* Find fieldidx, if any */ switch (type) { case TUPLE_VAR_FIELD: - tmp->fieldidx = tuple_field_by_name (name); - tmp->ctype = tuple_field_get_type (tmp->fieldidx); + var->ctype = tuple_field_get_type (field); break; case TUPLE_VAR_CONST: if (ctype == TUPLE_INT) - tmp->defvali = atoi(name); + var->defvali = atoi (name); break; } - /* Find a free slot */ - for (i = 0; i < ctx->nvariables; i++) - if (!ctx->variables[i]) { - ctx->variables[i] = tmp; - return i; - } - - i = ctx->nvariables; - ctx->variables = g_renew(TupleEvalVar *, ctx->variables, ctx->nvariables + MIN_ALLOC_NODES); - memset(&(ctx->variables[ctx->nvariables]), 0, MIN_ALLOC_NODES * sizeof(TupleEvalVar *)); - ctx->nvariables += MIN_ALLOC_NODES; - ctx->variables[i] = tmp; - return i; } @@ -196,12 +177,6 @@ static void tuple_evalnode_insert(TupleEvalNode **nodes, TupleEvalNode *node) } -static TupleEvalNode *tuple_evalnode_new(void) -{ - return g_new0(TupleEvalNode, 1); -} - - void tuple_evalnode_free(TupleEvalNode *expr) { TupleEvalNode *curr = expr, *next; @@ -209,12 +184,12 @@ void tuple_evalnode_free(TupleEvalNode *expr) while (curr) { next = curr->next; - g_free(curr->text); + str_unref (curr->text); if (curr->children) tuple_evalnode_free(curr->children); - g_free(curr); + g_slice_free (TupleEvalNode, curr); curr = next; } @@ -246,11 +221,11 @@ static bool_t tc_get_item(TupleEvalContext *ctx, } if (*literal == FALSE) { - while (*s != '\0' && *s != tmpendch && (isalnum(*s) || *s == '-') && i < (max - 1)) { + while (*s != '\0' && *s != tmpendch && (g_ascii_isalnum(*s) || *s == '-') && i < (max - 1)) { buf[i++] = *(s++); } - if (*s != tmpendch && *s != '}' && !isalnum(*s) && *s != '-') { + if (*s != tmpendch && *s != '}' && !g_ascii_isalnum(*s) && *s != '-') { tuple_error(ctx, "Invalid field '%s' in '%s'.\n", *str, item); return FALSE; } else if (*s != tmpendch) { @@ -286,20 +261,17 @@ static bool_t tc_get_item(TupleEvalContext *ctx, static int tc_get_variable(TupleEvalContext *ctx, char *name, int type) { - int i; TupleValueType ctype = TUPLE_UNKNOWN; - if (name == '\0') return -1; - - if (isdigit(name[0])) { + if (g_ascii_isdigit(name[0])) { ctype = TUPLE_INT; type = TUPLE_VAR_CONST; } else ctype = TUPLE_STRING; if (type != TUPLE_VAR_CONST) { - for (i = 0; i < ctx->nvariables; i++) - if (ctx->variables[i] && !strcmp(ctx->variables[i]->name, name)) + for (int i = 0; i < ctx->len; i ++) + if (! strcmp (GET_VAR (ctx, i)->name, name)) return i; } @@ -317,7 +289,7 @@ static bool_t tc_parse_construct(TupleEvalContext *ctx, TupleEvalNode **res, if (tc_get_item(ctx, c, tmps1, MAX_STR, ',', &literal1, "tag1", item)) { (*c)++; if (tc_get_item(ctx, c, tmps2, MAX_STR, ':', &literal2, "tag2", item)) { - TupleEvalNode *tmp = tuple_evalnode_new(); + TupleEvalNode *tmp = g_slice_new0 (TupleEvalNode); (*c)++; tmp->opcode = opcode; @@ -374,7 +346,7 @@ static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, co literal = FALSE; if (tc_get_item(ctx, &c, tmps1, MAX_STR, ':', &literal, "tag", item)) { c++; - tmp = tuple_evalnode_new(); + tmp = g_slice_new0 (TupleEvalNode); tmp->opcode = OP_EXISTS; if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) { tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr); @@ -395,7 +367,7 @@ static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, co if (*c == '"') { /* String */ c++; - } else if (isdigit(*c)) { + } else if (g_ascii_isdigit(*c)) { /* Integer */ } @@ -444,7 +416,7 @@ static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, co literal = FALSE; if (tc_get_item(ctx, &c, tmps1, MAX_STR, ':', &literal, "tag", item)) { c++; - tmp = tuple_evalnode_new(); + tmp = g_slice_new0 (TupleEvalNode); tmp->opcode = OP_IS_EMPTY; if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) { tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr); @@ -467,7 +439,7 @@ static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, co /* FIXME!! FIX ME! Check for external expressions */ /* I HAS A FIELD - A field. You has it. */ - tmp = tuple_evalnode_new(); + tmp = g_slice_new0 (TupleEvalNode); tmp->opcode = OP_FIELD; if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) { tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr); @@ -491,7 +463,7 @@ static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, co gssize i = 0; c++; - while (*c != '\0' && (isalnum(*c) || *c == '-') && *c != '}' && *c != ':' && i < (MAX_STR - 1)) + while (*c != '\0' && (g_ascii_isalnum(*c) || *c == '-') && *c != '}' && *c != ':' && i < (MAX_STR - 1)) tmps1[i++] = *(c++); tmps1[i] = '\0'; @@ -516,9 +488,9 @@ static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, co } tmps1[i] = '\0'; - tmp = tuple_evalnode_new(); + tmp = g_slice_new0 (TupleEvalNode); tmp->opcode = OP_RAW; - tmp->text = g_strdup(tmps1); + tmp->text = str_get (tmps1); tuple_evalnode_insert(&res, tmp); } } @@ -565,16 +537,16 @@ static bool_t tf_get_fieldval (TupleEvalVar * var, const Tuple * tuple) if (var->fieldread) return var->fieldvalid; - if (tuple_get_value_type (tuple, var->fieldidx, NULL) != var->ctype) { + if (tuple_get_value_type (tuple, var->fieldidx) != var->ctype) { var->fieldread = TRUE; var->fieldvalid = FALSE; return FALSE; } if (var->ctype == TUPLE_INT) - var->defvali = tuple_get_int (tuple, var->fieldidx, NULL); + var->defvali = tuple_get_int (tuple, var->fieldidx); else if (var->ctype == TUPLE_STRING) - var->fieldstr = tuple_get_str (tuple, var->fieldidx, NULL); + var->fieldstr = tuple_get_str (tuple, var->fieldidx); var->fieldread = TRUE; var->fieldvalid = TRUE; @@ -642,7 +614,7 @@ static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode * break; case OP_FIELD: - var0 = ctx->variables[curr->var[0]]; + var0 = GET_VAR (ctx, curr->var[0]); switch (var0->type) { case TUPLE_VAR_FIELD: @@ -653,7 +625,7 @@ static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode * break; case TUPLE_INT: - g_snprintf (tmps, sizeof (tmps), "%d", var0->defvali); + str_itoa (var0->defvali, tmps, sizeof (tmps)); str = tmps; break; @@ -669,8 +641,8 @@ static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode * case OP_NOT_EQUALS: case OP_LT: case OP_LTEQ: case OP_GT: case OP_GTEQ: - var0 = ctx->variables[curr->var[0]]; - var1 = ctx->variables[curr->var[1]]; + var0 = GET_VAR (ctx, curr->var[0]); + var1 = GET_VAR (ctx, curr->var[1]); type0 = tf_get_var(&tmps0, &tmpi0, var0, tuple); type1 = tf_get_var(&tmps1, &tmpi1, var1, tuple); @@ -705,14 +677,15 @@ static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode * break; case OP_EXISTS: - if (tf_get_fieldval (ctx->variables[curr->var[0]], tuple)) { + if (tf_get_fieldval (GET_VAR (ctx, curr->var[0]), tuple)) + { if (! tuple_formatter_eval_do (ctx, curr->children, tuple, out)) return FALSE; } break; case OP_IS_EMPTY: - var0 = ctx->variables[curr->var[0]]; + var0 = GET_VAR (ctx, curr->var[0]); if (tf_get_fieldval (var0, tuple)) { switch (var0->ctype) { @@ -725,9 +698,8 @@ static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode * tmps2 = var0->fieldstr; while (result && tmps2 && *tmps2 != '\0') { - gunichar uc = g_utf8_get_char(tmps2); - if (g_unichar_isspace(uc)) - tmps2 = g_utf8_next_char(tmps2); + if (g_ascii_isspace (* tmps2)) + tmps2 ++; else result = FALSE; } diff --git a/src/libaudcore/tuple_compiler.h b/src/libaudcore/tuple_compiler.h index 0e2ba54..1a951c1 100644 --- a/src/libaudcore/tuple_compiler.h +++ b/src/libaudcore/tuple_compiler.h @@ -23,12 +23,9 @@ #include <glib.h> #include <libaudcore/tuple.h> -struct _TupleEvalNode; +typedef GArray TupleEvalContext; typedef struct _TupleEvalNode TupleEvalNode; -struct _TupleEvalContext; -typedef struct _TupleEvalContext TupleEvalContext; - TupleEvalContext * tuple_evalctx_new(void); void tuple_evalctx_reset(TupleEvalContext *ctx); void tuple_evalctx_free(TupleEvalContext *ctx); diff --git a/src/libaudcore/tuple_formatter.c b/src/libaudcore/tuple_formatter.c index 04ea555..44c6e44 100644 --- a/src/libaudcore/tuple_formatter.c +++ b/src/libaudcore/tuple_formatter.c @@ -1,6 +1,6 @@ /* * tuple_formatter.c - * Copyright (c) 2007 William Pitcock + * Copyright (c) 2007-2013 William Pitcock and John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -18,11 +18,9 @@ */ #include <glib.h> -#include <pthread.h> -#include <string.h> +#include "tuple.h" #include "tuple_compiler.h" -#include "tuple_formatter.h" /* * the tuple formatter: @@ -47,48 +45,52 @@ * - %{function:args,arg2,...}: runs function and inserts the result. * * everything else is treated as raw text. - * additionally, plugins can add additional instructions and functions! */ -/* - * Compile a tuplez string and cache the result. - * This caches the result for the last string, so that - * successive calls are sped up. - */ +struct _TupleFormatter +{ + TupleEvalContext * context; + TupleEvalNode * node; + GString * buf; +}; -char * tuple_formatter_process_string (const Tuple * tuple, const char * string) +EXPORT TupleFormatter * tuple_formatter_new (const char * format) { - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock (& mutex); + TupleFormatter * formatter = g_slice_new (TupleFormatter); - static char *last_string = NULL; - static TupleEvalContext *last_ctx = NULL; - static TupleEvalNode *last_ev = NULL; + formatter->context = tuple_evalctx_new (); + formatter->node = tuple_formatter_compile (formatter->context, format); + formatter->buf = g_string_sized_new (255); - if (! last_string || strcmp (string, last_string)) - { - g_free(last_string); + return formatter; +} + +EXPORT void tuple_formatter_free (TupleFormatter * formatter) +{ + tuple_evalctx_free (formatter->context); + tuple_evalnode_free (formatter->node); + g_string_free (formatter->buf, TRUE); - if (last_ctx != NULL) - { - tuple_evalctx_free(last_ctx); - tuple_evalnode_free(last_ev); - } + g_slice_free (TupleFormatter, formatter); +} - last_ctx = tuple_evalctx_new(); - last_string = g_strdup(string); - last_ev = tuple_formatter_compile(last_ctx, last_string); - } +EXPORT char * tuple_format_title (TupleFormatter * formatter, const Tuple * tuple) +{ + tuple_formatter_eval (formatter->context, formatter->node, tuple, formatter->buf); + tuple_evalctx_reset (formatter->context); - static GString * buf; - if (! buf) - buf = g_string_sized_new (255); + if (formatter->buf->len) + return str_get (formatter->buf->str); - tuple_formatter_eval (last_ctx, last_ev, tuple, buf); - tuple_evalctx_reset (last_ctx); + /* formatting failed, try fallbacks */ + static const int fallbacks[] = {FIELD_TITLE, FIELD_FILE_NAME}; - char * result = str_get (buf->str); + for (int i = 0; i < ARRAY_LEN (fallbacks); i ++) + { + char * title = tuple_get_str (tuple, fallbacks[i]); + if (title) + return title; + } - pthread_mutex_unlock (& mutex); - return result; + return str_get (""); } diff --git a/src/libaudcore/vfs.c b/src/libaudcore/vfs.c index b1c3b03..000205f 100644 --- a/src/libaudcore/vfs.c +++ b/src/libaudcore/vfs.c @@ -1,6 +1,6 @@ /* * vfs.c - * Copyright 2006-2011 William Pitcock, Daniel Barkalow, Ralf Ertzinger, + * Copyright 2006-2013 William Pitcock, Daniel Barkalow, Ralf Ertzinger, * Yoshiki Yazawa, Matti Hämäläinen, and John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -18,16 +18,20 @@ * the use of this software. */ -#include <glib.h> -#include <inttypes.h> - #include "vfs.h" -#include "audstrings.h" + +#include <inttypes.h> #include <stdio.h> -#include <unistd.h> +#include <string.h> #include <sys/stat.h> #include <sys/types.h> -#include <string.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "vfs_local.h" #define VFS_SIG ('V' | ('F' << 8) | ('S' << 16)) @@ -123,24 +127,31 @@ vfs_fopen(const char * path, const char * mode) { g_return_val_if_fail (path && mode, NULL); - g_return_val_if_fail (lookup_func, NULL); - const char * s = strstr (path, "://"); - g_return_val_if_fail (s, NULL); - char scheme[s - path + 1]; - strncpy (scheme, path, s - path); - scheme[s - path] = 0; + VFSConstructor * vtable = NULL; - VFSConstructor * vtable = lookup_func (scheme); - if (! vtable) - return NULL; + if (! strncmp (path, "file://", 7)) + vtable = & vfs_local_vtable; + else + { + const char * s = strstr (path, "://"); + + if (! s) + { + fprintf (stderr, "Invalid URI: %s\n", path); + return NULL; + } + + SNCOPY (scheme, path, s - path); + + if (! (vtable = lookup_func (scheme))) + return NULL; + } const gchar * sub; uri_parse (path, NULL, NULL, & sub, NULL); - gchar buf[sub - path + 1]; - memcpy (buf, path, sub - path); - buf[sub - path] = 0; + SNCOPY (buf, path, sub - path); void * handle = vtable->vfs_fopen_impl (buf, mode); if (! handle) @@ -174,8 +185,6 @@ vfs_fclose(VFSFile * file) ret = -1; str_unref (file->uri); - - memset (file, 0, sizeof (VFSFile)); g_slice_free (VFSFile, file); return ret; @@ -234,12 +243,12 @@ EXPORT int64_t vfs_fwrite (const void * ptr, int64_t size, int64_t nmemb, VFSFil EXPORT int vfs_getc(VFSFile *file) { - g_return_val_if_fail (file && file->sig == VFS_SIG, EOF); + unsigned char c; - if (verbose) - logger ("VFS: <%p> getc\n", file); + if (vfs_fread (& c, 1, 1, file) != 1) + return EOF; - return file->base->vfs_getc_impl(file); + return c; } /** @@ -252,12 +261,10 @@ vfs_getc(VFSFile *file) EXPORT int vfs_ungetc(int c, VFSFile *file) { - g_return_val_if_fail (file && file->sig == VFS_SIG, EOF); - - if (verbose) - logger ("VFS: <%p> ungetc\n", file); + if (vfs_fseek (file, -1, SEEK_CUR) < 0) + return EOF; - return file->base->vfs_ungetc_impl(c, file); + return c; } /** @@ -285,23 +292,13 @@ vfs_fseek(VFSFile * file, SEEK_CUR ? "current" : whence == SEEK_SET ? "beginning" : whence == SEEK_END ? "end" : "invalid"); - return file->base->vfs_fseek_impl(file, offset, whence); -} - -/** - * Rewinds a VFS stream. - * - * @param file #VFSFile object that represents the VFS stream. - */ -EXPORT void -vfs_rewind(VFSFile * file) -{ - g_return_if_fail (file && file->sig == VFS_SIG); + if (! file->base->vfs_fseek_impl (file, offset, whence)) + return 0; if (verbose) - logger ("VFS: <%p> rewind\n", file); + logger ("VFS: <%p> seek failed!\n", file); - file->base->vfs_rewind_impl(file); + return -1; } /** @@ -415,8 +412,8 @@ vfs_file_test(const char * path, int test) #ifdef S_ISLNK if (test & VFS_IS_SYMLINK) { - struct stat st; - if (lstat (path2, & st) < 0) + GStatBuf st; + if (g_lstat (path2, & st) < 0) goto DONE; if (S_ISLNK (st.st_mode)) @@ -426,8 +423,8 @@ vfs_file_test(const char * path, int test) if (test & (VFS_IS_REGULAR | VFS_IS_DIR | VFS_IS_EXECUTABLE | VFS_EXISTS)) { - struct stat st; - if (stat (path2, & st) < 0) + GStatBuf st; + if (g_stat (path2, & st) < 0) goto DONE; if (S_ISREG (st.st_mode)) @@ -441,8 +438,7 @@ vfs_file_test(const char * path, int test) } DONE: - g_free (path2); - + str_unref (path2); return ! test; } @@ -455,14 +451,13 @@ DONE: EXPORT bool_t vfs_is_writeable(const char * path) { - struct stat info; + GStatBuf info; char * realfn = uri_to_filename (path); - if (stat(realfn, &info) == -1) + if (! realfn || g_stat (realfn, & info) < 0) return FALSE; - g_free(realfn); - + str_unref (realfn); return (info.st_mode & S_IWUSR); } diff --git a/src/libaudcore/vfs.h b/src/libaudcore/vfs.h index 006c3b7..8dcbc32 100644 --- a/src/libaudcore/vfs.h +++ b/src/libaudcore/vfs.h @@ -1,6 +1,6 @@ /* * vfs.h - * Copyright 2006-2011 William Pitcock, Daniel Barkalow, Ralf Ertzinger, + * Copyright 2006-2013 William Pitcock, Daniel Barkalow, Ralf Ertzinger, * Yoshiki Yazawa, Matti Hämäläinen, and John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -62,15 +62,14 @@ struct _VFSConstructor { int64_t (* vfs_fwrite_impl) (const void * ptr, int64_t size, int64_t nmemb, VFSFile * file); - /** A function pointer which points to a getc implementation. */ - int (* vfs_getc_impl) (VFSFile * stream); - /** A function pointer which points to an ungetc implementation. */ - int (* vfs_ungetc_impl) (int c, VFSFile * stream); + void (* obs_getc) (void); // obsolete + void (* obs_ungetc) (void); // obsolete /** A function pointer which points to a fseek implementation. */ int (* vfs_fseek_impl) (VFSFile * file, int64_t offset, int whence); - /** function pointer which points to a rewind implementation. */ - void (* vfs_rewind_impl) (VFSFile * file); + + void (* obs_rewind) (void); // obsolete + /** A function pointer which points to a ftell implementation. */ int64_t (* vfs_ftell_impl) (VFSFile * file); /** A function pointer which points to a feof implementation. */ @@ -111,34 +110,21 @@ int vfs_fprintf (VFSFile * stream, char const * format, ...) __attribute__ ((__format__ (__printf__, 2, 3))); int vfs_fseek (VFSFile * file, int64_t offset, int whence) WARN_RETURN; -void vfs_rewind (VFSFile * file); int64_t vfs_ftell (VFSFile * file) WARN_RETURN; int64_t vfs_fsize (VFSFile * file) WARN_RETURN; int vfs_ftruncate (VFSFile * file, int64_t length) WARN_RETURN; -bool_t vfs_fget_le16 (uint16_t * value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fget_le32 (uint32_t * value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fget_le64 (uint64_t * value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fget_be16 (uint16_t * value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fget_be32 (uint32_t * value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fget_be64 (uint64_t * value, VFSFile * stream) WARN_RETURN; - -bool_t vfs_fput_le16 (uint16_t value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fput_le32 (uint32_t value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fput_le64 (uint64_t value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fput_be16 (uint16_t value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fput_be32 (uint32_t value, VFSFile * stream) WARN_RETURN; -bool_t vfs_fput_be64 (uint64_t value, VFSFile * stream) WARN_RETURN; - bool_t vfs_is_streaming (VFSFile * file) WARN_RETURN; + +/* free returned string with str_unref() */ char * vfs_get_metadata (VFSFile * file, const char * field) WARN_RETURN; bool_t vfs_file_test (const char * path, int test) WARN_RETURN; bool_t vfs_is_writeable (const char * path) WARN_RETURN; bool_t vfs_is_remote (const char * path) WARN_RETURN; -void vfs_file_get_contents (const char * filename, void * * buf, int64_t * - size); +void vfs_file_read_all (VFSFile * file, void * * buf, int64_t * size); +void vfs_file_get_contents (const char * filename, void * * buf, int64_t * size); void vfs_set_lookup_func (VFSConstructor * (* func) (const char * scheme)); void vfs_set_verbose (bool_t verbose); diff --git a/src/libaudcore/vfs_async.c b/src/libaudcore/vfs_async.c index 88a47ea..d8864c6 100644 --- a/src/libaudcore/vfs_async.c +++ b/src/libaudcore/vfs_async.c @@ -1,6 +1,6 @@ /* * vfs_async.c - * Copyright 2010 William Pitcock + * Copyright 2010-2012 William Pitcock and John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/libaudcore/vfs_common.c b/src/libaudcore/vfs_common.c index 3e64b5c..18b05c7 100644 --- a/src/libaudcore/vfs_common.c +++ b/src/libaudcore/vfs_common.c @@ -1,7 +1,7 @@ /* * vfs_common.c - * Copyright 2006-2010 Tony Vroon, William Pitcock, Maciej Grela, - * Matti Hämäläinen, and John Lindgren + * Copyright 2006-2013 Tony Vroon, William Pitcock, Matti Hämäläinen, and + * John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -18,12 +18,12 @@ * the use of this software. */ -#include <glib.h> -#include <glib/gprintf.h> #include <stdio.h> -#include <stdlib.h> #include <string.h> +#include <glib.h> + +#include "audstrings.h" #include "vfs.h" /** @@ -94,7 +94,7 @@ EXPORT char *vfs_fgets(char *s, int n, VFSFile *stream) */ EXPORT int vfs_fputs(const char *s, VFSFile *stream) { - gsize n = strlen(s); + int64_t n = strlen(s); return ((vfs_fwrite(s, 1, n, stream) == n) ? n : EOF); } @@ -109,12 +109,8 @@ EXPORT int vfs_fputs(const char *s, VFSFile *stream) */ EXPORT int vfs_vfprintf(VFSFile *stream, char const *format, va_list args) { - char *string; - int rv = g_vasprintf(&string, format, args); - if (rv < 0) return rv; - rv = vfs_fputs(string, stream); - g_free(string); - return rv; + VSPRINTF (buf, format, args); + return vfs_fputs (buf, stream); } /** @@ -137,242 +133,66 @@ EXPORT int vfs_fprintf(VFSFile *stream, char const *format, ...) return rv; } -/** - * Gets contents of the file into a buffer. Buffer of filesize bytes - * is allocated by this function as necessary. - * - * @param filename URI of the file to read in. - * @param buf Pointer to a pointer variable of buffer. - * @param size Pointer to gsize variable that will hold the amount of - * read data e.g. filesize. - */ -EXPORT void vfs_file_get_contents (const char * filename, void * * buf, int64_t * size) +EXPORT void vfs_file_read_all (VFSFile * file, void * * bufp, int64_t * sizep) { - * buf = NULL; - * size = 0; - - VFSFile *fd; - gsize filled_size = 0, buf_size = 4096; - unsigned char * ptr; - - if ((fd = vfs_fopen(filename, "rb")) == NULL) - return; + char * buf = NULL; + int64_t size = vfs_fsize (file); - if ((* size = vfs_fsize (fd)) >= 0) + if (size >= 0) { - * buf = g_malloc (* size); - * size = vfs_fread (* buf, 1, * size, fd); - goto close_handle; + size = MIN (size, SIZE_MAX - 1); + buf = g_malloc (size + 1); + size = vfs_fread (buf, 1, size, file); } + else + { + size = 0; - if ((*buf = g_malloc(buf_size)) == NULL) - goto close_handle; - - ptr = *buf; - while (TRUE) { - gsize read_size = vfs_fread(ptr, 1, buf_size - filled_size, fd); - if (read_size == 0) break; - - filled_size += read_size; - ptr += read_size; - - if (filled_size == buf_size) { - buf_size += 4096; + size_t bufsize = 4096; + buf = g_malloc (bufsize); - *buf = g_realloc(*buf, buf_size); + size_t readsize; + while ((readsize = vfs_fread (buf + size, 1, bufsize - 1 - size, file))) + { + size += readsize; - if (*buf == NULL) - goto close_handle; + if (size == bufsize - 1) + { + if (bufsize > SIZE_MAX - 4096) + break; - ptr = (unsigned char *) (* buf) + filled_size; + bufsize += 4096; + buf = g_realloc (buf, bufsize); + } } } - *size = filled_size; + buf[size] = 0; // nul-terminate -close_handle: - vfs_fclose(fd); + * bufp = buf; + if (sizep) + * sizep = size; } - /** - * Reads an unsigned 16-bit Little Endian value from the stream - * into native endian format. - * - * @param value Pointer to the variable to read the value into. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fget_le16(uint16_t *value, VFSFile *stream) -{ - uint16_t tmp; - if (vfs_fread(&tmp, sizeof(tmp), 1, stream) != 1) - return FALSE; - *value = GUINT16_FROM_LE(tmp); - return TRUE; -} - -/** - * Reads an unsigned 32-bit Little Endian value from the stream into native endian format. - * - * @param value Pointer to the variable to read the value into. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fget_le32(uint32_t *value, VFSFile *stream) -{ - uint32_t tmp; - if (vfs_fread(&tmp, sizeof(tmp), 1, stream) != 1) - return FALSE; - *value = GUINT32_FROM_LE(tmp); - return TRUE; -} - -/** - * Reads an unsigned 64-bit Little Endian value from the stream into native endian format. - * - * @param value Pointer to the variable to read the value into. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fget_le64(uint64_t *value, VFSFile *stream) -{ - uint64_t tmp; - if (vfs_fread(&tmp, sizeof(tmp), 1, stream) != 1) - return FALSE; - *value = GUINT64_FROM_LE(tmp); - return TRUE; -} - - -/** - * Reads an unsigned 16-bit Big Endian value from the stream into native endian format. - * - * @param value Pointer to the variable to read the value into. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fget_be16(uint16_t *value, VFSFile *stream) -{ - uint16_t tmp; - if (vfs_fread(&tmp, sizeof(tmp), 1, stream) != 1) - return FALSE; - *value = GUINT16_FROM_BE(tmp); - return TRUE; -} - -/** - * Reads an unsigned 32-bit Big Endian value from the stream into native endian format. - * - * @param value Pointer to the variable to read the value into. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fget_be32(uint32_t *value, VFSFile *stream) -{ - uint32_t tmp; - if (vfs_fread(&tmp, sizeof(tmp), 1, stream) != 1) - return FALSE; - *value = GUINT32_FROM_BE(tmp); - return TRUE; -} - -/** - * Reads an unsigned 64-bit Big Endian value from the stream into native endian format. - * - * @param value Pointer to the variable to read the value into. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fget_be64(uint64_t *value, VFSFile *stream) -{ - uint64_t tmp; - if (vfs_fread(&tmp, sizeof(tmp), 1, stream) != 1) - return FALSE; - *value = GUINT64_FROM_BE(tmp); - return TRUE; -} - -/** - * Writes an unsigned 16-bit native endian value into the stream as a - * Little Endian value. - * - * @param value Value to write into the stream. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fput_le16(uint16_t value, VFSFile *stream) -{ - uint16_t tmp = GUINT16_TO_LE(value); - return vfs_fwrite(&tmp, sizeof(tmp), 1, stream) == 1; -} - -/** - * Writes an unsigned 32-bit native endian value into the stream as a - * Big Endian value. - * - * @param value Value to write into the stream. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fput_le32(uint32_t value, VFSFile *stream) -{ - uint32_t tmp = GUINT32_TO_LE(value); - return vfs_fwrite(&tmp, sizeof(tmp), 1, stream) == 1; -} - -/** - * Writes an unsigned 64-bit native endian value into the stream as a - * Big Endian value. - * - * @param value Value to write into the stream. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fput_le64(uint64_t value, VFSFile *stream) -{ - uint64_t tmp = GUINT64_TO_LE(value); - return vfs_fwrite(&tmp, sizeof(tmp), 1, stream) == 1; -} - -/** - * Writes an unsigned 16-bit native endian value into the stream as a - * Big Endian value. + * Gets contents of the file into a buffer. Buffer of filesize bytes + * is allocated by this function as necessary. * - * @param value Value to write into the stream. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. + * @param filename URI of the file to read in. + * @param buf Pointer to a pointer variable of buffer. + * @param size Pointer to gsize variable that will hold the amount of + * read data e.g. filesize. */ -EXPORT bool_t vfs_fput_be16(uint16_t value, VFSFile *stream) +EXPORT void vfs_file_get_contents (const char * filename, void * * buf, int64_t * size) { - uint16_t tmp = GUINT16_TO_BE(value); - return vfs_fwrite(&tmp, sizeof(tmp), 1, stream) == 1; -} + * buf = NULL; + if (size) + * size = 0; -/** - * Writes an unsigned 32-bit native endian value into the stream as a - * Big Endian value. - * - * @param value Value to write into the stream. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fput_be32(uint32_t value, VFSFile *stream) -{ - uint32_t tmp = GUINT32_TO_BE(value); - return vfs_fwrite(&tmp, sizeof(tmp), 1, stream) == 1; -} + VFSFile * file = vfs_fopen (filename, "r"); + if (! file) + return; -/** - * Writes an unsigned 64-bit native endian value into the stream as a - * Big Endian value. - * - * @param value Value to write into the stream. - * @param stream A #VFSFile object representing the stream. - * @return TRUE if read was succesful, FALSE if there was an error. - */ -EXPORT bool_t vfs_fput_be64(uint64_t value, VFSFile *stream) -{ - uint64_t tmp = GUINT64_TO_BE(value); - return vfs_fwrite(&tmp, sizeof(tmp), 1, stream) == 1; + vfs_file_read_all (file, buf, size); + vfs_fclose (file); } diff --git a/src/libaudcore/vfs_local.c b/src/libaudcore/vfs_local.c new file mode 100644 index 0000000..d91317a --- /dev/null +++ b/src/libaudcore/vfs_local.c @@ -0,0 +1,231 @@ +/* + * vfs_local.c + * Copyright 2009-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "vfs_local.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include "audstrings.h" + +#ifdef _WIN32 +#define fseeko fseeko64 +#define ftello ftello64 +#endif + +typedef enum { + OP_NONE, + OP_READ, + OP_WRITE +} LocalOp; + +typedef struct { + char * path; + FILE * stream; + int64_t cached_size; + LocalOp last_op; +} LocalFile; + +static void * local_fopen (const char * uri, const char * mode) +{ + char * path = uri_to_filename (uri); + g_return_val_if_fail (path, NULL); + + const char * suffix = ""; + +#ifdef _WIN32 + if (! strchr (mode, 'b')) /* binary mode (Windows) */ + suffix = "b"; +#else + if (! strchr (mode, 'e')) /* close on exec (POSIX) */ + suffix = "e"; +#endif + + SCONCAT2 (mode2, mode, suffix); + + FILE * stream = g_fopen (path, mode2); + + if (! stream) + { + perror (path); + str_unref (path); + return NULL; + } + + LocalFile * local = g_slice_new (LocalFile); + + local->path = path; + local->stream = stream; + local->cached_size = -1; + local->last_op = OP_NONE; + + return local; +} + +static int local_fclose (VFSFile * file) +{ + LocalFile * local = vfs_get_handle (file); + + int result = fclose (local->stream); + if (result < 0) + perror (local->path); + + str_unref (local->path); + g_slice_free (LocalFile, local); + + return result; +} + +static int64_t local_fread (void * ptr, int64_t size, int64_t nitems, VFSFile * file) +{ + LocalFile * local = vfs_get_handle (file); + + if (local->last_op == OP_WRITE) + { + if (fseeko (local->stream, 0, SEEK_CUR) < 0) /* flush buffers */ + { + perror (local->path); + return 0; + } + } + + local->last_op = OP_READ; + + clearerr (local->stream); + + int64_t result = fread (ptr, size, nitems, local->stream); + if (result < nitems && ferror (local->stream)) + perror (local->path); + + return result; +} + +static int64_t local_fwrite (const void * ptr, int64_t size, int64_t nitems, VFSFile * file) +{ + LocalFile * local = vfs_get_handle (file); + + if (local->last_op == OP_READ) + { + if (fseeko (local->stream, 0, SEEK_CUR) < 0) /* flush buffers */ + { + perror (local->path); + return 0; + } + } + + local->last_op = OP_WRITE; + local->cached_size = -1; + + clearerr (local->stream); + + int64_t result = fwrite (ptr, size, nitems, local->stream); + if (result < nitems && ferror (local->stream)) + perror (local->path); + + return result; +} + +static int local_fseek (VFSFile * file, int64_t offset, int whence) +{ + LocalFile * local = vfs_get_handle (file); + + int result = fseeko (local->stream, offset, whence); + if (result < 0) + perror (local->path); + + if (result == 0) + local->last_op = OP_NONE; + + return result; +} + +static int64_t local_ftell (VFSFile * file) +{ + LocalFile * local = vfs_get_handle (file); + return ftello (local->stream); +} + +static bool_t local_feof (VFSFile * file) +{ + LocalFile * local = vfs_get_handle (file); + return feof (local->stream); +} + +static int local_ftruncate (VFSFile * file, int64_t length) +{ + LocalFile * local = vfs_get_handle (file); + + if (local->last_op != OP_NONE) + { + if (fseeko (local->stream, 0, SEEK_CUR) < 0) /* flush buffers */ + { + perror (local->path); + return 0; + } + } + + int result = ftruncate (fileno (local->stream), length); + if (result < 0) + perror (local->path); + + if (result == 0) + { + local->last_op = OP_NONE; + local->cached_size = length; + } + + return result; +} + +static int64_t local_fsize (VFSFile * file) +{ + LocalFile * local = vfs_get_handle (file); + + if (local->cached_size >= 0) + return local->cached_size; + + int64_t saved_pos = ftello (local->stream); + + if (local_fseek (file, 0, SEEK_END) < 0) + return -1; + + int64_t length = ftello (local->stream); + + if (local_fseek (file, saved_pos, SEEK_SET) < 0) + return -1; + + return length; +} + +VFSConstructor vfs_local_vtable = { + .vfs_fopen_impl = local_fopen, + .vfs_fclose_impl = local_fclose, + .vfs_fread_impl = local_fread, + .vfs_fwrite_impl = local_fwrite, + .vfs_fseek_impl = local_fseek, + .vfs_ftell_impl = local_ftell, + .vfs_feof_impl = local_feof, + .vfs_ftruncate_impl = local_ftruncate, + .vfs_fsize_impl = local_fsize +}; diff --git a/src/libaudcore/tuple_formatter.h b/src/libaudcore/vfs_local.h index bb3052b..6fa3686 100644 --- a/src/libaudcore/tuple_formatter.h +++ b/src/libaudcore/vfs_local.h @@ -1,6 +1,6 @@ /* - * tuple_formatter.h - * Copyright (c) 2007 William Pitcock + * vfs_local.h + * Copyright 2013 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,12 +17,11 @@ * the use of this software. */ -#ifndef LIBAUDCORE_TUPLE_FORMATTER_H -#define LIBAUDCORE_TUPLE_FORMATTER_H +#ifndef LIBAUDCORE_VFS_LOCAL_H +#define LIBAUDCORE_VFS_LOCAL_H -#include <libaudcore/tuple.h> +#include "vfs.h" -/* returned string be released with str_unref() */ -char * tuple_formatter_process_string (const Tuple * tuple, const char * string); +extern VFSConstructor vfs_local_vtable; -#endif /* LIBAUDCORE_TUPLE_FORMATTER_H */ +#endif /* LIBAUDCORE_VFS_LOCAL_H */ |