From 8e6a21a9c26408f22a0d5525ca49f6c289578d31 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 22 Mar 2020 10:48:53 +0100 Subject: [PATCH 01/42] increment version number to 0.21.22 --- NEWS | 2 ++ android/AndroidManifest.xml | 4 ++-- doc/conf.py | 2 +- meson.build | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 47b6ff01d..71e2eb381 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +ver 0.21.22 (not yet released) + ver 0.21.21 (2020/03/19) * configuration - fix bug in "metadata_to_use" setting diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 83286b0b7..6de3d89eb 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="45" + android:versionName="0.21.22"> diff --git a/doc/conf.py b/doc/conf.py index a0d4b393f..f70f7b890 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -38,7 +38,7 @@ author = 'Max Kellermann' # built documents. # # The short X.Y version. -version = '0.21.21' +version = '0.21.22' # The full version, including alpha/beta/rc tags. release = version diff --git a/meson.build b/meson.build index d6d0412ec..07f437627 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'mpd', ['c', 'cpp'], - version: '0.21.21', + version: '0.21.22', meson_version: '>= 0.49.0', default_options: [ 'c_std=c99', From 36a89e8fe77945e17b6f24a410f45a91b3497904 Mon Sep 17 00:00:00 2001 From: kowalcj0 Date: Fri, 20 Mar 2020 11:29:21 +0000 Subject: [PATCH 02/42] Support RSS feeds with application/xml MIME-type --- NEWS | 2 ++ src/playlist/plugins/RssPlaylistPlugin.cxx | 1 + 2 files changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 71e2eb381..9f62c5140 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.21.22 (not yet released) +* playlist + - rss: support MIME type application/xml ver 0.21.21 (2020/03/19) * configuration diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx index 4761afd0c..ecf1c726b 100644 --- a/src/playlist/plugins/RssPlaylistPlugin.cxx +++ b/src/playlist/plugins/RssPlaylistPlugin.cxx @@ -162,6 +162,7 @@ static const char *const rss_suffixes[] = { static const char *const rss_mime_types[] = { "application/rss+xml", + "application/xml", "text/xml", nullptr }; From 3449c14ff58d482ad0a940bf26562004deb1767c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 24 Apr 2019 13:56:59 +0200 Subject: [PATCH 03/42] java/Object: rename class Object to GlobalObject --- src/android/Context.hxx | 4 ++-- src/android/LogListener.hxx | 4 ++-- src/java/Object.hxx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/android/Context.hxx b/src/android/Context.hxx index 1bf00715c..6cd75f062 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -24,9 +24,9 @@ class AllocatedPath; -class Context : public Java::Object { +class Context : public Java::GlobalObject { public: - Context(JNIEnv *env, jobject obj):Java::Object(env, obj) {} + Context(JNIEnv *env, jobject obj):Java::GlobalObject(env, obj) {} gcc_pure AllocatedPath GetCacheDir(JNIEnv *env) const; diff --git a/src/android/LogListener.hxx b/src/android/LogListener.hxx index 2c81078fe..9123fd85c 100644 --- a/src/android/LogListener.hxx +++ b/src/android/LogListener.hxx @@ -22,9 +22,9 @@ #include "java/Object.hxx" -class LogListener : public Java::Object { +class LogListener : public Java::GlobalObject { public: - LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {} + LogListener(JNIEnv *env, jobject obj):Java::GlobalObject(env, obj) {} void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const; }; diff --git a/src/java/Object.hxx b/src/java/Object.hxx index f16354929..743558a30 100644 --- a/src/java/Object.hxx +++ b/src/java/Object.hxx @@ -40,15 +40,15 @@ namespace Java { */ typedef LocalRef LocalObject; - class Object : public GlobalRef { + class GlobalObject : public GlobalRef { public: /** * Constructs an uninitialized object. The method * set() must be called before it is destructed. */ - Object() = default; + GlobalObject() = default; - Object(JNIEnv *env, jobject obj) noexcept + GlobalObject(JNIEnv *env, jobject obj) noexcept :GlobalRef(env, obj) {} }; } From 5418bb49fbe4767b3d7631093038c948e434e69f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 24 Apr 2019 14:49:24 +0200 Subject: [PATCH 04/42] android/Context: add `noexcept` --- src/android/Context.cxx | 4 ++-- src/android/Context.hxx | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/android/Context.cxx b/src/android/Context.cxx index c8fcd69db..f73026e94 100644 --- a/src/android/Context.cxx +++ b/src/android/Context.cxx @@ -1,5 +1,5 @@ /* - * Copyright 2003-2018 The Music Player Daemon Project + * Copyright 2003-2019 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include "fs/AllocatedPath.hxx" AllocatedPath -Context::GetCacheDir(JNIEnv *env) const +Context::GetCacheDir(JNIEnv *env) const noexcept { assert(env != nullptr); diff --git a/src/android/Context.hxx b/src/android/Context.hxx index 6cd75f062..8d65ae6e2 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -1,5 +1,5 @@ /* - * Copyright 2003-2018 The Music Player Daemon Project + * Copyright 2003-2019 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,10 +26,11 @@ class AllocatedPath; class Context : public Java::GlobalObject { public: - Context(JNIEnv *env, jobject obj):Java::GlobalObject(env, obj) {} + Context(JNIEnv *env, jobject obj) noexcept + :Java::GlobalObject(env, obj) {} gcc_pure - AllocatedPath GetCacheDir(JNIEnv *env) const; + AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; }; #endif From 200258c7c33d8ecbe4a21b91ffbade67f17f1f0b Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Mon, 23 Mar 2020 18:15:07 +0100 Subject: [PATCH 05/42] android: add AudioManager --- meson.build | 1 + src/android/AudioManager.cxx | 56 ++++++++++++++++++++++++++++++++++++ src/android/AudioManager.hxx | 42 +++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/android/AudioManager.cxx create mode 100644 src/android/AudioManager.hxx diff --git a/meson.build b/meson.build index 07f437627..c169384c7 100644 --- a/meson.build +++ b/meson.build @@ -290,6 +290,7 @@ if not is_android else sources += [ 'src/android/Context.cxx', + 'src/android/AudioManager.cxx', 'src/android/Environment.cxx', 'src/android/LogListener.cxx', ] diff --git a/src/android/AudioManager.cxx b/src/android/AudioManager.cxx new file mode 100644 index 000000000..5e3062250 --- /dev/null +++ b/src/android/AudioManager.cxx @@ -0,0 +1,56 @@ +/* + * Copyright 2003-2020 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 "AudioManager.hxx" +#include "java/Class.hxx" +#include "java/Exception.hxx" +#include "java/File.hxx" + +#define STREAM_MUSIC 3 + +AudioManager::AudioManager(JNIEnv *env, jobject obj) noexcept + : Java::GlobalObject(env, obj) +{ + Java::Class cls(env, env->GetObjectClass(Get())); + jmethodID method = env->GetMethodID(cls, "getStreamMaxVolume", "(I)I"); + assert(method); + maxVolume = env->CallIntMethod(Get(), method, STREAM_MUSIC); + + getStreamVolumeMethod = env->GetMethodID(cls, "getStreamVolume", "(I)I"); + assert(getStreamVolumeMethod); + + setStreamVolumeMethod = env->GetMethodID(cls, "setStreamVolume", "(III)V"); + assert(setStreamVolumeMethod); +} + +int +AudioManager::GetVolume(JNIEnv *env) +{ + if (maxVolume == 0) + return 0; + return env->CallIntMethod(Get(), getStreamVolumeMethod, STREAM_MUSIC); +} + +void +AudioManager::SetVolume(JNIEnv *env, int volume) +{ + if (maxVolume == 0) + return; + env->CallVoidMethod(Get(), setStreamVolumeMethod, STREAM_MUSIC, volume, 0); +} diff --git a/src/android/AudioManager.hxx b/src/android/AudioManager.hxx new file mode 100644 index 000000000..d4de3deb3 --- /dev/null +++ b/src/android/AudioManager.hxx @@ -0,0 +1,42 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ANDROID_AUDIO_MANAGER_HXX +#define MPD_ANDROID_AUDIO_MANAGER_HXX + +#include "java/Object.hxx" + +class AudioManager : public Java::GlobalObject { + int maxVolume; + jmethodID getStreamVolumeMethod; + jmethodID setStreamVolumeMethod; + +public: + AudioManager(JNIEnv *env, jobject obj) noexcept; + + AudioManager(std::nullptr_t) noexcept { maxVolume = 0; } + + ~AudioManager() noexcept {} + + int GetMaxVolume() { return maxVolume; } + int GetVolume(JNIEnv *env); + void SetVolume(JNIEnv *env, int); +}; + +#endif From 5619fd0bba65aa512638aafe63be1ded23016b9e Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Mon, 23 Mar 2020 21:34:18 +0100 Subject: [PATCH 06/42] android: Context: add GetAudioManager --- src/android/Context.cxx | 21 +++++++++++++++++++++ src/android/Context.hxx | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/src/android/Context.cxx b/src/android/Context.cxx index f73026e94..59c630b13 100644 --- a/src/android/Context.cxx +++ b/src/android/Context.cxx @@ -20,8 +20,11 @@ #include "Context.hxx" #include "java/Class.hxx" #include "java/File.hxx" +#include "java/String.hxx" #include "fs/AllocatedPath.hxx" +#include "AudioManager.hxx" + AllocatedPath Context::GetCacheDir(JNIEnv *env) const noexcept { @@ -40,3 +43,21 @@ Context::GetCacheDir(JNIEnv *env) const noexcept return Java::File::ToAbsolutePath(env, file); } + +AudioManager * +Context::GetAudioManager(JNIEnv *env) noexcept +{ + assert(env != nullptr); + + Java::Class cls(env, env->GetObjectClass(Get())); + jmethodID method = env->GetMethodID(cls, "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;"); + assert(method); + + Java::String name(env, "audio"); + jobject am = env->CallObjectMethod(Get(), method, name.Get()); + if (Java::DiscardException(env) || am == nullptr) + return nullptr; + + return new AudioManager(env, am); +} diff --git a/src/android/Context.hxx b/src/android/Context.hxx index 8d65ae6e2..b8b1d994c 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -23,6 +23,7 @@ #include "java/Object.hxx" class AllocatedPath; +class AudioManager; class Context : public Java::GlobalObject { public: @@ -31,6 +32,9 @@ public: gcc_pure AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; + + gcc_pure + AudioManager *GetAudioManager(JNIEnv *env) noexcept; }; #endif From 801ae86b5d89202402f9e40a6f9269417efd1975 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Mon, 23 Mar 2020 21:35:26 +0100 Subject: [PATCH 07/42] mixer: add AndroidMixerPlugin --- src/mixer/MixerList.hxx | 1 + src/mixer/plugins/AndroidMixerPlugin.cxx | 116 +++++++++++++++++++++++ src/mixer/plugins/meson.build | 4 + 3 files changed, 121 insertions(+) create mode 100644 src/mixer/plugins/AndroidMixerPlugin.cxx diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx index e62604ce2..a3fc90e0a 100644 --- a/src/mixer/MixerList.hxx +++ b/src/mixer/MixerList.hxx @@ -29,6 +29,7 @@ struct MixerPlugin; extern const MixerPlugin null_mixer_plugin; extern const MixerPlugin software_mixer_plugin; +extern const MixerPlugin android_mixer_plugin; extern const MixerPlugin alsa_mixer_plugin; extern const MixerPlugin haiku_mixer_plugin; extern const MixerPlugin oss_mixer_plugin; diff --git a/src/mixer/plugins/AndroidMixerPlugin.cxx b/src/mixer/plugins/AndroidMixerPlugin.cxx new file mode 100644 index 000000000..b24b64fab --- /dev/null +++ b/src/mixer/plugins/AndroidMixerPlugin.cxx @@ -0,0 +1,116 @@ +/* + * Copyright 2003-2020 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 "mixer/MixerInternal.hxx" +#include "filter/plugins/VolumeFilterPlugin.hxx" +#include "pcm/Volume.hxx" +#include "android/Context.hxx" +#include "android/AudioManager.hxx" + +#include "Main.hxx" + +#include +#include + +class AndroidMixer final : public Mixer { + AudioManager *audioManager; + int currentVolume; + int maxAndroidVolume; + int lastAndroidVolume; +public: + explicit AndroidMixer(MixerListener &_listener); + + ~AndroidMixer() override; + + /* virtual methods from class Mixer */ + void Open() override { + } + + void Close() noexcept override { + } + + int GetVolume() override; + + void SetVolume(unsigned volume) override; +}; + +static Mixer * +android_mixer_init([[maybe_unused]] EventLoop &event_loop, + [[maybe_unused]] AudioOutput &ao, + MixerListener &listener, + [[maybe_unused]] const ConfigBlock &block) +{ + return new AndroidMixer(listener); +} + +AndroidMixer::AndroidMixer(MixerListener &_listener) + :Mixer(android_mixer_plugin, _listener) +{ + JNIEnv *env = Java::GetEnv(); + audioManager = context->GetAudioManager(env); + + maxAndroidVolume = audioManager->GetMaxVolume(); + if (maxAndroidVolume != 0) + { + lastAndroidVolume = audioManager->GetVolume(env); + currentVolume = 100 * lastAndroidVolume / maxAndroidVolume; + } +} + +AndroidMixer::~AndroidMixer() +{ + delete audioManager; +} + +int +AndroidMixer::GetVolume() +{ + JNIEnv *env = Java::GetEnv(); + if (maxAndroidVolume == 0) + return -1; + + // The android volume index (or scale) is very likely inferior to the + // MPD one (100). The last volume set by MPD is saved into + // currentVolume, this volume is returned instead of the Android one + // when the Android mixer was not touched by an other application. This + // allows to fake a 0..100 scale from MPD. + + int volume = audioManager->GetVolume(env); + if (volume == lastAndroidVolume) + return currentVolume; + + return 100 * volume / maxAndroidVolume; +} + +void +AndroidMixer::SetVolume(unsigned newVolume) +{ + JNIEnv *env = Java::GetEnv(); + if (maxAndroidVolume == 0) + return; + currentVolume = newVolume; + lastAndroidVolume = currentVolume * maxAndroidVolume / 100; + audioManager->SetVolume(env, lastAndroidVolume); + +} + +const MixerPlugin android_mixer_plugin = { + android_mixer_init, + true, +}; diff --git a/src/mixer/plugins/meson.build b/src/mixer/plugins/meson.build index eda7c8e2a..ed9df39b3 100644 --- a/src/mixer/plugins/meson.build +++ b/src/mixer/plugins/meson.build @@ -34,6 +34,10 @@ if is_windows mixer_plugins_sources += 'WinmmMixerPlugin.cxx' endif +if is_android + mixer_plugins_sources += 'AndroidMixerPlugin.cxx' +endif + mixer_plugins = static_library( 'mixer_plugins', mixer_plugins_sources, From 81c16273c543822fa8b87522cb1146a7859edd7a Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Mon, 23 Mar 2020 21:36:22 +0100 Subject: [PATCH 08/42] output/sles: use the AndroidMixerPlugin --- NEWS | 2 ++ src/output/plugins/sles/SlesOutputPlugin.cxx | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 9f62c5140..9d73dba96 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ ver 0.21.22 (not yet released) * playlist - rss: support MIME type application/xml +* mixer + - android: new mixer plugin for "sles" output ver 0.21.21 (2020/03/19) * configuration diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx index f1ef758e4..a2a265c59 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.cxx +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -28,6 +28,7 @@ #include "util/Macros.hxx" #include "util/Domain.hxx" #include "system/ByteOrder.hxx" +#include "mixer/MixerList.hxx" #include "Log.hxx" #include @@ -412,5 +413,5 @@ const struct AudioOutputPlugin sles_output_plugin = { "sles", sles_test_default_device, SlesOutput::Create, - nullptr, + &android_mixer_plugin, }; From 85c27840a303145e887d7d30f0141236c21aafef Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 2 Feb 2020 21:21:57 -0800 Subject: [PATCH 09/42] treewide: use boost::lround when std::round is unavailable This is the case with uClibc-ng currently. Signed-off-by: Rosen Penev (cherry picked from commit 769cd0ee9f0cf8ceb026aa751b5d4a390bb5dbdc) (changed define to match master) --- src/Stats.cxx | 4 +-- src/command/PlayerCommands.cxx | 5 ++- src/decoder/plugins/FaadDecoderPlugin.cxx | 2 +- src/mixer/plugins/WinmmMixerPlugin.cxx | 2 +- src/output/plugins/HaikuOutputPlugin.cxx | 3 +- src/pcm/PcmMix.cxx | 5 ++- src/player/CrossFade.cxx | 5 ++- src/util/Math.hxx | 41 +++++++++++++++++++++++ 8 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 src/util/Math.hxx diff --git a/src/Stats.cxx b/src/Stats.cxx index 2bf4458fc..045ac3b7a 100644 --- a/src/Stats.cxx +++ b/src/Stats.cxx @@ -29,9 +29,9 @@ #include "system/Clock.hxx" #include "Log.hxx" #include "time/ChronoUtil.hxx" +#include "util/Math.hxx" #include -#include #ifndef _WIN32 /** @@ -121,7 +121,7 @@ stats_print(Response &r, const Partition &partition) #else (unsigned)std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time).count(), #endif - std::lround(partition.pc.GetTotalPlayTime().count())); + lround(partition.pc.GetTotalPlayTime().count())); #ifdef ENABLE_DATABASE const Database *db = partition.instance.GetDatabase(); diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 716f9a221..6a38de830 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -34,13 +34,12 @@ #include "util/StringBuffer.hxx" #include "util/ScopeExit.hxx" #include "util/Exception.hxx" +#include "util/Math.hxx" #ifdef ENABLE_DATABASE #include "db/update/Service.hxx" #endif -#include - #define COMMAND_STATUS_STATE "state" #define COMMAND_STATUS_REPEAT "repeat" #define COMMAND_STATUS_SINGLE "single" @@ -154,7 +153,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r) if (pc.GetCrossFade() > FloatDuration::zero()) r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n", - std::lround(pc.GetCrossFade().count())); + lround(pc.GetCrossFade().count())); if (pc.GetMixRampDelay() > FloatDuration::zero()) r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n", diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx index 418f5f688..2f3d4e991 100644 --- a/src/decoder/plugins/FaadDecoderPlugin.cxx +++ b/src/decoder/plugins/FaadDecoderPlugin.cxx @@ -26,11 +26,11 @@ #include "util/ScopeExit.hxx" #include "util/ConstBuffer.hxx" #include "util/Domain.hxx" +#include "util/Math.hxx" #include "Log.hxx" #include -#include #include #include diff --git a/src/mixer/plugins/WinmmMixerPlugin.cxx b/src/mixer/plugins/WinmmMixerPlugin.cxx index 08e2b11b9..4da01c37e 100644 --- a/src/mixer/plugins/WinmmMixerPlugin.cxx +++ b/src/mixer/plugins/WinmmMixerPlugin.cxx @@ -20,13 +20,13 @@ #include "mixer/MixerInternal.hxx" #include "output/OutputAPI.hxx" #include "output/plugins/WinmmOutputPlugin.hxx" +#include "util/Math.hxx" #include #include #include -#include #include class WinmmMixer final : public Mixer { diff --git a/src/output/plugins/HaikuOutputPlugin.cxx b/src/output/plugins/HaikuOutputPlugin.cxx index 86fa7baf5..0d4f3d361 100644 --- a/src/output/plugins/HaikuOutputPlugin.cxx +++ b/src/output/plugins/HaikuOutputPlugin.cxx @@ -22,6 +22,7 @@ #include "../OutputAPI.hxx" #include "mixer/MixerList.hxx" #include "util/Domain.hxx" +#include "util/Math.hxx" #include "system/Error.hxx" #include "Log.hxx" @@ -37,8 +38,6 @@ #include #include -#include - #include #define UTF8_PLAY "\xE2\x96\xB6" diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx index 13f558d5e..bbb26d1c4 100644 --- a/src/pcm/PcmMix.cxx +++ b/src/pcm/PcmMix.cxx @@ -22,11 +22,10 @@ #include "Clamp.hxx" #include "Traits.hxx" #include "util/Clamp.hxx" +#include "util/Math.hxx" #include "PcmDither.cxx" // including the .cxx file to get inlined templates -#include - #include template> @@ -225,7 +224,7 @@ pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size, s = sin(M_PI_2 * portion1); s *= s; - int vol1 = std::lround(s * PCM_VOLUME_1S); + int vol1 = lround(s * PCM_VOLUME_1S); vol1 = Clamp(vol1, 0, PCM_VOLUME_1S); return pcm_add_vol(dither, buffer1, buffer2, size, diff --git a/src/player/CrossFade.cxx b/src/player/CrossFade.cxx index 72b3598bb..b31610e66 100644 --- a/src/player/CrossFade.cxx +++ b/src/player/CrossFade.cxx @@ -23,10 +23,9 @@ #include "AudioFormat.hxx" #include "util/NumberParser.hxx" #include "util/Domain.hxx" +#include "util/Math.hxx" #include "Log.hxx" -#include - #include static constexpr Domain cross_fade_domain("cross_fade"); @@ -112,7 +111,7 @@ CrossFadeSettings::Calculate(SignedSongTime total_time, if (mixramp_delay <= FloatDuration::zero() || !mixramp_start || !mixramp_prev_end) { - chunks = std::lround(duration / chunk_duration); + chunks = lround(duration / chunk_duration); } else { /* Calculate mixramp overlap. */ const auto mixramp_overlap_current = diff --git a/src/util/Math.hxx b/src/util/Math.hxx new file mode 100644 index 000000000..9784c0b64 --- /dev/null +++ b/src/util/Math.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MATH_HXX +#define MATH_HXX + +#if (defined(__GLIBCPP__) || defined(__GLIBCXX__)) && !defined(_GLIBCXX_USE_C99_MATH) +#include +using boost::math::lround; +#else +#include +using std::lround; +#endif + +#endif From abe06a5fa6a7092a772cc372a054ba1f53c5616d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 4 Apr 2019 20:06:11 +0200 Subject: [PATCH 10/42] lib/curl/Init: add `noexcept` --- src/lib/curl/Init.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/curl/Init.hxx b/src/lib/curl/Init.hxx index b08196d86..d19655a31 100644 --- a/src/lib/curl/Init.hxx +++ b/src/lib/curl/Init.hxx @@ -50,11 +50,11 @@ public: CurlInit(const CurlInit &) = delete; CurlInit &operator=(const CurlInit &) = delete; - CurlGlobal &operator*() { + CurlGlobal &operator*() noexcept { return *instance; } - CurlGlobal *operator->() { + CurlGlobal *operator->() noexcept { return instance; } }; From 1de3ac6c78f2ff1b99e59c94a4ed016aa6718fb1 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 4 Apr 2019 20:06:25 +0200 Subject: [PATCH 11/42] lib/curl/Init: add `const` overloads --- src/lib/curl/Init.hxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/curl/Init.hxx b/src/lib/curl/Init.hxx index d19655a31..4256f2603 100644 --- a/src/lib/curl/Init.hxx +++ b/src/lib/curl/Init.hxx @@ -54,9 +54,17 @@ public: return *instance; } + const CurlGlobal &operator*() const noexcept { + return *instance; + } + CurlGlobal *operator->() noexcept { return instance; } + + const CurlGlobal *operator->() const noexcept { + return instance; + } }; #endif From 8cd5e79fbdda5c978046d4e40e77cfd1a2a070f5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 4 Apr 2019 19:48:28 +0200 Subject: [PATCH 12/42] event/*, ...: make GetEventLoop() const --- src/db/update/Service.hxx | 2 +- src/event/DeferEvent.hxx | 2 +- src/event/MaskMonitor.hxx | 2 +- src/event/SocketMonitor.hxx | 2 +- src/event/TimerEvent.hxx | 2 +- src/input/AsyncInputStream.hxx | 2 +- src/input/plugins/TidalSessionManager.hxx | 2 +- src/lib/curl/Global.hxx | 2 +- src/lib/dbus/Glue.hxx | 2 +- src/lib/dbus/Watch.hxx | 2 +- src/lib/nfs/Connection.hxx | 4 +--- src/lib/nfs/FileReader.hxx | 2 +- src/lib/upnp/Discovery.cxx | 2 +- src/lib/upnp/Discovery.hxx | 2 +- src/neighbor/plugins/UdisksNeighborPlugin.cxx | 2 +- src/storage/plugins/NfsStorage.cxx | 2 +- src/storage/plugins/UdisksStorage.cxx | 2 +- 17 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/db/update/Service.hxx b/src/db/update/Service.hxx index c00b8f42f..a03e451ad 100644 --- a/src/db/update/Service.hxx +++ b/src/db/update/Service.hxx @@ -66,7 +66,7 @@ public: ~UpdateService(); - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return defer.GetEventLoop(); } diff --git a/src/event/DeferEvent.hxx b/src/event/DeferEvent.hxx index 4dd8c9e66..64ddbf280 100644 --- a/src/event/DeferEvent.hxx +++ b/src/event/DeferEvent.hxx @@ -50,7 +50,7 @@ public: Cancel(); } - EventLoop &GetEventLoop() noexcept { + EventLoop &GetEventLoop() const noexcept { return loop; } diff --git a/src/event/MaskMonitor.hxx b/src/event/MaskMonitor.hxx index 6b91ab0fd..5331c603b 100644 --- a/src/event/MaskMonitor.hxx +++ b/src/event/MaskMonitor.hxx @@ -44,7 +44,7 @@ public: :defer(_loop, BIND_THIS_METHOD(RunDeferred)), callback(_callback), pending_mask(0) {} - EventLoop &GetEventLoop() { + auto &GetEventLoop() const noexcept { return defer.GetEventLoop(); } diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx index 1d8814cc5..e4a2087d6 100644 --- a/src/event/SocketMonitor.hxx +++ b/src/event/SocketMonitor.hxx @@ -68,7 +68,7 @@ public: ~SocketMonitor() noexcept; - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return loop; } diff --git a/src/event/TimerEvent.hxx b/src/event/TimerEvent.hxx index 993a2d82c..1e103f4b8 100644 --- a/src/event/TimerEvent.hxx +++ b/src/event/TimerEvent.hxx @@ -62,7 +62,7 @@ public: Cancel(); } - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return loop; } diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx index acdae2d60..9583db22c 100644 --- a/src/input/AsyncInputStream.hxx +++ b/src/input/AsyncInputStream.hxx @@ -76,7 +76,7 @@ public: virtual ~AsyncInputStream(); - EventLoop &GetEventLoop() { + auto &GetEventLoop() const noexcept { return deferred_resume.GetEventLoop(); } diff --git a/src/input/plugins/TidalSessionManager.hxx b/src/input/plugins/TidalSessionManager.hxx index f5b4c9299..429612255 100644 --- a/src/input/plugins/TidalSessionManager.hxx +++ b/src/input/plugins/TidalSessionManager.hxx @@ -102,7 +102,7 @@ public: ~TidalSessionManager() noexcept; - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return defer_invoke_handlers.GetEventLoop(); } diff --git a/src/lib/curl/Global.hxx b/src/lib/curl/Global.hxx index 0a8067fa2..c0d63a035 100644 --- a/src/lib/curl/Global.hxx +++ b/src/lib/curl/Global.hxx @@ -50,7 +50,7 @@ class CurlGlobal final { public: explicit CurlGlobal(EventLoop &_loop); - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return timeout_event.GetEventLoop(); } diff --git a/src/lib/dbus/Glue.hxx b/src/lib/dbus/Glue.hxx index ed6612155..66e0ec15a 100644 --- a/src/lib/dbus/Glue.hxx +++ b/src/lib/dbus/Glue.hxx @@ -42,7 +42,7 @@ public: DisconnectIndirect(); } - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return watch.GetEventLoop(); } diff --git a/src/lib/dbus/Watch.hxx b/src/lib/dbus/Watch.hxx index 939793043..87fbf5217 100644 --- a/src/lib/dbus/Watch.hxx +++ b/src/lib/dbus/Watch.hxx @@ -102,7 +102,7 @@ public: void Shutdown() noexcept; - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return defer_dispatch.GetEventLoop(); } diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index 7260c5f6d..20905add0 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -161,9 +161,7 @@ public: return export_name.c_str(); } - EventLoop &GetEventLoop() noexcept { - return SocketMonitor::GetEventLoop(); - } + using SocketMonitor::GetEventLoop; /** * Ensure that the connection is established. The connection diff --git a/src/lib/nfs/FileReader.hxx b/src/lib/nfs/FileReader.hxx index 02697e097..e1f5d81e7 100644 --- a/src/lib/nfs/FileReader.hxx +++ b/src/lib/nfs/FileReader.hxx @@ -69,7 +69,7 @@ public: NfsFileReader() noexcept; ~NfsFileReader() noexcept; - EventLoop &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return defer_open.GetEventLoop(); } diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx index facf94a10..9e720bf31 100644 --- a/src/lib/upnp/Discovery.cxx +++ b/src/lib/upnp/Discovery.cxx @@ -272,7 +272,7 @@ UPnPDeviceDirectory::~UPnPDeviceDirectory() noexcept } inline EventLoop & -UPnPDeviceDirectory::GetEventLoop() noexcept +UPnPDeviceDirectory::GetEventLoop() const noexcept { return curl->GetEventLoop(); } diff --git a/src/lib/upnp/Discovery.hxx b/src/lib/upnp/Discovery.hxx index 8af1c1ae6..fa6673203 100644 --- a/src/lib/upnp/Discovery.hxx +++ b/src/lib/upnp/Discovery.hxx @@ -168,7 +168,7 @@ public: UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete; UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete; - EventLoop &GetEventLoop() noexcept; + EventLoop &GetEventLoop() const noexcept; void Start(); diff --git a/src/neighbor/plugins/UdisksNeighborPlugin.cxx b/src/neighbor/plugins/UdisksNeighborPlugin.cxx index bb6a70d51..cb5d422f2 100644 --- a/src/neighbor/plugins/UdisksNeighborPlugin.cxx +++ b/src/neighbor/plugins/UdisksNeighborPlugin.cxx @@ -70,7 +70,7 @@ public: NeighborListener &_listener) noexcept :NeighborExplorer(_listener), event_loop(_event_loop) {} - auto &GetEventLoop() noexcept { + auto &GetEventLoop() const noexcept { return event_loop; } diff --git a/src/storage/plugins/NfsStorage.cxx b/src/storage/plugins/NfsStorage.cxx index 5f9450497..e485fd8e4 100644 --- a/src/storage/plugins/NfsStorage.cxx +++ b/src/storage/plugins/NfsStorage.cxx @@ -129,7 +129,7 @@ public: } private: - EventLoop &GetEventLoop() noexcept { + EventLoop &GetEventLoop() const noexcept { return defer_connect.GetEventLoop(); } diff --git a/src/storage/plugins/UdisksStorage.cxx b/src/storage/plugins/UdisksStorage.cxx index 60f1e27e0..1bd17b4e2 100644 --- a/src/storage/plugins/UdisksStorage.cxx +++ b/src/storage/plugins/UdisksStorage.cxx @@ -94,7 +94,7 @@ public: } } - EventLoop &GetEventLoop() noexcept { + EventLoop &GetEventLoop() const noexcept { return defer_mount.GetEventLoop(); } From dab39dc778217c28cee4e9f2a14aa8dfadb915a2 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 19 Aug 2019 21:16:29 +0200 Subject: [PATCH 13/42] lib/curl: fix coding style --- src/lib/curl/Global.cxx | 37 +++++++++++++++++++------------------ src/lib/curl/Global.hxx | 4 ++-- src/lib/curl/Slist.hxx | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/lib/curl/Global.cxx b/src/lib/curl/Global.cxx index 0cd8b533d..bd4494b7a 100644 --- a/src/lib/curl/Global.cxx +++ b/src/lib/curl/Global.cxx @@ -48,7 +48,7 @@ public: CurlSocket(CurlGlobal &_global, EventLoop &_loop, SocketDescriptor _fd) :SocketMonitor(_fd, _loop), global(_global) {} - ~CurlSocket() { + ~CurlSocket() noexcept { /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after closing the socket, and sometimes, it uses CURL_POLL_REMOVE just to move the (still open) @@ -109,7 +109,8 @@ CurlGlobal::CurlGlobal(EventLoop &_loop) int CurlSocket::SocketFunction(gcc_unused CURL *easy, curl_socket_t s, int action, - void *userp, void *socketp) noexcept { + void *userp, void *socketp) noexcept +{ auto &global = *(CurlGlobal *)userp; CurlSocket *cs = (CurlSocket *)socketp; @@ -217,6 +218,20 @@ CurlGlobal::ReadInfo() noexcept } } +void +CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) noexcept +{ + int running_handles; + CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask, + &running_handles); + if (mcode != CURLM_OK) + FormatError(curlm_domain, + "curl_multi_socket_action() failed: %s", + curl_multi_strerror(mcode)); + + defer_read_info.Schedule(); +} + inline void CurlGlobal::UpdateTimeout(long timeout_ms) noexcept { @@ -236,11 +251,11 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept } int -CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, +CurlGlobal::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp) noexcept { auto &global = *(CurlGlobal *)userp; - assert(_global == global.multi.Get()); + assert(_multi == global.multi.Get()); global.UpdateTimeout(timeout_ms); return 0; @@ -251,17 +266,3 @@ CurlGlobal::OnTimeout() noexcept { SocketAction(CURL_SOCKET_TIMEOUT, 0); } - -void -CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) noexcept -{ - int running_handles; - CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask, - &running_handles); - if (mcode != CURLM_OK) - FormatError(curlm_domain, - "curl_multi_socket_action() failed: %s", - curl_multi_strerror(mcode)); - - defer_read_info.Schedule(); -} diff --git a/src/lib/curl/Global.hxx b/src/lib/curl/Global.hxx index c0d63a035..38b909b1f 100644 --- a/src/lib/curl/Global.hxx +++ b/src/lib/curl/Global.hxx @@ -70,13 +70,13 @@ public: void SocketAction(curl_socket_t fd, int ev_bitmask) noexcept; - void InvalidateSockets() { + void InvalidateSockets() noexcept { SocketAction(CURL_SOCKET_TIMEOUT, 0); } private: void UpdateTimeout(long timeout_ms) noexcept; - static int TimerFunction(CURLM *global, long timeout_ms, + static int TimerFunction(CURLM *multi, long timeout_ms, void *userp) noexcept; /* callback for #timeout_event */ diff --git a/src/lib/curl/Slist.hxx b/src/lib/curl/Slist.hxx index 82c466b34..b53580a21 100644 --- a/src/lib/curl/Slist.hxx +++ b/src/lib/curl/Slist.hxx @@ -42,7 +42,7 @@ class CurlSlist { struct curl_slist *head = nullptr; public: - CurlSlist() = default; + CurlSlist() noexcept = default; CurlSlist(CurlSlist &&src) noexcept :head(std::exchange(src.head, nullptr)) {} From 6e3b2fd844b3bbffaa93a4d85736e405f917acc7 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 19 Aug 2019 21:20:32 +0200 Subject: [PATCH 14/42] lib/curl/Global: remove redundant API docs --- src/lib/curl/Global.cxx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lib/curl/Global.cxx b/src/lib/curl/Global.cxx index bd4494b7a..168b98de4 100644 --- a/src/lib/curl/Global.cxx +++ b/src/lib/curl/Global.cxx @@ -154,11 +154,6 @@ CurlSocket::OnSocketReady(unsigned flags) noexcept return true; } -/** - * Runs in the I/O thread. No lock needed. - * - * Throws std::runtime_error on error. - */ void CurlGlobal::Add(CURL *easy, CurlRequest &request) { @@ -195,11 +190,6 @@ ToRequest(CURL *easy) noexcept return (CurlRequest *)p; } -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ inline void CurlGlobal::ReadInfo() noexcept { From 185fbca28210581d9e003ffcf305e431a7c2a20e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 19 Aug 2019 21:27:18 +0200 Subject: [PATCH 15/42] lib/curl/Global: make ReadInfo() private --- src/lib/curl/Global.hxx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/curl/Global.hxx b/src/lib/curl/Global.hxx index 38b909b1f..9e75a16e4 100644 --- a/src/lib/curl/Global.hxx +++ b/src/lib/curl/Global.hxx @@ -57,13 +57,6 @@ public: void Add(CURL *easy, CurlRequest &request); void Remove(CURL *easy) noexcept; - /** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ - void ReadInfo() noexcept; - void Assign(curl_socket_t fd, CurlSocket &cs) noexcept { curl_multi_assign(multi.Get(), fd, &cs); } @@ -75,6 +68,13 @@ public: } private: + /** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ + void ReadInfo() noexcept; + void UpdateTimeout(long timeout_ms) noexcept; static int TimerFunction(CURLM *multi, long timeout_ms, void *userp) noexcept; From ab39f64fc0131b4b7d9ebc7ad0d4bcab02d0cdc6 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 19 Aug 2019 21:16:51 +0200 Subject: [PATCH 16/42] lib/curl/Easy: add setter functions --- src/lib/curl/Easy.hxx | 74 +++++++++++++++++++++++++++++++++++++++- src/lib/curl/Request.cxx | 24 ++++++------- src/lib/curl/Request.hxx | 6 ++-- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/src/lib/curl/Easy.hxx b/src/lib/curl/Easy.hxx index 4d813511e..91de2b69e 100644 --- a/src/lib/curl/Easy.hxx +++ b/src/lib/curl/Easy.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Max Kellermann + * Copyright 2016-2018 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -90,6 +90,78 @@ public: throw std::runtime_error(curl_easy_strerror(code)); } + void SetPrivate(void *pointer) { + SetOption(CURLOPT_PRIVATE, pointer); + } + + void SetErrorBuffer(char *buf) { + SetOption(CURLOPT_ERRORBUFFER, buf); + } + + void SetURL(const char *value) { + SetOption(CURLOPT_URL, value); + } + + void SetUserAgent(const char *value) { + SetOption(CURLOPT_USERAGENT, value); + } + + void SetRequestHeaders(struct curl_slist *headers) { + SetOption(CURLOPT_HTTPHEADER, headers); + } + + void SetBasicAuth(const char *userpwd) { + SetOption(CURLOPT_USERPWD, userpwd); + } + + void SetNoProgress(bool value=true) { + SetOption(CURLOPT_NOPROGRESS, (long)value); + } + + void SetNoSignal(bool value=true) { + SetOption(CURLOPT_NOSIGNAL, (long)value); + } + + void SetFailOnError(bool value=true) { + SetOption(CURLOPT_FAILONERROR, (long)value); + } + + void SetConnectTimeout(long timeout) { + SetOption(CURLOPT_CONNECTTIMEOUT, timeout); + } + + void SetHeaderFunction(size_t (*function)(char *buffer, size_t size, + size_t nitems, + void *userdata), + void *userdata) { + SetOption(CURLOPT_HEADERFUNCTION, function); + SetOption(CURLOPT_HEADERDATA, userdata); + } + + void SetWriteFunction(size_t (*function)(char *ptr, size_t size, + size_t nmemb, void *userdata), + void *userdata) { + SetOption(CURLOPT_WRITEFUNCTION, function); + SetOption(CURLOPT_WRITEDATA, userdata); + } + + void SetNoBody(bool value=true) { + SetOption(CURLOPT_NOBODY, (long)value); + } + + void SetPost(bool value=true) { + SetOption(CURLOPT_POST, (long)value); + } + + void SetRequestBody(const void *data, size_t size) { + SetOption(CURLOPT_POSTFIELDS, data); + SetOption(CURLOPT_POSTFIELDSIZE, (long)size); + } + + void SetHttpPost(const struct curl_httppost *post) { + SetOption(CURLOPT_HTTPPOST, post); + } + CurlString Escape(const char *string, int length=0) const noexcept { return CurlString(curl_easy_escape(handle, string, length)); } diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx index a72fde549..326af6015 100644 --- a/src/lib/curl/Request.cxx +++ b/src/lib/curl/Request.cxx @@ -52,17 +52,15 @@ CurlRequest::CurlRequest(CurlGlobal &_global, { error_buffer[0] = 0; - easy.SetOption(CURLOPT_PRIVATE, (void *)this); - easy.SetOption(CURLOPT_USERAGENT, "Music Player Daemon " VERSION); - easy.SetOption(CURLOPT_HEADERFUNCTION, _HeaderFunction); - easy.SetOption(CURLOPT_WRITEHEADER, this); - easy.SetOption(CURLOPT_WRITEFUNCTION, WriteFunction); - easy.SetOption(CURLOPT_WRITEDATA, this); + easy.SetPrivate((void *)this); + easy.SetUserAgent("Music Player Daemon " VERSION); + easy.SetHeaderFunction(_HeaderFunction, this); + easy.SetWriteFunction(WriteFunction, this); easy.SetOption(CURLOPT_NETRC, 1l); - easy.SetOption(CURLOPT_ERRORBUFFER, error_buffer); - easy.SetOption(CURLOPT_NOPROGRESS, 1l); - easy.SetOption(CURLOPT_NOSIGNAL, 1l); - easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l); + easy.SetErrorBuffer(error_buffer); + easy.SetNoProgress(); + easy.SetNoSignal(); + easy.SetConnectTimeout(10); easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY); } @@ -220,14 +218,14 @@ CurlRequest::HeaderFunction(StringView s) noexcept } size_t -CurlRequest::_HeaderFunction(void *ptr, size_t size, size_t nmemb, +CurlRequest::_HeaderFunction(char *ptr, size_t size, size_t nmemb, void *stream) noexcept { CurlRequest &c = *(CurlRequest *)stream; size *= nmemb; - c.HeaderFunction({(const char *)ptr, size}); + c.HeaderFunction({ptr, size}); return size; } @@ -254,7 +252,7 @@ CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept } size_t -CurlRequest::WriteFunction(void *ptr, size_t size, size_t nmemb, +CurlRequest::WriteFunction(char *ptr, size_t size, size_t nmemb, void *stream) noexcept { CurlRequest &c = *(CurlRequest *)stream; diff --git a/src/lib/curl/Request.hxx b/src/lib/curl/Request.hxx index 59af67b7c..baca4fb8e 100644 --- a/src/lib/curl/Request.hxx +++ b/src/lib/curl/Request.hxx @@ -127,7 +127,7 @@ public: } void SetUrl(const char *url) { - easy.SetOption(CURLOPT_URL, url); + easy.SetURL(url); } /** @@ -160,11 +160,11 @@ private: void OnPostponeError() noexcept; /** called by curl when new data is available */ - static size_t _HeaderFunction(void *ptr, size_t size, size_t nmemb, + 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(void *ptr, size_t size, size_t nmemb, + static size_t WriteFunction(char *ptr, size_t size, size_t nmemb, void *stream) noexcept; }; From 8474599ed68e549f4863c86ed71f405146d98255 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 19 Aug 2019 21:18:59 +0200 Subject: [PATCH 17/42] lib/curl/Easy: add method Unpause() --- src/lib/curl/Easy.hxx | 4 ++++ src/lib/curl/Request.cxx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/curl/Easy.hxx b/src/lib/curl/Easy.hxx index 91de2b69e..83ddbd55f 100644 --- a/src/lib/curl/Easy.hxx +++ b/src/lib/curl/Easy.hxx @@ -162,6 +162,10 @@ public: SetOption(CURLOPT_HTTPPOST, post); } + bool Unpause() noexcept { + return ::curl_easy_pause(handle, CURLPAUSE_CONT) == CURLE_OK; + } + CurlString Escape(const char *string, int length=0) const noexcept { return CurlString(curl_easy_escape(handle, string, length)); } diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx index 326af6015..d36b83ef3 100644 --- a/src/lib/curl/Request.cxx +++ b/src/lib/curl/Request.cxx @@ -119,7 +119,7 @@ CurlRequest::Resume() noexcept { assert(registered); - curl_easy_pause(easy.Get(), CURLPAUSE_CONT); + easy.Unpause(); global.InvalidateSockets(); } From 608d7ec1e7b2493a369639021e3074fd5a7ae1fa Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 2 Feb 2020 14:31:45 -0800 Subject: [PATCH 18/42] [clang-tidy] change integer prefixes to uppercase Found with readability-uppercase-literal-suffix Signed-off-by: Rosen Penev --- src/command/PlayerCommands.cxx | 2 +- src/db/plugins/ProxyDatabasePlugin.cxx | 4 ++-- src/decoder/plugins/AdPlugDecoderPlugin.cxx | 2 +- src/decoder/plugins/FluidsynthDecoderPlugin.cxx | 2 +- src/decoder/plugins/HybridDsdDecoderPlugin.cxx | 4 ++-- src/decoder/plugins/MikmodDecoderPlugin.cxx | 2 +- src/decoder/plugins/SidplayDecoderPlugin.cxx | 4 ++-- src/encoder/plugins/FlacEncoderPlugin.cxx | 2 +- src/encoder/plugins/OpusEncoderPlugin.cxx | 2 +- src/input/plugins/CdioParanoiaInputPlugin.cxx | 2 +- src/input/plugins/CurlInputPlugin.cxx | 12 ++++++------ src/lib/curl/Request.cxx | 2 +- src/lib/icu/CaseFold.cxx | 2 +- src/output/plugins/AlsaOutputPlugin.cxx | 2 +- src/output/plugins/AoOutputPlugin.cxx | 2 +- src/output/plugins/JackOutputPlugin.cxx | 2 +- src/output/plugins/ShoutOutputPlugin.cxx | 2 +- src/output/plugins/httpd/HttpdOutputPlugin.cxx | 4 ++-- src/storage/plugins/CurlStorage.cxx | 4 ++-- src/util/UTF8.cxx | 4 ++-- 20 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 716f9a221..537bad629 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -173,7 +173,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r) COMMAND_STATUS_BITRATE ": %u\n", player_status.elapsed_time.RoundS(), player_status.total_time.IsNegative() - ? 0u + ? 0U : unsigned(player_status.total_time.RoundS()), player_status.elapsed_time.ToDoubleS(), player_status.bit_rate); diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx index 42edd23d0..5894c2c90 100644 --- a/src/db/plugins/ProxyDatabasePlugin.cxx +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -448,7 +448,7 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener, listener(_listener), host(block.GetBlockValue("host", "")), password(block.GetBlockValue("password", "")), - port(block.GetBlockValue("port", 0u)), + port(block.GetBlockValue("port", 0U)), keepalive(block.GetBlockValue("keepalive", false)) { } @@ -517,7 +517,7 @@ ProxyDatabase::Connect() (void)keepalive; #endif - idle_received = ~0u; + idle_received = ~0U; is_idle = false; SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection)))); diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx index c082f6f2f..50e0eadd8 100644 --- a/src/decoder/plugins/AdPlugDecoderPlugin.cxx +++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx @@ -41,7 +41,7 @@ adplug_init(const ConfigBlock &block) FormatDebug(adplug_domain, "adplug %s", CAdPlug::get_version().c_str()); - sample_rate = block.GetPositiveValue("sample_rate", 48000u); + sample_rate = block.GetPositiveValue("sample_rate", 48000U); CheckSampleRate(sample_rate); return true; diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx index 9002da363..ad09c6f3d 100644 --- a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx @@ -78,7 +78,7 @@ fluidsynth_mpd_log_function(int level, static bool fluidsynth_init(const ConfigBlock &block) { - sample_rate = block.GetPositiveValue("sample_rate", 48000u); + sample_rate = block.GetPositiveValue("sample_rate", 48000U); CheckSampleRate(sample_rate); soundfont_path = block.GetBlockValue("soundfont", diff --git a/src/decoder/plugins/HybridDsdDecoderPlugin.cxx b/src/decoder/plugins/HybridDsdDecoderPlugin.cxx index 3b46de0f1..993bfb283 100644 --- a/src/decoder/plugins/HybridDsdDecoderPlugin.cxx +++ b/src/decoder/plugins/HybridDsdDecoderPlugin.cxx @@ -186,7 +186,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input) client.Ready(result.first, true, duration); frame_size = result.first.GetFrameSize(); kbit_rate = frame_size * result.first.sample_rate / - (1024u / 8u); + (1024U / 8U); total_frames = result.second / frame_size; } catch (UnsupportedFile) { /* not a Hybrid-DSD file; let the next decoder plugin @@ -236,7 +236,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input) /* fill the buffer */ auto w = buffer.Write(); if (!w.empty()) { - if (remaining_bytes < (1<<30ull) && + if (remaining_bytes < (1<<30ULL) && w.size > size_t(remaining_bytes)) w.size = remaining_bytes; diff --git a/src/decoder/plugins/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx index e023dd2c4..eb3f893a5 100644 --- a/src/decoder/plugins/MikmodDecoderPlugin.cxx +++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx @@ -107,7 +107,7 @@ mikmod_decoder_init(const ConfigBlock &block) static char params[] = ""; mikmod_loop = block.GetBlockValue("loop", false); - mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100u); + mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100U); if (!audio_valid_sample_rate(mikmod_sample_rate)) throw FormatRuntimeError("Invalid sample rate in line %d: %u", block.line, mikmod_sample_rate); diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx index 8a22e0817..581f78981 100644 --- a/src/decoder/plugins/SidplayDecoderPlugin.cxx +++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx @@ -115,7 +115,7 @@ sidplay_init(const ConfigBlock &block) if (!database_path.IsNull()) songlength_database = sidplay_load_songlength_db(database_path); - default_songlength = block.GetPositiveValue("default_songlength", 0u); + default_songlength = block.GetPositiveValue("default_songlength", 0U); all_files_are_containers = block.GetBlockValue("all_files_are_containers", true); @@ -387,7 +387,7 @@ sidplay_file_decode(DecoderClient &client, Path path_fs) const unsigned timebase = player.timebase(); #endif const unsigned end = duration.IsNegative() - ? 0u + ? 0U : duration.ToScale(timebase); DecoderCommand cmd; diff --git a/src/encoder/plugins/FlacEncoderPlugin.cxx b/src/encoder/plugins/FlacEncoderPlugin.cxx index ed70a6832..7af23c534 100644 --- a/src/encoder/plugins/FlacEncoderPlugin.cxx +++ b/src/encoder/plugins/FlacEncoderPlugin.cxx @@ -94,7 +94,7 @@ public: }; PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block) - :compression(block.GetBlockValue("compression", 5u)) + :compression(block.GetBlockValue("compression", 5U)) { } diff --git a/src/encoder/plugins/OpusEncoderPlugin.cxx b/src/encoder/plugins/OpusEncoderPlugin.cxx index 8a55ea07e..fea308d61 100644 --- a/src/encoder/plugins/OpusEncoderPlugin.cxx +++ b/src/encoder/plugins/OpusEncoderPlugin.cxx @@ -107,7 +107,7 @@ PreparedOpusEncoder::PreparedOpusEncoder(const ConfigBlock &block) throw std::runtime_error("Invalid bit rate"); } - complexity = block.GetBlockValue("complexity", 10u); + complexity = block.GetBlockValue("complexity", 10U); if (complexity > 10) throw std::runtime_error("Invalid complexity"); diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index 6b7630941..eb7fb26c8 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -113,7 +113,7 @@ input_cdio_init(EventLoop &, const ConfigBlock &block) throw FormatRuntimeError("Unrecognized 'default_byte_order' setting: %s", value); } - speed = block.GetBlockValue("speed",0u); + speed = block.GetBlockValue("speed",0U); } struct CdioUri { diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index 71161c770..10b257ccc 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -320,7 +320,7 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block) http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); proxy = block.GetBlockValue("proxy"); - proxy_port = block.GetBlockValue("proxy_port", 0u); + proxy_port = block.GetBlockValue("proxy_port", 0U); proxy_user = block.GetBlockValue("proxy_user"); proxy_password = block.GetBlockValue("proxy_password"); @@ -365,9 +365,9 @@ CurlInputStream::InitEasy() request = new CurlRequest(**curl_init, GetURI(), *this); request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases); - request->SetOption(CURLOPT_FOLLOWLOCATION, 1l); - request->SetOption(CURLOPT_MAXREDIRS, 5l); - request->SetOption(CURLOPT_FAILONERROR, 1l); + request->SetOption(CURLOPT_FOLLOWLOCATION, 1L); + request->SetOption(CURLOPT_MAXREDIRS, 5L); + request->SetOption(CURLOPT_FAILONERROR, 1L); if (proxy != nullptr) request->SetOption(CURLOPT_PROXY, proxy); @@ -380,8 +380,8 @@ CurlInputStream::InitEasy() StringFormat<1024>("%s:%s", proxy_user, proxy_password).c_str()); - request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l); - request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l); + request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L); + request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L); request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get()); } diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx index d36b83ef3..a47e7f703 100644 --- a/src/lib/curl/Request.cxx +++ b/src/lib/curl/Request.cxx @@ -56,7 +56,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global, easy.SetUserAgent("Music Player Daemon " VERSION); easy.SetHeaderFunction(_HeaderFunction, this); easy.SetWriteFunction(WriteFunction, this); - easy.SetOption(CURLOPT_NETRC, 1l); + easy.SetOption(CURLOPT_NETRC, 1L); easy.SetErrorBuffer(error_buffer); easy.SetNoProgress(); easy.SetNoSignal(); diff --git a/src/lib/icu/CaseFold.cxx b/src/lib/icu/CaseFold.cxx index c1b87ca49..8ad8dadfa 100644 --- a/src/lib/icu/CaseFold.cxx +++ b/src/lib/icu/CaseFold.cxx @@ -59,7 +59,7 @@ try { if (u.IsNull()) return AllocatedString<>::Duplicate(src); - AllocatedArray folded(u.size() * 2u); + AllocatedArray folded(u.size() * 2U); UErrorCode error_code = U_ZERO_ERROR; size_t folded_length = u_strFoldCase(folded.begin(), folded.size(), diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 8ccdd2121..214abd45e 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -395,7 +395,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block) #endif buffer_time(block.GetPositiveValue("buffer_time", MPD_ALSA_BUFFER_TIME_US)), - period_time(block.GetPositiveValue("period_time", 0u)) + period_time(block.GetPositiveValue("period_time", 0U)) { #ifdef SND_PCM_NO_AUTO_RESAMPLE if (!block.GetBlockValue("auto_resample", true)) diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index 72ec94d22..10fdd130d 100644 --- a/src/output/plugins/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -102,7 +102,7 @@ MakeAoError() AoOutput::AoOutput(const ConfigBlock &block) :AudioOutput(0), - write_size(block.GetPositiveValue("write_size", 1024u)) + write_size(block.GetPositiveValue("write_size", 1024U)) { const char *value = block.GetBlockValue("driver", "default"); if (0 == strcmp(value, "default")) diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index 141105909..d4ae40129 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -212,7 +212,7 @@ JackOutput::JackOutput(const ConfigBlock &block) num_source_ports, num_destination_ports, block.line); - ringbuffer_size = block.GetPositiveValue("ringbuffer_size", 32768u); + ringbuffer_size = block.GetPositiveValue("ringbuffer_size", 32768U); } inline jack_nframes_t diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx index 659de603f..e5aaa63e6 100644 --- a/src/output/plugins/ShoutOutputPlugin.cxx +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -101,7 +101,7 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block) { const char *host = require_block_string(block, "host"); const char *mount = require_block_string(block, "mount"); - unsigned port = block.GetBlockValue("port", 0u); + unsigned port = block.GetBlockValue("port", 0U); if (port == 0) throw std::runtime_error("shout port must be configured"); diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx index 7a73ff999..e90dc6c2e 100644 --- a/src/output/plugins/httpd/HttpdOutputPlugin.cxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx @@ -54,11 +54,11 @@ HttpdOutput::HttpdOutput(EventLoop &_loop, const ConfigBlock &block) genre = block.GetBlockValue("genre", "Set genre in config"); website = block.GetBlockValue("website", "Set website in config"); - clients_max = block.GetBlockValue("max_clients", 0u); + clients_max = block.GetBlockValue("max_clients", 0U); /* set up bind_to_address */ - ServerSocketAddGeneric(*this, block.GetBlockValue("bind_to_address"), block.GetBlockValue("port", 8000u)); + ServerSocketAddGeneric(*this, block.GetBlockValue("bind_to_address"), block.GetBlockValue("port", 8000U)); /* determine content type */ content_type = prepared_encoder->GetMimeType(); diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx index 5417dae29..e2bc99bdf 100644 --- a/src/storage/plugins/CurlStorage.cxx +++ b/src/storage/plugins/CurlStorage.cxx @@ -266,8 +266,8 @@ public: CommonExpatParser(ExpatNamespaceSeparator{'|'}) { request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND"); - request.SetOption(CURLOPT_FOLLOWLOCATION, 1l); - request.SetOption(CURLOPT_MAXREDIRS, 1l); + request.SetOption(CURLOPT_FOLLOWLOCATION, 1L); + request.SetOption(CURLOPT_MAXREDIRS, 1L); request_headers.Append(StringFormat<40>("depth: %u", depth)); diff --git a/src/util/UTF8.cxx b/src/util/UTF8.cxx index 21ce6e1a8..42e89e48a 100644 --- a/src/util/UTF8.cxx +++ b/src/util/UTF8.cxx @@ -202,7 +202,7 @@ struct CheckSequenceUTF8 { }; template<> -struct CheckSequenceUTF8<0u> { +struct CheckSequenceUTF8<0U> { constexpr bool operator()(gcc_unused const char *p) const noexcept { return true; } @@ -215,7 +215,7 @@ InnerSequenceLengthUTF8(const char *p) noexcept { return CheckSequenceUTF8()(p) ? L + 1 - : 0u; + : 0U; } size_t From 4dd10894bae81f142239e4580910bd2f04067ca4 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 24 Mar 2020 14:52:22 +0100 Subject: [PATCH 19/42] lib/curl/Request: fix Exception "error" on Android Apparently, it's not possible to change CURLOPT_NETRC on Android. --- NEWS | 2 ++ src/lib/curl/Request.cxx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 9d73dba96..61903f216 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.21.22 (not yet released) +* input + - curl: fix streaming errors on Android * playlist - rss: support MIME type application/xml * mixer diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx index a47e7f703..75ce11eed 100644 --- a/src/lib/curl/Request.cxx +++ b/src/lib/curl/Request.cxx @@ -56,7 +56,9 @@ CurlRequest::CurlRequest(CurlGlobal &_global, easy.SetUserAgent("Music Player Daemon " VERSION); easy.SetHeaderFunction(_HeaderFunction, this); easy.SetWriteFunction(WriteFunction, this); +#ifndef ANDROID easy.SetOption(CURLOPT_NETRC, 1L); +#endif easy.SetErrorBuffer(error_buffer); easy.SetNoProgress(); easy.SetNoSignal(); From 9c15760c4d932b1a9fde83661d0a58dd8e2efece Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 25 Mar 2020 22:08:17 +0100 Subject: [PATCH 20/42] android/Main: handle API26 NotificationChannel This seems to be required on recent Android versions (tested with Android 10). This is also required for android TV services (cf. next commit). This is done using Java reflection so that the project doesn't depend on android compat libs. --- android/AndroidManifest.xml | 1 + android/src/Main.java | 49 ++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 6de3d89eb..01ff051c4 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -11,6 +11,7 @@ + ncClass = Class.forName("android.app.NotificationChannel"); + Constructor ncCtor = ncClass.getConstructor(String.class, CharSequence.class, int.class); + Object nc = ncCtor.newInstance(id, name, importance); + + Method nmCreateNotificationChannelMethod = + NotificationManager.class.getMethod("createNotificationChannel", ncClass); + nmCreateNotificationChannelMethod.invoke(notificationManager, nc); + + Constructor nbCtor = Notification.Builder.class.getConstructor(Context.class, String.class); + return (Notification.Builder) nbCtor.newInstance(this, id); + } catch (Exception e) + { + Log.e(TAG, "error creating the NotificationChannel", e); + return null; + } + } + private void start() { if (mThread != null) return; - mThread = new Thread(this); - mThread.start(); final Intent mainIntent = new Intent(this, Settings.class); mainIntent.setAction("android.intent.action.MAIN"); @@ -168,13 +197,25 @@ public class Main extends Service implements Runnable { final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_CANCEL_CURRENT); - Notification notification = new Notification.Builder(this) - .setContentTitle(getText(R.string.notification_title_mpd_running)) + Notification.Builder nBuilder; + if (Build.VERSION.SDK_INT >= 26 /* Build.VERSION_CODES.O */) + { + nBuilder = createNotificationBuilderWithChannel(); + if (nBuilder == null) + return; + } + else + nBuilder = new Notification.Builder(this); + + Notification notification = nBuilder.setContentTitle(getText(R.string.notification_title_mpd_running)) .setContentText(getText(R.string.notification_text_mpd_running)) .setSmallIcon(R.drawable.notification_icon) .setContentIntent(contentIntent) .build(); + mThread = new Thread(this); + mThread.start(); + startForeground(R.string.notification_title_mpd_running, notification); startService(new Intent(this, Main.class)); } From 4ff2532330c004494a4abf844f4177735b307389 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 25 Mar 2020 21:57:50 +0100 Subject: [PATCH 21/42] android: add TV support TODO: Not sure the app could be accepted on the play store without a valid banner. --- NEWS | 2 ++ android/AndroidManifest.xml | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/NEWS b/NEWS index 0e6d253c0..f0617e47d 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ ver 0.21.22 (not yet released) - rss: support MIME type application/xml * mixer - android: new mixer plugin for "sles" output +* Android + - TV support * fix build failures with uClibc-ng ver 0.21.21 (2020/03/19) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 01ff051c4..4dcdba4fd 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -7,6 +7,11 @@ + + + @@ -15,6 +20,7 @@ @@ -23,6 +29,14 @@ + + + + + + + From 60f211620287374e8052d086aa0332c7f63b9cb1 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 25 Mar 2020 22:23:10 +0100 Subject: [PATCH 22/42] android/Settings: remove the EXPIRIMENTAL text Using MPD from Android since quite some times now. I consider it very stable now. --- android/src/Settings.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/Settings.java b/android/src/Settings.java index 2713fdc8c..873e7ff2d 100644 --- a/android/src/Settings.java +++ b/android/src/Settings.java @@ -105,12 +105,13 @@ public class Settings extends Activity { else mRunButton.setChecked(false); mFirstRun = true; + mTextStatus.setText(""); break; case MSG_STARTED: Log.d(TAG, "onStarted"); mRunButton.setChecked(true); mFirstRun = true; - mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX + mTextStatus.setText("MPD service started"); break; case MSG_LOG: if (mLogListArray.size() > MAX_LOGS) From 9c3e1d450a9a03172f9cc84dc19c3726d61d663b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 31 Mar 2020 15:07:18 +0200 Subject: [PATCH 23/42] fs/io/GunzipReader: increase buffer size to 64 kB Reduces I/O overhead while reading a compressed database file. --- NEWS | 2 ++ src/fs/io/GunzipReader.hxx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f0617e47d..0e9346ea6 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.21.22 (not yet released) +* database + - simple: reduce I/O overhead while reading database file * input - curl: fix streaming errors on Android * playlist diff --git a/src/fs/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx index 817698f14..05e5330a9 100644 --- a/src/fs/io/GunzipReader.hxx +++ b/src/fs/io/GunzipReader.hxx @@ -36,7 +36,7 @@ class GunzipReader final : public Reader { z_stream z; - StaticFifoBuffer buffer; + StaticFifoBuffer buffer; public: /** From 90184e0ce76877912bc168597e7d40eb2040c1ac Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 15:49:12 +0200 Subject: [PATCH 24/42] python/build/libs.py: update CURL to 7.69.1 --- python/build/libs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/build/libs.py b/python/build/libs.py index 7c317d46a..627e3360f 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -341,8 +341,8 @@ ffmpeg = FfmpegProject( ) curl = AutotoolsProject( - 'http://curl.haxx.se/download/curl-7.68.0.tar.xz', - 'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a', + 'http://curl.haxx.se/download/curl-7.69.1.tar.xz', + '03c7d5e6697f7b7e40ada1b2256e565a555657398e6c1fcfa4cb251ccd819d4f', 'lib/libcurl.a', [ '--disable-shared', '--enable-static', From 60610e90b1258b37f6ae946cfc5b2737c192d2ae Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 15:53:43 +0200 Subject: [PATCH 25/42] test/net/TestIPv[46]Address: fix Windows build errors --- test/net/TestIPv4Address.cxx | 13 +++++++++++-- test/net/TestIPv6Address.cxx | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/test/net/TestIPv4Address.cxx b/test/net/TestIPv4Address.cxx index aa7f727be..a6c05e681 100644 --- a/test/net/TestIPv4Address.cxx +++ b/test/net/TestIPv4Address.cxx @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 Max Kellermann + * Copyright 2012-2020 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -34,13 +34,22 @@ #include +#ifndef _WIN32 #include +#endif static std::string ToString(const struct in_addr &a) { +#ifdef _WIN32 + /* on mingw32, the parameter is non-const (PVOID) */ + const auto p = const_cast(&a); +#else + const auto p = &a; +#endif + char buffer[256]; - const char *result = inet_ntop(AF_INET, &a, buffer, sizeof(buffer)); + const char *result = inet_ntop(AF_INET, p, buffer, sizeof(buffer)); if (result == nullptr) throw std::runtime_error("inet_ntop() failed"); return result; diff --git a/test/net/TestIPv6Address.cxx b/test/net/TestIPv6Address.cxx index 325d25bbf..cdadf6a7f 100644 --- a/test/net/TestIPv6Address.cxx +++ b/test/net/TestIPv6Address.cxx @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 Max Kellermann + * Copyright 2012-2020 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -34,13 +34,22 @@ #include +#ifndef _WIN32 #include +#endif static std::string ToString(const struct in6_addr &a) { +#ifdef _WIN32 + /* on mingw32, the parameter is non-const (PVOID) */ + const auto p = const_cast(&a); +#else + const auto p = &a; +#endif + char buffer[256]; - const char *result = inet_ntop(AF_INET6, &a, buffer, sizeof(buffer)); + const char *result = inet_ntop(AF_INET6, p, buffer, sizeof(buffer)); if (result == nullptr) throw std::runtime_error("inet_ntop() failed"); return result; From a4c925c8d7b67ef8f099710682e16622746ce648 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 16:10:49 +0200 Subject: [PATCH 26/42] test/meson.build: move TestTime to time/ --- test/meson.build | 14 +------------- test/{ => time}/TestISO8601.cxx | 0 test/time/meson.build | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 13 deletions(-) rename test/{ => time}/TestISO8601.cxx (100%) create mode 100644 test/time/meson.build diff --git a/test/meson.build b/test/meson.build index 4e3fbab49..f20a79aa0 100644 --- a/test/meson.build +++ b/test/meson.build @@ -24,6 +24,7 @@ gtest_dep = declare_dependency( ) subdir('net') +subdir('time') executable( 'read_conf', @@ -51,19 +52,6 @@ test('TestUtil', executable( ], )) -test( - 'TestTime', - executable( - 'TestTime', - 'TestISO8601.cxx', - include_directories: inc, - dependencies: [ - time_dep, - gtest_dep, - ], - ), -) - test('TestRewindInputStream', executable( 'TestRewindInputStream', 'TestRewindInputStream.cxx', diff --git a/test/TestISO8601.cxx b/test/time/TestISO8601.cxx similarity index 100% rename from test/TestISO8601.cxx rename to test/time/TestISO8601.cxx diff --git a/test/time/meson.build b/test/time/meson.build new file mode 100644 index 000000000..0aba67ac2 --- /dev/null +++ b/test/time/meson.build @@ -0,0 +1,16 @@ +test_time_sources = [ + 'TestISO8601.cxx', +] + +test( + 'TestTime', + executable( + 'TestTime', + test_time_sources, + include_directories: inc, + dependencies: [ + time_dep, + gtest_dep, + ], + ), +) From 62229f14da8d7794f73491c28db47fd9e1c25b3f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 16:16:11 +0200 Subject: [PATCH 27/42] test/time: add test for LocalTime(), GmTime() --- test/time/TestConvert.cxx | 65 +++++++++++++++++++++++++++++++++++++++ test/time/meson.build | 1 + 2 files changed, 66 insertions(+) create mode 100644 test/time/TestConvert.cxx diff --git a/test/time/TestConvert.cxx b/test/time/TestConvert.cxx new file mode 100644 index 000000000..ff31d7097 --- /dev/null +++ b/test/time/TestConvert.cxx @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Max Kellermann + * All rights reserved. + * + * author: Max Kellermann + * + * 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 "time/Convert.hxx" + +#include + +static constexpr time_t times[] = { + 1234567890, + 1580566807, + 1585750807, + 1590934807, +}; + +TEST(Time, LocalTime) +{ + /* convert back and forth using local time zone */ + + for (const auto t : times) { + auto tp = std::chrono::system_clock::from_time_t(t); + auto tm = LocalTime(tp); + EXPECT_EQ(MakeTime(tm), tp); + } +} + +TEST(Time, GmTime) +{ + /* convert back and forth using UTC */ + + for (const auto t : times) { + auto tp = std::chrono::system_clock::from_time_t(t); + auto tm = GmTime(tp); + EXPECT_EQ(std::chrono::system_clock::to_time_t(TimeGm(tm)), + t); + } +} diff --git a/test/time/meson.build b/test/time/meson.build index 0aba67ac2..53b583cf1 100644 --- a/test/time/meson.build +++ b/test/time/meson.build @@ -1,4 +1,5 @@ test_time_sources = [ + 'TestConvert.cxx', 'TestISO8601.cxx', ] From 672bc3ab671e59691cbafdb48a71f38ed3735b04 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 17 Mar 2020 16:59:21 +0100 Subject: [PATCH 28/42] time/Convert: fix GetTimeZoneOffset() on Windows Was using the wrong parameter. --- NEWS | 2 ++ src/time/Convert.cxx | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 0e9346ea6..ec8566280 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ ver 0.21.22 (not yet released) - android: new mixer plugin for "sles" output * Android - TV support +* Windows + - fix time zone offset check * fix build failures with uClibc-ng ver 0.21.21 (2020/03/19) diff --git a/src/time/Convert.cxx b/src/time/Convert.cxx index 2351f4b52..352c60604 100644 --- a/src/time/Convert.cxx +++ b/src/time/Convert.cxx @@ -77,15 +77,15 @@ static time_t GetTimeZoneOffset() noexcept { time_t t = 1234567890; - struct tm tm; - tm.tm_isdst = 0; #ifdef _WIN32 struct tm *p = gmtime(&t); #else + struct tm tm; + tm.tm_isdst = 0; struct tm *p = &tm; gmtime_r(&t, p); #endif - return t - mktime(&tm); + return t - mktime(p); } #endif /* !__GLIBC__ */ From 3852ddbbceafd36804a6017c53c0a497bac58990 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 16:36:17 +0200 Subject: [PATCH 29/42] .travis.yml: install more packages on OSX Enable lots of plugins for better CI coverage. --- .travis.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.travis.yml b/.travis.yml index 38b72d547..d546e6500 100644 --- a/.travis.yml +++ b/.travis.yml @@ -126,6 +126,22 @@ jobs: packages: - ccache - meson + - icu4c + - ffmpeg + - libnfs + - yajl + - libupnp + - libid3tag + - chromaprint + - libsamplerate + - libsoxr + - libzzip + - flac + - opus + - libvorbis + - faad2 + - wavpack + - libmpdclient update: true env: - MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1" From c00ce42bca3830672395ea66c9083dc3b2f6fa7b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 17:17:29 +0200 Subject: [PATCH 30/42] python/build/libs.py: update libmpdclient to 2.18 --- python/build/libs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/build/libs.py b/python/build/libs.py index 627e3360f..af018ed8f 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -9,8 +9,8 @@ from build.ffmpeg import FfmpegProject from build.boost import BoostProject libmpdclient = MesonProject( - 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.17.tar.xz', - 'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559', + 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz', + '4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa', 'lib/libmpdclient.a', ) From 82700430535f2215eacad7ced492262c96e4ba5d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 17:28:58 +0200 Subject: [PATCH 31/42] Revert "decoder/ffmpeg: copy the AVPacket in ffmpeg_send_packet()" This reverts commit eb192137d61b0c62541904351660f9cd63bc90d5. This is no longer necessary because we require FFmpeg 3.1 or newer since MPD 0.21.2. This fixes a deprecation warning because the implicit AVPacket copy constructor copies the deprecated attribute `convergence_duration`. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 2b55316e7..fa7ae1371 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -297,7 +297,7 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is, */ static DecoderCommand ffmpeg_send_packet(DecoderClient &client, InputStream &is, - AVPacket &&packet, + const AVPacket &packet, AVCodecContext &codec_context, const AVStream &stream, AVFrame &frame, @@ -350,24 +350,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is, return cmd; } -static DecoderCommand -ffmpeg_send_packet(DecoderClient &client, InputStream &is, - const AVPacket &packet, - AVCodecContext &codec_context, - const AVStream &stream, - AVFrame &frame, - uint64_t min_frame, size_t pcm_frame_size, - FfmpegBuffer &buffer) -{ - return ffmpeg_send_packet(client, is, - /* copy the AVPacket, because FFmpeg - < 3.0 requires this */ - AVPacket(packet), - codec_context, stream, - frame, min_frame, pcm_frame_size, - buffer); -} - gcc_const static SampleFormat ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept From b267ba5f0a79817de1f1c52d5c3896cdb4a3f682 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 1 Apr 2020 19:51:47 +0200 Subject: [PATCH 32/42] tag/Pool: enlarge hash table This consumes more memory (plus 48 kB on 32 bit systems), but reduces the number of hash collisions, speeding up MPD startup with large databases. --- NEWS | 2 +- src/tag/Pool.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index ec8566280..0c4d9af64 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ ver 0.21.22 (not yet released) * database - - simple: reduce I/O overhead while reading database file + - simple: optimize startup * input - curl: fix streaming errors on Android * playlist diff --git a/src/tag/Pool.cxx b/src/tag/Pool.cxx index 95172b46a..89ca0d4fb 100644 --- a/src/tag/Pool.cxx +++ b/src/tag/Pool.cxx @@ -32,7 +32,7 @@ Mutex tag_pool_lock; -static constexpr size_t NUM_SLOTS = 4093; +static constexpr size_t NUM_SLOTS = 16127; struct TagPoolSlot { TagPoolSlot *next; From cdddaf21b006163fa771ab2a626925d5bbaa0eb9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 15:36:58 +0200 Subject: [PATCH 33/42] db/simple/Directory: optimize GetName() using the parent's path This method gets called a lot during MPD startup, via FindChild() and directory_load_subdir(), so this is worth optimizing at the expense of code readability. This speeds up MPD startup by 10%. --- src/db/plugins/simple/Directory.cxx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx index 27602afc2..66aa6cd48 100644 --- a/src/db/plugins/simple/Directory.cxx +++ b/src/db/plugins/simple/Directory.cxx @@ -32,6 +32,7 @@ #include "fs/Traits.hxx" #include "util/Alloc.hxx" #include "util/DeleteDisposer.hxx" +#include "util/StringCompare.hxx" #include #include @@ -69,7 +70,15 @@ Directory::GetName() const noexcept { assert(!IsRoot()); - return PathTraitsUTF8::GetBase(path.c_str()); + if (parent->IsRoot()) + return path.c_str(); + + assert(StringAfterPrefix(path.c_str(), parent->path.c_str()) != nullptr); + assert(*StringAfterPrefix(path.c_str(), parent->path.c_str()) == PathTraitsUTF8::SEPARATOR); + + /* strip the parent directory path and the slash separator + from this directory's path, and the base name remains */ + return path.c_str() + parent->path.length() + 1; } Directory * From 61d7b436a29d492610fbc6d60928427f1533dfbe Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 16:24:08 +0200 Subject: [PATCH 34/42] fs/NarrowPath: un-inline Windows constructor --- src/fs/NarrowPath.cxx | 36 ++++++++++++++++++++++++++++++++++++ src/fs/NarrowPath.hxx | 10 +--------- src/fs/meson.build | 1 + 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 src/fs/NarrowPath.cxx diff --git a/src/fs/NarrowPath.cxx b/src/fs/NarrowPath.cxx new file mode 100644 index 000000000..1939998ec --- /dev/null +++ b/src/fs/NarrowPath.cxx @@ -0,0 +1,36 @@ +/* + * Copyright 2003-2018 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 "NarrowPath.hxx" + +#ifdef _UNICODE + +#include "lib/icu/Win32.hxx" + +#include + +NarrowPath::NarrowPath(Path _path) noexcept + :value(WideCharToMultiByte(CP_ACP, _path.c_str())) +{ + if (value.IsNull()) + /* fall back to empty string */ + value = Value::Empty(); +} + +#endif /* _UNICODE */ diff --git a/src/fs/NarrowPath.hxx b/src/fs/NarrowPath.hxx index 20d05b7cb..df92053ba 100644 --- a/src/fs/NarrowPath.hxx +++ b/src/fs/NarrowPath.hxx @@ -21,12 +21,9 @@ #define MPD_FS_NARROW_PATH_HXX #include "Path.hxx" -#include "util/Macros.hxx" #ifdef _UNICODE -#include "lib/icu/Win32.hxx" #include "util/AllocatedString.hxx" -#include #else #include "util/StringPointer.hxx" #endif @@ -48,12 +45,7 @@ class NarrowPath { public: #ifdef _UNICODE - explicit NarrowPath(Path _path) - :value(WideCharToMultiByte(CP_ACP, _path.c_str())) { - if (value.IsNull()) - /* fall back to empty string */ - value = Value::Empty(); - } + explicit NarrowPath(Path _path) noexcept; #else explicit NarrowPath(Path _path):value(_path.c_str()) {} #endif diff --git a/src/fs/meson.build b/src/fs/meson.build index 201d446a2..7ceb3f39d 100644 --- a/src/fs/meson.build +++ b/src/fs/meson.build @@ -6,6 +6,7 @@ fs_sources = [ 'Path.cxx', 'Path2.cxx', 'AllocatedPath.cxx', + 'NarrowPath.cxx', 'FileSystem.cxx', 'List.cxx', 'StandardDirectory.cxx', From 4d453a8313c1eab29880666f3e34164c4e73d35c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 16:18:06 +0200 Subject: [PATCH 35/42] fs/NarrowPath: add class FromNarrowPath Move code from ParseCommandLine(). --- src/CommandLine.cxx | 14 ++------------ src/fs/NarrowPath.cxx | 19 +++++++++++++++++++ src/fs/NarrowPath.hxx | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index 5ede49580..93396bc0b 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -33,11 +33,11 @@ #include "playlist/PlaylistRegistry.hxx" #include "playlist/PlaylistPlugin.hxx" #include "fs/AllocatedPath.hxx" +#include "fs/NarrowPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" #include "fs/StandardDirectory.hxx" #include "system/Error.hxx" -#include "util/Macros.hxx" #include "util/RuntimeError.hxx" #include "util/Domain.hxx" #include "util/OptionDef.hxx" @@ -380,17 +380,7 @@ ParseCommandLine(int argc, char **argv, struct options &options, if (config_file != nullptr) { /* use specified configuration file */ -#ifdef _UNICODE - wchar_t buffer[MAX_PATH]; - auto result = MultiByteToWideChar(CP_ACP, 0, config_file, -1, - buffer, ARRAY_SIZE(buffer)); - if (result <= 0) - throw MakeLastError("MultiByteToWideChar() failed"); - - ReadConfigFile(config, Path::FromFS(buffer)); -#else - ReadConfigFile(config, Path::FromFS(config_file)); -#endif + ReadConfigFile(config, FromNarrowPath(config_file)); return; } diff --git a/src/fs/NarrowPath.cxx b/src/fs/NarrowPath.cxx index 1939998ec..d92af7eae 100644 --- a/src/fs/NarrowPath.cxx +++ b/src/fs/NarrowPath.cxx @@ -22,6 +22,8 @@ #ifdef _UNICODE #include "lib/icu/Win32.hxx" +#include "system/Error.hxx" +#include "util/Macros.hxx" #include @@ -33,4 +35,21 @@ NarrowPath::NarrowPath(Path _path) noexcept value = Value::Empty(); } +static AllocatedPath +AcpToAllocatedPath(const char *s) +{ + wchar_t buffer[MAX_PATH]; + auto result = MultiByteToWideChar(CP_ACP, 0, s, -1, + buffer, ARRAY_SIZE(buffer)); + if (result <= 0) + throw MakeLastError("MultiByteToWideChar() failed"); + + return AllocatedPath::FromFS(buffer); +} + +FromNarrowPath::FromNarrowPath(const char *s) + :value(AcpToAllocatedPath(s)) +{ +} + #endif /* _UNICODE */ diff --git a/src/fs/NarrowPath.hxx b/src/fs/NarrowPath.hxx index df92053ba..48198ffbf 100644 --- a/src/fs/NarrowPath.hxx +++ b/src/fs/NarrowPath.hxx @@ -23,6 +23,7 @@ #include "Path.hxx" #ifdef _UNICODE +#include "AllocatedPath.hxx" #include "util/AllocatedString.hxx" #else #include "util/StringPointer.hxx" @@ -59,4 +60,38 @@ public: } }; +/** + * A path name converted from a "narrow" string. This is used to + * import an existing narrow string to a #Path. + */ +class FromNarrowPath { +#ifdef _UNICODE + using Value = AllocatedPath; +#else + using Value = Path; +#endif + + Value value{nullptr}; + +public: + FromNarrowPath() = default; + +#ifdef _UNICODE + /** + * Throws on error. + */ + FromNarrowPath(const char *s); +#else + constexpr FromNarrowPath(const char *s) noexcept + :value(Value::FromFS(s)) {} +#endif + +#ifndef _UNICODE + constexpr +#endif + operator Path() const noexcept { + return value; + } +}; + #endif From 9c66b0414a02ff03fcf3c122dbeb83d8017ff1b8 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 16:40:18 +0200 Subject: [PATCH 36/42] test/*: fix Windows build using class FromNarrowPath --- test/ContainerScan.cxx | 3 ++- test/DumpDatabase.cxx | 3 ++- test/ReadApeTags.cxx | 3 ++- test/WriteFile.cxx | 3 ++- test/dump_playlist.cxx | 3 ++- test/read_conf.cxx | 4 ++-- test/read_tags.cxx | 9 +++++---- test/run_decoder.cxx | 7 ++++--- test/run_filter.cxx | 3 ++- test/run_gunzip.cxx | 3 ++- test/run_input.cxx | 5 +++-- test/run_output.cxx | 3 ++- 12 files changed, 30 insertions(+), 19 deletions(-) diff --git a/test/ContainerScan.cxx b/test/ContainerScan.cxx index 6512ba9cb..ac1fca3d3 100644 --- a/test/ContainerScan.cxx +++ b/test/ContainerScan.cxx @@ -23,6 +23,7 @@ #include "decoder/DecoderList.hxx" #include "decoder/DecoderPlugin.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "fs/io/StdioOutputStream.hxx" #include "fs/io/BufferedOutputStream.hxx" #include "util/UriUtil.hxx" @@ -63,7 +64,7 @@ try { return EXIT_FAILURE; } - const Path path = Path::FromFS(argv[1]); + const FromNarrowPath path = argv[1]; const ScopeDecoderPluginsInit decoder_plugins_init({}); diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx index 76315c133..39801baf2 100644 --- a/test/DumpDatabase.cxx +++ b/test/DumpDatabase.cxx @@ -29,6 +29,7 @@ #include "ConfigGlue.hxx" #include "tag/Config.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "event/Thread.hxx" #include "util/ScopeExit.hxx" #include "util/PrintException.hxx" @@ -107,7 +108,7 @@ try { return 1; } - const Path config_path = Path::FromFS(argv[1]); + const FromNarrowPath config_path = argv[1]; const char *const plugin_name = argv[2]; const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); diff --git a/test/ReadApeTags.cxx b/test/ReadApeTags.cxx index 7afba2b49..028874edc 100644 --- a/test/ReadApeTags.cxx +++ b/test/ReadApeTags.cxx @@ -21,6 +21,7 @@ #include "tag/ApeLoader.hxx" #include "thread/Mutex.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "input/InputStream.hxx" #include "input/LocalOpen.hxx" #include "util/StringView.hxx" @@ -58,7 +59,7 @@ try { return EXIT_FAILURE; } - const Path path = Path::FromFS(argv[1]); + const FromNarrowPath path = argv[1]; Mutex mutex; diff --git a/test/WriteFile.cxx b/test/WriteFile.cxx index 9cba451e9..1feeed1d8 100644 --- a/test/WriteFile.cxx +++ b/test/WriteFile.cxx @@ -18,6 +18,7 @@ */ #include "fs/io/FileOutputStream.hxx" +#include "fs/NarrowPath.hxx" #include "util/PrintException.hxx" #include @@ -54,7 +55,7 @@ try { return EXIT_FAILURE; } - const Path path = Path::FromFS(argv[1]); + const FromNarrowPath path = argv[1]; FileOutputStream fos(path); diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx index c4aea7de8..ea3be3a3f 100644 --- a/test/dump_playlist.cxx +++ b/test/dump_playlist.cxx @@ -28,6 +28,7 @@ #include "playlist/PlaylistRegistry.hxx" #include "playlist/PlaylistPlugin.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "fs/io/BufferedOutputStream.hxx" #include "fs/io/StdioOutputStream.hxx" #include "thread/Cond.hxx" @@ -54,7 +55,7 @@ try { return EXIT_FAILURE; } - const Path config_path = Path::FromFS(argv[1]); + const FromNarrowPath config_path = argv[1]; uri = argv[2]; /* initialize MPD */ diff --git a/test/read_conf.cxx b/test/read_conf.cxx index f6a8019e7..0f9873056 100644 --- a/test/read_conf.cxx +++ b/test/read_conf.cxx @@ -21,7 +21,7 @@ #include "config/Param.hxx" #include "config/File.hxx" #include "fs/Path.hxx" -#include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "util/PrintException.hxx" #include "util/RuntimeError.hxx" @@ -36,7 +36,7 @@ try { return EXIT_FAILURE; } - const Path config_path = Path::FromFS(argv[1]); + const FromNarrowPath config_path = argv[1]; const char *name = argv[2]; const auto option = ParseConfigOptionName(name); diff --git a/test/read_tags.cxx b/test/read_tags.cxx index a8ab2a061..7c392dc2b 100644 --- a/test/read_tags.cxx +++ b/test/read_tags.cxx @@ -27,6 +27,7 @@ #include "tag/Handler.hxx" #include "tag/Generic.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "AudioFormat.hxx" #include "util/ScopeExit.hxx" #include "util/StringBuffer.hxx" @@ -88,7 +89,7 @@ try { } decoder_name = argv[1]; - const Path path = Path::FromFS(argv[2]); + const char *path = argv[2]; EventThread io_thread; io_thread.Start(); @@ -107,7 +108,7 @@ try { DumpTagHandler h; bool success; try { - success = plugin->ScanFile(path, h); + success = plugin->ScanFile(FromNarrowPath(path), h); } catch (...) { PrintException(std::current_exception()); success = false; @@ -117,7 +118,7 @@ try { InputStreamPtr is; if (!success && plugin->scan_stream != NULL) { - is = InputStream::OpenReady(path.c_str(), mutex); + is = InputStream::OpenReady(path, mutex); success = plugin->ScanStream(*is, h); } @@ -130,7 +131,7 @@ try { if (is) ScanGenericTags(*is, h); else - ScanGenericTags(path, h); + ScanGenericTags(FromNarrowPath(path), h); } return 0; diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx index f7e3d6739..8d78534da 100644 --- a/test/run_decoder.cxx +++ b/test/run_decoder.cxx @@ -26,6 +26,7 @@ #include "input/Init.hxx" #include "input/InputStream.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "AudioFormat.hxx" #include "util/OptionDef.hxx" #include "util/OptionParser.hxx" @@ -44,7 +45,7 @@ struct CommandLine { const char *decoder = nullptr; const char *uri = nullptr; - Path config_path = nullptr; + FromNarrowPath config_path; bool verbose = false; }; @@ -68,7 +69,7 @@ ParseCommandLine(int argc, char **argv) while (auto o = option_parser.Next()) { switch (Option(o.index)) { case OPTION_CONFIG: - c.config_path = Path::FromFS(o.value); + c.config_path = o.value; break; case OPTION_VERBOSE: @@ -118,7 +119,7 @@ try { DumpDecoderClient client; if (plugin->file_decode != nullptr) { try { - plugin->FileDecode(client, Path::FromFS(c.uri)); + plugin->FileDecode(client, FromNarrowPath(c.uri)); } catch (StopDecoder) { } } else if (plugin->stream_decode != nullptr) { diff --git a/test/run_filter.cxx b/test/run_filter.cxx index 31452f440..5a70d6828 100644 --- a/test/run_filter.cxx +++ b/test/run_filter.cxx @@ -19,6 +19,7 @@ #include "ConfigGlue.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "AudioParser.hxx" #include "AudioFormat.hxx" #include "filter/LoadOne.hxx" @@ -68,7 +69,7 @@ try { return EXIT_FAILURE; } - const Path config_path = Path::FromFS(argv[1]); + const FromNarrowPath config_path = argv[1]; AudioFormat audio_format(44100, SampleFormat::S16, 2); diff --git a/test/run_gunzip.cxx b/test/run_gunzip.cxx index f8a0e618e..3a8619836 100644 --- a/test/run_gunzip.cxx +++ b/test/run_gunzip.cxx @@ -20,6 +20,7 @@ #include "fs/io/GunzipReader.hxx" #include "fs/io/FileReader.hxx" #include "fs/io/StdioOutputStream.hxx" +#include "fs/NarrowPath.hxx" #include "util/PrintException.hxx" #include @@ -62,7 +63,7 @@ try { return EXIT_FAILURE; } - Path path = Path::FromFS(argv[1]); + FromNarrowPath path = argv[1]; CopyGunzip(stdout, path); return EXIT_SUCCESS; diff --git a/test/run_input.cxx b/test/run_input.cxx index e5deafda8..a3b5e7277 100644 --- a/test/run_input.cxx +++ b/test/run_input.cxx @@ -32,6 +32,7 @@ #include "Log.hxx" #include "LogBackend.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "fs/io/BufferedOutputStream.hxx" #include "fs/io/StdioOutputStream.hxx" #include "util/ConstBuffer.hxx" @@ -51,7 +52,7 @@ struct CommandLine { const char *uri = nullptr; - Path config_path = nullptr; + FromNarrowPath config_path; bool verbose = false; @@ -79,7 +80,7 @@ ParseCommandLine(int argc, char **argv) while (auto o = option_parser.Next()) { switch (Option(o.index)) { case OPTION_CONFIG: - c.config_path = Path::FromFS(o.value); + c.config_path = o.value; break; case OPTION_VERBOSE: diff --git a/test/run_output.cxx b/test/run_output.cxx index c69e8f67b..d6fc0ada6 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -23,6 +23,7 @@ #include "ConfigGlue.hxx" #include "event/Thread.hxx" #include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" #include "AudioParser.hxx" #include "pcm/PcmConvert.hxx" #include "util/StringBuffer.hxx" @@ -111,7 +112,7 @@ try { return EXIT_FAILURE; } - const Path config_path = Path::FromFS(argv[1]); + const FromNarrowPath config_path = argv[1]; AudioFormat audio_format(44100, SampleFormat::S16, 2); From bad829509e43479a4c06d4f7cc36e37246b7b2ed Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 17:10:27 +0200 Subject: [PATCH 37/42] test/ShutdownHandler: add `inline` to work around Windows linker problems --- test/ShutdownHandler.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ShutdownHandler.hxx b/test/ShutdownHandler.hxx index 2283225d2..58ae74790 100644 --- a/test/ShutdownHandler.hxx +++ b/test/ShutdownHandler.hxx @@ -29,8 +29,8 @@ public: }; #ifdef _WIN32 -ShutdownHandler::ShutdownHandler(EventLoop &loop) {} -ShutdownHandler::~ShutdownHandler() {} +inline ShutdownHandler::ShutdownHandler(EventLoop &) {} +inline ShutdownHandler::~ShutdownHandler() {} #endif #endif From e94c436264ae76b10fa8a539a7f64362efb8e766 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 17:10:44 +0200 Subject: [PATCH 38/42] src/event/meson.build: depend in libnet.a The event library uses various libnet.a classes, e.g. SocketDescriptor. --- meson.build | 2 +- src/event/meson.build | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index c169384c7..c0f859d78 100644 --- a/meson.build +++ b/meson.build @@ -312,6 +312,7 @@ subdir('src/util') subdir('src/time') subdir('src/system') subdir('src/thread') +subdir('src/net') subdir('src/event') subdir('src/lib/dbus') @@ -336,7 +337,6 @@ subdir('src/lib/yajl') subdir('src/fs') subdir('src/config') -subdir('src/net') subdir('src/tag') subdir('src/pcm') subdir('src/neighbor') diff --git a/src/event/meson.build b/src/event/meson.build index bc13bbcd2..972943835 100644 --- a/src/event/meson.build +++ b/src/event/meson.build @@ -25,6 +25,7 @@ event_dep = declare_dependency( link_with: event, dependencies: [ thread_dep, + net_dep, system_dep, boost_dep, ], From a689b881d3a909c9075a832034c61c823443c2bc Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 17:13:50 +0200 Subject: [PATCH 39/42] test/meson.build: work around linker failure due to statically linked CURL --- test/meson.build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/meson.build b/test/meson.build index f20a79aa0..a1dc7840d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -336,6 +336,11 @@ if curl_dep.found() include_directories: inc, dependencies: [ curl_dep, + + # Explicitly linking with zlib here works around a linker + # failure on Windows, because our Windows CURL build is + # statically linked and thus declares no dependency on zlib + zlib_dep, ], ) From 9b11caa0e6222746219da3da58d98915f2d71fcb Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 17:17:06 +0200 Subject: [PATCH 40/42] fs/io/BufferedReader: larger default buffer (4 kB -> 16 kB) Reduce I/O overhead. --- src/fs/io/BufferedReader.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx index 0835c3d35..5b3f6a9cc 100644 --- a/src/fs/io/BufferedReader.hxx +++ b/src/fs/io/BufferedReader.hxx @@ -40,7 +40,7 @@ class BufferedReader { public: explicit BufferedReader(Reader &_reader) noexcept - :reader(_reader), buffer(4096) {} + :reader(_reader), buffer(16384) {} /** * Reset the internal state. Should be called after rewinding From afe2aaa5f6dbbd084f3af8d01f87f2b6b83a1656 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 17:17:44 +0200 Subject: [PATCH 41/42] fs/io/GzipOutputStream: increase buffer size to 16 kB Reduce I/O overhead. --- src/fs/io/GzipOutputStream.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fs/io/GzipOutputStream.cxx b/src/fs/io/GzipOutputStream.cxx index 67f97a278..051582ae3 100644 --- a/src/fs/io/GzipOutputStream.cxx +++ b/src/fs/io/GzipOutputStream.cxx @@ -62,7 +62,7 @@ GzipOutputStream::Flush() z.avail_in = 0; while (true) { - Bytef output[4096]; + Bytef output[16384]; z.next_out = output; z.avail_out = sizeof(output); @@ -87,7 +87,7 @@ GzipOutputStream::Write(const void *_data, size_t size) z.avail_in = size; while (z.avail_in > 0) { - Bytef output[4096]; + Bytef output[16384]; z.next_out = output; z.avail_out = sizeof(output); From 5ccfcffcc124e406233359fe8fe65b704b98b8c8 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 2 Apr 2020 17:48:56 +0200 Subject: [PATCH 42/42] release v0.21.22 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0c4d9af64..fb8a7d89a 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -ver 0.21.22 (not yet released) +ver 0.21.22 (2020/04/02) * database - simple: optimize startup * input