2 * Copyright (C) 2003 Erik Walthinsen <omega@cse.ogi.edu>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
20 #include <gst/gst_private.h>
21 #include <gst/gstversion.h>
22 #include <gst/gstplugin.h>
23 #include <gst/gstindex.h>
31 #define GST_TYPE_FILE_INDEX \
32 (gst_file_index_get_type ())
33 #define GST_FILE_INDEX(obj) \
34 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_FILE_INDEX, GstFileIndex))
35 #define GST_FILE_INDEX_CLASS(klass) \
36 (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_FILE_INDEX, GstFileIndexClass))
37 #define GST_IS_FILE_INDEX(obj) \
38 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_FILE_INDEX))
39 #define GST_IS_FILE_INDEX_CLASS(obj) \
40 (GST_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_FILE_INDEX))
45 * We build an index to each entry for each id.
49 * -----------------------------...
55 * The fileindex creates a FileIndexId object for each writer id, a
56 * Hashtable is kept to map the id to the FileIndexId
58 * The FileIndexId also keeps all the values in a sorted GArray.
60 * Finding a value for an id/format requires locating the correct GArray,
61 * then do a binary search to get the required value.
63 * Unlike gstmemindex: All formats are assumed to sort to the
64 * same order. All formats are assumed to be available from
69 * Each array element is (32bits flags, nformats * 64bits)
79 typedef struct _GstFileIndex GstFileIndex;
80 typedef struct _GstFileIndexClass GstFileIndexClass;
82 #define ARRAY_ROW_SIZE(_ii) \
83 (sizeof (gint32) + (_ii)->nformats * sizeof (gint64))
84 #define ARRAY_TOTAL_SIZE(_ii) \
85 (_ii->array->len * ARRAY_ROW_SIZE(_ii))
87 // don't forget to convert to/from BE byte-order
88 #define ARRAY_ROW_FLAGS(_row) \
90 #define ARRAY_ROW_VALUE(_row,_vx) \
91 (*(gint64*) (((gchar*)(_row)) + sizeof (gint32) + (_vx) * sizeof (gint64)))
93 struct _GstFileIndex {
101 GstIndexEntry *ret_entry; // hack to avoid leaking memory
104 struct _GstFileIndexClass {
105 GstIndexClass parent_class;
113 static void gst_file_index_class_init (GstFileIndexClass *klass);
114 static void gst_file_index_init (GstFileIndex *index);
115 static void gst_file_index_dispose (GObject *object);
118 gst_file_index_set_property (GObject *object,
123 gst_file_index_get_property (GObject *object,
129 gst_file_index_resolve_writer (GstIndex *_index, GstObject *writer,
130 gint *id, gchar **writer_string);
132 static void gst_file_index_commit (GstIndex *index, gint writer_id);
133 static void gst_file_index_add_entry (GstIndex *index, GstIndexEntry *entry);
134 static GstIndexEntry* gst_file_index_get_assoc_entry (GstIndex *index, gint id,
135 GstIndexLookupMethod method,
137 GstFormat format, gint64 value,
138 GCompareDataFunc func,
141 #define CLASS(file_index) GST_FILE_INDEX_CLASS (G_OBJECT_GET_CLASS (file_index))
143 static GstIndex *parent_class = NULL;
146 gst_file_index_get_type(void) {
147 static GType file_index_type = 0;
149 if (!file_index_type) {
150 static const GTypeInfo file_index_info = {
151 sizeof(GstFileIndexClass),
154 (GClassInitFunc)gst_file_index_class_init,
157 sizeof(GstFileIndex),
159 (GInstanceInitFunc)gst_file_index_init,
162 file_index_type = g_type_register_static(GST_TYPE_INDEX, "GstFileIndex", &file_index_info, 0);
164 return file_index_type;
168 gst_file_index_class_init (GstFileIndexClass *klass)
170 GObjectClass *gobject_class;
171 GstIndexClass *gstindex_class;
173 gobject_class = (GObjectClass*)klass;
174 gstindex_class = (GstIndexClass*)klass;
176 parent_class = g_type_class_ref(GST_TYPE_INDEX);
178 gobject_class->dispose = gst_file_index_dispose;
179 gobject_class->set_property = gst_file_index_set_property;
180 gobject_class->get_property = gst_file_index_get_property;
182 gstindex_class->add_entry = gst_file_index_add_entry;
183 gstindex_class->get_assoc_entry = gst_file_index_get_assoc_entry;
184 gstindex_class->commit = gst_file_index_commit;
185 gstindex_class->resolve_writer = gst_file_index_resolve_writer;
187 g_object_class_install_property (gobject_class, ARG_LOCATION,
188 g_param_spec_string ("location", "File Location",
189 "Location of the index file",
190 NULL, G_PARAM_READWRITE));
194 gst_file_index_init (GstFileIndex *index)
196 GST_DEBUG(0, "created new file index");
198 index->id_index = g_hash_table_new (g_int_hash, g_int_equal);
202 gst_file_index_dispose (GObject *object)
204 GstFileIndex *index = GST_FILE_INDEX (object);
207 g_free (index->location);
209 // need to take care when destroying garrays with mmap'd segments
211 G_OBJECT_CLASS (parent_class)->dispose (object);
215 gst_file_index_resolve_writer (GstIndex *_index, GstObject *writer,
216 gint *id, gchar **writer_string)
218 GstFileIndex *index = GST_FILE_INDEX (_index);
219 GSList *pending = index->unresolved;
220 gboolean match = FALSE;
223 if (!index->is_loaded)
226 g_return_val_if_fail (id, FALSE);
227 g_return_val_if_fail (writer_string, FALSE);
228 g_return_val_if_fail (*writer_string, FALSE);
230 index->unresolved = NULL;
232 for (elem = pending; elem; elem = g_slist_next (elem)) {
233 GstFileIndexId *ii = elem->data;
234 if (strcmp (ii->id_desc, *writer_string) != 0) {
235 index->unresolved = g_slist_prepend (index->unresolved, ii);
240 g_warning ("Duplicate matches for writer '%s'", *writer_string);
244 //g_warning ("resolve %d %s", *id, *writer_string);
246 g_hash_table_insert (index->id_index, id, ii);
250 g_slist_free (pending);
256 _fc_alloc_array (GstFileIndexId *id_index)
258 g_assert (!id_index->array);
260 g_array_sized_new (FALSE, FALSE, ARRAY_ROW_SIZE (id_index), 0);
264 gst_file_index_load (GstFileIndex *index)
267 xmlNodePtr root, part;
270 g_assert (index->location);
271 g_return_if_fail (!index->is_loaded);
274 gchar *path = g_strdup_printf ("%s/gstindex.xml", index->location);
278 g_file_get_contents (path, &buf, &len, &err);
280 if (err) g_error ("%s", err->message);
282 doc = xmlParseMemory (buf, len);
286 //xmlDocFormatDump (stderr, doc, TRUE);
288 root = doc->xmlRootNode;
289 if (strcmp (root->name, "gstfileindex") != 0)
290 g_error ("root node isn't a gstfileindex");
292 val = xmlGetProp (root, "version");
293 if (!val || atoi (val) != 1)
294 g_error ("version != 1");
297 for (part = root->children; part; part = part->next) {
298 if (strcmp (part->name, "writers") == 0) {
300 for (writer = part->children; writer; writer = writer->next) {
301 xmlChar *datafile = xmlGetProp (writer, "datafile");
302 gchar *path = g_strdup_printf ("%s/%s", index->location, datafile);
304 GstFileIndexId *id_index;
306 xmlChar *entries_str;
311 fd = open (path, O_RDONLY);
314 g_warning ("Can't open '%s': %s", path, strerror (errno));
318 id_index = g_new0 (GstFileIndexId, 1);
319 id_index->id_desc = xmlGetProp (writer, "id");
321 for (wpart = writer->children; wpart; wpart = wpart->next) {
322 if (strcmp (wpart->name, "formats") == 0) {
323 xmlChar *count_str = xmlGetProp (wpart, "count");
327 id_index->nformats = atoi (count_str);
330 id_index->format = g_new (GstFormat, id_index->nformats);
332 for (format = wpart->children;
333 format; format = format->next) {
334 xmlChar *nick = xmlGetProp (format, "nick");
335 GstFormat fmt = gst_format_get_by_nick (nick);
336 if (fmt == GST_FORMAT_UNDEFINED)
337 g_error ("format '%s' undefined", nick);
338 g_assert (fx < id_index->nformats);
339 id_index->format[fx++] = fmt;
343 g_warning ("unknown wpart '%s'", wpart->name);
346 g_assert (id_index->nformats > 0);
347 _fc_alloc_array (id_index);
348 g_assert (id_index->array->data == NULL); // little bit risky
350 entries_str = xmlGetProp (writer, "entries");
351 id_index->array->len = atoi (entries_str);
355 mmap (NULL, ARRAY_TOTAL_SIZE (id_index), PROT_READ, MAP_SHARED, fd, 0);
357 if (array_data == MAP_FAILED) {
358 g_error ("mmap %s failed: %s", path, strerror (errno));
362 id_index->array->data = array_data;
364 index->unresolved = g_slist_prepend (index->unresolved, id_index);
367 g_warning ("unknown part '%s'", part->name);
372 GST_FLAG_UNSET (index, GST_INDEX_WRITABLE);
373 index->is_loaded = TRUE;
377 gst_file_index_set_property (GObject *object,
382 GstFileIndex *index = GST_FILE_INDEX (object);
387 g_free (index->location);
388 index->location = g_value_dup_string (value);
390 if (index->location && !g_hash_table_size (index->id_index))
391 gst_file_index_load (index);
397 gst_file_index_get_property (GObject *object,
402 GstFileIndex *index = GST_FILE_INDEX (object);
406 g_value_set_string (value, index->location);
412 _file_index_id_save_xml (gpointer _key, GstFileIndexId *ii, xmlNodePtr writers)
414 const gint bufsize = 16;
420 writer = xmlNewChild (writers, NULL, "writer", NULL);
421 xmlSetProp (writer, "id", ii->id_desc);
422 g_snprintf (buf, bufsize, "%d", ii->array->len);
423 xmlSetProp (writer, "entries", buf);
424 g_snprintf (buf, bufsize, "%d", ii->id); // any unique number is OK
425 xmlSetProp (writer, "datafile", buf);
427 formats = xmlNewChild (writer, NULL, "formats", NULL);
428 g_snprintf (buf, bufsize, "%d", ii->nformats);
429 xmlSetProp (formats, "count", buf);
431 for (xx=0; xx < ii->nformats; xx++) {
432 xmlNodePtr format = xmlNewChild (formats, NULL, "format", NULL);
433 const GstFormatDefinition* def =
434 gst_format_get_details (ii->format[xx]);
435 xmlSetProp (format, "nick", def->nick);
440 // We must save the binary data in separate files because
441 // mmap wants getpagesize() alignment. If we append all
442 // the data to one file then we don't know the appropriate
443 // padding since the page size isn't fixed.
446 _file_index_id_save_entries (gpointer *_key,
451 gchar *path = g_strdup_printf ("%s/%d", prefix, ii->id);
453 g_io_channel_new_file (path, "w", &err);
455 if (err) g_error ("%s", err->message);
457 g_io_channel_set_encoding (chan, NULL, &err);
458 if (err) g_error ("%s", err->message);
460 g_io_channel_write_chars (chan,
462 ARRAY_TOTAL_SIZE (ii),
465 if (err) g_error ("%s", err->message);
467 g_io_channel_shutdown (chan, TRUE, &err);
468 if (err) g_error ("%s", err->message);
470 g_io_channel_unref (chan);
473 // We have to save the whole set of indexes into a single file
474 // so it doesn't make sense to commit only a single writer.
478 // gst_index_commit (index, -1);
481 gst_file_index_commit (GstIndex *_index, gint _writer_id)
483 GstFileIndex *index = GST_FILE_INDEX (_index);
490 g_return_if_fail (index->location);
491 g_return_if_fail (!index->is_loaded);
493 GST_FLAG_UNSET (index, GST_INDEX_WRITABLE);
495 doc = xmlNewDoc ("1.0");
496 doc->xmlRootNode = xmlNewDocNode (doc, NULL, "gstfileindex", NULL);
497 xmlSetProp (doc->xmlRootNode, "version", "1");
499 writers = xmlNewChild (doc->xmlRootNode, NULL, "writers", NULL);
500 g_hash_table_foreach (index->id_index,
501 (GHFunc) _file_index_id_save_xml, writers);
503 if (mkdir (index->location, 0777) &&
505 g_error ("mkdir %s: %s", index->location, strerror (errno));
507 path = g_strdup_printf ("%s/gstindex.xml", index->location);
509 g_io_channel_new_file (path, "w", &err);
511 if (err) g_error ("%s", err->message);
513 g_io_channel_set_encoding (tocfile, NULL, &err);
514 if (err) g_error ("%s", err->message);
519 xmlDocDumpMemory (doc, &xmlmem, &xmlsize);
520 g_io_channel_write_chars (tocfile, xmlmem, xmlsize, NULL, &err);
521 if (err) g_error ("%s", err->message);
526 g_io_channel_shutdown (tocfile, TRUE, &err);
527 if (err) g_error ("%s", err->message);
529 g_io_channel_unref (tocfile);
531 g_hash_table_foreach (index->id_index,
532 (GHFunc) _file_index_id_save_entries,
537 gst_file_index_add_id (GstIndex *index, GstIndexEntry *entry)
539 GstFileIndex *fileindex = GST_FILE_INDEX (index);
540 GstFileIndexId *id_index;
542 id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
545 id_index = g_new0 (GstFileIndexId, 1);
547 id_index->id = entry->id;
548 id_index->id_desc = g_strdup (entry->data.id.description);
550 // It would be useful to know the GType of the writer so
551 // we can try to cope with changes in the id_desc path.
553 g_hash_table_insert (fileindex->id_index, &entry->id, id_index);
557 // This algorithm differs from libc bsearch in the handling
558 // of non-exact matches.
561 _fc_bsearch (GArray * ary,
563 GCompareDataFunc compare,
564 gconstpointer sample,
573 g_return_val_if_fail (compare, FALSE);
584 midsize = last - first;
586 while (midsize > 1) {
587 mid = first + midsize / 2;
589 cmp = (*compare) (sample, &g_array_index (ary, char, mid), user_data);
593 // if there are multiple matches then scan for the first match
596 &g_array_index (ary, char, mid - 1),
609 midsize = last - first;
612 for (tx = first; tx <= last; tx++)
614 cmp = (*compare) (sample, &g_array_index (ary, char, tx), user_data);
628 if (ret) *ret = last+1;
633 file_index_compare (gconstpointer sample,
637 //GstFileIndexId *id_index = user_data;
638 const GstIndexAssociation *ca = sample;
639 gint64 val1 = ca->value;
640 gint64 val2 = GINT64_FROM_BE (ARRAY_ROW_VALUE (row, ca->format));
641 gint64 diff = val2 - val1;
642 return (diff == 0 ? 0 : (diff > 0 ? 1 : -1));
646 gst_file_index_add_association (GstIndex *index, GstIndexEntry *entry)
648 GstFileIndex *fileindex = GST_FILE_INDEX (index);
649 GstFileIndexId *id_index;
651 GstIndexAssociation sample;
655 id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
659 if (!id_index->nformats) {
661 id_index->nformats = GST_INDEX_NASSOCS (entry);
662 //g_warning ("%d: formats = %d", entry->id, id_index->nformats);
663 id_index->format = g_new (GstFormat, id_index->nformats);
664 for (fx=0; fx < id_index->nformats; fx++)
665 id_index->format[fx] = GST_INDEX_ASSOC_FORMAT (entry, fx);
666 _fc_alloc_array (id_index);
668 /* only sanity checking */
669 if (id_index->nformats != GST_INDEX_NASSOCS (entry))
670 g_warning ("fileindex arity change %d -> %d",
671 id_index->nformats, GST_INDEX_NASSOCS (entry));
674 for (fx=0; fx < id_index->nformats; fx++)
675 if (id_index->format[fx] != GST_INDEX_ASSOC_FORMAT (entry, fx))
676 g_warning ("fileindex format[%d] changed %d -> %d",
678 id_index->format[fx],
679 GST_INDEX_ASSOC_FORMAT (entry, fx));
683 /* this is a hack, we should use a private structure instead */
685 sample.value = GST_INDEX_ASSOC_VALUE (entry, 0);
688 _fc_bsearch (id_index->array, &mx, file_index_compare,
692 // maybe overwrite instead?
693 g_warning ("ignoring duplicate index association at %lld",
694 GST_INDEX_ASSOC_VALUE (entry, 0));
698 // should verify that all formats are ordered XXX
701 gchar row_data[ARRAY_ROW_SIZE (id_index)];
703 ARRAY_ROW_FLAGS (row_data) =
704 GINT32_TO_BE (GST_INDEX_ASSOC_FLAGS (entry));
706 for (fx = 0; fx < id_index->nformats; fx++)
707 ARRAY_ROW_VALUE (row_data, fx) =
708 GINT64_TO_BE (GST_INDEX_ASSOC_VALUE (entry, fx));
710 g_array_insert_val (id_index->array, mx, row_data);
716 show_entry (GstIndexEntry *entry)
718 switch (entry->type) {
719 case GST_INDEX_ENTRY_ID:
720 g_print ("id %d describes writer %s\n", entry->id,
721 GST_INDEX_ID_DESCRIPTION (entry));
723 case GST_INDEX_ENTRY_FORMAT:
724 g_print ("%d: registered format %d for %s\n", entry->id,
725 GST_INDEX_FORMAT_FORMAT (entry),
726 GST_INDEX_FORMAT_KEY (entry));
728 case GST_INDEX_ENTRY_ASSOCIATION:
732 g_print ("%d: %08x ", entry->id, GST_INDEX_ASSOC_FLAGS (entry));
733 for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) {
734 g_print ("%d %lld ", GST_INDEX_ASSOC_FORMAT (entry, i),
735 GST_INDEX_ASSOC_VALUE (entry, i));
747 gst_file_index_add_entry (GstIndex *index, GstIndexEntry *entry)
749 GstFileIndex *fileindex = GST_FILE_INDEX (index);
751 GST_DEBUG (0, "adding entry %p\n", fileindex);
753 //show_entry (entry);
755 switch (entry->type){
756 case GST_INDEX_ENTRY_ID:
757 gst_file_index_add_id (index, entry);
759 case GST_INDEX_ENTRY_ASSOCIATION:
760 gst_file_index_add_association (index, entry);
762 case GST_INDEX_ENTRY_OBJECT:
763 g_error ("gst_file_index_add_object not implemented");
765 case GST_INDEX_ENTRY_FORMAT:
766 g_warning ("gst_file_index_add_format not implemented");
773 static GstIndexEntry*
774 gst_file_index_get_assoc_entry (GstIndex *index,
776 GstIndexLookupMethod method,
780 GCompareDataFunc _ignore_func,
781 gpointer _ignore_user_data)
783 GstFileIndex *fileindex = GST_FILE_INDEX (index);
784 GstFileIndexId *id_index;
787 GstIndexAssociation sample;
791 GstIndexEntry *entry;
794 id_index = g_hash_table_lookup (fileindex->id_index, &id);
798 for (fx=0; fx < id_index->nformats; fx++)
799 if (id_index->format[fx] == format)
800 { formatx = fx; break; }
803 g_warning ("index does not contain format %d", format);
807 /* this is a hack, we should use a private structure instead */
808 sample.format = formatx;
809 sample.value = value;
811 exact = _fc_bsearch (id_index->array, &mx, file_index_compare,
815 if (method == GST_INDEX_LOOKUP_EXACT)
817 else if (method == GST_INDEX_LOOKUP_BEFORE) {
821 } else if (method == GST_INDEX_LOOKUP_AFTER) {
822 if (mx == id_index->array->len)
827 row_data = &g_array_index (id_index->array, char, mx);
829 // if exact then ignore flags (?)
830 if (method != GST_INDEX_LOOKUP_EXACT)
831 while ((GINT32_FROM_BE (ARRAY_ROW_FLAGS (row_data)) & flags) != flags) {
832 if (method == GST_INDEX_LOOKUP_BEFORE)
834 else if (method == GST_INDEX_LOOKUP_AFTER)
836 if (mx < 0 || mx >= id_index->array->len)
838 row_data = &g_array_index (id_index->array, char, mx);
841 // entry memory management needs improvement
842 if (!fileindex->ret_entry)
843 fileindex->ret_entry = g_new0 (GstIndexEntry, 1);
844 entry = fileindex->ret_entry;
845 if (entry->data.assoc.assocs)
846 g_free (entry->data.assoc.assocs);
848 entry->type = GST_INDEX_ENTRY_ASSOCIATION;
850 GST_INDEX_NASSOCS (entry) = id_index->nformats;
851 entry->data.assoc.assocs =
852 g_new (GstIndexAssociation, id_index->nformats);
854 GST_INDEX_ASSOC_FLAGS (entry) =
855 GINT32_FROM_BE (ARRAY_ROW_FLAGS (row_data));
857 for (xx=0; xx < id_index->nformats; xx++)
859 GST_INDEX_ASSOC_FORMAT (entry, xx) = id_index->format[xx];
860 GST_INDEX_ASSOC_VALUE (entry, xx) =
861 GINT64_FROM_BE (ARRAY_ROW_VALUE (row_data, xx));
867 //////////////////////////////////////////////////////////////////////
868 // [UNTESTED] i don't understand the following plugin stuff [UNTESTED]
869 //////////////////////////////////////////////////////////////////////
872 plugin_init (GModule *module, GstPlugin *plugin)
874 GstIndexFactory *factory;
876 gst_plugin_set_longname (plugin, "A file index");
878 factory = gst_index_factory_new ("fileindex",
879 "A index that stores entries in file",
880 gst_file_index_get_type());
882 if (factory != NULL) {
883 gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
886 g_warning ("could not register fileindex");
891 GstPluginDesc plugin_desc2 = {