Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1f28790476 | ||
![]() |
c8dae95eff | ||
![]() |
547a084c7e | ||
![]() |
493677ff81 | ||
![]() |
6b430ba271 | ||
![]() |
bc6924d303 | ||
![]() |
02b00f9146 | ||
![]() |
e807ed5870 | ||
![]() |
f08944253b | ||
![]() |
792d6584b9 | ||
![]() |
7b45d01462 | ||
![]() |
5c17b2966a | ||
![]() |
0c54f29446 | ||
![]() |
9c3cf39fdd | ||
![]() |
d2fb229685 | ||
![]() |
f55bc6682f | ||
![]() |
6857286b42 | ||
![]() |
c0d5bd2048 | ||
![]() |
666e5d7904 | ||
![]() |
3613407ac5 | ||
![]() |
c32dceb4d4 | ||
![]() |
5573e78364 | ||
![]() |
807a19889f | ||
![]() |
df7242de91 | ||
![]() |
d62426f168 | ||
![]() |
1714cf3417 | ||
![]() |
1080c917be | ||
![]() |
8eb3164878 | ||
![]() |
915c5442d1 | ||
![]() |
be0360d5e8 | ||
![]() |
4d6ae6ffdd | ||
![]() |
ecee6f415b | ||
![]() |
47680f936b | ||
![]() |
2d7181105d | ||
![]() |
9bdc75524b | ||
![]() |
2f6ceb4949 | ||
![]() |
cd933aa35f | ||
![]() |
138738075b | ||
![]() |
2ee57f9b0d | ||
![]() |
5a5655b790 | ||
![]() |
b88d1e6820 | ||
![]() |
19d2864c34 | ||
![]() |
29e3a17f26 | ||
![]() |
252e9f736f | ||
![]() |
5d08988dda | ||
![]() |
47ca4246aa | ||
![]() |
f8338d4f00 | ||
![]() |
5cf6032c90 | ||
![]() |
8d8b77412d | ||
![]() |
fd9114e7e2 | ||
![]() |
a3fba2f8f7 | ||
![]() |
e2b671f1b2 | ||
![]() |
2a35fbe29e | ||
![]() |
81cde72fd0 | ||
![]() |
bf9ffba4f7 | ||
![]() |
c975d8b943 | ||
![]() |
2730f91872 | ||
![]() |
97ca85e155 | ||
![]() |
39bb4c5871 | ||
![]() |
bdceb90c59 | ||
![]() |
8bd1b5228c | ||
![]() |
a009e95afd | ||
![]() |
32aafb3572 | ||
![]() |
b577783cf0 | ||
![]() |
aa7b872a14 | ||
![]() |
c6f7f57776 | ||
![]() |
106ad08cd2 |
.github
NEWSdoc
meson.buildpython/build
src
Log.hxx
apple
client
command
decoder
input
plugins
lib
curl
Adapter.cxxAdapter.hxxDelegate.cxxDelegate.hxxForm.cxxForm.hxxHandler.hxxHeaders.hxxRequest.cxxRequest.hxxSetup.cxxSetup.hxxmeson.build
patches
upnp
mixer
output
MultipleOutputs.hxx
plugins
storage
plugins
subprojects
test
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: MaxK
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -18,5 +18,9 @@ about: Create a bug report
|
||||
<!-- Paste the output of "mpd --version" here -->
|
||||
|
||||
|
||||
## Configuration
|
||||
<!-- Paste your MPD configuration here -->
|
||||
|
||||
|
||||
## Log
|
||||
<!-- Paste relevant portions of the log file here (--verbose) -->
|
||||
|
9
.github/ISSUE_TEMPLATE/question.md
vendored
9
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,9 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about MPD
|
||||
---
|
||||
|
||||
<!-- Before you ask a question on GitHub, please read MPD's
|
||||
documentation. A copy is available at
|
||||
https://www.musicpd.org/doc/html/ -->
|
||||
## Question
|
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
@@ -41,7 +41,8 @@ jobs:
|
||||
key: linux
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt install -y --no-install-recommends \
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
g++-10 libfmt-dev libboost-dev \
|
||||
libgtest-dev \
|
||||
libpcre2-dev \
|
||||
@@ -73,19 +74,30 @@ jobs:
|
||||
libgcrypt20-dev
|
||||
|
||||
- name: Full Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Unit Tests
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Mini Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
action: build
|
||||
directory: output/mini
|
||||
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
build-macos:
|
||||
@@ -124,10 +136,20 @@ jobs:
|
||||
wavpack \
|
||||
libmpdclient
|
||||
|
||||
- name: Meson Build
|
||||
- name: Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Unit Tests
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
20
NEWS
20
NEWS
@@ -1,3 +1,23 @@
|
||||
ver 0.23.8 (2022/07/09)
|
||||
* storage
|
||||
- curl: fix crash if web server does not understand WebDAV
|
||||
* input
|
||||
- cdio_paranoia: fix crash if no drive was found
|
||||
- cdio_paranoia: faster cancellation
|
||||
- cdio_paranoia: don't scan for replay gain tags
|
||||
- pipewire: fix playback of very short tracks
|
||||
- pipewire: drop all buffers before manual song change
|
||||
- pipewire: fix stuttering after manual song change
|
||||
- snapcast: fix busy loop while paused
|
||||
- snapcast: fix stuttering after resuming playback
|
||||
* mixer
|
||||
- better error messages
|
||||
- alsa: fix setting volume before playback starts
|
||||
- pipewire: fix crash bug
|
||||
- pipewire: fix volume change events with PipeWire 0.3.53
|
||||
- pipewire: don't force initial volume=100%
|
||||
* support libfmt 9
|
||||
|
||||
ver 0.23.7 (2022/05/09)
|
||||
* database
|
||||
- upnp: support pupnp 1.14
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.23.7'
|
||||
version = '0.23.8'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
#release = version + '~git'
|
||||
|
||||
|
18
doc/user.rst
18
doc/user.rst
@@ -301,7 +301,7 @@ Configuring neighbor plugins
|
||||
----------------------------
|
||||
|
||||
All neighbor plugins are disabled by default to avoid unwanted
|
||||
overhead. To enable (and configure) a plugin, add a :code:`neighbor`
|
||||
overhead. To enable (and configure) a plugin, add a :code:`neighbors`
|
||||
block to :file:`mpd.conf`:
|
||||
|
||||
.. code-block:: none
|
||||
@@ -538,7 +538,7 @@ The following table lists the playlist_plugin options valid for all plugins:
|
||||
|
||||
* - Name
|
||||
- Description
|
||||
* - **plugin**
|
||||
* - **name**
|
||||
- The name of the plugin
|
||||
* - **enabled yes|no**
|
||||
- Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
|
||||
@@ -1063,7 +1063,19 @@ The "music directory" is where you store your music files. :program:`MPD` stores
|
||||
|
||||
Depending on the size of your music collection and the speed of the storage, this can take a while.
|
||||
|
||||
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
|
||||
To exclude a file from the update, create a file called
|
||||
:file:`.mpdignore` in its parent directory. Each line of that file
|
||||
may contain a list of shell wildcards. Matching files (or
|
||||
directories) in the current directory and all subdirectories are
|
||||
excluded. Example::
|
||||
|
||||
*.opus
|
||||
99*
|
||||
|
||||
Subject to pattern matching is the file/directory name. It is (not
|
||||
yet) possible to match nested path names, e.g. something like
|
||||
``foo/*.flac`` is not possible.
|
||||
|
||||
|
||||
Mounting other storages into the music directory
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23.7',
|
||||
version: '0.23.8',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -73,6 +73,9 @@ test_common_flags = [
|
||||
# clang specific warning options:
|
||||
'-Wunreachable-code-aggressive',
|
||||
'-Wused-but-marked-unused',
|
||||
|
||||
# suppress bogus GCC12 warnings in libfmt headers
|
||||
'-Wno-stringop-overflow',
|
||||
]
|
||||
|
||||
test_global_cxxflags = test_global_common_flags + [
|
||||
|
@@ -177,6 +177,8 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
'--disable-vulkan',
|
||||
|
||||
'--disable-parser=bmp',
|
||||
'--disable-parser=cavsvideo',
|
||||
'--disable-parser=dvbsub',
|
||||
@@ -380,14 +382,14 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
openssl = OpenSSLProject(
|
||||
'https://www.openssl.org/source/openssl-3.0.3.tar.gz',
|
||||
'9384a2b0570dd80358841464677115df785edb941c71211f75076d72fe6b438f',
|
||||
'https://www.openssl.org/source/openssl-3.0.5.tar.gz',
|
||||
'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a',
|
||||
'include/openssl/ossl_typ.h',
|
||||
)
|
||||
|
||||
curl = CmakeProject(
|
||||
'https://curl.se/download/curl-7.83.0.tar.xz',
|
||||
'bbff0e6b5047e773f3c3b084d80546cc1be4e354c09e419c2d0ef6116253511a',
|
||||
'https://curl.se/download/curl-7.84.0.tar.xz',
|
||||
'2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'-DBUILD_CURL_EXE=OFF',
|
||||
|
@@ -45,7 +45,10 @@ void
|
||||
LogFmt(LogLevel level, const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
#if FMT_VERSION >= 70000
|
||||
#if FMT_VERSION >= 90000
|
||||
return LogVFmt(level, domain, format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif FMT_VERSION >= 70000
|
||||
return LogVFmt(level, domain, fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
|
@@ -31,6 +31,7 @@
|
||||
#include "ErrorRef.hxx"
|
||||
#include "StringRef.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Apple {
|
||||
@@ -57,8 +58,8 @@ ThrowOSStatus(OSStatus status, const char *_msg)
|
||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||
|
||||
char msg[1024];
|
||||
strcpy(msg, _msg);
|
||||
size_t length = strlen(msg);
|
||||
std::strcpy(msg, _msg);
|
||||
size_t length = std::strlen(msg);
|
||||
|
||||
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
||||
throw std::runtime_error(msg);
|
||||
|
@@ -82,7 +82,10 @@ public:
|
||||
|
||||
template<typename S, typename... Args>
|
||||
bool Fmt(const S &format_str, Args&&... args) noexcept {
|
||||
#if FMT_VERSION >= 70000
|
||||
#if FMT_VERSION >= 90000
|
||||
return VFmt(format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif FMT_VERSION >= 70000
|
||||
return VFmt(fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
@@ -109,7 +112,10 @@ public:
|
||||
template<typename S, typename... Args>
|
||||
void FmtError(enum ack code,
|
||||
const S &format_str, Args&&... args) noexcept {
|
||||
#if FMT_VERSION >= 70000
|
||||
#if FMT_VERSION >= 90000
|
||||
return VFmtError(code, format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif FMT_VERSION >= 70000
|
||||
return VFmtError(code, fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
|
@@ -333,15 +333,11 @@ handle_getvol(Client &client, Request, Response &r)
|
||||
}
|
||||
|
||||
CommandResult
|
||||
handle_setvol(Client &client, Request args, Response &r)
|
||||
handle_setvol(Client &client, Request args, Response &)
|
||||
{
|
||||
unsigned level = args.ParseUnsigned(0, 100);
|
||||
|
||||
if (!volume_level_change(client.GetPartition().outputs, level)) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
volume_level_change(client.GetPartition().outputs, level);
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
@@ -364,11 +360,8 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
else if (new_volume > 100)
|
||||
new_volume = 100;
|
||||
|
||||
if (new_volume != old_volume &&
|
||||
!volume_level_change(outputs, new_volume)) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
if (new_volume != old_volume)
|
||||
volume_level_change(outputs, new_volume);
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
@@ -257,6 +257,12 @@ public:
|
||||
return HasFailed();
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
bool LockIsReplayGainEnabled() const noexcept {
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return replay_gain_mode != ReplayGainMode::OFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition this obejct from DecoderState::START to
|
||||
* DecoderState::DECODE.
|
||||
|
@@ -36,6 +36,7 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "tag/ApeReplayGain.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -261,12 +262,16 @@ LoadReplayGain(DecoderClient &client, InputStream &is)
|
||||
static void
|
||||
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
||||
{
|
||||
{
|
||||
const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
|
||||
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
|
||||
/* ReplayGain is disabled */
|
||||
return;
|
||||
}
|
||||
if (!bridge.dc.LockIsReplayGainEnabled())
|
||||
/* ReplayGain is disabled */
|
||||
return;
|
||||
|
||||
if (is.HasMimeType() &&
|
||||
StringStartsWith(is.GetMimeType(), "audio/x-mpd-"))
|
||||
/* skip for (virtual) files (e.g. from the
|
||||
cdio_paranoia input plugin) which cannot possibly
|
||||
contain tags */
|
||||
return;
|
||||
|
||||
LoadReplayGain(bridge, is);
|
||||
}
|
||||
|
@@ -30,10 +30,12 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -48,21 +50,19 @@ class CdioParanoiaInputStream final : public InputStream {
|
||||
CdIo_t *const cdio;
|
||||
CdromParanoia para;
|
||||
|
||||
const lsn_t lsn_from, lsn_to;
|
||||
int lsn_relofs;
|
||||
const lsn_t lsn_from;
|
||||
|
||||
char buffer[CDIO_CD_FRAMESIZE_RAW];
|
||||
int buffer_lsn;
|
||||
lsn_t buffer_lsn;
|
||||
|
||||
public:
|
||||
CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
|
||||
cdrom_drive_t *_drv, CdIo_t *_cdio,
|
||||
bool reverse_endian,
|
||||
lsn_t _lsn_from, lsn_t _lsn_to)
|
||||
lsn_t _lsn_from, lsn_t lsn_to)
|
||||
:InputStream(_uri, _mutex),
|
||||
drv(_drv), cdio(_cdio), para(drv),
|
||||
lsn_from(_lsn_from), lsn_to(_lsn_to),
|
||||
lsn_relofs(0),
|
||||
lsn_from(_lsn_from),
|
||||
buffer_lsn(-1)
|
||||
{
|
||||
/* Set reading mode for full paranoia, but allow
|
||||
@@ -173,9 +173,12 @@ cdio_detect_device()
|
||||
if (devices == nullptr)
|
||||
return nullptr;
|
||||
|
||||
AllocatedPath path = AllocatedPath::FromFS(devices[0]);
|
||||
cdio_free_device_list(devices);
|
||||
return path;
|
||||
AtScopeExit(devices) { cdio_free_device_list(devices); };
|
||||
|
||||
if (devices[0] == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return AllocatedPath::FromFS(devices[0]);
|
||||
}
|
||||
|
||||
static InputStreamPtr
|
||||
@@ -271,81 +274,70 @@ CdioParanoiaInputStream::Seek(std::unique_lock<Mutex> &,
|
||||
return;
|
||||
|
||||
/* calculate current LSN */
|
||||
lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
offset = new_offset;
|
||||
const lsn_t lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
|
||||
{
|
||||
if (lsn_relofs != buffer_lsn) {
|
||||
const ScopeUnlock unlock(mutex);
|
||||
para.Seek(lsn_from + lsn_relofs);
|
||||
}
|
||||
|
||||
offset = new_offset;
|
||||
}
|
||||
|
||||
size_t
|
||||
CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
|
||||
void *ptr, size_t length)
|
||||
{
|
||||
size_t nbytes = 0;
|
||||
char *wptr = (char *) ptr;
|
||||
/* end of track ? */
|
||||
if (IsEOF())
|
||||
return 0;
|
||||
|
||||
while (length > 0) {
|
||||
/* end of track ? */
|
||||
if (lsn_from + lsn_relofs > lsn_to)
|
||||
break;
|
||||
//current sector was changed ?
|
||||
const int16_t *rbuf;
|
||||
|
||||
//current sector was changed ?
|
||||
const int16_t *rbuf;
|
||||
if (lsn_relofs != buffer_lsn) {
|
||||
const ScopeUnlock unlock(mutex);
|
||||
const lsn_t lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
const std::size_t diff = offset % CDIO_CD_FRAMESIZE_RAW;
|
||||
|
||||
try {
|
||||
rbuf = para.Read().data;
|
||||
} catch (...) {
|
||||
char *s_err = cdio_cddap_errors(drv);
|
||||
if (s_err) {
|
||||
FmtError(cdio_domain,
|
||||
"paranoia_read: {}", s_err);
|
||||
cdio_cddap_free_messages(s_err);
|
||||
}
|
||||
if (lsn_relofs != buffer_lsn) {
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
throw;
|
||||
try {
|
||||
rbuf = para.Read().data;
|
||||
} catch (...) {
|
||||
char *s_err = cdio_cddap_errors(drv);
|
||||
if (s_err) {
|
||||
FmtError(cdio_domain,
|
||||
"paranoia_read: {}", s_err);
|
||||
cdio_cddap_free_messages(s_err);
|
||||
}
|
||||
|
||||
//store current buffer
|
||||
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
|
||||
buffer_lsn = lsn_relofs;
|
||||
} else {
|
||||
//use cached sector
|
||||
rbuf = (const int16_t *)buffer;
|
||||
throw;
|
||||
}
|
||||
|
||||
//correct offset
|
||||
const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
|
||||
|
||||
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
|
||||
|
||||
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
|
||||
const size_t len = (length < maxwrite? length : maxwrite);
|
||||
|
||||
//skip diff bytes from this lsn
|
||||
memcpy(wptr, ((const char *)rbuf) + diff, len);
|
||||
//update pointer
|
||||
wptr += len;
|
||||
nbytes += len;
|
||||
|
||||
//update offset
|
||||
offset += len;
|
||||
lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
//update length
|
||||
length -= len;
|
||||
//store current buffer
|
||||
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
|
||||
buffer_lsn = lsn_relofs;
|
||||
} else {
|
||||
//use cached sector
|
||||
rbuf = (const int16_t *)buffer;
|
||||
}
|
||||
|
||||
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
|
||||
const std::size_t nbytes = std::min(length, maxwrite);
|
||||
|
||||
//skip diff bytes from this lsn
|
||||
memcpy(ptr, ((const char *)rbuf) + diff, nbytes);
|
||||
|
||||
//update offset
|
||||
offset += nbytes;
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
bool
|
||||
CdioParanoiaInputStream::IsEOF() const noexcept
|
||||
{
|
||||
return lsn_from + lsn_relofs > lsn_to;
|
||||
return offset >= size;
|
||||
}
|
||||
|
||||
static constexpr const char *cdio_paranoia_prefixes[] = {
|
||||
|
@@ -82,7 +82,7 @@ class CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
||||
public:
|
||||
template<typename I>
|
||||
CurlInputStream(EventLoop &event_loop, const char *_url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
I &&_icy,
|
||||
Mutex &_mutex);
|
||||
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
CurlInputStream &operator=(const CurlInputStream &) = delete;
|
||||
|
||||
static InputStreamPtr Open(const char *url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
Mutex &mutex);
|
||||
|
||||
private:
|
||||
@@ -131,8 +131,7 @@ private:
|
||||
void SeekInternal(offset_type new_offset);
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) override;
|
||||
void OnData(ConstBuffer<void> data) override;
|
||||
void OnEnd() override;
|
||||
void OnError(std::exception_ptr e) noexcept override;
|
||||
@@ -227,7 +226,7 @@ WithConvertedTagValue(const char *uri, const char *value, F &&f) noexcept
|
||||
|
||||
void
|
||||
CurlInputStream::OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
Curl::Headers &&headers)
|
||||
{
|
||||
assert(GetEventLoop().IsInside());
|
||||
assert(!postponed_exception);
|
||||
@@ -391,7 +390,7 @@ input_curl_finish() noexcept
|
||||
template<typename I>
|
||||
inline
|
||||
CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
I &&_icy,
|
||||
Mutex &_mutex)
|
||||
:AsyncInputStream(event_loop, _url, _mutex,
|
||||
@@ -491,7 +490,7 @@ CurlInputStream::DoSeek(offset_type new_offset)
|
||||
|
||||
inline InputStreamPtr
|
||||
CurlInputStream::Open(const char *url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
Mutex &mutex)
|
||||
{
|
||||
auto icy = std::make_shared<IcyMetaDataParser>();
|
||||
@@ -510,8 +509,7 @@ CurlInputStream::Open(const char *url,
|
||||
}
|
||||
|
||||
InputStreamPtr
|
||||
OpenCurlInputStream(const char *uri,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
|
||||
Mutex &mutex)
|
||||
{
|
||||
return CurlInputStream::Open(uri, headers, mutex);
|
||||
|
@@ -20,12 +20,10 @@
|
||||
#ifndef MPD_INPUT_CURL_HXX
|
||||
#define MPD_INPUT_CURL_HXX
|
||||
|
||||
#include "lib/curl/Headers.hxx"
|
||||
#include "input/Ptr.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
extern const struct InputPlugin input_plugin_curl;
|
||||
|
||||
/**
|
||||
@@ -36,8 +34,7 @@ extern const struct InputPlugin input_plugin_curl;
|
||||
* Throws on error.
|
||||
*/
|
||||
InputStreamPtr
|
||||
OpenCurlInputStream(const char *uri,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
|
||||
Mutex &mutex);
|
||||
|
||||
#endif
|
||||
|
@@ -164,7 +164,7 @@ QobuzClient::InvokeHandlers() noexcept
|
||||
|
||||
std::string
|
||||
QobuzClient::MakeUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept
|
||||
const Curl::Headers &query) const noexcept
|
||||
{
|
||||
assert(!query.empty());
|
||||
|
||||
@@ -183,7 +183,7 @@ QobuzClient::MakeUrl(const char *object, const char *method,
|
||||
|
||||
std::string
|
||||
QobuzClient::MakeSignedUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept
|
||||
const Curl::Headers &query) const noexcept
|
||||
{
|
||||
assert(!query.empty());
|
||||
|
||||
|
@@ -23,12 +23,12 @@
|
||||
#include "QobuzSession.hxx"
|
||||
#include "QobuzLoginRequest.hxx"
|
||||
#include "lib/curl/Init.hxx"
|
||||
#include "lib/curl/Headers.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "event/DeferEvent.hxx"
|
||||
#include "util/IntrusiveList.hxx"
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class QobuzSessionHandler
|
||||
@@ -94,10 +94,10 @@ public:
|
||||
QobuzSession GetSession() const;
|
||||
|
||||
std::string MakeUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept;
|
||||
const Curl::Headers &query) const noexcept;
|
||||
|
||||
std::string MakeSignedUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept;
|
||||
const Curl::Headers &query) const noexcept;
|
||||
|
||||
private:
|
||||
void StartLogin();
|
||||
|
@@ -38,7 +38,7 @@ static constexpr yajl_callbacks qobuz_error_parser_callbacks = {
|
||||
};
|
||||
|
||||
QobuzErrorParser::QobuzErrorParser(unsigned _status,
|
||||
const std::multimap<std::string, std::string> &headers)
|
||||
const Curl::Headers &headers)
|
||||
:YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this),
|
||||
status(_status)
|
||||
{
|
||||
|
@@ -20,11 +20,9 @@
|
||||
#ifndef QOBUZ_ERROR_PARSER_HXX
|
||||
#define QOBUZ_ERROR_PARSER_HXX
|
||||
|
||||
#include "lib/curl/Headers.hxx"
|
||||
#include "lib/yajl/ResponseParser.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
template<typename T> struct ConstBuffer;
|
||||
struct StringView;
|
||||
|
||||
@@ -46,8 +44,7 @@ public:
|
||||
* May throw if there is a formal error in the response
|
||||
* headers.
|
||||
*/
|
||||
QobuzErrorParser(unsigned status,
|
||||
const std::multimap<std::string, std::string> &headers);
|
||||
QobuzErrorParser(unsigned status, const Curl::Headers &headers);
|
||||
|
||||
protected:
|
||||
/* virtual methods from CurlResponseParser */
|
||||
|
@@ -77,7 +77,7 @@ QobuzLoginRequest::ResponseParser::GetSession()
|
||||
return std::move(session);
|
||||
}
|
||||
|
||||
static std::multimap<std::string, std::string>
|
||||
static Curl::Headers
|
||||
MakeLoginForm(const char *app_id,
|
||||
const char *username, const char *email,
|
||||
const char *password,
|
||||
@@ -85,7 +85,7 @@ MakeLoginForm(const char *app_id,
|
||||
{
|
||||
assert(username != nullptr || email != nullptr);
|
||||
|
||||
std::multimap<std::string, std::string> form{
|
||||
Curl::Headers form{
|
||||
{"app_id", app_id},
|
||||
{"password", password},
|
||||
{"device_manufacturer_id", device_manufacturer_id},
|
||||
@@ -134,8 +134,7 @@ QobuzLoginRequest::~QobuzLoginRequest() noexcept
|
||||
}
|
||||
|
||||
std::unique_ptr<CurlResponseParser>
|
||||
QobuzLoginRequest::MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
QobuzLoginRequest::MakeParser(unsigned status, Curl::Headers &&headers)
|
||||
{
|
||||
if (status != 200)
|
||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||
|
@@ -56,7 +56,7 @@ public:
|
||||
private:
|
||||
/* virtual methods from DelegateCurlResponseHandler */
|
||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
Curl::Headers &&headers) override;
|
||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
|
@@ -99,8 +99,7 @@ QobuzTagScanner::~QobuzTagScanner() noexcept
|
||||
}
|
||||
|
||||
std::unique_ptr<CurlResponseParser>
|
||||
QobuzTagScanner::MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
QobuzTagScanner::MakeParser(unsigned status, Curl::Headers &&headers)
|
||||
{
|
||||
if (status != 200)
|
||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||
|
@@ -49,7 +49,7 @@ public:
|
||||
private:
|
||||
/* virtual methods from DelegateCurlResponseHandler */
|
||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
Curl::Headers &&headers) override;
|
||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
|
@@ -93,7 +93,7 @@ QobuzTrackRequest::~QobuzTrackRequest() noexcept
|
||||
|
||||
std::unique_ptr<CurlResponseParser>
|
||||
QobuzTrackRequest::MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
Curl::Headers &&headers)
|
||||
{
|
||||
if (status != 200)
|
||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||
|
@@ -56,7 +56,7 @@ public:
|
||||
private:
|
||||
/* virtual methods from DelegateCurlResponseHandler */
|
||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
Curl::Headers &&headers) override;
|
||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
|
204
src/lib/curl/Adapter.cxx
Normal file
204
src/lib/curl/Adapter.cxx
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* 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 "Adapter.hxx"
|
||||
#include "Easy.hxx"
|
||||
#include "Handler.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::Install(CurlEasy &easy)
|
||||
{
|
||||
assert(state == State::UNINITIALISED);
|
||||
|
||||
error_buffer[0] = 0;
|
||||
easy.SetErrorBuffer(error_buffer);
|
||||
|
||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||
easy.SetWriteFunction(WriteFunction, this);
|
||||
|
||||
curl = easy.Get();
|
||||
|
||||
state = State::HEADERS;
|
||||
}
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::FinishHeaders()
|
||||
{
|
||||
assert(state >= State::HEADERS);
|
||||
|
||||
if (state != State::HEADERS)
|
||||
return;
|
||||
|
||||
state = State::BODY;
|
||||
|
||||
long status = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
}
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::FinishBody()
|
||||
{
|
||||
FinishHeaders();
|
||||
|
||||
if (state != State::BODY)
|
||||
return;
|
||||
|
||||
state = State::CLOSED;
|
||||
handler.OnEnd();
|
||||
}
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::Done(CURLcode result) noexcept
|
||||
{
|
||||
if (postponed_error) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::move(postponed_error));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (result != CURLE_OK) {
|
||||
StripRight(error_buffer);
|
||||
const char *msg = error_buffer;
|
||||
if (*msg == 0)
|
||||
msg = curl_easy_strerror(result);
|
||||
throw FormatRuntimeError("CURL failed: %s", msg);
|
||||
}
|
||||
|
||||
FinishBody();
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static bool
|
||||
IsResponseBoundaryHeader(StringView s) noexcept
|
||||
{
|
||||
return s.size > 5 && (s.StartsWith("HTTP/") ||
|
||||
/* the proprietary "ICY 200 OK" is
|
||||
emitted by Shoutcast */
|
||||
s.StartsWith("ICY 2"));
|
||||
}
|
||||
|
||||
inline void
|
||||
CurlResponseHandlerAdapter::HeaderFunction(StringView s) noexcept
|
||||
{
|
||||
if (state > State::HEADERS)
|
||||
return;
|
||||
|
||||
if (IsResponseBoundaryHeader(s)) {
|
||||
/* this is the boundary to a new response, for example
|
||||
after a redirect */
|
||||
headers.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const char *header = s.data;
|
||||
const char *end = StripRight(header, header + s.size);
|
||||
|
||||
const char *value = s.Find(':');
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
std::string name(header, value);
|
||||
std::transform(name.begin(), name.end(), name.begin(),
|
||||
static_cast<char(*)(char)>(ToLowerASCII));
|
||||
|
||||
/* skip the colon */
|
||||
|
||||
++value;
|
||||
|
||||
/* strip the value */
|
||||
|
||||
value = StripLeft(value, end);
|
||||
end = StripRight(value, end);
|
||||
|
||||
headers.emplace(std::move(name), std::string(value, end));
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CurlResponseHandlerAdapter::_HeaderFunction(char *ptr, std::size_t size,
|
||||
std::size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
c.HeaderFunction({ptr, size});
|
||||
return size;
|
||||
}
|
||||
|
||||
inline std::size_t
|
||||
CurlResponseHandlerAdapter::DataReceived(const void *ptr,
|
||||
std::size_t received_size) noexcept
|
||||
{
|
||||
assert(received_size > 0);
|
||||
|
||||
try {
|
||||
FinishHeaders();
|
||||
handler.OnData({ptr, received_size});
|
||||
return received_size;
|
||||
} catch (CurlResponseHandler::Pause) {
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
} catch (...) {
|
||||
/* from inside this libCURL callback function, we
|
||||
can't do much, so we remember the exception to be
|
||||
handled later by Done(), and return 0, causing the
|
||||
response to be aborted with CURLE_WRITE_ERROR */
|
||||
postponed_error = std::current_exception();
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CurlResponseHandlerAdapter::WriteFunction(char *ptr, std::size_t size,
|
||||
std::size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
return c.DataReceived(ptr, size);
|
||||
}
|
91
src/lib/curl/Adapter.hxx
Normal file
91
src/lib/curl/Adapter.hxx
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Headers.hxx"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
|
||||
struct StringView;
|
||||
class CurlEasy;
|
||||
class CurlResponseHandler;
|
||||
|
||||
class CurlResponseHandlerAdapter {
|
||||
CURL *curl;
|
||||
|
||||
CurlResponseHandler &handler;
|
||||
|
||||
Curl::Headers headers;
|
||||
|
||||
/**
|
||||
* An exception caught from within the WriteFunction() which
|
||||
* will later be handled by Done().
|
||||
*/
|
||||
std::exception_ptr postponed_error;
|
||||
|
||||
/** error message provided by libcurl */
|
||||
char error_buffer[CURL_ERROR_SIZE];
|
||||
|
||||
enum class State {
|
||||
UNINITIALISED,
|
||||
HEADERS,
|
||||
BODY,
|
||||
CLOSED,
|
||||
} state = State::UNINITIALISED;
|
||||
|
||||
public:
|
||||
explicit CurlResponseHandlerAdapter(CurlResponseHandler &_handler) noexcept
|
||||
:handler(_handler) {}
|
||||
|
||||
void Install(CurlEasy &easy);
|
||||
|
||||
void Done(CURLcode result) noexcept;
|
||||
|
||||
private:
|
||||
void FinishHeaders();
|
||||
void FinishBody();
|
||||
|
||||
void HeaderFunction(StringView s) noexcept;
|
||||
|
||||
/** called by curl when a new header is available */
|
||||
static std::size_t _HeaderFunction(char *ptr,
|
||||
std::size_t size, std::size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
|
||||
std::size_t DataReceived(const void *ptr, std::size_t size) noexcept;
|
||||
|
||||
/** called by curl when new data is available */
|
||||
static std::size_t WriteFunction(char *ptr,
|
||||
std::size_t size, std::size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -34,8 +34,7 @@
|
||||
#include <utility>
|
||||
|
||||
void
|
||||
DelegateCurlResponseHandler::OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
DelegateCurlResponseHandler::OnHeaders(unsigned status, Curl::Headers &&headers)
|
||||
{
|
||||
parser = MakeParser(status, std::move(headers));
|
||||
assert(parser);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -27,8 +27,7 @@
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CURL_DELEGATE_HXX
|
||||
#define CURL_DELEGATE_HXX
|
||||
#pragma once
|
||||
|
||||
#include "Handler.hxx"
|
||||
|
||||
@@ -53,7 +52,7 @@ protected:
|
||||
* CurlResponseParser::OnError()).
|
||||
*/
|
||||
virtual std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) = 0;
|
||||
Curl::Headers &&headers) = 0;
|
||||
|
||||
/**
|
||||
* The parser has finished parsing the response body. This
|
||||
@@ -64,10 +63,7 @@ protected:
|
||||
virtual void FinishParser(std::unique_ptr<CurlResponseParser> p) = 0;
|
||||
|
||||
public:
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) final;
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) final;
|
||||
void OnData(ConstBuffer<void> data) final;
|
||||
void OnEnd() final;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -31,8 +31,7 @@
|
||||
#include "String.hxx"
|
||||
|
||||
std::string
|
||||
EncodeForm(CURL *curl,
|
||||
const std::multimap<std::string, std::string> &fields) noexcept
|
||||
EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept
|
||||
{
|
||||
std::string result;
|
||||
|
||||
|
@@ -30,17 +30,17 @@
|
||||
#ifndef CURL_FORM_HXX
|
||||
#define CURL_FORM_HXX
|
||||
|
||||
#include "Headers.hxx"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* Encode the given map of form fields to a
|
||||
* "application/x-www-form-urlencoded" string.
|
||||
*/
|
||||
std::string
|
||||
EncodeForm(CURL *curl,
|
||||
const std::multimap<std::string, std::string> &fields) noexcept;
|
||||
EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -27,14 +27,12 @@
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CURL_HANDLER_HXX
|
||||
#define CURL_HANDLER_HXX
|
||||
#pragma once
|
||||
|
||||
#include "Headers.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* Asynchronous response handler for a #CurlRequest.
|
||||
@@ -52,28 +50,31 @@ public:
|
||||
|
||||
/**
|
||||
* Status line and headers have been received.
|
||||
*
|
||||
* Exceptions thrown by this method will be passed to
|
||||
* OnError(), aborting the request.
|
||||
*/
|
||||
virtual void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) = 0;
|
||||
virtual void OnHeaders(unsigned status, Curl::Headers &&headers) = 0;
|
||||
|
||||
/**
|
||||
* Response body data has been received.
|
||||
*
|
||||
* May throw #Pause (but nothing else).
|
||||
* May throw #Pause.
|
||||
*
|
||||
* Other exceptions thrown by this method will be passed to
|
||||
* OnError(), aborting the request.
|
||||
*/
|
||||
virtual void OnData(ConstBuffer<void> data) = 0;
|
||||
|
||||
/**
|
||||
* The response has ended. The method is allowed delete the
|
||||
* #CurlRequest here.
|
||||
* The response has ended. The method is allowed to delete the
|
||||
* #CurlRequest.
|
||||
*/
|
||||
virtual void OnEnd() = 0;
|
||||
|
||||
/**
|
||||
* An error has occurred. The method is allowed delete the
|
||||
* #CurlRequest here.
|
||||
* An error has occurred. The method is allowed to delete the
|
||||
* #CurlRequest.
|
||||
*/
|
||||
virtual void OnError(std::exception_ptr e) noexcept = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
42
src/lib/curl/Headers.hxx
Normal file
42
src/lib/curl/Headers.hxx
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020-2021 CM4all GmbH
|
||||
* All rights reserved.
|
||||
*
|
||||
* author: Max Kellermann <mk@cm4all.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Curl {
|
||||
|
||||
using Headers = std::multimap<std::string, std::string, std::less<>>;
|
||||
|
||||
} // namespace Curl
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -27,42 +27,27 @@
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Request.hxx"
|
||||
#include "Setup.hxx"
|
||||
#include "Global.hxx"
|
||||
#include "Handler.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "Version.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#include <string.h>
|
||||
CurlRequest::CurlRequest(CurlGlobal &_global, CurlEasy _easy,
|
||||
CurlResponseHandler &_handler)
|
||||
:global(_global), handler(_handler), easy(std::move(_easy))
|
||||
{
|
||||
SetupEasy();
|
||||
}
|
||||
|
||||
CurlRequest::CurlRequest(CurlGlobal &_global,
|
||||
CurlResponseHandler &_handler)
|
||||
:global(_global), handler(_handler)
|
||||
{
|
||||
error_buffer[0] = 0;
|
||||
|
||||
easy.SetPrivate((void *)this);
|
||||
easy.SetUserAgent("Music Player Daemon " VERSION);
|
||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||
easy.SetWriteFunction(WriteFunction, this);
|
||||
#if !defined(ANDROID) && !defined(_WIN32)
|
||||
easy.SetOption(CURLOPT_NETRC, 1L);
|
||||
#endif
|
||||
easy.SetErrorBuffer(error_buffer);
|
||||
easy.SetNoProgress();
|
||||
easy.SetNoSignal();
|
||||
easy.SetConnectTimeout(10);
|
||||
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
|
||||
SetupEasy();
|
||||
}
|
||||
|
||||
CurlRequest::~CurlRequest() noexcept
|
||||
@@ -70,6 +55,16 @@ CurlRequest::~CurlRequest() noexcept
|
||||
FreeEasy();
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::SetupEasy()
|
||||
{
|
||||
easy.SetPrivate((void *)this);
|
||||
|
||||
handler.Install(easy);
|
||||
|
||||
Curl::Setup(easy);
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Start()
|
||||
{
|
||||
@@ -125,135 +120,10 @@ CurlRequest::Resume() noexcept
|
||||
global.InvalidateSockets();
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::FinishHeaders()
|
||||
{
|
||||
if (state != State::HEADERS)
|
||||
return;
|
||||
|
||||
state = State::BODY;
|
||||
|
||||
long status = 0;
|
||||
easy.GetInfo(CURLINFO_RESPONSE_CODE, &status);
|
||||
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::FinishBody()
|
||||
{
|
||||
FinishHeaders();
|
||||
|
||||
if (state != State::BODY)
|
||||
return;
|
||||
|
||||
state = State::CLOSED;
|
||||
handler.OnEnd();
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Done(CURLcode result) noexcept
|
||||
{
|
||||
Stop();
|
||||
|
||||
try {
|
||||
if (result != CURLE_OK) {
|
||||
StripRight(error_buffer);
|
||||
const char *msg = error_buffer;
|
||||
if (*msg == 0)
|
||||
msg = curl_easy_strerror(result);
|
||||
throw FormatRuntimeError("CURL failed: %s", msg);
|
||||
}
|
||||
|
||||
FinishBody();
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static bool
|
||||
IsResponseBoundaryHeader(StringView s) noexcept
|
||||
{
|
||||
return s.size > 5 && (s.StartsWith("HTTP/") ||
|
||||
/* the proprietary "ICY 200 OK" is
|
||||
emitted by Shoutcast */
|
||||
s.StartsWith("ICY 2"));
|
||||
}
|
||||
|
||||
inline void
|
||||
CurlRequest::HeaderFunction(StringView s) noexcept
|
||||
{
|
||||
if (state > State::HEADERS)
|
||||
return;
|
||||
|
||||
if (IsResponseBoundaryHeader(s)) {
|
||||
/* this is the boundary to a new response, for example
|
||||
after a redirect */
|
||||
headers.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const char *header = s.data;
|
||||
const char *end = StripRight(header, header + s.size);
|
||||
|
||||
const char *value = s.Find(':');
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
std::string name(header, value);
|
||||
std::transform(name.begin(), name.end(), name.begin(),
|
||||
static_cast<char(*)(char)>(ToLowerASCII));
|
||||
|
||||
/* skip the colon */
|
||||
|
||||
++value;
|
||||
|
||||
/* strip the value */
|
||||
|
||||
value = StripLeft(value, end);
|
||||
end = StripRight(value, end);
|
||||
|
||||
headers.emplace(std::move(name), std::string(value, end));
|
||||
}
|
||||
|
||||
size_t
|
||||
CurlRequest::_HeaderFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlRequest &c = *(CurlRequest *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
c.HeaderFunction({ptr, size});
|
||||
return size;
|
||||
}
|
||||
|
||||
inline size_t
|
||||
CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept
|
||||
{
|
||||
assert(received_size > 0);
|
||||
|
||||
try {
|
||||
FinishHeaders();
|
||||
handler.OnData({ptr, received_size});
|
||||
return received_size;
|
||||
} catch (CurlResponseHandler::Pause) {
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
size_t
|
||||
CurlRequest::WriteFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlRequest &c = *(CurlRequest *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
return c.DataReceived(ptr, size);
|
||||
handler.Done(result);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -31,39 +31,34 @@
|
||||
#define CURL_REQUEST_HXX
|
||||
|
||||
#include "Easy.hxx"
|
||||
#include "Adapter.hxx"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
|
||||
struct StringView;
|
||||
class CurlGlobal;
|
||||
class CurlResponseHandler;
|
||||
|
||||
/**
|
||||
* A non-blocking HTTP request integrated via #CurlGlobal into the
|
||||
* #EventLoop.
|
||||
*
|
||||
* To start sending the request, call Start().
|
||||
*/
|
||||
class CurlRequest final {
|
||||
CurlGlobal &global;
|
||||
|
||||
CurlResponseHandler &handler;
|
||||
CurlResponseHandlerAdapter handler;
|
||||
|
||||
/** the curl handle */
|
||||
CurlEasy easy;
|
||||
|
||||
enum class State {
|
||||
HEADERS,
|
||||
BODY,
|
||||
CLOSED,
|
||||
} state = State::HEADERS;
|
||||
|
||||
std::multimap<std::string, std::string> headers;
|
||||
|
||||
/** 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, CurlEasy easy,
|
||||
CurlResponseHandler &_handler);
|
||||
|
||||
CurlRequest(CurlGlobal &_global,
|
||||
CurlResponseHandler &_handler);
|
||||
|
||||
@@ -136,7 +131,7 @@ public:
|
||||
easy.SetPost(value);
|
||||
}
|
||||
|
||||
void SetRequestBody(const void *data, size_t size) {
|
||||
void SetRequestBody(const void *data, std::size_t size) {
|
||||
easy.SetRequestBody(data, size);
|
||||
}
|
||||
|
||||
@@ -148,6 +143,8 @@ public:
|
||||
void Done(CURLcode result) noexcept;
|
||||
|
||||
private:
|
||||
void SetupEasy();
|
||||
|
||||
/**
|
||||
* Frees the current "libcurl easy" handle, and everything
|
||||
* associated with it.
|
||||
@@ -156,18 +153,6 @@ private:
|
||||
|
||||
void FinishHeaders();
|
||||
void FinishBody();
|
||||
|
||||
size_t DataReceived(const void *ptr, size_t size) noexcept;
|
||||
|
||||
void HeaderFunction(StringView s) noexcept;
|
||||
|
||||
/** called by curl when new data is available */
|
||||
static size_t _HeaderFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
|
||||
/** called by curl when new data is available */
|
||||
static size_t WriteFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
51
src/lib/curl/Setup.cxx
Normal file
51
src/lib/curl/Setup.cxx
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* 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 "Setup.hxx"
|
||||
#include "Easy.hxx"
|
||||
#include "Version.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Curl {
|
||||
|
||||
void
|
||||
Setup(CurlEasy &easy)
|
||||
{
|
||||
easy.SetUserAgent("Music Player Daemon " VERSION);
|
||||
#if !defined(ANDROID) && !defined(_WIN32)
|
||||
easy.SetOption(CURLOPT_NETRC, 1L);
|
||||
#endif
|
||||
easy.SetNoProgress();
|
||||
easy.SetNoSignal();
|
||||
easy.SetConnectTimeout(10);
|
||||
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
|
||||
}
|
||||
|
||||
} // namespace Curl
|
39
src/lib/curl/Setup.hxx
Normal file
39
src/lib/curl/Setup.hxx
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class CurlEasy;
|
||||
|
||||
namespace Curl {
|
||||
|
||||
void
|
||||
Setup(CurlEasy &easy);
|
||||
|
||||
} // namespace Curl
|
@@ -18,6 +18,8 @@ curl = static_library(
|
||||
'Init.cxx',
|
||||
'Global.cxx',
|
||||
'Request.cxx',
|
||||
'Setup.cxx',
|
||||
'Adapter.cxx',
|
||||
'Escape.cxx',
|
||||
'Form.cxx',
|
||||
include_directories: inc,
|
||||
|
@@ -1,6 +1,8 @@
|
||||
--- curl-7.75.0.orig/CMakeLists.txt 2021-02-02 09:26:24.000000000 +0100
|
||||
+++ curl-7.75.0/CMakeLists.txt 2021-03-25 20:17:25.445684029 +0100
|
||||
@@ -1453,7 +1453,7 @@
|
||||
Index: curl-7.84.0/CMakeLists.txt
|
||||
===================================================================
|
||||
--- curl-7.84.0.orig/CMakeLists.txt
|
||||
+++ curl-7.84.0/CMakeLists.txt
|
||||
@@ -1536,7 +1536,7 @@ set(includedir "\${prefix}/
|
||||
set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
|
||||
set(LIBCURL_LIBS "")
|
||||
set(libdir "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
@@ -8,4 +10,4 @@
|
||||
+foreach(_lib ${CURL_LIBS})
|
||||
if(TARGET "${_lib}")
|
||||
set(_libname "${_lib}")
|
||||
get_target_property(_libtype "${_libname}" TYPE)
|
||||
get_target_property(_imported "${_libname}" IMPORTED)
|
||||
|
@@ -1,20 +1,20 @@
|
||||
Index: curl-7.71.1/lib/url.c
|
||||
Index: curl-7.84.0/lib/url.c
|
||||
===================================================================
|
||||
--- curl-7.71.1.orig/lib/url.c
|
||||
+++ curl-7.71.1/lib/url.c
|
||||
@@ -2871,6 +2871,7 @@
|
||||
}
|
||||
--- curl-7.84.0.orig/lib/url.c
|
||||
+++ curl-7.84.0/lib/url.c
|
||||
@@ -3003,6 +3003,7 @@ static CURLcode override_login(struct Cu
|
||||
|
||||
#ifndef CURL_DISABLE_NETRC
|
||||
conn->bits.netrc = FALSE;
|
||||
+#ifndef __BIONIC__
|
||||
if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
|
||||
bool netrc_user_changed = FALSE;
|
||||
bool netrc_passwd_changed = FALSE;
|
||||
@@ -2895,6 +2896,7 @@
|
||||
conn->bits.user_passwd = TRUE; /* enable user+password */
|
||||
@@ -3079,6 +3080,7 @@ static CURLcode override_login(struct Cu
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
+#endif
|
||||
|
||||
/* for updated strings, we update them in the URL */
|
||||
if(*userp) {
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
@@ -24,6 +24,10 @@
|
||||
/* libupnp versions until 1.10.1 redefine "bool" and "true" */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wkeyword-macro"
|
||||
|
||||
/* libupnp 1.8.4 uses a flawed kludge to suppress this warning in
|
||||
inline function __list_add_valid() */
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
|
||||
#endif
|
||||
|
||||
#include <upnp.h>
|
||||
|
@@ -55,7 +55,7 @@ UPnPDeviceDirectory::Downloader::Destroy() noexcept
|
||||
|
||||
void
|
||||
UPnPDeviceDirectory::Downloader::OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&)
|
||||
Curl::Headers &&)
|
||||
{
|
||||
if (status != 200) {
|
||||
Destroy();
|
||||
|
@@ -113,8 +113,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
|
||||
}
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) override;
|
||||
void OnData(ConstBuffer<void> data) override;
|
||||
void OnEnd() override;
|
||||
void OnError(std::exception_ptr e) noexcept override;
|
||||
|
@@ -73,42 +73,77 @@ MultipleOutputs::GetVolume() const noexcept
|
||||
return total / ok;
|
||||
}
|
||||
|
||||
static bool
|
||||
output_mixer_set_volume(AudioOutputControl &ao, unsigned volume) noexcept
|
||||
enum class SetVolumeResult {
|
||||
NO_MIXER,
|
||||
DISABLED,
|
||||
ERROR,
|
||||
OK,
|
||||
};
|
||||
|
||||
static SetVolumeResult
|
||||
output_mixer_set_volume(AudioOutputControl &ao, unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
|
||||
auto *mixer = ao.GetMixer();
|
||||
if (mixer == nullptr)
|
||||
return false;
|
||||
return SetVolumeResult::NO_MIXER;
|
||||
|
||||
/* software mixers are always updated, even if they are
|
||||
disabled */
|
||||
if (!ao.IsReallyEnabled() && !mixer->IsPlugin(software_mixer_plugin))
|
||||
return false;
|
||||
if (!mixer->IsPlugin(software_mixer_plugin) &&
|
||||
/* "global" mixers can be used even if the output hasn't
|
||||
been used yet */
|
||||
!(mixer->IsGlobal() ? ao.IsEnabled() : ao.IsReallyEnabled()))
|
||||
return SetVolumeResult::DISABLED;
|
||||
|
||||
try {
|
||||
mixer_set_volume(mixer, volume);
|
||||
return true;
|
||||
return SetVolumeResult::OK;
|
||||
} catch (...) {
|
||||
FmtError(mixer_domain,
|
||||
"Failed to set mixer for '{}': {}",
|
||||
ao.GetName(), std::current_exception());
|
||||
return false;
|
||||
std::throw_with_nested(std::runtime_error(fmt::format("Failed to set mixer for '{}'",
|
||||
ao.GetName())));
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MultipleOutputs::SetVolume(unsigned volume) noexcept
|
||||
void
|
||||
MultipleOutputs::SetVolume(unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
|
||||
bool success = false;
|
||||
for (const auto &ao : outputs)
|
||||
success = output_mixer_set_volume(*ao, volume)
|
||||
|| success;
|
||||
SetVolumeResult result = SetVolumeResult::NO_MIXER;
|
||||
std::exception_ptr error;
|
||||
|
||||
return success;
|
||||
for (const auto &ao : outputs) {
|
||||
try {
|
||||
auto r = output_mixer_set_volume(*ao, volume);
|
||||
if (r > result)
|
||||
result = r;
|
||||
} catch (...) {
|
||||
/* remember the first error */
|
||||
if (!error) {
|
||||
error = std::current_exception();
|
||||
result = SetVolumeResult::ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case SetVolumeResult::NO_MIXER:
|
||||
throw std::runtime_error{"No mixer"};
|
||||
|
||||
case SetVolumeResult::DISABLED:
|
||||
throw std::runtime_error{"All outputs are disabled"};
|
||||
|
||||
case SetVolumeResult::ERROR:
|
||||
std::rethrow_exception(error);
|
||||
|
||||
case SetVolumeResult::OK:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
|
@@ -60,9 +60,9 @@ mixer_open(Mixer *mixer)
|
||||
try {
|
||||
mixer->Open();
|
||||
mixer->open = true;
|
||||
mixer->failed = false;
|
||||
mixer->failure = {};
|
||||
} catch (...) {
|
||||
mixer->failed = true;
|
||||
mixer->failure = std::current_exception();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ mixer_close_internal(Mixer *mixer)
|
||||
|
||||
mixer->Close();
|
||||
mixer->open = false;
|
||||
mixer->failure = {};
|
||||
}
|
||||
|
||||
void
|
||||
@@ -95,20 +96,6 @@ mixer_auto_close(Mixer *mixer)
|
||||
mixer_close(mixer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the mixer due to failure. The mutex must be locked before
|
||||
* calling this function.
|
||||
*/
|
||||
static void
|
||||
mixer_failed(Mixer *mixer)
|
||||
{
|
||||
assert(mixer->open);
|
||||
|
||||
mixer_close_internal(mixer);
|
||||
|
||||
mixer->failed = true;
|
||||
}
|
||||
|
||||
int
|
||||
mixer_get_volume(Mixer *mixer)
|
||||
{
|
||||
@@ -116,7 +103,7 @@ mixer_get_volume(Mixer *mixer)
|
||||
|
||||
assert(mixer != nullptr);
|
||||
|
||||
if (mixer->plugin.global && !mixer->failed)
|
||||
if (mixer->plugin.global && !mixer->failure)
|
||||
mixer_open(mixer);
|
||||
|
||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||
@@ -125,7 +112,8 @@ mixer_get_volume(Mixer *mixer)
|
||||
try {
|
||||
volume = mixer->GetVolume();
|
||||
} catch (...) {
|
||||
mixer_failed(mixer);
|
||||
mixer_close_internal(mixer);
|
||||
mixer->failure = std::current_exception();
|
||||
throw;
|
||||
}
|
||||
} else
|
||||
@@ -140,11 +128,13 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
|
||||
assert(mixer != nullptr);
|
||||
assert(volume <= 100);
|
||||
|
||||
if (mixer->plugin.global && !mixer->failed)
|
||||
if (mixer->plugin.global && !mixer->failure)
|
||||
mixer_open(mixer);
|
||||
|
||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||
|
||||
if (mixer->open)
|
||||
mixer->SetVolume(volume);
|
||||
else if (mixer->failure)
|
||||
std::rethrow_exception(mixer->failure);
|
||||
}
|
||||
|
@@ -25,6 +25,8 @@
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
class MixerListener;
|
||||
|
||||
class Mixer {
|
||||
@@ -39,17 +41,17 @@ public:
|
||||
*/
|
||||
Mutex mutex;
|
||||
|
||||
/**
|
||||
* Contains error details if this mixer has failed. If set,
|
||||
* it should not be reopened automatically.
|
||||
*/
|
||||
std::exception_ptr failure;
|
||||
|
||||
/**
|
||||
* Is the mixer device currently open?
|
||||
*/
|
||||
bool open = false;
|
||||
|
||||
/**
|
||||
* Has this mixer failed, and should not be reopened
|
||||
* automatically?
|
||||
*/
|
||||
bool failed = false;
|
||||
|
||||
public:
|
||||
explicit Mixer(const MixerPlugin &_plugin,
|
||||
MixerListener &_listener) noexcept
|
||||
@@ -63,6 +65,10 @@ public:
|
||||
return &plugin == &other;
|
||||
}
|
||||
|
||||
bool IsGlobal() const noexcept {
|
||||
return plugin.global;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open mixer device
|
||||
*
|
||||
|
@@ -71,16 +71,16 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
/* reset the cache */
|
||||
last_hardware_volume = -1;
|
||||
|
||||
return outputs.SetVolume(volume);
|
||||
outputs.SetVolume(volume);
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
volume_level_change(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
@@ -89,7 +89,7 @@ volume_level_change(MultipleOutputs &outputs, unsigned volume)
|
||||
|
||||
idle_add(IDLE_MIXER);
|
||||
|
||||
return hardware_volume_change(outputs, volume);
|
||||
hardware_volume_change(outputs, volume);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@@ -30,7 +30,10 @@ InvalidateHardwareVolume() noexcept;
|
||||
int
|
||||
volume_level_get(const MultipleOutputs &outputs) noexcept;
|
||||
|
||||
bool
|
||||
/**
|
||||
* Throws on error.
|
||||
*/
|
||||
void
|
||||
volume_level_change(MultipleOutputs &outputs, unsigned volume);
|
||||
|
||||
bool
|
||||
|
@@ -141,10 +141,11 @@ public:
|
||||
/**
|
||||
* Sets the volume on all available mixers.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param volume the volume (range 0..100)
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool SetVolume(unsigned volume) noexcept;
|
||||
void SetVolume(unsigned volume);
|
||||
|
||||
/**
|
||||
* Similar to GetVolume(), but gets the volume only for
|
||||
|
@@ -160,13 +160,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block)
|
||||
static constexpr AudioObjectPropertyAddress default_system_output_device{
|
||||
kAudioHardwarePropertyDefaultSystemOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster,
|
||||
kAudioObjectPropertyElementMain,
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress default_output_device{
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
const auto &aopa =
|
||||
@@ -195,9 +195,9 @@ int
|
||||
OSXOutput::GetVolume()
|
||||
{
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster,
|
||||
kAudioObjectPropertyElementMain,
|
||||
};
|
||||
|
||||
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
|
||||
@@ -211,9 +211,9 @@ OSXOutput::SetVolume(unsigned new_volume)
|
||||
{
|
||||
Float32 vol = new_volume / 100.0;
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
UInt32 size = sizeof(vol);
|
||||
OSStatus status = AudioObjectSetPropertyData(dev_id,
|
||||
@@ -366,25 +366,25 @@ osx_output_set_device_format(AudioDeviceID dev_id,
|
||||
static constexpr AudioObjectPropertyAddress aopa_device_streams = {
|
||||
kAudioDevicePropertyStreams,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
|
||||
kAudioStreamPropertyDirection,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
|
||||
kAudioStreamPropertyPhysicalFormat,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
OSStatus err;
|
||||
@@ -484,7 +484,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioDevicePropertyHogMode,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
pid_t hog_pid;
|
||||
@@ -538,7 +538,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
|
||||
static constexpr AudioObjectPropertyAddress aopa_name{
|
||||
kAudioObjectPropertyName,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster,
|
||||
kAudioObjectPropertyElementMain,
|
||||
};
|
||||
|
||||
char actual_name[256];
|
||||
@@ -561,7 +561,7 @@ FindAudioDeviceByName(const char *name)
|
||||
static constexpr AudioObjectPropertyAddress aopa_hw_devices{
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster,
|
||||
kAudioObjectPropertyElementMain,
|
||||
};
|
||||
|
||||
const auto ids =
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "../Error.hxx"
|
||||
#include "mixer/plugins/PipeWireMixerPlugin.hxx"
|
||||
#include "pcm/Silence.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/BitReverse.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
@@ -56,6 +57,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -85,7 +87,14 @@ class PipeWireOutput final : AudioOutput {
|
||||
|
||||
uint32_t target_id = PW_ID_ANY;
|
||||
|
||||
float volume = 1.0;
|
||||
/**
|
||||
* The current volume level (0.0 .. 1.0).
|
||||
*
|
||||
* This get initialized to -1 which means "unknown", so
|
||||
* restore_volume will not attempt to override PipeWire's
|
||||
* initial volume level.
|
||||
*/
|
||||
float volume = -1;
|
||||
|
||||
PipeWireMixer *mixer = nullptr;
|
||||
unsigned channels;
|
||||
@@ -217,26 +226,34 @@ private:
|
||||
o.Drained();
|
||||
}
|
||||
|
||||
void ControlInfo(const struct pw_stream_control *control) noexcept {
|
||||
float sum = 0;
|
||||
unsigned c;
|
||||
for (c = 0; c < control->n_values; c++)
|
||||
sum += control->values[c];
|
||||
void OnChannelVolumes(const struct pw_stream_control &control) noexcept {
|
||||
if (control.n_values < 1)
|
||||
return;
|
||||
|
||||
sum /= control->n_values;
|
||||
float sum = std::accumulate(control.values,
|
||||
control.values + control.n_values,
|
||||
0.0f);
|
||||
volume = std::cbrt(sum / control.n_values);
|
||||
|
||||
if (mixer != nullptr)
|
||||
pipewire_mixer_on_change(*mixer, std::cbrt(sum));
|
||||
pipewire_mixer_on_change(*mixer, volume);
|
||||
|
||||
pw_thread_loop_signal(thread_loop, false);
|
||||
}
|
||||
|
||||
static void ControlInfo(void *data,
|
||||
[[maybe_unused]] uint32_t id,
|
||||
void ControlInfo([[maybe_unused]] uint32_t id,
|
||||
const struct pw_stream_control &control) noexcept {
|
||||
switch (id) {
|
||||
case SPA_PROP_channelVolumes:
|
||||
OnChannelVolumes(control);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ControlInfo(void *data, uint32_t id,
|
||||
const struct pw_stream_control *control) noexcept {
|
||||
auto &o = *(PipeWireOutput *)data;
|
||||
if (StringIsEqual(control->name, "Channel Volumes"))
|
||||
o.ControlInfo(control);
|
||||
o.ControlInfo(id, *control);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||
@@ -308,22 +325,38 @@ PipeWireOutput::PipeWireOutput(const ConfigBlock &block)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws on error.
|
||||
*
|
||||
* @param volume a volume level between 0.0 and 1.0
|
||||
*/
|
||||
static void
|
||||
SetVolume(struct pw_stream &stream, unsigned channels, float volume)
|
||||
{
|
||||
float value[MAX_CHANNELS];
|
||||
std::fill_n(value, channels, volume * volume * volume);
|
||||
|
||||
if (pw_stream_set_control(&stream,
|
||||
SPA_PROP_channelVolumes, channels, value,
|
||||
0) != 0)
|
||||
throw std::runtime_error("pw_stream_set_control() failed");
|
||||
}
|
||||
|
||||
void
|
||||
PipeWireOutput::SetVolume(float _volume)
|
||||
{
|
||||
if (thread_loop == nullptr) {
|
||||
/* the mixer is open (because it is a "global" mixer),
|
||||
but Enable() on this output has not yet been
|
||||
called */
|
||||
volume = _volume;
|
||||
return;
|
||||
}
|
||||
|
||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||
|
||||
float newvol = _volume*_volume*_volume;
|
||||
|
||||
if (stream != nullptr && !restore_volume) {
|
||||
float vol[MAX_CHANNELS];
|
||||
std::fill_n(vol, channels, newvol);
|
||||
|
||||
if (pw_stream_set_control(stream,
|
||||
SPA_PROP_channelVolumes, channels, vol,
|
||||
0) != 0)
|
||||
throw std::runtime_error("pw_stream_set_control() failed");
|
||||
}
|
||||
if (stream != nullptr && !restore_volume)
|
||||
::SetVolume(*stream, channels, _volume);
|
||||
|
||||
volume = _volume;
|
||||
}
|
||||
@@ -639,7 +672,16 @@ PipeWireOutput::ParamChanged([[maybe_unused]] uint32_t id,
|
||||
{
|
||||
if (restore_volume) {
|
||||
restore_volume = false;
|
||||
SetVolume(volume);
|
||||
|
||||
if (volume >= 0) {
|
||||
try {
|
||||
::SetVolume(*stream, channels, volume);
|
||||
} catch (...) {
|
||||
FmtError(pipewire_output_domain,
|
||||
FMT_STRING("Failed to restore volume: {}"),
|
||||
std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||
@@ -824,6 +866,17 @@ PipeWireOutput::Drain()
|
||||
{
|
||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||
|
||||
if (drained)
|
||||
return;
|
||||
|
||||
if (!active) {
|
||||
/* there is data in the ring_buffer, but the stream is
|
||||
not yet active; activate it now to ensure it is
|
||||
played before this method returns */
|
||||
active = true;
|
||||
pw_stream_set_active(stream, true);
|
||||
}
|
||||
|
||||
drain_requested = true;
|
||||
AtScopeExit(this) { drain_requested = false; };
|
||||
|
||||
@@ -839,7 +892,24 @@ PipeWireOutput::Cancel() noexcept
|
||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||
interrupted = false;
|
||||
|
||||
if (drained)
|
||||
return;
|
||||
|
||||
/* clear MPD's ring buffer */
|
||||
ring_buffer->reset();
|
||||
|
||||
/* clear libpipewire's buffer */
|
||||
pw_stream_flush(stream, false);
|
||||
drained = true;
|
||||
|
||||
/* pause the PipeWire stream so libpipewire ceases invoking
|
||||
the "process" callback (we have no data until our Play()
|
||||
method gets called again); the stream will be resume by
|
||||
Play() after the ring_buffer has been refilled */
|
||||
if (active) {
|
||||
active = false;
|
||||
pw_stream_set_active(stream, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
@@ -214,9 +214,8 @@ SnapcastOutput::RemoveClient(SnapcastClient &client) noexcept
|
||||
std::chrono::steady_clock::duration
|
||||
SnapcastOutput::Delay() const noexcept
|
||||
{
|
||||
if (!LockHasClients() && pause) {
|
||||
/* if there's no client and this output is paused,
|
||||
then Pause() will not do anything, it will not fill
|
||||
if (pause) {
|
||||
/* Pause() will not do anything, it will not fill
|
||||
the buffer and it will not update the timer;
|
||||
therefore, we reset the timer here */
|
||||
timer->Reset();
|
||||
|
@@ -227,7 +227,7 @@ IsXmlContentType(const char *content_type) noexcept
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsXmlContentType(const std::multimap<std::string, std::string> &headers) noexcept
|
||||
IsXmlContentType(const Curl::Headers &headers) noexcept
|
||||
{
|
||||
auto i = headers.find("content-type");
|
||||
return i != headers.end() && IsXmlContentType(i->second.c_str());
|
||||
@@ -297,8 +297,7 @@ private:
|
||||
}
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) final {
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) final {
|
||||
if (status != 207)
|
||||
throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
|
||||
status);
|
||||
|
@@ -3,10 +3,10 @@ directory = fmt-8.1.1
|
||||
source_url = https://github.com/fmtlib/fmt/archive/8.1.1.tar.gz
|
||||
source_filename = fmt-8.1.1.tar.gz
|
||||
source_hash = 3d794d3cf67633b34b2771eb9f073bde87e846e0d395d254df7b211ef1ec7346
|
||||
patch_filename = fmt_8.1.1-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-1/get_patch
|
||||
patch_hash = 6035a67c7a8c90bed74c293c7265c769f47a69816125f7566bccb8e2543cee5e
|
||||
patch_filename = fmt_8.1.1-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-2/get_patch
|
||||
patch_hash = cd001046281330a8862591780a9ea71a1fa594edd0d015deb24e44680c9ea33b
|
||||
wrapdb_version = 8.1.1-2
|
||||
|
||||
[provide]
|
||||
fmt = fmt_dep
|
||||
|
||||
|
@@ -3,9 +3,10 @@ directory = libvorbis-1.3.7
|
||||
source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz
|
||||
source_filename = libvorbis-1.3.7.tar.xz
|
||||
source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b
|
||||
patch_filename = vorbis_1.3.7-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-2/get_patch
|
||||
patch_hash = fe302576cbf8408754b332b539ea1b83f0f96fa9aae50a5d1fea911713d5f21c
|
||||
patch_filename = vorbis_1.3.7-3_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-3/get_patch
|
||||
patch_hash = 6cb90a61ede8c64d3e8e379b96dcc800c9dd69e925122b3d73d8f59a563c3afa
|
||||
wrapdb_version = 1.3.7-3
|
||||
|
||||
[provide]
|
||||
vorbis = vorbis_dep
|
||||
|
@@ -41,8 +41,7 @@ public:
|
||||
}
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override {
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) override {
|
||||
fprintf(stderr, "status: %u\n", status);
|
||||
for (const auto &i : headers)
|
||||
fprintf(stderr, "%s: %s\n",
|
||||
|
Reference in New Issue
Block a user