1 /* GStreamer
2 * Copyright (C) 2003 Erik Walthinsen <omega@cse.ogi.edu>
3 * 2003 Joshua N Pritikin <jpritikin@pobox.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
21 #include <gst/gst.h>
23 #include <unistd.h>
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <string.h>
30 #ifdef GST_DISABLE_DEPRECATED
31 #include <libxml/parser.h>
32 #endif
34 #include "gstindexers.h"
36 #define GST_TYPE_FILE_INDEX \
37 (gst_file_index_get_type ())
38 #define GST_FILE_INDEX(obj) \
39 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_FILE_INDEX, GstFileIndex))
40 #define GST_FILE_INDEX_CLASS(klass) \
41 (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_FILE_INDEX, GstFileIndexClass))
42 #define GST_IS_FILE_INDEX(obj) \
43 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_FILE_INDEX))
44 #define GST_IS_FILE_INDEX_CLASS(klass) \
45 (GST_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_FILE_INDEX))
47 /*
48 * Object model:
49 *
50 * We build an index to each entry for each id.
51 *
52 *
53 * fileindex
54 * -----------------------------...
55 * ! !
56 * id1 id2
57 * !
58 * GArray
59 *
60 * The fileindex creates a FileIndexId object for each writer id, a
61 * Hashtable is kept to map the id to the FileIndexId
62 *
63 * The FileIndexId also keeps all the values in a sorted GArray.
64 *
65 * Finding a value for an id/format requires locating the correct GArray,
66 * then do a binary search to get the required value.
67 *
68 * Unlike gstmemindex: All formats are assumed to sort to the
69 * same order. All formats are assumed to be available from
70 * any entry.
71 */
73 /*
74 * Each array element is (32bits flags, nformats * 64bits)
75 */
76 typedef struct
77 {
78 gint id;
79 gchar *id_desc;
80 gint nformats;
81 GstFormat *format;
82 GArray *array;
83 }
84 GstFileIndexId;
86 typedef struct _GstFileIndex GstFileIndex;
87 typedef struct _GstFileIndexClass GstFileIndexClass;
89 #define ARRAY_ROW_SIZE(_ii) \
90 (sizeof (gint32) + (_ii)->nformats * sizeof (gint64))
91 #define ARRAY_TOTAL_SIZE(_ii) \
92 (_ii->array->len * ARRAY_ROW_SIZE(_ii))
94 /* don't forget to convert to/from BE byte-order */
95 #define ARRAY_ROW_FLAGS(_row) \
96 (*((gint32*) (_row)))
97 #define ARRAY_ROW_VALUE(_row,_vx) \
98 (*(gint64*) (((gchar*)(_row)) + sizeof (gint32) + (_vx) * sizeof (gint64)))
100 GST_DEBUG_CATEGORY_STATIC (DC);
101 #define GST_CAT_DEFAULT DC
103 struct _GstFileIndex
104 {
105 GstIndex parent;
107 gchar *location;
108 gboolean is_loaded;
109 GSList *unresolved;
110 gint next_id;
111 GHashTable *id_index;
113 GstIndexEntry *ret_entry; /* hack to avoid leaking memory */
114 };
116 struct _GstFileIndexClass
117 {
118 GstIndexClass parent_class;
119 };
121 enum
122 {
123 ARG_0,
124 ARG_LOCATION,
125 };
127 static void gst_file_index_dispose (GObject * object);
129 static void
130 gst_file_index_set_property (GObject * object,
131 guint prop_id, const GValue * value, GParamSpec * pspec);
132 static void
133 gst_file_index_get_property (GObject * object,
134 guint prop_id, GValue * value, GParamSpec * pspec);
136 static gboolean
137 gst_file_index_get_writer_id (GstIndex * _index, gint * id,
138 gchar * writer_string);
140 static void gst_file_index_commit (GstIndex * index, gint writer_id);
141 static void gst_file_index_add_entry (GstIndex * index, GstIndexEntry * entry);
142 static GstIndexEntry *gst_file_index_get_assoc_entry (GstIndex * index, gint id,
143 GstIndexLookupMethod method,
144 GstAssocFlags flags,
145 GstFormat format, gint64 value, GCompareDataFunc func, gpointer user_data);
147 #define CLASS(file_index) GST_FILE_INDEX_CLASS (G_OBJECT_GET_CLASS (file_index))
149 GType gst_file_index_get_type (void);
151 G_DEFINE_TYPE (GstFileIndex, gst_file_index, GST_TYPE_INDEX);
153 static void
154 gst_file_index_class_init (GstFileIndexClass * klass)
155 {
156 GObjectClass *gobject_class;
157 GstIndexClass *gstindex_class;
159 gobject_class = (GObjectClass *) klass;
160 gstindex_class = (GstIndexClass *) klass;
162 gobject_class->dispose = gst_file_index_dispose;
163 gobject_class->set_property = gst_file_index_set_property;
164 gobject_class->get_property = gst_file_index_get_property;
166 gstindex_class->add_entry = gst_file_index_add_entry;
167 gstindex_class->get_assoc_entry = gst_file_index_get_assoc_entry;
168 gstindex_class->commit = gst_file_index_commit;
169 gstindex_class->get_writer_id = gst_file_index_get_writer_id;
171 g_object_class_install_property (gobject_class, ARG_LOCATION,
172 g_param_spec_string ("location", "File Location",
173 "Location of the index file", NULL,
174 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
175 }
177 static void
178 gst_file_index_init (GstFileIndex * index)
179 {
180 GST_DEBUG ("created new file index");
182 index->id_index = g_hash_table_new (g_int_hash, g_int_equal);
183 }
185 static void
186 _file_index_id_free (GstFileIndexId * index_id, gboolean is_mmapped)
187 {
188 if (index_id->id_desc)
189 g_free (index_id->id_desc);
190 if (index_id->format)
191 g_free (index_id->format);
192 if (index_id->array) {
193 if (is_mmapped)
194 munmap (index_id->array->data, ARRAY_TOTAL_SIZE (index_id));
195 g_array_free (index_id->array, !is_mmapped);
196 }
197 g_slice_free (GstFileIndexId, index_id);
198 }
200 static gboolean
201 _id_index_free_helper (gpointer _key, GstFileIndexId * index_id,
202 GstFileIndex * index)
203 {
204 _file_index_id_free (index_id, index->is_loaded);
205 return TRUE;
206 }
208 static void
209 gst_file_index_dispose (GObject * object)
210 {
211 GstFileIndex *index = GST_FILE_INDEX (object);
213 if (index->location) {
214 g_free (index->location);
215 index->location = NULL;
216 }
218 {
219 GSList *elem;
221 for (elem = index->unresolved; elem; elem = g_slist_next (elem))
222 _file_index_id_free (elem->data, index->is_loaded);
223 g_slist_free (index->unresolved);
224 index->unresolved = NULL;
225 }
227 g_hash_table_foreach_steal (index->id_index,
228 (GHRFunc) _id_index_free_helper, index);
229 g_hash_table_destroy (index->id_index);
230 index->id_index = NULL;
232 gst_index_entry_free (index->ret_entry); /* hack */
234 G_OBJECT_CLASS (gst_file_index_parent_class)->dispose (object);
235 }
237 struct fi_find_writer_context
238 {
239 const gchar *writer_string;
240 GstFileIndexId *ii;
241 };
243 static void
244 _fi_find_writer (gpointer key, gpointer val, gpointer data)
245 {
246 struct fi_find_writer_context *cx = data;
247 GstFileIndexId *ii = val;
249 if (strcmp (ii->id_desc, cx->writer_string) == 0)
250 cx->ii = ii;
251 }
253 static gboolean
254 gst_file_index_get_writer_id (GstIndex * _index,
255 gint * id, gchar * writer_string)
256 {
257 GstFileIndex *index = GST_FILE_INDEX (_index);
258 GSList *pending = index->unresolved;
259 gboolean match = FALSE;
260 GSList *elem;
262 if (!index->is_loaded)
263 return FALSE;
265 g_return_val_if_fail (id, FALSE);
266 g_return_val_if_fail (writer_string, FALSE);
268 index->unresolved = NULL;
270 for (elem = pending; elem; elem = g_slist_next (elem)) {
271 GstFileIndexId *ii = elem->data;
273 if (strcmp (ii->id_desc, writer_string) != 0) {
274 index->unresolved = g_slist_prepend (index->unresolved, ii);
275 continue;
276 }
278 if (match) {
279 GST_WARNING_OBJECT (index, "Duplicate matches for writer '%s'",
280 writer_string);
281 continue;
282 }
284 ii->id = *id = ++index->next_id;
285 g_hash_table_insert (index->id_index, &ii->id, ii);
286 match = TRUE;
287 }
289 g_slist_free (pending);
291 if (!match) {
292 struct fi_find_writer_context cx;
294 cx.writer_string = writer_string;
295 cx.ii = NULL;
296 g_hash_table_foreach (index->id_index, _fi_find_writer, &cx);
298 if (cx.ii) {
299 match = TRUE;
300 GST_DEBUG_OBJECT (index, "Resolved writer '%s' again", writer_string);
301 } else
302 GST_WARNING_OBJECT (index, "Can't resolve writer '%s'", writer_string);
303 }
305 return match;
306 }
308 static void
309 _fc_alloc_array (GstFileIndexId * id_index)
310 {
311 g_assert (!id_index->array);
312 id_index->array =
313 g_array_sized_new (FALSE, FALSE, ARRAY_ROW_SIZE (id_index), 0);
314 }
316 static void
317 gst_file_index_load (GstFileIndex * index)
318 {
319 xmlDocPtr doc;
320 xmlNodePtr root, part;
321 xmlChar *val;
323 g_assert (index->location);
324 g_return_if_fail (!index->is_loaded);
326 {
327 gchar *path = g_strdup_printf ("%s/gstindex.xml", index->location);
328 GError *err = NULL;
329 gchar *buf;
330 gsize len;
332 g_file_get_contents (path, &buf, &len, &err);
333 g_free (path);
334 if (err) {
335 GST_ERROR_OBJECT (index, "%s", err->message);
336 return;
337 }
339 doc = xmlParseMemory (buf, len);
340 g_free (buf);
341 }
343 //xmlDocFormatDump (stderr, doc, TRUE);
345 root = doc->xmlRootNode;
346 if (strcmp ((char *) root->name, "gstfileindex") != 0) {
347 GST_ERROR_OBJECT (index, "root node isn't a gstfileindex");
348 return;
349 }
351 val = xmlGetProp (root, (xmlChar *) "version");
352 if (!val || atoi ((char *) val) != 1) {
353 GST_ERROR_OBJECT (index, "version != 1");
354 return;
355 }
356 free (val);
358 for (part = root->children; part; part = part->next) {
359 if (strcmp ((char *) part->name, "writers") == 0) {
360 xmlNodePtr writer;
362 for (writer = part->children; writer; writer = writer->next) {
363 xmlChar *datafile = xmlGetProp (writer, (xmlChar *) "datafile");
364 gchar *path = g_strdup_printf ("%s/%s", index->location, datafile);
365 int fd;
366 GstFileIndexId *id_index;
367 xmlNodePtr wpart;
368 xmlChar *entries_str;
369 gpointer array_data;
371 free (datafile);
373 fd = open (path, O_RDONLY);
374 g_free (path);
375 if (fd < 0) {
376 GST_ERROR_OBJECT (index,
377 "Can't open '%s': %s", path, g_strerror (errno));
378 continue;
379 }
381 id_index = g_slice_new0 (GstFileIndexId);
382 id_index->id_desc = (char *) xmlGetProp (writer, (xmlChar *) "id");
384 for (wpart = writer->children; wpart; wpart = wpart->next) {
385 if (strcmp ((char *) wpart->name, "formats") == 0) {
386 xmlChar *count_str = xmlGetProp (wpart, (xmlChar *) "count");
387 gint fx = 0;
388 xmlNodePtr format;
390 id_index->nformats = atoi ((char *) count_str);
391 free (count_str);
393 id_index->format = g_new (GstFormat, id_index->nformats);
395 for (format = wpart->children; format; format = format->next) {
396 xmlChar *nick = xmlGetProp (format, (xmlChar *) "nick");
397 GstFormat fmt = gst_format_get_by_nick ((gchar *) nick);
399 if (fmt == GST_FORMAT_UNDEFINED)
400 GST_ERROR_OBJECT (index, "format '%s' undefined", nick);
401 g_assert (fx < id_index->nformats);
402 id_index->format[fx++] = fmt;
403 free (nick);
404 }
405 } else
406 GST_INFO_OBJECT (index, "unknown wpart '%s'", wpart->name);
407 }
409 g_assert (id_index->nformats > 0);
410 _fc_alloc_array (id_index);
411 g_assert (id_index->array->data == NULL); /* little bit risky */
413 entries_str = xmlGetProp (writer, (xmlChar *) "entries");
414 id_index->array->len = atoi ((char *) entries_str);
415 free (entries_str);
417 array_data =
418 mmap (NULL, ARRAY_TOTAL_SIZE (id_index), PROT_READ, MAP_SHARED, fd,
419 0);
420 close (fd);
421 if (array_data == MAP_FAILED) {
422 GST_ERROR_OBJECT (index,
423 "mmap %s failed: %s", path, g_strerror (errno));
424 continue;
425 }
427 id_index->array->data = array_data;
429 index->unresolved = g_slist_prepend (index->unresolved, id_index);
430 }
431 } else
432 GST_INFO_OBJECT (index, "unknown part '%s'", part->name);
433 }
435 xmlFreeDoc (doc);
437 GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
438 index->is_loaded = TRUE;
439 GST_LOG_OBJECT (index, "index %s loaded OK", index->location);
440 }
442 static void
443 gst_file_index_set_property (GObject * object,
444 guint prop_id, const GValue * value, GParamSpec * pspec)
445 {
446 GstFileIndex *index = GST_FILE_INDEX (object);
448 switch (prop_id) {
449 case ARG_LOCATION:
450 if (index->location)
451 g_free (index->location);
452 index->location = g_value_dup_string (value);
454 if (index->location && !g_hash_table_size (index->id_index))
455 gst_file_index_load (index);
456 break;
457 }
458 }
460 static void
461 gst_file_index_get_property (GObject * object,
462 guint prop_id, GValue * value, GParamSpec * pspec)
463 {
464 GstFileIndex *index = GST_FILE_INDEX (object);
466 switch (prop_id) {
467 case ARG_LOCATION:
468 g_value_set_string (value, index->location);
469 break;
470 }
471 }
473 static void
474 _file_index_id_save_xml (gpointer _key, GstFileIndexId * ii, xmlNodePtr writers)
475 {
476 const gint bufsize = 16;
477 gchar buf[16];
478 xmlNodePtr writer;
479 xmlNodePtr formats;
480 gint xx;
482 if (!ii->array) {
483 GST_INFO ("Index for %s is empty", ii->id_desc);
484 return;
485 }
487 writer = xmlNewChild (writers, NULL, (xmlChar *) "writer", NULL);
488 xmlSetProp (writer, (xmlChar *) "id", (xmlChar *) ii->id_desc);
489 g_snprintf (buf, bufsize, "%d", ii->array->len);
490 xmlSetProp (writer, (xmlChar *) "entries", (xmlChar *) buf);
491 g_snprintf (buf, bufsize, "%d", ii->id); /* any unique number is OK */
492 xmlSetProp (writer, (xmlChar *) "datafile", (xmlChar *) buf);
494 formats = xmlNewChild (writer, NULL, (xmlChar *) "formats", NULL);
495 g_snprintf (buf, bufsize, "%d", ii->nformats);
496 xmlSetProp (formats, (xmlChar *) "count", (xmlChar *) buf);
498 for (xx = 0; xx < ii->nformats; xx++) {
499 xmlNodePtr format = xmlNewChild (formats, NULL, (xmlChar *) "format", NULL);
500 const GstFormatDefinition *def = gst_format_get_details (ii->format[xx]);
502 xmlSetProp (format, (xmlChar *) "nick", (xmlChar *) def->nick);
503 }
504 }
506 /*
507 We must save the binary data in separate files because
508 mmap wants getpagesize() alignment. If we append all
509 the data to one file then we don't know the appropriate
510 padding since the page size isn't fixed.
511 */
512 static void
513 _file_index_id_save_entries (gpointer * _key,
514 GstFileIndexId * ii, gchar * prefix)
515 {
516 GError *err;
517 gchar *path;
518 GIOChannel *chan;
520 if (!ii->array)
521 return;
523 err = NULL;
524 path = g_strdup_printf ("%s/%d", prefix, ii->id);
525 chan = g_io_channel_new_file (path, "w", &err);
526 g_free (path);
527 if (err)
528 goto fail;
530 g_io_channel_set_encoding (chan, NULL, &err);
531 if (err)
532 goto fail;
534 g_io_channel_write_chars (chan,
535 ii->array->data, ARRAY_TOTAL_SIZE (ii), NULL, &err);
536 if (err)
537 goto fail;
539 g_io_channel_shutdown (chan, TRUE, &err);
540 if (err)
541 goto fail;
543 g_io_channel_unref (chan);
544 return;
546 fail:
547 GST_ERROR ("%s", err->message);
548 }
550 /*
551 We have to save the whole set of indexes into a single file
552 so it doesn't make sense to commit only a single writer.
554 i suggest:
556 gst_index_commit (index, -1);
557 */
558 static void
559 gst_file_index_commit (GstIndex * _index, gint _writer_id)
560 {
561 GstFileIndex *index = GST_FILE_INDEX (_index);
562 xmlDocPtr doc;
563 xmlNodePtr writers;
564 GError *err = NULL;
565 gchar *path;
566 GIOChannel *tocfile;
568 g_return_if_fail (index->location);
569 g_return_if_fail (!index->is_loaded);
571 GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
573 doc = xmlNewDoc ((xmlChar *) "1.0");
574 doc->xmlRootNode =
575 xmlNewDocNode (doc, NULL, (xmlChar *) "gstfileindex", NULL);
576 xmlSetProp (doc->xmlRootNode, (xmlChar *) "version", (xmlChar *) "1");
578 writers = xmlNewChild (doc->xmlRootNode, NULL, (xmlChar *) "writers", NULL);
579 g_hash_table_foreach (index->id_index,
580 (GHFunc) _file_index_id_save_xml, writers);
582 if (mkdir (index->location, 0777) && errno != EEXIST) {
583 GST_ERROR_OBJECT (index, "mkdir %s: %s", index->location,
584 g_strerror (errno));
585 return;
586 }
588 path = g_strdup_printf ("%s/gstindex.xml", index->location);
589 tocfile = g_io_channel_new_file (path, "w", &err);
590 g_free (path);
591 if (err) {
592 GST_ERROR_OBJECT (index, "%s", err->message);
593 return;
594 }
596 g_io_channel_set_encoding (tocfile, NULL, &err);
597 if (err) {
598 GST_ERROR_OBJECT (index, "%s", err->message);
599 return;
600 }
602 {
603 xmlChar *xmlmem;
604 int xmlsize;
606 xmlDocDumpMemory (doc, &xmlmem, &xmlsize);
607 g_io_channel_write_chars (tocfile, (gchar *) xmlmem, xmlsize, NULL, &err);
608 if (err) {
609 GST_ERROR_OBJECT (index, "%s", err->message);
610 return;
611 }
612 xmlFreeDoc (doc);
613 free (xmlmem);
614 }
616 g_io_channel_shutdown (tocfile, TRUE, &err);
617 if (err) {
618 GST_ERROR_OBJECT (index, "%s", err->message);
619 return;
620 }
622 g_io_channel_unref (tocfile);
624 g_hash_table_foreach (index->id_index,
625 (GHFunc) _file_index_id_save_entries, index->location);
626 }
628 static void
629 gst_file_index_add_id (GstIndex * index, GstIndexEntry * entry)
630 {
631 GstFileIndex *fileindex = GST_FILE_INDEX (index);
632 GstFileIndexId *id_index;
634 id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
636 if (!id_index) {
637 id_index = g_slice_new0 (GstFileIndexId);
639 id_index->id = entry->id;
640 id_index->id_desc = g_strdup (entry->data.id.description);
642 /* It would be useful to know the GType of the writer so
643 we can try to cope with changes in the id_desc path. */
645 g_hash_table_insert (fileindex->id_index, &id_index->id, id_index);
646 }
647 }
649 /* This algorithm differs from libc bsearch in the handling
650 of non-exact matches. */
652 static gboolean
653 _fc_bsearch (GArray * ary,
654 gint stride,
655 gint * ret,
656 GCompareDataFunc compare, gconstpointer sample, gpointer user_data)
657 {
658 gint first, last;
659 gint mid;
660 gint midsize;
661 gint cmp;
662 gint tx;
664 g_return_val_if_fail (compare, FALSE);
666 if (!ary->len) {
667 if (ret)
668 *ret = 0;
669 return FALSE;
670 }
672 first = 0;
673 last = ary->len - 1;
675 midsize = last - first;
677 while (midsize > 1) {
678 mid = first + midsize / 2;
680 cmp = (*compare) (sample, ary->data + mid * stride, user_data);
682 if (cmp == 0) {
683 /* if there are multiple matches then scan for the first match */
684 while (mid > 0 &&
685 (*compare) (sample, ary->data + (mid - 1) * stride, user_data) == 0)
686 --mid;
688 if (ret)
689 *ret = mid;
690 return TRUE;
691 }
693 if (cmp < 0)
694 last = mid - 1;
695 else
696 first = mid + 1;
698 midsize = last - first;
699 }
701 for (tx = first; tx <= last; tx++) {
702 cmp = (*compare) (sample, ary->data + tx * stride, user_data);
704 if (cmp < 0) {
705 if (ret)
706 *ret = tx;
707 return FALSE;
708 }
709 if (cmp == 0) {
710 if (ret)
711 *ret = tx;
712 return TRUE;
713 }
714 }
716 if (ret)
717 *ret = last + 1;
718 return FALSE;
719 }
721 static gint
722 file_index_compare (gconstpointer sample, gconstpointer row, gpointer user_data)
723 {
724 //GstFileIndexId *id_index = user_data;
725 const GstIndexAssociation *ca = sample;
726 gint64 val1 = ca->value;
727 gint64 val2_be = ARRAY_ROW_VALUE (row, ca->format);
728 gint64 val2 = GINT64_FROM_BE (val2_be);
729 gint64 diff = val2 - val1;
731 return (diff == 0 ? 0 : (diff < 0 ? 1 : -1));
732 }
734 static void
735 gst_file_index_add_association (GstIndex * index, GstIndexEntry * entry)
736 {
737 GstFileIndex *fileindex = GST_FILE_INDEX (index);
738 GstFileIndexId *id_index;
739 gint mx;
740 GstIndexAssociation sample;
741 gboolean exact;
743 id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
744 if (!id_index)
745 return;
747 if (!id_index->nformats) {
748 gint fx;
750 id_index->nformats = GST_INDEX_NASSOCS (entry);
751 GST_LOG_OBJECT (fileindex, "creating %d formats for %d",
752 id_index->nformats, entry->id);
753 id_index->format = g_new (GstFormat, id_index->nformats);
754 for (fx = 0; fx < id_index->nformats; fx++)
755 id_index->format[fx] = GST_INDEX_ASSOC_FORMAT (entry, fx);
756 _fc_alloc_array (id_index);
757 } else {
758 /* only sanity checking */
759 if (id_index->nformats != GST_INDEX_NASSOCS (entry))
760 GST_WARNING_OBJECT (fileindex, "arity change %d -> %d",
761 id_index->nformats, GST_INDEX_NASSOCS (entry));
762 else {
763 gint fx;
765 for (fx = 0; fx < id_index->nformats; fx++)
766 if (id_index->format[fx] != GST_INDEX_ASSOC_FORMAT (entry, fx))
767 GST_WARNING_OBJECT (fileindex, "format[%d] changed %d -> %d",
768 fx, id_index->format[fx], GST_INDEX_ASSOC_FORMAT (entry, fx));
769 }
770 }
772 /* this is a hack, we should use a private structure instead */
773 sample.format = GST_FORMAT_UNDEFINED;
774 sample.value = GST_INDEX_ASSOC_VALUE (entry, 0);
776 exact =
777 _fc_bsearch (id_index->array, ARRAY_ROW_SIZE (id_index),
778 &mx, file_index_compare, &sample, id_index);
780 if (exact) {
781 /* maybe overwrite instead? */
782 GST_DEBUG_OBJECT (index,
783 "Ignoring duplicate index association at %" G_GINT64_FORMAT,
784 GST_INDEX_ASSOC_VALUE (entry, 0));
785 return;
786 }
788 {
789 gchar *row_data = (gchar *) g_malloc (ARRAY_ROW_SIZE (id_index));
790 gint fx;
792 gint32 flags_host = GST_INDEX_ASSOC_FLAGS (entry);
794 ARRAY_ROW_FLAGS (row_data) = GINT32_TO_BE (flags_host);
796 for (fx = 0; fx < id_index->nformats; fx++) {
797 gint64 val_host = GST_INDEX_ASSOC_VALUE (entry, fx);
799 ARRAY_ROW_VALUE (row_data, fx) = GINT64_TO_BE (val_host);
800 }
802 g_array_insert_vals (id_index->array, mx, row_data, 1);
804 g_free (row_data);
805 }
806 }
808 /*
809 static void
810 show_entry (GstIndexEntry *entry)
811 {
812 switch (entry->type) {
813 case GST_INDEX_ENTRY_ID:
814 g_print ("id %d describes writer %s\n", entry->id,
815 GST_INDEX_ID_DESCRIPTION (entry));
816 break;
817 case GST_INDEX_ENTRY_FORMAT:
818 g_print ("%d: registered format %d for %s\n", entry->id,
819 GST_INDEX_FORMAT_FORMAT (entry),
820 GST_INDEX_FORMAT_KEY (entry));
821 break;
822 case GST_INDEX_ENTRY_ASSOCIATION:
823 {
824 gint i;
826 g_print ("%d: %08x ", entry->id, GST_INDEX_ASSOC_FLAGS (entry));
827 for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) {
828 g_print ("%d %" G_GINT64_FORMAT, GST_INDEX_ASSOC_FORMAT (entry, i),
829 GST_INDEX_ASSOC_VALUE (entry, i));
830 }
831 g_print ("\n");
832 break;
833 }
834 default:
835 break;
836 }
837 }
838 */
840 static void
841 gst_file_index_add_entry (GstIndex * index, GstIndexEntry * entry)
842 {
843 GST_LOG_OBJECT (index, "adding this entry");
845 switch (entry->type) {
846 case GST_INDEX_ENTRY_ID:
847 gst_file_index_add_id (index, entry);
848 break;
849 case GST_INDEX_ENTRY_ASSOCIATION:
850 gst_file_index_add_association (index, entry);
851 break;
852 case GST_INDEX_ENTRY_OBJECT:
853 GST_ERROR_OBJECT (index, "gst_file_index_add_object not implemented");
854 break;
855 case GST_INDEX_ENTRY_FORMAT:
856 /*
857 We infer the formats from the entry itself so this type of
858 GST_INDEX_ENTRY_* can probably go away.
859 */
860 GST_DEBUG_OBJECT (index, "gst_file_index_add_format not implemented");
861 break;
862 default:
863 break;
864 }
865 }
867 static GstIndexEntry *
868 gst_file_index_get_assoc_entry (GstIndex * index,
869 gint id,
870 GstIndexLookupMethod method,
871 GstAssocFlags flags,
872 GstFormat format,
873 gint64 value, GCompareDataFunc _ignore_func, gpointer _ignore_user_data)
874 {
875 GstFileIndex *fileindex = GST_FILE_INDEX (index);
876 GstFileIndexId *id_index;
877 gint formatx = -1;
878 gint fx;
879 GstIndexAssociation sample;
880 gint mx;
881 gboolean exact;
882 gpointer row_data;
883 GstIndexEntry *entry;
884 gint xx;
886 g_return_val_if_fail (id > 0, NULL);
888 id_index = g_hash_table_lookup (fileindex->id_index, &id);
889 if (!id_index) {
890 GST_WARNING_OBJECT (fileindex, "writer %d unavailable", id);
891 return NULL;
892 }
894 for (fx = 0; fx < id_index->nformats; fx++)
895 if (id_index->format[fx] == format) {
896 formatx = fx;
897 break;
898 }
900 if (formatx == -1) {
901 GST_WARNING_OBJECT (fileindex, "format %d not available", format);
902 return NULL;
903 }
905 /* this is a hack, we should use a private structure instead */
906 sample.format = (GstFormat) formatx;
907 sample.value = value;
909 exact = _fc_bsearch (id_index->array, ARRAY_ROW_SIZE (id_index),
910 &mx, file_index_compare, &sample, id_index);
912 if (!exact) {
913 if (method == GST_INDEX_LOOKUP_EXACT)
914 return NULL;
915 else if (method == GST_INDEX_LOOKUP_BEFORE) {
916 if (mx == 0)
917 return NULL;
918 mx -= 1;
919 } else if (method == GST_INDEX_LOOKUP_AFTER) {
920 if (mx == id_index->array->len)
921 return NULL;
922 }
923 }
925 row_data = id_index->array->data + mx * ARRAY_ROW_SIZE (id_index);
927 /* if exact then ignore flags (?) */
928 if (method != GST_INDEX_LOOKUP_EXACT)
929 while ((GINT32_FROM_BE (ARRAY_ROW_FLAGS (row_data)) & flags) != flags) {
930 if (method == GST_INDEX_LOOKUP_BEFORE)
931 mx -= 1;
932 else if (method == GST_INDEX_LOOKUP_AFTER)
933 mx += 1;
934 if (mx < 0 || mx >= id_index->array->len)
935 return NULL;
936 row_data = id_index->array->data + mx * ARRAY_ROW_SIZE (id_index);
937 }
939 /* entry memory management needs improvement FIXME */
940 if (!fileindex->ret_entry)
941 fileindex->ret_entry = g_slice_new0 (GstIndexEntry);
942 entry = fileindex->ret_entry;
943 if (entry->data.assoc.assocs) {
944 g_free (entry->data.assoc.assocs);
945 entry->data.assoc.assocs = NULL;
946 }
948 entry->type = GST_INDEX_ENTRY_ASSOCIATION;
950 GST_INDEX_NASSOCS (entry) = id_index->nformats;
951 entry->data.assoc.assocs = g_new (GstIndexAssociation, id_index->nformats);
953 {
954 gint32 flags_be = ARRAY_ROW_FLAGS (row_data);
956 GST_INDEX_ASSOC_FLAGS (entry) = (GstAssocFlags) GINT32_FROM_BE (flags_be);
958 for (xx = 0; xx < id_index->nformats; xx++) {
959 gint64 val_be = ARRAY_ROW_VALUE (row_data, xx);
961 GST_INDEX_ASSOC_FORMAT (entry, xx) = id_index->format[xx];
962 GST_INDEX_ASSOC_VALUE (entry, xx) = GINT64_FROM_BE (val_be);
963 }
964 }
966 return entry;
967 }
969 gboolean
970 gst_file_index_plugin_init (GstPlugin * plugin)
971 {
972 GstIndexFactory *factory;
974 factory = gst_index_factory_new ("fileindex",
975 "A index that stores entries in file", gst_file_index_get_type ());
977 if (factory == NULL) {
978 return FALSE;
979 }
981 GST_PLUGIN_FEATURE (factory)->plugin_name = plugin->desc.name;
982 GST_PLUGIN_FEATURE (factory)->loaded = TRUE;
984 gst_registry_add_feature (gst_registry_get_default (),
985 GST_PLUGIN_FEATURE (factory));
987 GST_DEBUG_CATEGORY_INIT (DC, "GST_FILEINDEX", 0, NULL);
989 return TRUE;
990 }