gstfileindex.c 25.9 KB
Newer Older
Wim Taymans's avatar
Wim Taymans committed
1 2
/* GStreamer
 * Copyright (C) 2003 Erik Walthinsen <omega@cse.ogi.edu>
3
 *               2003 Joshua N Pritikin <jpritikin@pobox.com>
Wim Taymans's avatar
Wim Taymans committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

21
#include <gst/gst.h>
Wim Taymans's avatar
Wim Taymans committed
22 23 24 25 26 27

#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
28
#include <string.h>
Wim Taymans's avatar
Wim Taymans committed
29

30 31 32 33
#ifdef GST_DISABLE_DEPRECATED
#include <libxml/parser.h>
#endif

34 35
#include "gstindexers.h"

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
36
#define GST_TYPE_FILE_INDEX             \
Wim Taymans's avatar
Wim Taymans committed
37
  (gst_file_index_get_type ())
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
38
#define GST_FILE_INDEX(obj)             \
Wim Taymans's avatar
Wim Taymans committed
39
  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_FILE_INDEX, GstFileIndex))
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
40
#define GST_FILE_INDEX_CLASS(klass)     \
Wim Taymans's avatar
Wim Taymans committed
41
  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_FILE_INDEX, GstFileIndexClass))
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
42
#define GST_IS_FILE_INDEX(obj)          \
Wim Taymans's avatar
Wim Taymans committed
43
  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_FILE_INDEX))
44
#define GST_IS_FILE_INDEX_CLASS(klass)    \
Wim Taymans's avatar
Wim Taymans committed
45
  (GST_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_FILE_INDEX))
46

Wim Taymans's avatar
Wim Taymans committed
47 48 49 50
/*
 * Object model:
 *
 * We build an index to each entry for each id.
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
51
 *
Wim Taymans's avatar
Wim Taymans committed
52 53 54
 *
 *  fileindex
 *    -----------------------------...
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
55 56
 *    !                  !
 *   id1                 id2
Wim Taymans's avatar
Wim Taymans committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
 *    !
 *   GArray
 *
 * The fileindex creates a FileIndexId object for each writer id, a
 * Hashtable is kept to map the id to the FileIndexId
 *
 * The FileIndexId also keeps all the values in a sorted GArray.
 *
 * Finding a value for an id/format requires locating the correct GArray,
 * then do a binary search to get the required value.
 *
 * Unlike gstmemindex:  All formats are assumed to sort to the
 * same order.  All formats are assumed to be available from
 * any entry.
 */

/*
 * Each array element is (32bits flags, nformats * 64bits)
 */
76 77 78 79 80 81 82
typedef struct
{
  gint id;
  gchar *id_desc;
  gint nformats;
  GstFormat *format;
  GArray *array;
83 84
}
GstFileIndexId;
Wim Taymans's avatar
Wim Taymans committed
85 86 87 88 89 90 91 92 93

typedef struct _GstFileIndex GstFileIndex;
typedef struct _GstFileIndexClass GstFileIndexClass;

#define ARRAY_ROW_SIZE(_ii) \
  (sizeof (gint32) + (_ii)->nformats * sizeof (gint64))
#define ARRAY_TOTAL_SIZE(_ii) \
  (_ii->array->len * ARRAY_ROW_SIZE(_ii))

94
/* don't forget to convert to/from BE byte-order */
Wim Taymans's avatar
Wim Taymans committed
95 96 97 98 99
#define ARRAY_ROW_FLAGS(_row) \
  (*((gint32*) (_row)))
#define ARRAY_ROW_VALUE(_row,_vx) \
  (*(gint64*) (((gchar*)(_row)) + sizeof (gint32) + (_vx) * sizeof (gint64)))

100
GST_DEBUG_CATEGORY_STATIC (DC);
101
#define GST_CAT_DEFAULT DC
102

103 104 105
struct _GstFileIndex
{
  GstIndex parent;
Wim Taymans's avatar
Wim Taymans committed
106

107 108 109 110 111
  gchar *location;
  gboolean is_loaded;
  GSList *unresolved;
  gint next_id;
  GHashTable *id_index;
Wim Taymans's avatar
Wim Taymans committed
112

113
  GstIndexEntry *ret_entry;     /* hack to avoid leaking memory */
Wim Taymans's avatar
Wim Taymans committed
114 115
};

116 117
struct _GstFileIndexClass
{
Wim Taymans's avatar
Wim Taymans committed
118 119 120
  GstIndexClass parent_class;
};

121 122
enum
{
Wim Taymans's avatar
Wim Taymans committed
123 124 125 126
  ARG_0,
  ARG_LOCATION,
};

127
static void gst_file_index_dispose (GObject * object);
Wim Taymans's avatar
Wim Taymans committed
128 129

static void
130 131
gst_file_index_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec);
Wim Taymans's avatar
Wim Taymans committed
132
static void
133 134
gst_file_index_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec);
Wim Taymans's avatar
Wim Taymans committed
135 136

static gboolean
137 138
gst_file_index_get_writer_id (GstIndex * _index, gint * id,
    gchar * writer_string);
Wim Taymans's avatar
Wim Taymans committed
139

140 141 142 143 144 145
static void gst_file_index_commit (GstIndex * index, gint writer_id);
static void gst_file_index_add_entry (GstIndex * index, GstIndexEntry * entry);
static GstIndexEntry *gst_file_index_get_assoc_entry (GstIndex * index, gint id,
    GstIndexLookupMethod method,
    GstAssocFlags flags,
    GstFormat format, gint64 value, GCompareDataFunc func, gpointer user_data);
Wim Taymans's avatar
Wim Taymans committed
146 147 148

#define CLASS(file_index)  GST_FILE_INDEX_CLASS (G_OBJECT_GET_CLASS (file_index))

149 150
GType gst_file_index_get_type (void);

151
G_DEFINE_TYPE (GstFileIndex, gst_file_index, GST_TYPE_INDEX);
Wim Taymans's avatar
Wim Taymans committed
152 153

static void
154
gst_file_index_class_init (GstFileIndexClass * klass)
Wim Taymans's avatar
Wim Taymans committed
155 156 157 158
{
  GObjectClass *gobject_class;
  GstIndexClass *gstindex_class;

159 160
  gobject_class = (GObjectClass *) klass;
  gstindex_class = (GstIndexClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
161

162 163 164
  gobject_class->dispose = gst_file_index_dispose;
  gobject_class->set_property = gst_file_index_set_property;
  gobject_class->get_property = gst_file_index_get_property;
Wim Taymans's avatar
Wim Taymans committed
165

166
  gstindex_class->add_entry = gst_file_index_add_entry;
Wim Taymans's avatar
Wim Taymans committed
167
  gstindex_class->get_assoc_entry = gst_file_index_get_assoc_entry;
168 169
  gstindex_class->commit = gst_file_index_commit;
  gstindex_class->get_writer_id = gst_file_index_get_writer_id;
Wim Taymans's avatar
Wim Taymans committed
170 171

  g_object_class_install_property (gobject_class, ARG_LOCATION,
172
      g_param_spec_string ("location", "File Location",
173 174
          "Location of the index file", NULL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Wim Taymans's avatar
Wim Taymans committed
175 176 177
}

static void
178
gst_file_index_init (GstFileIndex * index)
Wim Taymans's avatar
Wim Taymans committed
179
{
180
  GST_DEBUG ("created new file index");
Wim Taymans's avatar
Wim Taymans committed
181 182 183 184

  index->id_index = g_hash_table_new (g_int_hash, g_int_equal);
}

Wim Taymans's avatar
Wim Taymans committed
185
static void
186
_file_index_id_free (GstFileIndexId * index_id, gboolean is_mmapped)
Wim Taymans's avatar
Wim Taymans committed
187 188 189 190 191 192 193
{
  if (index_id->id_desc)
    g_free (index_id->id_desc);
  if (index_id->format)
    g_free (index_id->format);
  if (index_id->array) {
    if (is_mmapped)
194
      munmap (index_id->array->data, ARRAY_TOTAL_SIZE (index_id));
Wim Taymans's avatar
Wim Taymans committed
195 196
    g_array_free (index_id->array, !is_mmapped);
  }
197
  g_slice_free (GstFileIndexId, index_id);
Wim Taymans's avatar
Wim Taymans committed
198 199 200
}

static gboolean
201 202
_id_index_free_helper (gpointer _key, GstFileIndexId * index_id,
    GstFileIndex * index)
Wim Taymans's avatar
Wim Taymans committed
203 204 205 206 207
{
  _file_index_id_free (index_id, index->is_loaded);
  return TRUE;
}

Wim Taymans's avatar
Wim Taymans committed
208
static void
209
gst_file_index_dispose (GObject * object)
Wim Taymans's avatar
Wim Taymans committed
210 211 212
{
  GstFileIndex *index = GST_FILE_INDEX (object);

Wim Taymans's avatar
Wim Taymans committed
213
  if (index->location) {
Wim Taymans's avatar
Wim Taymans committed
214
    g_free (index->location);
Wim Taymans's avatar
Wim Taymans committed
215 216 217 218 219
    index->location = NULL;
  }

  {
    GSList *elem;
220

Wim Taymans's avatar
Wim Taymans committed
221 222 223 224 225
    for (elem = index->unresolved; elem; elem = g_slist_next (elem))
      _file_index_id_free (elem->data, index->is_loaded);
    g_slist_free (index->unresolved);
    index->unresolved = NULL;
  }
226

Wim Taymans's avatar
Wim Taymans committed
227
  g_hash_table_foreach_steal (index->id_index,
228
      (GHRFunc) _id_index_free_helper, index);
Wim Taymans's avatar
Wim Taymans committed
229 230
  g_hash_table_destroy (index->id_index);
  index->id_index = NULL;
Wim Taymans's avatar
Wim Taymans committed
231

232
  gst_index_entry_free (index->ret_entry);      /* hack */
Wim Taymans's avatar
Wim Taymans committed
233

234
  G_OBJECT_CLASS (gst_file_index_parent_class)->dispose (object);
Wim Taymans's avatar
Wim Taymans committed
235 236
}

237 238
struct fi_find_writer_context
{
239 240 241 242
  const gchar *writer_string;
  GstFileIndexId *ii;
};

243
static void
244 245 246 247
_fi_find_writer (gpointer key, gpointer val, gpointer data)
{
  struct fi_find_writer_context *cx = data;
  GstFileIndexId *ii = val;
248

249 250 251 252
  if (strcmp (ii->id_desc, cx->writer_string) == 0)
    cx->ii = ii;
}

Wim Taymans's avatar
Wim Taymans committed
253
static gboolean
254 255
gst_file_index_get_writer_id (GstIndex * _index,
    gint * id, gchar * writer_string)
Wim Taymans's avatar
Wim Taymans committed
256 257 258 259 260 261 262
{
  GstFileIndex *index = GST_FILE_INDEX (_index);
  GSList *pending = index->unresolved;
  gboolean match = FALSE;
  GSList *elem;

  if (!index->is_loaded)
263
    return FALSE;
Wim Taymans's avatar
Wim Taymans committed
264 265 266 267 268 269 270 271

  g_return_val_if_fail (id, FALSE);
  g_return_val_if_fail (writer_string, FALSE);

  index->unresolved = NULL;

  for (elem = pending; elem; elem = g_slist_next (elem)) {
    GstFileIndexId *ii = elem->data;
272

273
    if (strcmp (ii->id_desc, writer_string) != 0) {
Wim Taymans's avatar
Wim Taymans committed
274 275 276
      index->unresolved = g_slist_prepend (index->unresolved, ii);
      continue;
    }
277

Wim Taymans's avatar
Wim Taymans committed
278
    if (match) {
279
      GST_WARNING_OBJECT (index, "Duplicate matches for writer '%s'",
280
          writer_string);
Wim Taymans's avatar
Wim Taymans committed
281 282 283
      continue;
    }

Wim Taymans's avatar
Wim Taymans committed
284 285
    ii->id = *id = ++index->next_id;
    g_hash_table_insert (index->id_index, &ii->id, ii);
Wim Taymans's avatar
Wim Taymans committed
286 287 288 289 290
    match = TRUE;
  }

  g_slist_free (pending);

291 292
  if (!match) {
    struct fi_find_writer_context cx;
293

294 295 296 297 298 299
    cx.writer_string = writer_string;
    cx.ii = NULL;
    g_hash_table_foreach (index->id_index, _fi_find_writer, &cx);

    if (cx.ii) {
      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
  }
Wim Taymans's avatar
Wim Taymans committed
304

Wim Taymans's avatar
Wim Taymans committed
305 306 307 308
  return match;
}

static void
309
_fc_alloc_array (GstFileIndexId * id_index)
Wim Taymans's avatar
Wim Taymans committed
310 311 312
{
  g_assert (!id_index->array);
  id_index->array =
313
      g_array_sized_new (FALSE, FALSE, ARRAY_ROW_SIZE (id_index), 0);
Wim Taymans's avatar
Wim Taymans committed
314 315 316
}

static void
317
gst_file_index_load (GstFileIndex * index)
Wim Taymans's avatar
Wim Taymans committed
318 319 320 321 322 323 324 325 326 327 328 329 330
{
  xmlDocPtr doc;
  xmlNodePtr root, part;
  xmlChar *val;

  g_assert (index->location);
  g_return_if_fail (!index->is_loaded);

  {
    gchar *path = g_strdup_printf ("%s/gstindex.xml", index->location);
    GError *err = NULL;
    gchar *buf;
    gsize len;
331

Wim Taymans's avatar
Wim Taymans committed
332 333
    g_file_get_contents (path, &buf, &len, &err);
    g_free (path);
334
    if (err) {
335
      GST_ERROR_OBJECT (index, "%s", err->message);
336 337
      return;
    }
Wim Taymans's avatar
Wim Taymans committed
338 339 340 341 342 343 344 345

    doc = xmlParseMemory (buf, len);
    g_free (buf);
  }

  //xmlDocFormatDump (stderr, doc, TRUE);

  root = doc->xmlRootNode;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
346
  if (strcmp ((char *) root->name, "gstfileindex") != 0) {
347
    GST_ERROR_OBJECT (index, "root node isn't a gstfileindex");
348 349
    return;
  }
350

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
351 352
  val = xmlGetProp (root, (xmlChar *) "version");
  if (!val || atoi ((char *) val) != 1) {
353
    GST_ERROR_OBJECT (index, "version != 1");
354 355
    return;
  }
Wim Taymans's avatar
Wim Taymans committed
356 357 358
  free (val);

  for (part = root->children; part; part = part->next) {
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
359
    if (strcmp ((char *) part->name, "writers") == 0) {
Wim Taymans's avatar
Wim Taymans committed
360
      xmlNodePtr writer;
361

Wim Taymans's avatar
Wim Taymans committed
362
      for (writer = part->children; writer; writer = writer->next) {
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
363
        xmlChar *datafile = xmlGetProp (writer, (xmlChar *) "datafile");
364 365 366 367 368 369 370 371 372 373 374 375
        gchar *path = g_strdup_printf ("%s/%s", index->location, datafile);
        int fd;
        GstFileIndexId *id_index;
        xmlNodePtr wpart;
        xmlChar *entries_str;
        gpointer array_data;

        free (datafile);

        fd = open (path, O_RDONLY);
        g_free (path);
        if (fd < 0) {
376
          GST_ERROR_OBJECT (index,
377
              "Can't open '%s': %s", path, g_strerror (errno));
378 379 380
          continue;
        }

381
        id_index = g_slice_new0 (GstFileIndexId);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
382
        id_index->id_desc = (char *) xmlGetProp (writer, (xmlChar *) "id");
383 384

        for (wpart = writer->children; wpart; wpart = wpart->next) {
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
385 386
          if (strcmp ((char *) wpart->name, "formats") == 0) {
            xmlChar *count_str = xmlGetProp (wpart, (xmlChar *) "count");
387 388 389
            gint fx = 0;
            xmlNodePtr format;

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
390
            id_index->nformats = atoi ((char *) count_str);
391 392 393 394 395
            free (count_str);

            id_index->format = g_new (GstFormat, id_index->nformats);

            for (format = wpart->children; format; format = format->next) {
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
396 397
              xmlChar *nick = xmlGetProp (format, (xmlChar *) "nick");
              GstFormat fmt = gst_format_get_by_nick ((gchar *) nick);
398 399

              if (fmt == GST_FORMAT_UNDEFINED)
400
                GST_ERROR_OBJECT (index, "format '%s' undefined", nick);
401 402 403 404 405
              g_assert (fx < id_index->nformats);
              id_index->format[fx++] = fmt;
              free (nick);
            }
          } else
406
            GST_INFO_OBJECT (index, "unknown wpart '%s'", wpart->name);
407 408 409 410 411 412
        }

        g_assert (id_index->nformats > 0);
        _fc_alloc_array (id_index);
        g_assert (id_index->array->data == NULL);       /* little bit risky */

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
413 414
        entries_str = xmlGetProp (writer, (xmlChar *) "entries");
        id_index->array->len = atoi ((char *) entries_str);
415 416 417 418 419 420 421
        free (entries_str);

        array_data =
            mmap (NULL, ARRAY_TOTAL_SIZE (id_index), PROT_READ, MAP_SHARED, fd,
            0);
        close (fd);
        if (array_data == MAP_FAILED) {
422
          GST_ERROR_OBJECT (index,
423
              "mmap %s failed: %s", path, g_strerror (errno));
424 425 426 427 428 429
          continue;
        }

        id_index->array->data = array_data;

        index->unresolved = g_slist_prepend (index->unresolved, id_index);
Wim Taymans's avatar
Wim Taymans committed
430 431
      }
    } else
432
      GST_INFO_OBJECT (index, "unknown part '%s'", part->name);
Wim Taymans's avatar
Wim Taymans committed
433 434 435 436
  }

  xmlFreeDoc (doc);

437
  GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
Wim Taymans's avatar
Wim Taymans committed
438
  index->is_loaded = TRUE;
439
  GST_LOG_OBJECT (index, "index %s loaded OK", index->location);
Wim Taymans's avatar
Wim Taymans committed
440 441 442
}

static void
443 444
gst_file_index_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec)
Wim Taymans's avatar
Wim Taymans committed
445 446 447 448
{
  GstFileIndex *index = GST_FILE_INDEX (object);

  switch (prop_id) {
449 450
    case ARG_LOCATION:
      if (index->location)
451
        g_free (index->location);
452
      index->location = g_value_dup_string (value);
Wim Taymans's avatar
Wim Taymans committed
453

454
      if (index->location && !g_hash_table_size (index->id_index))
455
        gst_file_index_load (index);
456
      break;
Wim Taymans's avatar
Wim Taymans committed
457 458 459 460
  }
}

static void
461 462
gst_file_index_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec)
Wim Taymans's avatar
Wim Taymans committed
463 464
{
  GstFileIndex *index = GST_FILE_INDEX (object);
465

Wim Taymans's avatar
Wim Taymans committed
466
  switch (prop_id) {
467 468 469
    case ARG_LOCATION:
      g_value_set_string (value, index->location);
      break;
Wim Taymans's avatar
Wim Taymans committed
470 471 472 473
  }
}

static void
474
_file_index_id_save_xml (gpointer _key, GstFileIndexId * ii, xmlNodePtr writers)
Wim Taymans's avatar
Wim Taymans committed
475 476
{
  const gint bufsize = 16;
477
  gchar buf[16];
Wim Taymans's avatar
Wim Taymans committed
478 479 480
  xmlNodePtr writer;
  xmlNodePtr formats;
  gint xx;
481

482 483 484 485 486
  if (!ii->array) {
    GST_INFO ("Index for %s is empty", ii->id_desc);
    return;
  }

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
487 488
  writer = xmlNewChild (writers, NULL, (xmlChar *) "writer", NULL);
  xmlSetProp (writer, (xmlChar *) "id", (xmlChar *) ii->id_desc);
Wim Taymans's avatar
Wim Taymans committed
489
  g_snprintf (buf, bufsize, "%d", ii->array->len);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
490
  xmlSetProp (writer, (xmlChar *) "entries", (xmlChar *) buf);
491
  g_snprintf (buf, bufsize, "%d", ii->id);      /* any unique number is OK */
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
492
  xmlSetProp (writer, (xmlChar *) "datafile", (xmlChar *) buf);
Wim Taymans's avatar
Wim Taymans committed
493

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
494
  formats = xmlNewChild (writer, NULL, (xmlChar *) "formats", NULL);
Wim Taymans's avatar
Wim Taymans committed
495
  g_snprintf (buf, bufsize, "%d", ii->nformats);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
496
  xmlSetProp (formats, (xmlChar *) "count", (xmlChar *) buf);
Wim Taymans's avatar
Wim Taymans committed
497

498
  for (xx = 0; xx < ii->nformats; xx++) {
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
499
    xmlNodePtr format = xmlNewChild (formats, NULL, (xmlChar *) "format", NULL);
500 501
    const GstFormatDefinition *def = gst_format_get_details (ii->format[xx]);

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
502
    xmlSetProp (format, (xmlChar *) "nick", (xmlChar *) def->nick);
Wim Taymans's avatar
Wim Taymans committed
503 504 505
  }
}

506 507 508 509 510 511
/*
  We must save the binary data in separate files because
  mmap wants getpagesize() alignment.  If we append all
  the data to one file then we don't know the appropriate
  padding since the page size isn't fixed.
*/
Wim Taymans's avatar
Wim Taymans committed
512
static void
513 514
_file_index_id_save_entries (gpointer * _key,
    GstFileIndexId * ii, gchar * prefix)
Wim Taymans's avatar
Wim Taymans committed
515
{
Ronald S. Bultje's avatar
Ronald S. Bultje committed
516 517 518 519
  GError *err;
  gchar *path;
  GIOChannel *chan;

520 521 522
  if (!ii->array)
    return;

Ronald S. Bultje's avatar
Ronald S. Bultje committed
523 524
  err = NULL;
  path = g_strdup_printf ("%s/%d", prefix, ii->id);
525
  chan = g_io_channel_new_file (path, "w", &err);
Wim Taymans's avatar
Wim Taymans committed
526
  g_free (path);
527 528 529
  if (err)
    goto fail;

Wim Taymans's avatar
Wim Taymans committed
530
  g_io_channel_set_encoding (chan, NULL, &err);
531 532
  if (err)
    goto fail;
Wim Taymans's avatar
Wim Taymans committed
533 534

  g_io_channel_write_chars (chan,
535 536 537
      ii->array->data, ARRAY_TOTAL_SIZE (ii), NULL, &err);
  if (err)
    goto fail;
Wim Taymans's avatar
Wim Taymans committed
538 539

  g_io_channel_shutdown (chan, TRUE, &err);
540 541
  if (err)
    goto fail;
Wim Taymans's avatar
Wim Taymans committed
542 543

  g_io_channel_unref (chan);
544 545
  return;

546
fail:
547
  GST_ERROR ("%s", err->message);
Wim Taymans's avatar
Wim Taymans committed
548 549
}

550 551 552
/*
  We have to save the whole set of indexes into a single file
  so it doesn't make sense to commit only a single writer.
Wim Taymans's avatar
Wim Taymans committed
553

554 555 556 557
  i suggest:

  gst_index_commit (index, -1);
*/
Wim Taymans's avatar
Wim Taymans committed
558
static void
559
gst_file_index_commit (GstIndex * _index, gint _writer_id)
Wim Taymans's avatar
Wim Taymans committed
560 561 562 563 564 565 566 567 568 569 570
{
  GstFileIndex *index = GST_FILE_INDEX (_index);
  xmlDocPtr doc;
  xmlNodePtr writers;
  GError *err = NULL;
  gchar *path;
  GIOChannel *tocfile;

  g_return_if_fail (index->location);
  g_return_if_fail (!index->is_loaded);

571
  GST_OBJECT_FLAG_UNSET (index, GST_INDEX_WRITABLE);
Wim Taymans's avatar
Wim Taymans committed
572

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
573 574 575 576
  doc = xmlNewDoc ((xmlChar *) "1.0");
  doc->xmlRootNode =
      xmlNewDocNode (doc, NULL, (xmlChar *) "gstfileindex", NULL);
  xmlSetProp (doc->xmlRootNode, (xmlChar *) "version", (xmlChar *) "1");
Wim Taymans's avatar
Wim Taymans committed
577

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
578
  writers = xmlNewChild (doc->xmlRootNode, NULL, (xmlChar *) "writers", NULL);
Wim Taymans's avatar
Wim Taymans committed
579
  g_hash_table_foreach (index->id_index,
580
      (GHFunc) _file_index_id_save_xml, writers);
Wim Taymans's avatar
Wim Taymans committed
581

582
  if (mkdir (index->location, 0777) && errno != EEXIST) {
583 584
    GST_ERROR_OBJECT (index, "mkdir %s: %s", index->location,
        g_strerror (errno));
585 586
    return;
  }
Wim Taymans's avatar
Wim Taymans committed
587 588

  path = g_strdup_printf ("%s/gstindex.xml", index->location);
589
  tocfile = g_io_channel_new_file (path, "w", &err);
Wim Taymans's avatar
Wim Taymans committed
590
  g_free (path);
591
  if (err) {
592
    GST_ERROR_OBJECT (index, "%s", err->message);
593 594
    return;
  }
Wim Taymans's avatar
Wim Taymans committed
595 596

  g_io_channel_set_encoding (tocfile, NULL, &err);
597
  if (err) {
598
    GST_ERROR_OBJECT (index, "%s", err->message);
599 600
    return;
  }
Wim Taymans's avatar
Wim Taymans committed
601 602 603 604

  {
    xmlChar *xmlmem;
    int xmlsize;
605

Wim Taymans's avatar
Wim Taymans committed
606
    xmlDocDumpMemory (doc, &xmlmem, &xmlsize);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
607
    g_io_channel_write_chars (tocfile, (gchar *) xmlmem, xmlsize, NULL, &err);
608
    if (err) {
609
      GST_ERROR_OBJECT (index, "%s", err->message);
610 611
      return;
    }
Wim Taymans's avatar
Wim Taymans committed
612 613 614 615 616
    xmlFreeDoc (doc);
    free (xmlmem);
  }

  g_io_channel_shutdown (tocfile, TRUE, &err);
617
  if (err) {
618
    GST_ERROR_OBJECT (index, "%s", err->message);
619 620
    return;
  }
Wim Taymans's avatar
Wim Taymans committed
621 622 623 624

  g_io_channel_unref (tocfile);

  g_hash_table_foreach (index->id_index,
625
      (GHFunc) _file_index_id_save_entries, index->location);
Wim Taymans's avatar
Wim Taymans committed
626 627 628
}

static void
629
gst_file_index_add_id (GstIndex * index, GstIndexEntry * entry)
Wim Taymans's avatar
Wim Taymans committed
630 631 632 633 634 635 636
{
  GstFileIndex *fileindex = GST_FILE_INDEX (index);
  GstFileIndexId *id_index;

  id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);

  if (!id_index) {
637
    id_index = g_slice_new0 (GstFileIndexId);
Wim Taymans's avatar
Wim Taymans committed
638 639 640 641

    id_index->id = entry->id;
    id_index->id_desc = g_strdup (entry->data.id.description);

642 643
    /* It would be useful to know the GType of the writer so
       we can try to cope with changes in the id_desc path. */
Wim Taymans's avatar
Wim Taymans committed
644

Wim Taymans's avatar
Wim Taymans committed
645
    g_hash_table_insert (fileindex->id_index, &id_index->id, id_index);
Wim Taymans's avatar
Wim Taymans committed
646 647 648
  }
}

649 650
/* This algorithm differs from libc bsearch in the handling
   of non-exact matches. */
Wim Taymans's avatar
Wim Taymans committed
651 652

static gboolean
653 654 655 656
_fc_bsearch (GArray * ary,
    gint stride,
    gint * ret,
    GCompareDataFunc compare, gconstpointer sample, gpointer user_data)
Wim Taymans's avatar
Wim Taymans committed
657 658 659 660 661 662 663 664 665
{
  gint first, last;
  gint mid;
  gint midsize;
  gint cmp;
  gint tx;

  g_return_val_if_fail (compare, FALSE);

666 667 668 669 670
  if (!ary->len) {
    if (ret)
      *ret = 0;
    return FALSE;
  }
Wim Taymans's avatar
Wim Taymans committed
671 672 673 674 675

  first = 0;
  last = ary->len - 1;

  midsize = last - first;
676

Wim Taymans's avatar
Wim Taymans committed
677 678
  while (midsize > 1) {
    mid = first + midsize / 2;
679 680 681 682 683 684

    cmp = (*compare) (sample, ary->data + mid * stride, user_data);

    if (cmp == 0) {
      /* if there are multiple matches then scan for the first match */
      while (mid > 0 &&
685 686
          (*compare) (sample, ary->data + (mid - 1) * stride, user_data) == 0)
        --mid;
687 688

      if (ret)
689
        *ret = mid;
690 691 692
      return TRUE;
    }

Wim Taymans's avatar
Wim Taymans committed
693
    if (cmp < 0)
694
      last = mid - 1;
Wim Taymans's avatar
Wim Taymans committed
695
    else
696 697
      first = mid + 1;

Wim Taymans's avatar
Wim Taymans committed
698 699 700
    midsize = last - first;
  }

701 702
  for (tx = first; tx <= last; tx++) {
    cmp = (*compare) (sample, ary->data + tx * stride, user_data);
Wim Taymans's avatar
Wim Taymans committed
703

704 705
    if (cmp < 0) {
      if (ret)
706
        *ret = tx;
707
      return FALSE;
Wim Taymans's avatar
Wim Taymans committed
708
    }
709 710
    if (cmp == 0) {
      if (ret)
711
        *ret = tx;
712 713 714
      return TRUE;
    }
  }
Wim Taymans's avatar
Wim Taymans committed
715

716 717
  if (ret)
    *ret = last + 1;
Wim Taymans's avatar
Wim Taymans committed
718 719 720 721
  return FALSE;
}

static gint
722
file_index_compare (gconstpointer sample, gconstpointer row, gpointer user_data)
Wim Taymans's avatar
Wim Taymans committed
723 724 725 726
{
  //GstFileIndexId *id_index = user_data;
  const GstIndexAssociation *ca = sample;
  gint64 val1 = ca->value;
727 728
  gint64 val2_be = ARRAY_ROW_VALUE (row, ca->format);
  gint64 val2 = GINT64_FROM_BE (val2_be);
Wim Taymans's avatar
Wim Taymans committed
729
  gint64 diff = val2 - val1;
730

731
  return (diff == 0 ? 0 : (diff < 0 ? 1 : -1));
Wim Taymans's avatar
Wim Taymans committed
732 733 734
}

static void
735
gst_file_index_add_association (GstIndex * index, GstIndexEntry * entry)
Wim Taymans's avatar
Wim Taymans committed
736 737 738 739 740 741 742 743 744 745 746 747 748
{
  GstFileIndex *fileindex = GST_FILE_INDEX (index);
  GstFileIndexId *id_index;
  gint mx;
  GstIndexAssociation sample;
  gboolean exact;

  id_index = g_hash_table_lookup (fileindex->id_index, &entry->id);
  if (!id_index)
    return;

  if (!id_index->nformats) {
    gint fx;
749

Wim Taymans's avatar
Wim Taymans committed
750
    id_index->nformats = GST_INDEX_NASSOCS (entry);
751
    GST_LOG_OBJECT (fileindex, "creating %d formats for %d",
752
        id_index->nformats, entry->id);
Wim Taymans's avatar
Wim Taymans committed
753
    id_index->format = g_new (GstFormat, id_index->nformats);
754
    for (fx = 0; fx < id_index->nformats; fx++)
Wim Taymans's avatar
Wim Taymans committed
755 756 757 758 759
      id_index->format[fx] = GST_INDEX_ASSOC_FORMAT (entry, fx);
    _fc_alloc_array (id_index);
  } else {
    /* only sanity checking */
    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));
Wim Taymans's avatar
Wim Taymans committed
762 763
    else {
      gint fx;
764 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));
Wim Taymans's avatar
Wim Taymans committed
769 770 771 772
    }
  }

  /* this is a hack, we should use a private structure instead */
773
  sample.format = GST_FORMAT_UNDEFINED;
Wim Taymans's avatar
Wim Taymans committed
774 775 776
  sample.value = GST_INDEX_ASSOC_VALUE (entry, 0);

  exact =
777 778
      _fc_bsearch (id_index->array, ARRAY_ROW_SIZE (id_index),
      &mx, file_index_compare, &sample, id_index);
Wim Taymans's avatar
Wim Taymans committed
779 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));
Wim Taymans's avatar
Wim Taymans committed
785 786 787 788
    return;
  }

  {
789
    gchar *row_data = (gchar *) g_malloc (ARRAY_ROW_SIZE (id_index));
Wim Taymans's avatar
Wim Taymans committed
790
    gint fx;
Wim Taymans's avatar
Wim Taymans committed
791

792
    gint32 flags_host = GST_INDEX_ASSOC_FLAGS (entry);
793

794
    ARRAY_ROW_FLAGS (row_data) = GINT32_TO_BE (flags_host);
Wim Taymans's avatar
Wim Taymans committed
795

796 797
    for (fx = 0; fx < id_index->nformats; fx++) {
      gint64 val_host = GST_INDEX_ASSOC_VALUE (entry, fx);
798

799 800
      ARRAY_ROW_VALUE (row_data, fx) = GINT64_TO_BE (val_host);
    }
Wim Taymans's avatar
Wim Taymans committed
801

Wim Taymans's avatar
Wim Taymans committed
802
    g_array_insert_vals (id_index->array, mx, row_data, 1);
803 804

    g_free (row_data);
Wim Taymans's avatar
Wim Taymans committed
805 806 807 808 809 810 811 812 813
  }
}

/*
static void
show_entry (GstIndexEntry *entry)
{
  switch (entry->type) {
    case GST_INDEX_ENTRY_ID:
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
814
      g_print ("id %d describes writer %s\n", entry->id,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
815
                      GST_INDEX_ID_DESCRIPTION (entry));
Wim Taymans's avatar
Wim Taymans committed
816 817
      break;
    case GST_INDEX_ENTRY_FORMAT:
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
818
      g_print ("%d: registered format %d for %s\n", entry->id,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
819 820
                      GST_INDEX_FORMAT_FORMAT (entry),
                      GST_INDEX_FORMAT_KEY (entry));
Wim Taymans's avatar
Wim Taymans committed
821 822 823 824 825 826 827
      break;
    case GST_INDEX_ENTRY_ASSOCIATION:
    {
      gint i;

      g_print ("%d: %08x ", entry->id, GST_INDEX_ASSOC_FLAGS (entry));
      for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) {
828
        g_print ("%d %" G_GINT64_FORMAT, GST_INDEX_ASSOC_FORMAT (entry, i),
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
829
                             GST_INDEX_ASSOC_VALUE (entry, i));
Wim Taymans's avatar
Wim Taymans committed
830 831 832 833 834 835 836 837 838 839 840
      }
      g_print ("\n");
      break;
    }
    default:
      break;
  }
}
*/

static void
841
gst_file_index_add_entry (GstIndex * index, GstIndexEntry * entry)
Wim Taymans's avatar
Wim Taymans committed
842
{
843
  GST_LOG_OBJECT (index, "adding this entry");
Wim Taymans's avatar
Wim Taymans committed
844

845 846 847 848 849 850 851 852
  switch (entry->type) {
    case GST_INDEX_ENTRY_ID:
      gst_file_index_add_id (index, entry);
      break;
    case GST_INDEX_ENTRY_ASSOCIATION:
      gst_file_index_add_association (index, entry);
      break;
    case GST_INDEX_ENTRY_OBJECT:
853
      GST_ERROR_OBJECT (index, "gst_file_index_add_object not implemented");
854 855 856 857 858
      break;
    case GST_INDEX_ENTRY_FORMAT:
      /*
         We infer the formats from the entry itself so this type of
         GST_INDEX_ENTRY_* can probably go away.
859
       */
860
      GST_DEBUG_OBJECT (index, "gst_file_index_add_format not implemented");
861 862 863
      break;
    default:
      break;
Wim Taymans's avatar
Wim Taymans committed
864 865 866
  }
}

867 868 869 870 871 872 873
static GstIndexEntry *
gst_file_index_get_assoc_entry (GstIndex * index,
    gint id,
    GstIndexLookupMethod method,
    GstAssocFlags flags,
    GstFormat format,
    gint64 value, GCompareDataFunc _ignore_func, gpointer _ignore_user_data)
Wim Taymans's avatar
Wim Taymans committed
874 875 876 877 878 879 880 881 882 883 884 885
{
  GstFileIndex *fileindex = GST_FILE_INDEX (index);
  GstFileIndexId *id_index;
  gint formatx = -1;
  gint fx;
  GstIndexAssociation sample;
  gint mx;
  gboolean exact;
  gpointer row_data;
  GstIndexEntry *entry;
  gint xx;

886 887
  g_return_val_if_fail (id > 0, NULL);

Wim Taymans's avatar
Wim Taymans committed
888
  id_index = g_hash_table_lookup (fileindex->id_index, &id);
889
  if (!id_index) {
890
    GST_WARNING_OBJECT (fileindex, "writer %d unavailable", id);
Wim Taymans's avatar
Wim Taymans committed
891
    return NULL;
892
  }
Wim Taymans's avatar
Wim Taymans committed
893

894 895 896 897 898
  for (fx = 0; fx < id_index->nformats; fx++)
    if (id_index->format[fx] == format) {
      formatx = fx;
      break;
    }
Wim Taymans's avatar
Wim Taymans committed
899 900

  if (formatx == -1) {
901
    GST_WARNING_OBJECT (fileindex, "format %d not available", format);
Wim Taymans's avatar
Wim Taymans committed
902 903 904 905
    return NULL;
  }

  /* this is a hack, we should use a private structure instead */
906
  sample.format = (GstFormat) formatx;
Wim Taymans's avatar
Wim Taymans committed
907 908
  sample.value = value;

909
  exact = _fc_bsearch (id_index->array, ARRAY_ROW_SIZE (id_index),
910
      &mx, file_index_compare, &sample, id_index);
Wim Taymans's avatar
Wim Taymans committed
911 912 913 914 915 916

  if (!exact) {
    if (method == GST_INDEX_LOOKUP_EXACT)
      return NULL;
    else if (method == GST_INDEX_LOOKUP_BEFORE) {
      if (mx == 0)
917
        return NULL;
Wim Taymans's avatar
Wim Taymans committed
918 919 920
      mx -= 1;
    } else if (method == GST_INDEX_LOOKUP_AFTER) {
      if (mx == id_index->array->len)
921
        return NULL;