c7fb607703344619ca4a65e2337898518fb80d44
[platform/upstream/gstreamer.git] / ext / gnomevfs / gstgnomevfssrc.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *                    2000 Wim Taymans <wtay@chello.be>
4  *                    2001 Bastien Nocera <hadess@hadess.net>
5  *                    2002 Kristian Rietveld <kris@gtk.org>
6  *                    2002,2003 Colin Walters <walters@gnu.org>
7  *
8  * gnomevfssrc.c:
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the
22  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */
25
26 /**
27  * SECTION:element-gnomevfssrc
28  * @see_also: #GstFileSrc, #GstGnomeVFSSink
29  *
30  * This plugin reads data from a local or remote location specified
31  * by an URI. This location can be specified using any protocol supported by
32  * the GnomeVFS library. Common protocols are 'file', 'http', 'ftp', or 'smb'.
33  *
34  * In case the #GstGnomeVFSSrc:iradio-mode property is set and the
35  * location is a http resource, gnomevfssrc will send special icecast http
36  * headers to the server to request additional icecast metainformation. If
37  * the server is not an icecast server, it will display the same behaviour
38  * as if the #GstGnomeVFSSrc:iradio-mode property was not set. However,
39  * if the server is in fact an icecast server, gnomevfssrc will output
40  * data with a media type of application/x-icy, in which case you will
41  * need to use the #GstICYDemux element as follow-up element to extract
42  * the icecast meta data and to determine the underlying media type.
43  *
44  * <refsect2>
45  * <title>Example launch lines</title>
46  * |[
47  * gst-launch -v gnomevfssrc location=file:///home/joe/foo.xyz ! fakesink
48  * ]| The above pipeline will simply read a local file and do nothing with the
49  * data read. Instead of gnomevfssrc, we could just as well have used the
50  * filesrc element here.
51  * |[
52  * gst-launch -v gnomevfssrc location=smb://othercomputer/foo.xyz ! filesink location=/home/joe/foo.xyz
53  * ]| The above pipeline will copy a file from a remote host to the local file
54  * system using the Samba protocol.
55  * |[
56  * gst-launch -v gnomevfssrc location=http://music.foobar.com/demo.mp3 ! mad ! audioconvert ! audioresample ! alsasink
57  * ]| The above pipeline will read and decode and play an mp3 file from a
58  * web server using the http protocol.
59  * </refsect2>
60  */
61
62
63 #define BROKEN_SIG 1
64 /*#undef BROKEN_SIG */
65
66 #ifdef HAVE_CONFIG_H
67 #include "config.h"
68 #endif
69
70 #include "gst/gst-i18n-plugin.h"
71
72 #include "gstgnomevfssrc.h"
73
74 #include <stdlib.h>
75 #include <sys/types.h>
76 #include <sys/socket.h>
77 #include <sys/time.h>
78 #include <netinet/in.h>
79 #include <arpa/inet.h>
80 #include <netdb.h>
81 #include <sys/stat.h>
82 #include <fcntl.h>
83 #include <unistd.h>
84 #include <sys/mman.h>
85 #include <errno.h>
86 #include <string.h>
87
88 #include <gst/gst.h>
89 #include <gst/tag/tag.h>
90
91 /* gnome-vfs.h doesn't include the following header, which we need: */
92 #include <libgnomevfs/gnome-vfs-standard-callbacks.h>
93
94 GST_DEBUG_CATEGORY_STATIC (gnomevfssrc_debug);
95 #define GST_CAT_DEFAULT gnomevfssrc_debug
96
97 static const GstElementDetails gst_gnome_vfs_src_details =
98 GST_ELEMENT_DETAILS ("GnomeVFS Source",
99     "Source/File",
100     "Read from any GnomeVFS-supported file",
101     "Bastien Nocera <hadess@hadess.net>\n"
102     "Ronald S. Bultje <rbultje@ronald.bitfreak.net>");
103
104 static GStaticMutex count_lock = G_STATIC_MUTEX_INIT;
105 static gint ref_count = 0;
106 static gboolean vfs_owner = FALSE;
107
108 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
109     GST_PAD_SRC,
110     GST_PAD_ALWAYS,
111     GST_STATIC_CAPS_ANY);
112
113 enum
114 {
115   ARG_0,
116   ARG_HANDLE,
117   ARG_LOCATION,
118   ARG_IRADIO_MODE,
119   ARG_IRADIO_NAME,
120   ARG_IRADIO_GENRE,
121   ARG_IRADIO_URL,
122   ARG_IRADIO_TITLE
123 };
124
125 static void gst_gnome_vfs_src_base_init (gpointer g_class);
126 static void gst_gnome_vfs_src_class_init (GstGnomeVFSSrcClass * klass);
127 static void gst_gnome_vfs_src_init (GstGnomeVFSSrc * gnomevfssrc);
128 static void gst_gnome_vfs_src_finalize (GObject * object);
129 static void gst_gnome_vfs_src_uri_handler_init (gpointer g_iface,
130     gpointer iface_data);
131
132 static void gst_gnome_vfs_src_set_property (GObject * object, guint prop_id,
133     const GValue * value, GParamSpec * pspec);
134 static void gst_gnome_vfs_src_get_property (GObject * object, guint prop_id,
135     GValue * value, GParamSpec * pspec);
136
137 static gboolean gst_gnome_vfs_src_stop (GstBaseSrc * src);
138 static gboolean gst_gnome_vfs_src_start (GstBaseSrc * src);
139 static gboolean gst_gnome_vfs_src_is_seekable (GstBaseSrc * src);
140 static gboolean gst_gnome_vfs_src_check_get_range (GstBaseSrc * src);
141 static gboolean gst_gnome_vfs_src_get_size (GstBaseSrc * src, guint64 * size);
142 static GstFlowReturn gst_gnome_vfs_src_create (GstBaseSrc * basesrc,
143     guint64 offset, guint size, GstBuffer ** buffer);
144 static gboolean gst_gnome_vfs_src_query (GstBaseSrc * src, GstQuery * query);
145
146 static GstElementClass *parent_class = NULL;
147
148 GType
149 gst_gnome_vfs_src_get_type (void)
150 {
151   static GType gnomevfssrc_type = 0;
152
153   if (!gnomevfssrc_type) {
154     static const GTypeInfo gnomevfssrc_info = {
155       sizeof (GstGnomeVFSSrcClass),
156       gst_gnome_vfs_src_base_init,
157       NULL,
158       (GClassInitFunc) gst_gnome_vfs_src_class_init,
159       NULL,
160       NULL,
161       sizeof (GstGnomeVFSSrc),
162       0,
163       (GInstanceInitFunc) gst_gnome_vfs_src_init,
164     };
165     static const GInterfaceInfo urihandler_info = {
166       gst_gnome_vfs_src_uri_handler_init,
167       NULL,
168       NULL
169     };
170
171     gnomevfssrc_type =
172         g_type_register_static (GST_TYPE_BASE_SRC,
173         "GstGnomeVFSSrc", &gnomevfssrc_info, 0);
174     g_type_add_interface_static (gnomevfssrc_type, GST_TYPE_URI_HANDLER,
175         &urihandler_info);
176   }
177   return gnomevfssrc_type;
178 }
179
180 static void
181 gst_gnome_vfs_src_base_init (gpointer g_class)
182 {
183   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
184
185   gst_element_class_add_pad_template (element_class,
186       gst_static_pad_template_get (&srctemplate));
187   gst_element_class_set_details (element_class, &gst_gnome_vfs_src_details);
188
189   GST_DEBUG_CATEGORY_INIT (gnomevfssrc_debug, "gnomevfssrc", 0,
190       "Gnome-VFS Source");
191 }
192
193 static void
194 gst_gnome_vfs_src_class_init (GstGnomeVFSSrcClass * klass)
195 {
196   GObjectClass *gobject_class;
197   GstElementClass *gstelement_class;
198   GstBaseSrcClass *gstbasesrc_class;
199
200   gobject_class = G_OBJECT_CLASS (klass);
201   gstelement_class = GST_ELEMENT_CLASS (klass);
202   gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
203
204   parent_class = g_type_class_peek_parent (klass);
205
206   gobject_class->finalize = gst_gnome_vfs_src_finalize;
207   gobject_class->set_property = gst_gnome_vfs_src_set_property;
208   gobject_class->get_property = gst_gnome_vfs_src_get_property;
209
210   /* properties */
211   gst_element_class_install_std_props (GST_ELEMENT_CLASS (klass),
212       "location", ARG_LOCATION, G_PARAM_READWRITE, NULL);
213   g_object_class_install_property (gobject_class,
214       ARG_HANDLE,
215       g_param_spec_boxed ("handle",
216           "GnomeVFSHandle", "Handle for GnomeVFS",
217           GST_TYPE_GNOME_VFS_HANDLE,
218           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
219
220   /* icecast stuff */
221   g_object_class_install_property (gobject_class,
222       ARG_IRADIO_MODE,
223       g_param_spec_boolean ("iradio-mode",
224           "iradio-mode",
225           "Enable internet radio mode (extraction of shoutcast/icecast metadata)",
226           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
227   g_object_class_install_property (gobject_class,
228       ARG_IRADIO_NAME,
229       g_param_spec_string ("iradio-name",
230           "iradio-name", "Name of the stream", NULL,
231           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
232   g_object_class_install_property (gobject_class, ARG_IRADIO_GENRE,
233       g_param_spec_string ("iradio-genre", "iradio-genre",
234           "Genre of the stream", NULL,
235           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
236   g_object_class_install_property (gobject_class, ARG_IRADIO_URL,
237       g_param_spec_string ("iradio-url", "iradio-url",
238           "Homepage URL for radio stream", NULL,
239           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
240   g_object_class_install_property (gobject_class, ARG_IRADIO_TITLE,
241       g_param_spec_string ("iradio-title", "iradio-title",
242           "Name of currently playing song", NULL,
243           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
244
245   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_start);
246   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_stop);
247   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_get_size);
248   gstbasesrc_class->is_seekable =
249       GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_is_seekable);
250   gstbasesrc_class->check_get_range =
251       GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_check_get_range);
252   gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_create);
253   gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_gnome_vfs_src_query);
254 }
255
256 static void
257 gst_gnome_vfs_src_init (GstGnomeVFSSrc * gnomevfssrc)
258 {
259   gnomevfssrc->uri = NULL;
260   gnomevfssrc->uri_name = NULL;
261   gnomevfssrc->handle = NULL;
262   gnomevfssrc->curoffset = 0;
263   gnomevfssrc->seekable = FALSE;
264
265   gnomevfssrc->iradio_mode = FALSE;
266   gnomevfssrc->http_callbacks_pushed = FALSE;
267   gnomevfssrc->iradio_name = NULL;
268   gnomevfssrc->iradio_genre = NULL;
269   gnomevfssrc->iradio_url = NULL;
270   gnomevfssrc->iradio_title = NULL;
271
272   g_static_mutex_lock (&count_lock);
273   if (ref_count == 0) {
274     /* gnome vfs engine init */
275     if (gnome_vfs_initialized () == FALSE) {
276       gnome_vfs_init ();
277       vfs_owner = TRUE;
278     }
279   }
280   ref_count++;
281   g_static_mutex_unlock (&count_lock);
282 }
283
284 static void
285 gst_gnome_vfs_src_finalize (GObject * object)
286 {
287   GstGnomeVFSSrc *src = GST_GNOME_VFS_SRC (object);
288
289   g_static_mutex_lock (&count_lock);
290   ref_count--;
291   if (ref_count == 0 && vfs_owner) {
292     if (gnome_vfs_initialized () == TRUE) {
293       gnome_vfs_shutdown ();
294     }
295   }
296   g_static_mutex_unlock (&count_lock);
297
298   if (src->uri) {
299     gnome_vfs_uri_unref (src->uri);
300     src->uri = NULL;
301   }
302
303   g_free (src->uri_name);
304   src->uri_name = NULL;
305
306   g_free (src->iradio_name);
307   src->iradio_name = NULL;
308
309   g_free (src->iradio_genre);
310   src->iradio_genre = NULL;
311
312   g_free (src->iradio_url);
313   src->iradio_url = NULL;
314
315   g_free (src->iradio_title);
316   src->iradio_title = NULL;
317
318   G_OBJECT_CLASS (parent_class)->finalize (object);
319 }
320
321 /*
322  * URI interface support.
323  */
324
325 static GstURIType
326 gst_gnome_vfs_src_uri_get_type (void)
327 {
328   return GST_URI_SRC;
329 }
330
331 static gchar **
332 gst_gnome_vfs_src_uri_get_protocols (void)
333 {
334   return gst_gnomevfs_get_supported_uris ();
335 }
336
337 static const gchar *
338 gst_gnome_vfs_src_uri_get_uri (GstURIHandler * handler)
339 {
340   GstGnomeVFSSrc *src = GST_GNOME_VFS_SRC (handler);
341
342   return src->uri_name;
343 }
344
345 static gboolean
346 gst_gnome_vfs_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
347 {
348   GstGnomeVFSSrc *src = GST_GNOME_VFS_SRC (handler);
349
350   if (GST_STATE (src) == GST_STATE_PLAYING ||
351       GST_STATE (src) == GST_STATE_PAUSED)
352     return FALSE;
353
354   g_object_set (G_OBJECT (src), "location", uri, NULL);
355
356   return TRUE;
357 }
358
359 static void
360 gst_gnome_vfs_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
361 {
362   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
363
364   iface->get_type = gst_gnome_vfs_src_uri_get_type;
365   iface->get_protocols = gst_gnome_vfs_src_uri_get_protocols;
366   iface->get_uri = gst_gnome_vfs_src_uri_get_uri;
367   iface->set_uri = gst_gnome_vfs_src_uri_set_uri;
368 }
369
370 static void
371 gst_gnome_vfs_src_set_property (GObject * object, guint prop_id,
372     const GValue * value, GParamSpec * pspec)
373 {
374   GstGnomeVFSSrc *src;
375
376   src = GST_GNOME_VFS_SRC (object);
377
378   switch (prop_id) {
379     case ARG_LOCATION:{
380       const gchar *new_location;
381
382       /* the element must be stopped or paused in order to do this */
383       if (GST_STATE (src) == GST_STATE_PLAYING ||
384           GST_STATE (src) == GST_STATE_PAUSED)
385         break;
386
387       if (src->uri) {
388         gnome_vfs_uri_unref (src->uri);
389         src->uri = NULL;
390       }
391       if (src->uri_name) {
392         g_free (src->uri_name);
393         src->uri_name = NULL;
394       }
395
396       new_location = g_value_get_string (value);
397       if (new_location) {
398         src->uri_name = gst_gnome_vfs_location_to_uri_string (new_location);
399         src->uri = gnome_vfs_uri_new (src->uri_name);
400       }
401       break;
402     }
403     case ARG_HANDLE:
404       if (GST_STATE (src) == GST_STATE_NULL ||
405           GST_STATE (src) == GST_STATE_READY) {
406         if (src->uri) {
407           gnome_vfs_uri_unref (src->uri);
408           src->uri = NULL;
409         }
410         if (src->uri_name) {
411           g_free (src->uri_name);
412           src->uri_name = NULL;
413         }
414         src->handle = g_value_get_boxed (value);
415       }
416       break;
417     case ARG_IRADIO_MODE:
418       src->iradio_mode = g_value_get_boolean (value);
419       break;
420     default:
421       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
422       break;
423   }
424 }
425
426 static void
427 gst_gnome_vfs_src_get_property (GObject * object, guint prop_id, GValue * value,
428     GParamSpec * pspec)
429 {
430   GstGnomeVFSSrc *src;
431
432   src = GST_GNOME_VFS_SRC (object);
433
434   switch (prop_id) {
435     case ARG_LOCATION:
436       g_value_set_string (value, src->uri_name);
437       break;
438     case ARG_HANDLE:
439       g_value_set_boxed (value, src->handle);
440       break;
441     case ARG_IRADIO_MODE:
442       g_value_set_boolean (value, src->iradio_mode);
443       break;
444     case ARG_IRADIO_NAME:
445       g_value_set_string (value, src->iradio_name);
446       break;
447     case ARG_IRADIO_GENRE:
448       g_value_set_string (value, src->iradio_genre);
449       break;
450     case ARG_IRADIO_URL:
451       g_value_set_string (value, src->iradio_url);
452       break;
453     case ARG_IRADIO_TITLE:
454       g_value_set_string (value, src->iradio_title);
455       break;
456     default:
457       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
458       break;
459   }
460 }
461
462 static char *
463 gst_gnome_vfs_src_unicodify (const char *str)
464 {
465   const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
466     "GST_TAG_ENCODING", NULL
467   };
468
469   return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
470 }
471
472 static void
473 gst_gnome_vfs_src_send_additional_headers_callback (gconstpointer in,
474     gsize in_size, gpointer out, gsize out_size, gpointer callback_data)
475 {
476   GstGnomeVFSSrc *src = GST_GNOME_VFS_SRC (callback_data);
477   GnomeVFSModuleCallbackAdditionalHeadersOut *out_args =
478       (GnomeVFSModuleCallbackAdditionalHeadersOut *) out;
479
480   if (!src->iradio_mode)
481     return;
482   GST_DEBUG_OBJECT (src, "sending headers\n");
483
484   out_args->headers = g_list_append (out_args->headers,
485       g_strdup ("icy-metadata:1\r\n"));
486 }
487
488 static void
489 gst_gnome_vfs_src_received_headers_callback (gconstpointer in,
490     gsize in_size, gpointer out, gsize out_size, gpointer callback_data)
491 {
492   GList *i;
493   gint icy_metaint;
494   GstGnomeVFSSrc *src = GST_GNOME_VFS_SRC (callback_data);
495   GnomeVFSModuleCallbackReceivedHeadersIn *in_args =
496       (GnomeVFSModuleCallbackReceivedHeadersIn *) in;
497
498   /* This is only used for internet radio stuff right now */
499   if (!src->iradio_mode)
500     return;
501
502   GST_DEBUG_OBJECT (src, "receiving internet radio metadata\n");
503
504   /* FIXME: Could we use "Accept-Ranges: bytes"
505    * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.5
506    * to enable pull-mode?
507    */
508
509   for (i = in_args->headers; i; i = i->next) {
510     char *data = (char *) i->data;
511     char *key = data;
512     char *value = strchr (data, ':');
513
514     if (!value)
515       continue;
516
517     value++;
518     g_strstrip (value);
519     if (!strlen (value))
520       continue;
521
522     GST_LOG_OBJECT (src, "data %s", data);
523
524     /* Icecast stuff */
525     if (strncmp (data, "icy-metaint:", 12) == 0) {      /* ugh */
526       if (sscanf (data + 12, "%d", &icy_metaint) == 1) {
527         if (icy_metaint > 0) {
528           GstCaps *icy_caps;
529
530           icy_caps = gst_caps_new_simple ("application/x-icy",
531               "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
532           gst_pad_set_caps (GST_BASE_SRC_PAD (src), icy_caps);
533           gst_caps_unref (icy_caps);
534         }
535       }
536       continue;
537     }
538
539     if (!strncmp (data, "icy-", 4))
540       key = data + 4;
541     else
542       continue;
543
544     GST_DEBUG_OBJECT (src, "key: %s", key);
545     if (!strncmp (key, "name", 4)) {
546       g_free (src->iradio_name);
547       src->iradio_name = gst_gnome_vfs_src_unicodify (value);
548       if (src->iradio_name)
549         g_object_notify (G_OBJECT (src), "iradio-name");
550     } else if (!strncmp (key, "genre", 5)) {
551       g_free (src->iradio_genre);
552       src->iradio_genre = gst_gnome_vfs_src_unicodify (value);
553       if (src->iradio_genre)
554         g_object_notify (G_OBJECT (src), "iradio-genre");
555     } else if (!strncmp (key, "url", 3)) {
556       g_free (src->iradio_url);
557       src->iradio_url = gst_gnome_vfs_src_unicodify (value);
558       if (src->iradio_url)
559         g_object_notify (G_OBJECT (src), "iradio-url");
560     }
561   }
562 }
563
564 static void
565 gst_gnome_vfs_src_push_callbacks (GstGnomeVFSSrc * src)
566 {
567   if (src->http_callbacks_pushed)
568     return;
569
570   GST_DEBUG_OBJECT (src, "pushing callbacks");
571   gnome_vfs_module_callback_push
572       (GNOME_VFS_MODULE_CALLBACK_HTTP_SEND_ADDITIONAL_HEADERS,
573       gst_gnome_vfs_src_send_additional_headers_callback, src, NULL);
574   gnome_vfs_module_callback_push
575       (GNOME_VFS_MODULE_CALLBACK_HTTP_RECEIVED_HEADERS,
576       gst_gnome_vfs_src_received_headers_callback, src, NULL);
577
578   src->http_callbacks_pushed = TRUE;
579 }
580
581 static void
582 gst_gnome_vfs_src_pop_callbacks (GstGnomeVFSSrc * src)
583 {
584   if (!src->http_callbacks_pushed)
585     return;
586
587   GST_DEBUG_OBJECT (src, "popping callbacks");
588   gnome_vfs_module_callback_pop
589       (GNOME_VFS_MODULE_CALLBACK_HTTP_SEND_ADDITIONAL_HEADERS);
590   gnome_vfs_module_callback_pop
591       (GNOME_VFS_MODULE_CALLBACK_HTTP_RECEIVED_HEADERS);
592
593   src->http_callbacks_pushed = FALSE;
594 }
595
596 /*
597  * Read a new buffer from src->reqoffset, takes care of events
598  * and seeking and such.
599  */
600 static GstFlowReturn
601 gst_gnome_vfs_src_create (GstBaseSrc * basesrc, guint64 offset, guint size,
602     GstBuffer ** buffer)
603 {
604   GnomeVFSResult res;
605   GstBuffer *buf;
606   GnomeVFSFileSize readbytes;
607   guint8 *data;
608   guint todo;
609   GstGnomeVFSSrc *src;
610
611   src = GST_GNOME_VFS_SRC (basesrc);
612
613   GST_DEBUG ("now at %llu, reading from %lld, size %u", src->curoffset, offset,
614       size);
615
616   /* seek if required */
617   if (G_UNLIKELY (src->curoffset != offset)) {
618     GST_DEBUG ("need to seek");
619     if (src->seekable) {
620       GST_DEBUG ("seeking to %" G_GUINT64_FORMAT, offset);
621       res = gnome_vfs_seek (src->handle, GNOME_VFS_SEEK_START, offset);
622       if (res != GNOME_VFS_OK)
623         goto seek_failed;
624       src->curoffset = offset;
625     } else {
626       goto cannot_seek;
627     }
628   }
629
630   buf = gst_buffer_try_new_and_alloc (size);
631   if (G_UNLIKELY (buf == NULL && size == 0)) {
632     GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size);
633     return GST_FLOW_ERROR;
634   }
635
636   data = GST_BUFFER_DATA (buf);
637
638   todo = size;
639   while (todo > 0) {
640     /* this can return less that we ask for */
641     res = gnome_vfs_read (src->handle, data, todo, &readbytes);
642
643     if (G_UNLIKELY (res == GNOME_VFS_ERROR_EOF || (res == GNOME_VFS_OK
644                 && readbytes == 0)))
645       goto eos;
646
647     if (G_UNLIKELY (res != GNOME_VFS_OK))
648       goto read_failed;
649
650     if (readbytes < todo) {
651       data = &data[readbytes];
652       todo -= readbytes;
653     } else {
654       todo = 0;
655     }
656     GST_LOG ("  got size %" G_GUINT64_FORMAT, readbytes);
657   }
658   GST_BUFFER_OFFSET (buf) = src->curoffset;
659   src->curoffset += size;
660
661   /* we're done, return the buffer */
662   *buffer = buf;
663
664   return GST_FLOW_OK;
665
666 seek_failed:
667   {
668     GST_ELEMENT_ERROR (src, RESOURCE, SEEK, (NULL),
669         ("Failed to seek to requested position %" G_GINT64_FORMAT ": %s",
670             offset, gnome_vfs_result_to_string (res)));
671     return GST_FLOW_ERROR;
672   }
673 cannot_seek:
674   {
675     GST_ELEMENT_ERROR (src, RESOURCE, SEEK, (NULL),
676         ("Requested seek from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT
677             " on non-seekable stream", src->curoffset, offset));
678     return GST_FLOW_ERROR;
679   }
680 read_failed:
681   {
682     gst_buffer_unref (buf);
683     GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL),
684         ("Failed to read data: %s", gnome_vfs_result_to_string (res)));
685     return GST_FLOW_ERROR;
686   }
687 eos:
688   {
689     gst_buffer_unref (buf);
690     GST_DEBUG_OBJECT (src, "Reading data gave EOS");
691     return GST_FLOW_UNEXPECTED;
692   }
693 }
694
695 static gboolean
696 gst_gnome_vfs_src_query (GstBaseSrc * basesrc, GstQuery * query)
697 {
698   gboolean ret = FALSE;
699   GstGnomeVFSSrc *src = GST_GNOME_VFS_SRC (basesrc);
700
701   switch (GST_QUERY_TYPE (query)) {
702     case GST_QUERY_URI:
703       gst_query_set_uri (query, src->uri_name);
704       ret = TRUE;
705       break;
706     default:
707       ret = FALSE;
708       break;
709   }
710
711   if (!ret)
712     ret = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
713
714   return ret;
715 }
716
717 static gboolean
718 gst_gnome_vfs_src_is_seekable (GstBaseSrc * basesrc)
719 {
720   GstGnomeVFSSrc *src;
721
722   src = GST_GNOME_VFS_SRC (basesrc);
723
724   return src->seekable;
725 }
726
727 static gboolean
728 gst_gnome_vfs_src_check_get_range (GstBaseSrc * basesrc)
729 {
730   GstGnomeVFSSrc *src;
731   const gchar *protocol;
732
733   src = GST_GNOME_VFS_SRC (basesrc);
734
735   if (src->uri == NULL) {
736     GST_WARNING_OBJECT (src, "no URI set yet");
737     return FALSE;
738   }
739
740   if (gnome_vfs_uri_is_local (src->uri)) {
741     GST_LOG_OBJECT (src, "local URI (%s), assuming random access is possible",
742         GST_STR_NULL (src->uri_name));
743     return TRUE;
744   }
745
746   /* blacklist certain protocols we know won't work getrange-based */
747   protocol = gnome_vfs_uri_get_scheme (src->uri);
748   if (protocol == NULL)
749     goto undecided;
750
751   if (strcmp (protocol, "http") == 0 || strcmp (protocol, "https") == 0) {
752     GST_LOG_OBJECT (src, "blacklisted protocol '%s', no random access possible"
753         " (URI=%s)", protocol, GST_STR_NULL (src->uri_name));
754     return FALSE;
755   }
756
757   /* fall through to undecided */
758
759 undecided:
760   {
761     /* don't know what to do, let the basesrc class decide for us */
762     GST_LOG_OBJECT (src, "undecided about URI '%s', let base class handle it",
763         GST_STR_NULL (src->uri_name));
764
765     if (GST_BASE_SRC_CLASS (parent_class)->check_get_range)
766       return GST_BASE_SRC_CLASS (parent_class)->check_get_range (basesrc);
767
768     return FALSE;
769   }
770 }
771
772 static gboolean
773 gst_gnome_vfs_src_get_size (GstBaseSrc * basesrc, guint64 * size)
774 {
775   GstGnomeVFSSrc *src;
776   GnomeVFSFileInfo *info;
777   GnomeVFSFileInfoOptions options;
778   GnomeVFSResult res;
779
780   src = GST_GNOME_VFS_SRC (basesrc);
781
782   *size = -1;
783   info = gnome_vfs_file_info_new ();
784   options = GNOME_VFS_FILE_INFO_DEFAULT | GNOME_VFS_FILE_INFO_FOLLOW_LINKS;
785   res = gnome_vfs_get_file_info_from_handle (src->handle, info, options);
786   if (res == GNOME_VFS_OK) {
787     if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE) != 0) {
788       *size = info->size;
789       GST_DEBUG_OBJECT (src, "from handle: %" G_GUINT64_FORMAT " bytes", *size);
790     } else if (src->own_handle && gnome_vfs_uri_is_local (src->uri)) {
791       GST_DEBUG_OBJECT (src,
792           "file size not known, file local, trying fallback");
793       res = gnome_vfs_get_file_info_uri (src->uri, info, options);
794       if (res == GNOME_VFS_OK &&
795           (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE) != 0) {
796         *size = info->size;
797         GST_DEBUG_OBJECT (src, "from uri: %" G_GUINT64_FORMAT " bytes", *size);
798       }
799     }
800   } else {
801     GST_WARNING_OBJECT (src, "getting info failed: %s",
802         gnome_vfs_result_to_string (res));
803   }
804   gnome_vfs_file_info_unref (info);
805
806   if (*size == (GnomeVFSFileSize) - 1)
807     return FALSE;
808
809   GST_DEBUG_OBJECT (src, "return size %" G_GUINT64_FORMAT, *size);
810
811   return TRUE;
812 }
813
814 /* open the file, do stuff necessary to go to PAUSED state */
815 static gboolean
816 gst_gnome_vfs_src_start (GstBaseSrc * basesrc)
817 {
818   GnomeVFSResult res;
819   GstGnomeVFSSrc *src;
820
821   src = GST_GNOME_VFS_SRC (basesrc);
822
823   gst_gnome_vfs_src_push_callbacks (src);
824
825   if (src->uri != NULL) {
826     GnomeVFSOpenMode mode = GNOME_VFS_OPEN_READ;
827
828     /* this can block... */
829     res = gnome_vfs_open_uri (&src->handle, src->uri, mode);
830     if (res != GNOME_VFS_OK)
831       goto open_failed;
832     src->own_handle = TRUE;
833   } else if (!src->handle) {
834     goto no_filename;
835   } else {
836     src->own_handle = FALSE;
837   }
838
839   if (gnome_vfs_seek (src->handle, GNOME_VFS_SEEK_CURRENT, 0) == GNOME_VFS_OK) {
840     src->seekable = TRUE;
841   } else {
842     src->seekable = FALSE;
843   }
844
845   return TRUE;
846
847   /* ERRORS */
848 open_failed:
849   {
850     gchar *filename = gnome_vfs_uri_to_string (src->uri,
851         GNOME_VFS_URI_HIDE_PASSWORD);
852
853     gst_gnome_vfs_src_pop_callbacks (src);
854
855     if (res == GNOME_VFS_ERROR_NOT_FOUND ||
856         res == GNOME_VFS_ERROR_HOST_NOT_FOUND ||
857         res == GNOME_VFS_ERROR_SERVICE_NOT_AVAILABLE) {
858       GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL),
859           ("Could not open vfs file \"%s\" for reading: %s (%d)",
860               filename, gnome_vfs_result_to_string (res), res));
861     } else {
862       GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
863           ("Could not open vfs file \"%s\" for reading: %s (%d)",
864               filename, gnome_vfs_result_to_string (res), res));
865     }
866     g_free (filename);
867     return FALSE;
868   }
869 no_filename:
870   {
871     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("No filename given"));
872     return FALSE;
873   }
874 }
875
876 static gboolean
877 gst_gnome_vfs_src_stop (GstBaseSrc * basesrc)
878 {
879   GstGnomeVFSSrc *src;
880
881   src = GST_GNOME_VFS_SRC (basesrc);
882
883   gst_gnome_vfs_src_pop_callbacks (src);
884
885   if (src->own_handle) {
886     GnomeVFSResult res;
887
888     res = gnome_vfs_close (src->handle);
889     if (res != GNOME_VFS_OK) {
890       GST_ELEMENT_ERROR (src, RESOURCE, CLOSE, (NULL),
891           ("Could not close vfs handle: %s", gnome_vfs_result_to_string (res)));
892     }
893     src->handle = NULL;
894   }
895   src->curoffset = 0;
896
897   return TRUE;
898 }