Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
tuple.c
Go to the documentation of this file.
1 /*
2  * tuple.c
3  * Copyright 2007-2011 William Pitcock, Christian Birchinger, Matti Hämäläinen,
4  * Giacomo Lozito, Eugene Zagidullin, and John Lindgren
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice,
10  * this list of conditions, and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions, and the following disclaimer in the documentation
14  * provided with the distribution.
15  *
16  * This software is provided "as is" and without any warranty, express or
17  * implied. In no event shall the authors be liable for any damages arising from
18  * the use of this software.
19  */
20 
26 #include <glib.h>
27 #include <pthread.h>
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #include <audacious/i18n.h>
34 
35 #include "audstrings.h"
36 #include "tuple.h"
37 #include "tuple_formatter.h"
38 
39 #define BLOCK_VALS 4
40 
41 typedef struct {
42  char *name;
45 
46 typedef union {
47  char * str;
48  int x;
49 } TupleVal;
50 
51 typedef struct _TupleBlock TupleBlock;
52 
53 struct _TupleBlock {
54  TupleBlock * next;
57 };
58 
63 struct _Tuple {
64  int refcount;
65  int64_t setmask;
66  TupleBlock * blocks;
67 
68  int nsubtunes;
71  int *subtunes;
74 };
75 
76 #define BIT(i) ((int64_t) 1 << (i))
77 
81  { "artist", TUPLE_STRING },
82  { "title", TUPLE_STRING },
83  { "album", TUPLE_STRING },
84  { "comment", TUPLE_STRING },
85  { "genre", TUPLE_STRING },
86 
87  { "track-number", TUPLE_INT },
88  { "length", TUPLE_INT },
89  { "year", TUPLE_INT },
90  { "quality", TUPLE_STRING },
91 
92  { "codec", TUPLE_STRING },
93  { "file-name", TUPLE_STRING },
94  { "file-path", TUPLE_STRING },
95  { "file-ext", TUPLE_STRING },
96 
97  { "song-artist", TUPLE_STRING },
98  { "composer", TUPLE_STRING },
99  { "performer", TUPLE_STRING },
100  { "copyright", TUPLE_STRING },
101  { "date", TUPLE_STRING },
102 
103  { "subsong-id", TUPLE_INT },
104  { "subsong-num", TUPLE_INT },
105  { "mime-type", TUPLE_STRING },
106  { "bitrate", TUPLE_INT },
107 
108  { "segment-start", TUPLE_INT },
109  { "segment-end", TUPLE_INT },
110 
111  { "gain-album-gain", TUPLE_INT },
112  { "gain-album-peak", TUPLE_INT },
113  { "gain-track-gain", TUPLE_INT },
114  { "gain-track-peak", TUPLE_INT },
115  { "gain-gain-unit", TUPLE_INT },
116  { "gain-peak-unit", TUPLE_INT },
117 };
118 
119 typedef struct {
120  const char * name;
121  int field;
123 
124 /* used for binary search, MUST be in alphabetical order */
126  {"album", FIELD_ALBUM},
127  {"artist", FIELD_ARTIST},
128  {"bitrate", FIELD_BITRATE},
129  {"codec", FIELD_CODEC},
130  {"comment", FIELD_COMMENT},
131  {"composer", FIELD_COMPOSER},
132  {"copyright", FIELD_COPYRIGHT},
133  {"date", FIELD_DATE},
134  {"file-ext", FIELD_FILE_EXT},
135  {"file-name", FIELD_FILE_NAME},
136  {"file-path", FIELD_FILE_PATH},
137  {"gain-album-gain", FIELD_GAIN_ALBUM_GAIN},
138  {"gain-album-peak", FIELD_GAIN_ALBUM_PEAK},
139  {"gain-gain-unit", FIELD_GAIN_GAIN_UNIT},
140  {"gain-peak-unit", FIELD_GAIN_PEAK_UNIT},
141  {"gain-track-gain", FIELD_GAIN_TRACK_GAIN},
142  {"gain-track-peak", FIELD_GAIN_TRACK_PEAK},
143  {"genre", FIELD_GENRE},
144  {"length", FIELD_LENGTH},
145  {"mime-type", FIELD_MIMETYPE},
146  {"performer", FIELD_PERFORMER},
147  {"quality", FIELD_QUALITY},
148  {"segment-end", FIELD_SEGMENT_END},
149  {"segment-start", FIELD_SEGMENT_START},
150  {"song-artist", FIELD_SONG_ARTIST},
151  {"subsong-id", FIELD_SUBSONG_ID},
152  {"subsong-num", FIELD_SUBSONG_NUM},
153  {"title", FIELD_TITLE},
154  {"track-number", FIELD_TRACK_NUMBER},
155  {"year", FIELD_YEAR}};
156 
157 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
158 
159 
160 static int field_dict_compare (const void * a, const void * b)
161 {
162  return strcmp (((FieldDictEntry *) a)->name, ((FieldDictEntry *) b)->name);
163 }
164 
165 EXPORT int tuple_field_by_name (const char * name)
166 {
167  FieldDictEntry find = {name, -1};
168  FieldDictEntry * found = bsearch (& find, field_dict, TUPLE_FIELDS,
170 
171  if (found)
172  return found->field;
173 
174  fprintf (stderr, "Unknown tuple field name \"%s\".\n", name);
175  return -1;
176 }
177 
178 EXPORT const char * tuple_field_get_name (int field)
179 {
180  if (field < 0 || field >= TUPLE_FIELDS)
181  return NULL;
182 
183  return tuple_fields[field].name;
184 }
185 
187 {
188  if (field < 0 || field >= TUPLE_FIELDS)
189  return TUPLE_UNKNOWN;
190 
191  return tuple_fields[field].type;
192 }
193 
194 static TupleVal * lookup_val (Tuple * tuple, int field, bool_t add, bool_t remove)
195 {
196  if ((tuple->setmask & BIT (field)))
197  {
198  for (TupleBlock * block = tuple->blocks; block; block = block->next)
199  {
200  for (int i = 0; i < BLOCK_VALS; i ++)
201  {
202  if (block->fields[i] == field)
203  {
204  if (remove)
205  {
206  tuple->setmask &= ~BIT (field);
207  block->fields[i] = -1;
208  }
209 
210  return & block->vals[i];
211  }
212  }
213  }
214  }
215 
216  if (! add)
217  return NULL;
218 
219  tuple->setmask |= BIT (field);
220 
221  for (TupleBlock * block = tuple->blocks; block; block = block->next)
222  {
223  for (int i = 0; i < BLOCK_VALS; i ++)
224  {
225  if (block->fields[i] < 0)
226  {
227  block->fields[i] = field;
228  return & block->vals[i];
229  }
230  }
231  }
232 
233  TupleBlock * block = g_slice_new0 (TupleBlock);
234  memset (block->fields, -1, BLOCK_VALS);
235 
236  block->next = tuple->blocks;
237  tuple->blocks = block;
238 
239  block->fields[0] = field;
240  return & block->vals[0];
241 }
242 
243 static void tuple_destroy_unlocked (Tuple * tuple)
244 {
245  TupleBlock * next;
246  for (TupleBlock * block = tuple->blocks; block; block = next)
247  {
248  next = block->next;
249 
250  for (int i = 0; i < BLOCK_VALS; i ++)
251  {
252  int field = block->fields[i];
253  if (field >= 0 && tuple_fields[field].type == TUPLE_STRING)
254  str_unref (block->vals[i].str);
255  }
256 
257  memset (block, 0, sizeof (TupleBlock));
258  g_slice_free (TupleBlock, block);
259  }
260 
261  g_free(tuple->subtunes);
262 
263  memset (tuple, 0, sizeof (Tuple));
264  g_slice_free (Tuple, tuple);
265 }
266 
267 EXPORT Tuple * tuple_new (void)
268 {
269  Tuple * tuple = g_slice_new0 (Tuple);
270  tuple->refcount = 1;
271  return tuple;
272 }
273 
274 EXPORT Tuple * tuple_ref (Tuple * tuple)
275 {
276  pthread_mutex_lock (& mutex);
277 
278  tuple->refcount ++;
279 
280  pthread_mutex_unlock (& mutex);
281  return tuple;
282 }
283 
284 EXPORT void tuple_unref (Tuple * tuple)
285 {
286  pthread_mutex_lock (& mutex);
287 
288  if (! -- tuple->refcount)
289  tuple_destroy_unlocked (tuple);
290 
291  pthread_mutex_unlock (& mutex);
292 }
293 
302 EXPORT void tuple_set_filename (Tuple * tuple, const char * filename)
303 {
304  const char * base, * ext, * sub;
305  int isub;
306 
307  uri_parse (filename, & base, & ext, & sub, & isub);
308 
309  char path[base - filename + 1];
310  str_decode_percent (filename, base - filename, path);
311  tuple_set_str (tuple, FIELD_FILE_PATH, NULL, path);
312 
313  char name[ext - base + 1];
314  str_decode_percent (base, ext - base, name);
315  tuple_set_str (tuple, FIELD_FILE_NAME, NULL, name);
316 
317  if (ext < sub)
318  {
319  char extbuf[sub - ext];
320  str_decode_percent (ext + 1, sub - ext - 1, extbuf);
321  tuple_set_str (tuple, FIELD_FILE_EXT, NULL, extbuf);
322  }
323 
324  if (sub[0])
325  tuple_set_int (tuple, FIELD_SUBSONG_ID, NULL, isub);
326 }
327 
334 EXPORT Tuple * tuple_copy (const Tuple * old)
335 {
336  pthread_mutex_lock (& mutex);
337 
338  Tuple * new = tuple_new ();
339 
340  for (int f = 0; f < TUPLE_FIELDS; f ++)
341  {
342  TupleVal * oldval = lookup_val ((Tuple *) old, f, FALSE, FALSE);
343  if (oldval)
344  {
345  TupleVal * newval = lookup_val (new, f, TRUE, FALSE);
346  if (tuple_fields[f].type == TUPLE_STRING)
347  newval->str = str_ref (oldval->str);
348  else
349  newval->x = oldval->x;
350  }
351  }
352 
353  new->nsubtunes = old->nsubtunes;
354 
355  if (old->subtunes)
356  new->subtunes = g_memdup (old->subtunes, sizeof (int) * old->nsubtunes);
357 
358  pthread_mutex_unlock (& mutex);
359  return new;
360 }
361 
369 EXPORT Tuple *
371 {
372  Tuple *tuple = tuple_new();
373 
374  tuple_set_filename(tuple, filename);
375  return tuple;
376 }
377 
378 EXPORT void tuple_set_int (Tuple * tuple, int nfield, const char * field, int x)
379 {
380  if (nfield < 0)
381  nfield = tuple_field_by_name (field);
382  if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_INT)
383  return;
384 
385  pthread_mutex_lock (& mutex);
386 
387  TupleVal * val = lookup_val (tuple, nfield, TRUE, FALSE);
388  val->x = x;
389 
390  pthread_mutex_unlock (& mutex);
391 }
392 
393 EXPORT void tuple_set_str (Tuple * tuple, int nfield, const char * field, const char * str)
394 {
395  if (! str)
396  {
397  tuple_unset (tuple, nfield, field);
398  return;
399  }
400 
401  if (! g_utf8_validate (str, -1, NULL))
402  {
403  fprintf (stderr, "Invalid UTF-8: %s\n", str);
404  return;
405  }
406 
407  if (nfield < 0)
408  nfield = tuple_field_by_name (field);
409  if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_STRING)
410  return;
411 
412  pthread_mutex_lock (& mutex);
413 
414  TupleVal * val = lookup_val (tuple, nfield, TRUE, FALSE);
415  if (val->str)
416  str_unref (val->str);
417  val->str = str_get (str);
418 
419  pthread_mutex_unlock (& mutex);
420 }
421 
422 EXPORT void tuple_unset (Tuple * tuple, int nfield, const char * field)
423 {
424  if (nfield < 0)
425  nfield = tuple_field_by_name (field);
426  if (nfield < 0 || nfield >= TUPLE_FIELDS)
427  return;
428 
429  pthread_mutex_lock (& mutex);
430 
431  TupleVal * val = lookup_val (tuple, nfield, FALSE, TRUE);
432  if (val)
433  {
434  if (tuple_fields[nfield].type == TUPLE_STRING)
435  {
436  str_unref (val->str);
437  val->str = NULL;
438  }
439  else
440  val->x = 0;
441  }
442 
443  pthread_mutex_unlock (& mutex);
444 }
445 
456 EXPORT TupleValueType tuple_get_value_type (const Tuple * tuple, int nfield, const char * field)
457 {
458  if (nfield < 0)
459  nfield = tuple_field_by_name (field);
460  if (nfield < 0 || nfield >= TUPLE_FIELDS)
461  return TUPLE_UNKNOWN;
462 
463  pthread_mutex_lock (& mutex);
464 
466 
467  TupleVal * val = lookup_val ((Tuple *) tuple, nfield, FALSE, FALSE);
468  if (val)
469  type = tuple_fields[nfield].type;
470 
471  pthread_mutex_unlock (& mutex);
472  return type;
473 }
474 
475 EXPORT char * tuple_get_str (const Tuple * tuple, int nfield, const char * field)
476 {
477  if (nfield < 0)
478  nfield = tuple_field_by_name (field);
479  if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_STRING)
480  return NULL;
481 
482  pthread_mutex_lock (& mutex);
483 
484  char * str = NULL;
485 
486  TupleVal * val = lookup_val ((Tuple *) tuple, nfield, FALSE, FALSE);
487  if (val)
488  str = str_ref (val->str);
489 
490  pthread_mutex_unlock (& mutex);
491  return str;
492 }
493 
506 EXPORT int tuple_get_int (const Tuple * tuple, int nfield, const char * field)
507 {
508  if (nfield < 0)
509  nfield = tuple_field_by_name (field);
510  if (nfield < 0 || nfield >= TUPLE_FIELDS || tuple_fields[nfield].type != TUPLE_INT)
511  return 0;
512 
513  pthread_mutex_lock (& mutex);
514 
515  int x = 0;
516 
517  TupleVal * val = lookup_val ((Tuple *) tuple, nfield, FALSE, FALSE);
518  if (val)
519  x = val->x;
520 
521  pthread_mutex_unlock (& mutex);
522  return x;
523 }
524 
525 #define APPEND(b, ...) snprintf (b + strlen (b), sizeof b - strlen (b), \
526  __VA_ARGS__)
527 
528 EXPORT void tuple_set_format (Tuple * t, const char * format, int chans, int rate,
529  int brate)
530 {
531  if (format)
532  tuple_set_str (t, FIELD_CODEC, NULL, format);
533 
534  char buf[32];
535  buf[0] = 0;
536 
537  if (chans > 0)
538  {
539  if (chans == 1)
540  APPEND (buf, _("Mono"));
541  else if (chans == 2)
542  APPEND (buf, _("Stereo"));
543  else
544  APPEND (buf, dngettext (PACKAGE, "%d channel", "%d channels",
545  chans), chans);
546 
547  if (rate > 0)
548  APPEND (buf, ", ");
549  }
550 
551  if (rate > 0)
552  APPEND (buf, "%d kHz", rate / 1000);
553 
554  if (buf[0])
555  tuple_set_str (t, FIELD_QUALITY, NULL, buf);
556 
557  if (brate > 0)
558  tuple_set_int (t, FIELD_BITRATE, NULL, brate);
559 }
560 
561 EXPORT void tuple_set_subtunes (Tuple * tuple, int n_subtunes, const int * subtunes)
562 {
563  pthread_mutex_lock (& mutex);
564 
565  g_free (tuple->subtunes);
566  tuple->subtunes = NULL;
567 
568  tuple->nsubtunes = n_subtunes;
569  if (subtunes)
570  tuple->subtunes = g_memdup (subtunes, sizeof (int) * n_subtunes);
571 
572  pthread_mutex_unlock (& mutex);
573 }
574 
575 EXPORT int tuple_get_n_subtunes (Tuple * tuple)
576 {
577  pthread_mutex_lock (& mutex);
578 
579  int n_subtunes = tuple->nsubtunes;
580 
581  pthread_mutex_unlock (& mutex);
582  return n_subtunes;
583 }
584 
585 EXPORT int tuple_get_nth_subtune (Tuple * tuple, int n)
586 {
587  pthread_mutex_lock (& mutex);
588 
589  int subtune = -1;
590  if (n >= 0 && n < tuple->nsubtunes)
591  subtune = tuple->subtunes ? tuple->subtunes[n] : 1 + n;
592 
593  pthread_mutex_unlock (& mutex);
594  return subtune;
595 }
596 
597 EXPORT char * tuple_format_title (Tuple * tuple, const char * format)
598 {
599  static const gint fallbacks[] = {FIELD_TITLE, FIELD_FILE_NAME, FIELD_FILE_PATH};
600 
601  char * title = tuple_formatter_process_string (tuple, format);
602 
603  for (int i = 0; i < G_N_ELEMENTS (fallbacks); i ++)
604  {
605  if (title && title[0])
606  break;
607 
608  str_unref (title);
609  title = tuple_get_str (tuple, fallbacks[i], NULL);
610  }
611 
612  return title ? title : str_get ("");
613 }