Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed80863eac | ||
|
|
c3fc84de12 | ||
|
|
904f83cd85 | ||
|
|
28bf100a50 | ||
|
|
accbd4e82a | ||
|
|
d7f478c154 | ||
|
|
8f7f13fea4 | ||
|
|
c82b03a74c | ||
|
|
58fb36bdb9 | ||
|
|
4297a7b0a4 | ||
|
|
1bab6d0dd7 | ||
|
|
13b85edbe2 | ||
|
|
dc53098e43 | ||
|
|
3c66feff5a | ||
|
|
218c3bc0d5 | ||
|
|
9f5eddcd13 | ||
|
|
3cba76552b | ||
|
|
e98a8b624b | ||
|
|
6c6947b01f | ||
|
|
78c91e9e5b | ||
|
|
44493ca0c4 | ||
|
|
42acf78b09 | ||
|
|
3aa9f8af18 | ||
|
|
8a32ee30a5 | ||
|
|
981dc0626b | ||
|
|
8986d14e98 | ||
|
|
5163b1a624 | ||
|
|
860aa9d6d0 | ||
|
|
64dc5212f9 | ||
|
|
6cff3214f3 | ||
|
|
fd910bd5e9 | ||
|
|
c6086bed41 | ||
|
|
1a9dfdfab8 | ||
|
|
5284cd11a9 | ||
|
|
d1a47cffad | ||
|
|
f469595eee | ||
|
|
9cfc52f114 | ||
|
|
30bfb756c2 |
36
Makefile.am
36
Makefile.am
@@ -233,6 +233,15 @@ libmpd_a_SOURCES += \
|
||||
src/db/Selection.cxx src/db/Selection.hxx
|
||||
endif
|
||||
|
||||
CURL_SOURCES = \
|
||||
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
||||
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
||||
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
||||
src/lib/curl/Handler.hxx \
|
||||
src/lib/curl/Easy.hxx \
|
||||
src/lib/curl/Multi.hxx \
|
||||
src/lib/curl/Slist.hxx
|
||||
|
||||
UPNP_SOURCES = \
|
||||
src/lib/upnp/Init.cxx src/lib/upnp/Init.hxx \
|
||||
src/lib/upnp/ClientInit.cxx src/lib/upnp/ClientInit.hxx \
|
||||
@@ -420,6 +429,7 @@ libutil_a_SOURCES = \
|
||||
src/util/FormatString.cxx src/util/FormatString.hxx \
|
||||
src/util/Tokenizer.cxx src/util/Tokenizer.hxx \
|
||||
src/util/TextFile.hxx \
|
||||
src/util/TimeParser.cxx src/util/TimeParser.hxx \
|
||||
src/util/UriUtil.cxx src/util/UriUtil.hxx \
|
||||
src/util/Manual.hxx \
|
||||
src/util/RefCount.hxx \
|
||||
@@ -689,6 +699,8 @@ libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
|
||||
STORAGE_LIBS = \
|
||||
libstorage.a \
|
||||
$(CURL_LIBS) \
|
||||
$(EXPAT_LIBS) \
|
||||
$(NFS_LIBS) \
|
||||
$(SMBCLIENT_LIBS)
|
||||
|
||||
@@ -704,6 +716,12 @@ libstorage_a_SOURCES += \
|
||||
src/storage/plugins/NfsStorage.cxx src/storage/plugins/NfsStorage.hxx
|
||||
endif
|
||||
|
||||
if ENABLE_WEBDAV
|
||||
libstorage_a_SOURCES += \
|
||||
src/lib/expat/ExpatParser.cxx \
|
||||
src/storage/plugins/CurlStorage.cxx src/storage/plugins/CurlStorage.hxx
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
# neighbor plugins
|
||||
@@ -1279,12 +1297,7 @@ if ENABLE_CURL
|
||||
libinput_a_SOURCES += \
|
||||
src/input/IcyInputStream.cxx src/input/IcyInputStream.hxx \
|
||||
src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \
|
||||
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
||||
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
||||
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
||||
src/lib/curl/Handler.hxx \
|
||||
src/lib/curl/Easy.hxx \
|
||||
src/lib/curl/Multi.hxx \
|
||||
$(CURL_SOURCES) \
|
||||
src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx
|
||||
endif
|
||||
|
||||
@@ -1382,6 +1395,7 @@ libmixer_plugins_a_SOURCES = \
|
||||
src/mixer/plugins/NullMixerPlugin.cxx \
|
||||
src/mixer/plugins/SoftwareMixerPlugin.cxx \
|
||||
src/mixer/plugins/SoftwareMixerPlugin.hxx
|
||||
|
||||
libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ALSA_CFLAGS) \
|
||||
$(PULSE_CFLAGS)
|
||||
@@ -1390,7 +1404,10 @@ if ENABLE_ALSA
|
||||
liboutput_plugins_a_SOURCES += \
|
||||
src/output/plugins/AlsaOutputPlugin.cxx \
|
||||
src/output/plugins/AlsaOutputPlugin.hxx
|
||||
libmixer_plugins_a_SOURCES += src/mixer/plugins/AlsaMixerPlugin.cxx
|
||||
libmixer_plugins_a_SOURCES += \
|
||||
src/mixer/plugins/volume_mapping.h \
|
||||
src/mixer/plugins/volume_mapping.c \
|
||||
src/mixer/plugins/AlsaMixerPlugin.cxx
|
||||
endif
|
||||
|
||||
if ANDROID
|
||||
@@ -1573,6 +1590,7 @@ endif
|
||||
|
||||
if ENABLE_EXPAT
|
||||
libplaylist_plugins_a_SOURCES += \
|
||||
src/lib/expat/StreamExpatParser.cxx \
|
||||
src/lib/expat/ExpatParser.cxx src/lib/expat/ExpatParser.hxx \
|
||||
src/playlist/plugins/XspfPlaylistPlugin.cxx \
|
||||
src/playlist/plugins/XspfPlaylistPlugin.hxx \
|
||||
@@ -1758,6 +1776,10 @@ test_run_storage_SOURCES = \
|
||||
test/ScopeIOThread.hxx \
|
||||
test/run_storage.cxx
|
||||
|
||||
if ENABLE_WEBDAV
|
||||
test_run_storage_SOURCES += $(CURL_SOURCES)
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
test_run_input_LDADD = \
|
||||
|
||||
12
NEWS
12
NEWS
@@ -1,3 +1,15 @@
|
||||
ver 0.20.1 (2017/01/09)
|
||||
* input
|
||||
- curl: fix crash bug
|
||||
- curl: fix freeze bug
|
||||
* decoder
|
||||
- wavpack: fix crash bug
|
||||
* storage
|
||||
- curl: new storage plugin for WebDAV (work in progress)
|
||||
* mixer
|
||||
- alsa: normalize displayed volume according to human perception
|
||||
* fix crash with volume_normalization enabled
|
||||
|
||||
ver 0.20 (2017/01/04)
|
||||
* protocol
|
||||
- "commands" returns playlist commands only if playlist_directory configured
|
||||
|
||||
17
configure.ac
17
configure.ac
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.1, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=0
|
||||
VERSION_REVISION=1
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -720,6 +720,19 @@ fi
|
||||
MPD_ENABLE_AUTO_PKG(mms, MMS, [libmms >= 0.4],
|
||||
[libmms mms:// protocol support], [libmms not found])
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Storage Plugins
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
MPD_ENABLE_AUTO(webdav, WEBDAV, [WebDAV storage plugin],
|
||||
[WebDAV requires libcurl and libexpat],
|
||||
[auto],
|
||||
[if test x$enable_curl = xyes && test x$enable_expat = xyes; then
|
||||
found_webdav=yes
|
||||
else
|
||||
found_webdav=no
|
||||
fi])
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Playlist Plugins
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<book>
|
||||
<title>The Music Player Daemon - Developer's Manual</title>
|
||||
|
||||
<chapter>
|
||||
<chapter id="introduction">
|
||||
<title>Introduction</title>
|
||||
|
||||
<para>
|
||||
@@ -21,7 +21,7 @@
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="code_style">
|
||||
<title>Code Style</title>
|
||||
|
||||
<itemizedlist>
|
||||
@@ -40,21 +40,47 @@
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
the code should be C++11 compliant, and must compile with
|
||||
comment your code, document your APIs
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
the code should be C++14 compliant, and must compile with
|
||||
<application>GCC</application> 4.9 and
|
||||
<application>clang</application> 3.4
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
report error conditions with C++ exceptions, preferable
|
||||
derived from <varname>std::runtime_error</varname>
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
all code must be exception-safe
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
classes and functions names use CamelCase; variables are
|
||||
lower-case with words separated by underscore
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Some example code:
|
||||
</para>
|
||||
|
||||
<programlisting lang="C">static inline int
|
||||
foo(const char *abc, int xyz)
|
||||
Foo(const char *abc, int xyz)
|
||||
{
|
||||
if (abc == NULL) {
|
||||
if (abc == nullptr) {
|
||||
LogWarning("Foo happened!");
|
||||
return -1;
|
||||
}
|
||||
@@ -66,7 +92,7 @@ foo(const char *abc, int xyz)
|
||||
</itemizedlist>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="hacking">
|
||||
<title>Hacking The Source</title>
|
||||
|
||||
<para>
|
||||
@@ -151,7 +177,7 @@ foo(const char *abc, int xyz)
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="submitting_patches">
|
||||
<title>Submitting Patches</title>
|
||||
|
||||
<para>
|
||||
@@ -167,9 +193,16 @@ foo(const char *abc, int xyz)
|
||||
url="http://git.musicpd.org/account-policy.html">an account on
|
||||
git.musicpd.org</ulink>, but any public git repository will do.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
There is <ulink url="https://github.com/MaxKellermann/MPD">a
|
||||
mirror of the <application>MPD</application> git repository on
|
||||
GitHub</ulink>, and you can use that as well to submit pull
|
||||
requests.
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="tools">
|
||||
<title>Development Tools</title>
|
||||
|
||||
<section>
|
||||
|
||||
16
doc/user.xml
16
doc/user.xml
@@ -1868,11 +1868,23 @@ run</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="curl_storage">
|
||||
<title><varname>curl</varname></title>
|
||||
|
||||
<para>
|
||||
A WebDAV client using <filename>libcurl</filename>. It is
|
||||
used when <varname>music_directory</varname> contains a
|
||||
<parameter>http://</parameter> or
|
||||
<parameter>https://</parameter> URI, for example
|
||||
"<parameter>https://the.server/dav/</parameter>".
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="smbclient_storage">
|
||||
<title><varname>smbclient</varname></title>
|
||||
|
||||
<para>
|
||||
Load music files from a SMB/CIFS server. It used used when
|
||||
Load music files from a SMB/CIFS server. It is used when
|
||||
<varname>music_directory</varname> contains a
|
||||
<parameter>smb://</parameter> URI, for example
|
||||
"<parameter>smb://myfileserver/Music</parameter>".
|
||||
@@ -1883,7 +1895,7 @@ run</programlisting>
|
||||
<title><varname>nfs</varname></title>
|
||||
|
||||
<para>
|
||||
Load music files from a NFS server. It used used when
|
||||
Load music files from a NFS server. It is used when
|
||||
<varname>music_directory</varname> contains a
|
||||
<parameter>nfs://</parameter> URI according to <ulink
|
||||
url="http://tools.ietf.org/html/rfc2224">RFC2224</ulink>,
|
||||
|
||||
@@ -25,9 +25,12 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/TimeParser.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "lib/icu/Collate.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -180,24 +183,6 @@ SongFilter::~SongFilter()
|
||||
/* this destructor exists here just so it won't get inlined */
|
||||
}
|
||||
|
||||
#if !defined(__GLIBC__) && !defined(WIN32)
|
||||
|
||||
/**
|
||||
* Determine the time zone offset in a portable way.
|
||||
*/
|
||||
gcc_const
|
||||
static time_t
|
||||
GetTimeZoneOffset()
|
||||
{
|
||||
time_t t = 1234567890;
|
||||
struct tm tm;
|
||||
tm.tm_isdst = 0;
|
||||
gmtime_r(&t, &tm);
|
||||
return t - mktime(&tm);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
gcc_pure
|
||||
static time_t
|
||||
ParseTimeStamp(const char *s)
|
||||
@@ -210,26 +195,13 @@ ParseTimeStamp(const char *s)
|
||||
/* it's an integral UNIX time stamp */
|
||||
return (time_t)value;
|
||||
|
||||
#ifdef WIN32
|
||||
/* TODO: emulate strptime()? */
|
||||
return 0;
|
||||
#else
|
||||
/* try ISO 8601 */
|
||||
|
||||
struct tm tm;
|
||||
const char *end = strptime(s, "%FT%TZ", &tm);
|
||||
if (end == nullptr || *end != 0)
|
||||
try {
|
||||
/* try ISO 8601 */
|
||||
const auto t = ParseTimePoint(s, "%FT%TZ");
|
||||
return std::chrono::system_clock::to_time_t(t);
|
||||
} catch (const std::runtime_error &) {
|
||||
return 0;
|
||||
|
||||
#ifdef __GLIBC__
|
||||
/* timegm() is a GNU extension */
|
||||
return timegm(&tm);
|
||||
#else
|
||||
tm.tm_isdst = 0;
|
||||
return mktime(&tm) + GetTimeZoneOffset();
|
||||
#endif /* !__GLIBC__ */
|
||||
|
||||
#endif /* !WIN32 */
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -124,7 +124,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void StartElement(const XML_Char *name, const XML_Char **attrs)
|
||||
void StartElement(const XML_Char *name, const XML_Char **attrs) override
|
||||
{
|
||||
if (object.type != UPnPDirObject::Type::UNKNOWN &&
|
||||
tag_type == TAG_NUM_OF_ITEM_TYPES) {
|
||||
@@ -188,7 +188,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void EndElement(const XML_Char *name)
|
||||
void EndElement(const XML_Char *name) override
|
||||
{
|
||||
if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
|
||||
assert(object.type != UPnPDirObject::Type::UNKNOWN);
|
||||
@@ -212,7 +212,7 @@ protected:
|
||||
state = NONE;
|
||||
}
|
||||
|
||||
virtual void CharacterData(const XML_Char *s, int len)
|
||||
void CharacterData(const XML_Char *s, int len) override
|
||||
{
|
||||
if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
|
||||
assert(object.type != UPnPDirObject::Type::UNKNOWN);
|
||||
|
||||
@@ -77,9 +77,9 @@ public:
|
||||
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
virtual const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
void Visit(const DatabaseSelection &selection,
|
||||
|
||||
@@ -536,7 +536,7 @@ wavpack_streamdecode(DecoderClient &client, InputStream &is)
|
||||
auto is_wvc = wavpack_open_wvc(client, is.GetURI());
|
||||
if (is_wvc) {
|
||||
open_flags |= OPEN_WVC;
|
||||
can_seek &= wvc->is.IsSeekable();
|
||||
can_seek &= is_wvc->IsSeekable();
|
||||
|
||||
wvc.reset(new WavpackInput(&client, *is_wvc));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct AudioFormat;
|
||||
@@ -36,9 +37,10 @@ class Filter {
|
||||
protected:
|
||||
AudioFormat out_audio_format;
|
||||
|
||||
Filter() = default;
|
||||
explicit Filter(AudioFormat _out_audio_format)
|
||||
:out_audio_format(_out_audio_format) {}
|
||||
:out_audio_format(_out_audio_format) {
|
||||
assert(out_audio_format.IsValid());
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~Filter() {}
|
||||
@@ -56,7 +58,6 @@ public:
|
||||
* Throws std::runtime_error on error.
|
||||
*
|
||||
* @param src the input buffer
|
||||
* @param error location to store the error occurring
|
||||
* @return the destination buffer on success (will be
|
||||
* invalidated by deleting this object or the next FilterPCM()
|
||||
* call)
|
||||
|
||||
@@ -45,7 +45,8 @@ class AutoConvertFilter final : public Filter {
|
||||
public:
|
||||
AutoConvertFilter(std::unique_ptr<Filter> &&_filter,
|
||||
std::unique_ptr<Filter> &&_convert)
|
||||
:filter(std::move(_filter)), convert(std::move(_convert)) {}
|
||||
:Filter(_filter->GetOutAudioFormat()),
|
||||
filter(std::move(_filter)), convert(std::move(_convert)) {}
|
||||
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||
};
|
||||
@@ -92,11 +93,8 @@ PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format)
|
||||
ConstBuffer<void>
|
||||
AutoConvertFilter::FilterPCM(ConstBuffer<void> src)
|
||||
{
|
||||
if (convert != nullptr) {
|
||||
if (convert != nullptr)
|
||||
src = convert->FilterPCM(src);
|
||||
if (src.IsNull())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return filter->FilterPCM(src);
|
||||
}
|
||||
|
||||
@@ -57,8 +57,6 @@ public:
|
||||
|
||||
class PreparedConvertFilter final : public PreparedFilter {
|
||||
public:
|
||||
void Set(const AudioFormat &_out_audio_format);
|
||||
|
||||
Filter *Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "lib/curl/Global.hxx"
|
||||
#include "lib/curl/Request.hxx"
|
||||
#include "lib/curl/Handler.hxx"
|
||||
#include "lib/curl/Slist.hxx"
|
||||
#include "../AsyncInputStream.hxx"
|
||||
#include "../IcyInputStream.hxx"
|
||||
#include "../InputPlugin.hxx"
|
||||
@@ -64,7 +65,7 @@ struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
||||
/* some buffers which were passed to libcurl, which we have
|
||||
too free */
|
||||
char range[32];
|
||||
struct curl_slist *request_headers;
|
||||
CurlSlist request_headers;
|
||||
|
||||
CurlRequest *request = nullptr;
|
||||
|
||||
@@ -75,7 +76,6 @@ struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
||||
:AsyncInputStream(_url, _mutex, _cond,
|
||||
CURL_MAX_BUFFERED,
|
||||
CURL_RESUME_AT),
|
||||
request_headers(nullptr),
|
||||
icy(new IcyInputStream(this)) {
|
||||
}
|
||||
|
||||
@@ -155,8 +155,7 @@ CurlInputStream::FreeEasy()
|
||||
delete request;
|
||||
request = nullptr;
|
||||
|
||||
curl_slist_free_all(request_headers);
|
||||
request_headers = nullptr;
|
||||
request_headers.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -371,10 +370,11 @@ CurlInputStream::InitEasy()
|
||||
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
|
||||
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
|
||||
|
||||
request_headers = nullptr;
|
||||
request_headers = curl_slist_append(request_headers,
|
||||
"Icy-Metadata: 1");
|
||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers);
|
||||
request_headers.Clear();
|
||||
request_headers.Append("Icy-Metadata: 1");
|
||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||
|
||||
request->Start();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -385,11 +385,13 @@ CurlInputStream::SeekInternal(offset_type new_offset)
|
||||
FreeEasy();
|
||||
|
||||
offset = new_offset;
|
||||
if (offset == size)
|
||||
if (offset == size) {
|
||||
/* seek to EOF: simulate empty result; avoid
|
||||
triggering a "416 Requested Range Not Satisfiable"
|
||||
response */
|
||||
SeekDone();
|
||||
return;
|
||||
}
|
||||
|
||||
InitEasy();
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ private:
|
||||
};
|
||||
|
||||
CurlGlobal::CurlGlobal(EventLoop &_loop)
|
||||
:TimeoutMonitor(_loop)
|
||||
:TimeoutMonitor(_loop), DeferredMonitor(_loop)
|
||||
{
|
||||
multi.SetOption(CURLMOPT_SOCKETFUNCTION, CurlSocket::SocketFunction);
|
||||
multi.SetOption(CURLMOPT_SOCKETDATA, this);
|
||||
@@ -217,25 +217,31 @@ CurlGlobal::ReadInfo()
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp)
|
||||
inline void
|
||||
CurlGlobal::UpdateTimeout(long timeout_ms)
|
||||
{
|
||||
auto &global = *(CurlGlobal *)userp;
|
||||
assert(_global == global.multi.Get());
|
||||
|
||||
if (timeout_ms < 0) {
|
||||
global.Cancel();
|
||||
return 0;
|
||||
TimeoutMonitor::Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout_ms >= 0 && timeout_ms < 10)
|
||||
if (timeout_ms < 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. */
|
||||
timeout_ms = 10;
|
||||
|
||||
global.Schedule(std::chrono::milliseconds(timeout_ms));
|
||||
TimeoutMonitor::Schedule(std::chrono::milliseconds(timeout_ms));
|
||||
}
|
||||
|
||||
int
|
||||
CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp)
|
||||
{
|
||||
auto &global = *(CurlGlobal *)userp;
|
||||
assert(_global == global.multi.Get());
|
||||
|
||||
global.UpdateTimeout(timeout_ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -256,5 +262,11 @@ CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask)
|
||||
"curl_multi_socket_action() failed: %s",
|
||||
curl_multi_strerror(mcode));
|
||||
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
void
|
||||
CurlGlobal::RunDeferred()
|
||||
{
|
||||
ReadInfo();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "Multi.hxx"
|
||||
#include "event/TimeoutMonitor.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
|
||||
class CurlSocket;
|
||||
class CurlRequest;
|
||||
@@ -39,12 +40,14 @@ class CurlRequest;
|
||||
/**
|
||||
* Manager for the global CURLM object.
|
||||
*/
|
||||
class CurlGlobal final : private TimeoutMonitor {
|
||||
class CurlGlobal final : TimeoutMonitor, DeferredMonitor {
|
||||
CurlMulti multi;
|
||||
|
||||
public:
|
||||
explicit CurlGlobal(EventLoop &_loop);
|
||||
|
||||
using TimeoutMonitor::GetEventLoop;
|
||||
|
||||
void Add(CURL *easy, CurlRequest &request);
|
||||
void Remove(CURL *easy);
|
||||
|
||||
@@ -76,9 +79,14 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateTimeout(long timeout_ms);
|
||||
static int TimerFunction(CURLM *global, long timeout_ms, void *userp);
|
||||
|
||||
virtual void OnTimeout() override;
|
||||
/* virtual methods from class TimeoutMonitor */
|
||||
void OnTimeout() override;
|
||||
|
||||
/* virtual methods from class DeferredMonitor */
|
||||
void RunDeferred() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
CURL *Get() {
|
||||
CURLM *Get() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
|
||||
CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
|
||||
CurlResponseHandler &_handler)
|
||||
:global(_global), handler(_handler)
|
||||
:DeferredMonitor(_global.GetEventLoop()),
|
||||
global(_global), handler(_handler)
|
||||
{
|
||||
error_buffer[0] = 0;
|
||||
|
||||
@@ -62,8 +63,6 @@ CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
|
||||
easy.SetOption(CURLOPT_NOSIGNAL, 1l);
|
||||
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
|
||||
easy.SetOption(CURLOPT_URL, url);
|
||||
|
||||
global.Add(easy.Get(), *this);
|
||||
}
|
||||
|
||||
CurlRequest::~CurlRequest()
|
||||
@@ -71,19 +70,40 @@ CurlRequest::~CurlRequest()
|
||||
FreeEasy();
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Start()
|
||||
{
|
||||
assert(!registered);
|
||||
|
||||
global.Add(easy.Get(), *this);
|
||||
registered = true;
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Stop()
|
||||
{
|
||||
if (!registered)
|
||||
return;
|
||||
|
||||
global.Remove(easy.Get());
|
||||
registered = false;
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::FreeEasy()
|
||||
{
|
||||
if (!easy)
|
||||
return;
|
||||
|
||||
global.Remove(easy.Get());
|
||||
Stop();
|
||||
easy = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Resume()
|
||||
{
|
||||
assert(registered);
|
||||
|
||||
curl_easy_pause(easy.Get(), CURLPAUSE_CONT);
|
||||
|
||||
if (IsCurlOlderThan(0x072000))
|
||||
@@ -95,32 +115,24 @@ CurlRequest::Resume()
|
||||
global.InvalidateSockets();
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
CurlRequest::FinishHeaders()
|
||||
{
|
||||
if (state != State::HEADERS)
|
||||
return true;
|
||||
return;
|
||||
|
||||
state = State::BODY;
|
||||
|
||||
long status = 0;
|
||||
curl_easy_getinfo(easy.Get(), CURLINFO_RESPONSE_CODE, &status);
|
||||
|
||||
try {
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
return true;
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
return false;
|
||||
}
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::FinishBody()
|
||||
{
|
||||
if (!FinishHeaders())
|
||||
return;
|
||||
FinishHeaders();
|
||||
|
||||
if (state != State::BODY)
|
||||
return;
|
||||
@@ -132,7 +144,7 @@ CurlRequest::FinishBody()
|
||||
void
|
||||
CurlRequest::Done(CURLcode result)
|
||||
{
|
||||
FreeEasy();
|
||||
Stop();
|
||||
|
||||
try {
|
||||
if (result != CURLE_OK) {
|
||||
@@ -148,7 +160,12 @@ CurlRequest::Done(CURLcode result)
|
||||
return;
|
||||
}
|
||||
|
||||
FinishBody();
|
||||
try {
|
||||
FinishBody();
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -202,18 +219,19 @@ CurlRequest::DataReceived(const void *ptr, size_t received_size)
|
||||
{
|
||||
assert(received_size > 0);
|
||||
|
||||
if (!FinishHeaders())
|
||||
return 0;
|
||||
|
||||
try {
|
||||
FinishHeaders();
|
||||
handler.OnData({ptr, received_size});
|
||||
return received_size;
|
||||
} catch (Pause) {
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
return 0;
|
||||
/* move the CurlResponseHandler::OnError() call into a
|
||||
"safe" stack frame */
|
||||
postponed_error = std::current_exception();
|
||||
DeferredMonitor::Schedule();
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -229,3 +247,11 @@ CurlRequest::WriteFunction(void *ptr, size_t size, size_t nmemb, void *stream)
|
||||
|
||||
return c.DataReceived(ptr, size);
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::RunDeferred()
|
||||
{
|
||||
assert(postponed_error);
|
||||
|
||||
handler.OnError(postponed_error);
|
||||
}
|
||||
|
||||
@@ -31,15 +31,17 @@
|
||||
#define CURL_REQUEST_HXX
|
||||
|
||||
#include "Easy.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
struct StringView;
|
||||
class CurlGlobal;
|
||||
class CurlResponseHandler;
|
||||
|
||||
class CurlRequest {
|
||||
class CurlRequest final : DeferredMonitor {
|
||||
CurlGlobal &global;
|
||||
|
||||
CurlResponseHandler &handler;
|
||||
@@ -55,10 +57,25 @@ class CurlRequest {
|
||||
|
||||
std::multimap<std::string, std::string> headers;
|
||||
|
||||
/**
|
||||
* An exception caught by DataReceived(), which will be
|
||||
* forwarded into a "safe" stack frame by
|
||||
* DeferredMonitor::RunDeferred(). This works around the
|
||||
* problem that libcurl crashes if you call
|
||||
* curl_multi_remove_handle() from within the WRITEFUNCTION
|
||||
* (i.e. DataReceived()).
|
||||
*/
|
||||
std::exception_ptr postponed_error;
|
||||
|
||||
/** error message provided by libcurl */
|
||||
char error_buffer[CURL_ERROR_SIZE];
|
||||
|
||||
bool registered = false;
|
||||
|
||||
public:
|
||||
/**
|
||||
* To start sending the request, call Start().
|
||||
*/
|
||||
CurlRequest(CurlGlobal &_global, const char *url,
|
||||
CurlResponseHandler &_handler);
|
||||
~CurlRequest();
|
||||
@@ -66,6 +83,21 @@ public:
|
||||
CurlRequest(const CurlRequest &) = delete;
|
||||
CurlRequest &operator=(const CurlRequest &) = delete;
|
||||
|
||||
/**
|
||||
* Register this request via CurlGlobal::Add(), which starts
|
||||
* the request.
|
||||
*
|
||||
* This method must be called in the event loop thread.
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* Unregister this request via CurlGlobal::Remove().
|
||||
*
|
||||
* This method must be called in the event loop thread.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
CURL *Get() {
|
||||
return easy.Get();
|
||||
}
|
||||
@@ -95,7 +127,7 @@ private:
|
||||
*/
|
||||
void FreeEasy();
|
||||
|
||||
bool FinishHeaders();
|
||||
void FinishHeaders();
|
||||
void FinishBody();
|
||||
|
||||
size_t DataReceived(const void *ptr, size_t size);
|
||||
@@ -109,6 +141,9 @@ private:
|
||||
/** called by curl when new data is available */
|
||||
static size_t WriteFunction(void *ptr, size_t size, size_t nmemb,
|
||||
void *stream);
|
||||
|
||||
/* virtual methods from class DeferredMonitor */
|
||||
void RunDeferred() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
76
src/lib/curl/Slist.hxx
Normal file
76
src/lib/curl/Slist.hxx
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CURL_SLIST_HXX
|
||||
#define CURL_SLIST_HXX
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* OO wrapper for "struct curl_slist *".
|
||||
*/
|
||||
class CurlSlist {
|
||||
struct curl_slist *head = nullptr;
|
||||
|
||||
public:
|
||||
CurlSlist() = default;
|
||||
|
||||
CurlSlist(CurlSlist &&src)
|
||||
:head(std::exchange(src.head, nullptr)) {}
|
||||
|
||||
~CurlSlist() {
|
||||
if (head != nullptr)
|
||||
curl_slist_free_all(head);
|
||||
}
|
||||
|
||||
CurlSlist &operator=(CurlSlist &&src) {
|
||||
std::swap(head, src.head);
|
||||
return *this;
|
||||
}
|
||||
|
||||
struct curl_slist *Get() {
|
||||
return head;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
curl_slist_free_all(head);
|
||||
head = nullptr;
|
||||
}
|
||||
|
||||
void Append(const char *value) {
|
||||
auto *new_head = curl_slist_append(head, value);
|
||||
if (new_head == nullptr)
|
||||
throw std::runtime_error("curl_slist_append() failed");
|
||||
head = new_head;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "ExpatParser.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
|
||||
#include <string.h>
|
||||
@@ -31,23 +30,6 @@ ExpatParser::Parse(const char *data, size_t length, bool is_final)
|
||||
throw ExpatError(parser);
|
||||
}
|
||||
|
||||
void
|
||||
ExpatParser::Parse(InputStream &is)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
while (true) {
|
||||
char buffer[4096];
|
||||
size_t nbytes = is.LockRead(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
Parse(buffer, nbytes, false);
|
||||
}
|
||||
|
||||
Parse("", 0, true);
|
||||
}
|
||||
|
||||
const char *
|
||||
ExpatParser::GetAttribute(const XML_Char **atts,
|
||||
const char *name)
|
||||
|
||||
@@ -31,22 +31,31 @@ class InputStream;
|
||||
|
||||
class ExpatError final : public std::runtime_error {
|
||||
public:
|
||||
ExpatError(XML_Error code)
|
||||
explicit ExpatError(XML_Error code)
|
||||
:std::runtime_error(XML_ErrorString(code)) {}
|
||||
|
||||
ExpatError(XML_Parser parser)
|
||||
explicit ExpatError(XML_Parser parser)
|
||||
:ExpatError(XML_GetErrorCode(parser)) {}
|
||||
};
|
||||
|
||||
struct ExpatNamespaceSeparator {
|
||||
char separator;
|
||||
};
|
||||
|
||||
class ExpatParser final {
|
||||
const XML_Parser parser;
|
||||
|
||||
public:
|
||||
ExpatParser(void *userData)
|
||||
explicit ExpatParser(void *userData)
|
||||
:parser(XML_ParserCreate(nullptr)) {
|
||||
XML_SetUserData(parser, userData);
|
||||
}
|
||||
|
||||
ExpatParser(ExpatNamespaceSeparator ns, void *userData)
|
||||
:parser(XML_ParserCreateNS(nullptr, ns.separator)) {
|
||||
XML_SetUserData(parser, userData);
|
||||
}
|
||||
|
||||
~ExpatParser() {
|
||||
XML_ParserFree(parser);
|
||||
}
|
||||
@@ -89,6 +98,12 @@ public:
|
||||
parser.SetCharacterDataHandler(CharacterData);
|
||||
}
|
||||
|
||||
explicit CommonExpatParser(ExpatNamespaceSeparator ns)
|
||||
:parser(ns, this) {
|
||||
parser.SetElementHandler(StartElement, EndElement);
|
||||
parser.SetCharacterDataHandler(CharacterData);
|
||||
}
|
||||
|
||||
void Parse(const char *data, size_t length, bool is_final) {
|
||||
parser.Parse(data, length, is_final);
|
||||
}
|
||||
|
||||
39
src/lib/expat/StreamExpatParser.cxx
Normal file
39
src/lib/expat/StreamExpatParser.cxx
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2003-2017 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 "ExpatParser.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
|
||||
void
|
||||
ExpatParser::Parse(InputStream &is)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
while (true) {
|
||||
char buffer[4096];
|
||||
size_t nbytes = is.LockRead(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
Parse(buffer, nbytes, false);
|
||||
}
|
||||
|
||||
Parse("", 0, true);
|
||||
}
|
||||
@@ -25,15 +25,18 @@
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
extern "C" {
|
||||
#include "volume_mapping.h"
|
||||
}
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#define VOLUME_MIXER_ALSA_DEFAULT "default"
|
||||
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
|
||||
static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0;
|
||||
@@ -68,9 +71,6 @@ class AlsaMixer final : public Mixer {
|
||||
|
||||
snd_mixer_t *handle;
|
||||
snd_mixer_elem_t *elem;
|
||||
long volume_min;
|
||||
long volume_max;
|
||||
int volume_set;
|
||||
|
||||
AlsaMixerMonitor *monitor;
|
||||
|
||||
@@ -228,9 +228,6 @@ AlsaMixer::Setup()
|
||||
if (elem == nullptr)
|
||||
throw FormatRuntimeError("no such mixer control: %s", control);
|
||||
|
||||
snd_mixer_selem_get_playback_volume_range(elem, &volume_min,
|
||||
&volume_max);
|
||||
|
||||
snd_mixer_elem_set_callback_private(elem, this);
|
||||
snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback);
|
||||
|
||||
@@ -242,8 +239,6 @@ AlsaMixer::Open()
|
||||
{
|
||||
int err;
|
||||
|
||||
volume_set = -1;
|
||||
|
||||
err = snd_mixer_open(&handle, 0);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_mixer_open() failed: %s",
|
||||
@@ -272,8 +267,6 @@ int
|
||||
AlsaMixer::GetVolume()
|
||||
{
|
||||
int err;
|
||||
int ret;
|
||||
long level;
|
||||
|
||||
assert(handle != nullptr);
|
||||
|
||||
@@ -282,43 +275,15 @@ AlsaMixer::GetVolume()
|
||||
throw FormatRuntimeError("snd_mixer_handle_events() failed: %s",
|
||||
snd_strerror(err));
|
||||
|
||||
err = snd_mixer_selem_get_playback_volume(elem,
|
||||
SND_MIXER_SCHN_FRONT_LEFT,
|
||||
&level);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("failed to read ALSA volume: %s",
|
||||
snd_strerror(err));
|
||||
|
||||
ret = ((volume_set / 100.0) * (volume_max - volume_min)
|
||||
+ volume_min) + 0.5;
|
||||
if (volume_set > 0 && ret == level) {
|
||||
ret = volume_set;
|
||||
} else {
|
||||
ret = (int)(100 * (((float)(level - volume_min)) /
|
||||
(volume_max - volume_min)) + 0.5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return lrint(100 * get_normalized_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT));
|
||||
}
|
||||
|
||||
void
|
||||
AlsaMixer::SetVolume(unsigned volume)
|
||||
{
|
||||
float vol;
|
||||
long level;
|
||||
int err;
|
||||
|
||||
assert(handle != nullptr);
|
||||
|
||||
vol = volume;
|
||||
|
||||
volume_set = vol + 0.5;
|
||||
|
||||
level = (long)(((vol / 100.0) * (volume_max - volume_min) +
|
||||
volume_min) + 0.5);
|
||||
level = Clamp(level, volume_min, volume_max);
|
||||
|
||||
err = snd_mixer_selem_set_playback_volume_all(elem, level);
|
||||
int err = set_normalized_playback_volume(elem, 0.01*volume, 1);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("failed to set ALSA volume: %s",
|
||||
snd_strerror(err));
|
||||
|
||||
180
src/mixer/plugins/volume_mapping.c
Normal file
180
src/mixer/plugins/volume_mapping.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The functions in this file map the value ranges of ALSA mixer controls onto
|
||||
* the interval 0..1.
|
||||
*
|
||||
* The mapping is designed so that the position in the interval is proportional
|
||||
* to the volume as a human ear would perceive it (i.e., the position is the
|
||||
* cubic root of the linear sample multiplication factor). For controls with
|
||||
* a small range (24 dB or less), the mapping is linear in the dB values so
|
||||
* that each step has the same size visually. Only for controls without dB
|
||||
* information, a linear mapping of the hardware volume register values is used
|
||||
* (this is the same algorithm as used in the old alsamixer).
|
||||
*
|
||||
* When setting the volume, 'dir' is the rounding direction:
|
||||
* -1/0/1 = down/nearest/up.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include "volume_mapping.h"
|
||||
|
||||
#ifdef __UCLIBC__
|
||||
/* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */
|
||||
#define exp10(x) (exp((x) * log(10)))
|
||||
#endif /* __UCLIBC__ */
|
||||
|
||||
#define MAX_LINEAR_DB_SCALE 24
|
||||
|
||||
static inline bool use_linear_dB_scale(long dBmin, long dBmax)
|
||||
{
|
||||
return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
|
||||
}
|
||||
|
||||
static long lrint_dir(double x, int dir)
|
||||
{
|
||||
if (dir > 0)
|
||||
return lrint(ceil(x));
|
||||
else if (dir < 0)
|
||||
return lrint(floor(x));
|
||||
else
|
||||
return lrint(x);
|
||||
}
|
||||
|
||||
enum ctl_dir { PLAYBACK, CAPTURE };
|
||||
|
||||
static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
|
||||
snd_mixer_selem_get_playback_dB_range,
|
||||
snd_mixer_selem_get_capture_dB_range,
|
||||
};
|
||||
static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
|
||||
snd_mixer_selem_get_playback_volume_range,
|
||||
snd_mixer_selem_get_capture_volume_range,
|
||||
};
|
||||
static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
|
||||
snd_mixer_selem_get_playback_dB,
|
||||
snd_mixer_selem_get_capture_dB,
|
||||
};
|
||||
static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
|
||||
snd_mixer_selem_get_playback_volume,
|
||||
snd_mixer_selem_get_capture_volume,
|
||||
};
|
||||
static int (* const set_dB[2])(snd_mixer_elem_t *, long, int) = {
|
||||
snd_mixer_selem_set_playback_dB_all,
|
||||
snd_mixer_selem_set_capture_dB_all,
|
||||
};
|
||||
static int (* const set_raw[2])(snd_mixer_elem_t *, long) = {
|
||||
snd_mixer_selem_set_playback_volume_all,
|
||||
snd_mixer_selem_set_capture_volume_all,
|
||||
};
|
||||
|
||||
static double get_normalized_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
enum ctl_dir ctl_dir)
|
||||
{
|
||||
long min, max, value;
|
||||
double normalized, min_norm;
|
||||
int err;
|
||||
|
||||
err = get_dB_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min >= max) {
|
||||
err = get_raw_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min == max)
|
||||
return 0;
|
||||
|
||||
err = get_raw[ctl_dir](elem, channel, &value);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
return (value - min) / (double)(max - min);
|
||||
}
|
||||
|
||||
err = get_dB[ctl_dir](elem, channel, &value);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
if (use_linear_dB_scale(min, max))
|
||||
return (value - min) / (double)(max - min);
|
||||
|
||||
normalized = exp10((value - max) / 6000.0);
|
||||
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
min_norm = exp10((min - max) / 6000.0);
|
||||
normalized = (normalized - min_norm) / (1 - min_norm);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
static int set_normalized_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir,
|
||||
enum ctl_dir ctl_dir)
|
||||
{
|
||||
long min, max, value;
|
||||
double min_norm;
|
||||
int err;
|
||||
|
||||
err = get_dB_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min >= max) {
|
||||
err = get_raw_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_raw[ctl_dir](elem, value);
|
||||
}
|
||||
|
||||
if (use_linear_dB_scale(min, max)) {
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_dB[ctl_dir](elem, value, dir);
|
||||
}
|
||||
|
||||
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
min_norm = exp10((min - max) / 6000.0);
|
||||
volume = volume * (1 - min_norm) + min_norm;
|
||||
}
|
||||
value = lrint_dir(6000.0 * log10(volume), dir) + max;
|
||||
return set_dB[ctl_dir](elem, value, dir);
|
||||
}
|
||||
|
||||
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel)
|
||||
{
|
||||
return get_normalized_volume(elem, channel, PLAYBACK);
|
||||
}
|
||||
|
||||
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel)
|
||||
{
|
||||
return get_normalized_volume(elem, channel, CAPTURE);
|
||||
}
|
||||
|
||||
|
||||
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir)
|
||||
{
|
||||
return set_normalized_volume(elem, volume, dir, PLAYBACK);
|
||||
}
|
||||
|
||||
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir)
|
||||
{
|
||||
return set_normalized_volume(elem, volume, dir, CAPTURE);
|
||||
}
|
||||
17
src/mixer/plugins/volume_mapping.h
Normal file
17
src/mixer/plugins/volume_mapping.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef VOLUME_MAPPING_H_INCLUDED
|
||||
#define VOLUME_MAPPING_H_INCLUDED
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel);
|
||||
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel);
|
||||
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir);
|
||||
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir);
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "plugins/LocalStorage.hxx"
|
||||
#include "plugins/SmbclientStorage.hxx"
|
||||
#include "plugins/NfsStorage.hxx"
|
||||
#include "plugins/CurlStorage.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -34,6 +35,9 @@ const StoragePlugin *const storage_plugins[] = {
|
||||
#endif
|
||||
#ifdef ENABLE_NFS
|
||||
&nfs_storage_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_WEBDAV
|
||||
&curl_storage_plugin,
|
||||
#endif
|
||||
nullptr
|
||||
};
|
||||
|
||||
590
src/storage/plugins/CurlStorage.cxx
Normal file
590
src/storage/plugins/CurlStorage.cxx
Normal file
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* Copyright 2003-2016 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 "CurlStorage.hxx"
|
||||
#include "storage/StoragePlugin.hxx"
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#include "storage/FileInfo.hxx"
|
||||
#include "storage/MemoryDirectoryReader.hxx"
|
||||
#include "lib/curl/Global.hxx"
|
||||
#include "lib/curl/Slist.hxx"
|
||||
#include "lib/curl/Request.hxx"
|
||||
#include "lib/curl/Handler.hxx"
|
||||
#include "lib/expat/ExpatParser.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/TimeParser.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
class CurlStorage final : public Storage {
|
||||
const std::string base;
|
||||
|
||||
CurlGlobal *const curl;
|
||||
|
||||
public:
|
||||
CurlStorage(EventLoop &_loop, const char *_base)
|
||||
:base(_base),
|
||||
curl(new CurlGlobal(_loop)) {}
|
||||
|
||||
~CurlStorage() {
|
||||
BlockingCall(curl->GetEventLoop(), [this](){ delete curl; });
|
||||
}
|
||||
|
||||
/* virtual methods from class Storage */
|
||||
StorageFileInfo GetInfo(const char *uri_utf8, bool follow) override;
|
||||
|
||||
StorageDirectoryReader *OpenDirectory(const char *uri_utf8) override;
|
||||
|
||||
std::string MapUTF8(const char *uri_utf8) const override;
|
||||
|
||||
const char *MapToRelativeUTF8(const char *uri_utf8) const override;
|
||||
};
|
||||
|
||||
std::string
|
||||
CurlStorage::MapUTF8(const char *uri_utf8) const
|
||||
{
|
||||
assert(uri_utf8 != nullptr);
|
||||
|
||||
if (StringIsEmpty(uri_utf8))
|
||||
return base;
|
||||
|
||||
// TODO: escape the given URI
|
||||
|
||||
return PathTraitsUTF8::Build(base.c_str(), uri_utf8);
|
||||
}
|
||||
|
||||
const char *
|
||||
CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const
|
||||
{
|
||||
// TODO: escape/unescape?
|
||||
|
||||
return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
|
||||
}
|
||||
|
||||
class BlockingHttpRequest : protected CurlResponseHandler, DeferredMonitor {
|
||||
std::exception_ptr postponed_error;
|
||||
|
||||
bool done = false;
|
||||
|
||||
protected:
|
||||
CurlRequest request;
|
||||
|
||||
Mutex mutex;
|
||||
Cond cond;
|
||||
|
||||
public:
|
||||
BlockingHttpRequest(CurlGlobal &curl, const char *uri)
|
||||
:DeferredMonitor(curl.GetEventLoop()),
|
||||
request(curl, uri, *this) {
|
||||
// TODO: use CurlInputStream's configuration
|
||||
|
||||
/* start the transfer inside the IOThread */
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
while (!done)
|
||||
cond.wait(mutex);
|
||||
|
||||
if (postponed_error)
|
||||
std::rethrow_exception(postponed_error);
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetDone() {
|
||||
assert(!done);
|
||||
|
||||
request.Stop();
|
||||
done = true;
|
||||
cond.signal();
|
||||
}
|
||||
|
||||
void LockSetDone() {
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
SetDone();
|
||||
}
|
||||
|
||||
private:
|
||||
/* virtual methods from DeferredMonitor */
|
||||
void RunDeferred() final {
|
||||
assert(!done);
|
||||
|
||||
request.Start();
|
||||
}
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnError(std::exception_ptr e) final {
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
postponed_error = std::move(e);
|
||||
SetDone();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper class which feeds a (foreign) memory buffer into the
|
||||
* CURLOPT_READFUNCTION.
|
||||
*/
|
||||
class CurlRequestBody {
|
||||
ConstBuffer<char> data;
|
||||
|
||||
public:
|
||||
explicit CurlRequestBody(ConstBuffer<void> _data)
|
||||
:data(ConstBuffer<char>::FromVoid(_data)) {}
|
||||
|
||||
explicit constexpr CurlRequestBody(StringView _data)
|
||||
:data(_data) {}
|
||||
|
||||
template<typename T>
|
||||
CurlRequestBody(CurlRequest &request, T _data)
|
||||
:CurlRequestBody(_data) {
|
||||
request.SetOption(CURLOPT_READFUNCTION, Callback);
|
||||
request.SetOption(CURLOPT_READDATA, this);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t Read(char *buffer, size_t size) {
|
||||
size_t n = std::min(size, data.size);
|
||||
std::copy_n(data.begin(), n, buffer);
|
||||
return n;
|
||||
}
|
||||
|
||||
static size_t Callback(char *buffer, size_t size, size_t nitems,
|
||||
void *instream) {
|
||||
auto &rb = *(CurlRequestBody *)instream;
|
||||
return rb.Read(buffer, size * nitems);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The (relevant) contents of a "<D:response>" element.
|
||||
*/
|
||||
struct DavResponse {
|
||||
std::string href;
|
||||
unsigned status = 0;
|
||||
bool collection = false;
|
||||
std::chrono::system_clock::time_point mtime =
|
||||
std::chrono::system_clock::time_point::min();
|
||||
uint64_t length = 0;
|
||||
|
||||
bool Check() const {
|
||||
return !href.empty();
|
||||
}
|
||||
};
|
||||
|
||||
static unsigned
|
||||
ParseStatus(const char *s)
|
||||
{
|
||||
/* skip the "HTTP/1.1" prefix */
|
||||
const char *space = strchr(s, ' ');
|
||||
if (space == nullptr)
|
||||
return 0;
|
||||
|
||||
return strtoul(space + 1, nullptr, 10);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
ParseStatus(const char *s, size_t length)
|
||||
{
|
||||
return ParseStatus(std::string(s, length).c_str());
|
||||
}
|
||||
|
||||
static std::chrono::system_clock::time_point
|
||||
ParseTimeStamp(const char *s)
|
||||
{
|
||||
try {
|
||||
// TODO: make this more robust
|
||||
return ParseTimePoint(s, "%a, %d %b %Y %T %Z");
|
||||
} catch (const std::runtime_error &) {
|
||||
return std::chrono::system_clock::time_point::min();
|
||||
}
|
||||
}
|
||||
|
||||
static std::chrono::system_clock::time_point
|
||||
ParseTimeStamp(const char *s, size_t length)
|
||||
{
|
||||
return ParseTimeStamp(std::string(s, length).c_str());
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
ParseU64(const char *s)
|
||||
{
|
||||
return strtoull(s, nullptr, 10);
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
ParseU64(const char *s, size_t length)
|
||||
{
|
||||
return ParseU64(std::string(s, length).c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* A WebDAV PROPFIND request. Each "response" element will be passed
|
||||
* to OnDavResponse() (to be implemented by a derived class).
|
||||
*/
|
||||
class PropfindOperation : BlockingHttpRequest, CommonExpatParser {
|
||||
CurlSlist request_headers;
|
||||
CurlRequestBody request_body;
|
||||
|
||||
enum class State {
|
||||
ROOT,
|
||||
RESPONSE,
|
||||
HREF,
|
||||
STATUS,
|
||||
TYPE,
|
||||
MTIME,
|
||||
LENGTH,
|
||||
} state = State::ROOT;
|
||||
|
||||
DavResponse response;
|
||||
|
||||
public:
|
||||
PropfindOperation(CurlGlobal &_curl, const char *_uri, unsigned depth)
|
||||
:BlockingHttpRequest(_curl, _uri),
|
||||
CommonExpatParser(ExpatNamespaceSeparator{'|'}),
|
||||
request_body(request,
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<a:propfind xmlns:a=\"DAV:\">"
|
||||
"<a:prop><a:getcontenttype/></a:prop>"
|
||||
"<a:prop><a:getcontentlength/></a:prop>"
|
||||
"</a:propfind>")
|
||||
{
|
||||
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
|
||||
|
||||
char buffer[40];
|
||||
sprintf(buffer, "depth: %u", depth);
|
||||
request_headers.Append(buffer);
|
||||
|
||||
request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||
|
||||
// TODO: send request body
|
||||
}
|
||||
|
||||
using BlockingHttpRequest::Wait;
|
||||
|
||||
protected:
|
||||
virtual void OnDavResponse(DavResponse &&r) = 0;
|
||||
|
||||
private:
|
||||
void FinishResponse() {
|
||||
if (response.Check())
|
||||
OnDavResponse(std::move(response));
|
||||
response = DavResponse();
|
||||
}
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) final {
|
||||
if (status != 207)
|
||||
throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
|
||||
status);
|
||||
|
||||
auto i = headers.find("content-type");
|
||||
if (i == headers.end() ||
|
||||
strncmp(i->second.c_str(), "text/xml", 8) != 0)
|
||||
throw std::runtime_error("Unexpected Content-Type from WebDAV server");
|
||||
}
|
||||
|
||||
void OnData(ConstBuffer<void> _data) final {
|
||||
const auto data = ConstBuffer<char>::FromVoid(_data);
|
||||
Parse(data.data, data.size, false);
|
||||
}
|
||||
|
||||
void OnEnd() final {
|
||||
Parse("", 0, true);
|
||||
LockSetDone();
|
||||
}
|
||||
|
||||
/* virtual methods from CommonExpatParser */
|
||||
void StartElement(const XML_Char *name,
|
||||
gcc_unused const XML_Char **attrs) final {
|
||||
switch (state) {
|
||||
case State::ROOT:
|
||||
if (strcmp(name, "DAV:|response") == 0)
|
||||
state = State::RESPONSE;
|
||||
break;
|
||||
|
||||
case State::RESPONSE:
|
||||
if (strcmp(name, "DAV:|href") == 0)
|
||||
state = State::HREF;
|
||||
else if (strcmp(name, "DAV:|status") == 0)
|
||||
state = State::STATUS;
|
||||
else if (strcmp(name, "DAV:|resourcetype") == 0)
|
||||
state = State::TYPE;
|
||||
else if (strcmp(name, "DAV:|getlastmodified") == 0)
|
||||
state = State::MTIME;
|
||||
else if (strcmp(name, "DAV:|getcontentlength") == 0)
|
||||
state = State::LENGTH;
|
||||
break;
|
||||
|
||||
case State::TYPE:
|
||||
if (strcmp(name, "DAV:|collection") == 0)
|
||||
response.collection = true;
|
||||
break;
|
||||
|
||||
case State::HREF:
|
||||
case State::STATUS:
|
||||
case State::LENGTH:
|
||||
case State::MTIME:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EndElement(const XML_Char *name) final {
|
||||
switch (state) {
|
||||
case State::ROOT:
|
||||
break;
|
||||
|
||||
case State::RESPONSE:
|
||||
if (strcmp(name, "DAV:|response") == 0) {
|
||||
FinishResponse();
|
||||
state = State::ROOT;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case State::HREF:
|
||||
if (strcmp(name, "DAV:|href") == 0)
|
||||
state = State::RESPONSE;
|
||||
break;
|
||||
|
||||
case State::STATUS:
|
||||
if (strcmp(name, "DAV:|status") == 0)
|
||||
state = State::RESPONSE;
|
||||
break;
|
||||
|
||||
case State::TYPE:
|
||||
if (strcmp(name, "DAV:|resourcetype") == 0)
|
||||
state = State::RESPONSE;
|
||||
break;
|
||||
|
||||
case State::MTIME:
|
||||
if (strcmp(name, "DAV:|getlastmodified") == 0)
|
||||
state = State::RESPONSE;
|
||||
break;
|
||||
|
||||
case State::LENGTH:
|
||||
if (strcmp(name, "DAV:|getcontentlength") == 0)
|
||||
state = State::RESPONSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterData(const XML_Char *s, int len) final {
|
||||
switch (state) {
|
||||
case State::ROOT:
|
||||
case State::RESPONSE:
|
||||
case State::TYPE:
|
||||
break;
|
||||
|
||||
case State::HREF:
|
||||
response.href.assign(s, len);
|
||||
break;
|
||||
|
||||
case State::STATUS:
|
||||
response.status = ParseStatus(s, len);
|
||||
break;
|
||||
|
||||
case State::MTIME:
|
||||
response.mtime = ParseTimeStamp(s, len);
|
||||
break;
|
||||
|
||||
case State::LENGTH:
|
||||
response.length = ParseU64(s, len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Obtain information about a single file using WebDAV PROPFIND.
|
||||
*/
|
||||
class HttpGetInfoOperation final : public PropfindOperation {
|
||||
StorageFileInfo info;
|
||||
|
||||
public:
|
||||
HttpGetInfoOperation(CurlGlobal &curl, const char *uri)
|
||||
:PropfindOperation(curl, uri, 0) {
|
||||
info.type = StorageFileInfo::Type::OTHER;
|
||||
info.size = 0;
|
||||
info.mtime = 0;
|
||||
info.device = info.inode = 0;
|
||||
}
|
||||
|
||||
const StorageFileInfo &Perform() {
|
||||
Wait();
|
||||
return info;
|
||||
}
|
||||
|
||||
protected:
|
||||
/* virtual methods from PropfindOperation */
|
||||
void OnDavResponse(DavResponse &&r) override {
|
||||
if (r.status != 200)
|
||||
return;
|
||||
|
||||
info.type = r.collection
|
||||
? StorageFileInfo::Type::DIRECTORY
|
||||
: StorageFileInfo::Type::REGULAR;
|
||||
info.size = r.length;
|
||||
info.mtime = r.mtime > std::chrono::system_clock::time_point()
|
||||
? std::chrono::system_clock::to_time_t(r.mtime)
|
||||
: 0;
|
||||
info.device = info.inode = 0;
|
||||
}
|
||||
};
|
||||
|
||||
StorageFileInfo
|
||||
CurlStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
|
||||
{
|
||||
// TODO: escape the given URI
|
||||
|
||||
std::string uri = base;
|
||||
uri += uri_utf8;
|
||||
|
||||
return HttpGetInfoOperation(*curl, uri.c_str()).Perform();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const char *
|
||||
UriPathOrSlash(const char *uri)
|
||||
{
|
||||
const char *path = uri_get_path(uri);
|
||||
if (path == nullptr)
|
||||
path = "/";
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a directory listing using WebDAV PROPFIND.
|
||||
*/
|
||||
class HttpListDirectoryOperation final : public PropfindOperation {
|
||||
const std::string base_path;
|
||||
|
||||
MemoryStorageDirectoryReader::List entries;
|
||||
|
||||
public:
|
||||
HttpListDirectoryOperation(CurlGlobal &curl, const char *uri)
|
||||
:PropfindOperation(curl, uri, 1),
|
||||
base_path(UriPathOrSlash(uri)) {}
|
||||
|
||||
StorageDirectoryReader *Perform() {
|
||||
Wait();
|
||||
return ToReader();
|
||||
}
|
||||
|
||||
private:
|
||||
StorageDirectoryReader *ToReader() {
|
||||
return new MemoryStorageDirectoryReader(std::move(entries));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a "href" attribute (which may be an absolute URI)
|
||||
* to the base file name.
|
||||
*/
|
||||
gcc_pure
|
||||
StringView HrefToEscapedName(const char *href) const {
|
||||
const char *path = uri_get_path(href);
|
||||
if (path == nullptr)
|
||||
return nullptr;
|
||||
|
||||
path = StringAfterPrefix(path, base_path.c_str());
|
||||
if (path == nullptr || *path == 0)
|
||||
return nullptr;
|
||||
|
||||
const char *slash = strchr(path, '/');
|
||||
if (slash == nullptr)
|
||||
/* regular file */
|
||||
return path;
|
||||
else if (slash[1] == 0)
|
||||
/* trailing slash: collection; strip the slash */
|
||||
return {path, slash};
|
||||
else
|
||||
/* strange, better ignore it */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
/* virtual methods from PropfindOperation */
|
||||
void OnDavResponse(DavResponse &&r) override {
|
||||
if (r.status != 200)
|
||||
return;
|
||||
|
||||
const auto escaped_name = HrefToEscapedName(r.href.c_str());
|
||||
if (escaped_name.IsNull())
|
||||
return;
|
||||
|
||||
// TODO: unescape
|
||||
const auto name = escaped_name;
|
||||
|
||||
entries.emplace_front(std::string(name.data, name.size));
|
||||
|
||||
auto &info = entries.front().info;
|
||||
info.type = r.collection
|
||||
? StorageFileInfo::Type::DIRECTORY
|
||||
: StorageFileInfo::Type::REGULAR;
|
||||
info.size = r.length;
|
||||
info.mtime = r.mtime > std::chrono::system_clock::time_point()
|
||||
? std::chrono::system_clock::to_time_t(r.mtime)
|
||||
: 0;
|
||||
info.device = info.inode = 0;
|
||||
}
|
||||
};
|
||||
|
||||
StorageDirectoryReader *
|
||||
CurlStorage::OpenDirectory(const char *uri_utf8)
|
||||
{
|
||||
// TODO: escape the given URI
|
||||
|
||||
std::string uri = base;
|
||||
uri += uri_utf8;
|
||||
|
||||
/* collection URIs must end with a slash */
|
||||
if (uri.back() != '/')
|
||||
uri.push_back('/');
|
||||
|
||||
return HttpListDirectoryOperation(*curl, uri.c_str()).Perform();
|
||||
}
|
||||
|
||||
static Storage *
|
||||
CreateCurlStorageURI(EventLoop &event_loop, const char *uri)
|
||||
{
|
||||
if (strncmp(uri, "http://", 7) != 0 &&
|
||||
strncmp(uri, "https://", 8) != 0)
|
||||
return nullptr;
|
||||
|
||||
return new CurlStorage(event_loop, uri);
|
||||
}
|
||||
|
||||
const StoragePlugin curl_storage_plugin = {
|
||||
"curl",
|
||||
CreateCurlStorageURI,
|
||||
};
|
||||
29
src/storage/plugins/CurlStorage.hxx
Normal file
29
src/storage/plugins/CurlStorage.hxx
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2003-2016 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_STORAGE_CURL_HXX
|
||||
#define MPD_STORAGE_CURL_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
struct StoragePlugin;
|
||||
|
||||
extern const StoragePlugin curl_storage_plugin;
|
||||
|
||||
#endif
|
||||
81
src/util/TimeParser.cxx
Normal file
81
src/util/TimeParser.cxx
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Max Kellermann <max@duempel.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "TimeParser.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
#if !defined(__GLIBC__) && !defined(WIN32)
|
||||
|
||||
/**
|
||||
* Determine the time zone offset in a portable way.
|
||||
*/
|
||||
gcc_const
|
||||
static time_t
|
||||
GetTimeZoneOffset()
|
||||
{
|
||||
time_t t = 1234567890;
|
||||
struct tm tm;
|
||||
tm.tm_isdst = 0;
|
||||
gmtime_r(&t, &tm);
|
||||
return t - mktime(&tm);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::chrono::system_clock::time_point
|
||||
ParseTimePoint(const char *s, const char *format)
|
||||
{
|
||||
assert(s != nullptr);
|
||||
assert(format != nullptr);
|
||||
|
||||
#ifdef WIN32
|
||||
/* TODO: emulate strptime()? */
|
||||
throw std::runtime_error("Time parsing not implemented on Windows");
|
||||
#else
|
||||
struct tm tm;
|
||||
const char *end = strptime(s, format, &tm);
|
||||
if (end == nullptr || *end != 0)
|
||||
throw std::runtime_error("Failed to parse time stamp");
|
||||
|
||||
#ifdef __GLIBC__
|
||||
/* timegm() is a GNU extension */
|
||||
const auto t = timegm(&tm);
|
||||
#else
|
||||
tm.tm_isdst = 0;
|
||||
const auto t = mktime(&tm) + GetTimeZoneOffset();
|
||||
#endif /* !__GLIBC__ */
|
||||
|
||||
return std::chrono::system_clock::from_time_t(t);
|
||||
|
||||
#endif /* !WIN32 */
|
||||
}
|
||||
46
src/util/TimeParser.hxx
Normal file
46
src/util/TimeParser.hxx
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Max Kellermann <max@duempel.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TIME_PARSER_HXX
|
||||
#define TIME_PARSER_HXX
|
||||
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* Parse a time stamp.
|
||||
*
|
||||
* Throws std::runtime_error on error.
|
||||
*/
|
||||
gcc_pure
|
||||
std::chrono::system_clock::time_point
|
||||
ParseTimePoint(const char *s, const char *format);
|
||||
|
||||
#endif
|
||||
@@ -19,10 +19,57 @@
|
||||
|
||||
#include "UriUtil.hxx"
|
||||
#include "StringCompare.hxx"
|
||||
#include "CharUtil.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr bool
|
||||
IsValidSchemeStart(char ch)
|
||||
{
|
||||
return IsLowerAlphaASCII(ch);
|
||||
}
|
||||
|
||||
static constexpr bool
|
||||
IsValidSchemeChar(char ch)
|
||||
{
|
||||
return IsLowerAlphaASCII(ch) || IsDigitASCII(ch) ||
|
||||
ch == '+' || ch == '.' || ch == '-';
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsValidScheme(StringView p)
|
||||
{
|
||||
if (p.IsEmpty() || !IsValidSchemeStart(p.front()))
|
||||
return false;
|
||||
|
||||
for (size_t i = 1; i < p.size; ++i)
|
||||
if (!IsValidSchemeChar(p[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URI part after the scheme specification (and after the
|
||||
* double slash).
|
||||
*/
|
||||
gcc_pure
|
||||
static const char *
|
||||
uri_after_scheme(const char *uri)
|
||||
{
|
||||
if (uri[0] == '/' && uri[1] == '/' && uri[2] != '/')
|
||||
return uri + 2;
|
||||
|
||||
const char *colon = strchr(uri, ':');
|
||||
return colon != nullptr &&
|
||||
IsValidScheme({uri, colon}) &&
|
||||
colon[1] == '/' && colon[2] == '/'
|
||||
? colon + 3
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool uri_has_scheme(const char *uri)
|
||||
{
|
||||
return strstr(uri, "://") != nullptr;
|
||||
@@ -38,6 +85,16 @@ uri_get_scheme(const char *uri)
|
||||
return std::string(uri, end);
|
||||
}
|
||||
|
||||
const char *
|
||||
uri_get_path(const char *uri)
|
||||
{
|
||||
const char *ap = uri_after_scheme(uri);
|
||||
if (ap != nullptr)
|
||||
return strchr(ap, '/');
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/* suffixes should be ascii only characters */
|
||||
const char *
|
||||
uri_get_suffix(const char *uri)
|
||||
|
||||
@@ -38,6 +38,14 @@ gcc_pure
|
||||
std::string
|
||||
uri_get_scheme(const char *uri);
|
||||
|
||||
/**
|
||||
* Returns the URI path (including the query string) or nullptr if the
|
||||
* given URI has no path.
|
||||
*/
|
||||
gcc_pure gcc_nonnull_all
|
||||
const char *
|
||||
uri_get_path(const char *uri);
|
||||
|
||||
gcc_pure
|
||||
const char *
|
||||
uri_get_suffix(const char *uri);
|
||||
|
||||
@@ -67,8 +67,13 @@ Ls(Storage &storage, const char *path)
|
||||
break;
|
||||
}
|
||||
|
||||
char mtime[32];
|
||||
strftime(mtime, sizeof(mtime), "%F", gmtime(&info.mtime));
|
||||
char mtime_buffer[32];
|
||||
const char *mtime = " ";
|
||||
if (info.mtime > 0) {
|
||||
strftime(mtime_buffer, sizeof(mtime_buffer), "%F",
|
||||
gmtime(&info.mtime));
|
||||
mtime = mtime_buffer;
|
||||
}
|
||||
|
||||
printf("%s %10llu %s %s\n",
|
||||
type, (unsigned long long)info.size,
|
||||
|
||||
Reference in New Issue
Block a user