Compare commits

...

34 Commits

Author SHA1 Message Date
Max Kellermann
bf41d1ad2b release v0.21.18 2019-12-24 16:13:16 +01:00
Max Kellermann
d27e534a85 time/ISO8601: fix Windows build failure
Caused by 2bc127bb43
2019-12-24 16:12:52 +01:00
Max Kellermann
6d54928d7c Revert "lib/curl/Global: remove lower bound on timeouts"
This reverts commit 4475b8ca04.  Further
testing revealed that the threaded resolver still uses a timeout of
0ms.  This revert however lowers the bound to a minimum of 1ms instead
of 10ms.
2019-12-24 16:09:26 +01:00
Max Kellermann
0dffe05bf7 input/curl: remove unnecessary InvalidateSockets() call
Like fe598e7d30
2019-12-24 11:41:52 +01:00
Max Kellermann
9ef1f10319 .travis.yml: install brew packages using addons/homebrew 2019-12-24 11:20:54 +01:00
Max Kellermann
23fcfdbd2a .travis.yml: remove "brew update"
The packages from the Travis image are good enough, and this speeds up
the Travis build.
2019-12-24 11:20:29 +01:00
Max Kellermann
3401d26d4c .travis.yml: switch to xcode9.4, the current default 2019-12-24 10:20:04 +01:00
Max Kellermann
256753ea46 .travis.yml: disable Homebrew analytics
Don't bother sending analytics data for a CI runner.  This adds
traffic but doesn't bring anybody any benefit.
2019-12-24 10:19:25 +01:00
Max Kellermann
76cd5f8595 .travis.yml: cache Homebrew on osx 2019-12-24 10:17:37 +01:00
Max Kellermann
5684025847 .travis.yml: change "cache" yaml syntax 2019-12-24 10:17:35 +01:00
Max Kellermann
744bd1eadc time/ISO8601: refactor ParseTimeOfDay() to parse one by one
This prepares the migration away from strptime() for Windows
portability.

But the real reason I'm doing this is that strptime() on Apple is
buggy: strptime("14", "%H%M%S") (without separating colons) succeeds
even though only the hour has been parsed.  This fixes recent Travis
failures in the ParseISO8601() unit test.
2019-12-24 10:15:03 +01:00
Max Kellermann
2bc127bb43 time/ISO8601: move code to ParseTimeOfDay() 2019-12-24 10:15:01 +01:00
Max Kellermann
7770298a65 util/Compiler.h: use [[fallthrough]] on clang
Older clang versions don't support the GCC __attribute__ syntax.  For
those, don't use anything at all, and new clang versions shall use the
standard syntax.
2019-12-24 08:04:48 +01:00
Max Kellermann
fa50cdb39e .travis.yml: escape dollar signs in MATRIX_EVAL
Expand $PATH at evaluation and not at assignment, which fixes the
problem that /usr/lib/ccache was added to $PATH between the
MATRIX_EVAL assignment and its evaluation.
2019-12-24 07:51:55 +01:00
Max Kellermann
816ef12088 .travis.yml: add Ubuntu Bionic build 2019-12-23 18:12:04 +01:00
Max Kellermann
5ff786e59c .travis.yml: enable ccache on Linux 2019-12-23 18:10:58 +01:00
Max Kellermann
80fe88e8f6 .travis.yml: enable ccache on osx 2019-12-23 18:10:52 +01:00
Max Kellermann
a1afe9afc6 util/Compiler.h: add gcc_fallthrough
Works around build failures with ccache which may feed processed code
to GCC, which doesn't have the "fall through" code comments.
2019-12-23 17:53:57 +01:00
Max Kellermann
fe598e7d30 lib/curl/Global: remove InvalidateSockets() call from Remove()
curl_multi_remove_handle() calls our socket function, and there's no
need to call curl_multi_socket_action().
2019-12-23 14:52:46 +01:00
Max Kellermann
4475b8ca04 lib/curl/Global: remove lower bound on timeouts
This was a problem 9 years ago, and apparently, it has been fixed long
ago.
2019-12-23 14:50:51 +01:00
Max Kellermann
a714bdb0ce lib/curl: drop support for CURL versions older than 7.32.0
For simplicity, this commit removes a workaround for an old CURL bug.
2019-12-23 14:41:06 +01:00
Max Kellermann
087874620f test/RunCurl: new debug program 2019-12-23 14:26:56 +01:00
Max Kellermann
f1116c9258 event/Loop: remove bogus assertion
Can fail if somebody calls Break().
2019-12-23 14:20:09 +01:00
Max Kellermann
d01fb6730a storage/curl: move start call out of the constructor
This can cause request completion in the I/O thread before this
constructor returns, leaving the object in an abstract state, causing
a crash due to pure virtual method call.  We should not start the
request until this object is fully constructed.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/665
2019-12-23 13:37:58 +01:00
Max Kellermann
7bfe6a3304 test/run_storage: add command "stat" 2019-12-22 19:54:31 +01:00
Max Kellermann
9a577f8060 event/MultiSocketMonitor: add workaround for /dev/null
The ALSA "null" driver opens /dev/null and returns the file handle
from snd_pcm_poll_descriptors(), but /dev/null cannot be used with
epoll, the epoll_ctl() system call returns -EPERM.  This means that
the ALSA output hangs, eventually freezing the whole MPD process.

This commit adds a workaround to the MultiSocketMonitor class which is
used by the ALSA output plugin.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/695
2019-12-22 12:08:44 +01:00
Max Kellermann
d75a0d714e event/MultiSocketMonitor: remove unnecessary friend declaration 2019-12-22 12:08:44 +01:00
Max Kellermann
9be3a1554e event/MultiSocketMonitor: remove duplicate IdleMonitor::Schedule() call
SetReady() does this already.
2019-12-22 12:08:10 +01:00
Max Kellermann
7764719513 event/MultiSocketMonitor: un-inline AddSocket() 2019-12-22 12:00:12 +01:00
Max Kellermann
dcbb9fe07c event/Loop: round timeout up to avoid unnecessary wakeups 2019-12-22 11:58:31 +01:00
Max Kellermann
e3b347820a event/MultiSocketMonitor: use std::exchange() 2019-12-20 13:42:35 +01:00
Max Kellermann
a84bf5a92e event/MultiSocketMonitor: AddSocket() returns bool 2019-12-18 17:50:21 +01:00
Max Kellermann
732bdc800d event/SocketMonitor: Schedule() returns bool 2019-12-18 17:46:33 +01:00
Max Kellermann
a8661b5931 increment version number to 0.21.18 2019-12-18 16:49:04 +01:00
23 changed files with 378 additions and 81 deletions

@@ -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

@@ -1,3 +1,13 @@
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"/>

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.17'
version = '0.21.18'
# The full version, including alpha/beta/rc tags.
release = version

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.17',
version: '0.21.18',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',

@@ -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

@@ -50,12 +50,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();
@@ -86,8 +84,6 @@ class MultiSocketMonitor : IdleMonitor
}
};
friend class SingleFD;
TimerEvent timeout_event;
/**
@@ -106,6 +102,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;
@@ -147,9 +158,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.
@@ -204,6 +213,11 @@ public:
i.ClearReturnedEvents();
}
}
#ifdef USE_EPOLL
for (const auto &i : always_ready_fds)
f(i.fd, i.revents);
#endif
}
protected:
@@ -232,7 +246,6 @@ private:
void OnTimeout() noexcept {
SetReady();
IdleMonitor::Schedule();
}
virtual void OnIdle() noexcept final;

@@ -68,20 +68,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);
scheduled_flags = flags;
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();
});
}

@@ -181,8 +181,6 @@ CurlGlobal::Remove(CURL *easy) noexcept
assert(easy != nullptr);
curl_multi_remove_handle(multi.Get(), easy);
InvalidateSockets();
}
static CurlRequest *
@@ -227,12 +225,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));
}

@@ -74,16 +74,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:
void UpdateTimeout(long timeout_ms) noexcept;
static int TimerFunction(CURLM *global, long timeout_ms,

@@ -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"
@@ -124,12 +123,6 @@ CurlRequest::Resume() noexcept
curl_easy_pause(easy.Get(), CURLPAUSE_CONT);
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();
}

@@ -1,4 +1,4 @@
curl_dep = dependency('libcurl', version: '>= 7.18', required: get_option('curl'))
curl_dep = dependency('libcurl', version: '>= 7.32', required: get_option('curl'))
conf.set('ENABLE_CURL', curl_dep.found())
if not curl_dep.found()
subdir_done()

@@ -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"
@@ -1171,6 +1172,7 @@ try {
}
/* fall through */
gcc_fallthrough;
case PlayerCommand::PAUSE:
next_song.reset();

@@ -109,7 +109,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();
}
@@ -283,6 +285,7 @@ public:
}
using BlockingHttpRequest::GetEasy;
using BlockingHttpRequest::DeferStart;
using BlockingHttpRequest::Wait;
protected:
@@ -430,6 +433,7 @@ public:
}
const StorageFileInfo &Perform() {
DeferStart();
Wait();
return info;
}
@@ -481,6 +485,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 */

93
test/RunCurl.cxx Normal file

@@ -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;
}

@@ -334,6 +334,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;