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:
|
||||
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
|
||||
dist: trusty
|
||||
addons:
|
||||
|
@ -25,8 +47,9 @@ matrix:
|
|||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
env:
|
||||
# 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
|
||||
dist: trusty
|
||||
addons:
|
||||
|
@ -50,25 +73,37 @@ matrix:
|
|||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
env:
|
||||
# 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
|
||||
osx_image: xcode9.3beta
|
||||
osx_image: xcode9.4
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- ccache
|
||||
- meson
|
||||
env:
|
||||
- MATRIX_EVAL=""
|
||||
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||
|
||||
cache:
|
||||
- apt
|
||||
- ccache
|
||||
apt: true
|
||||
ccache: true
|
||||
directories:
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
|
||||
before_cache:
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
|
||||
|
||||
before_install:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew update
|
||||
|
||||
install:
|
||||
# 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
|
||||
|
||||
before_script:
|
||||
|
|
10
NEWS
10
NEWS
|
@ -30,6 +30,16 @@ ver 0.22 (not yet released)
|
|||
* switch to C++17
|
||||
- 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)
|
||||
* protocol
|
||||
- relax the ISO 8601 parser: allow omitting field separators, the
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="40"
|
||||
android:versionName="0.21.17">
|
||||
android:versionCode="41"
|
||||
android:versionName="0.21.18">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||
|
||||
|
|
|
@ -137,7 +137,8 @@ static constexpr int
|
|||
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -220,7 +221,6 @@ EventLoop::Run() noexcept
|
|||
} while (!quit);
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(busy);
|
||||
assert(thread.IsInside());
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <poll.h>
|
||||
#endif
|
||||
|
@ -37,17 +41,42 @@ MultiSocketMonitor::Reset() noexcept
|
|||
assert(GetEventLoop().IsInside());
|
||||
|
||||
fds.clear();
|
||||
#ifdef USE_EPOLL
|
||||
always_ready_fds.clear();
|
||||
#endif
|
||||
IdleMonitor::Cancel();
|
||||
timeout_event.Cancel();
|
||||
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
|
||||
MultiSocketMonitor::ClearSocketList() noexcept
|
||||
{
|
||||
assert(GetEventLoop().IsInside());
|
||||
|
||||
fds.clear();
|
||||
#ifdef USE_EPOLL
|
||||
always_ready_fds.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
|
@ -55,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
|
|||
void
|
||||
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||
{
|
||||
#ifdef USE_EPOLL
|
||||
always_ready_fds.clear();
|
||||
#endif
|
||||
|
||||
pollfd *const end = pfds + n;
|
||||
|
||||
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
|
||||
|
@ -64,9 +97,7 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
|||
if (i == end)
|
||||
return 0;
|
||||
|
||||
auto events = i->events;
|
||||
i->events = 0;
|
||||
return events;
|
||||
return std::exchange(i->events, 0);
|
||||
});
|
||||
|
||||
for (auto i = pfds; i != end; ++i)
|
||||
|
@ -79,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
|||
void
|
||||
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())
|
||||
timeout_event.Schedule(timeout);
|
||||
else
|
||||
|
|
|
@ -49,12 +49,10 @@ class MultiSocketMonitor : IdleMonitor
|
|||
unsigned revents;
|
||||
|
||||
public:
|
||||
SingleFD(MultiSocketMonitor &_multi, SocketDescriptor _fd,
|
||||
unsigned events) noexcept
|
||||
SingleFD(MultiSocketMonitor &_multi,
|
||||
SocketDescriptor _fd) noexcept
|
||||
:SocketMonitor(_fd, _multi.GetEventLoop()),
|
||||
multi(_multi), revents(0) {
|
||||
Schedule(events);
|
||||
}
|
||||
multi(_multi), revents(0) {}
|
||||
|
||||
SocketDescriptor GetSocket() const noexcept {
|
||||
return SocketMonitor::GetSocket();
|
||||
|
@ -85,8 +83,6 @@ class MultiSocketMonitor : IdleMonitor
|
|||
}
|
||||
};
|
||||
|
||||
friend class SingleFD;
|
||||
|
||||
TimerEvent timeout_event;
|
||||
|
||||
/**
|
||||
|
@ -105,6 +101,21 @@ class MultiSocketMonitor : IdleMonitor
|
|||
|
||||
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:
|
||||
static constexpr unsigned READ = SocketMonitor::READ;
|
||||
static constexpr unsigned WRITE = SocketMonitor::WRITE;
|
||||
|
@ -146,9 +157,7 @@ public:
|
|||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void AddSocket(SocketDescriptor fd, unsigned events) noexcept {
|
||||
fds.emplace_front(*this, fd, events);
|
||||
}
|
||||
bool AddSocket(SocketDescriptor fd, unsigned events) noexcept;
|
||||
|
||||
/**
|
||||
* Remove all sockets.
|
||||
|
@ -203,6 +212,11 @@ public:
|
|||
i.ClearReturnedEvents();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
for (const auto &i : always_ready_fds)
|
||||
f(i.fd, i.revents);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -231,7 +245,6 @@ private:
|
|||
|
||||
void OnTimeout() noexcept {
|
||||
SetReady();
|
||||
IdleMonitor::Schedule();
|
||||
}
|
||||
|
||||
virtual void OnIdle() noexcept final;
|
||||
|
|
|
@ -64,20 +64,24 @@ SocketMonitor::Close() noexcept
|
|||
Steal().Close();
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
SocketMonitor::Schedule(unsigned flags) noexcept
|
||||
{
|
||||
assert(IsDefined());
|
||||
|
||||
if (flags == GetScheduledFlags())
|
||||
return;
|
||||
return true;
|
||||
|
||||
bool success;
|
||||
if (scheduled_flags == 0)
|
||||
loop.AddFD(fd.Get(), flags, *this);
|
||||
success = loop.AddFD(fd.Get(), flags, *this);
|
||||
else if (flags == 0)
|
||||
loop.RemoveFD(fd.Get(), *this);
|
||||
success = loop.RemoveFD(fd.Get(), *this);
|
||||
else
|
||||
loop.ModifyFD(fd.Get(), flags, *this);
|
||||
success = loop.ModifyFD(fd.Get(), flags, *this);
|
||||
|
||||
if (success)
|
||||
scheduled_flags = flags;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -98,18 +98,22 @@ public:
|
|||
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 {
|
||||
Schedule(0);
|
||||
}
|
||||
|
||||
void ScheduleRead() noexcept {
|
||||
Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
||||
bool ScheduleRead() noexcept {
|
||||
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
||||
}
|
||||
|
||||
void ScheduleWrite() noexcept {
|
||||
Schedule(GetScheduledFlags() | WRITE);
|
||||
bool ScheduleWrite() noexcept {
|
||||
return Schedule(GetScheduledFlags() | WRITE);
|
||||
}
|
||||
|
||||
void CancelRead() noexcept {
|
||||
|
|
|
@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
|
|||
{
|
||||
BlockingCall(GetEventLoop(), [this](){
|
||||
FreeEasy();
|
||||
(*curl_init)->InvalidateSockets();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -162,7 +162,6 @@ CurlGlobal::Remove(CurlRequest &r) noexcept
|
|||
assert(GetEventLoop().IsInside());
|
||||
|
||||
curl_multi_remove_handle(multi.Get(), r.Get());
|
||||
InvalidateSockets();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -220,12 +219,12 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
|
|||
return;
|
||||
}
|
||||
|
||||
if (timeout_ms < 10)
|
||||
/* CURL 7.21.1 likes to report "timeout=0", which
|
||||
if (timeout_ms < 1)
|
||||
/* CURL's threaded resolver sets a timeout of 0ms, 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;
|
||||
of 1ms. */
|
||||
timeout_ms = 1;
|
||||
|
||||
timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
|
||||
}
|
||||
|
|
|
@ -67,16 +67,6 @@ public:
|
|||
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:
|
||||
/**
|
||||
* Check for finished HTTP responses.
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include "config.h"
|
||||
#include "Request.hxx"
|
||||
#include "Global.hxx"
|
||||
#include "Version.hxx"
|
||||
#include "Handler.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
@ -122,12 +121,6 @@ CurlRequest::Resume() noexcept
|
|||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include "CrossFade.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "util/Domain.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "Log.hxx"
|
||||
|
@ -1175,6 +1176,7 @@ try {
|
|||
}
|
||||
|
||||
/* fall through */
|
||||
gcc_fallthrough;
|
||||
|
||||
case PlayerCommand::PAUSE:
|
||||
next_song.reset();
|
||||
|
|
|
@ -105,7 +105,9 @@ public:
|
|||
BIND_THIS_METHOD(OnDeferredStart)),
|
||||
request(curl, uri, *this) {
|
||||
// TODO: use CurlInputStream's configuration
|
||||
}
|
||||
|
||||
void DeferStart() noexcept {
|
||||
/* start the transfer inside the IOThread */
|
||||
defer_start.Schedule();
|
||||
}
|
||||
|
@ -278,6 +280,7 @@ public:
|
|||
}
|
||||
|
||||
using BlockingHttpRequest::GetEasy;
|
||||
using BlockingHttpRequest::DeferStart;
|
||||
using BlockingHttpRequest::Wait;
|
||||
|
||||
protected:
|
||||
|
@ -425,6 +428,7 @@ public:
|
|||
}
|
||||
|
||||
const StorageFileInfo &Perform() {
|
||||
DeferStart();
|
||||
Wait();
|
||||
return info;
|
||||
}
|
||||
|
@ -476,6 +480,7 @@ public:
|
|||
base_path(UriPathOrSlash(uri)) {}
|
||||
|
||||
std::unique_ptr<StorageDirectoryReader> Perform() {
|
||||
DeferStart();
|
||||
Wait();
|
||||
return ToReader();
|
||||
}
|
||||
|
|
|
@ -58,6 +58,8 @@ FormatISO8601(std::chrono::system_clock::time_point tp)
|
|||
return FormatISO8601(GmTime(tp));
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
static std::pair<unsigned, unsigned>
|
||||
ParseTimeZoneOffsetRaw(const char *&s)
|
||||
{
|
||||
|
@ -108,6 +110,67 @@ ParseTimeZoneOffset(const char *&s)
|
|||
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::chrono::system_clock::duration>
|
||||
ParseISO8601(const char *s)
|
||||
|
@ -138,22 +201,9 @@ ParseISO8601(const char *s)
|
|||
if (*s == 'T') {
|
||||
++s;
|
||||
|
||||
if ((end = strptime(s, "%T", &tm)) != nullptr)
|
||||
precision = std::chrono::seconds(1);
|
||||
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
|
||||
s = ParseTimeOfDay(s, tm, precision);
|
||||
if (s == nullptr)
|
||||
throw std::runtime_error("Failed to parse time of day");
|
||||
|
||||
s = end;
|
||||
}
|
||||
|
||||
auto tp = TimeGm(tm);
|
||||
|
|
|
@ -143,6 +143,14 @@
|
|||
#define gcc_flatten
|
||||
#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
|
||||
/* plain C99 has "restrict" */
|
||||
#define gcc_restrict restrict
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "format.h"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
@ -238,6 +239,7 @@ format_object2(const char *format, const char **last, const void *object,
|
|||
}
|
||||
|
||||
/* fall through */
|
||||
gcc_fallthrough;
|
||||
|
||||
default:
|
||||
/* 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()
|
||||
executable(
|
||||
'RunCurl',
|
||||
'RunCurl.cxx',
|
||||
'ShutdownHandler.cxx',
|
||||
'../src/Log.cxx',
|
||||
'../src/LogBackend.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
curl_dep,
|
||||
],
|
||||
)
|
||||
|
||||
test('test_icy_parser', executable(
|
||||
'test_icy_parser',
|
||||
'test_icy_parser.cxx',
|
||||
|
|
|
@ -90,6 +90,29 @@ Ls(Storage &storage, const char *path)
|
|||
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
|
||||
main(int argc, char **argv)
|
||||
try {
|
||||
|
@ -117,6 +140,18 @@ try {
|
|||
storage_uri);
|
||||
|
||||
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 {
|
||||
fprintf(stderr, "Unknown command\n");
|
||||
return EXIT_FAILURE;
|
||||
|
|
Loading…
Reference in New Issue