release v0.21.18
-----BEGIN PGP SIGNATURE----- iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAl4CKwwQHG1heEBtdXNp Y3BkLm9yZwAKCRAjbopYxttFEp46D/9q1pRkhYzCyZPQGX967UPoV+Bc1YMX4o2U Uh/HqQrKKRAK9goaxu9yMKBIFCbzj6WEZou+/uMEf6ZwXuRcnJMobrUU4+G9Yu3r FzlQPW870DyOhk2PWHF8CW3oMt/YH05b6nYNo2ocRnX69/oqL1G7ukbF2Pz3TPc0 0uNQYYERdMawNCEi1OarzziR6DKuiN+MtZRgUQVacAsoUirwWGNJuaGSDtc3MMM7 YhgKVmd9XsgVr7fykArj6PLsm2iyXJP5nDB/tIqmwMpQFyhuLUnGOMfhCq02em+r 47LGvmZiSS/9F2JzPU8EL2yzYdBe4QvU6Ol5SfXbom11MZc3Ty502g2jUXVHjCeo 1FljCPHbarTmKhvUc0xQXA9i6exZ0wwtxL+Zv7ZQKquRPAhq8E07qkQpdaTWa6vn 3RfilLE1B/GCgoT6D1+zABxdJ1HRLT7tzFob2kkCccxguK3j2JjCUIkSgM6IY0yv a6sMEXbqa+Lh8jggs9ksoT6O+T2HHEr3tEfpFHY+t0NFRfwHd9aX9bNjK+Ji0n51 YBf2FCb2EBrMAWNZmEnq+TuKX0HASDtoAXTviRKqBXtEG5V8MdAt4PPICCb+bkDr psIuYcGeK3vofvq1SwEC7h7gCvsBK5w17/oE2/jYcZLo12H1IdHe5gMP3OUhjel5 BRi6rLlkYw== =XXXs -----END PGP SIGNATURE----- Merge tag 'v0.21.18' release v0.21.18
This commit is contained in:
commit
803a48e96d
53
.travis.yml
53
.travis.yml
|
@ -2,6 +2,28 @@ language: cpp
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
# Ubuntu Bionic (18.04) with GCC 7
|
||||||
|
- os: linux
|
||||||
|
dist: bionic
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||||
|
packages:
|
||||||
|
- libgtest-dev
|
||||||
|
- libboost-dev
|
||||||
|
- python3.6
|
||||||
|
- python3-urllib3
|
||||||
|
- ninja-build
|
||||||
|
before_install:
|
||||||
|
- wget https://bootstrap.pypa.io/get-pip.py
|
||||||
|
- /usr/bin/python3.6 get-pip.py --user
|
||||||
|
install:
|
||||||
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
|
# Ubuntu Trusty (16.04) with GCC 6
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
|
@ -25,8 +47,9 @@ matrix:
|
||||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||||
env:
|
env:
|
||||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||||
- MATRIX_EVAL="export CC=gcc-6 CXX=g++-6 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
|
# Ubuntu Trusty (16.04) with GCC 8
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
|
@ -50,25 +73,37 @@ matrix:
|
||||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||||
env:
|
env:
|
||||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||||
- MATRIX_EVAL="export CC=gcc-8 CXX=g++-8 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode9.3beta
|
osx_image: xcode9.4
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- ccache
|
||||||
|
- meson
|
||||||
env:
|
env:
|
||||||
- MATRIX_EVAL=""
|
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- apt
|
apt: true
|
||||||
- ccache
|
ccache: true
|
||||||
|
directories:
|
||||||
|
- $HOME/Library/Caches/Homebrew
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- eval "${MATRIX_EVAL}"
|
- eval "${MATRIX_EVAL}"
|
||||||
# C++14
|
|
||||||
- test "$TRAVIS_OS_NAME" != "osx" || brew update
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# C++14
|
# C++14
|
||||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install ccache meson
|
|
||||||
|
# Work around "Target /usr/local/lib/libgtest.a is a symlink
|
||||||
|
# belonging to nss. You can unlink it" during gtest install
|
||||||
|
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
|
||||||
|
|
||||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
|
10
NEWS
10
NEWS
|
@ -30,6 +30,16 @@ ver 0.22 (not yet released)
|
||||||
* switch to C++17
|
* switch to C++17
|
||||||
- GCC 7 or clang 4 (or newer) recommended
|
- GCC 7 or clang 4 (or newer) recommended
|
||||||
|
|
||||||
|
ver 0.21.18 (2019/12/24)
|
||||||
|
* protocol
|
||||||
|
- work around Mac OS X bug in the ISO 8601 parser
|
||||||
|
* output
|
||||||
|
- alsa: fix hang bug with ALSA "null" outputs
|
||||||
|
* storage
|
||||||
|
- curl: fix crash bug
|
||||||
|
* drop support for CURL versions older than 7.32.0
|
||||||
|
* reduce unnecessary CPU wakeups
|
||||||
|
|
||||||
ver 0.21.17 (2019/12/16)
|
ver 0.21.17 (2019/12/16)
|
||||||
* protocol
|
* protocol
|
||||||
- relax the ISO 8601 parser: allow omitting field separators, the
|
- relax the ISO 8601 parser: allow omitting field separators, the
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="40"
|
android:versionCode="41"
|
||||||
android:versionName="0.21.17">
|
android:versionName="0.21.18">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,8 @@ static constexpr int
|
||||||
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
||||||
{
|
{
|
||||||
return timeout >= timeout.zero()
|
return timeout >= timeout.zero()
|
||||||
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count())
|
/* round up (+1) to avoid unnecessary wakeups */
|
||||||
|
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
|
||||||
: -1;
|
: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +221,6 @@ EventLoop::Run() noexcept
|
||||||
} while (!quit);
|
} while (!quit);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
assert(busy);
|
|
||||||
assert(thread.IsInside());
|
assert(thread.IsInside());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
#include <errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -37,17 +41,42 @@ MultiSocketMonitor::Reset() noexcept
|
||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
|
|
||||||
fds.clear();
|
fds.clear();
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
always_ready_fds.clear();
|
||||||
|
#endif
|
||||||
IdleMonitor::Cancel();
|
IdleMonitor::Cancel();
|
||||||
timeout_event.Cancel();
|
timeout_event.Cancel();
|
||||||
ready = refresh = false;
|
ready = refresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept
|
||||||
|
{
|
||||||
|
fds.emplace_front(*this, fd);
|
||||||
|
bool success = fds.front().Schedule(events);
|
||||||
|
if (!success) {
|
||||||
|
fds.pop_front();
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
if (errno == EPERM)
|
||||||
|
/* not supported by epoll (e.g. "/dev/null"):
|
||||||
|
add it to the "always ready" list */
|
||||||
|
always_ready_fds.push_front({fd, events});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MultiSocketMonitor::ClearSocketList() noexcept
|
MultiSocketMonitor::ClearSocketList() noexcept
|
||||||
{
|
{
|
||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
|
|
||||||
fds.clear();
|
fds.clear();
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
always_ready_fds.clear();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -55,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
|
||||||
void
|
void
|
||||||
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||||
{
|
{
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
always_ready_fds.clear();
|
||||||
|
#endif
|
||||||
|
|
||||||
pollfd *const end = pfds + n;
|
pollfd *const end = pfds + n;
|
||||||
|
|
||||||
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
|
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
|
||||||
|
@ -64,9 +97,7 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||||
if (i == end)
|
if (i == end)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auto events = i->events;
|
return std::exchange(i->events, 0);
|
||||||
i->events = 0;
|
|
||||||
return events;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (auto i = pfds; i != end; ++i)
|
for (auto i = pfds; i != end; ++i)
|
||||||
|
@ -79,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||||
void
|
void
|
||||||
MultiSocketMonitor::Prepare() noexcept
|
MultiSocketMonitor::Prepare() noexcept
|
||||||
{
|
{
|
||||||
const auto timeout = PrepareSockets();
|
auto timeout = PrepareSockets();
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
if (!always_ready_fds.empty()) {
|
||||||
|
/* if there was at least one file descriptor not
|
||||||
|
supported by epoll, install a very short timeout
|
||||||
|
because we assume it's always ready */
|
||||||
|
constexpr std::chrono::steady_clock::duration ready_timeout =
|
||||||
|
std::chrono::milliseconds(1);
|
||||||
|
if (timeout < timeout.zero() || timeout > ready_timeout)
|
||||||
|
timeout = ready_timeout;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (timeout >= timeout.zero())
|
if (timeout >= timeout.zero())
|
||||||
timeout_event.Schedule(timeout);
|
timeout_event.Schedule(timeout);
|
||||||
else
|
else
|
||||||
|
|
|
@ -49,12 +49,10 @@ class MultiSocketMonitor : IdleMonitor
|
||||||
unsigned revents;
|
unsigned revents;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SingleFD(MultiSocketMonitor &_multi, SocketDescriptor _fd,
|
SingleFD(MultiSocketMonitor &_multi,
|
||||||
unsigned events) noexcept
|
SocketDescriptor _fd) noexcept
|
||||||
:SocketMonitor(_fd, _multi.GetEventLoop()),
|
:SocketMonitor(_fd, _multi.GetEventLoop()),
|
||||||
multi(_multi), revents(0) {
|
multi(_multi), revents(0) {}
|
||||||
Schedule(events);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketDescriptor GetSocket() const noexcept {
|
SocketDescriptor GetSocket() const noexcept {
|
||||||
return SocketMonitor::GetSocket();
|
return SocketMonitor::GetSocket();
|
||||||
|
@ -85,8 +83,6 @@ class MultiSocketMonitor : IdleMonitor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
friend class SingleFD;
|
|
||||||
|
|
||||||
TimerEvent timeout_event;
|
TimerEvent timeout_event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,6 +101,21 @@ class MultiSocketMonitor : IdleMonitor
|
||||||
|
|
||||||
std::forward_list<SingleFD> fds;
|
std::forward_list<SingleFD> fds;
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
struct AlwaysReady {
|
||||||
|
const SocketDescriptor fd;
|
||||||
|
const unsigned revents;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of file descriptors which are always ready. This is
|
||||||
|
* a kludge needed because the ALSA output plugin gives us a
|
||||||
|
* file descriptor to /dev/null, which is incompatible with
|
||||||
|
* epoll (epoll_ctl() returns -EPERM).
|
||||||
|
*/
|
||||||
|
std::forward_list<AlwaysReady> always_ready_fds;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr unsigned READ = SocketMonitor::READ;
|
static constexpr unsigned READ = SocketMonitor::READ;
|
||||||
static constexpr unsigned WRITE = SocketMonitor::WRITE;
|
static constexpr unsigned WRITE = SocketMonitor::WRITE;
|
||||||
|
@ -146,9 +157,7 @@ public:
|
||||||
*
|
*
|
||||||
* May only be called from PrepareSockets().
|
* May only be called from PrepareSockets().
|
||||||
*/
|
*/
|
||||||
void AddSocket(SocketDescriptor fd, unsigned events) noexcept {
|
bool AddSocket(SocketDescriptor fd, unsigned events) noexcept;
|
||||||
fds.emplace_front(*this, fd, events);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all sockets.
|
* Remove all sockets.
|
||||||
|
@ -203,6 +212,11 @@ public:
|
||||||
i.ClearReturnedEvents();
|
i.ClearReturnedEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
for (const auto &i : always_ready_fds)
|
||||||
|
f(i.fd, i.revents);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -231,7 +245,6 @@ private:
|
||||||
|
|
||||||
void OnTimeout() noexcept {
|
void OnTimeout() noexcept {
|
||||||
SetReady();
|
SetReady();
|
||||||
IdleMonitor::Schedule();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnIdle() noexcept final;
|
virtual void OnIdle() noexcept final;
|
||||||
|
|
|
@ -64,20 +64,24 @@ SocketMonitor::Close() noexcept
|
||||||
Steal().Close();
|
Steal().Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
SocketMonitor::Schedule(unsigned flags) noexcept
|
SocketMonitor::Schedule(unsigned flags) noexcept
|
||||||
{
|
{
|
||||||
assert(IsDefined());
|
assert(IsDefined());
|
||||||
|
|
||||||
if (flags == GetScheduledFlags())
|
if (flags == GetScheduledFlags())
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
|
bool success;
|
||||||
if (scheduled_flags == 0)
|
if (scheduled_flags == 0)
|
||||||
loop.AddFD(fd.Get(), flags, *this);
|
success = loop.AddFD(fd.Get(), flags, *this);
|
||||||
else if (flags == 0)
|
else if (flags == 0)
|
||||||
loop.RemoveFD(fd.Get(), *this);
|
success = loop.RemoveFD(fd.Get(), *this);
|
||||||
else
|
else
|
||||||
loop.ModifyFD(fd.Get(), flags, *this);
|
success = loop.ModifyFD(fd.Get(), flags, *this);
|
||||||
|
|
||||||
scheduled_flags = flags;
|
if (success)
|
||||||
|
scheduled_flags = flags;
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,18 +98,22 @@ public:
|
||||||
return scheduled_flags;
|
return scheduled_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Schedule(unsigned flags) noexcept;
|
/**
|
||||||
|
* @return true on success, false on error (with errno set if
|
||||||
|
* USE_EPOLL is defined)
|
||||||
|
*/
|
||||||
|
bool Schedule(unsigned flags) noexcept;
|
||||||
|
|
||||||
void Cancel() noexcept {
|
void Cancel() noexcept {
|
||||||
Schedule(0);
|
Schedule(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScheduleRead() noexcept {
|
bool ScheduleRead() noexcept {
|
||||||
Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScheduleWrite() noexcept {
|
bool ScheduleWrite() noexcept {
|
||||||
Schedule(GetScheduledFlags() | WRITE);
|
return Schedule(GetScheduledFlags() | WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CancelRead() noexcept {
|
void CancelRead() noexcept {
|
||||||
|
|
|
@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
|
||||||
{
|
{
|
||||||
BlockingCall(GetEventLoop(), [this](){
|
BlockingCall(GetEventLoop(), [this](){
|
||||||
FreeEasy();
|
FreeEasy();
|
||||||
(*curl_init)->InvalidateSockets();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,6 @@ CurlGlobal::Remove(CurlRequest &r) noexcept
|
||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
|
|
||||||
curl_multi_remove_handle(multi.Get(), r.Get());
|
curl_multi_remove_handle(multi.Get(), r.Get());
|
||||||
InvalidateSockets();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,12 +219,12 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout_ms < 10)
|
if (timeout_ms < 1)
|
||||||
/* CURL 7.21.1 likes to report "timeout=0", which
|
/* CURL's threaded resolver sets a timeout of 0ms, which
|
||||||
means we're running in a busy loop. Quite a bad
|
means we're running in a busy loop. Quite a bad
|
||||||
idea to waste so much CPU. Let's use a lower limit
|
idea to waste so much CPU. Let's use a lower limit
|
||||||
of 10ms. */
|
of 1ms. */
|
||||||
timeout_ms = 10;
|
timeout_ms = 1;
|
||||||
|
|
||||||
timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
|
timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,16 +67,6 @@ public:
|
||||||
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a kludge to allow pausing/resuming a stream with
|
|
||||||
* libcurl < 7.32.0. Read the curl_easy_pause manpage for
|
|
||||||
* more information.
|
|
||||||
*/
|
|
||||||
void ResumeSockets() {
|
|
||||||
int running_handles;
|
|
||||||
curl_multi_socket_all(multi.Get(), &running_handles);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Check for finished HTTP responses.
|
* Check for finished HTTP responses.
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "Request.hxx"
|
#include "Request.hxx"
|
||||||
#include "Global.hxx"
|
#include "Global.hxx"
|
||||||
#include "Version.hxx"
|
|
||||||
#include "Handler.hxx"
|
#include "Handler.hxx"
|
||||||
#include "event/Call.hxx"
|
#include "event/Call.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
@ -122,12 +121,6 @@ CurlRequest::Resume() noexcept
|
||||||
|
|
||||||
easy.Unpause();
|
easy.Unpause();
|
||||||
|
|
||||||
if (IsCurlOlderThan(0x072000))
|
|
||||||
/* libcurl older than 7.32.0 does not update
|
|
||||||
its sockets after curl_easy_pause(); force
|
|
||||||
libcurl to do it now */
|
|
||||||
global.ResumeSockets();
|
|
||||||
|
|
||||||
global.InvalidateSockets();
|
global.InvalidateSockets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
#include "CrossFade.hxx"
|
#include "CrossFade.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
|
#include "util/Compiler.h"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "thread/Name.hxx"
|
#include "thread/Name.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
@ -1175,6 +1176,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fall through */
|
/* fall through */
|
||||||
|
gcc_fallthrough;
|
||||||
|
|
||||||
case PlayerCommand::PAUSE:
|
case PlayerCommand::PAUSE:
|
||||||
next_song.reset();
|
next_song.reset();
|
||||||
|
|
|
@ -105,7 +105,9 @@ public:
|
||||||
BIND_THIS_METHOD(OnDeferredStart)),
|
BIND_THIS_METHOD(OnDeferredStart)),
|
||||||
request(curl, uri, *this) {
|
request(curl, uri, *this) {
|
||||||
// TODO: use CurlInputStream's configuration
|
// TODO: use CurlInputStream's configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeferStart() noexcept {
|
||||||
/* start the transfer inside the IOThread */
|
/* start the transfer inside the IOThread */
|
||||||
defer_start.Schedule();
|
defer_start.Schedule();
|
||||||
}
|
}
|
||||||
|
@ -278,6 +280,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
using BlockingHttpRequest::GetEasy;
|
using BlockingHttpRequest::GetEasy;
|
||||||
|
using BlockingHttpRequest::DeferStart;
|
||||||
using BlockingHttpRequest::Wait;
|
using BlockingHttpRequest::Wait;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -425,6 +428,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
const StorageFileInfo &Perform() {
|
const StorageFileInfo &Perform() {
|
||||||
|
DeferStart();
|
||||||
Wait();
|
Wait();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -476,6 +480,7 @@ public:
|
||||||
base_path(UriPathOrSlash(uri)) {}
|
base_path(UriPathOrSlash(uri)) {}
|
||||||
|
|
||||||
std::unique_ptr<StorageDirectoryReader> Perform() {
|
std::unique_ptr<StorageDirectoryReader> Perform() {
|
||||||
|
DeferStart();
|
||||||
Wait();
|
Wait();
|
||||||
return ToReader();
|
return ToReader();
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,8 @@ FormatISO8601(std::chrono::system_clock::time_point tp)
|
||||||
return FormatISO8601(GmTime(tp));
|
return FormatISO8601(GmTime(tp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
static std::pair<unsigned, unsigned>
|
static std::pair<unsigned, unsigned>
|
||||||
ParseTimeZoneOffsetRaw(const char *&s)
|
ParseTimeZoneOffsetRaw(const char *&s)
|
||||||
{
|
{
|
||||||
|
@ -108,6 +110,67 @@ ParseTimeZoneOffset(const char *&s)
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
ParseTimeOfDay(const char *s, struct tm &tm,
|
||||||
|
std::chrono::system_clock::duration &precision) noexcept
|
||||||
|
{
|
||||||
|
/* this function always checks "end==s" to work around a
|
||||||
|
strptime() bug on OS X: if nothing could be parsed,
|
||||||
|
strptime() returns the input string (indicating success)
|
||||||
|
instead of nullptr (indicating error) */
|
||||||
|
|
||||||
|
const char *end = strptime(s, "%H", &tm);
|
||||||
|
if (end == nullptr || end == s)
|
||||||
|
return end;
|
||||||
|
|
||||||
|
s = end;
|
||||||
|
precision = std::chrono::hours(1);
|
||||||
|
|
||||||
|
if (*s == ':') {
|
||||||
|
/* with field separators: now a minute must follow */
|
||||||
|
|
||||||
|
++s;
|
||||||
|
|
||||||
|
end = strptime(s, "%M", &tm);
|
||||||
|
if (end == nullptr || end == s)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
s = end;
|
||||||
|
precision = std::chrono::minutes(1);
|
||||||
|
|
||||||
|
/* the "seconds" field is optional */
|
||||||
|
if (*s != ':')
|
||||||
|
return s;
|
||||||
|
|
||||||
|
++s;
|
||||||
|
|
||||||
|
end = strptime(s, "%S", &tm);
|
||||||
|
if (end == nullptr || end == s)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
precision = std::chrono::seconds(1);
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* without field separators */
|
||||||
|
|
||||||
|
end = strptime(s, "%M", &tm);
|
||||||
|
if (end == nullptr || end == s)
|
||||||
|
return s;
|
||||||
|
|
||||||
|
s = end;
|
||||||
|
precision = std::chrono::minutes(1);
|
||||||
|
|
||||||
|
end = strptime(s, "%S", &tm);
|
||||||
|
if (end == nullptr || end == s)
|
||||||
|
return s;
|
||||||
|
|
||||||
|
precision = std::chrono::seconds(1);
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
std::pair<std::chrono::system_clock::time_point,
|
std::pair<std::chrono::system_clock::time_point,
|
||||||
std::chrono::system_clock::duration>
|
std::chrono::system_clock::duration>
|
||||||
ParseISO8601(const char *s)
|
ParseISO8601(const char *s)
|
||||||
|
@ -138,22 +201,9 @@ ParseISO8601(const char *s)
|
||||||
if (*s == 'T') {
|
if (*s == 'T') {
|
||||||
++s;
|
++s;
|
||||||
|
|
||||||
if ((end = strptime(s, "%T", &tm)) != nullptr)
|
s = ParseTimeOfDay(s, tm, precision);
|
||||||
precision = std::chrono::seconds(1);
|
if (s == nullptr)
|
||||||
else if ((end = strptime(s, "%H%M%S", &tm)) != nullptr)
|
|
||||||
/* no field separators */
|
|
||||||
precision = std::chrono::seconds(1);
|
|
||||||
else if ((end = strptime(s, "%H%M", &tm)) != nullptr)
|
|
||||||
/* no field separators */
|
|
||||||
precision = std::chrono::minutes(1);
|
|
||||||
else if ((end = strptime(s, "%H:%M", &tm)) != nullptr)
|
|
||||||
precision = std::chrono::minutes(1);
|
|
||||||
else if ((end = strptime(s, "%H", &tm)) != nullptr)
|
|
||||||
precision = std::chrono::hours(1);
|
|
||||||
else
|
|
||||||
throw std::runtime_error("Failed to parse time of day");
|
throw std::runtime_error("Failed to parse time of day");
|
||||||
|
|
||||||
s = end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tp = TimeGm(tm);
|
auto tp = TimeGm(tm);
|
||||||
|
|
|
@ -143,6 +143,14 @@
|
||||||
#define gcc_flatten
|
#define gcc_flatten
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if GCC_CHECK_VERSION(7,0)
|
||||||
|
#define gcc_fallthrough __attribute__((fallthrough))
|
||||||
|
#elif CLANG_CHECK_VERSION(10,0)
|
||||||
|
#define gcc_fallthrough [[fallthrough]]
|
||||||
|
#else
|
||||||
|
#define gcc_fallthrough
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef __cplusplus
|
#ifndef __cplusplus
|
||||||
/* plain C99 has "restrict" */
|
/* plain C99 has "restrict" */
|
||||||
#define gcc_restrict restrict
|
#define gcc_restrict restrict
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -238,6 +239,7 @@ format_object2(const char *format, const char **last, const void *object,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fall through */
|
/* fall through */
|
||||||
|
gcc_fallthrough;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
/* pass-through non-escaped portions of the format string */
|
/* pass-through non-escaped portions of the format string */
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2003-2019 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 "ShutdownHandler.hxx"
|
||||||
|
#include "lib/curl/Global.hxx"
|
||||||
|
#include "lib/curl/Request.hxx"
|
||||||
|
#include "lib/curl/Handler.hxx"
|
||||||
|
#include "event/Loop.hxx"
|
||||||
|
#include "util/PrintException.hxx"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
class MyHandler final : public CurlResponseHandler {
|
||||||
|
EventLoop &event_loop;
|
||||||
|
|
||||||
|
std::exception_ptr error;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MyHandler(EventLoop &_event_loop) noexcept
|
||||||
|
:event_loop(_event_loop) {}
|
||||||
|
|
||||||
|
void Finish() {
|
||||||
|
if (error)
|
||||||
|
std::rethrow_exception(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual methods from CurlResponseHandler */
|
||||||
|
void OnHeaders(unsigned status,
|
||||||
|
std::multimap<std::string, std::string> &&headers) override {
|
||||||
|
fprintf(stderr, "status: %u\n", status);
|
||||||
|
for (const auto &i : headers)
|
||||||
|
fprintf(stderr, "%s: %s\n",
|
||||||
|
i.first.c_str(), i.second.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnData(ConstBuffer<void> data) override {
|
||||||
|
if (fwrite(data.data, data.size, 1, stdout) != 1)
|
||||||
|
throw std::runtime_error("Failed to write");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEnd() override {
|
||||||
|
event_loop.Break();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnError(std::exception_ptr e) noexcept override {
|
||||||
|
error = std::move(e);
|
||||||
|
event_loop.Break();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv) noexcept
|
||||||
|
try {
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf(stderr, "Usage: RunCurl URI\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *const uri = argv[1];
|
||||||
|
|
||||||
|
EventLoop event_loop;
|
||||||
|
const ShutdownHandler shutdown_handler(event_loop);
|
||||||
|
CurlGlobal curl_global(event_loop);
|
||||||
|
|
||||||
|
MyHandler handler(event_loop);
|
||||||
|
CurlRequest request(curl_global, uri, handler);
|
||||||
|
request.Start();
|
||||||
|
|
||||||
|
event_loop.Run();
|
||||||
|
|
||||||
|
handler.Finish();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
} catch (...) {
|
||||||
|
PrintException(std::current_exception());
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
|
@ -342,6 +342,18 @@ executable(
|
||||||
)
|
)
|
||||||
|
|
||||||
if curl_dep.found()
|
if curl_dep.found()
|
||||||
|
executable(
|
||||||
|
'RunCurl',
|
||||||
|
'RunCurl.cxx',
|
||||||
|
'ShutdownHandler.cxx',
|
||||||
|
'../src/Log.cxx',
|
||||||
|
'../src/LogBackend.cxx',
|
||||||
|
include_directories: inc,
|
||||||
|
dependencies: [
|
||||||
|
curl_dep,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
test('test_icy_parser', executable(
|
test('test_icy_parser', executable(
|
||||||
'test_icy_parser',
|
'test_icy_parser',
|
||||||
'test_icy_parser.cxx',
|
'test_icy_parser.cxx',
|
||||||
|
|
|
@ -90,6 +90,29 @@ Ls(Storage &storage, const char *path)
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
Stat(Storage &storage, const char *path)
|
||||||
|
{
|
||||||
|
const auto info = storage.GetInfo(path, false);
|
||||||
|
switch (info.type) {
|
||||||
|
case StorageFileInfo::Type::OTHER:
|
||||||
|
printf("other\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StorageFileInfo::Type::REGULAR:
|
||||||
|
printf("regular\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StorageFileInfo::Type::DIRECTORY:
|
||||||
|
printf("directory\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("size: %llu\n", (unsigned long long)info.size);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
try {
|
try {
|
||||||
|
@ -117,6 +140,18 @@ try {
|
||||||
storage_uri);
|
storage_uri);
|
||||||
|
|
||||||
return Ls(*storage, path);
|
return Ls(*storage, path);
|
||||||
|
} else if (strcmp(command, "stat") == 0) {
|
||||||
|
if (argc != 4) {
|
||||||
|
fprintf(stderr, "Usage: run_storage stat URI PATH\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *const path = argv[3];
|
||||||
|
|
||||||
|
auto storage = MakeStorage(io_thread.GetEventLoop(),
|
||||||
|
storage_uri);
|
||||||
|
|
||||||
|
return Stat(*storage, path);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Unknown command\n");
|
fprintf(stderr, "Unknown command\n");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
Loading…
Reference in New Issue