diff options
Diffstat (limited to '')
44 files changed, 3405 insertions, 4227 deletions
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx new file mode 100644 index 000000000..0d856527f --- /dev/null +++ b/src/input/ArchiveInputPlugin.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" +#include "ArchiveInputPlugin.hxx" +#include "ArchiveLookup.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "InputPlugin.hxx" + +#include <glib.h> + +/** + * select correct archive plugin to handle the input stream + * may allow stacking of archive plugins. for example for handling + * tar.gz a gzip handler opens file (through inputfile stream) + * then it opens a tar handler and sets gzip inputstream as + * parent_stream so tar plugin fetches file data from gzip + * plugin and gzip fetches file from disk + */ +static struct input_stream * +input_archive_open(const char *pathname, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + const struct archive_plugin *arplug; + char *archive, *filename, *suffix, *pname; + struct input_stream *is; + + if (!g_path_is_absolute(pathname)) + return NULL; + + pname = g_strdup(pathname); + // archive_lookup will modify pname when true is returned + if (!archive_lookup(pname, &archive, &filename, &suffix)) { + g_debug("not an archive, lookup %s failed\n", pname); + g_free(pname); + return NULL; + } + + //check which archive plugin to use (by ext) + arplug = archive_plugin_from_suffix(suffix); + if (!arplug) { + g_warning("can't handle archive %s\n",archive); + g_free(pname); + return NULL; + } + + auto file = archive_file_open(arplug, archive, error_r); + if (file == NULL) { + g_free(pname); + return NULL; + } + + //setup fileops + is = file->OpenStream(filename, mutex, cond, error_r); + g_free(pname); + file->Close(); + + return is; +} + +const struct input_plugin input_plugin_archive = { + "archive", + nullptr, + nullptr, + input_archive_open, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/ArchiveInputPlugin.hxx new file mode 100644 index 000000000..96fcd0dd1 --- /dev/null +++ b/src/input/ArchiveInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef MPD_INPUT_ARCHIVE_HXX +#define MPD_INPUT_ARCHIVE_HXX + +extern const struct input_plugin input_plugin_archive; + +#endif diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx new file mode 100644 index 000000000..f0fa835b3 --- /dev/null +++ b/src/input/CdioParanoiaInputPlugin.cxx @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +/** + * CD-Audio handling (requires libcdio_paranoia) + */ + +#include "config.h" +#include "CdioParanoiaInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#include <cdio/paranoia.h> +#include <cdio/cd_types.h> + +struct CdioParanoiaInputStream { + struct input_stream base; + + cdrom_drive_t *drv; + CdIo_t *cdio; + cdrom_paranoia_t *para; + + lsn_t lsn_from, lsn_to; + int lsn_relofs; + + int trackno; + + char buffer[CDIO_CD_FRAMESIZE_RAW]; + int buffer_lsn; + + CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond, + int _trackno) + :base(input_plugin_cdio_paranoia, uri, mutex, cond), + drv(nullptr), cdio(nullptr), para(nullptr), + trackno(_trackno) + { + } + + ~CdioParanoiaInputStream() { + if (para != nullptr) + cdio_paranoia_free(para); + if (drv != nullptr) + cdio_cddap_close_no_free_cdio(drv); + if (cdio != nullptr) + cdio_destroy(cdio); + } +}; + +static inline GQuark +cdio_quark(void) +{ + return g_quark_from_static_string("cdio"); +} + +static void +input_cdio_close(struct input_stream *is) +{ + CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is; + + delete i; +} + +struct cdio_uri { + char device[64]; + int track; +}; + +static bool +parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) +{ + if (!g_str_has_prefix(src, "cdda://")) + return false; + + src += 7; + + if (*src == 0) { + /* play the whole CD in the default drive */ + dest->device[0] = 0; + dest->track = -1; + return true; + } + + const char *slash = strrchr(src, '/'); + if (slash == nullptr) { + /* play the whole CD in the specified drive */ + g_strlcpy(dest->device, src, sizeof(dest->device)); + dest->track = -1; + return true; + } + + size_t device_length = slash - src; + if (device_length >= sizeof(dest->device)) + device_length = sizeof(dest->device) - 1; + + memcpy(dest->device, src, device_length); + dest->device[device_length] = 0; + + const char *track = slash + 1; + + char *endptr; + dest->track = strtoul(track, &endptr, 10); + if (*endptr != 0) { + g_set_error(error_r, cdio_quark(), 0, + "Malformed track number"); + return false; + } + + if (endptr == track) + /* play the whole CD */ + dest->track = -1; + + return true; +} + +static char * +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, + false); + if (devices == nullptr) + return nullptr; + + char *device = g_strdup(devices[0]); + cdio_free_device_list(devices); + + return device; +} + +static struct input_stream * +input_cdio_open(const char *uri, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + struct cdio_uri parsed_uri; + if (!parse_cdio_uri(&parsed_uri, uri, error_r)) + return nullptr; + + CdioParanoiaInputStream *i = + new CdioParanoiaInputStream(uri, mutex, cond, + parsed_uri.track); + + /* get list of CD's supporting CD-DA */ + char *device = parsed_uri.device[0] != 0 + ? g_strdup(parsed_uri.device) + : cdio_detect_device(); + if (device == nullptr) { + g_set_error(error_r, cdio_quark(), 0, + "Unable find or access a CD-ROM drive with an audio CD in it."); + delete i; + return nullptr; + } + + /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ + i->cdio = cdio_open(device, DRIVER_UNKNOWN); + g_free(device); + + i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr); + + if ( !i->drv ) { + g_set_error(error_r, cdio_quark(), 0, + "Unable to identify audio CD disc."); + delete i; + return nullptr; + } + + cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + if ( 0 != cdio_cddap_open(i->drv) ) { + g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); + delete i; + return nullptr; + } + + bool reverse_endian; + switch (data_bigendianp(i->drv)) { + case -1: + g_debug("cdda: drive returns unknown audio data"); + reverse_endian = false; + break; + case 0: + g_debug("cdda: drive returns audio data Little Endian."); + reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; + break; + case 1: + g_debug("cdda: drive returns audio data Big Endian."); + reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; + break; + default: + g_set_error(error_r, cdio_quark(), 0, + "Drive returns unknown data type %d", + data_bigendianp(i->drv)); + delete i; + return nullptr; + } + + i->lsn_relofs = 0; + + if (i->trackno >= 0) { + i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); + i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); + } else { + i->lsn_from = 0; + i->lsn_to = cdio_get_disc_last_lsn(i->cdio); + } + + i->para = cdio_paranoia_init(i->drv); + + /* Set reading mode for full paranoia, but allow skipping sectors. */ + paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + + /* seek to beginning of the track */ + cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); + + i->base.ready = true; + i->base.seekable = true; + i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; + + /* hack to make MPD select the "pcm" decoder plugin */ + i->base.mime = g_strdup(reverse_endian + ? "audio/x-mpd-cdda-pcm-reverse" + : "audio/x-mpd-cdda-pcm"); + + return &i->base; +} + +static bool +input_cdio_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + /* calculate absolute offset */ + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += cis->base.offset; + break; + case SEEK_END: + offset += cis->base.size; + break; + } + + if (offset < 0 || offset > cis->base.size) { + g_set_error(error_r, cdio_quark(), 0, + "Invalid offset to seek %ld (%ld)", + (long int)offset, (long int)cis->base.size); + return false; + } + + /* simple case */ + if (offset == cis->base.offset) + return true; + + /* calculate current LSN */ + cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + cis->base.offset = offset; + + cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); + + return true; +} + +static size_t +input_cdio_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + size_t nbytes = 0; + int diff; + size_t len, maxwrite; + int16_t *rbuf; + char *s_err, *s_mess; + char *wptr = (char *) ptr; + + while (length > 0) { + + + /* end of track ? */ + if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) + break; + + //current sector was changed ? + if (cis->lsn_relofs != cis->buffer_lsn) { + rbuf = cdio_paranoia_read(cis->para, nullptr); + + s_err = cdda_errors(cis->drv); + if (s_err) { + g_warning("paranoia_read: %s", s_err ); + free(s_err); + } + s_mess = cdda_messages(cis->drv); + if (s_mess) { + free(s_mess); + } + if (!rbuf) { + g_set_error(error_r, cdio_quark(), 0, + "paranoia read error. Stopping."); + return 0; + } + //store current buffer + memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + cis->buffer_lsn = cis->lsn_relofs; + } else { + //use cached sector + rbuf = (int16_t*) cis->buffer; + } + + //correct offset + diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + + assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + + maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + len = (length < maxwrite? length : maxwrite); + + //skip diff bytes from this lsn + memcpy(wptr, ((char*)rbuf) + diff, len); + //update pointer + wptr += len; + nbytes += len; + + //update offset + cis->base.offset += len; + cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; + //update length + length -= len; + } + + return nbytes; +} + +static bool +input_cdio_eof(struct input_stream *is) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); +} + +const struct input_plugin input_plugin_cdio_paranoia = { + "cdio_paranoia", + nullptr, + nullptr, + input_cdio_open, + input_cdio_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_cdio_read, + input_cdio_eof, + input_cdio_seek, +}; diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/CdioParanoiaInputPlugin.hxx new file mode 100644 index 000000000..80d98b4bf --- /dev/null +++ b/src/input/CdioParanoiaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libcdio_paranoia library. + */ +extern const struct input_plugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx new file mode 100644 index 000000000..fe944b752 --- /dev/null +++ b/src/input/CurlInputPlugin.cxx @@ -0,0 +1,1187 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" +#include "CurlInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "conf.h" +#include "tag.h" +#include "IcyMetaDataParser.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/Loop.hxx" +#include "IOThread.hxx" + +#include <assert.h> + +#if defined(WIN32) + #include <winsock2.h> +#else + #include <sys/select.h> +#endif + +#include <string.h> +#include <errno.h> + +#include <list> +#include <forward_list> + +#include <curl/curl.h> +#include <glib.h> + +#if LIBCURL_VERSION_NUM < 0x071200 +#error libcurl is too old +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_curl" + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t CURL_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t CURL_RESUME_AT = 384 * 1024; + +/** + * Buffers created by input_curl_writefunction(). + */ +class CurlInputBuffer { + /** size of the payload */ + size_t size; + + /** how much has been consumed yet? */ + size_t consumed; + + /** the payload */ + uint8_t *data; + +public: + CurlInputBuffer(const void *_data, size_t _size) + :size(_size), consumed(0), data(new uint8_t[size]) { + memcpy(data, _data, size); + } + + ~CurlInputBuffer() { + delete[] data; + } + + CurlInputBuffer(const CurlInputBuffer &) = delete; + CurlInputBuffer &operator=(const CurlInputBuffer &) = delete; + + const void *Begin() const { + return data + consumed; + } + + size_t TotalSize() const { + return size; + } + + size_t Available() const { + return size - consumed; + } + + /** + * Mark a part of the buffer as consumed. + * + * @return false if the buffer is now empty + */ + bool Consume(size_t length) { + assert(consumed < size); + + consumed += length; + if (consumed < size) + return true; + + assert(consumed == size); + return false; + } + + bool Read(void *dest, size_t length) { + assert(consumed + length <= size); + + memcpy(dest, data + consumed, length); + return Consume(length); + } +}; + +struct input_curl { + struct input_stream base; + + /* some buffers which were passed to libcurl, which we have + too free */ + char *range; + struct curl_slist *request_headers; + + /** the curl handles */ + CURL *easy; + + /** list of buffers, where input_curl_writefunction() appends + to, and input_curl_read() reads from them */ + std::list<CurlInputBuffer> buffers; + + /** + * Is the connection currently paused? That happens when the + * buffer was getting too large. It will be unpaused when the + * buffer is below the threshold again. + */ + bool paused; + + /** error message provided by libcurl */ + char error[CURL_ERROR_SIZE]; + + /** parser for icy-metadata */ + IcyMetaDataParser icy; + + /** the stream name from the icy-name response header */ + char *meta_name; + + /** the tag object ready to be requested via + input_stream_tag() */ + struct tag *tag; + + GError *postponed_error; + + input_curl(const char *url, Mutex &mutex, Cond &cond) + :base(input_plugin_curl, url, mutex, cond), + range(nullptr), request_headers(nullptr), + paused(false), + meta_name(nullptr), + tag(nullptr), + postponed_error(nullptr) { + } + + ~input_curl(); + + input_curl(const input_curl &) = delete; + input_curl &operator=(const input_curl &) = delete; +}; + +/** + * This class monitors all CURL file descriptors. + */ +class CurlSockets final : private MultiSocketMonitor { + /** + * Did CURL give us a timeout? If yes, then we need to call + * curl_multi_perform(), even if there was no event on any + * file descriptor. + */ + bool have_timeout; + + /** + * The absolute time stamp when the timeout expires. + */ + gint64 absolute_timeout; + +public: + CurlSockets(EventLoop &_loop) + :MultiSocketMonitor(_loop) {} + + using MultiSocketMonitor::InvalidateSockets; + +private: + void UpdateSockets(); + + virtual void PrepareSockets(gcc_unused gint *timeout_r) override; + virtual bool CheckSockets() const override; + virtual void DispatchSockets() override; +}; + +/** libcurl should accept "ICY 200 OK" */ +static struct curl_slist *http_200_aliases; + +/** HTTP proxy settings */ +static const char *proxy, *proxy_user, *proxy_password; +static unsigned proxy_port; + +static struct { + CURLM *multi; + + /** + * A linked list of all active HTTP requests. An active + * request is one that doesn't have the "eof" flag set. + */ + std::forward_list<input_curl *> requests; + + CurlSockets *sockets; +} curl; + +static inline GQuark +curl_quark(void) +{ + return g_quark_from_static_string("curl"); +} + +/** + * Find a request by its CURL "easy" handle. + * + * Runs in the I/O thread. No lock needed. + */ +static struct input_curl * +input_curl_find_request(CURL *easy) +{ + assert(io_thread_inside()); + + for (auto c : curl.requests) + if (c->easy == easy) + return c; + + return NULL; +} + +static gpointer +input_curl_resume(gpointer data) +{ + assert(io_thread_inside()); + + struct input_curl *c = (struct input_curl *)data; + + if (c->paused) { + c->paused = false; + curl_easy_pause(c->easy, CURLPAUSE_CONT); + } + + return NULL; +} + +/** + * Calculates the GLib event bit mask for one file descriptor, + * obtained from three #fd_set objects filled by curl_multi_fdset(). + */ +static unsigned +input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + gushort events = 0; + + if (FD_ISSET(fd, rfds)) { + events |= G_IO_IN | G_IO_HUP | G_IO_ERR; + FD_CLR(fd, rfds); + } + + if (FD_ISSET(fd, wfds)) { + events |= G_IO_OUT | G_IO_ERR; + FD_CLR(fd, wfds); + } + + if (FD_ISSET(fd, efds)) { + events |= G_IO_HUP | G_IO_ERR; + FD_CLR(fd, efds); + } + + return events; +} + +/** + * Updates all registered GPollFD objects, unregisters old ones, + * registers new ones. + * + * Runs in the I/O thread. No lock needed. + */ +void +CurlSockets::UpdateSockets() +{ + assert(io_thread_inside()); + + fd_set rfds, wfds, efds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + int max_fd; + CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, + &efds, &max_fd); + if (mcode != CURLM_OK) { + g_warning("curl_multi_fdset() failed: %s\n", + curl_multi_strerror(mcode)); + return; + } + + UpdateSocketList([&rfds, &wfds, &efds](int fd){ + return input_curl_fd_events(fd, &rfds, + &wfds, &efds); + }); + + for (int fd = 0; fd <= max_fd; ++fd) { + unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) + AddSocket(fd, events); + } +} + +/** + * Runs in the I/O thread. No lock needed. + */ +static bool +input_curl_easy_add(struct input_curl *c, GError **error_r) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy != NULL); + assert(input_curl_find_request(c->easy) == NULL); + + curl.requests.push_front(c); + + CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); + if (mcode != CURLM_OK) { + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + return false; + } + + curl.sockets->InvalidateSockets(); + + return true; +} + +struct easy_add_params { + struct input_curl *c; + GError **error_r; +}; + +static gpointer +input_curl_easy_add_callback(gpointer data) +{ + const struct easy_add_params *params = + (const struct easy_add_params *)data; + + bool success = input_curl_easy_add(params->c, params->error_r); + return GUINT_TO_POINTER(success); +} + +/** + * Call input_curl_easy_add() in the I/O thread. May be called from + * any thread. Caller must not hold a mutex. + */ +static bool +input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) +{ + assert(c != NULL); + assert(c->easy != NULL); + + struct easy_add_params params = { + c, + error_r, + }; + + gpointer result = + io_thread_call(input_curl_easy_add_callback, ¶ms); + return GPOINTER_TO_UINT(result); +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * Runs in the I/O thread. + */ +static void +input_curl_easy_free(struct input_curl *c) +{ + assert(io_thread_inside()); + assert(c != NULL); + + if (c->easy == NULL) + return; + + curl.requests.remove(c); + + curl_multi_remove_handle(curl.multi, c->easy); + curl_easy_cleanup(c->easy); + c->easy = NULL; + + curl_slist_free_all(c->request_headers); + c->request_headers = NULL; + + g_free(c->range); + c->range = NULL; +} + +static gpointer +input_curl_easy_free_callback(gpointer data) +{ + struct input_curl *c = (struct input_curl *)data; + + input_curl_easy_free(c); + curl.sockets->InvalidateSockets(); + + return NULL; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The mutex must not be locked. + */ +static void +input_curl_easy_free_indirect(struct input_curl *c) +{ + io_thread_call(input_curl_easy_free_callback, c); + assert(c->easy == NULL); +} + +/** + * Abort and free all HTTP requests. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_abort_all_requests(GError *error) +{ + assert(io_thread_inside()); + assert(error != NULL); + + while (!curl.requests.empty()) { + struct input_curl *c = curl.requests.front(); + assert(c->postponed_error == NULL); + + input_curl_easy_free(c); + + const ScopeLock protect(c->base.mutex); + + c->postponed_error = g_error_copy(error); + c->base.ready = true; + + c->base.cond.broadcast(); + } + + g_error_free(error); + +} + +/** + * A HTTP request is finished. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_request_done(struct input_curl *c, CURLcode result, long status) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy == NULL); + assert(c->postponed_error == NULL); + + const ScopeLock protect(c->base.mutex); + + if (result != CURLE_OK) { + c->postponed_error = g_error_new(curl_quark(), result, + "curl failed: %s", + c->error); + } else if (status < 200 || status >= 300) { + c->postponed_error = g_error_new(curl_quark(), 0, + "got HTTP status %ld", + status); + } + + c->base.ready = true; + + c->base.cond.broadcast(); +} + +static void +input_curl_handle_done(CURL *easy_handle, CURLcode result) +{ + struct input_curl *c = input_curl_find_request(easy_handle); + assert(c != NULL); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + input_curl_easy_free(c); + input_curl_request_done(c, result, status); +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_info_read(void) +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(curl.multi, + &msgs_in_queue)) != NULL) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +/** + * Give control to CURL. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static bool +input_curl_perform(void) +{ + assert(io_thread_inside()); + + CURLMcode mcode; + + do { + int running_handles; + mcode = curl_multi_perform(curl.multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + GError *error = g_error_new(curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); + input_curl_abort_all_requests(error); + return false; + } + + return true; +} + +void +CurlSockets::PrepareSockets(gint *timeout_r) +{ + UpdateSockets(); + + have_timeout = false; + + long timeout2; + CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); + if (mcode == CURLM_OK) { + if (timeout2 >= 0) + absolute_timeout = GetTime() + timeout2 * 1000; + + if (timeout2 >= 0 && timeout2 < 10) + /* CURL 7.21.1 likes to report "timeout=0", + which means we're running in a busy loop. + Quite a bad idea to waste so much CPU. + Let's use a lower limit of 10ms. */ + timeout2 = 10; + + *timeout_r = timeout2; + + have_timeout = timeout2 >= 0; + } else + g_warning("curl_multi_timeout() failed: %s\n", + curl_multi_strerror(mcode)); +} + +bool +CurlSockets::CheckSockets() const +{ + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file descriptor + event */ + return have_timeout && GetTime() >= absolute_timeout; +} + +void +CurlSockets::DispatchSockets() +{ + if (input_curl_perform()) + input_curl_info_read(); +} + +/* + * input_plugin methods + * + */ + +static bool +input_curl_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code != CURLE_OK) { + g_set_error(error_r, curl_quark(), code, + "curl_global_init() failed: %s\n", + curl_easy_strerror(code)); + return false; + } + + http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); + + proxy = config_get_block_string(param, "proxy", NULL); + proxy_port = config_get_block_unsigned(param, "proxy_port", 0); + proxy_user = config_get_block_string(param, "proxy_user", NULL); + proxy_password = config_get_block_string(param, "proxy_password", + NULL); + + if (proxy == NULL) { + /* deprecated proxy configuration */ + proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL); + proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); + proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL); + proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, + ""); + } + + curl.multi = curl_multi_init(); + if (curl.multi == NULL) { + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); + return false; + } + + curl.sockets = new CurlSockets(io_thread_get()); + + return true; +} + +static gpointer +curl_destroy_sources(G_GNUC_UNUSED gpointer data) +{ + delete curl.sockets; + + return NULL; +} + +static void +input_curl_finish(void) +{ + assert(curl.requests.empty()); + + io_thread_call(curl_destroy_sources, NULL); + + curl_multi_cleanup(curl.multi); + + curl_slist_free_all(http_200_aliases); + + curl_global_cleanup(); +} + +/** + * Determine the total sizes of all buffers, including portions that + * have already been consumed. + * + * The caller must lock the mutex. + */ +G_GNUC_PURE +static size_t +curl_total_buffer_size(const struct input_curl *c) +{ + size_t total = 0; + + for (const auto &i : c->buffers) + total += i.TotalSize(); + + return total; +} + +input_curl::~input_curl() +{ + if (tag != NULL) + tag_free(tag); + g_free(meta_name); + + input_curl_easy_free_indirect(this); + + if (postponed_error != NULL) + g_error_free(postponed_error); +} + +static bool +input_curl_check(struct input_stream *is, GError **error_r) +{ + struct input_curl *c = (struct input_curl *)is; + + bool success = c->postponed_error == NULL; + if (!success) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + } + + return success; +} + +static struct tag * +input_curl_tag(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + struct tag *tag = c->tag; + + c->tag = NULL; + return tag; +} + +static bool +fill_buffer(struct input_curl *c, GError **error_r) +{ + while (c->easy != NULL && c->buffers.empty()) + c->base.cond.wait(c->base.mutex); + + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; + } + + return !c->buffers.empty(); +} + +static size_t +read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers, + void *dest0, size_t length) +{ + auto &buffer = buffers.front(); + uint8_t *dest = (uint8_t *)dest0; + size_t nbytes = 0; + + if (length > buffer.Available()) + length = buffer.Available(); + + while (true) { + size_t chunk; + + chunk = icy.Data(length); + if (chunk > 0) { + const bool empty = !buffer.Read(dest, chunk); + + nbytes += chunk; + dest += chunk; + length -= chunk; + + if (empty) { + buffers.pop_front(); + break; + } + + if (length == 0) + break; + } + + chunk = icy.Meta(buffer.Begin(), length); + if (chunk > 0) { + const bool empty = !buffer.Consume(chunk); + + length -= chunk; + + if (empty) { + buffers.pop_front(); + break; + } + + if (length == 0) + break; + } + } + + return nbytes; +} + +static void +copy_icy_tag(struct input_curl *c) +{ + struct tag *tag = c->icy.ReadTag(); + + if (tag == NULL) + return; + + if (c->tag != NULL) + tag_free(c->tag); + + if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) + tag_add_item(tag, TAG_NAME, c->meta_name); + + c->tag = tag; +} + +static bool +input_curl_available(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->postponed_error != NULL || c->easy == NULL || + !c->buffers.empty(); +} + +static size_t +input_curl_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + struct input_curl *c = (struct input_curl *)is; + bool success; + size_t nbytes = 0; + char *dest = (char *)ptr; + + do { + /* fill the buffer */ + + success = fill_buffer(c, error_r); + if (!success) + return 0; + + /* send buffer contents */ + + while (size > 0 && !c->buffers.empty()) { + size_t copy = read_from_buffer(c->icy, c->buffers, + dest + nbytes, size); + + nbytes += copy; + size -= copy; + } + } while (nbytes == 0); + + if (c->icy.IsDefined()) + copy_icy_tag(c); + + is->offset += (goffset)nbytes; + + if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { + c->base.mutex.unlock(); + io_thread_call(input_curl_resume, c); + c->base.mutex.lock(); + } + + return nbytes; +} + +static void +input_curl_close(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + delete c; +} + +static bool +input_curl_eof(G_GNUC_UNUSED struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->easy == NULL && c->buffers.empty(); +} + +/** called by curl when new data is available */ +static size_t +input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_curl *c = (struct input_curl *)stream; + char name[64]; + + size *= nmemb; + + const char *header = (const char *)ptr; + const char *end = header + size; + + const char *value = (const char *)memchr(header, ':', size); + if (value == NULL || (size_t)(value - header) >= sizeof(name)) + return size; + + memcpy(name, header, value - header); + name[value - header] = 0; + + /* skip the colon */ + + ++value; + + /* strip the value */ + + while (value < end && g_ascii_isspace(*value)) + ++value; + + while (end > value && g_ascii_isspace(end[-1])) + --end; + + if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { + /* a stream with icy-metadata is not seekable */ + if (!c->icy.IsDefined()) + c->base.seekable = true; + } else if (g_ascii_strcasecmp(name, "content-length") == 0) { + char buffer[64]; + + if ((size_t)(end - header) >= sizeof(buffer)) + return size; + + memcpy(buffer, value, end - value); + buffer[end - value] = 0; + + c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); + } else if (g_ascii_strcasecmp(name, "content-type") == 0) { + c->base.mime.assign(value, end); + } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || + g_ascii_strcasecmp(name, "ice-name") == 0 || + g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { + g_free(c->meta_name); + c->meta_name = g_strndup(value, end - value); + + if (c->tag != NULL) + tag_free(c->tag); + + c->tag = tag_new(); + tag_add_item(c->tag, TAG_NAME, c->meta_name); + } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { + char buffer[64]; + size_t icy_metaint; + + if ((size_t)(end - header) >= sizeof(buffer) || + c->icy.IsDefined()) + return size; + + memcpy(buffer, value, end - value); + buffer[end - value] = 0; + + icy_metaint = g_ascii_strtoull(buffer, NULL, 10); + g_debug("icy-metaint=%zu", icy_metaint); + + if (icy_metaint > 0) { + c->icy.Start(icy_metaint); + + /* a stream with icy-metadata is not + seekable */ + c->base.seekable = false; + } + } + + return size; +} + +/** called by curl when new data is available */ +static size_t +input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_curl *c = (struct input_curl *)stream; + + size *= nmemb; + if (size == 0) + return 0; + + const ScopeLock protect(c->base.mutex); + + if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { + c->paused = true; + return CURL_WRITEFUNC_PAUSE; + } + + c->buffers.emplace_back(ptr, size); + c->base.ready = true; + + c->base.cond.broadcast(); + return size; +} + +static bool +input_curl_easy_init(struct input_curl *c, GError **error_r) +{ + CURLcode code; + + c->easy = curl_easy_init(); + if (c->easy == NULL) { + g_set_error(error_r, curl_quark(), 0, + "curl_easy_init() failed"); + return false; + } + + curl_easy_setopt(c->easy, CURLOPT_USERAGENT, + "Music Player Daemon " VERSION); + curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, + input_curl_headerfunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); + curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, + input_curl_writefunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); + curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); + curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); + curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); + curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); + curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); + curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); + curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); + curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); + + if (proxy != NULL) + curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); + + if (proxy_port > 0) + curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); + + if (proxy_user != NULL && proxy_password != NULL) { + char *proxy_auth_str = + g_strconcat(proxy_user, ":", proxy_password, NULL); + curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); + g_free(proxy_auth_str); + } + + code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str()); + if (code != CURLE_OK) { + g_set_error(error_r, curl_quark(), code, + "curl_easy_setopt() failed: %s", + curl_easy_strerror(code)); + return false; + } + + c->request_headers = NULL; + c->request_headers = curl_slist_append(c->request_headers, + "Icy-Metadata: 1"); + curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); + + return true; +} + +static bool +input_curl_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + struct input_curl *c = (struct input_curl *)is; + bool ret; + + assert(is->ready); + + if (whence == SEEK_SET && offset == is->offset) + /* no-op */ + return true; + + if (!is->seekable) + return false; + + /* calculate the absolute offset */ + + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is->offset; + break; + + case SEEK_END: + if (is->size < 0) + /* stream size is not known */ + return false; + + offset += is->size; + break; + + default: + return false; + } + + if (offset < 0) + return false; + + /* check if we can fast-forward the buffer */ + + while (offset > is->offset && !c->buffers.empty()) { + auto &buffer = c->buffers.front(); + size_t length = buffer.Available(); + if (offset - is->offset < (goffset)length) + length = offset - is->offset; + + const bool empty = !buffer.Consume(length); + if (empty) + c->buffers.pop_front(); + + is->offset += length; + } + + if (offset == is->offset) + return true; + + /* close the old connection and open a new one */ + + c->base.mutex.unlock(); + + input_curl_easy_free_indirect(c); + c->buffers.clear(); + + is->offset = offset; + if (is->offset == is->size) { + /* seek to EOF: simulate empty result; avoid + triggering a "416 Requested Range Not Satisfiable" + response */ + return true; + } + + ret = input_curl_easy_init(c, error_r); + if (!ret) + return false; + + /* send the "Range" header */ + + if (is->offset > 0) { + c->range = g_strdup_printf("%lld-", (long long)is->offset); + curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); + } + + c->base.ready = false; + + if (!input_curl_easy_add_indirect(c, error_r)) + return false; + + c->base.mutex.lock(); + + while (!c->base.ready) + c->base.cond.wait(c->base.mutex); + + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; + } + + return true; +} + +static struct input_stream * +input_curl_open(const char *url, Mutex &mutex, Cond &cond, + GError **error_r) +{ + if (strncmp(url, "http://", 7) != 0) + return NULL; + + struct input_curl *c = new input_curl(url, mutex, cond); + + if (!input_curl_easy_init(c, error_r)) { + delete c; + return NULL; + } + + if (!input_curl_easy_add_indirect(c, error_r)) { + delete c; + return NULL; + } + + return &c->base; +} + +const struct input_plugin input_plugin_curl = { + "curl", + input_curl_init, + input_curl_finish, + input_curl_open, + input_curl_close, + input_curl_check, + nullptr, + input_curl_tag, + input_curl_available, + input_curl_read, + input_curl_eof, + input_curl_seek, +}; diff --git a/src/input/CurlInputPlugin.hxx b/src/input/CurlInputPlugin.hxx new file mode 100644 index 000000000..20d1309d8 --- /dev/null +++ b/src/input/CurlInputPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef MPD_INPUT_CURL_HXX +#define MPD_INPUT_CURL_HXX + +struct input_stream; + +extern const struct input_plugin input_plugin_curl; + +#endif diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx new file mode 100644 index 000000000..1e5a8c606 --- /dev/null +++ b/src/input/DespotifyInputPlugin.cxx @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" +#include "DespotifyInputPlugin.hxx" +#include "DespotifyUtils.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "tag.h" + +extern "C" { +#include <despotify.h> +} + +#include <glib.h> + +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <stdio.h> + +struct DespotifyInputStream { + struct input_stream base; + + struct despotify_session *session; + struct ds_track *track; + struct tag *tag; + struct ds_pcm_data pcm; + size_t len_available; + bool eof; + + DespotifyInputStream(const char *uri, + Mutex &mutex, Cond &cond, + despotify_session *_session, + ds_track *_track) + :base(input_plugin_despotify, uri, mutex, cond), + session(_session), track(_track), + tag(mpd_despotify_tag_from_track(track)), + len_available(0), eof(false) { + + memset(&pcm, 0, sizeof(pcm)); + + /* Despotify outputs pcm data */ + base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + base.ready = true; + } + + ~DespotifyInputStream() { + if (tag != NULL) + tag_free(tag); + + despotify_free_track(track); + } +}; + +static void +refill_buffer(DespotifyInputStream *ctx) +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(ctx->session, &ctx->pcm); + + if (rc == 0 && ctx->pcm.len) { + ctx->len_available = ctx->pcm.len; + break; + } + if (ctx->eof == true) + break; + + if (rc < 0) { + g_debug("despotify_get_pcm error\n"); + ctx->eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +static void callback(G_GNUC_UNUSED struct despotify_session* ds, + int sig, G_GNUC_UNUSED void* data, void* callback_data) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; + + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + g_debug("Track play error\n"); + ctx->eof = true; + ctx->len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + ctx->eof = true; + g_debug("End of playlist: %d\n", ctx->eof); + break; + } +} + + +static struct input_stream * +input_despotify_open(const char *url, + Mutex &mutex, Cond &cond, + G_GNUC_UNUSED GError **error_r) +{ + struct despotify_session *session; + struct ds_link *ds_link; + struct ds_track *track; + + if (!g_str_has_prefix(url, "spt://")) + return NULL; + + session = mpd_despotify_get_session(); + if (!session) + return NULL; + + ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + g_debug("Can't find %s\n", url); + return NULL; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return NULL; + } + + track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) + return NULL; + + DespotifyInputStream *ctx = + new DespotifyInputStream(url, mutex, cond, + session, track); + + if (!mpd_despotify_register_callback(callback, ctx)) { + delete ctx; + return NULL; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + mpd_despotify_unregister_callback(callback); + delete ctx; + return NULL; + } + + return &ctx->base; +} + +static size_t +input_despotify_read(struct input_stream *is, void *ptr, size_t size, + G_GNUC_UNUSED GError **error_r) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + size_t to_cpy = size; + + if (ctx->len_available == 0) + refill_buffer(ctx); + + if (ctx->len_available < size) + to_cpy = ctx->len_available; + memcpy(ptr, ctx->pcm.buf, to_cpy); + ctx->len_available -= to_cpy; + + is->offset += to_cpy; + + return to_cpy; +} + +static void +input_despotify_close(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + mpd_despotify_unregister_callback(callback); + delete ctx; +} + +static bool +input_despotify_eof(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + return ctx->eof; +} + +static bool +input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +static struct tag * +input_despotify_tag(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + struct tag *tag = ctx->tag; + + ctx->tag = NULL; + + return tag; +} + +const struct input_plugin input_plugin_despotify = { + "spt", + nullptr, + nullptr, + input_despotify_open, + input_despotify_close, + nullptr, + nullptr, + .tag = input_despotify_tag, + nullptr, + input_despotify_read, + input_despotify_eof, + input_despotify_seek, +}; diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx new file mode 100644 index 000000000..00d699408 --- /dev/null +++ b/src/input/DespotifyInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef INPUT_DESPOTIFY_HXX +#define INPUT_DESPOTIFY_HXX + +extern const struct input_plugin input_plugin_despotify; + +#endif diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx new file mode 100644 index 000000000..1660f177d --- /dev/null +++ b/src/input/FfmpegInputPlugin.cxx @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" + +extern "C" { +#include <libavutil/avutil.h> +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +} + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_ffmpeg" + +struct FfmpegInputStream { + struct input_stream base; + + AVIOContext *h; + + bool eof; + + FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond, + AVIOContext *_h) + :base(input_plugin_ffmpeg, uri, mutex, cond), + h(_h), eof(false) { + base.ready = true; + base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0; + base.size = avio_size(h); + + /* hack to make MPD select the "ffmpeg" decoder plugin + - since avio.h doesn't tell us the MIME type of the + resource, we can't select a decoder plugin, but the + "ffmpeg" plugin is quite good at auto-detection */ + base.mime = g_strdup("audio/x-mpd-ffmpeg"); + } + + ~FfmpegInputStream() { + avio_close(h); + } +}; + +static inline GQuark +ffmpeg_quark(void) +{ + return g_quark_from_static_string("ffmpeg"); +} + +static inline bool +input_ffmpeg_supported(void) +{ + void *opaque = nullptr; + return avio_enum_protocols(&opaque, 0) != nullptr; +} + +static bool +input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + av_register_all(); + + /* disable this plugin if there's no registered protocol */ + if (!input_ffmpeg_supported()) { + g_set_error(error_r, ffmpeg_quark(), 0, + "No protocol"); + return false; + } + + return true; +} + +static struct input_stream * +input_ffmpeg_open(const char *uri, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + if (!g_str_has_prefix(uri, "gopher://") && + !g_str_has_prefix(uri, "rtp://") && + !g_str_has_prefix(uri, "rtsp://") && + !g_str_has_prefix(uri, "rtmp://") && + !g_str_has_prefix(uri, "rtmpt://") && + !g_str_has_prefix(uri, "rtmps://")) + return nullptr; + + AVIOContext *h; + int ret = avio_open(&h, uri, AVIO_FLAG_READ); + if (ret != 0) { + g_set_error(error_r, ffmpeg_quark(), ret, + "libavformat failed to open the URI"); + return nullptr; + } + + auto *i = new FfmpegInputStream(uri, mutex, cond, h); + return &i->base; +} + +static size_t +input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + int ret = avio_read(i->h, (unsigned char *)ptr, size); + if (ret <= 0) { + if (ret < 0) + g_set_error(error_r, ffmpeg_quark(), 0, + "url_read() failed"); + + i->eof = true; + return false; + } + + is->offset += ret; + return (size_t)ret; +} + +static void +input_ffmpeg_close(struct input_stream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + delete i; +} + +static bool +input_ffmpeg_eof(struct input_stream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + return i->eof; +} + +static bool +input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, + G_GNUC_UNUSED GError **error_r) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + int64_t ret = avio_seek(i->h, offset, whence); + + if (ret >= 0) { + i->eof = false; + return true; + } else { + g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed"); + return false; + } +} + +const struct input_plugin input_plugin_ffmpeg = { + "ffmpeg", + input_ffmpeg_init, + nullptr, + input_ffmpeg_open, + input_ffmpeg_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_ffmpeg_read, + input_ffmpeg_eof, + input_ffmpeg_seek, +}; diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/FfmpegInputPlugin.hxx new file mode 100644 index 000000000..d5e3a8d9b --- /dev/null +++ b/src/input/FfmpegInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX +#define MPD_FFMPEG_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libavformat's "avio" library. + */ +extern const struct input_plugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx new file mode 100644 index 000000000..2eecf32b6 --- /dev/null +++ b/src/input/FileInputPlugin.cxx @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" /* must be first for large file support */ +#include "FileInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "fd_util.h" +#include "open.h" +#include "io_error.h" + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_file" + +struct FileInputStream { + struct input_stream base; + + int fd; + + FileInputStream(const char *path, int _fd, off_t size, + Mutex &mutex, Cond &cond) + :base(input_plugin_file, path, mutex, cond), + fd(_fd) { + base.size = size; + base.seekable = true; + base.ready = true; + } + + ~FileInputStream() { + close(fd); + } +}; + +static struct input_stream * +input_file_open(const char *filename, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + int fd, ret; + struct stat st; + + if (!g_path_is_absolute(filename)) + return nullptr; + + fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); + if (fd < 0) { + if (errno != ENOENT && errno != ENOTDIR) + g_set_error(error_r, errno_quark(), errno, + "Failed to open \"%s\": %s", + filename, g_strerror(errno)); + return nullptr; + } + + ret = fstat(fd, &st); + if (ret < 0) { + g_set_error(error_r, errno_quark(), errno, + "Failed to stat \"%s\": %s", + filename, g_strerror(errno)); + close(fd); + return nullptr; + } + + if (!S_ISREG(st.st_mode)) { + g_set_error(error_r, errno_quark(), 0, + "Not a regular file: %s", filename); + close(fd); + return nullptr; + } + +#ifdef POSIX_FADV_SEQUENTIAL + posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); +#endif + + FileInputStream *fis = new FileInputStream(filename, fd, st.st_size, + mutex, cond); + return &fis->base; +} + +static bool +input_file_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + FileInputStream *fis = (FileInputStream *)is; + + offset = (goffset)lseek(fis->fd, (off_t)offset, whence); + if (offset < 0) { + g_set_error(error_r, errno_quark(), errno, + "Failed to seek: %s", g_strerror(errno)); + return false; + } + + is->offset = offset; + return true; +} + +static size_t +input_file_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + FileInputStream *fis = (FileInputStream *)is; + ssize_t nbytes; + + nbytes = read(fis->fd, ptr, size); + if (nbytes < 0) { + g_set_error(error_r, errno_quark(), errno, + "Failed to read: %s", g_strerror(errno)); + return 0; + } + + is->offset += nbytes; + return (size_t)nbytes; +} + +static void +input_file_close(struct input_stream *is) +{ + FileInputStream *fis = (FileInputStream *)is; + + delete fis; +} + +static bool +input_file_eof(struct input_stream *is) +{ + return is->offset >= is->size; +} + +const struct input_plugin input_plugin_file = { + "file", + nullptr, + nullptr, + input_file_open, + input_file_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_file_read, + input_file_eof, + input_file_seek, +}; diff --git a/src/input/FileInputPlugin.hxx b/src/input/FileInputPlugin.hxx new file mode 100644 index 000000000..aacfd0b5d --- /dev/null +++ b/src/input/FileInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef MPD_INPUT_FILE_HXX +#define MPD_INPUT_FILE_HXX + +extern const struct input_plugin input_plugin_file; + +#endif diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx new file mode 100644 index 000000000..b347eb92b --- /dev/null +++ b/src/input/MmsInputPlugin.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" +#include "MmsInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" + +#include <glib.h> +#include <libmms/mmsx.h> + +#include <string.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_mms" + +struct MmsInputStream { + struct input_stream base; + + mmsx_t *mms; + + bool eof; + + MmsInputStream(const char *uri, + Mutex &mutex, Cond &cond, + mmsx_t *_mms) + :base(input_plugin_mms, uri, mutex, cond), + mms(_mms), eof(false) { + /* XX is this correct? at least this selects the ffmpeg + decoder, which seems to work fine*/ + base.mime = g_strdup("audio/x-ms-wma"); + + base.ready = true; + } + + ~MmsInputStream() { + mmsx_close(mms); + } +}; + +static inline GQuark +mms_quark(void) +{ + return g_quark_from_static_string("mms"); +} + +static struct input_stream * +input_mms_open(const char *url, + Mutex &mutex, Cond &cond, + GError **error_r) +{ + if (!g_str_has_prefix(url, "mms://") && + !g_str_has_prefix(url, "mmsh://") && + !g_str_has_prefix(url, "mmst://") && + !g_str_has_prefix(url, "mmsu://")) + return nullptr; + + const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024); + if (mms == nullptr) { + g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); + return nullptr; + } + + auto m = new MmsInputStream(url, mutex, cond, mms); + return &m->base; +} + +static size_t +input_mms_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + MmsInputStream *m = (MmsInputStream *)is; + int ret; + + ret = mmsx_read(nullptr, m->mms, (char *)ptr, size); + if (ret <= 0) { + if (ret < 0) { + g_set_error(error_r, mms_quark(), errno, + "mmsx_read() failed: %s", + g_strerror(errno)); + } + + m->eof = true; + return false; + } + + is->offset += ret; + + return (size_t)ret; +} + +static void +input_mms_close(struct input_stream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + delete m; +} + +static bool +input_mms_eof(struct input_stream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + return m->eof; +} + +static bool +input_mms_seek(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +const struct input_plugin input_plugin_mms = { + "mms", + nullptr, + nullptr, + input_mms_open, + input_mms_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_mms_read, + input_mms_eof, + input_mms_seek, +}; diff --git a/src/input/mms_input_plugin.h b/src/input/MmsInputPlugin.hxx index d6aa593f2..d6aa593f2 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/MmsInputPlugin.hxx diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx new file mode 100644 index 000000000..d93d7d1ce --- /dev/null +++ b/src/input/RewindInputPlugin.cxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" +#include "RewindInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <stdio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_rewind" + +extern const struct input_plugin rewind_input_plugin; + +struct RewindInputStream { + struct input_stream base; + + struct input_stream *input; + + /** + * The read position within the buffer. Undefined as long as + * ReadingFromBuffer() returns false. + */ + size_t head; + + /** + * The write/append position within the buffer. + */ + size_t tail; + + /** + * The size of this buffer is the maximum number of bytes + * which can be rewinded cheaply without passing the "seek" + * call to CURL. + * + * The origin of this buffer is always the beginning of the + * stream (offset 0). + */ + char buffer[64 * 1024]; + + RewindInputStream(input_stream *_input) + :base(rewind_input_plugin, _input->uri.c_str(), + _input->mutex, _input->cond), + input(_input), tail(0) { + } + + ~RewindInputStream() { + input_stream_close(input); + } + + /** + * Are we currently reading from the buffer, and does the + * buffer contain more data for the next read operation? + */ + bool ReadingFromBuffer() const { + return tail > 0 && base.offset < input->offset; + } + + /** + * Copy public attributes from the underlying input stream to the + * "rewind" input stream. This function is called when a method of + * the underlying stream has returned, which may have modified these + * attributes. + */ + void CopyAttributes() { + struct input_stream *dest = &base; + const struct input_stream *src = input; + + assert(dest != src); + + bool dest_ready = dest->ready; + + dest->ready = src->ready; + dest->seekable = src->seekable; + dest->size = src->size; + dest->offset = src->offset; + + if (!dest_ready && src->ready) + dest->mime = src->mime; + } +}; + +static void +input_rewind_close(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + delete r; +} + +static bool +input_rewind_check(struct input_stream *is, GError **error_r) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return input_stream_check(r->input, error_r); +} + +static void +input_rewind_update(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (!r->ReadingFromBuffer()) + r->CopyAttributes(); +} + +static struct tag * +input_rewind_tag(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return input_stream_tag(r->input); +} + +static bool +input_rewind_available(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return input_stream_available(r->input); +} + +static size_t +input_rewind_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (r->ReadingFromBuffer()) { + /* buffered read */ + + assert(r->head == (size_t)is->offset); + assert(r->tail == (size_t)r->input->offset); + + if (size > r->tail - r->head) + size = r->tail - r->head; + + memcpy(ptr, r->buffer + r->head, size); + r->head += size; + is->offset += size; + + return size; + } else { + /* pass method call to underlying stream */ + + size_t nbytes = input_stream_read(r->input, ptr, size, error_r); + + if (r->input->offset > (goffset)sizeof(r->buffer)) + /* disable buffering */ + r->tail = 0; + else if (r->tail == (size_t)is->offset) { + /* append to buffer */ + + memcpy(r->buffer + r->tail, ptr, nbytes); + r->tail += nbytes; + + assert(r->tail == (size_t)r->input->offset); + } + + r->CopyAttributes(); + + return nbytes; + } +} + +static bool +input_rewind_eof(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return !r->ReadingFromBuffer() && input_stream_eof(r->input); +} + +static bool +input_rewind_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + RewindInputStream *r = (RewindInputStream *)is; + + assert(is->ready); + + if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { + /* buffered seek */ + + assert(!r->ReadingFromBuffer() || + r->head == (size_t)is->offset); + assert(r->tail == (size_t)r->input->offset); + + r->head = (size_t)offset; + is->offset = offset; + + return true; + } else { + bool success = input_stream_seek(r->input, offset, whence, + error_r); + r->CopyAttributes(); + + /* disable the buffer, because r->input has left the + buffered range now */ + r->tail = 0; + + return success; + } +} + +const struct input_plugin rewind_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + input_rewind_close, + input_rewind_check, + input_rewind_update, + input_rewind_tag, + input_rewind_available, + input_rewind_read, + input_rewind_eof, + input_rewind_seek, +}; + +struct input_stream * +input_rewind_open(struct input_stream *is) +{ + assert(is != NULL); + assert(is->offset == 0); + + if (is->seekable) + /* seekable resources don't need this plugin */ + return is; + + RewindInputStream *c = new RewindInputStream(is); + return &c->base; +} diff --git a/src/input/RewindInputPlugin.hxx b/src/input/RewindInputPlugin.hxx new file mode 100644 index 000000000..cf21e92f1 --- /dev/null +++ b/src/input/RewindInputPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +/** \file + * + * A wrapper for an input_stream object which allows cheap buffered + * rewinding. This is useful while detecting the stream codec (let + * each decoder plugin peek a portion from the stream). + */ + +#ifndef MPD_INPUT_REWIND_HXX +#define MPD_INPUT_REWIND_HXX + +#include "check.h" + +struct input_stream; + +struct input_stream * +input_rewind_open(struct input_stream *is); + +#endif diff --git a/src/input/SoupInputPlugin.cxx b/src/input/SoupInputPlugin.cxx new file mode 100644 index 000000000..e9767c20e --- /dev/null +++ b/src/input/SoupInputPlugin.cxx @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 "config.h" +#include "SoupInputPlugin.hxx" +#include "InputPlugin.hxx" +#include "InputStream.hxx" +#include "InputInternal.hxx" +#include "IOThread.hxx" +#include "event/Loop.hxx" +#include "conf.h" + +extern "C" { +#include <libsoup/soup-uri.h> +#include <libsoup/soup-session-async.h> +} + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_soup" + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t SOUP_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t SOUP_RESUME_AT = 384 * 1024; + +static SoupURI *soup_proxy; +static SoupSession *soup_session; + +struct SoupInputStream { + struct input_stream base; + + SoupMessage *msg; + + GQueue *buffers; + + size_t current_consumed; + + size_t total_buffered; + + bool alive, pause, eof; + + /** + * Set when the session callback has been invoked, when it is + * safe to free this object. + */ + bool completed; + + GError *postponed_error; + + SoupInputStream(const char *uri, Mutex &mutex, Cond &cond); + ~SoupInputStream(); + + bool CopyError(const SoupMessage *msg); + + bool WaitData(); + + size_t Read(void *ptr, size_t size, GError **error_r); +}; + +static inline GQuark +soup_quark(void) +{ + return g_quark_from_static_string("soup"); +} + +static bool +input_soup_init(const struct config_param *param, GError **error_r) +{ + assert(soup_proxy == NULL); + assert(soup_session == NULL); + + g_type_init(); + + const char *proxy = config_get_block_string(param, "proxy", NULL); + + if (proxy != NULL) { + soup_proxy = soup_uri_new(proxy); + if (soup_proxy == NULL) { + g_set_error(error_r, soup_quark(), 0, + "failed to parse proxy setting"); + return false; + } + } + + soup_session = + soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, + soup_proxy, + SOUP_SESSION_ASYNC_CONTEXT, + io_thread_get().GetContext(), + NULL); + + return true; +} + +static void +input_soup_finish(void) +{ + assert(soup_session != NULL); + + soup_session_abort(soup_session); + g_object_unref(G_OBJECT(soup_session)); + + if (soup_proxy != NULL) + soup_uri_free(soup_proxy); +} + +/** + * Copy the error from the SoupMessage object to + * input_soup::postponed_error. + * + * @return true if there was no error + */ +bool +SoupInputStream::CopyError(const SoupMessage *src) +{ + if (SOUP_STATUS_IS_SUCCESSFUL(src->status_code)) + return true; + + if (src->status_code == SOUP_STATUS_CANCELLED) + /* failure, but don't generate a GError, because this + status was caused by _close() */ + return false; + + if (postponed_error != nullptr) + /* there's already a GError, don't overwrite it */ + return false; + + if (SOUP_STATUS_IS_TRANSPORT_ERROR(src->status_code)) + postponed_error = + g_error_new(soup_quark(), src->status_code, + "HTTP client error: %s", + src->reason_phrase); + else + postponed_error = + g_error_new(soup_quark(), src->status_code, + "got HTTP status: %d %s", + src->status_code, src->reason_phrase); + + return false; +} + +static void +input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, + SoupMessage *msg, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + assert(msg == s->msg); + assert(!s->completed); + + const ScopeLock protect(s->base.mutex); + + if (!s->base.ready) + s->CopyError(msg); + + s->base.ready = true; + s->alive = false; + s->completed = true; + + s->base.cond.broadcast(); +} + +static void +input_soup_got_headers(SoupMessage *msg, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + s->base.mutex.lock(); + + if (!s->CopyError(msg)) { + s->base.mutex.unlock(); + + soup_session_cancel_message(soup_session, msg, + SOUP_STATUS_CANCELLED); + return; + } + + s->base.ready = true; + s->base.cond.broadcast(); + s->base.mutex.unlock(); + + soup_message_body_set_accumulate(msg->response_body, false); +} + +static void +input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + assert(msg == s->msg); + + const ScopeLock protect(s->base.mutex); + + g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); + s->total_buffered += chunk->length; + + if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { + s->pause = true; + soup_session_pause_message(soup_session, msg); + } + + s->base.cond.broadcast(); + s->base.mutex.unlock(); +} + +static void +input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) +{ + SoupInputStream *s = (SoupInputStream *)user_data; + + assert(msg == s->msg); + + const ScopeLock protect(s->base.mutex); + + s->base.ready = true; + s->eof = true; + s->alive = false; + + s->base.cond.broadcast(); + s->base.mutex.unlock(); +} + +inline bool +SoupInputStream::WaitData() +{ + while (true) { + if (eof) + return true; + + if (!alive) + return false; + + if (!g_queue_is_empty(buffers)) + return true; + + assert(current_consumed == 0); + + base.cond.wait(base.mutex); + } +} + +static gpointer +input_soup_queue(gpointer data) +{ + SoupInputStream *s = (SoupInputStream *)data; + + soup_session_queue_message(soup_session, s->msg, + input_soup_session_callback, s); + + return NULL; +} + +SoupInputStream::SoupInputStream(const char *uri, + Mutex &mutex, Cond &cond) + :base(input_plugin_soup, uri, mutex, cond), + buffers(g_queue_new()), + current_consumed(0), total_buffered(0), + alive(false), pause(false), eof(false), completed(false), + postponed_error(nullptr) +{ +#if GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic push + /* the libsoup macro SOUP_METHOD_GET discards the "const" + attribute of the g_intern_static_string() return value; + don't make the gcc warning fatal: */ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + + msg = soup_message_new(SOUP_METHOD_GET, uri); + +#if GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif + + soup_message_set_flags(msg, SOUP_MESSAGE_NO_REDIRECT); + + soup_message_headers_append(msg->request_headers, "User-Agent", + "Music Player Daemon " VERSION); + + g_signal_connect(msg, "got-headers", + G_CALLBACK(input_soup_got_headers), this); + g_signal_connect(msg, "got-chunk", + G_CALLBACK(input_soup_got_chunk), this); + g_signal_connect(msg, "got-body", + G_CALLBACK(input_soup_got_body), this); + + io_thread_call(input_soup_queue, this); +} + +static struct input_stream * +input_soup_open(const char *uri, + Mutex &mutex, Cond &cond, + G_GNUC_UNUSED GError **error_r) +{ + if (strncmp(uri, "http://", 7) != 0) + return NULL; + + SoupInputStream *s = new SoupInputStream(uri, mutex, cond); + return &s->base; +} + +static gpointer +input_soup_cancel(gpointer data) +{ + SoupInputStream *s = (SoupInputStream *)data; + + if (!s->completed) + soup_session_cancel_message(soup_session, s->msg, + SOUP_STATUS_CANCELLED); + + return NULL; +} + +SoupInputStream::~SoupInputStream() +{ + base.mutex.lock(); + + if (!completed) { + /* the messages's session callback hasn't been invoked + yet; cancel it and wait for completion */ + + base.mutex.unlock(); + + io_thread_call(input_soup_cancel, this); + + base.mutex.lock(); + while (!completed) + base.cond.wait(base.mutex); + } + + base.mutex.unlock(); + + SoupBuffer *buffer; + while ((buffer = (SoupBuffer *)g_queue_pop_head(buffers)) != NULL) + soup_buffer_free(buffer); + g_queue_free(buffers); + + if (postponed_error != NULL) + g_error_free(postponed_error); +} + +static void +input_soup_close(struct input_stream *is) +{ + SoupInputStream *s = (SoupInputStream *)is; + + delete s; +} + +static bool +input_soup_check(struct input_stream *is, GError **error_r) +{ + SoupInputStream *s = (SoupInputStream *)is; + + bool success = s->postponed_error == NULL; + if (!success) { + g_propagate_error(error_r, s->postponed_error); + s->postponed_error = NULL; + } + + return success; +} + +static bool +input_soup_available(struct input_stream *is) +{ + SoupInputStream *s = (SoupInputStream *)is; + + return s->eof || !s->alive || !g_queue_is_empty(s->buffers); +} + +inline size_t +SoupInputStream::Read(void *ptr, size_t size, GError **error_r) +{ + if (!WaitData()) { + assert(!alive); + + if (postponed_error != nullptr) { + g_propagate_error(error_r, postponed_error); + postponed_error = nullptr; + } else + g_set_error_literal(error_r, soup_quark(), 0, + "HTTP failure"); + return 0; + } + + char *p0 = (char *)ptr, *p = p0, *p_end = p0 + size; + + while (p < p_end) { + SoupBuffer *buffer = (SoupBuffer *) + g_queue_pop_head(buffers); + if (buffer == NULL) { + assert(current_consumed == 0); + break; + } + + assert(current_consumed < buffer->length); + assert(total_buffered >= buffer->length); + + const char *q = buffer->data; + q += current_consumed; + + size_t remaining = buffer->length - current_consumed; + size_t nbytes = p_end - p; + if (nbytes > remaining) + nbytes = remaining; + + memcpy(p, q, nbytes); + p += nbytes; + + current_consumed += remaining; + if (current_consumed >= buffer->length) { + /* done with this buffer */ + total_buffered -= buffer->length; + soup_buffer_free(buffer); + current_consumed = 0; + } else { + /* partial read */ + assert(p == p_end); + + g_queue_push_head(buffers, buffer); + } + } + + if (pause && total_buffered < SOUP_RESUME_AT) { + pause = false; + soup_session_unpause_message(soup_session, msg); + } + + size_t nbytes = p - p0; + base.offset += nbytes; + + return nbytes; +} + +static size_t +input_soup_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + SoupInputStream *s = (SoupInputStream *)is; + + return s->Read(ptr, size, error_r); +} + +static bool +input_soup_eof(G_GNUC_UNUSED struct input_stream *is) +{ + SoupInputStream *s = (SoupInputStream *)is; + + return !s->alive && g_queue_is_empty(s->buffers); +} + +const struct input_plugin input_plugin_soup = { + "soup", + input_soup_init, + input_soup_finish, + input_soup_open, + input_soup_close, + input_soup_check, + nullptr, + nullptr, + input_soup_available, + input_soup_read, + input_soup_eof, + nullptr, +}; diff --git a/src/input/SoupInputPlugin.hxx b/src/input/SoupInputPlugin.hxx new file mode 100644 index 000000000..4c089b39b --- /dev/null +++ b/src/input/SoupInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + */ + +#ifndef MPD_INPUT_SOUP_HXX +#define MPD_INPUT_SOUP_HXX + +extern const struct input_plugin input_plugin_soup; + +#endif diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c deleted file mode 100644 index 4a038b9e2..000000000 --- a/src/input/archive_input_plugin.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/archive_input_plugin.h" -#include "archive_api.h" -#include "archive_list.h" -#include "input_plugin.h" - -#include <glib.h> - -/** - * select correct archive plugin to handle the input stream - * may allow stacking of archive plugins. for example for handling - * tar.gz a gzip handler opens file (through inputfile stream) - * then it opens a tar handler and sets gzip inputstream as - * parent_stream so tar plugin fetches file data from gzip - * plugin and gzip fetches file from disk - */ -static struct input_stream * -input_archive_open(const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - const struct archive_plugin *arplug; - struct archive_file *file; - char *archive, *filename, *suffix, *pname; - struct input_stream *is; - - if (!g_path_is_absolute(pathname)) - return NULL; - - pname = g_strdup(pathname); - // archive_lookup will modify pname when true is returned - if (!archive_lookup(pname, &archive, &filename, &suffix)) { - g_debug("not an archive, lookup %s failed\n", pname); - g_free(pname); - return NULL; - } - - //check which archive plugin to use (by ext) - arplug = archive_plugin_from_suffix(suffix); - if (!arplug) { - g_warning("can't handle archive %s\n",archive); - g_free(pname); - return NULL; - } - - file = archive_file_open(arplug, archive, error_r); - if (file == NULL) - return NULL; - - //setup fileops - is = archive_file_open_stream(file, filename, mutex, cond, - error_r); - archive_file_close(file); - g_free(pname); - - return is; -} - -const struct input_plugin input_plugin_archive = { - .name = "archive", - .open = input_archive_open, -}; diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h deleted file mode 100644 index 51095f37f..000000000 --- a/src/input/archive_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_ARCHIVE_H -#define MPD_INPUT_ARCHIVE_H - -extern const struct input_plugin input_plugin_archive; - -#endif diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c deleted file mode 100644 index 1de7623a1..000000000 --- a/src/input/cdio_paranoia_input_plugin.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -/** - * CD-Audio handling (requires libcdio_paranoia) - */ - -#include "config.h" -#include "input/cdio_paranoia_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <stdio.h> -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> -#include <assert.h> - -#include <cdio/paranoia.h> -#include <cdio/cd_types.h> - -struct input_cdio_paranoia { - struct input_stream base; - - cdrom_drive_t *drv; - CdIo_t *cdio; - cdrom_paranoia_t *para; - - lsn_t lsn_from, lsn_to; - int lsn_relofs; - - int trackno; - - char buffer[CDIO_CD_FRAMESIZE_RAW]; - int buffer_lsn; -}; - -static inline GQuark -cdio_quark(void) -{ - return g_quark_from_static_string("cdio"); -} - -static void -input_cdio_close(struct input_stream *is) -{ - struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is; - - if (i->para) - cdio_paranoia_free(i->para); - if (i->drv) - cdio_cddap_close_no_free_cdio( i->drv); - if (i->cdio) - cdio_destroy( i->cdio ); - - input_stream_deinit(&i->base); - g_free(i); -} - -struct cdio_uri { - char device[64]; - int track; -}; - -static bool -parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) -{ - if (!g_str_has_prefix(src, "cdda://")) - return false; - - src += 7; - - if (*src == 0) { - /* play the whole CD in the default drive */ - dest->device[0] = 0; - dest->track = -1; - return true; - } - - const char *slash = strrchr(src, '/'); - if (slash == NULL) { - /* play the whole CD in the specified drive */ - g_strlcpy(dest->device, src, sizeof(dest->device)); - dest->track = -1; - return true; - } - - size_t device_length = slash - src; - if (device_length >= sizeof(dest->device)) - device_length = sizeof(dest->device) - 1; - - memcpy(dest->device, src, device_length); - dest->device[device_length] = 0; - - const char *track = slash + 1; - - char *endptr; - dest->track = strtoul(track, &endptr, 10); - if (*endptr != 0) { - g_set_error(error_r, cdio_quark(), 0, - "Malformed track number"); - return false; - } - - if (endptr == track) - /* play the whole CD */ - dest->track = -1; - - return true; -} - -static char * -cdio_detect_device(void) -{ - char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); - if (devices == NULL) - return NULL; - - char *device = g_strdup(devices[0]); - cdio_free_device_list(devices); - - return device; -} - -static struct input_stream * -input_cdio_open(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_cdio_paranoia *i; - - struct cdio_uri parsed_uri; - if (!parse_cdio_uri(&parsed_uri, uri, error_r)) - return NULL; - - i = g_new(struct input_cdio_paranoia, 1); - input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri, - mutex, cond); - - /* initialize everything (should be already) */ - i->drv = NULL; - i->cdio = NULL; - i->para = NULL; - i->trackno = parsed_uri.track; - - /* get list of CD's supporting CD-DA */ - char *device = parsed_uri.device[0] != 0 - ? g_strdup(parsed_uri.device) - : cdio_detect_device(); - if (device == NULL) { - g_set_error(error_r, cdio_quark(), 0, - "Unable find or access a CD-ROM drive with an audio CD in it."); - input_cdio_close(&i->base); - return NULL; - } - - /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ - i->cdio = cdio_open(device, DRIVER_UNKNOWN); - g_free(device); - - i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL); - - if ( !i->drv ) { - g_set_error(error_r, cdio_quark(), 0, - "Unable to identify audio CD disc."); - input_cdio_close(&i->base); - return NULL; - } - - cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); - - if ( 0 != cdio_cddap_open(i->drv) ) { - g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); - input_cdio_close(&i->base); - return NULL; - } - - bool reverse_endian; - switch (data_bigendianp(i->drv)) { - case -1: - g_debug("cdda: drive returns unknown audio data"); - reverse_endian = false; - break; - case 0: - g_debug("cdda: drive returns audio data Little Endian."); - reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; - break; - case 1: - g_debug("cdda: drive returns audio data Big Endian."); - reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; - break; - default: - g_set_error(error_r, cdio_quark(), 0, - "Drive returns unknown data type %d", - data_bigendianp(i->drv)); - input_cdio_close(&i->base); - return NULL; - } - - i->lsn_relofs = 0; - - if (i->trackno >= 0) { - i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); - i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); - } else { - i->lsn_from = 0; - i->lsn_to = cdio_get_disc_last_lsn(i->cdio); - } - - i->para = cdio_paranoia_init(i->drv); - - /* Set reading mode for full paranoia, but allow skipping sectors. */ - paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); - - /* seek to beginning of the track */ - cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); - - i->base.ready = true; - i->base.seekable = true; - i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; - - /* hack to make MPD select the "pcm" decoder plugin */ - i->base.mime = g_strdup(reverse_endian - ? "audio/x-mpd-cdda-pcm-reverse" - : "audio/x-mpd-cdda-pcm"); - - return &i->base; -} - -static bool -input_cdio_seek(struct input_stream *is, - goffset offset, int whence, GError **error_r) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - - /* calculate absolute offset */ - switch (whence) { - case SEEK_SET: - break; - case SEEK_CUR: - offset += cis->base.offset; - break; - case SEEK_END: - offset += cis->base.size; - break; - } - - if (offset < 0 || offset > cis->base.size) { - g_set_error(error_r, cdio_quark(), 0, - "Invalid offset to seek %ld (%ld)", - (long int)offset, (long int)cis->base.size); - return false; - } - - /* simple case */ - if (offset == cis->base.offset) - return true; - - /* calculate current LSN */ - cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; - cis->base.offset = offset; - - cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); - - return true; -} - -static size_t -input_cdio_read(struct input_stream *is, void *ptr, size_t length, - GError **error_r) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - size_t nbytes = 0; - int diff; - size_t len, maxwrite; - int16_t *rbuf; - char *s_err, *s_mess; - char *wptr = (char *) ptr; - - while (length > 0) { - - - /* end of track ? */ - if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) - break; - - //current sector was changed ? - if (cis->lsn_relofs != cis->buffer_lsn) { - rbuf = cdio_paranoia_read(cis->para, NULL); - - s_err = cdda_errors(cis->drv); - if (s_err) { - g_warning("paranoia_read: %s", s_err ); - free(s_err); - } - s_mess = cdda_messages(cis->drv); - if (s_mess) { - free(s_mess); - } - if (!rbuf) { - g_set_error(error_r, cdio_quark(), 0, - "paranoia read error. Stopping."); - return 0; - } - //store current buffer - memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); - cis->buffer_lsn = cis->lsn_relofs; - } else { - //use cached sector - rbuf = (int16_t*) cis->buffer; - } - - //correct offset - diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; - - assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); - - maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer - len = (length < maxwrite? length : maxwrite); - - //skip diff bytes from this lsn - memcpy(wptr, ((char*)rbuf) + diff, len); - //update pointer - wptr += len; - nbytes += len; - - //update offset - cis->base.offset += len; - cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; - //update length - length -= len; - } - - return nbytes; -} - -static bool -input_cdio_eof(struct input_stream *is) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - - return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); -} - -const struct input_plugin input_plugin_cdio_paranoia = { - .name = "cdio_paranoia", - .open = input_cdio_open, - .close = input_cdio_close, - .seek = input_cdio_seek, - .read = input_cdio_read, - .eof = input_cdio_eof -}; diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h deleted file mode 100644 index 71c5cbe8d..000000000 --- a/src/input/cdio_paranoia_input_plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_H -#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H - -/** - * An input plugin based on libcdio_paranoia library. - */ -extern const struct input_plugin input_plugin_cdio_paranoia; - -#endif diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c deleted file mode 100644 index 3f191141e..000000000 --- a/src/input/curl_input_plugin.c +++ /dev/null @@ -1,1301 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/curl_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "conf.h" -#include "tag.h" -#include "icy_metadata.h" -#include "io_thread.h" -#include "glib_compat.h" - -#include <assert.h> - -#if defined(WIN32) - #include <winsock2.h> -#else - #include <sys/select.h> -#endif - -#include <string.h> -#include <errno.h> - -#include <curl/curl.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_curl" - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t CURL_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t CURL_RESUME_AT = 384 * 1024; - -/** - * Buffers created by input_curl_writefunction(). - */ -struct buffer { - /** size of the payload */ - size_t size; - - /** how much has been consumed yet? */ - size_t consumed; - - /** the payload */ - unsigned char data[sizeof(long)]; -}; - -struct input_curl { - struct input_stream base; - - /* some buffers which were passed to libcurl, which we have - too free */ - char *url, *range; - struct curl_slist *request_headers; - - /** the curl handles */ - CURL *easy; - - /** the GMainLoop source used to poll all CURL file - descriptors */ - GSource *source; - - /** the source id of #source */ - guint source_id; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; - - /** list of buffers, where input_curl_writefunction() appends - to, and input_curl_read() reads from them */ - GQueue *buffers; - -#if LIBCURL_VERSION_NUM >= 0x071200 - /** - * Is the connection currently paused? That happens when the - * buffer was getting too large. It will be unpaused when the - * buffer is below the threshold again. - */ - bool paused; -#endif - - /** error message provided by libcurl */ - char error[CURL_ERROR_SIZE]; - - /** parser for icy-metadata */ - struct icy_metadata icy_metadata; - - /** the stream name from the icy-name response header */ - char *meta_name; - - /** the tag object ready to be requested via - input_stream_tag() */ - struct tag *tag; - - GError *postponed_error; -}; - -/** libcurl should accept "ICY 200 OK" */ -static struct curl_slist *http_200_aliases; - -/** HTTP proxy settings */ -static const char *proxy, *proxy_user, *proxy_password; -static unsigned proxy_port; - -static struct { - CURLM *multi; - - /** - * A linked list of all active HTTP requests. An active - * request is one that doesn't have the "eof" flag set. - */ - GSList *requests; - - /** - * The GMainLoop source used to poll all CURL file - * descriptors. - */ - GSource *source; - - /** - * The source id of #source. - */ - guint source_id; - - GSList *fds; - -#if LIBCURL_VERSION_NUM >= 0x070f04 - /** - * Did CURL give us a timeout? If yes, then we need to call - * curl_multi_perform(), even if there was no event on any - * file descriptor. - */ - bool timeout; - - /** - * The absolute time stamp when the timeout expires. This is - * used in the GSource method check(). - */ - gint64 absolute_timeout; -#endif -} curl; - -static inline GQuark -curl_quark(void) -{ - return g_quark_from_static_string("curl"); -} - -/** - * Find a request by its CURL "easy" handle. - * - * Runs in the I/O thread. No lock needed. - */ -static struct input_curl * -input_curl_find_request(CURL *easy) -{ - assert(io_thread_inside()); - - for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { - struct input_curl *c = i->data; - if (c->easy == easy) - return c; - } - - return NULL; -} - -#if LIBCURL_VERSION_NUM >= 0x071200 - -static gpointer -input_curl_resume(gpointer data) -{ - assert(io_thread_inside()); - - struct input_curl *c = data; - - if (c->paused) { - c->paused = false; - curl_easy_pause(c->easy, CURLPAUSE_CONT); - } - - return NULL; -} - -#endif - -/** - * Calculates the GLib event bit mask for one file descriptor, - * obtained from three #fd_set objects filled by curl_multi_fdset(). - */ -static gushort -input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) -{ - gushort events = 0; - - if (FD_ISSET(fd, rfds)) { - events |= G_IO_IN | G_IO_HUP | G_IO_ERR; - FD_CLR(fd, rfds); - } - - if (FD_ISSET(fd, wfds)) { - events |= G_IO_OUT | G_IO_ERR; - FD_CLR(fd, wfds); - } - - if (FD_ISSET(fd, efds)) { - events |= G_IO_HUP | G_IO_ERR; - FD_CLR(fd, efds); - } - - return events; -} - -/** - * Updates all registered GPollFD objects, unregisters old ones, - * registers new ones. - * - * Runs in the I/O thread. No lock needed. - */ -static void -curl_update_fds(void) -{ - assert(io_thread_inside()); - - fd_set rfds, wfds, efds; - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - int max_fd; - CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, - &efds, &max_fd); - if (mcode != CURLM_OK) { - g_warning("curl_multi_fdset() failed: %s\n", - curl_multi_strerror(mcode)); - return; - } - - GSList *fds = curl.fds; - curl.fds = NULL; - - while (fds != NULL) { - GPollFD *poll_fd = fds->data; - gushort events = input_curl_fd_events(poll_fd->fd, &rfds, - &wfds, &efds); - - assert(poll_fd->events != 0); - - fds = g_slist_remove(fds, poll_fd); - - if (events != poll_fd->events) - g_source_remove_poll(curl.source, poll_fd); - - if (events != 0) { - if (events != poll_fd->events) { - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - } - - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } else { - g_free(poll_fd); - } - } - - for (int fd = 0; fd <= max_fd; ++fd) { - gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); - if (events != 0) { - GPollFD *poll_fd = g_new(GPollFD, 1); - poll_fd->fd = fd; - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } - } -} - -/** - * Runs in the I/O thread. No lock needed. - */ -static bool -input_curl_easy_add(struct input_curl *c, GError **error_r) -{ - assert(io_thread_inside()); - assert(c != NULL); - assert(c->easy != NULL); - assert(input_curl_find_request(c->easy) == NULL); - - curl.requests = g_slist_prepend(curl.requests, c); - - CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - curl_update_fds(); - - return true; -} - -struct easy_add_params { - struct input_curl *c; - GError **error_r; -}; - -static gpointer -input_curl_easy_add_callback(gpointer data) -{ - const struct easy_add_params *params = data; - - bool success = input_curl_easy_add(params->c, params->error_r); - return GUINT_TO_POINTER(success); -} - -/** - * Call input_curl_easy_add() in the I/O thread. May be called from - * any thread. Caller must not hold a mutex. - */ -static bool -input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) -{ - assert(c != NULL); - assert(c->easy != NULL); - - struct easy_add_params params = { - .c = c, - .error_r = error_r, - }; - - gpointer result = - io_thread_call(input_curl_easy_add_callback, ¶ms); - return GPOINTER_TO_UINT(result); -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * Runs in the I/O thread. - */ -static void -input_curl_easy_free(struct input_curl *c) -{ - assert(io_thread_inside()); - assert(c != NULL); - - if (c->easy == NULL) - return; - - curl.requests = g_slist_remove(curl.requests, c); - - curl_multi_remove_handle(curl.multi, c->easy); - curl_easy_cleanup(c->easy); - c->easy = NULL; - - curl_slist_free_all(c->request_headers); - c->request_headers = NULL; - - g_free(c->range); - c->range = NULL; -} - -static gpointer -input_curl_easy_free_callback(gpointer data) -{ - struct input_curl *c = data; - - input_curl_easy_free(c); - curl_update_fds(); - - return NULL; -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * The mutex must not be locked. - */ -static void -input_curl_easy_free_indirect(struct input_curl *c) -{ - io_thread_call(input_curl_easy_free_callback, c); - assert(c->easy == NULL); -} - -/** - * Abort and free all HTTP requests. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_abort_all_requests(GError *error) -{ - assert(io_thread_inside()); - assert(error != NULL); - - while (curl.requests != NULL) { - struct input_curl *c = curl.requests->data; - assert(c->postponed_error == NULL); - - input_curl_easy_free(c); - - g_mutex_lock(c->base.mutex); - c->postponed_error = g_error_copy(error); - c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - } - - g_error_free(error); - -} - -/** - * A HTTP request is finished. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_request_done(struct input_curl *c, CURLcode result, long status) -{ - assert(io_thread_inside()); - assert(c != NULL); - assert(c->easy == NULL); - assert(c->postponed_error == NULL); - - g_mutex_lock(c->base.mutex); - - if (result != CURLE_OK) { - c->postponed_error = g_error_new(curl_quark(), result, - "curl failed: %s", - c->error); - } else if (status < 200 || status >= 300) { - c->postponed_error = g_error_new(curl_quark(), 0, - "got HTTP status %ld", - status); - } - - c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); -} - -static void -input_curl_handle_done(CURL *easy_handle, CURLcode result) -{ - struct input_curl *c = input_curl_find_request(easy_handle); - assert(c != NULL); - - long status = 0; - curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); - - input_curl_easy_free(c); - input_curl_request_done(c, result, status); -} - -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_info_read(void) -{ - assert(io_thread_inside()); - - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(curl.multi, - &msgs_in_queue)) != NULL) { - if (msg->msg == CURLMSG_DONE) - input_curl_handle_done(msg->easy_handle, msg->data.result); - } -} - -/** - * Give control to CURL. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static bool -input_curl_perform(void) -{ - assert(io_thread_inside()); - - CURLMcode mcode; - - do { - int running_handles; - mcode = curl_multi_perform(curl.multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM); - - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - GError *error = g_error_new(curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - input_curl_abort_all_requests(error); - return false; - } - - return true; -} - -/* - * GSource methods - * - */ - -/** - * The GSource prepare() method implementation. - */ -static gboolean -input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) -{ - curl_update_fds(); - -#if LIBCURL_VERSION_NUM >= 0x070f04 - curl.timeout = false; - - long timeout2; - CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); - if (mcode == CURLM_OK) { - if (timeout2 >= 0) - curl.absolute_timeout = g_source_get_time(source) - + timeout2 * 1000; - - if (timeout2 >= 0 && timeout2 < 10) - /* CURL 7.21.1 likes to report "timeout=0", - which means we're running in a busy loop. - Quite a bad idea to waste so much CPU. - Let's use a lower limit of 10ms. */ - timeout2 = 10; - - *timeout_r = timeout2; - - curl.timeout = timeout2 >= 0; - } else - g_warning("curl_multi_timeout() failed: %s\n", - curl_multi_strerror(mcode)); -#else - (void)timeout_r; -#endif - - return false; -} - -/** - * The GSource check() method implementation. - */ -static gboolean -input_curl_source_check(G_GNUC_UNUSED GSource *source) -{ -#if LIBCURL_VERSION_NUM >= 0x070f04 - if (curl.timeout) { - /* when a timeout has expired, we need to call - curl_multi_perform(), even if there was no file - descriptor event */ - - if (g_source_get_time(source) >= curl.absolute_timeout) - return true; - } -#endif - - for (GSList *i = curl.fds; i != NULL; i = i->next) { - GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; -} - -/** - * The GSource dispatch() method implementation. The callback isn't - * used, because we're handling all events directly. - */ -static gboolean -input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) -{ - if (input_curl_perform()) - input_curl_info_read(); - - return true; -} - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs curl_source_funcs = { - .prepare = input_curl_source_prepare, - .check = input_curl_source_check, - .dispatch = input_curl_source_dispatch, -}; - -/* - * input_plugin methods - * - */ - -static bool -input_curl_init(const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); - if (code != CURLE_OK) { - g_set_error(error_r, curl_quark(), code, - "curl_global_init() failed: %s\n", - curl_easy_strerror(code)); - return false; - } - - http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); - - proxy = config_get_block_string(param, "proxy", NULL); - proxy_port = config_get_block_unsigned(param, "proxy_port", 0); - proxy_user = config_get_block_string(param, "proxy_user", NULL); - proxy_password = config_get_block_string(param, "proxy_password", - NULL); - - if (proxy == NULL) { - /* deprecated proxy configuration */ - proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL); - proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); - proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL); - proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, - ""); - } - - curl.multi = curl_multi_init(); - if (curl.multi == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_multi_init() failed"); - return false; - } - - curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); - curl.source_id = g_source_attach(curl.source, io_thread_context()); - - return true; -} - -static gpointer -curl_destroy_sources(G_GNUC_UNUSED gpointer data) -{ - g_source_destroy(curl.source); - - return NULL; -} - -static void -input_curl_finish(void) -{ - assert(curl.requests == NULL); - - io_thread_call(curl_destroy_sources, NULL); - - curl_multi_cleanup(curl.multi); - - curl_slist_free_all(http_200_aliases); - - curl_global_cleanup(); -} - -#if LIBCURL_VERSION_NUM >= 0x071200 - -/** - * Determine the total sizes of all buffers, including portions that - * have already been consumed. - * - * The caller must lock the mutex. - */ -G_GNUC_PURE -static size_t -curl_total_buffer_size(const struct input_curl *c) -{ - size_t total = 0; - - for (GList *i = g_queue_peek_head_link(c->buffers); - i != NULL; i = g_list_next(i)) { - struct buffer *buffer = i->data; - total += buffer->size; - } - - return total; -} - -#endif - -static void -buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct buffer *buffer = data; - - assert(buffer->consumed <= buffer->size); - - g_free(buffer); -} - -static void -input_curl_flush_buffers(struct input_curl *c) -{ - g_queue_foreach(c->buffers, buffer_free_callback, NULL); - g_queue_clear(c->buffers); -} - -/** - * Frees this stream, including the input_stream struct. - */ -static void -input_curl_free(struct input_curl *c) -{ - if (c->tag != NULL) - tag_free(c->tag); - g_free(c->meta_name); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - g_queue_free(c->buffers); - - if (c->postponed_error != NULL) - g_error_free(c->postponed_error); - - g_free(c->url); - input_stream_deinit(&c->base); - g_free(c); -} - -static bool -input_curl_check(struct input_stream *is, GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - - bool success = c->postponed_error == NULL; - if (!success) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - } - - return success; -} - -static struct tag * -input_curl_tag(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - struct tag *tag = c->tag; - - c->tag = NULL; - return tag; -} - -static bool -fill_buffer(struct input_curl *c, GError **error_r) -{ - while (c->easy != NULL && g_queue_is_empty(c->buffers)) - g_cond_wait(c->base.cond, c->base.mutex); - - if (c->postponed_error != NULL) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - return false; - } - - return !g_queue_is_empty(c->buffers); -} - -/** - * Mark a part of the buffer object as consumed. - */ -static struct buffer * -consume_buffer(struct buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->consumed < buffer->size); - - buffer->consumed += length; - if (buffer->consumed < buffer->size) - return buffer; - - assert(buffer->consumed == buffer->size); - - g_free(buffer); - - return NULL; -} - -static size_t -read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers, - void *dest0, size_t length) -{ - struct buffer *buffer = g_queue_pop_head(buffers); - uint8_t *dest = dest0; - size_t nbytes = 0; - - assert(buffer->size > 0); - assert(buffer->consumed < buffer->size); - - if (length > buffer->size - buffer->consumed) - length = buffer->size - buffer->consumed; - - while (true) { - size_t chunk; - - chunk = icy_data(icy_metadata, length); - if (chunk > 0) { - memcpy(dest, buffer->data + buffer->consumed, - chunk); - buffer = consume_buffer(buffer, chunk); - - nbytes += chunk; - dest += chunk; - length -= chunk; - - if (length == 0) - break; - - assert(buffer != NULL); - } - - chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed, - length); - if (chunk > 0) { - buffer = consume_buffer(buffer, chunk); - - length -= chunk; - - if (length == 0) - break; - - assert(buffer != NULL); - } - } - - if (buffer != NULL) - g_queue_push_head(buffers, buffer); - - return nbytes; -} - -static void -copy_icy_tag(struct input_curl *c) -{ - struct tag *tag = icy_tag(&c->icy_metadata); - - if (tag == NULL) - return; - - if (c->tag != NULL) - tag_free(c->tag); - - if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) - tag_add_item(tag, TAG_NAME, c->meta_name); - - c->tag = tag; -} - -static bool -input_curl_available(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->postponed_error != NULL || c->easy == NULL || - !g_queue_is_empty(c->buffers); -} - -static size_t -input_curl_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - bool success; - size_t nbytes = 0; - char *dest = ptr; - - do { - /* fill the buffer */ - - success = fill_buffer(c, error_r); - if (!success) - return 0; - - /* send buffer contents */ - - while (size > 0 && !g_queue_is_empty(c->buffers)) { - size_t copy = read_from_buffer(&c->icy_metadata, c->buffers, - dest + nbytes, size); - - nbytes += copy; - size -= copy; - } - } while (nbytes == 0); - - if (icy_defined(&c->icy_metadata)) - copy_icy_tag(c); - - is->offset += (goffset)nbytes; - -#if LIBCURL_VERSION_NUM >= 0x071200 - if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { - g_mutex_unlock(c->base.mutex); - io_thread_call(input_curl_resume, c); - g_mutex_lock(c->base.mutex); - } -#endif - - return nbytes; -} - -static void -input_curl_close(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - input_curl_free(c); -} - -static bool -input_curl_eof(G_GNUC_UNUSED struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->easy == NULL && g_queue_is_empty(c->buffers); -} - -/** called by curl when new data is available */ -static size_t -input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - const char *header = ptr, *end, *value; - char name[64]; - - size *= nmemb; - end = header + size; - - value = memchr(header, ':', size); - if (value == NULL || (size_t)(value - header) >= sizeof(name)) - return size; - - memcpy(name, header, value - header); - name[value - header] = 0; - - /* skip the colon */ - - ++value; - - /* strip the value */ - - while (value < end && g_ascii_isspace(*value)) - ++value; - - while (end > value && g_ascii_isspace(end[-1])) - --end; - - if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { - /* a stream with icy-metadata is not seekable */ - if (!icy_defined(&c->icy_metadata)) - c->base.seekable = true; - } else if (g_ascii_strcasecmp(name, "content-length") == 0) { - char buffer[64]; - - if ((size_t)(end - header) >= sizeof(buffer)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); - } else if (g_ascii_strcasecmp(name, "content-type") == 0) { - g_free(c->base.mime); - c->base.mime = g_strndup(value, end - value); - } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || - g_ascii_strcasecmp(name, "ice-name") == 0 || - g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { - g_free(c->meta_name); - c->meta_name = g_strndup(value, end - value); - - if (c->tag != NULL) - tag_free(c->tag); - - c->tag = tag_new(); - tag_add_item(c->tag, TAG_NAME, c->meta_name); - } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { - char buffer[64]; - size_t icy_metaint; - - if ((size_t)(end - header) >= sizeof(buffer) || - icy_defined(&c->icy_metadata)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - icy_metaint = g_ascii_strtoull(buffer, NULL, 10); - g_debug("icy-metaint=%zu", icy_metaint); - - if (icy_metaint > 0) { - icy_start(&c->icy_metadata, icy_metaint); - - /* a stream with icy-metadata is not - seekable */ - c->base.seekable = false; - } - } - - return size; -} - -/** called by curl when new data is available */ -static size_t -input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - struct buffer *buffer; - - size *= nmemb; - if (size == 0) - return 0; - - g_mutex_lock(c->base.mutex); - -#if LIBCURL_VERSION_NUM >= 0x071200 - if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { - c->paused = true; - g_mutex_unlock(c->base.mutex); - return CURL_WRITEFUNC_PAUSE; - } -#endif - - buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - buffer->size = size; - buffer->consumed = 0; - memcpy(buffer->data, ptr, size); - - g_queue_push_tail(c->buffers, buffer); - c->base.ready = true; - - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - - return size; -} - -static bool -input_curl_easy_init(struct input_curl *c, GError **error_r) -{ - CURLcode code; - - c->easy = curl_easy_init(); - if (c->easy == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_easy_init() failed"); - return false; - } - - curl_easy_setopt(c->easy, CURLOPT_USERAGENT, - "Music Player Daemon " VERSION); - curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, - input_curl_headerfunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); - curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, - input_curl_writefunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); - curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); - curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); - curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); - curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); - curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); - curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); - curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); - curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); - - if (proxy != NULL) - curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); - - if (proxy_port > 0) - curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); - - if (proxy_user != NULL && proxy_password != NULL) { - char *proxy_auth_str = - g_strconcat(proxy_user, ":", proxy_password, NULL); - curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); - g_free(proxy_auth_str); - } - - code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); - if (code != CURLE_OK) { - g_set_error(error_r, curl_quark(), code, - "curl_easy_setopt() failed: %s", - curl_easy_strerror(code)); - return false; - } - - c->request_headers = NULL; - c->request_headers = curl_slist_append(c->request_headers, - "Icy-Metadata: 1"); - curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); - - return true; -} - -static bool -input_curl_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - bool ret; - - assert(is->ready); - - if (whence == SEEK_SET && offset == is->offset) - /* no-op */ - return true; - - if (!is->seekable) - return false; - - /* calculate the absolute offset */ - - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - offset += is->offset; - break; - - case SEEK_END: - if (is->size < 0) - /* stream size is not known */ - return false; - - offset += is->size; - break; - - default: - return false; - } - - if (offset < 0) - return false; - - /* check if we can fast-forward the buffer */ - - while (offset > is->offset && !g_queue_is_empty(c->buffers)) { - struct buffer *buffer; - size_t length; - - buffer = (struct buffer *)g_queue_pop_head(c->buffers); - - length = buffer->size - buffer->consumed; - if (offset - is->offset < (goffset)length) - length = offset - is->offset; - - buffer = consume_buffer(buffer, length); - if (buffer != NULL) - g_queue_push_head(c->buffers, buffer); - - is->offset += length; - } - - if (offset == is->offset) - return true; - - /* close the old connection and open a new one */ - - g_mutex_unlock(c->base.mutex); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - is->offset = offset; - if (is->offset == is->size) { - /* seek to EOF: simulate empty result; avoid - triggering a "416 Requested Range Not Satisfiable" - response */ - return true; - } - - ret = input_curl_easy_init(c, error_r); - if (!ret) - return false; - - /* send the "Range" header */ - - if (is->offset > 0) { - c->range = g_strdup_printf("%lld-", (long long)is->offset); - curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); - } - - c->base.ready = false; - - if (!input_curl_easy_add_indirect(c, error_r)) - return false; - - g_mutex_lock(c->base.mutex); - - while (!c->base.ready) - g_cond_wait(c->base.cond, c->base.mutex); - - if (c->postponed_error != NULL) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - return false; - } - - return true; -} - -static struct input_stream * -input_curl_open(const char *url, GMutex *mutex, GCond *cond, - GError **error_r) -{ - assert(mutex != NULL); - assert(cond != NULL); - - struct input_curl *c; - - if (strncmp(url, "http://", 7) != 0) - return NULL; - - c = g_new0(struct input_curl, 1); - input_stream_init(&c->base, &input_plugin_curl, url, - mutex, cond); - - c->url = g_strdup(url); - c->buffers = g_queue_new(); - - icy_clear(&c->icy_metadata); - c->tag = NULL; - - c->postponed_error = NULL; - -#if LIBCURL_VERSION_NUM >= 0x071200 - c->paused = false; -#endif - - if (!input_curl_easy_init(c, error_r)) { - input_curl_free(c); - return NULL; - } - - if (!input_curl_easy_add_indirect(c, error_r)) { - input_curl_free(c); - return NULL; - } - - return &c->base; -} - -const struct input_plugin input_plugin_curl = { - .name = "curl", - .init = input_curl_init, - .finish = input_curl_finish, - - .open = input_curl_open, - .close = input_curl_close, - .check = input_curl_check, - .tag = input_curl_tag, - .available = input_curl_available, - .read = input_curl_read, - .eof = input_curl_eof, - .seek = input_curl_seek, -}; diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h deleted file mode 100644 index c6e71bf40..000000000 --- a/src/input/curl_input_plugin.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_CURL_H -#define MPD_INPUT_CURL_H - -struct input_stream; - -extern const struct input_plugin input_plugin_curl; - -#endif diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c deleted file mode 100644 index 200a0afd6..000000000 --- a/src/input/despotify_input_plugin.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/despotify_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "tag.h" -#include "despotify_utils.h" - -#include <glib.h> - -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <despotify.h> - -#include <stdio.h> - -struct input_despotify { - struct input_stream base; - - struct despotify_session *session; - struct ds_track *track; - struct tag *tag; - struct ds_pcm_data pcm; - size_t len_available; - bool eof; -}; - - -static void -refill_buffer(struct input_despotify *ctx) -{ - /* Wait until there is data */ - while (1) { - int rc = despotify_get_pcm(ctx->session, &ctx->pcm); - - if (rc == 0 && ctx->pcm.len) { - ctx->len_available = ctx->pcm.len; - break; - } - if (ctx->eof == true) - break; - - if (rc < 0) { - g_debug("despotify_get_pcm error\n"); - ctx->eof = true; - break; - } - - /* Wait a while until next iteration */ - usleep(50 * 1000); - } -} - -static void callback(G_GNUC_UNUSED struct despotify_session* ds, - int sig, G_GNUC_UNUSED void* data, void* callback_data) -{ - struct input_despotify *ctx = (struct input_despotify *)callback_data; - - switch (sig) { - case DESPOTIFY_NEW_TRACK: - break; - - case DESPOTIFY_TIME_TELL: - break; - - case DESPOTIFY_TRACK_PLAY_ERROR: - g_debug("Track play error\n"); - ctx->eof = true; - ctx->len_available = 0; - break; - - case DESPOTIFY_END_OF_PLAYLIST: - ctx->eof = true; - g_debug("End of playlist: %d\n", ctx->eof); - break; - } -} - - -static struct input_stream * -input_despotify_open(const char *url, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) -{ - struct input_despotify *ctx; - struct despotify_session *session; - struct ds_link *ds_link; - struct ds_track *track; - - if (!g_str_has_prefix(url, "spt://")) - return NULL; - - session = mpd_despotify_get_session(); - if (!session) - return NULL; - - ds_link = despotify_link_from_uri(url + 6); - if (!ds_link) { - g_debug("Can't find %s\n", url); - return NULL; - } - if (ds_link->type != LINK_TYPE_TRACK) { - despotify_free_link(ds_link); - return NULL; - } - - ctx = g_new(struct input_despotify, 1); - memset(ctx, 0, sizeof(*ctx)); - - track = despotify_link_get_track(session, ds_link); - despotify_free_link(ds_link); - if (!track) { - g_free(ctx); - return NULL; - } - - input_stream_init(&ctx->base, &input_plugin_despotify, url, - mutex, cond); - ctx->session = session; - ctx->track = track; - ctx->tag = mpd_despotify_tag_from_track(track); - ctx->eof = false; - /* Despotify outputs pcm data */ - ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); - ctx->base.ready = true; - - if (!mpd_despotify_register_callback(callback, ctx)) { - despotify_free_link(ds_link); - - return NULL; - } - - if (despotify_play(ctx->session, ctx->track, false) == false) { - despotify_free_track(ctx->track); - g_free(ctx); - return NULL; - } - - return &ctx->base; -} - -static size_t -input_despotify_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - size_t to_cpy = size; - - if (ctx->len_available == 0) - refill_buffer(ctx); - - if (ctx->len_available < size) - to_cpy = ctx->len_available; - memcpy(ptr, ctx->pcm.buf, to_cpy); - ctx->len_available -= to_cpy; - - is->offset += to_cpy; - - return to_cpy; -} - -static void -input_despotify_close(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - - if (ctx->tag != NULL) - tag_free(ctx->tag); - - mpd_despotify_unregister_callback(callback); - despotify_free_track(ctx->track); - input_stream_deinit(&ctx->base); - g_free(ctx); -} - -static bool -input_despotify_eof(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - - return ctx->eof; -} - -static bool -input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -static struct tag * -input_despotify_tag(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - struct tag *tag = ctx->tag; - - ctx->tag = NULL; - - return tag; -} - -const struct input_plugin input_plugin_despotify = { - .name = "spt", - .open = input_despotify_open, - .close = input_despotify_close, - .read = input_despotify_read, - .eof = input_despotify_eof, - .seek = input_despotify_seek, - .tag = input_despotify_tag, -}; diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h deleted file mode 100644 index 4c070d882..000000000 --- a/src/input/despotify_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef INPUT_DESPOTIFY_H -#define INPUT_DESPOTIFY_H - -extern const struct input_plugin input_plugin_despotify; - -#endif diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c deleted file mode 100644 index 6d339a067..000000000 --- a/src/input/ffmpeg_input_plugin.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/ffmpeg_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" - -#include <libavutil/avutil.h> -#include <libavformat/avio.h> -#include <libavformat/avformat.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_ffmpeg" - -struct input_ffmpeg { - struct input_stream base; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - AVIOContext *h; -#else - URLContext *h; -#endif - - bool eof; -}; - -static inline GQuark -ffmpeg_quark(void) -{ - return g_quark_from_static_string("ffmpeg"); -} - -static inline bool -input_ffmpeg_supported(void) -{ -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - void *opaque = NULL; - return avio_enum_protocols(&opaque, 0) != NULL; -#else - return av_protocol_next(NULL) != NULL; -#endif -} - -static bool -input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - av_register_all(); - - /* disable this plugin if there's no registered protocol */ - if (!input_ffmpeg_supported()) { - g_set_error(error_r, ffmpeg_quark(), 0, - "No protocol"); - return false; - } - - return true; -} - -static struct input_stream * -input_ffmpeg_open(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_ffmpeg *i; - - if (!g_str_has_prefix(uri, "gopher://") && - !g_str_has_prefix(uri, "rtp://") && - !g_str_has_prefix(uri, "rtsp://") && - !g_str_has_prefix(uri, "rtmp://") && - !g_str_has_prefix(uri, "rtmpt://") && - !g_str_has_prefix(uri, "rtmps://")) - return NULL; - - i = g_new(struct input_ffmpeg, 1); - input_stream_init(&i->base, &input_plugin_ffmpeg, uri, - mutex, cond); - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0) - int ret = avio_open(&i->h, uri, AVIO_FLAG_READ); -#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_open(&i->h, uri, AVIO_RDONLY); -#else - int ret = url_open(&i->h, uri, URL_RDONLY); -#endif - if (ret != 0) { - g_free(i); - g_set_error(error_r, ffmpeg_quark(), ret, - "libavformat failed to open the URI"); - return NULL; - } - - i->eof = false; - - i->base.ready = true; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0; - i->base.size = avio_size(i->h); -#else - i->base.seekable = !i->h->is_streamed; - i->base.size = url_filesize(i->h); -#endif - - /* hack to make MPD select the "ffmpeg" decoder plugin - since - avio.h doesn't tell us the MIME type of the resource, we - can't select a decoder plugin, but the "ffmpeg" plugin is - quite good at auto-detection */ - i->base.mime = g_strdup("audio/x-mpd-ffmpeg"); - - return &i->base; -} - -static size_t -input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_read(i->h, ptr, size); -#else - int ret = url_read(i->h, ptr, size); -#endif - if (ret <= 0) { - if (ret < 0) - g_set_error(error_r, ffmpeg_quark(), 0, - "url_read() failed"); - - i->eof = true; - return false; - } - - is->offset += ret; - return (size_t)ret; -} - -static void -input_ffmpeg_close(struct input_stream *is) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - avio_close(i->h); -#else - url_close(i->h); -#endif - input_stream_deinit(&i->base); - g_free(i); -} - -static bool -input_ffmpeg_eof(struct input_stream *is) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; - - return i->eof; -} - -static bool -input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, - G_GNUC_UNUSED GError **error_r) -{ - struct input_ffmpeg *i = (struct input_ffmpeg *)is; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int64_t ret = avio_seek(i->h, offset, whence); -#else - int64_t ret = url_seek(i->h, offset, whence); -#endif - - if (ret >= 0) { - i->eof = false; - return true; - } else { - g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed"); - return false; - } -} - -const struct input_plugin input_plugin_ffmpeg = { - .name = "ffmpeg", - .init = input_ffmpeg_init, - .open = input_ffmpeg_open, - .close = input_ffmpeg_close, - .read = input_ffmpeg_read, - .eof = input_ffmpeg_eof, - .seek = input_ffmpeg_seek, -}; diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h deleted file mode 100644 index 393836ca5..000000000 --- a/src/input/ffmpeg_input_plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_FFMPEG_INPUT_PLUGIN_H -#define MPD_FFMPEG_INPUT_PLUGIN_H - -/** - * An input plugin based on libavformat's "avio" library. - */ -extern const struct input_plugin input_plugin_ffmpeg; - -#endif diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c deleted file mode 100644 index 5ee3f200b..000000000 --- a/src/input/file_input_plugin.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" /* must be first for large file support */ -#include "input/file_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "fd_util.h" -#include "open.h" - -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_file" - -struct file_input_stream { - struct input_stream base; - - int fd; -}; - -static inline GQuark -file_quark(void) -{ - return g_quark_from_static_string("file"); -} - -static struct input_stream * -input_file_open(const char *filename, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - int fd, ret; - struct stat st; - struct file_input_stream *fis; - - if (!g_path_is_absolute(filename)) - return NULL; - - fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); - if (fd < 0) { - if (errno != ENOENT && errno != ENOTDIR) - g_set_error(error_r, file_quark(), errno, - "Failed to open \"%s\": %s", - filename, g_strerror(errno)); - return NULL; - } - - ret = fstat(fd, &st); - if (ret < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to stat \"%s\": %s", - filename, g_strerror(errno)); - close(fd); - return NULL; - } - - if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, file_quark(), 0, - "Not a regular file: %s", filename); - close(fd); - return NULL; - } - -#ifdef POSIX_FADV_SEQUENTIAL - posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); -#endif - - fis = g_new(struct file_input_stream, 1); - input_stream_init(&fis->base, &input_plugin_file, filename, - mutex, cond); - - fis->base.size = st.st_size; - fis->base.seekable = true; - fis->base.ready = true; - - fis->fd = fd; - - return &fis->base; -} - -static bool -input_file_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - - offset = (goffset)lseek(fis->fd, (off_t)offset, whence); - if (offset < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to seek: %s", g_strerror(errno)); - return false; - } - - is->offset = offset; - return true; -} - -static size_t -input_file_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - ssize_t nbytes; - - nbytes = read(fis->fd, ptr, size); - if (nbytes < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to read: %s", g_strerror(errno)); - return 0; - } - - is->offset += nbytes; - return (size_t)nbytes; -} - -static void -input_file_close(struct input_stream *is) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - - close(fis->fd); - input_stream_deinit(&fis->base); - g_free(fis); -} - -static bool -input_file_eof(struct input_stream *is) -{ - return is->offset >= is->size; -} - -const struct input_plugin input_plugin_file = { - .name = "file", - .open = input_file_open, - .close = input_file_close, - .read = input_file_read, - .eof = input_file_eof, - .seek = input_file_seek, -}; diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h deleted file mode 100644 index f24769d57..000000000 --- a/src/input/file_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_FILE_H -#define MPD_INPUT_FILE_H - -extern const struct input_plugin input_plugin_file; - -#endif diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c deleted file mode 100644 index cff15125b..000000000 --- a/src/input/mms_input_plugin.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/mms_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" - -#include <glib.h> -#include <libmms/mmsx.h> - -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_mms" - -struct input_mms { - struct input_stream base; - - mmsx_t *mms; - - bool eof; -}; - -static inline GQuark -mms_quark(void) -{ - return g_quark_from_static_string("mms"); -} - -static struct input_stream * -input_mms_open(const char *url, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_mms *m; - - if (!g_str_has_prefix(url, "mms://") && - !g_str_has_prefix(url, "mmsh://") && - !g_str_has_prefix(url, "mmst://") && - !g_str_has_prefix(url, "mmsu://")) - return NULL; - - m = g_new(struct input_mms, 1); - input_stream_init(&m->base, &input_plugin_mms, url, - mutex, cond); - - m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); - if (m->mms == NULL) { - g_free(m); - g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); - return NULL; - } - - m->eof = false; - - /* XX is this correct? at least this selects the ffmpeg - decoder, which seems to work fine*/ - m->base.mime = g_strdup("audio/x-ms-wma"); - - m->base.ready = true; - - return &m->base; -} - -static size_t -input_mms_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_mms *m = (struct input_mms *)is; - int ret; - - ret = mmsx_read(NULL, m->mms, ptr, size); - if (ret <= 0) { - if (ret < 0) { - g_set_error(error_r, mms_quark(), errno, - "mmsx_read() failed: %s", - g_strerror(errno)); - } - - m->eof = true; - return false; - } - - is->offset += ret; - - return (size_t)ret; -} - -static void -input_mms_close(struct input_stream *is) -{ - struct input_mms *m = (struct input_mms *)is; - - mmsx_close(m->mms); - input_stream_deinit(&m->base); - g_free(m); -} - -static bool -input_mms_eof(struct input_stream *is) -{ - struct input_mms *m = (struct input_mms *)is; - - return m->eof; -} - -static bool -input_mms_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -const struct input_plugin input_plugin_mms = { - .name = "mms", - .open = input_mms_open, - .close = input_mms_close, - .read = input_mms_read, - .eof = input_mms_eof, - .seek = input_mms_seek, -}; diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c deleted file mode 100644 index cf06fc57b..000000000 --- a/src/input/rewind_input_plugin.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/rewind_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_rewind" - -struct input_rewind { - struct input_stream base; - - struct input_stream *input; - - /** - * The read position within the buffer. Undefined as long as - * reading_from_buffer() returns false. - */ - size_t head; - - /** - * The write/append position within the buffer. - */ - size_t tail; - - /** - * The size of this buffer is the maximum number of bytes - * which can be rewinded cheaply without passing the "seek" - * call to CURL. - * - * The origin of this buffer is always the beginning of the - * stream (offset 0). - */ - char buffer[64 * 1024]; -}; - -/** - * Are we currently reading from the buffer, and does the buffer - * contain more data for the next read operation? - */ -static bool -reading_from_buffer(const struct input_rewind *r) -{ - return r->tail > 0 && r->base.offset < r->input->offset; -} - -/** - * Copy public attributes from the underlying input stream to the - * "rewind" input stream. This function is called when a method of - * the underlying stream has returned, which may have modified these - * attributes. - */ -static void -copy_attributes(struct input_rewind *r) -{ - struct input_stream *dest = &r->base; - const struct input_stream *src = r->input; - - assert(dest != src); - assert(src->mime == NULL || dest->mime != src->mime); - - bool dest_ready = dest->ready; - - dest->ready = src->ready; - dest->seekable = src->seekable; - dest->size = src->size; - dest->offset = src->offset; - - if (!dest_ready && src->ready) { - g_free(dest->mime); - dest->mime = g_strdup(src->mime); - } -} - -static void -input_rewind_close(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - input_stream_close(r->input); - - input_stream_deinit(&r->base); - g_free(r); -} - -static bool -input_rewind_check(struct input_stream *is, GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_check(r->input, error_r); -} - -static void -input_rewind_update(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - if (!reading_from_buffer(r)) - copy_attributes(r); -} - -static struct tag * -input_rewind_tag(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_tag(r->input); -} - -static bool -input_rewind_available(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_available(r->input); -} - -static size_t -input_rewind_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - if (reading_from_buffer(r)) { - /* buffered read */ - - assert(r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - if (size > r->tail - r->head) - size = r->tail - r->head; - - memcpy(ptr, r->buffer + r->head, size); - r->head += size; - is->offset += size; - - return size; - } else { - /* pass method call to underlying stream */ - - size_t nbytes = input_stream_read(r->input, ptr, size, error_r); - - if (r->input->offset > (goffset)sizeof(r->buffer)) - /* disable buffering */ - r->tail = 0; - else if (r->tail == (size_t)is->offset) { - /* append to buffer */ - - memcpy(r->buffer + r->tail, ptr, nbytes); - r->tail += nbytes; - - assert(r->tail == (size_t)r->input->offset); - } - - copy_attributes(r); - - return nbytes; - } -} - -static bool -input_rewind_eof(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return !reading_from_buffer(r) && input_stream_eof(r->input); -} - -static bool -input_rewind_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - assert(is->ready); - - if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { - /* buffered seek */ - - assert(!reading_from_buffer(r) || - r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - r->head = (size_t)offset; - is->offset = offset; - - return true; - } else { - bool success = input_stream_seek(r->input, offset, whence, - error_r); - copy_attributes(r); - - /* disable the buffer, because r->input has left the - buffered range now */ - r->tail = 0; - - return success; - } -} - -static const struct input_plugin rewind_input_plugin = { - .close = input_rewind_close, - .check = input_rewind_check, - .update = input_rewind_update, - .tag = input_rewind_tag, - .available = input_rewind_available, - .read = input_rewind_read, - .eof = input_rewind_eof, - .seek = input_rewind_seek, -}; - -struct input_stream * -input_rewind_open(struct input_stream *is) -{ - struct input_rewind *c; - - assert(is != NULL); - assert(is->offset == 0); - - if (is->seekable) - /* seekable resources don't need this plugin */ - return is; - - c = g_new(struct input_rewind, 1); - input_stream_init(&c->base, &rewind_input_plugin, is->uri, - is->mutex, is->cond); - c->tail = 0; - c->input = is; - - return &c->base; -} diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h deleted file mode 100644 index 83abe257a..000000000 --- a/src/input/rewind_input_plugin.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -/** \file - * - * A wrapper for an input_stream object which allows cheap buffered - * rewinding. This is useful while detecting the stream codec (let - * each decoder plugin peek a portion from the stream). - */ - -#ifndef MPD_INPUT_REWIND_H -#define MPD_INPUT_REWIND_H - -#include "check.h" - -struct input_stream; - -struct input_stream * -input_rewind_open(struct input_stream *is); - -#endif diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c deleted file mode 100644 index fc903b48c..000000000 --- a/src/input/soup_input_plugin.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input/soup_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "io_thread.h" -#include "conf.h" - -#include <libsoup/soup-uri.h> -#include <libsoup/soup-session-async.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_soup" - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t SOUP_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t SOUP_RESUME_AT = 384 * 1024; - -static SoupURI *soup_proxy; -static SoupSession *soup_session; - -struct input_soup { - struct input_stream base; - - SoupMessage *msg; - - GQueue *buffers; - - size_t current_consumed; - - size_t total_buffered; - - bool alive, pause, eof; - - /** - * Set when the session callback has been invoked, when it is - * safe to free this object. - */ - bool completed; - - GError *postponed_error; -}; - -static inline GQuark -soup_quark(void) -{ - return g_quark_from_static_string("soup"); -} - -static bool -input_soup_init(const struct config_param *param, GError **error_r) -{ - assert(soup_proxy == NULL); - assert(soup_session == NULL); - - g_type_init(); - - const char *proxy = config_get_block_string(param, "proxy", NULL); - - if (proxy != NULL) { - soup_proxy = soup_uri_new(proxy); - if (soup_proxy == NULL) { - g_set_error(error_r, soup_quark(), 0, - "failed to parse proxy setting"); - return false; - } - } - - soup_session = - soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, - soup_proxy, - SOUP_SESSION_ASYNC_CONTEXT, - io_thread_context(), - NULL); - - return true; -} - -static void -input_soup_finish(void) -{ - assert(soup_session != NULL); - - soup_session_abort(soup_session); - g_object_unref(G_OBJECT(soup_session)); - - if (soup_proxy != NULL) - soup_uri_free(soup_proxy); -} - -/** - * Copy the error from the SoupMessage object to - * input_soup::postponed_error. - * - * @return true if there was no error - */ -static bool -input_soup_copy_error(struct input_soup *s, const SoupMessage *msg) -{ - if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) - return true; - - if (msg->status_code == SOUP_STATUS_CANCELLED) - /* failure, but don't generate a GError, because this - status was caused by _close() */ - return false; - - if (s->postponed_error != NULL) - /* there's already a GError, don't overwrite it */ - return false; - - if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, - "HTTP client error: %s", - msg->reason_phrase); - else - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, - "got HTTP status: %d %s", - msg->status_code, msg->reason_phrase); - - return false; -} - -static void -input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, - SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - assert(!s->completed); - - g_mutex_lock(s->base.mutex); - - if (!s->base.ready) - input_soup_copy_error(s, msg); - - s->base.ready = true; - s->alive = false; - s->completed = true; - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static void -input_soup_got_headers(SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - g_mutex_lock(s->base.mutex); - - if (!input_soup_copy_error(s, msg)) { - g_mutex_unlock(s->base.mutex); - - soup_session_cancel_message(soup_session, msg, - SOUP_STATUS_CANCELLED); - return; - } - - s->base.ready = true; - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); - - soup_message_body_set_accumulate(msg->response_body, false); -} - -static void -input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - - g_mutex_lock(s->base.mutex); - - g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); - s->total_buffered += chunk->length; - - if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { - s->pause = true; - soup_session_pause_message(soup_session, msg); - } - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static void -input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - - g_mutex_lock(s->base.mutex); - - s->base.ready = true; - s->eof = true; - s->alive = false; - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static bool -input_soup_wait_data(struct input_soup *s) -{ - while (true) { - if (s->eof) - return true; - - if (!s->alive) - return false; - - if (!g_queue_is_empty(s->buffers)) - return true; - - assert(s->current_consumed == 0); - - g_cond_wait(s->base.cond, s->base.mutex); - } -} - -static gpointer -input_soup_queue(gpointer data) -{ - struct input_soup *s = data; - - soup_session_queue_message(soup_session, s->msg, - input_soup_session_callback, s); - - return NULL; -} - -static struct input_stream * -input_soup_open(const char *uri, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) -{ - if (strncmp(uri, "http://", 7) != 0) - return NULL; - - struct input_soup *s = g_new(struct input_soup, 1); - input_stream_init(&s->base, &input_plugin_soup, uri, - mutex, cond); - - s->buffers = g_queue_new(); - s->current_consumed = 0; - s->total_buffered = 0; - -#if GCC_CHECK_VERSION(4,6) -#pragma GCC diagnostic push - /* the libsoup macro SOUP_METHOD_GET discards the "const" - attribute of the g_intern_static_string() return value; - don't make the gcc warning fatal: */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - - s->msg = soup_message_new(SOUP_METHOD_GET, uri); - -#if GCC_CHECK_VERSION(4,6) -#pragma GCC diagnostic pop -#endif - - soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT); - - soup_message_headers_append(s->msg->request_headers, "User-Agent", - "Music Player Daemon " VERSION); - - g_signal_connect(s->msg, "got-headers", - G_CALLBACK(input_soup_got_headers), s); - g_signal_connect(s->msg, "got-chunk", - G_CALLBACK(input_soup_got_chunk), s); - g_signal_connect(s->msg, "got-body", - G_CALLBACK(input_soup_got_body), s); - - s->alive = true; - s->pause = false; - s->eof = false; - s->completed = false; - s->postponed_error = NULL; - - io_thread_call(input_soup_queue, s); - - return &s->base; -} - -static gpointer -input_soup_cancel(gpointer data) -{ - struct input_soup *s = data; - - if (!s->completed) - soup_session_cancel_message(soup_session, s->msg, - SOUP_STATUS_CANCELLED); - - return NULL; -} - -static void -input_soup_close(struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - g_mutex_lock(s->base.mutex); - - if (!s->completed) { - /* the messages's session callback hasn't been invoked - yet; cancel it and wait for completion */ - - g_mutex_unlock(s->base.mutex); - - io_thread_call(input_soup_cancel, s); - - g_mutex_lock(s->base.mutex); - while (!s->completed) - g_cond_wait(s->base.cond, s->base.mutex); - } - - g_mutex_unlock(s->base.mutex); - - SoupBuffer *buffer; - while ((buffer = g_queue_pop_head(s->buffers)) != NULL) - soup_buffer_free(buffer); - g_queue_free(s->buffers); - - if (s->postponed_error != NULL) - g_error_free(s->postponed_error); - - input_stream_deinit(&s->base); - g_free(s); -} - -static bool -input_soup_check(struct input_stream *is, GError **error_r) -{ - struct input_soup *s = (struct input_soup *)is; - - bool success = s->postponed_error == NULL; - if (!success) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; - } - - return success; -} - -static bool -input_soup_available(struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - return s->eof || !s->alive || !g_queue_is_empty(s->buffers); -} - -static size_t -input_soup_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) -{ - struct input_soup *s = (struct input_soup *)is; - - if (!input_soup_wait_data(s)) { - assert(!s->alive); - - if (s->postponed_error != NULL) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; - } else - g_set_error_literal(error_r, soup_quark(), 0, - "HTTP failure"); - return 0; - } - - char *p0 = ptr, *p = p0, *p_end = p0 + size; - - while (p < p_end) { - SoupBuffer *buffer = g_queue_pop_head(s->buffers); - if (buffer == NULL) { - assert(s->current_consumed == 0); - break; - } - - assert(s->current_consumed < buffer->length); - assert(s->total_buffered >= buffer->length); - - const char *q = buffer->data; - q += s->current_consumed; - - size_t remaining = buffer->length - s->current_consumed; - size_t nbytes = p_end - p; - if (nbytes > remaining) - nbytes = remaining; - - memcpy(p, q, nbytes); - p += nbytes; - - s->current_consumed += remaining; - if (s->current_consumed >= buffer->length) { - /* done with this buffer */ - s->total_buffered -= buffer->length; - soup_buffer_free(buffer); - s->current_consumed = 0; - } else { - /* partial read */ - assert(p == p_end); - - g_queue_push_head(s->buffers, buffer); - } - } - - if (s->pause && s->total_buffered < SOUP_RESUME_AT) { - s->pause = false; - soup_session_unpause_message(soup_session, s->msg); - } - - size_t nbytes = p - p0; - s->base.offset += nbytes; - - return nbytes; -} - -static bool -input_soup_eof(G_GNUC_UNUSED struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - return !s->alive && g_queue_is_empty(s->buffers); -} - -const struct input_plugin input_plugin_soup = { - .name = "soup", - .init = input_soup_init, - .finish = input_soup_finish, - - .open = input_soup_open, - .close = input_soup_close, - .check = input_soup_check, - .available = input_soup_available, - .read = input_soup_read, - .eof = input_soup_eof, -}; diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h deleted file mode 100644 index 689b2d971..000000000 --- a/src/input/soup_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_SOUP_H -#define MPD_INPUT_SOUP_H - -extern const struct input_plugin input_plugin_soup; - -#endif diff --git a/src/input_init.c b/src/input_init.c deleted file mode 100644 index 771d648d1..000000000 --- a/src/input_init.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input_init.h" -#include "input_plugin.h" -#include "input_registry.h" -#include "conf.h" - -#include <assert.h> -#include <string.h> - -static inline GQuark -input_quark(void) -{ - return g_quark_from_static_string("input"); -} - -/** - * Find the "input" configuration block for the specified plugin. - * - * @param plugin_name the name of the input plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -input_plugin_config(const char *plugin_name, GError **error_r) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) { - g_set_error(error_r, input_quark(), 0, - "input configuration without 'plugin' name in line %d", - param->line); - return NULL; - } - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -bool -input_stream_global_init(GError **error_r) -{ - GError *error = NULL; - - for (unsigned i = 0; input_plugins[i] != NULL; ++i) { - const struct input_plugin *plugin = input_plugins[i]; - - assert(plugin->name != NULL); - assert(*plugin->name != 0); - assert(plugin->open != NULL); - - const struct config_param *param = - input_plugin_config(plugin->name, &error); - if (param == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (plugin->init == NULL || plugin->init(param, &error)) - input_plugins_enabled[i] = true; - else { - g_propagate_prefixed_error(error_r, error, - "Failed to initialize input plugin '%s': ", - plugin->name); - return false; - } - } - - return true; -} - -void input_stream_global_finish(void) -{ - input_plugins_for_each_enabled(plugin) - if (plugin->finish != NULL) - plugin->finish(); -} diff --git a/src/input_init.h b/src/input_init.h deleted file mode 100644 index ad92cda08..000000000 --- a/src/input_init.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_INIT_H -#define MPD_INPUT_INIT_H - -#include "check.h" - -#include <glib.h> -#include <stdbool.h> - -/** - * Initializes this library and all input_stream implementations. - * - * @param error_r location to store the error occurring, or NULL to - * ignore errors - */ -bool -input_stream_global_init(GError **error_r); - -/** - * Deinitializes this library and all input_stream implementations. - */ -void input_stream_global_finish(void); - -#endif diff --git a/src/input_internal.c b/src/input_internal.c deleted file mode 100644 index 92a71856e..000000000 --- a/src/input_internal.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input_internal.h" -#include "input_stream.h" - -#include <assert.h> - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond) -{ - assert(is != NULL); - assert(plugin != NULL); - assert(uri != NULL); - - is->plugin = plugin; - is->uri = g_strdup(uri); - is->mutex = mutex; - is->cond = cond; - is->ready = false; - is->seekable = false; - is->size = -1; - is->offset = 0; - is->mime = NULL; -} - -void -input_stream_deinit(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - g_free(is->uri); - g_free(is->mime); -} - -void -input_stream_signal_client(struct input_stream *is) -{ - if (is->cond != NULL) - g_cond_broadcast(is->cond); -} - -void -input_stream_set_ready(struct input_stream *is) -{ - g_mutex_lock(is->mutex); - - if (!is->ready) { - is->ready = true; - input_stream_signal_client(is); - } - - g_mutex_unlock(is->mutex); -} diff --git a/src/input_internal.h b/src/input_internal.h deleted file mode 100644 index d95142e46..000000000 --- a/src/input_internal.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_INTERNAL_H -#define MPD_INPUT_INTERNAL_H - -#include "check.h" - -#include <glib.h> - -struct input_stream; -struct input_plugin; - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond); - -void -input_stream_deinit(struct input_stream *is); - -void -input_stream_signal_client(struct input_stream *is); - -void -input_stream_set_ready(struct input_stream *is); - -#endif diff --git a/src/input_plugin.h b/src/input_plugin.h deleted file mode 100644 index 6b0c77c85..000000000 --- a/src/input_plugin.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_PLUGIN_H -#define MPD_INPUT_PLUGIN_H - -#include "input_stream.h" - -#include <stddef.h> -#include <stdbool.h> -#include <sys/types.h> - -struct config_param; -struct input_stream; - -struct input_plugin { - const char *name; - - /** - * Global initialization. This method is called when MPD starts. - * - * @param error_r location to store the error occurring, or - * NULL to ignore errors - * @return true on success, false if the plugin should be - * disabled - */ - bool (*init)(const struct config_param *param, GError **error_r); - - /** - * Global deinitialization. Called once before MPD shuts - * down (only if init() has returned true). - */ - void (*finish)(void); - - struct input_stream *(*open)(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r); - void (*close)(struct input_stream *is); - - /** - * Check for errors that may have occurred in the I/O thread. - * May be unimplemented for synchronous plugins. - * - * @return false on error - */ - bool (*check)(struct input_stream *is, GError **error_r); - - /** - * Update the public attributes. Call before access. Can be - * NULL if the plugin always keeps its attributes up to date. - */ - void (*update)(struct input_stream *is); - - struct tag *(*tag)(struct input_stream *is); - - /** - * Returns true if the next read operation will not block: - * either data is available, or end-of-stream has been - * reached, or an error has occurred. - * - * If this method is unimplemented, then it is assumed that - * reading will never block. - */ - bool (*available)(struct input_stream *is); - - size_t (*read)(struct input_stream *is, void *ptr, size_t size, - GError **error_r); - bool (*eof)(struct input_stream *is); - bool (*seek)(struct input_stream *is, goffset offset, int whence, - GError **error_r); -}; - -#endif diff --git a/src/input_registry.c b/src/input_registry.c deleted file mode 100644 index 5987d5da2..000000000 --- a/src/input_registry.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input_registry.h" -#include "input/file_input_plugin.h" - -#ifdef ENABLE_ARCHIVE -#include "input/archive_input_plugin.h" -#endif - -#ifdef ENABLE_CURL -#include "input/curl_input_plugin.h" -#endif - -#ifdef ENABLE_SOUP -#include "input/soup_input_plugin.h" -#endif - -#ifdef HAVE_FFMPEG -#include "input/ffmpeg_input_plugin.h" -#endif - -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif - -#ifdef ENABLE_CDIO_PARANOIA -#include "input/cdio_paranoia_input_plugin.h" -#endif - -#ifdef ENABLE_DESPOTIFY -#include "input/despotify_input_plugin.h" -#endif - -#include <glib.h> - -const struct input_plugin *const input_plugins[] = { - &input_plugin_file, -#ifdef ENABLE_ARCHIVE - &input_plugin_archive, -#endif -#ifdef ENABLE_CURL - &input_plugin_curl, -#endif -#ifdef ENABLE_SOUP - &input_plugin_soup, -#endif -#ifdef HAVE_FFMPEG - &input_plugin_ffmpeg, -#endif -#ifdef ENABLE_MMS - &input_plugin_mms, -#endif -#ifdef ENABLE_CDIO_PARANOIA - &input_plugin_cdio_paranoia, -#endif -#ifdef ENABLE_DESPOTIFY - &input_plugin_despotify, -#endif - NULL -}; - -bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/input_registry.h b/src/input_registry.h deleted file mode 100644 index 4f5fff8da..000000000 --- a/src/input_registry.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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. - */ - -#ifndef MPD_INPUT_REGISTRY_H -#define MPD_INPUT_REGISTRY_H - -#include "check.h" - -#include <stdbool.h> - -/** - * NULL terminated list of all input plugins which were enabled at - * compile time. - */ -extern const struct input_plugin *const input_plugins[]; - -extern bool input_plugins_enabled[]; - -#define input_plugins_for_each(plugin) \ - for (const struct input_plugin *plugin, \ - *const*input_plugin_iterator = &input_plugins[0]; \ - (plugin = *input_plugin_iterator) != NULL; \ - ++input_plugin_iterator) - -#define input_plugins_for_each_enabled(plugin) \ - input_plugins_for_each(plugin) \ - if (input_plugins_enabled[input_plugin_iterator - input_plugins]) - -#endif diff --git a/src/input_stream.c b/src/input_stream.c deleted file mode 100644 index e445dca6c..000000000 --- a/src/input_stream.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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 "config.h" -#include "input_stream.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "input/rewind_input_plugin.h" - -#include <glib.h> -#include <assert.h> - -static inline GQuark -input_quark(void) -{ - return g_quark_from_static_string("input"); -} - -struct input_stream * -input_stream_open(const char *url, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - GError *error = NULL; - - assert(mutex != NULL); - assert(error_r == NULL || *error_r == NULL); - - input_plugins_for_each_enabled(plugin) { - struct input_stream *is; - - is = plugin->open(url, mutex, cond, &error); - if (is != NULL) { - assert(is->plugin != NULL); - assert(is->plugin->close != NULL); - assert(is->plugin->read != NULL); - assert(is->plugin->eof != NULL); - assert(!is->seekable || is->plugin->seek != NULL); - - is = input_rewind_open(is); - - return is; - } else if (error != NULL) { - g_propagate_error(error_r, error); - return NULL; - } - } - - g_set_error(error_r, input_quark(), 0, "Unrecognized URI"); - return NULL; -} - -bool -input_stream_check(struct input_stream *is, GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->check == NULL || - is->plugin->check(is, error_r); -} - -void -input_stream_update(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->update != NULL) - is->plugin->update(is); -} - -void -input_stream_wait_ready(struct input_stream *is) -{ - assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - - while (true) { - input_stream_update(is); - if (is->ready) - break; - - g_cond_wait(is->cond, is->mutex); - } -} - -void -input_stream_lock_wait_ready(struct input_stream *is) -{ - assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - - g_mutex_lock(is->mutex); - input_stream_wait_ready(is); - g_mutex_unlock(is->mutex); -} - -bool -input_stream_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->seek == NULL) - return false; - - return is->plugin->seek(is, offset, whence, error_r); -} - -bool -input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->seek == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_seek(is, offset, whence, error_r); - - g_mutex_lock(is->mutex); - bool success = input_stream_seek(is, offset, whence, error_r); - g_mutex_unlock(is->mutex); - return success; -} - -struct tag * -input_stream_tag(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->tag != NULL - ? is->plugin->tag(is) - : NULL; -} - -struct tag * -input_stream_lock_tag(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->tag == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_tag(is); - - g_mutex_lock(is->mutex); - struct tag *tag = input_stream_tag(is); - g_mutex_unlock(is->mutex); - return tag; -} - -bool -input_stream_available(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->available != NULL - ? is->plugin->available(is) - : true; -} - -size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - assert(ptr != NULL); - assert(size > 0); - - return is->plugin->read(is, ptr, size, error_r); -} - -size_t -input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - assert(ptr != NULL); - assert(size > 0); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_read(is, ptr, size, error_r); - - g_mutex_lock(is->mutex); - size_t nbytes = input_stream_read(is, ptr, size, error_r); - g_mutex_unlock(is->mutex); - return nbytes; -} - -void input_stream_close(struct input_stream *is) -{ - is->plugin->close(is); -} - -bool input_stream_eof(struct input_stream *is) -{ - return is->plugin->eof(is); -} - -bool -input_stream_lock_eof(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_eof(is); - - g_mutex_lock(is->mutex); - bool eof = input_stream_eof(is); - g_mutex_unlock(is->mutex); - return eof; -} - diff --git a/src/input_stream.h b/src/input_stream.h index 10ad97161..24dda1eee 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -29,64 +29,13 @@ #include <stdbool.h> #include <sys/types.h> -struct input_stream { - /** - * the plugin which implements this input stream - */ - const struct input_plugin *plugin; +struct input_stream; - /** - * The absolute URI which was used to open this stream. May - * be NULL if this is unknown. - */ - char *uri; +#ifdef __cplusplus +extern "C" { - /** - * A mutex that protects the mutable attributes of this object - * and its implementation. It must be locked before calling - * any of the public methods. - * - * This object is allocated by the client, and the client is - * responsible for freeing it. - */ - GMutex *mutex; - - /** - * A cond that gets signalled when the state of this object - * changes from the I/O thread. The client of this object may - * wait on it. Optional, may be NULL. - * - * This object is allocated by the client, and the client is - * responsible for freeing it. - */ - GCond *cond; - - /** - * indicates whether the stream is ready for reading and - * whether the other attributes in this struct are valid - */ - bool ready; - - /** - * if true, then the stream is fully seekable - */ - bool seekable; - - /** - * the size of the resource, or -1 if unknown - */ - goffset size; - - /** - * the current offset within the stream - */ - goffset offset; - - /** - * the MIME content type of the resource, or NULL if unknown - */ - char *mime; -}; +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" /** * Opens a new input stream. You may not access it until the "ready" @@ -99,13 +48,15 @@ struct input_stream { * notifications * @return an #input_stream object on success, NULL on error */ -gcc_nonnull(1, 2) +gcc_nonnull(1) G_GNUC_MALLOC struct input_stream * input_stream_open(const char *uri, - GMutex *mutex, GCond *cond, + Mutex &mutex, Cond &cond, GError **error_r); +#endif + /** * Close the input stream and free resources. * @@ -115,20 +66,6 @@ gcc_nonnull(1) void input_stream_close(struct input_stream *is); -gcc_nonnull(1) -static inline void -input_stream_lock(struct input_stream *is) -{ - g_mutex_lock(is->mutex); -} - -gcc_nonnull(1) -static inline void -input_stream_unlock(struct input_stream *is) -{ - g_mutex_unlock(is->mutex); -} - /** * Check for errors that may have occurred in the I/O thread. * @@ -163,6 +100,33 @@ gcc_nonnull(1) void input_stream_lock_wait_ready(struct input_stream *is); +gcc_nonnull_all gcc_pure +const char * +input_stream_get_mime_type(const struct input_stream *is); + +gcc_nonnull_all +void +input_stream_override_mime_type(struct input_stream *is, const char *mime); + +gcc_nonnull_all gcc_pure +goffset +input_stream_get_size(const struct input_stream *is); + +gcc_nonnull_all gcc_pure +goffset +input_stream_get_offset(const struct input_stream *is); + +gcc_nonnull_all gcc_pure +bool +input_stream_is_seekable(const struct input_stream *is); + +/** + * Determines whether seeking is cheap. This is true for local files. + */ +gcc_pure gcc_nonnull(1) +bool +input_stream_cheap_seeking(const struct input_stream *is); + /** * Seeks to the specified position in the stream. This will most * likely fail if the "seekable" flag is false. @@ -264,4 +228,8 @@ size_t input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif |