Commit dbe983e4 authored by Aleksei Nikiforov's avatar Aleksei Nikiforov

Merge tag 'v0.20.21' into sisyphus

release v0.20.21
parents fa1bb162 98afae25
......@@ -31,3 +31,8 @@ The following people have contributed code to MPD:
Jean-Francois Dockes <jf@dockes.org>
Yue Wang <yuleopen@gmail.com>
Matthew Leon Grinshpun <ml@matthewleon.com>
Dimitris Papastamos <sin@2f30.org>
Florian Schlichting <fsfs@debian.org>
François Revol <revol@free.fr>
Jacob Vosmaer <contact@jacobvosmaer.nl>
Thomas Guillem <thomas@gllm.fr>
......@@ -61,8 +61,8 @@ src_mpd_LDADD = \
libnet.a \
$(FS_LIBS) \
libsystem.a \
libutil.a \
$(ICU_LDADD) \
libutil.a \
$(SYSTEMD_DAEMON_LIBS)
src_mpd_SOURCES = \
......@@ -234,6 +234,7 @@ libmpd_a_SOURCES += \
endif
CURL_SOURCES = \
src/lib/curl/Error.hxx \
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
......@@ -292,8 +293,8 @@ clean-local:
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
ANDROID_SDK_PLATFORM = android-17
ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
ANDROID_SDK_PLATFORM = android-21
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
......@@ -307,7 +308,7 @@ ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
JAVA_SOURCES = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
JAVA_CLASSFILES_DIR = android/build/classes
......@@ -328,9 +329,9 @@ android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawabl
# R.java is generated by aapt, when resources.apk is generated
android/build/gen/org/musicpd/R.java: android/build/resources.apk
android/build/classes.dex: $(JAVA_SOURCES) android/build/gen/org/musicpd/R.java
android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
$(JAVAC) -source 1.5 -target 1.5 -Xlint:-options \
$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
-d $(JAVA_CLASSFILES_DIR) $^
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
......@@ -340,7 +341,7 @@ android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
android/build/lib/armeabi-v7a/libmpd.so: libmpd.so
android/build/lib/$(ANDROID_ABI)/libmpd.so: libmpd.so
mkdir -p $(@D)
rm -f $@
$(STRIP) -o $@ $<
......@@ -350,7 +351,7 @@ android/build/res/drawable/icon.png: mpd.svg
rsvg-convert --width=48 --height=48 $< -o $@
.DELETE_ON_ERROR: android/build/unsigned.apk
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/armeabi-v7a/libmpd.so
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/$(ANDROID_ABI)/libmpd.so
cp android/build/resources.apk $@
cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
......@@ -444,6 +445,7 @@ libutil_a_SOURCES = \
src/util/NumberParser.hxx \
src/util/MimeType.cxx src/util/MimeType.hxx \
src/util/StringBuffer.hxx \
src/util/StringFormat.hxx \
src/util/StringPointer.hxx \
src/util/StringView.cxx src/util/StringView.hxx \
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
......@@ -497,6 +499,7 @@ libthread_a_SOURCES = \
libnet_a_SOURCES = \
src/net/Features.hxx \
src/net/Init.hxx \
src/net/ToString.cxx src/net/ToString.hxx \
src/net/Resolver.cxx src/net/Resolver.hxx \
src/net/StaticSocketAddress.cxx src/net/StaticSocketAddress.hxx \
......@@ -711,6 +714,7 @@ NFS_SOURCES = \
src/lib/nfs/Cancellable.hxx \
src/lib/nfs/Lease.hxx \
src/lib/nfs/Connection.cxx src/lib/nfs/Connection.hxx \
src/lib/nfs/Error.cxx src/lib/nfs/Error.hxx \
src/lib/nfs/Manager.cxx src/lib/nfs/Manager.hxx \
src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \
src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \
......@@ -733,6 +737,8 @@ libstorage_a_SOURCES = \
src/storage/FileInfo.hxx
libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
$(EXPAT_CFLAGS) \
$(NFS_CFLAGS) \
$(SMBCLIENT_CFLAGS)
......@@ -1291,7 +1297,7 @@ endif
#
libinput_a_SOURCES = \
src/input/Domain.cxx src/input/Domain.hxx \
src/input/Error.cxx src/input/Error.hxx \
src/input/Init.cxx src/input/Init.hxx \
src/input/Registry.cxx src/input/Registry.hxx \
src/input/Open.cxx \
......@@ -2159,6 +2165,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
libutil.a
test_run_output_SOURCES = test/run_output.cxx \
test/ScopeIOThread.hxx \
test/NullMixerListener.hxx \
src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/output/Domain.cxx \
......@@ -2182,6 +2189,7 @@ test_read_mixer_LDADD = \
libsystem.a \
libutil.a
test_read_mixer_SOURCES = test/read_mixer.cxx \
test/NullMixerListener.hxx \
src/Log.cxx src/LogBackend.cxx \
src/mixer/MixerControl.cxx \
src/filter/FilterPlugin.cxx \
......
ver 0.20.21 (2018/08/17)
* database
- proxy: add "password" setting
- proxy: support tags "ArtistSort", "AlbumArtistSort", "AlbumSort"
- simple: allow .mpdignore comments only at start of line
* output
- httpd: remove broken DLNA support code
* playlist
- cue: support file type declaration "FLAC" (non-standard)
* URI schemes are case insensitive
* Android, Windows
- enable the "curl" storage plugin
ver 0.20.20 (2018/05/22)
* protocol
- fix "modified-since" filter regression
* output
- pulse: cork stream when paused due to "single" mode
* decoder
- dsdiff, dsf: support more MIME types
- dsdiff, dsf: allow 4 MB ID3 tags
- opus: support R128_ALBUM_GAIN tag
* Android, Windows
- enable the "proxy" database plugin
ver 0.20.19 (2018/04/26)
* protocol
- validate absolute seek time, reject negative values
* database
- proxy: fix "search already in progress" errors
- proxy: implement "list ... group"
* input
- mms: fix lockup bug and a crash bug
* decoder
- ffmpeg: fix av_register_all() deprecation warning (FFmpeg 4.0)
* player
- fix spurious "Not seekable" error when switching radio streams
* macOS: fix crash bug
ver 0.20.18 (2018/02/24)
* input
- curl: allow authentication methods other than "Basic"
* decoder
- flac: improve seeking precision
* fix gapless CUE song transitions
* Android, Windows
- enable the NFS storage plugin
ver 0.20.17 (2018/02/11)
* output
- alsa: fix crash bug with 8 channels
* mixer
- alsa: fix rounding error at volume 0
* fix real-time and idle scheduling with Musl
* Android
- fix compatibility with Android 4.0
ver 0.20.16 (2018/02/03)
* output
- pulse: fix crash during auto-detection
* database
- simple: fix search within mount points
- upnp: enable IPv6
* archive
- iso9660: libcdio 2.0 compatibility
* fix crash in debug build on Haiku and other operating systems
ver 0.20.15 (2018/01/05)
* queue: fix crash after seek failure
* resampler
......
......@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="13"
android:versionName="0.19.9">
android:versionCode="20"
android:versionName="0.20.21">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21"/>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Main"
......
......@@ -3,13 +3,14 @@
import os, os.path
import sys, subprocess
if len(sys.argv) < 3:
print("Usage: build.py SDK_PATH NDK_PATH [configure_args...]", file=sys.stderr)
if len(sys.argv) < 4:
print("Usage: build.py SDK_PATH NDK_PATH ABI [configure_args...]", file=sys.stderr)
sys.exit(1)
sdk_path = sys.argv[1]
ndk_path = sys.argv[2]
configure_args = sys.argv[3:]
android_abi = sys.argv[3]
configure_args = sys.argv[4:]
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
print("SDK not found in", ndk_path, file=sys.stderr)
......@@ -19,8 +20,36 @@ if not os.path.isdir(ndk_path):
print("NDK not found in", ndk_path, file=sys.stderr)
sys.exit(1)
android_abis = {
'armeabi-v7a': {
'arch': 'arm-linux-androideabi',
'ndk_arch': 'arm',
'toolchain_arch': 'arm-linux-androideabi',
'llvm_triple': 'armv7-none-linux-androideabi',
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
},
'arm64-v8a': {
'android_api_level': '21',
'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64',
'toolchain_arch': 'aarch64-linux-android',
'llvm_triple': 'aarch64-none-linux-android',
'cflags': '',
},
'x86': {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'toolchain_arch': 'x86',
'llvm_triple': 'i686-none-linux-android',
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
}
# select the NDK target
arch = 'arm-linux-androideabi'
abi_info = android_abis[android_abi]
arch = abi_info['arch']
# the path to the MPD sources
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
......@@ -44,16 +73,16 @@ class AndroidNdkToolchain:
self.src_path = src_path
self.build_path = build_path
self.ndk_arch = 'arm'
android_abi = 'armeabi-v7a'
ndk_platform = 'android-21'
ndk_arch = abi_info['ndk_arch']
android_api_level = '21'
ndk_platform = 'android-' + android_api_level
# select the NDK compiler
gcc_version = '4.9'
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
sysroot = os.path.join(ndk_path, 'sysroot')
target_root = os.path.join(ndk_platform_path, 'arch-' + self.ndk_arch)
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
install_prefix = os.path.join(arch_path, 'root')
......@@ -61,11 +90,13 @@ class AndroidNdkToolchain:
self.install_prefix = install_prefix
self.sysroot = sysroot
toolchain_path = os.path.join(ndk_path, 'toolchains', arch + '-' + gcc_version, 'prebuilt', build_arch)
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = 'armv7-none-linux-androideabi'
llvm_triple = abi_info['llvm_triple']
common_flags = '-march=armv7-a -mfloat-abi=softfp'
common_flags = '-Os -g'
common_flags += ' -fPIC'
common_flags += ' ' + abi_info['cflags']
toolchain_bin = os.path.join(toolchain_path, 'bin')
llvm_bin = os.path.join(llvm_path, 'bin')
......@@ -80,12 +111,12 @@ class AndroidNdkToolchain:
self.nm = os.path.join(toolchain_bin, arch + '-nm')
self.strip = os.path.join(toolchain_bin, arch + '-strip')
self.cflags = '-Os -g ' + common_flags
self.cxxflags = '-Os -g ' + common_flags
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = '--sysroot=' + sysroot + \
' -isystem ' + os.path.join(install_prefix, 'include') + \
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
' -D__ANDROID_API__=21'
' -D__ANDROID_API__=' + android_api_level
self.ldflags = '--sysroot=' + sysroot + \
' -L' + os.path.join(install_prefix, 'lib') + \
' -L' + os.path.join(target_root, 'usr', 'lib') + \
......@@ -93,22 +124,21 @@ class AndroidNdkToolchain:
' ' + common_flags
self.libs = ''
self.is_arm = self.ndk_arch == 'arm'
self.is_arm = ndk_arch == 'arm'
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
if self.is_armv7:
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
libstdcxx_flags = '-stdlib=libc++'
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
libstdcxx_ldflags = libstdcxx_flags + ' -static-libstdc++ -L' + libcxx_libs_path
if use_cxx:
self.libs += ' ' + libstdcxx_ldadd
self.cppflags += ' ' + libstdcxx_cppflags
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags
self.env = dict(os.environ)
......@@ -119,14 +149,16 @@ class AndroidNdkToolchain:
# a list of third-party libraries to be used by MPD on Android
from build.libs import *
thirdparty_libs = [
libmpdclient,
libogg,
libvorbis,
opus,
flac,
libid3tag,
libmad,
ffmpeg,
curl,
libexpat,
libnfs,
boost,
]
......
......@@ -49,7 +49,7 @@ public class Main extends Activity implements Runnable {
TextView tv = new TextView(this);
tv.setText("Failed to load the native MPD libary.\n" +
"Report this problem to us, and include the following information:\n" +
"ABI=" + Build.CPU_ABI + "\n" +
"SUPPORTED_ABIS=" + String.join(", ", Build.SUPPORTED_ABIS) + "\n" +
"PRODUCT=" + Build.PRODUCT + "\n" +
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
"error=" + Loader.error);
......
AC_PREREQ(2.60)
AC_INIT(mpd, 0.20.15, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.20.21, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=20
VERSION_REVISION=15
VERSION_REVISION=21
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
......@@ -16,7 +16,7 @@ AC_CONFIG_MACRO_DIR([m4])
AC_DEFINE(PROTOCOL_VERSION, "0.20.0", [The MPD protocol version])
GIT_COMMIT=`GIT_DIR="$srcdir/.git" git describe --dirty --always 2>/dev/null`
GIT_COMMIT=`cd "$srcdir" && git describe --dirty --always 2>/dev/null`
if test x$GIT_COMMIT != x; then
AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit])
fi
......@@ -186,6 +186,7 @@ AC_ARG_WITH([android-sdk],
[Directory for Android SDK]),
[], [with_android_sdk=no])
android_abi=""
if test x$host_is_android = xyes; then
if test x$with_android_sdk = xno; then
AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR])
......@@ -194,9 +195,15 @@ if test x$host_is_android = xyes; then
if ! test -x $with_android_sdk/tools/android; then
AC_MSG_ERROR([Android SDK not found in $with_android_sdk])
fi
AS_CASE([$host_cpu],
[i686], [android_abi="x86"],
[aarch64], [android_abi="arm64-v8a"],
[android_abi="armeabi-v7a"])
fi
AC_SUBST(ANDROID_SDK, [$with_android_sdk])
AC_SUBST(ANDROID_ABI, [$android_abi])
dnl ---------------------------------------------------------------------------
dnl Language Checks
......@@ -315,7 +322,7 @@ else
fi
default_enable_daemon=yes
if test x$host_is_android = xyes || test x$host_is_android = xyes; then
if test x$host_is_android = xyes || test x$host_is_windows = xyes; then
default_enable_daemon=no
fi
AC_ARG_ENABLE(daemon,
......@@ -402,7 +409,7 @@ AC_ARG_ENABLE(recorder-output,
AC_ARG_ENABLE(sidplay,
AS_HELP_STRING([--enable-sidplay],
[enable C64 SID support via libsidplay2]),,
[enable C64 SID support via libsidplayfp or libsidplay2]),,
enable_sidplay=auto)
AC_ARG_ENABLE(shout,
......@@ -454,7 +461,7 @@ dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries
dnl ---------------------------------------------------------------------------
AX_BOOST_BASE([1.46],, [AC_MSG_ERROR([Boost not found])])
AX_BOOST_BASE([1.54],, [AC_MSG_ERROR([Boost not found])])
AC_ARG_ENABLE(icu,
AS_HELP_STRING([--enable-icu],
......@@ -1005,7 +1012,7 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
[found_sidplay=no])
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
[libsidplay2 not found])
[libsidplay2 or libsidutils not found])
fi
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
......@@ -1017,10 +1024,11 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
fi
if test x$enable_sidplay = xyes; then
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplayfp or libsidplay2 support])
if test x$found_sidplayfp = xyes; then
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
else
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
fi
fi
......
......@@ -66,6 +66,26 @@
</para>
</section>
<section id="install_android">
<title>Installing on Android</title>
<para>
An experimental Android build is available on <ulink
url="https://play.google.com/store/apps/details?id=org.musicpd">Google
Play</ulink>. After installing and launching it, MPD will
scan the music in your <filename>Music</filename> directory
and you can control it as usual with a MPD client.
</para>
<para>
If you need to tweak the configuration, you can create a file
called <filename>mpd.conf</filename> on the data partition
(the directory which is returned by Android's <ulink
url="https://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()">getExternalStorageDirectory()</ulink>
API function).
</para>
</section>
<section id="install_source">
<title>Compiling from source</title>
......@@ -94,7 +114,7 @@ cd mpd-version</programlisting>
<listitem>
<para>
<ulink url="http://www.boost.org/">Boost 1.46</ulink>
<ulink url="http://www.boost.org/">Boost 1.54</ulink>
</para>
</listitem>
......@@ -248,6 +268,59 @@ apt-get install g++ \
script.
</para>
</section>
<section id="android_build">
<title>Compiling for Android</title>
<para>
MPD can be compiled as an Android app. It can be installed
easily with <link linkend="install_android">Google
Play</link>, but if you want to build it from source, follow
this section.
</para>
<para>
You need:
</para>
<itemizedlist>
<listitem>
<para>
Android SDK
</para>
</listitem>
<listitem>
<para>
<ulink
url="https://developer.android.com/ndk/downloads/index.html">Android
NDK</ulink>
</para>
</listitem>
</itemizedlist>
<para>
Just like with the native build, unpack the
<application>MPD</application> source tarball and change
into the directory. Then, instead of
<command>./configure</command>, type:
</para>
<programlisting>./android/build.py SDK_PATH NDK_PATH ABI
make android/build/mpd-debug.apk</programlisting>
<para>
<varname>SDK_PATH</varname> is the absolute path where you
installed the Android SDK; <varname>NDK_PATH</varname> is
the Android NDK installation path; <varname>ABI</varname> is
the Android ABI to be built, e.g. "armeabi-v7a".
</para>
<para>
This downloads various library sources, and then configures
and builds <application>MPD</application>.
</para>
</section>
</section>
<section id="systemd_socket">
......@@ -323,7 +396,9 @@ systemctl start mpd.socket</programlisting>
<application>MPD</application> as a user daemon (and not as a
system daemon), the configuration is read from
<filename>$XDG_CONFIG_HOME/mpd/mpd.conf</filename> (usually
<filename>~/.config/mpd/mpd.conf</filename>).
<filename>~/.config/mpd/mpd.conf</filename>). On Android,
<filename>mpd.conf</filename> will be loaded from the
top-level directory of the data partition.
</para>
<para>
......@@ -2012,13 +2087,6 @@ run</programlisting>
database.
</para>
<para>
Note that unless overridden by the below settings (e.g. by
setting them to a blank value), general curl configuration
from environment variables such as http_proxy or specified
in ~/.curlrc will be in effect.
</para>
<informaltable>
<tgroup cols="2">
<thead>
......@@ -2048,6 +2116,15 @@ run</programlisting>
</row>
<row>
<entry>
<varname>password</varname>
</entry>
<entry>
The password used to log in to the "master"
<application>MPD</application> instance.
</entry>
</row>
<row>
<entry>
<varname>keepalive</varname>
<parameter>yes|no</parameter>
</entry>
......
......@@ -5,16 +5,20 @@ from build.makeproject import MakeProject
class AutotoolsProject(MakeProject):
def __init__(self, url, md5, installed, configure_args=[],
autogen=False,
autoreconf=False,
cppflags='',
ldflags='',
libs='',
subdirs=None,
**kwargs):
MakeProject.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args
self.autogen = autogen
self.autoreconf = autoreconf
self.cppflags = cppflags
self.ldflags = ldflags
self.libs = libs
self.subdirs = subdirs
def configure(self, toolchain):
src = self.unpack(toolchain)
......@@ -26,6 +30,8 @@ class AutotoolsProject(MakeProject):
subprocess.check_call(['aclocal'], cwd=src)
subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
subprocess.check_call(['autoconf'], cwd=src)
if self.autoreconf:
subprocess.check_call(['autoreconf', '-vif'], cwd=src)
build = self.make_build_path(toolchain)
......@@ -51,4 +57,8 @@ class AutotoolsProject(MakeProject):
def build(self, toolchain):
build = self.configure(toolchain)
MakeProject.build(self, toolchain, build)
if self.subdirs is not None:
for subdir in self.subdirs:
MakeProject.build(self, toolchain, os.path.join(build, subdir))
else:
MakeProject.build(self, toolchain, build)
......@@ -21,6 +21,8 @@ class FfmpegProject(Project):
if toolchain.is_arm:
arch = 'arm'
elif toolchain.is_aarch64:
arch = 'aarch64'
else:
arch = 'x86'
......
import re
from os.path import abspath
from build.project import Project
from build.zlib import ZlibProject
from build.meson import MesonProject
from build.autotools import AutotoolsProject
from build.ffmpeg import FfmpegProject
from build.boost import BoostProject
libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.14.tar.xz',
'0a84e2791bfe3077cf22ee1784c805d5bb550803dffe56a39aa3690a38061372',
'lib/libmpdclient.a',
)
libogg = AutotoolsProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
......@@ -15,12 +24,17 @@ libogg = AutotoolsProject(
)
libvorbis = AutotoolsProject(
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz',
'28cb28097c07a735d6af56e598e1c90f',
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
'lib/libvorbis.a',
[
'--disable-shared', '--enable-static',
],
edits={
# this option is not understood by clang
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
}
)
opus = AutotoolsProject(
......@@ -29,6 +43,8 @@ opus = AutotoolsProject(
'lib/libopus.a',
[
'--disable-shared', '--enable-static',
'--disable-doc',
'--disable-extra-programs',
],
# suppress "visibility default" from opus_defines.h
......@@ -42,7 +58,9 @@ flac = AutotoolsProject(
[
'--disable-shared', '--enable-static',
'--disable-xmms-plugin', '--disable-cpplibs',
'--disable-doxygen-docs',
],
subdirs=['include', 'src/libFLAC'],
)
zlib = ZlibProject(
......@@ -83,8 +101,8 @@ libmad = AutotoolsProject(
)
liblame = AutotoolsProject(
'http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz',
'24346b4158e4af3bd9f2e194bb23eb473c75fb7377011523353196b19b9a23ff',
'http://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz',
'ddfe36cab873794038ae2c1210557ad34857a4b6bdc515785d1da9e175b1da1e',
'lib/libmp3lame.a',
[
'--disable-shared', '--enable-static',
......@@ -94,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-3.4.1.tar.xz',
'5a77278a63741efa74e26bf197b9bb09ac6381b9757391b922407210f0f991c0',
'http://ffmpeg.org/releases/ffmpeg-4.0.2.tar.xz',
'a95c0cc9eb990e94031d2183f2e6e444cc61c99f6f182d1575c433d62afb2f97',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
......@@ -118,7 +136,6 @@ ffmpeg = FfmpegProject(
'--disable-protocols',
'--disable-devices',
'--disable-filters',
'--disable-filters',
'--disable-v4l2_m2m',
'--disable-parser=bmp',
......@@ -136,7 +153,6 @@ ffmpeg = FfmpegProject(
'--disable-parser=mjpeg',
'--disable-parser=mlp',
'--disable-parser=mpeg4video',
'--disable-parser=mpegaudio',
'--disable-parser=mpegvideo',
'--disable-parser=opus',
'--disable-parser=vc1',
......@@ -188,16 +204,6 @@ ffmpeg = FfmpegProject(
# we don't need these decoders, because we have the dedicated
# libraries
'--disable-decoder=flac',
'--disable-decoder=mp1',
'--disable-decoder=mp1float',
'--disable-decoder=mp2',
'--disable-decoder=mp2float',
'--disable-decoder=mp3',
'--disable-decoder=mp3adu',
'--disable-decoder=mp3adufloat',
'--disable-decoder=mp3float',
'--disable-decoder=mp3on4',
'--disable-decoder=mp3on4float',
'--disable-decoder=opus',
'--disable-decoder=vorbis',
......@@ -311,7 +317,7 @@ ffmpeg = FfmpegProject(
'--disable-decoder=svq1',
'--disable-decoder=svq3',
'--disable-decoder=tiff',
'--disable-decoder=mottiertexseqvideo',
'--disable-decoder=tiertexseqvideo',
'--disable-decoder=truemotion1',
'--disable-decoder=truemotion2',
'--disable-decoder=truemotion2rt',
......@@ -335,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.57.0.tar.xz',
'f5f6fd3c72b7b8389969f4fb671ed8532fa9b5bb7a5cae7ca89bc1cea45c7878',
'http://curl.haxx.se/download/curl-7.61.0.tar.xz',
'ef6e55192d04713673b4409ccbcb4cb6cd723137d6e10ca45b0c593a454e1720',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
......@@ -354,10 +360,39 @@ curl = AutotoolsProject(
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
],
patches='src/lib/curl/patches',
)
libexpat = AutotoolsProject(
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
'lib/libexpat.a',
[
'--disable-shared', '--enable-static',
'--without-docbook',
],
)
libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
'lib/libnfs.a',
[
'--disable-shared', '--enable-static',
'--disable-debug',
# work around -Wtautological-compare
'--disable-werror',
'--disable-utils', '--disable-examples',
],
base='libnfs-libnfs-3.0.0',
autoreconf=True,
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.66.0/boost_1_66_0.tar.bz2',
'5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9',
'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
'7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
'include/boost/version.hpp',
)
import os.path, subprocess, sys
from build.project import Project
class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[],
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args
def _make_cross_file(self, toolchain):
if toolchain.is_windows:
system = 'windows'
else:
system = 'linux'
if toolchain.is_arm:
cpu_family = 'arm'
if toolchain.is_armv7:
cpu = 'armv7'
else:
cpu = 'armv6'
elif toolchain.is_aarch64:
cpu_family = 'aarch64'
cpu = 'arm64-v8a'
else:
cpu_family = 'x86'
if 'x86_64' in toolchain.arch:
cpu = 'x86_64'
else:
cpu = 'i686'
# TODO: support more CPUs
endian = 'little'
# TODO: write pkg-config wrapper
path = os.path.join(toolchain.build_path, 'meson.cross')
os.makedirs(toolchain.build_path, exist_ok=True)
with open(path, 'w') as f:
f.write("""
[binaries]
c = '%s'
cpp = '%s'
ar = '%s'
strip = '%s'
[properties]
root = '%s'
c_args = %s
c_link_args = %s
cpp_args = %s
cpp_link_args = %s
# Keep Meson from executing Android-x86 test binariees
needs_exe_wrapper = true
[host_machine]
system = '%s'
cpu_family = '%s'
cpu = '%s'
endian = '%s'
""" % (toolchain.cc, toolchain.cxx, toolchain.ar, toolchain.strip,
toolchain.install_prefix,
repr((toolchain.cppflags + ' ' + toolchain.cflags).split()),
repr(toolchain.ldflags.split()),
repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split()),
repr(toolchain.ldflags.split()),
system, cpu_family, cpu, endian))
return path
def configure(self, toolchain):
src = self.unpack(toolchain)
cross_file = self._make_cross_file(toolchain)
build = self.make_build_path(toolchain)
configure = [
'meson',
src, build,
'--prefix', toolchain.install_prefix,
# this is necessary because Meson uses Debian's build machine
# MultiArch path (e.g. "lib/x86_64-linux-gnu") for cross
# builds, which is obviously wrong
'--libdir', 'lib',
'--buildtype', 'plain',
'--default-library=static',
'--cross-file', cross_file,
] + self.configure_args
subprocess.check_call(configure, env=toolchain.env)
return build
def build(self, toolchain):
build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'],
cwd=build, env=toolchain.env)
......@@ -3,10 +3,12 @@ import re
from build.download import download_and_verify
from build.tar import untar
from build.quilt import push_all
class Project:
def __init__(self, url, md5, installed, name=None, version=None,
base=None,
patches=None,
edits=None,
use_cxx=False):
if base is None:
......@@ -18,7 +20,7 @@ class Project:
self.base = base
if name is None or version is None:
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base)
if name is None: name = m.group(1)
if version is None: version = m.group(2)
......@@ -29,6 +31,10 @@ class Project:
self.md5 = md5
self.installed = installed
if patches is not None:
srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
patches = os.path.join(srcdir, patches)
self.patches = patches
self.edits = edits
self.use_cxx = use_cxx
......@@ -51,6 +57,9 @@ class Project:
parent_path = toolchain.build_path
path = untar(self.download(toolchain), parent_path, self.base)
if self.patches is not None:
push_all(toolchain, path, self.patches)
if self.edits is not None:
for filename, function in self.edits.items():
with open(os.path.join(path, filename), 'r+t') as f:
......
import subprocess
def run_quilt(toolchain, cwd, patches_path, *args):
env = dict(toolchain.env)
env['QUILT_PATCHES'] = patches_path
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
def push_all(toolchain, src_path, patches_path):
run_quilt(toolchain, src_path, patches_path, 'push', '-a')
......@@ -19,9 +19,9 @@
#include "AudioFormat.hxx"
#include "util/StringBuffer.hxx"
#include "util/StringFormat.hxx"
#include <assert.h>
#include <stdio.h>
void
AudioFormat::ApplyMask(AudioFormat mask) noexcept
......@@ -44,21 +44,16 @@ AudioFormat::ApplyMask(AudioFormat mask) noexcept
StringBuffer<24>
ToString(const AudioFormat af) noexcept
{
StringBuffer<24> buffer;
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
af.sample_rate % 44100 == 0) {
/* use shortcuts such as "dsd64" which implies the
sample rate */
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
af.sample_rate * 8 / 44100,
af.channels);
return buffer;
return StringFormat<24>("dsd%u:%u",
af.sample_rate * 8 / 44100,
af.channels);
}
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
af.sample_rate, sample_format_to_string(af.format),
af.channels);
return buffer;
return StringFormat<24>("%u:%s:%u",
af.sample_rate, sample_format_to_string(af.format),
af.channels);
}
......@@ -23,7 +23,6 @@
#include "pcm/SampleFormat.hxx"
#include "Compiler.h"
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
......
......@@ -27,12 +27,17 @@
#include <assert.h>
static struct {
static struct IOThread {
Mutex mutex;
Cond cond;
EventLoop *loop;
Thread thread;
IOThread():thread(BIND_THIS_METHOD(Run)) {}
private:
void Run() noexcept;
} io;
void
......@@ -44,15 +49,15 @@ io_thread_run(void)
io.loop->Run();
}
static void
io_thread_func(gcc_unused void *arg)
inline void
IOThread::Run() noexcept
{
SetThreadName("io");
/* lock+unlock to synchronize with io_thread_start(), to be
sure that io.thread is set */
io.mutex.lock();
io.mutex.unlock();
mutex.lock();
mutex.unlock();
io_thread_run();
}
......@@ -73,7 +78,7 @@ io_thread_start()
assert(!io.thread.IsDefined());
const std::lock_guard<Mutex> protect(io.mutex);
io.thread.Start(io_thread_func, nullptr);
io.thread.Start();
}
void
......@@ -103,8 +108,12 @@ io_thread_get() noexcept
return *io.loop;
}
#ifndef NDEBUG
bool
io_thread_inside() noexcept
{
return io.thread.IsInside();
}
#endif
......@@ -20,6 +20,7 @@
#ifndef MPD_IO_THREAD_HXX
#define MPD_IO_THREAD_HXX
#include "check.h"
#include "Compiler.h"
class EventLoop;
......@@ -53,6 +54,8 @@ gcc_const
EventLoop &
io_thread_get() noexcept;
#ifndef NDEBUG
/**
* Is the current thread the I/O thread?
*/
......@@ -61,3 +64,5 @@ bool
io_thread_inside() noexcept;
#endif
#endif
......@@ -23,7 +23,7 @@
#include "fs/AllocatedPath.hxx"
#include "ls.hxx"
#include "util/UriUtil.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#ifdef ENABLE_DATABASE
#include "storage/StorageInterface.hxx"
......@@ -83,7 +83,7 @@ LocateUri(const char *uri, const Client *client
)
{
/* skip the obsolete "file://" prefix */
const char *path_utf8 = StringAfterPrefix(uri, "file://");
const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://");
if (path_utf8 != nullptr) {
if (!PathTraitsUTF8::IsAbsolute(path_utf8))
throw std::runtime_error("Malformed file:// URI");
......
......@@ -50,6 +50,7 @@
#include "unix/SignalHandlers.hxx"
#include "system/FatalError.hxx"
#include "thread/Slack.hxx"
#include "net/Init.hxx"
#include "lib/icu/Init.hxx"
#include "config/ConfigGlobal.hxx"
#include "config/Param.hxx"
......@@ -106,15 +107,6 @@
#include <locale.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#ifdef __BLOCKS__
#include <dispatch/dispatch.h>
#endif
#include <limits.h>
static constexpr size_t KILOBYTE = 1024;
......@@ -285,25 +277,6 @@ glue_state_file_init()
}
/**
* Windows-only initialization of the Winsock2 library.
*/
static void winsock_init(void)
{
#ifdef _WIN32
WSADATA sockinfo;
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if(retval != 0)
FormatFatalError("Attempt to open Winsock2 failed; error code %d",
retval);
if (LOBYTE(sockinfo.wVersion) != 2)
FatalError("We use Winsock2 but your version is either too new "
"or old; please install Winsock 2.x");
#endif
}
/**
* Initialize the decoder and player core, including the music pipe.
*/
static void
......@@ -451,7 +424,8 @@ try {
IcuInit();
winsock_init();
const ScopeNetInit net_init;
io_thread_init();
config_global_init();
......@@ -505,21 +479,8 @@ try {
daemonize_begin(options.daemon);
#endif
#ifdef __BLOCKS__
/* Runs the OS X native event loop in the main thread, and runs
the rest of mpd_main on a new thread. This lets CoreAudio receive
route change notifications (e.g. plugging or unplugging headphones).
All hardware output on OS X ultimately uses CoreAudio internally.
This must be run after forking; if dispatch is called before forking,
the child process will have a broken internal dispatch state. */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
exit(mpd_main_after_fork(config));
});
dispatch_main();
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
#else
return mpd_main_after_fork(config);
#endif
} catch (const std::exception &e) {
LogError(e);
return EXIT_FAILURE;
......@@ -702,10 +663,6 @@ try {
daemonize_finish();
#endif
#ifdef _WIN32
WSACleanup();
#endif
IcuFinish();
log_deinit();
......
......@@ -95,6 +95,7 @@ public:
*/
gcc_pure
const MusicChunk *Peek() const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return head;
}
......@@ -120,6 +121,7 @@ public:
*/
gcc_pure
unsigned GetSize() const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return size;
}
......
......@@ -24,6 +24,8 @@
#include "tag/Tag.hxx"
#include "util/ConstBuffer.hxx"
#include "util/StringAPI.hxx"
#include "util/StringCompare.hxx"
#include "util/StringView.hxx"
#include "util/ASCII.hxx"
#include "util/TimeParser.hxx"
#include "util/UriUtil.hxx"
......@@ -59,13 +61,13 @@ locate_parse_type(const char *str) noexcept
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
:tag(_tag),
value(AllocatedString<>::Duplicate(_value)),
value(_value),
fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
{
}
SongFilter::Item::Item(unsigned _tag, time_t _time)
:tag(_tag), value(nullptr), time(_time)
:tag(_tag), time(_time)
{
}
......@@ -274,3 +276,33 @@ SongFilter::GetBase() const noexcept
return nullptr;
}
SongFilter
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
{
const StringView prefix(_prefix);
SongFilter result;
for (const auto &i : items) {
if (i.GetTag() == LOCATE_TAG_BASE_TYPE) {
const char *s = StringAfterPrefix(i.GetValue(), prefix);
if (s != nullptr) {
if (*s == 0)
continue;
if (*s == '/') {
++s;
if (*s != 0)
result.items.emplace_back(LOCATE_TAG_BASE_TYPE, s);
continue;
}
}
}
result.items.emplace_back(i);
}
return result;
}
......@@ -21,9 +21,9 @@
#define MPD_SONG_FILTER_HXX
#include "lib/icu/Compare.hxx"
#include "util/AllocatedString.hxx"
#include "Compiler.h"
#include <string>
#include <list>
#include <stdint.h>
......@@ -49,7 +49,7 @@ public:
class Item {
uint8_t tag;
AllocatedString<> value;
std::string value;
/**
* This value is only set if case folding is enabled.
......@@ -66,11 +66,6 @@ public:
Item(unsigned tag, const char *value, bool fold_case=false);
Item(unsigned tag, time_t time);
Item(const Item &other) = delete;
Item(Item &&) = default;
Item &operator=(const Item &other) = delete;
unsigned GetTag() const {
return tag;
}
......@@ -157,6 +152,13 @@ public:
*/
gcc_pure
const char *GetBase() const noexcept;
/**
* Create a copy of the filter with the given prefix stripped
* from all #LOCATE_TAG_BASE_TYPE items. This is used to
* filter songs in mounted databases.
*/
SongFilter WithoutBasePrefix(const char *prefix) const noexcept;
};
/**
......
......@@ -28,6 +28,7 @@
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
#include "util/RuntimeError.hxx"
#include "util/NumberParser.hxx"
#include <string.h>
#include <stdlib.h>
......@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri)
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) {
tag.SetDuration(SignedSongTime::FromS(atof(value)));
tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {
......
......@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
visitor.VisitArchiveEntry(path + 1);
}
}
#if LIBCDIO_VERSION_NUM >= 20000
iso9660_filelist_free(entlist);
#else
_cdio_list_free (entlist, true);
#endif
}
static ArchiveFile *
......
......@@ -82,6 +82,7 @@ class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
DatabaseListener &listener;
const std::string host;
const std::string password;
const unsigned port;
const bool keepalive;
......@@ -170,6 +171,13 @@ static constexpr struct {
{ TAG_MUSICBRAINZ_RELEASETRACKID,
MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,11,0)
{ TAG_ARTIST_SORT, MPD_TAG_ARTIST_SORT },
{ TAG_ALBUM_ARTIST_SORT, MPD_TAG_ALBUM_ARTIST_SORT },
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
{ TAG_ALBUM_SORT, MPD_TAG_ALBUM_SORT },
#endif
{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
};
......@@ -325,12 +333,41 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
return true;
}
static bool
SendGroupMask(mpd_connection *connection, tag_mask_t mask)
{
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
if ((mask & (tag_mask_t(1) << i)) == 0)
continue;
const auto tag = Convert(TagType(i));
if (tag == MPD_TAG_COUNT)
throw std::runtime_error("Unsupported tag");
if (!mpd_search_add_group_tag(connection, tag))
return false;
}
return true;
#else
(void)connection;
(void)mask;
if (mask != 0)
throw std::runtime_error("Grouping requires libmpdclient 2.12");
return true;
#endif
}
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
const ConfigBlock &block)
:Database(proxy_db_plugin),
SocketMonitor(_loop), IdleMonitor(_loop),
listener(_listener),
host(block.GetBlockValue("host", "")),
password(block.GetBlockValue("password", "")),
port(block.GetBlockValue("port", 0u)),
keepalive(block.GetBlockValue("keepalive", false))
{
......@@ -374,6 +411,10 @@ ProxyDatabase::Connect()
try {
CheckError(connection);
if (!password.empty() &&
!mpd_run_password(connection, password.c_str()))
ThrowError(connection);
} catch (...) {
mpd_connection_free(connection);
connection = nullptr;
......@@ -682,7 +723,7 @@ static void
SearchSongs(struct mpd_connection *connection,
const DatabaseSelection &selection,
VisitSong visit_song)
{
try {
assert(selection.recursive);
assert(visit_song);
......@@ -709,6 +750,11 @@ SearchSongs(struct mpd_connection *connection,
if (!mpd_response_finish(connection))
ThrowError(connection);
} catch (...) {
if (connection != nullptr)
mpd_search_cancel(connection);
throw;
}
/**
......@@ -756,9 +802,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
void
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type,
gcc_unused tag_mask_t group_mask,
tag_mask_t group_mask,
VisitTag visit_tag) const
{
try {
// TODO: eliminate the const_cast
const_cast<ProxyDatabase *>(this)->EnsureConnected();
......@@ -767,32 +813,47 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
throw std::runtime_error("Unsupported tag");
if (!mpd_search_db_tags(connection, tag_type2) ||
!SendConstraints(connection, selection))
!SendConstraints(connection, selection) ||
!SendGroupMask(connection, group_mask))
ThrowError(connection);
// TODO: use group_mask
if (!mpd_search_commit(connection))
ThrowError(connection);
while (auto *pair = mpd_recv_pair_tag(connection, tag_type2)) {
TagBuilder builder;
while (auto *pair = mpd_recv_pair(connection)) {
AtScopeExit(this, pair) {
mpd_return_pair(connection, pair);
};
TagBuilder tag;
tag.AddItem(tag_type, pair->value);
const auto current_type = tag_name_parse_i(pair->name);
if (current_type == TAG_NUM_OF_ITEM_TYPES)
continue;
if (tag.IsEmpty())
if (current_type == tag_type && !builder.IsEmpty()) {
try {
visit_tag(builder.Commit());
} catch (...) {
mpd_response_finish(connection);
throw;
}
}
builder.AddItem(current_type, pair->value);
if (!builder.HasType(current_type))
/* if no tag item has been added, then the
given value was not acceptable
(e.g. empty); forcefully insert an empty
tag in this case, as the caller expects the
given tag type to be present */
tag.AddEmptyItem(tag_type);
builder.AddEmptyItem(current_type);
}
if (!builder.IsEmpty()) {
try {
visit_tag(tag.Commit());
visit_tag(builder.Commit());
} catch (...) {
mpd_response_finish(connection);
throw;
......@@ -801,6 +862,11 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
if (!mpd_response_finish(connection))
ThrowError(connection);
} catch (...) {
if (connection != nullptr)
mpd_search_cancel(connection);
throw;
}
DatabaseStats
......
......@@ -20,18 +20,12 @@
#include "config.h"
#include "Mount.hxx"
#include "PrefixedLightSong.hxx"
#include "SongFilter.hxx"
#include "db/Selection.hxx"
#include "db/LightDirectory.hxx"
#include "db/Interface.hxx"
#include "fs/Traits.hxx"
#ifdef _LIBCPP_VERSION
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
trait expression" with libc++ version 3900 (from Android NDK
r13b) */
#include "db/PlaylistInfo.hxx"
#endif
#include <string>
struct PrefixedLightDirectory : LightDirectory {
......@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db,
vp = std::bind(PrefixVisitPlaylist,
base, std::ref(visit_playlist), _1, _2);
SongFilter prefix_filter;
if (base != nullptr && filter != nullptr) {
/* if the SongFilter contains a LOCATE_TAG_BASE_TYPE
item, copy the SongFilter and drop the mount point
from the filter, because the mounted database
doesn't know its own location within MPD's VFS */
prefix_filter = filter->WithoutBasePrefix(base);
filter = &prefix_filter;
}
db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
}
......@@ -27,6 +27,7 @@
#include "util/UriUtil.hxx"
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringFormat.hxx"
#include <stdio.h>
......@@ -47,10 +48,6 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
unsigned &didreadp,
unsigned &totalp) const
{
// Create request
char ofbuf[100], cntbuf[100];
sprintf(ofbuf, "%u", offset);
sprintf(cntbuf, "%u", count);
// Some devices require an empty SortCriteria, else bad params
IXML_Document *request =
MakeActionHelper("Browse", m_serviceType.c_str(),
......@@ -58,8 +55,10 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
"BrowseFlag", "BrowseDirectChildren",
"Filter", "*",
"SortCriteria", "",
"StartingIndex", ofbuf,
"RequestedCount", cntbuf);
"StartingIndex",
StringFormat<32>("%u", offset).c_str(),
"RequestedCount",
StringFormat<32>("%u", count).c_str());
if (request == nullptr)
throw std::runtime_error("UpnpMakeAction() failed");
......@@ -112,15 +111,13 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
unsigned offset = 0, total = -1, count;
do {
char ofbuf[100];
sprintf(ofbuf, "%d", offset);
UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
"ContainerID", objectId,
"SearchCriteria", ss,
"Filter", "*",
"SortCriteria", "",
"StartingIndex", ofbuf,
"StartingIndex",
StringFormat<32>("%u", offset).c_str(),
"RequestedCount", "0")); // Setting a value here gets twonky into fits
if (!request)
throw std::runtime_error("UpnpMakeAction() failed");
......
......@@ -26,8 +26,8 @@
#include "ExcludeList.hxx"
#include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/io/TextFile.hxx"
#include "system/Error.hxx"
#include "input/TextInputStream.hxx"
#include "util/StringUtil.hxx"
#include "Log.hxx"
#include <stdexcept>
......@@ -35,35 +35,29 @@
#include <assert.h>
#include <string.h>
inline void
ExcludeList::ParseLine(char *line) noexcept
{
char *p = Strip(line);
if (*p != 0 && *p != '#')
patterns.emplace_front(p);
}
bool
ExcludeList::LoadFile(Path path_fs) noexcept
try {
ExcludeList::Load(InputStreamPtr is)
{
#ifdef HAVE_CLASS_GLOB
TextFile file(path_fs);
TextInputStream tis(std::move(is));
char *line;
while ((line = file.ReadLine()) != nullptr) {
char *p = strchr(line, '#');
if (p != nullptr)
*p = 0;
p = Strip(line);
if (*p != 0)
patterns.emplace_front(p);
}
while ((line = tis.ReadLine()) != nullptr)
ParseLine(line);
#else
/* not implemented */
(void)path_fs;
#endif
return true;
} catch (const std::system_error &e) {
if (!IsFileNotFound(e))
LogError(e);
return false;
} catch (const std::exception &e) {
LogError(e);
return false;
}
bool
......
......@@ -28,6 +28,7 @@
#include "check.h"
#include "Compiler.h"
#include "fs/Glob.hxx"
#include "input/Ptr.hxx"
#ifdef HAVE_CLASS_GLOB
#include <forward_list>
......@@ -62,13 +63,16 @@ public:
/**
* Loads and parses a .mpdignore file.
*/
bool LoadFile(Path path_fs) noexcept;
bool Load(InputStreamPtr is);
/**
* Checks whether one of the patterns in the .mpdignore file matches
* the specified file name.
*/
bool Check(Path name_fs) const noexcept;
private:
void ParseLine(char *line) noexcept;
};
......
......@@ -49,6 +49,10 @@ struct UpdateQueueItem {
bool IsDefined() const {
return id != 0;
}
void Clear() {
id = 0;
}
};
class UpdateQueue {
......
......@@ -29,6 +29,7 @@
#include "Idle.hxx"
#include "Log.hxx"
#include "thread/Thread.hxx"
#include "thread/Name.hxx"
#include "thread/Util.hxx"
#ifndef NDEBUG
......@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
:DeferredMonitor(_loop),
db(_db), storage(_storage),
listener(_listener),
update_thread(BIND_THIS_METHOD(Task)),
update_task_id(0),
walk(nullptr)
{
......@@ -112,6 +114,8 @@ UpdateService::Task()
{
assert(walk != nullptr);
SetThreadName("update");
if (!next.path_utf8.empty())
FormatDebug(update_domain, "starting: %s",
next.path_utf8.c_str());
......@@ -141,13 +145,6 @@ UpdateService::Task()
}
void
UpdateService::Task(void *ctx)
{
UpdateService &service = *(UpdateService *)ctx;
return service.Task();
}
void
UpdateService::StartThread(UpdateQueueItem &&i)
{
assert(GetEventLoop().IsInsideOrNull());
......@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
next = std::move(i);
walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
update_thread.Start(Task, this);
update_thread.Start();
FormatDebug(update_domain,
"spawned thread for update job id %i", next.id);
......@@ -258,7 +255,7 @@ UpdateService::RunDeferred()
delete walk;
walk = nullptr;
next = UpdateQueueItem();
next.Clear();
idle_add(IDLE_UPDATE);
......
......@@ -98,7 +98,6 @@ private:
/* the update thread */
void Task();
static void Task(void *ctx);
void StartThread(UpdateQueueItem &&i);
......
......@@ -36,6 +36,9 @@
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
#include "storage/FileInfo.hxx"
#include "input/InputStream.hxx"
#include "input/Error.hxx"
#include "thread/Cond.hxx"
#include "util/Alloc.hxx"
#include "util/StringCompare.hxx"
#include "util/UriUtil.hxx"
......@@ -345,11 +348,16 @@ UpdateWalk::UpdateDirectory(Directory &directory,
ExcludeList child_exclude_list(exclude_list);
{
const auto exclude_path_fs =
storage.MapChildFS(directory.GetPath(), ".mpdignore");
if (!exclude_path_fs.IsNull())
child_exclude_list.LoadFile(exclude_path_fs);
try {
Mutex mutex;
Cond cond;
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
".mpdignore").c_str(),
mutex, cond);
child_exclude_list.Load(std::move(is));
} catch (...) {
if (!IsFileNotFound(std::current_exception()))
LogError(std::current_exception());
}
if (!child_exclude_list.IsEmpty())
......
......@@ -300,6 +300,7 @@ DecoderBridge::CommandFinished()
initial_seek_running = false;
timestamp = dc.start_time.ToDoubleS();
absolute_frame = dc.start_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
return;
}
......@@ -319,6 +320,7 @@ DecoderBridge::CommandFinished()
convert->Reset();
timestamp = dc.seek_time.ToDoubleS();
absolute_frame = dc.seek_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
}
dc.command = DecoderCommand::NONE;
......@@ -427,6 +429,7 @@ DecoderBridge::SubmitTimestamp(double t)
assert(t >= 0);
timestamp = t;
absolute_frame = uint64_t(t * dc.in_audio_format.sample_rate);
}
DecoderCommand
......@@ -464,6 +467,29 @@ DecoderBridge::SubmitData(InputStream *is,
return cmd;
}
cmd = DecoderCommand::NONE;
const size_t frame_size = dc.in_audio_format.GetFrameSize();
size_t data_frames = length / frame_size;
if (dc.end_time.IsPositive()) {
/* enforce the given end time */
const uint64_t end_frame =
dc.end_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
if (absolute_frame >= end_frame)
return DecoderCommand::STOP;
const uint64_t remaining_frames = end_frame - absolute_frame;
if (data_frames >= remaining_frames) {
/* past the end of the range: truncate this
data submission and stop the decoder */
data_frames = remaining_frames;
length = data_frames * frame_size;
cmd = DecoderCommand::STOP;
}
}
if (convert != nullptr) {
assert(dc.in_audio_format != dc.out_audio_format);
......@@ -521,15 +547,11 @@ DecoderBridge::SubmitData(InputStream *is,
timestamp += (double)nbytes /
dc.out_audio_format.GetTimeToSize();
if (dc.end_time.IsPositive() &&
timestamp >= dc.end_time.ToDoubleS())
/* the end of this range has been reached:
stop decoding */
return DecoderCommand::STOP;
}
return DecoderCommand::NONE;
absolute_frame += data_frames;
return cmd;
}
DecoderCommand
......
......@@ -50,6 +50,11 @@ public:
double timestamp = 0;
/**
* The time stamp of the next data chunk, in PCM frames.
*/
uint64_t absolute_frame = 0;
/**
* Is the initial seek (to the start position of the sub-song)
* pending, or has it been performed already?
*/
......
......@@ -30,7 +30,8 @@
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
const AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config)
:mutex(_mutex), client_cond(_client_cond),
:thread(BIND_THIS_METHOD(RunThread)),
mutex(_mutex), client_cond(_client_cond),
configured_audio_format(_configured_audio_format),
replay_gain_config(_replay_gain_config) {}
......
......@@ -308,9 +308,14 @@ struct DecoderControl {
bool IsCurrentSong(const DetachedSong &_song) const noexcept;
gcc_pure
bool LockIsCurrentSong(const DetachedSong &_song) const noexcept {
bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
return seekable && IsCurrentSong(_song);
}
gcc_pure
bool LockIsSeeakbleCurrentSong(const DetachedSong &_song) const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return IsCurrentSong(_song);
return IsSeekableCurrentSong(_song);
}
private:
......@@ -415,6 +420,9 @@ public:
* mixramp_start/mixramp_end.
*/
void CycleMixRamp();
private:
void RunThread();
};
#endif
......@@ -513,30 +513,28 @@ try {
dc.client_cond.signal();
}
static void
decoder_task(void *arg)
void
DecoderControl::RunThread()
{
DecoderControl &dc = *(DecoderControl *)arg;
SetThreadName("decoder");
const std::lock_guard<Mutex> protect(dc.mutex);
const std::lock_guard<Mutex> protect(mutex);
do {
assert(dc.state == DecoderState::STOP ||
dc.state == DecoderState::ERROR);
assert(state == DecoderState::STOP ||
state == DecoderState::ERROR);
switch (dc.command) {
switch (command) {
case DecoderCommand::START:
dc.CycleMixRamp();
dc.replay_gain_prev_db = dc.replay_gain_db;
dc.replay_gain_db = 0;
CycleMixRamp();
replay_gain_prev_db = replay_gain_db;
replay_gain_db = 0;
decoder_run(dc);
decoder_run(*this);
if (dc.state == DecoderState::ERROR) {
if (state == DecoderState::ERROR) {
try {
std::rethrow_exception(dc.error);
std::rethrow_exception(error);
} catch (const std::exception &e) {
LogError(e);
} catch (...) {
......@@ -552,20 +550,20 @@ decoder_task(void *arg)
/* we need to clear the pipe here; usually the
PlayerThread is responsible, but it is not
aware that the decoder has finished */
dc.pipe->Clear(*dc.buffer);
pipe->Clear(*buffer);
decoder_run(dc);
decoder_run(*this);
break;
case DecoderCommand::STOP:
dc.CommandFinishedLocked();
CommandFinishedLocked();
break;
case DecoderCommand::NONE:
dc.Wait();
Wait();
break;
}
} while (dc.command != DecoderCommand::NONE || !dc.quit);
} while (command != DecoderCommand::NONE || !quit);
}
void
......@@ -574,5 +572,5 @@ decoder_thread_start(DecoderControl &dc)
assert(!dc.thread.IsDefined());
dc.quit = false;
dc.thread.Start(decoder_task, &dc);
dc.thread.Start();
}
......@@ -128,7 +128,7 @@ dsdlib_tag_id3(InputStream &is,
return;
const auto count64 = size - tagoffset;
if (count64 < 10 || count64 > 1024 * 1024)
if (count64 < 10 || count64 > 4 * 1024 * 1024)
return;
if (!dsdlib_skip_to(nullptr, is, tagoffset))
......
......@@ -484,6 +484,8 @@ static const char *const dsdiff_suffixes[] = {
static const char *const dsdiff_mime_types[] = {
"application/x-dff",
"audio/x-dff",
"audio/x-dsd",
nullptr
};
......
......@@ -358,6 +358,8 @@ static const char *const dsf_suffixes[] = {
static const char *const dsf_mime_types[] = {
"application/x-dsf",
"audio/x-dsf",
"audio/x-dsd",
nullptr
};
......
......@@ -24,7 +24,6 @@
#include "config.h"
#include "FlacCommon.hxx"
#include "FlacMetadata.hxx"
#include "util/ConstBuffer.hxx"
#include "Log.hxx"
#include <stdexcept>
......@@ -143,25 +142,10 @@ FlacDecoder::OnWrite(const FLAC__Frame &frame,
if (!initialized && !OnFirstFrame(frame.header))
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
const auto data = pcm_import.Import(buf, frame.header.blocksize);
chunk = pcm_import.Import(buf, frame.header.blocksize);
unsigned bit_rate = nbytes * 8 * frame.header.sample_rate /
kbit_rate = nbytes * 8 * frame.header.sample_rate /
(1000 * frame.header.blocksize);
auto cmd = GetClient()->SubmitData(GetInputStream(),
data.data, data.size,
bit_rate);
switch (cmd) {
case DecoderCommand::NONE:
case DecoderCommand::START:
break;
case DecoderCommand::STOP:
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
case DecoderCommand::SEEK:
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
......@@ -27,6 +27,7 @@
#include "FlacInput.hxx"
#include "FlacPcm.hxx"
#include "../DecoderAPI.hxx"
#include "util/ConstBuffer.hxx"
#include <FLAC/stream_decoder.h>
......@@ -41,6 +42,12 @@ struct FlacDecoder : public FlacInput {
*/
bool unsupported = false;
/**
* The kbit_rate parameter for the next
* DecoderBridge::SubmitData() call.
*/
uint16_t kbit_rate;
FlacPcmImport pcm_import;
/**
......@@ -51,6 +58,13 @@ struct FlacDecoder : public FlacInput {
Tag tag;
/**
* Decoded PCM data obtained by our libFLAC write callback.
* If this is non-empty, then DecoderBridge::SubmitData()
* should be called.
*/
ConstBuffer<void> chunk = nullptr;
FlacDecoder(DecoderClient &_client, InputStream &_input_stream)
:FlacInput(_input_stream, &_client) {}
......
......@@ -139,19 +139,40 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd)
return data->initialized;
}
static DecoderCommand
FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept
{
if (d.tag.IsEmpty() && d.chunk.IsEmpty())
return client.GetCommand();
if (!d.tag.IsEmpty()) {
auto cmd = client.SubmitTag(d.GetInputStream(),
std::move(d.tag));
d.tag.Clear();
if (cmd != DecoderCommand::NONE)
return cmd;
}
if (!d.chunk.IsEmpty()) {
auto cmd = client.SubmitData(d.GetInputStream(),
d.chunk.data,
d.chunk.size,
d.kbit_rate);
d.chunk = nullptr;
if (cmd != DecoderCommand::NONE)
return cmd;
}
return DecoderCommand::NONE;
}
static void
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
{
DecoderClient &client = *data->GetClient();
while (true) {
DecoderCommand cmd;
if (!data->tag.IsEmpty()) {
cmd = client.SubmitTag(data->GetInputStream(),
std::move(data->tag));
data->tag.Clear();
} else
cmd = client.GetCommand();
DecoderCommand cmd = FlacSubmitToClient(client, *data);
if (cmd == DecoderCommand::SEEK) {
FLAC__uint64 seek_sample = client.GetSeekFrame();
......@@ -160,6 +181,11 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
client.CommandFinished();
} else
client.SeekError();
/* FLAC__stream_decoder_seek_absolute()
decodes one frame and may have provided
data to be submitted to the client */
continue;
} else if (cmd == DecoderCommand::STOP)
break;
......
......@@ -28,7 +28,7 @@
#include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx"
#include "util/ScopeExit.hxx"
#include "util/FormatString.hxx"
#include "util/StringFormat.hxx"
#include "util/UriUtil.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
......@@ -38,7 +38,6 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define SUBTUNE_PREFIX "tune_"
......@@ -191,20 +190,17 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
tag_handler_invoke_duration(handler, handler_ctx,
SongTime::FromMS(info.play_length));
if (track_count > 1) {
char track[16];
sprintf(track, "%u", song_num + 1);
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
}
if (track_count > 1)
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK,
StringFormat<16>("%u", song_num + 1));
if (info.song != nullptr) {
if (track_count > 1) {
/* start numbering subtunes from 1 */
char tag_title[1024];
snprintf(tag_title, sizeof(tag_title),
"%s (%u/%d)",
info.song, song_num + 1,
track_count);
const auto tag_title =
StringFormat<1024>("%s (%u/%d)",
info.song, song_num + 1,
track_count);
tag_handler_invoke_tag(handler, handler_ctx,
TAG_TITLE, tag_title);
} else
......@@ -297,9 +293,9 @@ gme_container_scan(Path path_fs)
ScanMusicEmu(emu, i,
add_tag_handler, &tag_builder);
char track_name[64];
snprintf(track_name, sizeof(track_name),
SUBTUNE_PREFIX "%03u.%s", i+1, subtune_suffix);
const auto track_name =
StringFormat<64>(SUBTUNE_PREFIX "%03u.%s", i+1,
subtune_suffix);
tail = list.emplace_after(tail, track_name,
tag_builder.Commit());
}
......
......@@ -53,6 +53,14 @@ ScanOneOpusTag(const char *name, const char *value,
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->track.gain = double(l) / 256.;
} else if (rgi != nullptr && strcmp(name, "R128_ALBUM_GAIN") == 0) {
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
dB */
char *endptr;
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->album.gain = double(l) / 256.;
}
tag_handler_invoke_pair(handler, ctx, name, value);
......
......@@ -26,7 +26,7 @@
#include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx"
#include "util/Macros.hxx"
#include "util/FormatString.hxx"
#include "util/StringFormat.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "Log.hxx"
......@@ -413,10 +413,9 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
title = "";
if (n_tracks > 1) {
char tag_title[1024];
snprintf(tag_title, sizeof(tag_title),
"%s (%u/%u)",
title, track, n_tracks);
const auto tag_title =
StringFormat<1024>("%s (%u/%u)",
title, track, n_tracks);
tag_handler_invoke_tag(handler, handler_ctx,
TAG_TITLE, tag_title);
} else
......@@ -435,9 +434,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
date);
/* track */
char track_buffer[16];
sprintf(track_buffer, "%d", track);
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track_buffer);
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK,
StringFormat<16>("%u", track));
}
static bool
......
......@@ -52,7 +52,7 @@ class OpusEncoder final : public OggEncoder {
ogg_int64_t packetno = 0;
ogg_int64_t granulepos;
ogg_int64_t granulepos = 0;
public:
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);
......
......@@ -27,7 +27,7 @@
#include <algorithm>
EventLoop::EventLoop()
:SocketMonitor(*this)
:SocketMonitor(*this), quit(false)
{
SocketMonitor::Open(wake_fd.Get());
SocketMonitor::Schedule(SocketMonitor::READ);
......@@ -46,7 +46,9 @@ EventLoop::~EventLoop()
void
EventLoop::Break()
{
quit = true;
if (quit.exchange(true))
return;
wake_fd.Write();
}
......
......@@ -30,6 +30,7 @@
#include "SocketMonitor.hxx"
#include <chrono>
#include <atomic>
#include <list>
#include <set>
......@@ -82,7 +83,7 @@ class EventLoop final : SocketMonitor
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
bool quit = false;
std::atomic_bool quit;
/**
* True when the object has been modified and another check is
......
......@@ -53,10 +53,16 @@ public:
void Set(const AudioFormat &_out_audio_format);
void Reset() override {
state.Reset();
if (IsActive())
state.Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
private:
bool IsActive() const noexcept {
return out_audio_format != in_audio_format;
}
};
class PreparedConvertFilter final : public PreparedFilter {
......@@ -80,7 +86,7 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format)
/* no change */
return;
if (out_audio_format != in_audio_format) {
if (IsActive()) {
out_audio_format = in_audio_format;
state.Close();
}
......@@ -111,7 +117,7 @@ ConvertFilter::~ConvertFilter()
{
assert(in_audio_format.IsValid());
if (out_audio_format != in_audio_format)
if (IsActive())
state.Close();
}
......@@ -120,11 +126,10 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src)
{
assert(in_audio_format.IsValid());
if (out_audio_format == in_audio_format)
return IsActive()
? state.Convert(src)
/* optimized special case: no-op */
return src;
return state.Convert(src);
: src;
}
const FilterPlugin convert_filter_plugin = {
......
......@@ -20,6 +20,7 @@
#include "config.h"
#include "FileOutputStream.hxx"
#include "system/Error.hxx"
#include "util/StringFormat.hxx"
FileOutputStream::FileOutputStream(Path _path, Mode _mode)
:path(_path), mode(_mode)
......@@ -212,10 +213,9 @@ FileOutputStream::Commit()
unlink(GetPath().c_str());
/* hard-link the temporary file to the final path */
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d",
fd.Get());
if (linkat(AT_FDCWD, fd_path, AT_FDCWD, path.c_str(),
if (linkat(AT_FDCWD,
StringFormat<64>("/proc/self/fd/%d", fd.Get()),
AT_FDCWD, path.c_str(),
AT_SYMLINK_FOLLOW) < 0)
throw FormatErrno("Failed to commit %s",
path.c_str());
......
......@@ -19,7 +19,6 @@
#include "config.h"
#include "AsyncInputStream.hxx"
#include "Domain.hxx"
#include "tag/Tag.hxx"
#include "thread/Cond.hxx"
#include "IOThread.hxx"
......
/*
* 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 "config.h"
#include "Error.hxx"
#include "system/Error.hxx"
#ifdef ENABLE_CURL
#include "lib/curl/Error.hxx"
#endif
#ifdef ENABLE_NFS
#include "lib/nfs/Error.hxx"
#include <nfsc/libnfs-raw-nfs.h>
#endif
bool
IsFileNotFound(std::exception_ptr ep)
{
try {
std::rethrow_exception(ep);
} catch (const std::system_error &e) {
return IsFileNotFound(e);
#ifdef ENABLE_CURL
} catch (const HttpStatusError &e) {
return e.GetStatus() == 404;
#endif
#ifdef ENABLE_NFS
} catch (const NfsClientError &e) {
return e.GetCode() == NFS3ERR_NOENT;
#endif
} catch (...) {
}
return false;
}
/*
* Copyright 2003-2017 The Music Player Daemon Project
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
......@@ -17,8 +17,21 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Domain.hxx"
#include "util/Domain.hxx"
#ifndef INPUT_ERROR_HXX
#define INPUT_ERROR_HXX
const Domain input_domain("input");
#include "check.h"
#include "Compiler.h"
#include <exception>
/**
* Was this exception thrown because the requested file does not
* exist? This function attempts to recognize exceptions thrown by
* various input plugins.
*/
gcc_pure
bool
IsFileNotFound(std::exception_ptr e);
#endif
......@@ -20,7 +20,7 @@
#include "config.h"
#include "InputStream.hxx"
#include "thread/Cond.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#include <stdexcept>
......@@ -77,8 +77,8 @@ gcc_pure
static bool
ExpensiveSeeking(const char *uri) noexcept
{
return StringStartsWith(uri, "http://") ||
StringStartsWith(uri, "https://");
return StringStartsWithCaseASCII(uri, "http://") ||
StringStartsWithCaseASCII(uri, "https://");
}
bool
......
......@@ -29,4 +29,10 @@
*/
typedef uint64_t offset_type;
/**
* To format an offset_type with printf(). To use this, include
* <cinttypes>.
*/
#define PRIoffset PRIu64
#endif
......@@ -22,7 +22,6 @@
#include "Registry.hxx"
#include "InputPlugin.hxx"
#include "LocalOpen.hxx"
#include "Domain.hxx"
#include "plugins/RewindInputPlugin.hxx"
#include "fs/Traits.hxx"
#include "fs/AllocatedPath.hxx"
......
......@@ -26,8 +26,12 @@
#include <assert.h>
#include <string.h>
ThreadInputStream::~ThreadInputStream()
void
ThreadInputStream::Stop() noexcept
{
if (!thread.IsDefined())
return;
{
const std::lock_guard<Mutex> lock(mutex);
close = true;
......@@ -42,6 +46,7 @@ ThreadInputStream::~ThreadInputStream()
buffer->Clear();
HugeFree(buffer->Write().data, buffer_size);
delete buffer;
buffer = nullptr;
}
}
......@@ -54,10 +59,10 @@ ThreadInputStream::Start()
assert(p != nullptr);
buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
thread.Start(ThreadFunc, this);
thread.Start();
}
inline void
void
ThreadInputStream::ThreadFunc()
{
FormatThreadName("input:%s", plugin);
......@@ -68,7 +73,7 @@ ThreadInputStream::ThreadFunc()
Open();
} catch (...) {
postponed_exception = std::current_exception();
cond.broadcast();
SetReady();
return;
}
......@@ -108,13 +113,6 @@ ThreadInputStream::ThreadFunc()
}
void
ThreadInputStream::ThreadFunc(void *ctx)
{
ThreadInputStream &tis = *(ThreadInputStream *)ctx;
tis.ThreadFunc();
}
void
ThreadInputStream::Check()
{
assert(!thread.IsInside());
......
......@@ -27,6 +27,7 @@
#include <exception>
#include <assert.h>
#include <stdint.h>
template<typename T> class CircularBuffer;
......@@ -39,6 +40,11 @@ template<typename T> class CircularBuffer;
* manages the thread and the buffer.
*
* This works only for "streams": unknown length, no seeking, no tags.
*
* The implementation must call Stop() before its destruction
* completes. This cannot be done in ~ThreadInputStream() because at
* this point, the class has been morphed back to #ThreadInputStream
* and the still-running thread will crash due to pure method call.
*/
class ThreadInputStream : public InputStream {
const char *const plugin;
......@@ -73,9 +79,16 @@ public:
size_t _buffer_size)
:InputStream(_uri, _mutex, _cond),
plugin(_plugin),
thread(BIND_THIS_METHOD(ThreadFunc)),
buffer_size(_buffer_size) {}
virtual ~ThreadInputStream();
#ifndef NDEBUG
~ThreadInputStream() override {
/* Stop() must have been called already */
assert(!thread.IsDefined());
assert(buffer == nullptr);
}
#endif
/**
* Initialize the object and start the thread.
......@@ -89,6 +102,12 @@ public:
size_t Read(void *ptr, size_t size) override final;
protected:
/**
* Stop the thread and free the buffer. This must be called
* before destruction of this object completes.
*/
void Stop() noexcept;
void SetMimeType(const char *_mime) {
assert(thread.IsInside());
......@@ -138,7 +157,6 @@ protected:
private:
void ThreadFunc();
static void ThreadFunc(void *ctx);
};
#endif
......@@ -33,7 +33,7 @@
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include "util/ReusableArray.hxx"
#include "util/ASCII.hxx"
#include "Log.hxx"
#include "event/MultiSocketMonitor.hxx"
#include "event/DeferredMonitor.hxx"
......@@ -147,7 +147,7 @@ private:
inline InputStream *
AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
{
const char *device = StringAfterPrefix(uri, "alsa://");
const char *device = StringAfterPrefixCaseASCII(uri, "alsa://");
if (device == nullptr)
return nullptr;
......@@ -270,6 +270,12 @@ AlsaInputStream::Recover(int err)
/* this is no error, so just keep running */
err = 0;
break;
default:
/* this default case is just here to work around
-Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
1.1.6) */
break;
}
......
......@@ -26,7 +26,7 @@
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/StringUtil.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
......@@ -128,7 +128,7 @@ struct cdio_uri {
static bool
parse_cdio_uri(struct cdio_uri *dest, const char *src)
{
if (!StringStartsWith(src, "cdda://"))
if (!StringStartsWithCaseASCII(src, "cdda://"))
return false;
src += 7;
......
......@@ -19,6 +19,7 @@
#include "config.h"
#include "CurlInputPlugin.hxx"
#include "lib/curl/Error.hxx"
#include "lib/curl/Easy.hxx"
#include "lib/curl/Global.hxx"
#include "lib/curl/Request.hxx"
......@@ -34,12 +35,15 @@
#include "IOThread.hxx"
#include "util/ASCII.hxx"
#include "util/StringUtil.hxx"
#include "util/StringFormat.hxx"
#include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include "PluginUnavailable.hxx"
#include <cinttypes>
#include <assert.h>
#include <string.h>
......@@ -185,7 +189,9 @@ CurlInputStream::OnHeaders(unsigned status,
assert(!postponed_exception);
if (status < 200 || status >= 300)
throw FormatRuntimeError("got HTTP status %ld", status);
throw HttpStatusError(status,
StringFormat<40>("got HTTP status %u",
status).c_str());
const std::lock_guard<Mutex> protect(mutex);
......@@ -371,13 +377,10 @@ CurlInputStream::InitEasy()
if (proxy_port > 0)
request->SetOption(CURLOPT_PROXYPORT, (long)proxy_port);
if (proxy_user != nullptr && proxy_password != nullptr) {
char proxy_auth_str[1024];
snprintf(proxy_auth_str, sizeof(proxy_auth_str),
"%s:%s",
proxy_user, proxy_password);
request->SetOption(CURLOPT_PROXYUSERPWD, proxy_auth_str);
}
if (proxy_user != nullptr && proxy_password != nullptr)
request->SetOption(CURLOPT_PROXYUSERPWD,
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);
......@@ -414,16 +417,10 @@ CurlInputStream::SeekInternal(offset_type new_offset)
/* send the "Range" header */
if (offset > 0) {
char range[32];
#ifdef _WIN32
// TODO: what can we use on Windows to format 64 bit?
sprintf(range, "%lu-", (long)offset);
#else
sprintf(range, "%llu-", (unsigned long long)offset);
#endif
request->SetOption(CURLOPT_RANGE, range);
}
if (offset > 0)
request->SetOption(CURLOPT_RANGE,
StringFormat<40>("%" PRIoffset "-",
offset).c_str());
StartRequest();
}
......@@ -461,8 +458,8 @@ CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond)
static InputStream *
input_curl_open(const char *url, Mutex &mutex, Cond &cond)
{
if (strncmp(url, "http://", 7) != 0 &&
strncmp(url, "https://", 8) != 0)
if (!StringStartsWithCaseASCII(url, "http://") &&
!StringStartsWithCaseASCII(url, "https://"))
return nullptr;
return CurlInputStream::Open(url, mutex, cond);
......
......@@ -28,7 +28,7 @@
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "PluginUnavailable.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
extern "C" {
#include <libavformat/avio.h>
......@@ -85,12 +85,12 @@ static InputStream *
input_ffmpeg_open(const char *uri,
Mutex &mutex, Cond &cond)
{
if (!StringStartsWith(uri, "gopher://") &&
!StringStartsWith(uri, "rtp://") &&
!StringStartsWith(uri, "rtsp://") &&
!StringStartsWith(uri, "rtmp://") &&
!StringStartsWith(uri, "rtmpt://") &&
!StringStartsWith(uri, "rtmps://"))
if (!StringStartsWithCaseASCII(uri, "gopher://") &&
!StringStartsWithCaseASCII(uri, "rtp://") &&
!StringStartsWithCaseASCII(uri, "rtsp://") &&
!StringStartsWithCaseASCII(uri, "rtmp://") &&
!StringStartsWithCaseASCII(uri, "rtmpt://") &&
!StringStartsWithCaseASCII(uri, "rtmps://"))
return nullptr;
AVIOContext *h;
......
......@@ -22,7 +22,7 @@
#include "input/ThreadInputStream.hxx"
#include "input/InputPlugin.hxx"
#include "system/Error.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#include <libmms/mmsx.h>
......@@ -39,6 +39,10 @@ public:
MMS_BUFFER_SIZE) {
}
~MmsInputStream() noexcept override {
Stop();
}
protected:
virtual void Open() override;
virtual size_t ThreadRead(void *ptr, size_t size) override;
......@@ -70,10 +74,10 @@ static InputStream *
input_mms_open(const char *url,
Mutex &mutex, Cond &cond)
{
if (!StringStartsWith(url, "mms://") &&
!StringStartsWith(url, "mmsh://") &&
!StringStartsWith(url, "mmst://") &&
!StringStartsWith(url, "mmsu://"))
if (!StringStartsWithCaseASCII(url, "mms://") &&
!StringStartsWithCaseASCII(url, "mmsh://") &&
!StringStartsWithCaseASCII(url, "mmst://") &&
!StringStartsWithCaseASCII(url, "mmsu://"))
return nullptr;
auto m = new MmsInputStream(url, mutex, cond);
......
......@@ -23,9 +23,7 @@
#include "../InputPlugin.hxx"
#include "lib/nfs/Glue.hxx"
#include "lib/nfs/FileReader.hxx"
#include "util/StringCompare.hxx"
#include <string.h>
#include "util/ASCII.hxx"
/**
* Do not buffer more than this number of bytes. It should be a
......@@ -219,7 +217,7 @@ static InputStream *
input_nfs_open(const char *uri,
Mutex &mutex, Cond &cond)
{
if (!StringStartsWith(uri, "nfs://"))
if (!StringStartsWithCaseASCII(uri, "nfs://"))
return nullptr;
NfsInputStream *is = new NfsInputStream(uri, mutex, cond);
......
......@@ -25,7 +25,7 @@
#include "../InputPlugin.hxx"
#include "PluginUnavailable.hxx"
#include "system/Error.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#include <libsmbclient.h>
......@@ -87,7 +87,7 @@ static InputStream *
input_smbclient_open(const char *uri,
Mutex &mutex, Cond &cond)
{
if (!StringStartsWith(uri, "smb://"))
if (!StringStartsWithCaseASCII(uri, "smb://"))
return nullptr;
const std::lock_guard<Mutex> protect(smbclient_mutex);
......
/*
* 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.
*/
#ifndef CURL_ERROR_HXX
#define CURL_ERROR_HXX
#include <stdexcept>
/**
* Thrown when an unsuccessful status was received from the HTTP
* server.
*/
class HttpStatusError : public std::runtime_error {
unsigned status;
public:
template<typename M>
explicit HttpStatusError(unsigned _status, M &&_msg) noexcept
:std::runtime_error(std::forward<M>(_msg)), status(_status) {}
unsigned GetStatus() const noexcept {
return status;
}
};
#endif
......@@ -62,6 +62,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
easy.SetOption(CURLOPT_NOPROGRESS, 1l);
easy.SetOption(CURLOPT_NOSIGNAL, 1l);
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
easy.SetOption(CURLOPT_URL, url);
}
......
Index: curl-7.58.0/lib/url.c
===================================================================
--- curl-7.58.0.orig/lib/url.c
+++ curl-7.58.0/lib/url.c
@@ -3503,6 +3503,7 @@ static CURLcode override_login(struct Cu
}
conn->bits.netrc = FALSE;
+#ifndef __BIONIC__
if(data->set.use_netrc != CURL_NETRC_IGNORED) {
int ret = Curl_parsenetrc(conn->host.name,
userp, passwdp,
@@ -3524,6 +3525,7 @@ static CURLcode override_login(struct Cu
conn->bits.user_passwd = TRUE; /* enable user+password */
}
}
+#endif
return CURLE_OK;
}
Index: curl-7.58.0/Makefile.in
===================================================================
--- curl-7.58.0.orig/Makefile.in
+++ curl-7.58.0/Makefile.in
@@ -641,8 +641,8 @@ CLEANFILES = $(VC6_LIBDSP) $(VC6_SRCDSP)
$(VC14_LIBVCXPROJ) $(VC14_SRCVCXPROJ) $(VC15_LIBVCXPROJ) $(VC15_SRCVCXPROJ)
bin_SCRIPTS = curl-config
-SUBDIRS = lib src
-DIST_SUBDIRS = $(SUBDIRS) tests packages scripts include docs
+SUBDIRS = lib
+DIST_SUBDIRS = $(SUBDIRS) include
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libcurl.pc
LIB_VAUTH_CFILES = vauth/vauth.c vauth/cleartext.c vauth/cram.c \
only_lib.patch
no_netrc.patch
......@@ -33,6 +33,9 @@ FfmpegInit()
{
av_log_set_callback(FfmpegLogCallback);
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
/* deprecated as of FFmpeg 4.0 */
av_register_all();
#endif
}
......@@ -25,6 +25,7 @@
#include "Domain.hxx"
#include "LogV.hxx"
#include "util/Domain.hxx"
#include "util/StringFormat.hxx"
extern "C" {
#include <libavutil/log.h>
......@@ -57,9 +58,10 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
cls = *(const AVClass *const*)ptr;
if (cls != nullptr) {
char domain[64];
snprintf(domain, sizeof(domain), "%s/%s",
ffmpeg_domain.GetName(), cls->item_name(ptr));
const auto domain =
StringFormat<64>("%s/%s",
ffmpeg_domain.GetName(),
cls->item_name(ptr));
const Domain d(domain);
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
}
......
......@@ -37,6 +37,18 @@ public:
explicit IcuCompare(const char *needle) noexcept;
IcuCompare(const IcuCompare &src) noexcept
:needle(src
? AllocatedString<>::Duplicate(src.needle.c_str())
: nullptr) {}
IcuCompare &operator=(const IcuCompare &src) noexcept {
needle = src
? AllocatedString<>::Duplicate(src.needle.c_str())
: nullptr;
return *this;
}
IcuCompare(IcuCompare &&) = default;
IcuCompare &operator=(IcuCompare &&) = default;
......
......@@ -19,6 +19,7 @@
#include "config.h"
#include "Connection.hxx"
#include "Error.hxx"
#include "Lease.hxx"
#include "Callback.hxx"
#include "event/Loop.hxx"
......@@ -31,7 +32,11 @@ extern "C" {
#include <utility>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <poll.h> /* for POLLIN, POLLOUT */
#endif
static constexpr std::chrono::steady_clock::duration NFS_MOUNT_TIMEOUT =
std::chrono::minutes(1);
......@@ -135,7 +140,7 @@ NfsConnection::CancellableCallback::Callback(int err, void *data)
if (err >= 0)
cb.OnNfsCallback((unsigned)err, data);
else
cb.OnNfsError(std::make_exception_ptr(std::runtime_error((const char *)data)));
cb.OnNfsError(std::make_exception_ptr(NfsClientError(-err, (const char *)data)));
} else {
if (open) {
/* a nfs_open_async() call was cancelled - to
......
/*
* Copyright 2007-2017 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Error.hxx"
#include "util/StringFormat.hxx"
extern "C" {
#include <nfsc/libnfs.h>
}
#include <assert.h>
#include <string.h>
static StringBuffer<256>
FormatNfsClientError(struct nfs_context *nfs, const char *msg) noexcept
{
assert(msg != nullptr);
const char *msg2 = nfs_get_error(nfs);
return StringFormat<256>("%s: %s", msg, msg2);
}
NfsClientError::NfsClientError(struct nfs_context *nfs, const char *msg) noexcept
:std::runtime_error(FormatNfsClientError(nfs, msg).c_str()),
code(0) {}
static StringBuffer<256>
FormatNfsClientError(int err, struct nfs_context *nfs, void *data,
const char *msg) noexcept
{
assert(msg != nullptr);
assert(err < 0);
const char *msg2 = (const char *)data;
if (data == nullptr || *(const char *)data == 0) {
msg2 = nfs_get_error(nfs);
if (msg2 == nullptr)
msg2 = strerror(-err);
}
return StringFormat<256>("%s: %s", msg, msg2);
}
NfsClientError::NfsClientError(int err, struct nfs_context *nfs, void *data,
const char *msg) noexcept
:std::runtime_error(FormatNfsClientError(err, nfs, data, msg).c_str()),
code(-err) {}
/*
* Copyright 2007-2017 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef NFS_ERROR_HXX
#define NFS_ERROR_HXX
#include <stdexcept>
class NfsClientError : public std::runtime_error {
int code;
public:
explicit NfsClientError(const char *_msg) noexcept
:std::runtime_error(_msg), code(0) {}
NfsClientError(int _code, const char *_msg) noexcept
:std::runtime_error(_msg), code(_code) {}
NfsClientError(struct nfs_context *nfs, const char *msg) noexcept;
NfsClientError(int err, struct nfs_context *nfs, void *data,
const char *msg) noexcept;
int GetCode() const noexcept {
return code;
}
};
#endif
......@@ -24,14 +24,13 @@
#include "Connection.hxx"
#include "event/Call.hxx"
#include "IOThread.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#include <utility>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
NfsFileReader::NfsFileReader()
:DeferredMonitor(io_thread_get()), state(State::INITIAL)
......@@ -94,7 +93,7 @@ NfsFileReader::Open(const char *uri)
{
assert(state == State::INITIAL);
if (!StringStartsWith(uri, "nfs://"))
if (!StringStartsWithCaseASCII(uri, "nfs://"))
throw std::runtime_error("Malformed nfs:// URI");
uri += 6;
......
......@@ -31,6 +31,7 @@
#include <stdint.h>
#include <stddef.h>
#include <sys/stat.h>
struct nfsfh;
class NfsConnection;
......
......@@ -34,7 +34,11 @@ static unsigned upnp_ref;
static void
DoInit()
{
auto code = UpnpInit(0, 0);
#ifdef UPNP_ENABLE_IPV6
auto code = UpnpInit2(nullptr, 0);
#else
auto code = UpnpInit(nullptr, 0);
#endif
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpInit() failed: %s",
UpnpGetErrorMessage(code));
......
......@@ -20,7 +20,7 @@
#include "config.h"
#include "ls.hxx"
#include "client/Response.hxx"
#include "util/StringCompare.hxx"
#include "util/ASCII.hxx"
#include "util/UriUtil.hxx"
#include <assert.h>
......@@ -97,7 +97,7 @@ uri_supported_scheme(const char *uri) noexcept
assert(uri_has_scheme(uri));
while (*urlPrefixes) {
if (StringStartsWith(uri, *urlPrefixes))
if (StringStartsWithCaseASCII(uri, *urlPrefixes))
return true;
urlPrefixes++;
}
......
......@@ -139,6 +139,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
return set_raw[ctl_dir](elem, value);
}
/* two special cases to avoid rounding errors at 0% and
100% */
if (volume <= 0)
return set_dB[ctl_dir](elem, min, dir);
else if (volume >= 100)
return set_dB[ctl_dir](elem, max, dir);
if (use_linear_dB_scale(min, max)) {
value = lrint_dir(volume * (max - min), dir) + min;
return set_dB[ctl_dir](elem, value, dir);
......
......@@ -69,7 +69,8 @@ class SmbclientNeighborExplorer final : public NeighborExplorer {
public:
SmbclientNeighborExplorer(NeighborListener &_listener)
:NeighborExplorer(_listener) {}
:NeighborExplorer(_listener),
thread(BIND_THIS_METHOD(ThreadFunc)) {}
/* virtual methods from class NeighborExplorer */
void Open() override;
......@@ -79,14 +80,13 @@ public:
private:
void Run();
void ThreadFunc();
static void ThreadFunc(void *ctx);
};
void
SmbclientNeighborExplorer::Open()
{
quit = false;
thread.Start(ThreadFunc, this);
thread.Start();
}
void
......@@ -239,6 +239,8 @@ SmbclientNeighborExplorer::Run()
inline void
SmbclientNeighborExplorer::ThreadFunc()
{
SetThreadName("smbclient");
mutex.lock();
while (!quit) {
......@@ -257,15 +259,6 @@ SmbclientNeighborExplorer::ThreadFunc()
mutex.unlock();
}
void
SmbclientNeighborExplorer::ThreadFunc(void *ctx)
{
SetThreadName("smbclient");
SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
e.ThreadFunc();
}
static NeighborExplorer *
smbclient_neighbor_create(gcc_unused EventLoop &loop,
NeighborListener &listener,
......
/*
* Copyright 2003-2017 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 NET_INIT_HXX
#define NET_INIT_HXX
#include "check.h"
#include "SocketError.hxx"
#ifdef _WIN32
#include <winsock2.h>
#endif
class ScopeNetInit {
#ifdef _WIN32
public:
ScopeNetInit() {
WSADATA sockinfo;
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if (retval != 0)
throw MakeSocketError(retval, "WSAStartup() failed");
}
~ScopeNetInit() noexcept {
WSACleanup();
}
#else
public:
ScopeNetInit() {}
#endif
};
#endif
......@@ -37,6 +37,7 @@
#include "config/ConfigGlobal.hxx"
#include "config/Block.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringFormat.hxx"
#include "Log.hxx"
#include <stdexcept>
......@@ -51,7 +52,8 @@
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
const ConfigBlock &block)
:plugin(_plugin)
:plugin(_plugin),
thread(BIND_THIS_METHOD(Task))
{
assert(plugin.finish != nullptr);
assert(plugin.open != nullptr);
......
......@@ -515,7 +515,6 @@ private:
* The OutputThread.
*/
void Task();
static void Task(void *arg);
};
/**
......
......@@ -396,7 +396,7 @@ AudioOutput::Pause()
pause = false;
}
inline void
void
AudioOutput::Task()
{
FormatThreadName("output:%s", name);
......@@ -513,16 +513,9 @@ AudioOutput::Task()
}
void
AudioOutput::Task(void *arg)
{
AudioOutput *ao = (AudioOutput *)arg;
ao->Task();
}
void
AudioOutput::StartThread()
{
assert(command == Command::NONE);
thread.Start(Task, this);
thread.Start();
}
......@@ -846,6 +846,12 @@ AlsaOutput::Recover(int err)
case SND_PCM_STATE_DRAINING:
err = 0;
break;
default:
/* this default case is just here to work around
-Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
1.1.6) */
break;
}
return err;
......
......@@ -27,6 +27,7 @@
#include "../Wrapper.hxx"
#include "mixer/MixerList.hxx"
#include "mixer/plugins/PulseMixerPlugin.hxx"
#include "util/ScopeExit.hxx"
#include "Log.hxx"
#include <pulse/thread-mainloop.h>
......@@ -854,7 +855,10 @@ PulseOutput::TestDefaultDevice()
try {
const ConfigBlock empty;
PulseOutput po(empty);
po.Enable();
AtScopeExit(&po) { po.Disable(); };
po.WaitConnection();
return true;
} catch (const std::runtime_error &e) {
return false;
......
......@@ -36,6 +36,8 @@
#include <roaraudio.h>
#undef new
#include <assert.h>
class RoarOutput {
friend struct AudioOutputWrapper<RoarOutput>;
......
......@@ -122,15 +122,6 @@ HttpdClient::HandleLine(const char *line)
return true;
}
if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
/* Send as dlna */
dlna_streaming_requested = true;
/* metadata is not supported by dlna streaming, so disable it */
metadata_supported = false;
metadata_requested = false;
return true;
}
/* expect more request headers */
return true;
}
......@@ -148,22 +139,7 @@ HttpdClient::SendResponse()
assert(state == RESPONSE);
if (dlna_streaming_requested) {
snprintf(buffer, sizeof(buffer),
"HTTP/1.1 206 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: 10000\r\n"
"Content-RangeX: 0-1000000/1000000\r\n"
"transferMode.dlna.org: Streaming\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
"\r\n",
httpd.content_type);
response = buffer;
} else if (metadata_requested) {
if (metadata_requested) {
allocated =
icy_server_metadata_header(httpd.name, httpd.genre,
httpd.website,
......@@ -202,7 +178,6 @@ HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
state(REQUEST),
queue_size(0),
head_method(false),
dlna_streaming_requested(false),
metadata_supported(_metadata_supported),
metadata_requested(false), metadata_sent(true),
metaint(8192), /*TODO: just a std value */
......
......@@ -82,11 +82,6 @@ class HttpdClient final
*/
bool head_method;
/**
* If DLNA streaming was an option.
*/
bool dlna_streaming_requested;
/* ICY */
/**
......
......@@ -35,6 +35,7 @@
#include "SoxrResampler.hxx"
#endif
#include <assert.h>
#include <string.h>
enum class SelectedResampler {
......
......@@ -88,7 +88,7 @@ static inline ConstBuffer<V>
ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer<V> src)
{
auto dest = buffer.GetT<V>(src.size);
ToAlsaChannelOrder71(dest, src.data, src.size / 6);
ToAlsaChannelOrder71(dest, src.data, src.size / 8);
return { dest, src.size };
}
......
......@@ -46,19 +46,20 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
assert(audio_valid_channel_count(channels));
assert(_src.size % channels == 0);
const unsigned num_src_samples = _src.size;
const unsigned num_src_frames = num_src_samples / channels;
const size_t num_src_samples = _src.size;
const size_t num_src_frames = num_src_samples / channels;
/* this rounds down and discards the last odd frame; not
/* this rounds down and discards up to 3 odd frames; not
elegant, but good enough for now */
const unsigned num_frames = num_src_frames / 2;
const unsigned num_samples = num_frames * channels;
const size_t num_dop_quads = num_src_frames / 4;
const size_t num_frames = num_dop_quads * 2;
const size_t num_samples = num_frames * channels;
uint32_t *const dest0 = (uint32_t *)buffer.GetT<uint32_t>(num_samples),
*dest = dest0;
auto src = _src.data;
for (unsigned i = num_frames / 2; i > 0; --i) {
for (size_t i = num_dop_quads; i > 0; --i) {
for (unsigned c = channels; c > 0; --c) {
/* each 24 bit sample has 16 DSD sample bits
plus the magic 0x05 marker */
......
......@@ -32,6 +32,8 @@
#include "PcmDop.hxx"
#endif
#include <assert.h>
void
PcmExport::Open(SampleFormat sample_format, unsigned _channels,
Params params)
......
......@@ -37,6 +37,7 @@ PlayerControl::PlayerControl(PlayerListener &_listener,
buffer_chunks(_buffer_chunks),
buffered_before_play(_buffered_before_play),
configured_audio_format(_configured_audio_format),
thread(BIND_THIS_METHOD(RunThread)),
replay_gain_config(_replay_gain_config)
{
}
......
......@@ -537,6 +537,9 @@ public:
void ApplyEnabled() override {
LockUpdateAudio();
}
private:
void RunThread();
};
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment