input/curl: move code to CurlInputStream methods

This commit is contained in:
Max Kellermann 2014-03-15 20:40:43 +01:00
parent e9f16fca96
commit 23eacbd132
1 changed files with 232 additions and 179 deletions

View File

@ -177,6 +177,63 @@ struct CurlInputStream {
CurlInputStream(const CurlInputStream &) = delete; CurlInputStream(const CurlInputStream &) = delete;
CurlInputStream &operator=(const CurlInputStream &) = delete; CurlInputStream &operator=(const CurlInputStream &) = delete;
bool Check(Error &error);
bool IsEOF() const {
return easy == nullptr && buffers.empty();
}
Tag *ReadTag();
bool IsAvailable() const {
return postponed_error.IsDefined() || easy == nullptr ||
!buffers.empty();
}
size_t Read(void *ptr, size_t size, Error &error);
/**
* Frees the current "libcurl easy" handle, and everything
* associated with it.
*
* Runs in the I/O thread.
*/
void FreeEasy();
/**
* Frees the current "libcurl easy" handle, and everything associated
* with it.
*
* The mutex must not be locked.
*/
void FreeEasyIndirect();
void HeaderReceived(const char *name,
const char *value, const char *end);
size_t DataReceived(const void *ptr, size_t size);
void Resume();
bool FillBuffer(Error &error);
/**
* Determine the total sizes of all buffers, including
* portions that have already been consumed.
*
* The caller must lock the mutex.
*/
gcc_pure
size_t GetTotalBufferSize() const;
void CopyIcyTag();
/**
* A HTTP request is finished.
*
* Runs in the I/O thread. The caller must not hold locks.
*/
void RequestDone(CURLcode result, long status);
}; };
class CurlMulti; class CurlMulti;
@ -335,14 +392,14 @@ input_curl_find_request(CURL *easy)
return (CurlInputStream *)p; return (CurlInputStream *)p;
} }
static void inline void
input_curl_resume(CurlInputStream *c) CurlInputStream::Resume()
{ {
assert(io_thread_inside()); assert(io_thread_inside());
if (c->paused) { if (paused) {
c->paused = false; paused = false;
curl_easy_pause(c->easy, CURLPAUSE_CONT); curl_easy_pause(easy, CURLPAUSE_CONT);
if (curl_version_num < 0x072000) if (curl_version_num < 0x072000)
/* libcurl older than 7.32.0 does not update /* libcurl older than 7.32.0 does not update
@ -445,74 +502,55 @@ CurlMulti::Remove(CurlInputStream *c)
curl_multi_remove_handle(multi, c->easy); curl_multi_remove_handle(multi, c->easy);
} }
/** void
* Frees the current "libcurl easy" handle, and everything associated CurlInputStream::FreeEasy()
* with it.
*
* Runs in the I/O thread.
*/
static void
input_curl_easy_free(CurlInputStream *c)
{ {
assert(io_thread_inside()); assert(io_thread_inside());
assert(c != nullptr);
if (c->easy == nullptr) if (easy == nullptr)
return; return;
curl_multi->Remove(c); curl_multi->Remove(this);
curl_easy_cleanup(c->easy); curl_easy_cleanup(easy);
c->easy = nullptr; easy = nullptr;
curl_slist_free_all(c->request_headers); curl_slist_free_all(request_headers);
c->request_headers = nullptr; request_headers = nullptr;
} }
/** void
* Frees the current "libcurl easy" handle, and everything associated CurlInputStream::FreeEasyIndirect()
* with it.
*
* The mutex must not be locked.
*/
static void
input_curl_easy_free_indirect(CurlInputStream *c)
{ {
BlockingCall(io_thread_get(), [c](){ BlockingCall(io_thread_get(), [this](){
input_curl_easy_free(c); FreeEasy();
curl_multi->InvalidateSockets(); curl_multi->InvalidateSockets();
}); });
assert(c->easy == nullptr); assert(easy == nullptr);
} }
/** inline void
* A HTTP request is finished. CurlInputStream::RequestDone(CURLcode result, long status)
*
* Runs in the I/O thread. The caller must not hold locks.
*/
static void
input_curl_request_done(CurlInputStream *c, CURLcode result, long status)
{ {
assert(io_thread_inside()); assert(io_thread_inside());
assert(c != nullptr); assert(!postponed_error.IsDefined());
assert(c->easy == nullptr);
assert(!c->postponed_error.IsDefined());
const ScopeLock protect(c->base.mutex); FreeEasy();
const ScopeLock protect(base.mutex);
if (result != CURLE_OK) { if (result != CURLE_OK) {
c->postponed_error.Format(curl_domain, result, postponed_error.Format(curl_domain, result,
"curl failed: %s", c->error_buffer); "curl failed: %s", error_buffer);
} else if (status < 200 || status >= 300) { } else if (status < 200 || status >= 300) {
c->postponed_error.Format(http_domain, status, postponed_error.Format(http_domain, status,
"got HTTP status %ld", "got HTTP status %ld",
status); status);
} }
c->base.ready = true; base.ready = true;
base.cond.broadcast();
c->base.cond.broadcast();
} }
static void static void
@ -524,8 +562,7 @@ input_curl_handle_done(CURL *easy_handle, CURLcode result)
long status = 0; long status = 0;
curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
input_curl_easy_free(c); c->RequestDone(result, status);
input_curl_request_done(c, result, status);
} }
void void
@ -656,19 +693,11 @@ input_curl_finish(void)
curl_global_cleanup(); curl_global_cleanup();
} }
/** size_t
* Determine the total sizes of all buffers, including portions that CurlInputStream::GetTotalBufferSize() const
* have already been consumed.
*
* The caller must lock the mutex.
*/
gcc_pure
static size_t
curl_total_buffer_size(const CurlInputStream *c)
{ {
size_t total = 0; size_t total = 0;
for (const auto &i : buffers)
for (const auto &i : c->buffers)
total += i.TotalSize(); total += i.TotalSize();
return total; return total;
@ -678,46 +707,56 @@ CurlInputStream::~CurlInputStream()
{ {
delete tag; delete tag;
input_curl_easy_free_indirect(this); FreeEasyIndirect();
} }
static bool inline bool
input_curl_check(InputStream *is, Error &error) CurlInputStream::Check(Error &error)
{ {
CurlInputStream *c = (CurlInputStream *)is; bool success = !postponed_error.IsDefined();
bool success = !c->postponed_error.IsDefined();
if (!success) { if (!success) {
error = std::move(c->postponed_error); error = std::move(postponed_error);
c->postponed_error.Clear(); postponed_error.Clear();
} }
return success; return success;
} }
static bool
input_curl_check(InputStream *is, Error &error)
{
CurlInputStream &c = *(CurlInputStream *)is;
return c.Check(error);
}
inline Tag *
CurlInputStream::ReadTag()
{
Tag *result = tag;
tag = nullptr;
return result;
}
static Tag * static Tag *
input_curl_tag(InputStream *is) input_curl_tag(InputStream *is)
{ {
CurlInputStream *c = (CurlInputStream *)is; CurlInputStream &c = *(CurlInputStream *)is;
Tag *tag = c->tag; return c.ReadTag();
c->tag = nullptr;
return tag;
} }
static bool inline bool
fill_buffer(CurlInputStream *c, Error &error) CurlInputStream::FillBuffer(Error &error)
{ {
while (c->easy != nullptr && c->buffers.empty()) while (easy != nullptr && buffers.empty())
c->base.cond.wait(c->base.mutex); base.cond.wait(base.mutex);
if (c->postponed_error.IsDefined()) { if (postponed_error.IsDefined()) {
error = std::move(c->postponed_error); error = std::move(postponed_error);
c->postponed_error.Clear(); postponed_error.Clear();
return false; return false;
} }
return !c->buffers.empty(); return !buffers.empty();
} }
static size_t static size_t
@ -770,54 +809,47 @@ read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers,
return nbytes; return nbytes;
} }
static void inline void
copy_icy_tag(CurlInputStream *c) CurlInputStream::CopyIcyTag()
{ {
Tag *tag = c->icy.ReadTag(); Tag *new_tag = icy.ReadTag();
if (new_tag == nullptr)
if (tag == nullptr)
return; return;
delete c->tag; delete tag;
if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) { if (!meta_name.empty() && !new_tag->HasType(TAG_NAME)) {
TagBuilder tag_builder(std::move(*tag)); TagBuilder tag_builder(std::move(*new_tag));
tag_builder.AddItem(TAG_NAME, c->meta_name.c_str()); tag_builder.AddItem(TAG_NAME, meta_name.c_str());
*tag = tag_builder.Commit(); *new_tag = tag_builder.Commit();
} }
c->tag = tag; tag = new_tag;
} }
static bool static bool
input_curl_available(InputStream *is) input_curl_available(InputStream *is)
{ {
CurlInputStream *c = (CurlInputStream *)is; const CurlInputStream &c = *(const CurlInputStream *)is;
return c.IsAvailable();
return c->postponed_error.IsDefined() || c->easy == nullptr ||
!c->buffers.empty();
} }
static size_t inline size_t
input_curl_read(InputStream *is, void *ptr, size_t size, CurlInputStream::Read(void *ptr, size_t size, Error &error)
Error &error)
{ {
CurlInputStream *c = (CurlInputStream *)is;
bool success;
size_t nbytes = 0; size_t nbytes = 0;
char *dest = (char *)ptr; char *dest = (char *)ptr;
do { do {
/* fill the buffer */ /* fill the buffer */
success = fill_buffer(c, error); if (!FillBuffer(error))
if (!success)
return 0; return 0;
/* send buffer contents */ /* send buffer contents */
while (size > 0 && !c->buffers.empty()) { while (size > 0 && !buffers.empty()) {
size_t copy = read_from_buffer(c->icy, c->buffers, size_t copy = read_from_buffer(icy, buffers,
dest + nbytes, size); dest + nbytes, size);
nbytes += copy; nbytes += copy;
@ -825,24 +857,32 @@ input_curl_read(InputStream *is, void *ptr, size_t size,
} }
} while (nbytes == 0); } while (nbytes == 0);
if (c->icy.IsDefined()) if (icy.IsDefined())
copy_icy_tag(c); CopyIcyTag();
is->offset += (InputPlugin::offset_type)nbytes; base.offset += (InputPlugin::offset_type)nbytes;
if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { if (paused && GetTotalBufferSize() < CURL_RESUME_AT) {
c->base.mutex.unlock(); base.mutex.unlock();
BlockingCall(io_thread_get(), [c](){ BlockingCall(io_thread_get(), [this](){
input_curl_resume(c); Resume();
}); });
c->base.mutex.lock(); base.mutex.lock();
} }
return nbytes; return nbytes;
} }
static size_t
input_curl_read(InputStream *is, void *ptr, size_t size,
Error &error)
{
CurlInputStream &c = *(CurlInputStream *)is;
return c.Read(ptr, size, error);
}
static void static void
input_curl_close(InputStream *is) input_curl_close(InputStream *is)
{ {
@ -854,23 +894,78 @@ input_curl_close(InputStream *is)
static bool static bool
input_curl_eof(gcc_unused InputStream *is) input_curl_eof(gcc_unused InputStream *is)
{ {
CurlInputStream *c = (CurlInputStream *)is; const CurlInputStream &c = *(const CurlInputStream *)is;
return c.IsEOF();
}
return c->easy == nullptr && c->buffers.empty(); inline void
CurlInputStream::HeaderReceived(const char *name,
const char *value, const char *end)
{
if (StringEqualsCaseASCII(name, "accept-ranges")) {
/* a stream with icy-metadata is not seekable */
if (!icy.IsDefined())
base.seekable = true;
} else if (StringEqualsCaseASCII(name, "content-length")) {
char buffer[64];
if ((size_t)(end - value) >= sizeof(buffer))
return;
memcpy(buffer, value, end - value);
buffer[end - value] = 0;
base.size = base.offset + ParseUint64(buffer);
} else if (StringEqualsCaseASCII(name, "content-type")) {
base.mime.assign(value, end);
} else if (StringEqualsCaseASCII(name, "icy-name") ||
StringEqualsCaseASCII(name, "ice-name") ||
StringEqualsCaseASCII(name, "x-audiocast-name")) {
meta_name.assign(value, end);
delete tag;
TagBuilder tag_builder;
tag_builder.AddItem(TAG_NAME, meta_name.c_str());
tag = tag_builder.CommitNew();
} else if (StringEqualsCaseASCII(name, "icy-metaint")) {
char buffer[64];
size_t icy_metaint;
if ((size_t)(end - value) >= sizeof(buffer) ||
icy.IsDefined())
return;
memcpy(buffer, value, end - value);
buffer[end - value] = 0;
icy_metaint = ParseUint64(buffer);
FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint);
if (icy_metaint > 0) {
icy.Start(icy_metaint);
/* a stream with icy-metadata is not
seekable */
base.seekable = false;
}
}
} }
/** called by curl when new data is available */ /** called by curl when new data is available */
static size_t static size_t
input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
{ {
CurlInputStream *c = (CurlInputStream *)stream; CurlInputStream &c = *(CurlInputStream *)stream;
char name[64];
size *= nmemb; size *= nmemb;
const char *header = (const char *)ptr; const char *header = (const char *)ptr;
const char *end = header + size; const char *end = header + size;
char name[64];
const char *value = (const char *)memchr(header, ':', size); const char *value = (const char *)memchr(header, ':', size);
if (value == nullptr || (size_t)(value - header) >= sizeof(name)) if (value == nullptr || (size_t)(value - header) >= sizeof(name))
return size; return size;
@ -890,56 +985,25 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
while (end > value && IsWhitespaceOrNull(end[-1])) while (end > value && IsWhitespaceOrNull(end[-1]))
--end; --end;
if (StringEqualsCaseASCII(name, "accept-ranges")) { c.HeaderReceived(name, value, end);
/* a stream with icy-metadata is not seekable */ return size;
if (!c->icy.IsDefined()) }
c->base.seekable = true;
} else if (StringEqualsCaseASCII(name, "content-length")) {
char buffer[64];
if ((size_t)(end - header) >= sizeof(buffer)) inline size_t
return size; CurlInputStream::DataReceived(const void *ptr, size_t size)
{
assert(size > 0);
memcpy(buffer, value, end - value); const ScopeLock protect(base.mutex);
buffer[end - value] = 0;
c->base.size = c->base.offset + ParseUint64(buffer); if (GetTotalBufferSize() + size >= CURL_MAX_BUFFERED) {
} else if (StringEqualsCaseASCII(name, "content-type")) { paused = true;
c->base.mime.assign(value, end); return CURL_WRITEFUNC_PAUSE;
} else if (StringEqualsCaseASCII(name, "icy-name") ||
StringEqualsCaseASCII(name, "ice-name") ||
StringEqualsCaseASCII(name, "x-audiocast-name")) {
c->meta_name.assign(value, end);
delete c->tag;
TagBuilder tag_builder;
tag_builder.AddItem(TAG_NAME, c->meta_name.c_str());
c->tag = tag_builder.CommitNew();
} else if (StringEqualsCaseASCII(name, "icy-metaint")) {
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 = ParseUint64(buffer);
FormatDebug(curl_domain, "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;
}
} }
buffers.emplace_back(ptr, size);
base.ready = true;
base.cond.broadcast();
return size; return size;
} }
@ -947,24 +1011,13 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
static size_t static size_t
input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
{ {
CurlInputStream *c = (CurlInputStream *)stream; CurlInputStream &c = *(CurlInputStream *)stream;
size *= nmemb; size *= nmemb;
if (size == 0) if (size == 0)
return 0; return 0;
const ScopeLock protect(c->base.mutex); return c.DataReceived(ptr, size);
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 static bool
@ -1091,7 +1144,7 @@ input_curl_seek(InputStream *is, InputPlugin::offset_type offset,
c->base.mutex.unlock(); c->base.mutex.unlock();
input_curl_easy_free_indirect(c); c->FreeEasyIndirect();
c->buffers.clear(); c->buffers.clear();
is->offset = offset; is->offset = offset;