summaryrefslogtreecommitdiff
path: root/plugins/vfs_curl
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-09-29 22:47:50 +0200
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-09-29 22:47:50 +0200
commitfc2689bbd0af9323a366d6fe6b37f613e28fc53f (patch)
tree32cef377a22338d1bde1e26d32e0313318651550 /plugins/vfs_curl
parent7ad596b7eb25cc2ab18b439d6159b0e12205c2f5 (diff)
vfs_curl WIP
Diffstat (limited to 'plugins/vfs_curl')
-rw-r--r--plugins/vfs_curl/Makefile.am7
-rw-r--r--plugins/vfs_curl/vfs_curl.c246
2 files changed, 253 insertions, 0 deletions
diff --git a/plugins/vfs_curl/Makefile.am b/plugins/vfs_curl/Makefile.am
new file mode 100644
index 00000000..189596a5
--- /dev/null
+++ b/plugins/vfs_curl/Makefile.am
@@ -0,0 +1,7 @@
+vfs_curldir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = vfs_curl.la
+vfs_curl_la_SOURCES = vfs_curl.c
+vfs_curl_la_LDFLAGS = -module
+
+vfs_curl_la_LIBADD = $(LDADD) $(CURL_LIBS)
+AM_CFLAGS = $(CFLAGS) -std=c99
diff --git a/plugins/vfs_curl/vfs_curl.c b/plugins/vfs_curl/vfs_curl.c
new file mode 100644
index 00000000..8ff59d98
--- /dev/null
+++ b/plugins/vfs_curl/vfs_curl.c
@@ -0,0 +1,246 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#include <curl/curl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include "../../deadbeef.h"
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+static DB_functions_t *deadbeef;
+
+#define BUFFER_SIZE (0x10000/2)
+#define BUFFER_MASK 0xffff
+
+#define STATUS_INITIAL 0
+#define STATUS_READING 1
+#define STATUS_FINISHED 2
+#define STATUS_ABORTED 3
+#define STATUS_REWIND 4
+
+typedef struct {
+ DB_vfs_t *vfs;
+ // buffer must be 2x size of BUFFER_SIZE, to be able to seek back a bit
+ uint8_t buffer[BUFFER_SIZE*2];
+ int pos; // position in stream; use "& BUFFER_MASK" to make it index into ringbuffer
+ int remaining; // remaining bytes in buffer read from stream
+ int status;
+ char *url;
+ intptr_t tid; // thread id which does http requests
+ intptr_t mutex;
+} HTTP_FILE;
+
+static DB_vfs_t plugin;
+
+static char http_err[CURL_ERROR_SIZE];
+
+static size_t
+lastfm_curl_write (void *ptr, size_t size, size_t nmemb, void *stream) {
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ int avail = size * nmemb;
+ for (;;) {
+ if (fp->status == STATUS_REWIND) {
+ return 0;
+ }
+ if (fp->status == STATUS_ABORTED) {
+ break;
+ }
+ deadbeef->mutex_lock (fp->mutex);
+ int sz = BUFFER_SIZE - fp->remaining;
+ int cp = min (avail, 10000);
+ if (sz >= cp) {
+ cp = min (avail, sz);
+ int writepos = (fp->pos + fp->remaining) & BUFFER_MASK;
+ // copy data
+ int part1 = BUFFER_SIZE * 2 - writepos;
+ part1 = min (part1, cp);
+ int part2 = sz - part1;
+ memcpy (fp->buffer+writepos, ptr, part1);
+ ptr += part1;
+ avail -= part1;
+ fp->remaining += part1;
+ if (part2 > 0) {
+ memcpy (fp->buffer, ptr, part2);
+ ptr += part2;
+ avail -= part2;
+ fp->remaining += part2;
+ }
+ }
+ deadbeef->mutex_unlock (fp->mutex);
+ if (avail >= size*nmemb) {
+ break;
+ }
+ usleep (3000);
+ }
+
+ return nmemb * size - avail;
+}
+
+static void
+http_thread_func (uintptr_t ctx) {
+ HTTP_FILE *fp = (HTTP_FILE *)ctx;
+ CURL *curl;
+ curl = curl_easy_init ();
+ for (;;) {
+ fp->pos = 0;
+ fp->remaining = 0;
+ curl_easy_setopt (curl, CURLOPT_URL, fp->url);
+ curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, lastfm_curl_write);
+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, ctx);
+ curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, http_err);
+ curl_easy_setopt (curl, CURLOPT_BUFFERSIZE, BUFFER_SIZE - fp->remaining);
+ curl_easy_setopt (curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ fp->status = STATUS_READING;
+ int status = curl_easy_perform (curl);
+ trace ("curl: curl_easy_perform status=%d\n", status);
+ if (fp->status != STATUS_REWIND) {
+ break;
+ }
+ }
+ curl_easy_cleanup (curl);
+ fp->status = STATUS_FINISHED;
+}
+
+static DB_FILE *
+http_open (const char *fname) {
+ HTTP_FILE *fp = malloc (sizeof (HTTP_FILE));
+ memset (fp, 0, sizeof (HTTP_FILE));
+ fp->vfs = &plugin;
+ fp->url = strdup (fname);
+ return (DB_FILE*)fp;
+}
+
+static void
+http_close (DB_FILE *stream) {
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->tid) {
+ fp->status = STATUS_ABORTED;
+ deadbeef->thread_join (fp->tid);
+ deadbeef->mutex_free (fp->mutex);
+ }
+ free (fp->url);
+ free (stream);
+}
+
+static size_t
+http_read (void *ptr, size_t size, size_t nmemb, DB_FILE *stream) {
+ assert (stream);
+ assert (ptr);
+ assert (size * nmemb <= BUFFER_SIZE);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (!fp->tid) {
+ fp->mutex = deadbeef->mutex_create ();
+ fp->tid = deadbeef->thread_start (http_thread_func, (uintptr_t)fp);
+ }
+ // wait until data is available
+ while (fp->remaining < size * nmemb && fp->status != STATUS_FINISHED) {
+ sleep (3000);
+ }
+ deadbeef->mutex_lock (fp->mutex);
+ int sz = size * nmemb;
+ int readpos = fp->pos & BUFFER_MASK;
+ int part1 = BUFFER_SIZE*2-(fp->pos&BUFFER_MASK);
+ part1 = min (part1, sz);
+ part1 = min (part1, fp->remaining);
+ memcpy (ptr, fp->buffer+readpos, part1);
+ fp->remaining -= part1;
+ fp->pos += part1;
+ sz -= part1;
+ if (fp->remaining > 0) {
+ int part2 = min (fp->remaining, sz);
+ memcpy (((char *)ptr) + part1, fp->buffer, part2);
+ fp->remaining -= part2;
+ fp->pos += part2;
+ sz -= part2;
+ }
+ deadbeef->mutex_unlock (fp->mutex);
+ return size * nmemb - sz;
+}
+
+static int
+http_seek (DB_FILE *stream, long offset, int whence) {
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (!fp->tid) {
+ if (offset == 0 && (whence == SEEK_SET || whence == SEEK_CUR)) {
+ return 0;
+ }
+ else {
+ trace ("vfs_curl: cannot do seek(%d,%d)\n", offset, whence);
+ return -1;
+ }
+ }
+ // FIXME: can do some limited seeking
+ trace ("vfs_curl: seeking not implemented\n");
+ return -1;
+}
+
+static long
+http_tell (DB_FILE *stream) {
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ return fp->pos;
+}
+
+static void
+http_rewind (DB_FILE *stream) {
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->tid) {
+ deadbeef->mutex_lock (fp->mutex);
+ fp->status = STATUS_REWIND;
+ deadbeef->mutex_unlock (fp->mutex);
+ }
+}
+
+static const char *scheme_names[] = { "http://", NULL };
+
+// standard stdio vfs
+static DB_vfs_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_VFS,
+ .plugin.name = "CURL VFS (streaming over http)",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .open = http_open,
+ .close = http_close,
+ .read = http_read,
+ .seek = http_seek,
+ .tell = http_tell,
+ .rewind = http_rewind,
+ .scheme_names = scheme_names,
+ .streaming = 1
+};
+
+DB_plugin_t *
+vfs_curl_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}