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 G_DEFINE_TYPE (GstFileIndex, gst_file_index, GST_TYPE_INDEX);
151 static void
152 gst_file_index_class_init (GstFileIndexClass * klass)
153 {
154 GObjectClass *gobject_class;
155 GstIndexClass *gstindex_class;
157 gobject_class = (GObjectClass *) klass;
158 gstindex_class = (GstIndexClass *) klass;
160 gobject_class->dispose = gst_file_index_dispose;
161 gobject_class->set_property = gst_file_index_set_property;
162 gobject_class->get_property = gst_file_index_get_property;
164 gstindex_class->add_entry = gst_file_index_add_entry;
165 gstindex_class->get_assoc_entry = gst_file_index_get_assoc_entry;
166 gstindex_class->commit = gst_file_index_commit;
167 gstindex_class->get_writer_id = gst_file_index_get_writer_id;
169 g_object_class_install_property (gobject_class, ARG_LOCATION,
170 g_param_spec_string ("location", "File Location",
171 "Location of the index file", NULL,
172 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
173 }
175 static void
176 gst_file_index_init (GstFileIndex * index)
177 {
178 GST_DEBUG ("created new file index");
180 index->id_index = g_hash_table_new (g_int_hash, g_int_equal);
181 }
183 static void
184 _file_index_id_free (GstFileIndexId * index_id, gboolean is_mmapped)
185 {
186 if (index_id->id_desc)
187 g_free (index_id->id_desc);
188 if (index_id->format)
189 g_free (index_id->format);
190 if (index_id->array) {
191 if (is_mmapped)
192 munmap (index_id->array->data, ARRAY_TOTAL_SIZE (index_id));
193 g_array_free (index_id->array, !is_mmapped);
194 }
195 g_slice_free (GstFileIndexId, index_id);
196 }
198 static gboolean
199 _id_index_free_helper (gpointer _key, GstFileIndexId * index_id,
200 GstFileIndex * index)
201 {
202 _file_index_id_free (index_id, index->is_loaded);
203 return TRUE;
204 }
206 static void
207 gst_file_index_dispose (GObject * object)
208 {
209 GstFileIndex *index = GST_FILE_INDEX (object);
211 if (index->location) {
212 g_free (index->location);
213 index->location = NULL;
214 }
216 {
217 GSList *elem;
219 for (elem = index->unresolved; elem; elem = g_slist_next (elem))
220 _file_index_id_free (elem->data, index->is_loaded);
221 g_slist_free (index->unresolved);
222 index->unresolved = NULL;
223 }
225 g_hash_table_foreach_steal (index->id_index,
226 (GHRFunc) _id_index_free_helper, index);
227 g_hash_table_destroy (index->id_index);
228 index->id_index = NULL;
230 gst_index_entry_free (index->ret_entry); /* hack */
232 G_OBJECT_CLASS (gst_file_index_parent_class)->dispose (object);
233 }
235 struct fi_find_writer_context
236 {
237 const gchar *writer_string;
238 GstFileIndexId *ii;
239 };
241 static void
242 _fi_find_writer (gpointer key, gpointer val, gpointer data)
243 {
244 struct fi_find_writer_context *cx = data;
245 GstFileIndexId *ii = val;
247 if (strcmp (ii->id_desc, cx->writer_string) == 0)
248 cx->ii = ii;
249 }
251 static gboolean
252 gst_file_index_get_writer_id (GstIndex * _index,
253 gint * id, gchar * writer_string)
254 {
255 GstFileIndex *index = GST_FILE_INDEX (_index);
256 GSList *pending = index->unresolved;
257 gboolean match = FALSE;
258 GSList *elem;
260 if (!index->is_loaded)
261 return FALSE;
263 g_return_val_if_fail (id, FALSE);
264 g_return_val_if_fail (writer_string, FALSE);
266 index->unresolved = NULL;
268 for (elem = pending; elem; elem = g_slist_next (elem)) {
269 GstFileIndexId *ii = elem->data;
271 if (strcmp (ii->id_desc, writer_string) != 0) {
272 index->unresolved = g_slist_prepend (index->unresolved, ii);
273 continue;
274 }
276 if (match) {
277 GST_WARNING_OBJECT (index, "Duplicate matches for writer '%s'",
278 writer_string);
279 continue;
280 }
282 ii->id = *id = ++index->next_id;
283 g_hash_table_insert (index->id_index, &ii->id, ii);
284 match = TRUE;
285 }
287 g_slist_free (pending);
289 if (!match) {
290 struct fi_find_writer_context cx;
292 cx.writer_string = writer_string;
293 cx.ii = NULL;
294 g_hash_table_foreach (index->id_index, _fi_find_writer, &cx);
296 if (cx.ii) {
297 match = TRUE;
298 GST_DEBUG_OBJECT (index, "Resolved writer '%s' again", writer_string);
299 } else
300 GST_WARNING_OBJECT (index, "Can't resolve writer '%s'", writer_string);
301 }
303 return match;
304 }
306 static void
307 _fc_alloc_array (GstFileIndexId * id_index)
308 {
309 g_assert (!id_index->array);
310 id_index->array =
311 g_array_sized_new (FALSE, FALSE, ARRAY_ROW_SIZE (id_index), 0);
312 }
314 static void
315 gst_file_index_load (GstFileIndex * index)
316 {
317 xmlDocPtr doc;
318 xmlNodePtr root, part;
319 xmlChar *val;
321 g_assert (index->location);
322 g_return_if_fail (!index->is_loaded);
324 {
325 gchar *path = g_strdup_printf ("%s/gstindex.xml", index->location);
326 GError *err = NULL;
327 gchar *buf;
328 gsize len;
330 g_file_get_contents (path, &buf, &len, &err);
331 g_free (path);
332 if (err) {
333 GST_ERROR_OBJECT (index, "%s", err->message);
334 return;
335 }
337 doc = xmlParseMemory (buf, len);
338 g_free (buf);
339 }
341 //xmlDocFormatDump (stderr, doc, TRUE);
343 root = doc->xmlRootNode;
344 if (strcmp ((char *) root->name, "gstfileindex") != 0) {
345 GST_ERROR_OBJECT (index, "root node isn't a gstfileindex");
346 return;
347 }
349 val = xmlGetProp (root, (xmlChar *) "version");
350 if (!val || atoi ((char *) val) != 1) {
351 GST_ERROR_OBJECT (index, "version != 1");
352 return;
353 }
354 free (val);
356 for (part = root->children; part; part = part->next) {
357 if (strcmp ((char *) part->name, "writers") == 0) {
358 xmlNodePtr writer;
360 for (writer = part->children; writer; writer = writer->next) {
361 xmlChar *datafile = xmlGetProp (writer, (xmlChar *) "datafile");
362 gchar *path = g_strdup_printf ("%s/%s", index->location, datafile);
363 int fd;
364 GstFileIndexId *id_index;
365 xmlNodePtr wpart;
366 xmlChar *entries_str;
367 gpointer array_data;
369 free (datafile);
371 fd = open (path, O_RDONLY);
372 g_free (path);
373 if (fd < 0) {
374 GST_ERROR_OBJECT (index,
375 "Can't open '%s': %s", path, g_strerror (errno));
376 continue;
377 }
379 id_index = g_slice_new0 (GstFileIndexId);
380 id_index->id_desc = (char *) xmlGetProp (writer, (xmlChar *) "id");
382 for (wpart = writer->children; wpart; wpart = wpart->next) {
383 if (strcmp ((char *) wpart->name, "formats") == 0) {
384 xmlChar *count_str = xmlGetProp (wpart, (xmlChar *) "count");
385 gint fx = 0;
386 xmlNodePtr format;
388 id_index->nformats = atoi ((char *) count_str);
389 free (count_str);
391 id_index->format = g_new (GstFormat, id_index->nformats);
393 for (format = wpart->children; format; format = format->next) {
394 xmlChar *nick = xmlGetProp (format, (xmlChar *) "nick");
395 GstFormat fmt = gst_format_get_by_nick ((gchar *) nick);
397 if (fmt == GST_FORMAT_UNDEFINED)
398 GST_ERROR_OBJECT (index, "format '%s' undefined", nick);
399 g_assert (fx < id_index->nformats);
400 id_index->format[fx++] = fmt;
401 free (nick);
402 }
403 } else
404 GST_INFO_OBJECT (index, "unknown wpart '%s'", wpart->name);
405 }
407 g_assert (id_index->nformats > 0);
408 _fc_alloc_array (id_index);
409 g_assert (id_index->array->data == NULL); /* little bit risky */
411 entries_str = xmlGetProp (writer, (xmlChar *) "entries");
412 id_index->array->len = atoi ((char *) entries_str);
413 free (entries_str);
415 array_data =
416 mmap (NULL, ARRAY_TOTAL_SIZE (id_index), PROT_READ, MAP_SHARED, fd,
417 0);
418 close (fd);
419 if (array_data == MAP_FAILED) {
420 GST_ERROR_OBJECT (index,
421 "mmap %s failed: %s", path, g_strerror (errno));
422 continue;
423 }
425 id_index->array->data = array_data;
427 index->unresolved = g_slist_prepend (index->unresolved, id_index);
428 }
429 } else
430 GST_INFO_OBJECT (index, "unknown part '%s'", part->name);
431 }
433 xmlFreeDoc (doc);
435 GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
436 index->is_loaded = TRUE;
437 GST_LOG_OBJECT (index, "index %s loaded OK", index->location);
438 }
440 static void
441 gst_file_index_set_property (GObject * object,
442 guint prop_id, const GValue * value, GParamSpec * pspec)
443 {
444 GstFileIndex *index = GST_FILE_INDEX (object);
446 switch (prop_id) {
447 case ARG_LOCATION:
448 if (index->location)
449 g_free (index->location);
450 index->location = g_value_dup_string (value);
452 if (index->location && !g_hash_table_size (index->id_index))
453 gst_file_index_load (index);
454 break;
455 }
456 }
458 static void
459 gst_file_index_get_property (GObject * object,
460 guint prop_id, GValue * value, GParamSpec * pspec)
461 {
462 GstFileIndex *index = GST_FILE_INDEX (object);
464 switch (prop_id) {
465 case ARG_LOCATION:
466 g_value_set_string (value, index->location);
467 break;
468 }
469 }
471 static void
472 _file_index_id_save_xml (gpointer _key, GstFileIndexId * ii, xmlNodePtr writers)
473 {
474 const gint bufsize = 16;
475 gchar buf[16];
476 xmlNodePtr writer;
477 xmlNodePtr formats;
478 gint xx;
480 if (!ii->array) {
481 GST_INFO ("Index for %s is empty", ii->id_desc);
482 return;
483 }
485 writer = xmlNewChild (writers, NULL, (xmlChar *) "writer", NULL);
486 xmlSetProp (writer, (xmlChar *) "id", (xmlChar *) ii->id_desc);
487 g_snprintf (buf, bufsize, "%d", ii->array->len);
488 xmlSetProp (writer, (xmlChar *) "entries", (xmlChar *) buf);
489 g_snprintf (buf, bufsize, "%d", ii->id); /* any unique number is OK */
490 xmlSetProp (writer, (xmlChar *) "datafile", (xmlChar *) buf);
492 formats = xmlNewChild (writer, NULL, (xmlChar *) "formats", NULL);
493 g_snprintf (buf, bufsize, "%d", ii->nformats);
494 xmlSetProp (formats, (xmlChar *) "count", (xmlChar *) buf);
496 for (xx = 0; xx < ii->nformats; xx++) {
497 xmlNodePtr format = xmlNewChild (formats, NULL, (xmlChar *) "format", NULL);
498 const GstFormatDefinition *def = gst_format_get_details (ii->format[xx]);
500 xmlSetProp (format, (xmlChar *) "nick", (xmlChar *) def->nick);
501 }
502 }
504 /*
505 We must save the binary data in separate files because
506 mmap wants getpagesize() alignment. If we append all
507 the data to one file then we don't know the appropriate
508 padding since the page size isn't fixed.
509 */
510 static void
511 _file_index_id_save_entries (gpointer * _key,
512 GstFileIndexId * ii, gchar * prefix)
513 {
514 GError *err;
515 gchar *path;
516 GIOChannel *chan;
518 if (!ii->array)
519 return;
521 err = NULL;
522 path = g_strdup_printf ("%s/%d", prefix, ii->id);
523 chan = g_io_channel_new_file (path, "w", &err);
524 g_free (path);
525 if (err)
526 goto fail;
528 g_io_channel_set_encoding (chan, NULL, &err);
529 if (err)
530 goto fail;
532 g_io_channel_write_chars (chan,
533 ii->array->data, ARRAY_TOTAL_SIZE (ii), NULL, &err);
534 if (err)
535 goto fail;
537 g_io_channel_shutdown (chan, TRUE, &err);
538 if (err)
539 goto fail;
541 g_io_channel_unref (chan);
542 return;
544 fail:
545 GST_ERROR ("%s", err->message);
546 }
548 /*
549 We have to save the whole set of indexes into a single file
550 so it doesn't make sense to commit only a single writer.
552 i suggest:
554 gst_index_commit (index, -1);
555 */
556 static void
557 gst_file_index_commit (GstIndex * _index, gint _writer_id)
558 {
559 GstFileIndex *index = GST_FILE_INDEX (_index);
560 xmlDocPtr doc;
561 xmlNodePtr writers;
562 GError *err = NULL;
563 gchar *path;
564 GIOChannel *tocfile;
566 g_return_if_fail (index->location);
567 g_return_if_fail (!index->is_loaded);
569 GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
571 doc = xmlNewDoc ((xmlChar *) "1.0");
572 doc->xmlRootNode =
573 xmlNewDocNode (doc, NULL, (xmlChar *) "gstfileindex", NULL);
574 xmlSetProp (doc->xmlRootNode, (xmlChar *) "version", (xmlChar *) "1");
576 writers = xmlNewChild (doc->xmlRootNode, NULL, (xmlChar *) "writers", NULL);
577 g_hash_table_foreach (index->id_index,
578 (GHFunc) _file_index_id_save_xml, writers);
580 if (mkdir (index->location, 0777) && errno != EEXIST) {
581 GST_ERROR_OBJECT (index, "mkdir %s: %s", index->location,
582 g_strerror (errno));
583 return;
584 }
586 path = g_strdup_printf ("%s/gstindex.xml", index->location);
587 tocfile = g_io_channel_new_file (path, "w", &err);
588 g_free (path);
589 if (err) {
590 GST_ERROR_OBJECT (index, "%s", err->message);
591 return;
592 }
594 g_io_channel_set_encoding (tocfile, NULL, &err);
595 if (err) {
596 GST_ERROR_OBJECT (index, "%s", err->message);
597 return;
598 }
600 {
601 xmlChar *xmlmem;
602 int xmlsize;
604 xmlDocDumpMemory (doc, &xmlmem, &xmlsize);
605 g_io_channel_write_chars (tocfile, (gchar *) xmlmem, xmlsize, NULL, &err);
606 if (err) {
607 GST_ERROR_OBJECT (index, "%s", err->message);
608 return;
609 }
610 xmlFreeDoc (doc);
611 free (xmlmem);
612 }
614 g_io_channel_shutdown (tocfile, TRUE, &err);
615 if (err) {
616 GST_ERROR_OBJECT (index, "%s", err->message);
617 return;
618 }
620 g_io_channel_unref (tocfile);
622 g_hash_table_foreach (index->id_index,
623 (GHFunc) _file_index_id_save_entries, index->location);
624 }
626 static void
627 gst_file_index_add_id (GstIndex * index, GstIndexEntry * entry)
628 {
629 GstFileIndex *fileindex = GST_FILE_INDEX (index);
630 GstFileIndexId *id_index;
632 id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
634 if (!id_index) {
635 id_index = g_slice_new0 (GstFileIndexId);
637 id_index->id = entry->id;
638 id_index->id_desc = g_strdup (entry->data.id.description);
640 /* It would be useful to know the GType of the writer so
641 we can try to cope with changes in the id_desc path. */
643 g_hash_table_insert (fileindex->id_index, &id_index->id, id_index);
644 }
645 }
647 /* This algorithm differs from libc bsearch in the handling
648 of non-exact matches. */
650 static gboolean
651 _fc_bsearch (GArray * ary,
652 gint stride,
653 gint * ret,
654 GCompareDataFunc compare, gconstpointer sample, gpointer user_data)
655 {
656 gint first, last;
657 gint mid;
658 gint midsize;
659 gint cmp;
660 gint tx;
662 g_return_val_if_fail (compare, FALSE);
664 if (!ary->len) {
665 if (ret)
666 *ret = 0;
667 return FALSE;
668 }
670 first = 0;
671 last = ary->len - 1;
673 midsize = last - first;
675 while (midsize > 1) {
676 mid = first + midsize / 2;
678 cmp = (*compare) (sample, ary->data + mid * stride, user_data);
680 if (cmp == 0) {
681 /* if there are multiple matches then scan for the first match */
682 while (mid > 0 &&
683 (*compare) (sample, ary->data + (mid - 1) * stride, user_data) == 0)
684 --mid;
686 if (ret)
687 *ret = mid;
688 return TRUE;
689 }
691 if (cmp < 0)
692 last = mid - 1;
693 else
694 first = mid + 1;
696 midsize = last - first;
697 }
699 for (tx = first; tx <= last; tx++) {
700 cmp = (*compare) (sample, ary->data + tx * stride, user_data);
702 if (cmp < 0) {
703 if (ret)
704 *ret = tx;
705 return FALSE;
706 }
707 if (cmp == 0) {
708 if (ret)
709 *ret = tx;
710 return TRUE;
711 }
712 }
714 if (ret)
715 *ret = last + 1;
716 return FALSE;
717 }
719 static gint
720 file_index_compare (gconstpointer sample, gconstpointer row, gpointer user_data)
721 {
722 //GstFileIndexId *id_index = user_data;
723 const GstIndexAssociation *ca = sample;
724 gint64 val1 = ca->value;
725 gint64 val2_be = ARRAY_ROW_VALUE (row, ca->format);
726 gint64 val2 = GINT64_FROM_BE (val2_be);
727 gint64 diff = val2 - val1;
729 return (diff == 0 ? 0 : (diff < 0 ? 1 : -1));
730 }
732 static void
733 gst_file_index_add_association (GstIndex * index, GstIndexEntry * entry)
734 {
735 GstFileIndex *fileindex = GST_FILE_INDEX (index);
736 GstFileIndexId *id_index;
737 gint mx;
738 GstIndexAssociation sample;
739 gboolean exact;
741 id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
742 if (!id_index)
743 return;
745 if (!id_index->nformats) {
746 gint fx;
748 id_index->nformats = GST_INDEX_NASSOCS (entry);
749 GST_LOG_OBJECT (fileindex, "creating %d formats for %d",
750 id_index->nformats, entry->id);
751 id_index->format = g_new (GstFormat, id_index->nformats);
752 for (fx = 0; fx < id_index->nformats; fx++)
753 id_index->format[fx] = GST_INDEX_ASSOC_FORMAT (entry, fx);
754 _fc_alloc_array (id_index);
755 } else {
756 /* only sanity checking */
757 if (id_index->nformats != GST_INDEX_NASSOCS (entry))
758 GST_WARNING_OBJECT (fileindex, "arity change %d -> %d",
759 id_index->nformats, GST_INDEX_NASSOCS (entry));
760 else {
761 gint fx;
763 for (fx = 0; fx < id_index->nformats; fx++)
764 if (id_index->format[fx] != GST_INDEX_ASSOC_FORMAT (entry, fx))
765 GST_WARNING_OBJECT (fileindex, "format[%d] changed %d -> %d",
766 fx, id_index->format[fx], GST_INDEX_ASSOC_FORMAT (entry, fx));
767 }
768 }
770 /* this is a hack, we should use a private structure instead */
771 sample.format = 0;
772 sample.value = GST_INDEX_ASSOC_VALUE (entry, 0);
774 exact =
775 _fc_bsearch (id_index->array, ARRAY_ROW_SIZE (id_index),
776 &mx, file_index_compare, &sample, id_index);
778 if (exact) {
779 /* maybe overwrite instead? */
780 GST_DEBUG_OBJECT (index,
781 "Ignoring duplicate index association at %" G_GINT64_FORMAT,
782 GST_INDEX_ASSOC_VALUE (entry, 0));
783 return;
784 }
786 {
787 gchar *row_data = (gchar *) g_malloc (ARRAY_ROW_SIZE (id_index));
788 gint fx;
790 gint32 flags_host = GST_INDEX_ASSOC_FLAGS (entry);
792 ARRAY_ROW_FLAGS (row_data) = GINT32_TO_BE (flags_host);
794 for (fx = 0; fx < id_index->nformats; fx++) {
795 gint64 val_host = GST_INDEX_ASSOC_VALUE (entry, fx);
797 ARRAY_ROW_VALUE (row_data, fx) = GINT64_TO_BE (val_host);
798 }
800 g_array_insert_vals (id_index->array, mx, row_data, 1);
802 g_free (row_data);
803 }
804 }
806 /*
807 static void
808 show_entry (GstIndexEntry *entry)
809 {
810 switch (entry->type) {
811 case GST_INDEX_ENTRY_ID:
812 g_print ("id %d describes writer %s\n", entry->id,
813 GST_INDEX_ID_DESCRIPTION (entry));
814 break;
815 case GST_INDEX_ENTRY_FORMAT:
816 g_print ("%d: registered format %d for %s\n", entry->id,
817 GST_INDEX_FORMAT_FORMAT (entry),
818 GST_INDEX_FORMAT_KEY (entry));
819 break;
820 case GST_INDEX_ENTRY_ASSOCIATION:
821 {
822 gint i;
824 g_print ("%d: %08x ", entry->id, GST_INDEX_ASSOC_FLAGS (entry));
825 for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) {
826 g_print ("%d %" G_GINT64_FORMAT, GST_INDEX_ASSOC_FORMAT (entry, i),
827 GST_INDEX_ASSOC_VALUE (entry, i));
828 }
829 g_print ("\n");
830 break;
831 }
832 default:
833 break;
834 }
835 }
836 */
838 static void
839 gst_file_index_add_entry (GstIndex * index, GstIndexEntry * entry)
840 {
841 GST_LOG_OBJECT (index, "adding this entry");
843 switch (entry->type) {
844 case GST_INDEX_ENTRY_ID:
845 gst_file_index_add_id (index, entry);
846 break;
847 case GST_INDEX_ENTRY_ASSOCIATION:
848 gst_file_index_add_association (index, entry);
849 break;
850 case GST_INDEX_ENTRY_OBJECT:
851 GST_ERROR_OBJECT (index, "gst_file_index_add_object not implemented");
852 break;
853 case GST_INDEX_ENTRY_FORMAT:
854 /*
855 We infer the formats from the entry itself so this type of
856 GST_INDEX_ENTRY_* can probably go away.
857 */
858 GST_DEBUG_OBJECT (index, "gst_file_index_add_format not implemented");
859 break;
860 default:
861 break;
862 }
863 }
865 static GstIndexEntry *
866 gst_file_index_get_assoc_entry (GstIndex * index,
867 gint id,
868 GstIndexLookupMethod method,
869 GstAssocFlags flags,
870 GstFormat format,
871 gint64 value, GCompareDataFunc _ignore_func, gpointer _ignore_user_data)
872 {
873 GstFileIndex *fileindex = GST_FILE_INDEX (index);
874 GstFileIndexId *id_index;
875 gint formatx = -1;
876 gint fx;
877 GstIndexAssociation sample;
878 gint mx;
879 gboolean exact;
880 gpointer row_data;
881 GstIndexEntry *entry;
882 gint xx;
884 g_return_val_if_fail (id > 0, NULL);
886 id_index = g_hash_table_lookup (fileindex->id_index, &id);
887 if (!id_index) {
888 GST_WARNING_OBJECT (fileindex, "writer %d unavailable", id);
889 return NULL;
890 }
892 for (fx = 0; fx < id_index->nformats; fx++)
893 if (id_index->format[fx] == format) {
894 formatx = fx;
895 break;
896 }
898 if (formatx == -1) {
899 GST_WARNING_OBJECT (fileindex, "format %d not available", format);
900 return NULL;
901 }
903 /* this is a hack, we should use a private structure instead */
904 sample.format = formatx;
905 sample.value = value;
907 exact = _fc_bsearch (id_index->array, ARRAY_ROW_SIZE (id_index),
908 &mx, file_index_compare, &sample, id_index);
910 if (!exact) {
911 if (method == GST_INDEX_LOOKUP_EXACT)
912 return NULL;
913 else if (method == GST_INDEX_LOOKUP_BEFORE) {
914 if (mx == 0)
915 return NULL;
916 mx -= 1;
917 } else if (method == GST_INDEX_LOOKUP_AFTER) {
918 if (mx == id_index->array->len)
919 return NULL;
920 }
921 }
923 row_data = id_index->array->data + mx * ARRAY_ROW_SIZE (id_index);
925 /* if exact then ignore flags (?) */
926 if (method != GST_INDEX_LOOKUP_EXACT)
927 while ((GINT32_FROM_BE (ARRAY_ROW_FLAGS (row_data)) & flags) != flags) {
928 if (method == GST_INDEX_LOOKUP_BEFORE)
929 mx -= 1;
930 else if (method == GST_INDEX_LOOKUP_AFTER)
931 mx += 1;
932 if (mx < 0 || mx >= id_index->array->len)
933 return NULL;
934 row_data = id_index->array->data + mx * ARRAY_ROW_SIZE (id_index);
935 }
937 /* entry memory management needs improvement FIXME */
938 if (!fileindex->ret_entry)
939 fileindex->ret_entry = g_slice_new0 (GstIndexEntry);
940 entry = fileindex->ret_entry;
941 if (entry->data.assoc.assocs) {
942 g_free (entry->data.assoc.assocs);
943 entry->data.assoc.assocs = NULL;
944 }
946 entry->type = GST_INDEX_ENTRY_ASSOCIATION;
948 GST_INDEX_NASSOCS (entry) = id_index->nformats;
949 entry->data.assoc.assocs = g_new (GstIndexAssociation, id_index->nformats);
951 {
952 gint32 flags_be = ARRAY_ROW_FLAGS (row_data);
954 GST_INDEX_ASSOC_FLAGS (entry) = GINT32_FROM_BE (flags_be);
956 for (xx = 0; xx < id_index->nformats; xx++) {
957 gint64 val_be = ARRAY_ROW_VALUE (row_data, xx);
959 GST_INDEX_ASSOC_FORMAT (entry, xx) = id_index->format[xx];
960 GST_INDEX_ASSOC_VALUE (entry, xx) = GINT64_FROM_BE (val_be);
961 }
962 }
964 return entry;
965 }
967 gboolean
968 gst_file_index_plugin_init (GstPlugin * plugin)
969 {
970 GstIndexFactory *factory;
972 factory = gst_index_factory_new ("fileindex",
973 "A index that stores entries in file", gst_file_index_get_type ());
975 if (factory == NULL) {
976 return FALSE;
977 }
979 GST_PLUGIN_FEATURE (factory)->plugin_name = g_strdup (plugin->desc.name);
980 GST_PLUGIN_FEATURE (factory)->loaded = TRUE;
982 gst_registry_add_feature (gst_registry_get_default (),
983 GST_PLUGIN_FEATURE (factory));
985 GST_DEBUG_CATEGORY_INIT (DC, "GST_FILEINDEX", 0, NULL);
987 return TRUE;
988 }