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: ...@@ -31,3 +31,8 @@ The following people have contributed code to MPD:
Jean-Francois Dockes <jf@dockes.org> Jean-Francois Dockes <jf@dockes.org>
Yue Wang <yuleopen@gmail.com> Yue Wang <yuleopen@gmail.com>
Matthew Leon Grinshpun <ml@matthewleon.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 = \ ...@@ -61,8 +61,8 @@ src_mpd_LDADD = \
libnet.a \ libnet.a \
$(FS_LIBS) \ $(FS_LIBS) \
libsystem.a \ libsystem.a \
libutil.a \
$(ICU_LDADD) \ $(ICU_LDADD) \
libutil.a \
$(SYSTEMD_DAEMON_LIBS) $(SYSTEMD_DAEMON_LIBS)
src_mpd_SOURCES = \ src_mpd_SOURCES = \
...@@ -234,6 +234,7 @@ libmpd_a_SOURCES += \ ...@@ -234,6 +234,7 @@ libmpd_a_SOURCES += \
endif endif
CURL_SOURCES = \ CURL_SOURCES = \
src/lib/curl/Error.hxx \
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \ src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \ src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \ src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
...@@ -292,8 +293,8 @@ clean-local: ...@@ -292,8 +293,8 @@ clean-local:
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a 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) $(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_BUILD_TOOLS_VERSION = 27.0.0
ANDROID_SDK_PLATFORM = android-17 ANDROID_SDK_PLATFORM = android-21
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION) ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM) ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
...@@ -307,7 +308,7 @@ ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml) ...@@ -307,7 +308,7 @@ ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES)) ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java 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 JAVA_CLASSFILES_DIR = android/build/classes
...@@ -328,9 +329,9 @@ android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawabl ...@@ -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 # R.java is generated by aapt, when resources.apk is generated
android/build/gen/org/musicpd/R.java: android/build/resources.apk 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) @$(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) \ -cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
-d $(JAVA_CLASSFILES_DIR) $^ -d $(JAVA_CLASSFILES_DIR) $^
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR) $(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
...@@ -340,7 +341,7 @@ android/build/include/org_musicpd_Bridge.h: android/build/classes.dex ...@@ -340,7 +341,7 @@ android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h 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) mkdir -p $(@D)
rm -f $@ rm -f $@
$(STRIP) -o $@ $< $(STRIP) -o $@ $<
...@@ -350,7 +351,7 @@ android/build/res/drawable/icon.png: mpd.svg ...@@ -350,7 +351,7 @@ android/build/res/drawable/icon.png: mpd.svg
rsvg-convert --width=48 --height=48 $< -o $@ rsvg-convert --width=48 --height=48 $< -o $@
.DELETE_ON_ERROR: android/build/unsigned.apk .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 $@ cp android/build/resources.apk $@
cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
...@@ -444,6 +445,7 @@ libutil_a_SOURCES = \ ...@@ -444,6 +445,7 @@ libutil_a_SOURCES = \
src/util/NumberParser.hxx \ src/util/NumberParser.hxx \
src/util/MimeType.cxx src/util/MimeType.hxx \ src/util/MimeType.cxx src/util/MimeType.hxx \
src/util/StringBuffer.hxx \ src/util/StringBuffer.hxx \
src/util/StringFormat.hxx \
src/util/StringPointer.hxx \ src/util/StringPointer.hxx \
src/util/StringView.cxx src/util/StringView.hxx \ src/util/StringView.cxx src/util/StringView.hxx \
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \ src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
...@@ -497,6 +499,7 @@ libthread_a_SOURCES = \ ...@@ -497,6 +499,7 @@ libthread_a_SOURCES = \
libnet_a_SOURCES = \ libnet_a_SOURCES = \
src/net/Features.hxx \ src/net/Features.hxx \
src/net/Init.hxx \
src/net/ToString.cxx src/net/ToString.hxx \ src/net/ToString.cxx src/net/ToString.hxx \
src/net/Resolver.cxx src/net/Resolver.hxx \ src/net/Resolver.cxx src/net/Resolver.hxx \
src/net/StaticSocketAddress.cxx src/net/StaticSocketAddress.hxx \ src/net/StaticSocketAddress.cxx src/net/StaticSocketAddress.hxx \
...@@ -711,6 +714,7 @@ NFS_SOURCES = \ ...@@ -711,6 +714,7 @@ NFS_SOURCES = \
src/lib/nfs/Cancellable.hxx \ src/lib/nfs/Cancellable.hxx \
src/lib/nfs/Lease.hxx \ src/lib/nfs/Lease.hxx \
src/lib/nfs/Connection.cxx src/lib/nfs/Connection.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/Manager.cxx src/lib/nfs/Manager.hxx \
src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \ src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \
src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \ src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \
...@@ -733,6 +737,8 @@ libstorage_a_SOURCES = \ ...@@ -733,6 +737,8 @@ libstorage_a_SOURCES = \
src/storage/FileInfo.hxx src/storage/FileInfo.hxx
libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \ libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
$(EXPAT_CFLAGS) \
$(NFS_CFLAGS) \ $(NFS_CFLAGS) \
$(SMBCLIENT_CFLAGS) $(SMBCLIENT_CFLAGS)
...@@ -1291,7 +1297,7 @@ endif ...@@ -1291,7 +1297,7 @@ endif
# #
libinput_a_SOURCES = \ 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/Init.cxx src/input/Init.hxx \
src/input/Registry.cxx src/input/Registry.hxx \ src/input/Registry.cxx src/input/Registry.hxx \
src/input/Open.cxx \ src/input/Open.cxx \
...@@ -2159,6 +2165,7 @@ test_run_output_LDADD = $(MPD_LIBS) \ ...@@ -2159,6 +2165,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
libutil.a libutil.a
test_run_output_SOURCES = test/run_output.cxx \ test_run_output_SOURCES = test/run_output.cxx \
test/ScopeIOThread.hxx \ test/ScopeIOThread.hxx \
test/NullMixerListener.hxx \
src/Log.cxx src/LogBackend.cxx \ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \ src/IOThread.cxx \
src/output/Domain.cxx \ src/output/Domain.cxx \
...@@ -2182,6 +2189,7 @@ test_read_mixer_LDADD = \ ...@@ -2182,6 +2189,7 @@ test_read_mixer_LDADD = \
libsystem.a \ libsystem.a \
libutil.a libutil.a
test_read_mixer_SOURCES = test/read_mixer.cxx \ test_read_mixer_SOURCES = test/read_mixer.cxx \
test/NullMixerListener.hxx \
src/Log.cxx src/LogBackend.cxx \ src/Log.cxx src/LogBackend.cxx \
src/mixer/MixerControl.cxx \ src/mixer/MixerControl.cxx \
src/filter/FilterPlugin.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) ver 0.20.15 (2018/01/05)
* queue: fix crash after seek failure * queue: fix crash after seek failure
* resampler * resampler
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="13" android:versionCode="20"
android:versionName="0.19.9"> 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"> <application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Main" <activity android:name=".Main"
......
...@@ -3,13 +3,14 @@ ...@@ -3,13 +3,14 @@
import os, os.path import os, os.path
import sys, subprocess import sys, subprocess
if len(sys.argv) < 3: if len(sys.argv) < 4:
print("Usage: build.py SDK_PATH NDK_PATH [configure_args...]", file=sys.stderr) print("Usage: build.py SDK_PATH NDK_PATH ABI [configure_args...]", file=sys.stderr)
sys.exit(1) sys.exit(1)
sdk_path = sys.argv[1] sdk_path = sys.argv[1]
ndk_path = sys.argv[2] 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')): if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
print("SDK not found in", ndk_path, file=sys.stderr) print("SDK not found in", ndk_path, file=sys.stderr)
...@@ -19,8 +20,36 @@ if not os.path.isdir(ndk_path): ...@@ -19,8 +20,36 @@ if not os.path.isdir(ndk_path):
print("NDK not found in", ndk_path, file=sys.stderr) print("NDK not found in", ndk_path, file=sys.stderr)
sys.exit(1) 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 # select the NDK target
arch = 'arm-linux-androideabi' abi_info = android_abis[android_abi]
arch = abi_info['arch']
# the path to the MPD sources # the path to the MPD sources
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..')) mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
...@@ -44,16 +73,16 @@ class AndroidNdkToolchain: ...@@ -44,16 +73,16 @@ class AndroidNdkToolchain:
self.src_path = src_path self.src_path = src_path
self.build_path = build_path self.build_path = build_path
self.ndk_arch = 'arm' ndk_arch = abi_info['ndk_arch']
android_abi = 'armeabi-v7a' android_api_level = '21'
ndk_platform = 'android-21' ndk_platform = 'android-' + android_api_level
# select the NDK compiler # select the NDK compiler
gcc_version = '4.9' gcc_version = '4.9'
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform) ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
sysroot = os.path.join(ndk_path, 'sysroot') 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') install_prefix = os.path.join(arch_path, 'root')
...@@ -61,11 +90,13 @@ class AndroidNdkToolchain: ...@@ -61,11 +90,13 @@ class AndroidNdkToolchain:
self.install_prefix = install_prefix self.install_prefix = install_prefix
self.sysroot = sysroot 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_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') toolchain_bin = os.path.join(toolchain_path, 'bin')
llvm_bin = os.path.join(llvm_path, 'bin') llvm_bin = os.path.join(llvm_path, 'bin')
...@@ -80,12 +111,12 @@ class AndroidNdkToolchain: ...@@ -80,12 +111,12 @@ class AndroidNdkToolchain:
self.nm = os.path.join(toolchain_bin, arch + '-nm') self.nm = os.path.join(toolchain_bin, arch + '-nm')
self.strip = os.path.join(toolchain_bin, arch + '-strip') self.strip = os.path.join(toolchain_bin, arch + '-strip')
self.cflags = '-Os -g ' + common_flags self.cflags = common_flags
self.cxxflags = '-Os -g ' + common_flags self.cxxflags = common_flags
self.cppflags = '--sysroot=' + sysroot + \ self.cppflags = '--sysroot=' + sysroot + \
' -isystem ' + os.path.join(install_prefix, 'include') + \ ' -isystem ' + os.path.join(install_prefix, 'include') + \
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \ ' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
' -D__ANDROID_API__=21' ' -D__ANDROID_API__=' + android_api_level
self.ldflags = '--sysroot=' + sysroot + \ self.ldflags = '--sysroot=' + sysroot + \
' -L' + os.path.join(install_prefix, 'lib') + \ ' -L' + os.path.join(install_prefix, 'lib') + \
' -L' + os.path.join(target_root, 'usr', 'lib') + \ ' -L' + os.path.join(target_root, 'usr', 'lib') + \
...@@ -93,22 +124,21 @@ class AndroidNdkToolchain: ...@@ -93,22 +124,21 @@ class AndroidNdkToolchain:
' ' + common_flags ' ' + common_flags
self.libs = '' 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_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False self.is_windows = False
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++') libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi) 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_flags = '-stdlib=libc++'
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a') 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 self.is_armv7:
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
if use_cxx: if use_cxx:
self.libs += ' ' + libstdcxx_ldadd self.cxxflags += ' ' + libstdcxx_cxxflags
self.cppflags += ' ' + libstdcxx_cppflags self.ldflags += ' ' + libstdcxx_ldflags
self.env = dict(os.environ) self.env = dict(os.environ)
...@@ -119,14 +149,16 @@ class AndroidNdkToolchain: ...@@ -119,14 +149,16 @@ class AndroidNdkToolchain:
# a list of third-party libraries to be used by MPD on Android # a list of third-party libraries to be used by MPD on Android
from build.libs import * from build.libs import *
thirdparty_libs = [ thirdparty_libs = [
libmpdclient,
libogg, libogg,
libvorbis, libvorbis,
opus, opus,
flac, flac,
libid3tag, libid3tag,
libmad,
ffmpeg, ffmpeg,
curl, curl,
libexpat,
libnfs,
boost, boost,
] ]
......
...@@ -49,7 +49,7 @@ public class Main extends Activity implements Runnable { ...@@ -49,7 +49,7 @@ public class Main extends Activity implements Runnable {
TextView tv = new TextView(this); TextView tv = new TextView(this);
tv.setText("Failed to load the native MPD libary.\n" + tv.setText("Failed to load the native MPD libary.\n" +
"Report this problem to us, and include the following information:\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" + "PRODUCT=" + Build.PRODUCT + "\n" +
"FINGERPRINT=" + Build.FINGERPRINT + "\n" + "FINGERPRINT=" + Build.FINGERPRINT + "\n" +
"error=" + Loader.error); "error=" + Loader.error);
......
AC_PREREQ(2.60) 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_MAJOR=0
VERSION_MINOR=20 VERSION_MINOR=20
VERSION_REVISION=15 VERSION_REVISION=21
VERSION_EXTRA=0 VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx]) AC_CONFIG_SRCDIR([src/Main.cxx])
...@@ -16,7 +16,7 @@ AC_CONFIG_MACRO_DIR([m4]) ...@@ -16,7 +16,7 @@ AC_CONFIG_MACRO_DIR([m4])
AC_DEFINE(PROTOCOL_VERSION, "0.20.0", [The MPD protocol version]) 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 if test x$GIT_COMMIT != x; then
AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit]) AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit])
fi fi
...@@ -186,6 +186,7 @@ AC_ARG_WITH([android-sdk], ...@@ -186,6 +186,7 @@ AC_ARG_WITH([android-sdk],
[Directory for Android SDK]), [Directory for Android SDK]),
[], [with_android_sdk=no]) [], [with_android_sdk=no])
android_abi=""
if test x$host_is_android = xyes; then if test x$host_is_android = xyes; then
if test x$with_android_sdk = xno; then if test x$with_android_sdk = xno; then
AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR]) AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR])
...@@ -194,9 +195,15 @@ if test x$host_is_android = xyes; then ...@@ -194,9 +195,15 @@ if test x$host_is_android = xyes; then
if ! test -x $with_android_sdk/tools/android; then if ! test -x $with_android_sdk/tools/android; then
AC_MSG_ERROR([Android SDK not found in $with_android_sdk]) AC_MSG_ERROR([Android SDK not found in $with_android_sdk])
fi fi
AS_CASE([$host_cpu],
[i686], [android_abi="x86"],
[aarch64], [android_abi="arm64-v8a"],
[android_abi="armeabi-v7a"])
fi fi
AC_SUBST(ANDROID_SDK, [$with_android_sdk]) AC_SUBST(ANDROID_SDK, [$with_android_sdk])
AC_SUBST(ANDROID_ABI, [$android_abi])
dnl --------------------------------------------------------------------------- dnl ---------------------------------------------------------------------------
dnl Language Checks dnl Language Checks
...@@ -315,7 +322,7 @@ else ...@@ -315,7 +322,7 @@ else
fi fi
default_enable_daemon=yes 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 default_enable_daemon=no
fi fi
AC_ARG_ENABLE(daemon, AC_ARG_ENABLE(daemon,
...@@ -402,7 +409,7 @@ AC_ARG_ENABLE(recorder-output, ...@@ -402,7 +409,7 @@ AC_ARG_ENABLE(recorder-output,
AC_ARG_ENABLE(sidplay, AC_ARG_ENABLE(sidplay,
AS_HELP_STRING([--enable-sidplay], AS_HELP_STRING([--enable-sidplay],
[enable C64 SID support via libsidplay2]),, [enable C64 SID support via libsidplayfp or libsidplay2]),,
enable_sidplay=auto) enable_sidplay=auto)
AC_ARG_ENABLE(shout, AC_ARG_ENABLE(shout,
...@@ -454,7 +461,7 @@ dnl --------------------------------------------------------------------------- ...@@ -454,7 +461,7 @@ dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries dnl Mandatory Libraries
dnl --------------------------------------------------------------------------- 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, AC_ARG_ENABLE(icu,
AS_HELP_STRING([--enable-icu], AS_HELP_STRING([--enable-icu],
...@@ -1005,7 +1012,7 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then ...@@ -1005,7 +1012,7 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
[found_sidplay=no]) [found_sidplay=no])
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
[libsidplay2 not found]) [libsidplay2 or libsidutils not found])
fi fi
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then 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 ...@@ -1017,10 +1024,11 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
fi fi
if test x$enable_sidplay = xyes; then if test x$enable_sidplay = xyes; then
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder" AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplayfp or libsidplay2 support])
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
if test x$found_sidplayfp = xyes; then if test x$found_sidplayfp = xyes; then
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2]) AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
else
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
fi fi
fi fi
......
...@@ -66,6 +66,26 @@ ...@@ -66,6 +66,26 @@
</para> </para>
</section> </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"> <section id="install_source">
<title>Compiling from source</title> <title>Compiling from source</title>
...@@ -94,7 +114,7 @@ cd mpd-version</programlisting> ...@@ -94,7 +114,7 @@ cd mpd-version</programlisting>
<listitem> <listitem>
<para> <para>
<ulink url="http://www.boost.org/">Boost 1.46</ulink> <ulink url="http://www.boost.org/">Boost 1.54</ulink>
</para> </para>
</listitem> </listitem>
...@@ -248,6 +268,59 @@ apt-get install g++ \ ...@@ -248,6 +268,59 @@ apt-get install g++ \
script. script.
</para> </para>
</section> </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>
<section id="systemd_socket"> <section id="systemd_socket">
...@@ -323,7 +396,9 @@ systemctl start mpd.socket</programlisting> ...@@ -323,7 +396,9 @@ systemctl start mpd.socket</programlisting>
<application>MPD</application> as a user daemon (and not as a <application>MPD</application> as a user daemon (and not as a
system daemon), the configuration is read from system daemon), the configuration is read from
<filename>$XDG_CONFIG_HOME/mpd/mpd.conf</filename> (usually <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>
<para> <para>
...@@ -2012,13 +2087,6 @@ run</programlisting> ...@@ -2012,13 +2087,6 @@ run</programlisting>
database. database.
</para> </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> <informaltable>
<tgroup cols="2"> <tgroup cols="2">
<thead> <thead>
...@@ -2048,6 +2116,15 @@ run</programlisting> ...@@ -2048,6 +2116,15 @@ run</programlisting>
</row> </row>
<row> <row>
<entry> <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> <varname>keepalive</varname>
<parameter>yes|no</parameter> <parameter>yes|no</parameter>
</entry> </entry>
......
...@@ -5,16 +5,20 @@ from build.makeproject import MakeProject ...@@ -5,16 +5,20 @@ from build.makeproject import MakeProject
class AutotoolsProject(MakeProject): class AutotoolsProject(MakeProject):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url, md5, installed, configure_args=[],
autogen=False, autogen=False,
autoreconf=False,
cppflags='', cppflags='',
ldflags='', ldflags='',
libs='', libs='',
subdirs=None,
**kwargs): **kwargs):
MakeProject.__init__(self, url, md5, installed, **kwargs) MakeProject.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
self.autogen = autogen self.autogen = autogen
self.autoreconf = autoreconf
self.cppflags = cppflags self.cppflags = cppflags
self.ldflags = ldflags self.ldflags = ldflags
self.libs = libs self.libs = libs
self.subdirs = subdirs
def configure(self, toolchain): def configure(self, toolchain):
src = self.unpack(toolchain) src = self.unpack(toolchain)
...@@ -26,6 +30,8 @@ class AutotoolsProject(MakeProject): ...@@ -26,6 +30,8 @@ class AutotoolsProject(MakeProject):
subprocess.check_call(['aclocal'], cwd=src) subprocess.check_call(['aclocal'], cwd=src)
subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src) subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
subprocess.check_call(['autoconf'], cwd=src) subprocess.check_call(['autoconf'], cwd=src)
if self.autoreconf:
subprocess.check_call(['autoreconf', '-vif'], cwd=src)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
...@@ -51,4 +57,8 @@ class AutotoolsProject(MakeProject): ...@@ -51,4 +57,8 @@ class AutotoolsProject(MakeProject):
def build(self, toolchain): def build(self, toolchain):
build = self.configure(toolchain) build = self.configure(toolchain)
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) MakeProject.build(self, toolchain, build)
...@@ -21,6 +21,8 @@ class FfmpegProject(Project): ...@@ -21,6 +21,8 @@ class FfmpegProject(Project):
if toolchain.is_arm: if toolchain.is_arm:
arch = 'arm' arch = 'arm'
elif toolchain.is_aarch64:
arch = 'aarch64'
else: else:
arch = 'x86' arch = 'x86'
......
import re import re
from os.path import abspath
from build.project import Project from build.project import Project
from build.zlib import ZlibProject from build.zlib import ZlibProject
from build.meson import MesonProject
from build.autotools import AutotoolsProject from build.autotools import AutotoolsProject
from build.ffmpeg import FfmpegProject from build.ffmpeg import FfmpegProject
from build.boost import BoostProject 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( libogg = AutotoolsProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz', 'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08', '4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
...@@ -15,12 +24,17 @@ libogg = AutotoolsProject( ...@@ -15,12 +24,17 @@ libogg = AutotoolsProject(
) )
libvorbis = AutotoolsProject( libvorbis = AutotoolsProject(
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz', 'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
'28cb28097c07a735d6af56e598e1c90f', 'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
'lib/libvorbis.a', 'lib/libvorbis.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
], ],
edits={
# this option is not understood by clang
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
}
) )
opus = AutotoolsProject( opus = AutotoolsProject(
...@@ -29,6 +43,8 @@ opus = AutotoolsProject( ...@@ -29,6 +43,8 @@ opus = AutotoolsProject(
'lib/libopus.a', 'lib/libopus.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
'--disable-doc',
'--disable-extra-programs',
], ],
# suppress "visibility default" from opus_defines.h # suppress "visibility default" from opus_defines.h
...@@ -42,7 +58,9 @@ flac = AutotoolsProject( ...@@ -42,7 +58,9 @@ flac = AutotoolsProject(
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
'--disable-xmms-plugin', '--disable-cpplibs', '--disable-xmms-plugin', '--disable-cpplibs',
'--disable-doxygen-docs',
], ],
subdirs=['include', 'src/libFLAC'],
) )
zlib = ZlibProject( zlib = ZlibProject(
...@@ -83,8 +101,8 @@ libmad = AutotoolsProject( ...@@ -83,8 +101,8 @@ libmad = AutotoolsProject(
) )
liblame = AutotoolsProject( liblame = AutotoolsProject(
'http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz', 'http://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz',
'24346b4158e4af3bd9f2e194bb23eb473c75fb7377011523353196b19b9a23ff', 'ddfe36cab873794038ae2c1210557ad34857a4b6bdc515785d1da9e175b1da1e',
'lib/libmp3lame.a', 'lib/libmp3lame.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -94,8 +112,8 @@ liblame = AutotoolsProject( ...@@ -94,8 +112,8 @@ liblame = AutotoolsProject(
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-3.4.1.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-4.0.2.tar.xz',
'5a77278a63741efa74e26bf197b9bb09ac6381b9757391b922407210f0f991c0', 'a95c0cc9eb990e94031d2183f2e6e444cc61c99f6f182d1575c433d62afb2f97',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -118,7 +136,6 @@ ffmpeg = FfmpegProject( ...@@ -118,7 +136,6 @@ ffmpeg = FfmpegProject(
'--disable-protocols', '--disable-protocols',
'--disable-devices', '--disable-devices',
'--disable-filters', '--disable-filters',
'--disable-filters',
'--disable-v4l2_m2m', '--disable-v4l2_m2m',
'--disable-parser=bmp', '--disable-parser=bmp',
...@@ -136,7 +153,6 @@ ffmpeg = FfmpegProject( ...@@ -136,7 +153,6 @@ ffmpeg = FfmpegProject(
'--disable-parser=mjpeg', '--disable-parser=mjpeg',
'--disable-parser=mlp', '--disable-parser=mlp',
'--disable-parser=mpeg4video', '--disable-parser=mpeg4video',
'--disable-parser=mpegaudio',
'--disable-parser=mpegvideo', '--disable-parser=mpegvideo',
'--disable-parser=opus', '--disable-parser=opus',
'--disable-parser=vc1', '--disable-parser=vc1',
...@@ -188,16 +204,6 @@ ffmpeg = FfmpegProject( ...@@ -188,16 +204,6 @@ ffmpeg = FfmpegProject(
# we don't need these decoders, because we have the dedicated # we don't need these decoders, because we have the dedicated
# libraries # libraries
'--disable-decoder=flac', '--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=opus',
'--disable-decoder=vorbis', '--disable-decoder=vorbis',
...@@ -311,7 +317,7 @@ ffmpeg = FfmpegProject( ...@@ -311,7 +317,7 @@ ffmpeg = FfmpegProject(
'--disable-decoder=svq1', '--disable-decoder=svq1',
'--disable-decoder=svq3', '--disable-decoder=svq3',
'--disable-decoder=tiff', '--disable-decoder=tiff',
'--disable-decoder=mottiertexseqvideo', '--disable-decoder=tiertexseqvideo',
'--disable-decoder=truemotion1', '--disable-decoder=truemotion1',
'--disable-decoder=truemotion2', '--disable-decoder=truemotion2',
'--disable-decoder=truemotion2rt', '--disable-decoder=truemotion2rt',
...@@ -335,8 +341,8 @@ ffmpeg = FfmpegProject( ...@@ -335,8 +341,8 @@ ffmpeg = FfmpegProject(
) )
curl = AutotoolsProject( curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.57.0.tar.xz', 'http://curl.haxx.se/download/curl-7.61.0.tar.xz',
'f5f6fd3c72b7b8389969f4fb671ed8532fa9b5bb7a5cae7ca89bc1cea45c7878', 'ef6e55192d04713673b4409ccbcb4cb6cd723137d6e10ca45b0c593a454e1720',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -354,10 +360,39 @@ curl = AutotoolsProject( ...@@ -354,10 +360,39 @@ curl = AutotoolsProject(
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies', '--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2', '--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( boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.66.0/boost_1_66_0.tar.bz2', 'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
'5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9', '7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
'include/boost/version.hpp', '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 ...@@ -3,10 +3,12 @@ import re
from build.download import download_and_verify from build.download import download_and_verify
from build.tar import untar from build.tar import untar
from build.quilt import push_all
class Project: class Project:
def __init__(self, url, md5, installed, name=None, version=None, def __init__(self, url, md5, installed, name=None, version=None,
base=None, base=None,
patches=None,
edits=None, edits=None,
use_cxx=False): use_cxx=False):
if base is None: if base is None:
...@@ -18,7 +20,7 @@ class Project: ...@@ -18,7 +20,7 @@ class Project:
self.base = base self.base = base
if name is None or version is None: 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 name is None: name = m.group(1)
if version is None: version = m.group(2) if version is None: version = m.group(2)
...@@ -29,6 +31,10 @@ class Project: ...@@ -29,6 +31,10 @@ class Project:
self.md5 = md5 self.md5 = md5
self.installed = installed 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.edits = edits
self.use_cxx = use_cxx self.use_cxx = use_cxx
...@@ -51,6 +57,9 @@ class Project: ...@@ -51,6 +57,9 @@ class Project:
parent_path = toolchain.build_path parent_path = toolchain.build_path
path = untar(self.download(toolchain), parent_path, self.base) 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: if self.edits is not None:
for filename, function in self.edits.items(): for filename, function in self.edits.items():
with open(os.path.join(path, filename), 'r+t') as f: 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 @@ ...@@ -19,9 +19,9 @@
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "util/StringFormat.hxx"
#include <assert.h> #include <assert.h>
#include <stdio.h>
void void
AudioFormat::ApplyMask(AudioFormat mask) noexcept AudioFormat::ApplyMask(AudioFormat mask) noexcept
...@@ -44,21 +44,16 @@ AudioFormat::ApplyMask(AudioFormat mask) noexcept ...@@ -44,21 +44,16 @@ AudioFormat::ApplyMask(AudioFormat mask) noexcept
StringBuffer<24> StringBuffer<24>
ToString(const AudioFormat af) noexcept ToString(const AudioFormat af) noexcept
{ {
StringBuffer<24> buffer;
if (af.format == SampleFormat::DSD && af.sample_rate > 0 && if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
af.sample_rate % 44100 == 0) { af.sample_rate % 44100 == 0) {
/* use shortcuts such as "dsd64" which implies the /* use shortcuts such as "dsd64" which implies the
sample rate */ sample rate */
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u", return StringFormat<24>("dsd%u:%u",
af.sample_rate * 8 / 44100, af.sample_rate * 8 / 44100,
af.channels); af.channels);
return buffer;
} }
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u", return StringFormat<24>("%u:%s:%u",
af.sample_rate, sample_format_to_string(af.format), af.sample_rate, sample_format_to_string(af.format),
af.channels); af.channels);
return buffer;
} }
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
#include "pcm/SampleFormat.hxx" #include "pcm/SampleFormat.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <assert.h>
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
......
...@@ -27,12 +27,17 @@ ...@@ -27,12 +27,17 @@
#include <assert.h> #include <assert.h>
static struct { static struct IOThread {
Mutex mutex; Mutex mutex;
Cond cond; Cond cond;
EventLoop *loop; EventLoop *loop;
Thread thread; Thread thread;
IOThread():thread(BIND_THIS_METHOD(Run)) {}
private:
void Run() noexcept;
} io; } io;
void void
...@@ -44,15 +49,15 @@ io_thread_run(void) ...@@ -44,15 +49,15 @@ io_thread_run(void)
io.loop->Run(); io.loop->Run();
} }
static void inline void
io_thread_func(gcc_unused void *arg) IOThread::Run() noexcept
{ {
SetThreadName("io"); SetThreadName("io");
/* lock+unlock to synchronize with io_thread_start(), to be /* lock+unlock to synchronize with io_thread_start(), to be
sure that io.thread is set */ sure that io.thread is set */
io.mutex.lock(); mutex.lock();
io.mutex.unlock(); mutex.unlock();
io_thread_run(); io_thread_run();
} }
...@@ -73,7 +78,7 @@ io_thread_start() ...@@ -73,7 +78,7 @@ io_thread_start()
assert(!io.thread.IsDefined()); assert(!io.thread.IsDefined());
const std::lock_guard<Mutex> protect(io.mutex); const std::lock_guard<Mutex> protect(io.mutex);
io.thread.Start(io_thread_func, nullptr); io.thread.Start();
} }
void void
...@@ -103,8 +108,12 @@ io_thread_get() noexcept ...@@ -103,8 +108,12 @@ io_thread_get() noexcept
return *io.loop; return *io.loop;
} }
#ifndef NDEBUG
bool bool
io_thread_inside() noexcept io_thread_inside() noexcept
{ {
return io.thread.IsInside(); return io.thread.IsInside();
} }
#endif
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#ifndef MPD_IO_THREAD_HXX #ifndef MPD_IO_THREAD_HXX
#define MPD_IO_THREAD_HXX #define MPD_IO_THREAD_HXX
#include "check.h"
#include "Compiler.h" #include "Compiler.h"
class EventLoop; class EventLoop;
...@@ -53,6 +54,8 @@ gcc_const ...@@ -53,6 +54,8 @@ gcc_const
EventLoop & EventLoop &
io_thread_get() noexcept; io_thread_get() noexcept;
#ifndef NDEBUG
/** /**
* Is the current thread the I/O thread? * Is the current thread the I/O thread?
*/ */
...@@ -61,3 +64,5 @@ bool ...@@ -61,3 +64,5 @@ bool
io_thread_inside() noexcept; io_thread_inside() noexcept;
#endif #endif
#endif
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "ls.hxx" #include "ls.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
...@@ -83,7 +83,7 @@ LocateUri(const char *uri, const Client *client ...@@ -83,7 +83,7 @@ LocateUri(const char *uri, const Client *client
) )
{ {
/* skip the obsolete "file://" prefix */ /* skip the obsolete "file://" prefix */
const char *path_utf8 = StringAfterPrefix(uri, "file://"); const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://");
if (path_utf8 != nullptr) { if (path_utf8 != nullptr) {
if (!PathTraitsUTF8::IsAbsolute(path_utf8)) if (!PathTraitsUTF8::IsAbsolute(path_utf8))
throw std::runtime_error("Malformed file:// URI"); throw std::runtime_error("Malformed file:// URI");
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include "unix/SignalHandlers.hxx" #include "unix/SignalHandlers.hxx"
#include "system/FatalError.hxx" #include "system/FatalError.hxx"
#include "thread/Slack.hxx" #include "thread/Slack.hxx"
#include "net/Init.hxx"
#include "lib/icu/Init.hxx" #include "lib/icu/Init.hxx"
#include "config/ConfigGlobal.hxx" #include "config/ConfigGlobal.hxx"
#include "config/Param.hxx" #include "config/Param.hxx"
...@@ -106,15 +107,6 @@ ...@@ -106,15 +107,6 @@
#include <locale.h> #include <locale.h>
#endif #endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#ifdef __BLOCKS__
#include <dispatch/dispatch.h>
#endif
#include <limits.h> #include <limits.h>
static constexpr size_t KILOBYTE = 1024; static constexpr size_t KILOBYTE = 1024;
...@@ -285,25 +277,6 @@ glue_state_file_init() ...@@ -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. * Initialize the decoder and player core, including the music pipe.
*/ */
static void static void
...@@ -451,7 +424,8 @@ try { ...@@ -451,7 +424,8 @@ try {
IcuInit(); IcuInit();
winsock_init(); const ScopeNetInit net_init;
io_thread_init(); io_thread_init();
config_global_init(); config_global_init();
...@@ -505,21 +479,8 @@ try { ...@@ -505,21 +479,8 @@ try {
daemonize_begin(options.daemon); daemonize_begin(options.daemon);
#endif #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); return mpd_main_after_fork(config);
#endif
} catch (const std::exception &e) { } catch (const std::exception &e) {
LogError(e); LogError(e);
return EXIT_FAILURE; return EXIT_FAILURE;
...@@ -702,10 +663,6 @@ try { ...@@ -702,10 +663,6 @@ try {
daemonize_finish(); daemonize_finish();
#endif #endif
#ifdef _WIN32
WSACleanup();
#endif
IcuFinish(); IcuFinish();
log_deinit(); log_deinit();
......
...@@ -95,6 +95,7 @@ public: ...@@ -95,6 +95,7 @@ public:
*/ */
gcc_pure gcc_pure
const MusicChunk *Peek() const noexcept { const MusicChunk *Peek() const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return head; return head;
} }
...@@ -120,6 +121,7 @@ public: ...@@ -120,6 +121,7 @@ public:
*/ */
gcc_pure gcc_pure
unsigned GetSize() const noexcept { unsigned GetSize() const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return size; return size;
} }
......
...@@ -24,6 +24,8 @@ ...@@ -24,6 +24,8 @@
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
#include "util/StringCompare.hxx"
#include "util/StringView.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/TimeParser.hxx" #include "util/TimeParser.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
...@@ -59,13 +61,13 @@ locate_parse_type(const char *str) noexcept ...@@ -59,13 +61,13 @@ locate_parse_type(const char *str) noexcept
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case) SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
:tag(_tag), :tag(_tag),
value(AllocatedString<>::Duplicate(_value)), value(_value),
fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare()) fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
{ {
} }
SongFilter::Item::Item(unsigned _tag, time_t _time) 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 ...@@ -274,3 +276,33 @@ SongFilter::GetBase() const noexcept
return nullptr; 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 @@ ...@@ -21,9 +21,9 @@
#define MPD_SONG_FILTER_HXX #define MPD_SONG_FILTER_HXX
#include "lib/icu/Compare.hxx" #include "lib/icu/Compare.hxx"
#include "util/AllocatedString.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <string>
#include <list> #include <list>
#include <stdint.h> #include <stdint.h>
...@@ -49,7 +49,7 @@ public: ...@@ -49,7 +49,7 @@ public:
class Item { class Item {
uint8_t tag; uint8_t tag;
AllocatedString<> value; std::string value;
/** /**
* This value is only set if case folding is enabled. * This value is only set if case folding is enabled.
...@@ -66,11 +66,6 @@ public: ...@@ -66,11 +66,6 @@ public:
Item(unsigned tag, const char *value, bool fold_case=false); Item(unsigned tag, const char *value, bool fold_case=false);
Item(unsigned tag, time_t time); Item(unsigned tag, time_t time);
Item(const Item &other) = delete;
Item(Item &&) = default;
Item &operator=(const Item &other) = delete;
unsigned GetTag() const { unsigned GetTag() const {
return tag; return tag;
} }
...@@ -157,6 +152,13 @@ public: ...@@ -157,6 +152,13 @@ public:
*/ */
gcc_pure gcc_pure
const char *GetBase() const noexcept; 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 @@ ...@@ -28,6 +28,7 @@
#include "tag/TagBuilder.hxx" #include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx" #include "util/StringUtil.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/NumberParser.hxx"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
...@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri) ...@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri)
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value); tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) { } else if (strcmp(line, "Time") == 0) {
tag.SetDuration(SignedSongTime::FromS(atof(value))); tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
} else if (strcmp(line, "Playlist") == 0) { } else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0); tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) { } else if (strcmp(line, SONG_MTIME) == 0) {
......
...@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity, ...@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
visitor.VisitArchiveEntry(path + 1); visitor.VisitArchiveEntry(path + 1);
} }
} }
#if LIBCDIO_VERSION_NUM >= 20000
iso9660_filelist_free(entlist);
#else
_cdio_list_free (entlist, true); _cdio_list_free (entlist, true);
#endif
} }
static ArchiveFile * static ArchiveFile *
......
...@@ -82,6 +82,7 @@ class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor { ...@@ -82,6 +82,7 @@ class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
DatabaseListener &listener; DatabaseListener &listener;
const std::string host; const std::string host;
const std::string password;
const unsigned port; const unsigned port;
const bool keepalive; const bool keepalive;
...@@ -170,6 +171,13 @@ static constexpr struct { ...@@ -170,6 +171,13 @@ static constexpr struct {
{ TAG_MUSICBRAINZ_RELEASETRACKID, { TAG_MUSICBRAINZ_RELEASETRACKID,
MPD_TAG_MUSICBRAINZ_RELEASETRACKID }, MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
#endif #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 } { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
}; };
...@@ -325,12 +333,41 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) ...@@ -325,12 +333,41 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
return true; 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, ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
const ConfigBlock &block) const ConfigBlock &block)
:Database(proxy_db_plugin), :Database(proxy_db_plugin),
SocketMonitor(_loop), IdleMonitor(_loop), SocketMonitor(_loop), IdleMonitor(_loop),
listener(_listener), listener(_listener),
host(block.GetBlockValue("host", "")), host(block.GetBlockValue("host", "")),
password(block.GetBlockValue("password", "")),
port(block.GetBlockValue("port", 0u)), port(block.GetBlockValue("port", 0u)),
keepalive(block.GetBlockValue("keepalive", false)) keepalive(block.GetBlockValue("keepalive", false))
{ {
...@@ -374,6 +411,10 @@ ProxyDatabase::Connect() ...@@ -374,6 +411,10 @@ ProxyDatabase::Connect()
try { try {
CheckError(connection); CheckError(connection);
if (!password.empty() &&
!mpd_run_password(connection, password.c_str()))
ThrowError(connection);
} catch (...) { } catch (...) {
mpd_connection_free(connection); mpd_connection_free(connection);
connection = nullptr; connection = nullptr;
...@@ -682,7 +723,7 @@ static void ...@@ -682,7 +723,7 @@ static void
SearchSongs(struct mpd_connection *connection, SearchSongs(struct mpd_connection *connection,
const DatabaseSelection &selection, const DatabaseSelection &selection,
VisitSong visit_song) VisitSong visit_song)
{ try {
assert(selection.recursive); assert(selection.recursive);
assert(visit_song); assert(visit_song);
...@@ -709,6 +750,11 @@ SearchSongs(struct mpd_connection *connection, ...@@ -709,6 +750,11 @@ SearchSongs(struct mpd_connection *connection,
if (!mpd_response_finish(connection)) if (!mpd_response_finish(connection))
ThrowError(connection); ThrowError(connection);
} catch (...) {
if (connection != nullptr)
mpd_search_cancel(connection);
throw;
} }
/** /**
...@@ -756,9 +802,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection, ...@@ -756,9 +802,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
void void
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type,
gcc_unused tag_mask_t group_mask, tag_mask_t group_mask,
VisitTag visit_tag) const VisitTag visit_tag) const
{ try {
// TODO: eliminate the const_cast // TODO: eliminate the const_cast
const_cast<ProxyDatabase *>(this)->EnsureConnected(); const_cast<ProxyDatabase *>(this)->EnsureConnected();
...@@ -767,32 +813,47 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, ...@@ -767,32 +813,47 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
throw std::runtime_error("Unsupported tag"); throw std::runtime_error("Unsupported tag");
if (!mpd_search_db_tags(connection, tag_type2) || if (!mpd_search_db_tags(connection, tag_type2) ||
!SendConstraints(connection, selection)) !SendConstraints(connection, selection) ||
!SendGroupMask(connection, group_mask))
ThrowError(connection); ThrowError(connection);
// TODO: use group_mask
if (!mpd_search_commit(connection)) if (!mpd_search_commit(connection))
ThrowError(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) { AtScopeExit(this, pair) {
mpd_return_pair(connection, pair); mpd_return_pair(connection, pair);
}; };
TagBuilder tag; const auto current_type = tag_name_parse_i(pair->name);
tag.AddItem(tag_type, pair->value); 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 /* if no tag item has been added, then the
given value was not acceptable given value was not acceptable
(e.g. empty); forcefully insert an empty (e.g. empty); forcefully insert an empty
tag in this case, as the caller expects the tag in this case, as the caller expects the
given tag type to be present */ given tag type to be present */
tag.AddEmptyItem(tag_type); builder.AddEmptyItem(current_type);
}
if (!builder.IsEmpty()) {
try { try {
visit_tag(tag.Commit()); visit_tag(builder.Commit());
} catch (...) { } catch (...) {
mpd_response_finish(connection); mpd_response_finish(connection);
throw; throw;
...@@ -801,6 +862,11 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, ...@@ -801,6 +862,11 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
if (!mpd_response_finish(connection)) if (!mpd_response_finish(connection))
ThrowError(connection); ThrowError(connection);
} catch (...) {
if (connection != nullptr)
mpd_search_cancel(connection);
throw;
} }
DatabaseStats DatabaseStats
......
...@@ -20,18 +20,12 @@ ...@@ -20,18 +20,12 @@
#include "config.h" #include "config.h"
#include "Mount.hxx" #include "Mount.hxx"
#include "PrefixedLightSong.hxx" #include "PrefixedLightSong.hxx"
#include "SongFilter.hxx"
#include "db/Selection.hxx" #include "db/Selection.hxx"
#include "db/LightDirectory.hxx" #include "db/LightDirectory.hxx"
#include "db/Interface.hxx" #include "db/Interface.hxx"
#include "fs/Traits.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> #include <string>
struct PrefixedLightDirectory : LightDirectory { struct PrefixedLightDirectory : LightDirectory {
...@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db, ...@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db,
vp = std::bind(PrefixVisitPlaylist, vp = std::bind(PrefixVisitPlaylist,
base, std::ref(visit_playlist), _1, _2); 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); db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
} }
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/StringFormat.hxx"
#include <stdio.h> #include <stdio.h>
...@@ -47,10 +48,6 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, ...@@ -47,10 +48,6 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
unsigned &didreadp, unsigned &didreadp,
unsigned &totalp) const 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 // Some devices require an empty SortCriteria, else bad params
IXML_Document *request = IXML_Document *request =
MakeActionHelper("Browse", m_serviceType.c_str(), MakeActionHelper("Browse", m_serviceType.c_str(),
...@@ -58,8 +55,10 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, ...@@ -58,8 +55,10 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
"BrowseFlag", "BrowseDirectChildren", "BrowseFlag", "BrowseDirectChildren",
"Filter", "*", "Filter", "*",
"SortCriteria", "", "SortCriteria", "",
"StartingIndex", ofbuf, "StartingIndex",
"RequestedCount", cntbuf); StringFormat<32>("%u", offset).c_str(),
"RequestedCount",
StringFormat<32>("%u", count).c_str());
if (request == nullptr) if (request == nullptr)
throw std::runtime_error("UpnpMakeAction() failed"); throw std::runtime_error("UpnpMakeAction() failed");
...@@ -112,15 +111,13 @@ ContentDirectoryService::search(UpnpClient_Handle hdl, ...@@ -112,15 +111,13 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
unsigned offset = 0, total = -1, count; unsigned offset = 0, total = -1, count;
do { do {
char ofbuf[100];
sprintf(ofbuf, "%d", offset);
UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(), UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
"ContainerID", objectId, "ContainerID", objectId,
"SearchCriteria", ss, "SearchCriteria", ss,
"Filter", "*", "Filter", "*",
"SortCriteria", "", "SortCriteria", "",
"StartingIndex", ofbuf, "StartingIndex",
StringFormat<32>("%u", offset).c_str(),
"RequestedCount", "0")); // Setting a value here gets twonky into fits "RequestedCount", "0")); // Setting a value here gets twonky into fits
if (!request) if (!request)
throw std::runtime_error("UpnpMakeAction() failed"); throw std::runtime_error("UpnpMakeAction() failed");
......
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
#include "ExcludeList.hxx" #include "ExcludeList.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx" #include "fs/NarrowPath.hxx"
#include "fs/io/TextFile.hxx" #include "input/TextInputStream.hxx"
#include "system/Error.hxx" #include "util/StringUtil.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <stdexcept> #include <stdexcept>
...@@ -35,35 +35,29 @@ ...@@ -35,35 +35,29 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
inline void
ExcludeList::ParseLine(char *line) noexcept
{
char *p = Strip(line);
if (*p != 0 && *p != '#')
patterns.emplace_front(p);
}
bool bool
ExcludeList::LoadFile(Path path_fs) noexcept ExcludeList::Load(InputStreamPtr is)
try { {
#ifdef HAVE_CLASS_GLOB #ifdef HAVE_CLASS_GLOB
TextFile file(path_fs); TextInputStream tis(std::move(is));
char *line; char *line;
while ((line = file.ReadLine()) != nullptr) { while ((line = tis.ReadLine()) != nullptr)
char *p = strchr(line, '#'); ParseLine(line);
if (p != nullptr)
*p = 0;
p = Strip(line);
if (*p != 0)
patterns.emplace_front(p);
}
#else #else
/* not implemented */ /* not implemented */
(void)path_fs; (void)path_fs;
#endif #endif
return true; 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 bool
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "check.h" #include "check.h"
#include "Compiler.h" #include "Compiler.h"
#include "fs/Glob.hxx" #include "fs/Glob.hxx"
#include "input/Ptr.hxx"
#ifdef HAVE_CLASS_GLOB #ifdef HAVE_CLASS_GLOB
#include <forward_list> #include <forward_list>
...@@ -62,13 +63,16 @@ public: ...@@ -62,13 +63,16 @@ public:
/** /**
* Loads and parses a .mpdignore file. * 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 * Checks whether one of the patterns in the .mpdignore file matches
* the specified file name. * the specified file name.
*/ */
bool Check(Path name_fs) const noexcept; bool Check(Path name_fs) const noexcept;
private:
void ParseLine(char *line) noexcept;
}; };
......
...@@ -49,6 +49,10 @@ struct UpdateQueueItem { ...@@ -49,6 +49,10 @@ struct UpdateQueueItem {
bool IsDefined() const { bool IsDefined() const {
return id != 0; return id != 0;
} }
void Clear() {
id = 0;
}
}; };
class UpdateQueue { class UpdateQueue {
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "Idle.hxx" #include "Idle.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "thread/Thread.hxx" #include "thread/Thread.hxx"
#include "thread/Name.hxx"
#include "thread/Util.hxx" #include "thread/Util.hxx"
#ifndef NDEBUG #ifndef NDEBUG
...@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db, ...@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
:DeferredMonitor(_loop), :DeferredMonitor(_loop),
db(_db), storage(_storage), db(_db), storage(_storage),
listener(_listener), listener(_listener),
update_thread(BIND_THIS_METHOD(Task)),
update_task_id(0), update_task_id(0),
walk(nullptr) walk(nullptr)
{ {
...@@ -112,6 +114,8 @@ UpdateService::Task() ...@@ -112,6 +114,8 @@ UpdateService::Task()
{ {
assert(walk != nullptr); assert(walk != nullptr);
SetThreadName("update");
if (!next.path_utf8.empty()) if (!next.path_utf8.empty())
FormatDebug(update_domain, "starting: %s", FormatDebug(update_domain, "starting: %s",
next.path_utf8.c_str()); next.path_utf8.c_str());
...@@ -141,13 +145,6 @@ UpdateService::Task() ...@@ -141,13 +145,6 @@ UpdateService::Task()
} }
void void
UpdateService::Task(void *ctx)
{
UpdateService &service = *(UpdateService *)ctx;
return service.Task();
}
void
UpdateService::StartThread(UpdateQueueItem &&i) UpdateService::StartThread(UpdateQueueItem &&i)
{ {
assert(GetEventLoop().IsInsideOrNull()); assert(GetEventLoop().IsInsideOrNull());
...@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i) ...@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
next = std::move(i); next = std::move(i);
walk = new UpdateWalk(GetEventLoop(), listener, *next.storage); walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
update_thread.Start(Task, this); update_thread.Start();
FormatDebug(update_domain, FormatDebug(update_domain,
"spawned thread for update job id %i", next.id); "spawned thread for update job id %i", next.id);
...@@ -258,7 +255,7 @@ UpdateService::RunDeferred() ...@@ -258,7 +255,7 @@ UpdateService::RunDeferred()
delete walk; delete walk;
walk = nullptr; walk = nullptr;
next = UpdateQueueItem(); next.Clear();
idle_add(IDLE_UPDATE); idle_add(IDLE_UPDATE);
......
...@@ -98,7 +98,6 @@ private: ...@@ -98,7 +98,6 @@ private:
/* the update thread */ /* the update thread */
void Task(); void Task();
static void Task(void *ctx);
void StartThread(UpdateQueueItem &&i); void StartThread(UpdateQueueItem &&i);
......
...@@ -36,6 +36,9 @@ ...@@ -36,6 +36,9 @@
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "storage/FileInfo.hxx" #include "storage/FileInfo.hxx"
#include "input/InputStream.hxx"
#include "input/Error.hxx"
#include "thread/Cond.hxx"
#include "util/Alloc.hxx" #include "util/Alloc.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
...@@ -345,11 +348,16 @@ UpdateWalk::UpdateDirectory(Directory &directory, ...@@ -345,11 +348,16 @@ UpdateWalk::UpdateDirectory(Directory &directory,
ExcludeList child_exclude_list(exclude_list); ExcludeList child_exclude_list(exclude_list);
{ try {
const auto exclude_path_fs = Mutex mutex;
storage.MapChildFS(directory.GetPath(), ".mpdignore"); Cond cond;
if (!exclude_path_fs.IsNull()) auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
child_exclude_list.LoadFile(exclude_path_fs); ".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()) if (!child_exclude_list.IsEmpty())
......
...@@ -300,6 +300,7 @@ DecoderBridge::CommandFinished() ...@@ -300,6 +300,7 @@ DecoderBridge::CommandFinished()
initial_seek_running = false; initial_seek_running = false;
timestamp = dc.start_time.ToDoubleS(); timestamp = dc.start_time.ToDoubleS();
absolute_frame = dc.start_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
return; return;
} }
...@@ -319,6 +320,7 @@ DecoderBridge::CommandFinished() ...@@ -319,6 +320,7 @@ DecoderBridge::CommandFinished()
convert->Reset(); convert->Reset();
timestamp = dc.seek_time.ToDoubleS(); timestamp = dc.seek_time.ToDoubleS();
absolute_frame = dc.seek_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
} }
dc.command = DecoderCommand::NONE; dc.command = DecoderCommand::NONE;
...@@ -427,6 +429,7 @@ DecoderBridge::SubmitTimestamp(double t) ...@@ -427,6 +429,7 @@ DecoderBridge::SubmitTimestamp(double t)
assert(t >= 0); assert(t >= 0);
timestamp = t; timestamp = t;
absolute_frame = uint64_t(t * dc.in_audio_format.sample_rate);
} }
DecoderCommand DecoderCommand
...@@ -464,6 +467,29 @@ DecoderBridge::SubmitData(InputStream *is, ...@@ -464,6 +467,29 @@ DecoderBridge::SubmitData(InputStream *is,
return cmd; 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) { if (convert != nullptr) {
assert(dc.in_audio_format != dc.out_audio_format); assert(dc.in_audio_format != dc.out_audio_format);
...@@ -521,15 +547,11 @@ DecoderBridge::SubmitData(InputStream *is, ...@@ -521,15 +547,11 @@ DecoderBridge::SubmitData(InputStream *is,
timestamp += (double)nbytes / timestamp += (double)nbytes /
dc.out_audio_format.GetTimeToSize(); 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 DecoderCommand
......
...@@ -50,6 +50,11 @@ public: ...@@ -50,6 +50,11 @@ public:
double timestamp = 0; 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) * Is the initial seek (to the start position of the sub-song)
* pending, or has it been performed already? * pending, or has it been performed already?
*/ */
......
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond, DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
const AudioFormat _configured_audio_format, const AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config) 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), configured_audio_format(_configured_audio_format),
replay_gain_config(_replay_gain_config) {} replay_gain_config(_replay_gain_config) {}
......
...@@ -308,9 +308,14 @@ struct DecoderControl { ...@@ -308,9 +308,14 @@ struct DecoderControl {
bool IsCurrentSong(const DetachedSong &_song) const noexcept; bool IsCurrentSong(const DetachedSong &_song) const noexcept;
gcc_pure 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); const std::lock_guard<Mutex> protect(mutex);
return IsCurrentSong(_song); return IsSeekableCurrentSong(_song);
} }
private: private:
...@@ -415,6 +420,9 @@ public: ...@@ -415,6 +420,9 @@ public:
* mixramp_start/mixramp_end. * mixramp_start/mixramp_end.
*/ */
void CycleMixRamp(); void CycleMixRamp();
private:
void RunThread();
}; };
#endif #endif
...@@ -513,30 +513,28 @@ try { ...@@ -513,30 +513,28 @@ try {
dc.client_cond.signal(); dc.client_cond.signal();
} }
static void void
decoder_task(void *arg) DecoderControl::RunThread()
{ {
DecoderControl &dc = *(DecoderControl *)arg;
SetThreadName("decoder"); SetThreadName("decoder");
const std::lock_guard<Mutex> protect(dc.mutex); const std::lock_guard<Mutex> protect(mutex);
do { do {
assert(dc.state == DecoderState::STOP || assert(state == DecoderState::STOP ||
dc.state == DecoderState::ERROR); state == DecoderState::ERROR);
switch (dc.command) { switch (command) {
case DecoderCommand::START: case DecoderCommand::START:
dc.CycleMixRamp(); CycleMixRamp();
dc.replay_gain_prev_db = dc.replay_gain_db; replay_gain_prev_db = replay_gain_db;
dc.replay_gain_db = 0; replay_gain_db = 0;
decoder_run(dc); decoder_run(*this);
if (dc.state == DecoderState::ERROR) { if (state == DecoderState::ERROR) {
try { try {
std::rethrow_exception(dc.error); std::rethrow_exception(error);
} catch (const std::exception &e) { } catch (const std::exception &e) {
LogError(e); LogError(e);
} catch (...) { } catch (...) {
...@@ -552,20 +550,20 @@ decoder_task(void *arg) ...@@ -552,20 +550,20 @@ decoder_task(void *arg)
/* we need to clear the pipe here; usually the /* we need to clear the pipe here; usually the
PlayerThread is responsible, but it is not PlayerThread is responsible, but it is not
aware that the decoder has finished */ aware that the decoder has finished */
dc.pipe->Clear(*dc.buffer); pipe->Clear(*buffer);
decoder_run(dc); decoder_run(*this);
break; break;
case DecoderCommand::STOP: case DecoderCommand::STOP:
dc.CommandFinishedLocked(); CommandFinishedLocked();
break; break;
case DecoderCommand::NONE: case DecoderCommand::NONE:
dc.Wait(); Wait();
break; break;
} }
} while (dc.command != DecoderCommand::NONE || !dc.quit); } while (command != DecoderCommand::NONE || !quit);
} }
void void
...@@ -574,5 +572,5 @@ decoder_thread_start(DecoderControl &dc) ...@@ -574,5 +572,5 @@ decoder_thread_start(DecoderControl &dc)
assert(!dc.thread.IsDefined()); assert(!dc.thread.IsDefined());
dc.quit = false; dc.quit = false;
dc.thread.Start(decoder_task, &dc); dc.thread.Start();
} }
...@@ -128,7 +128,7 @@ dsdlib_tag_id3(InputStream &is, ...@@ -128,7 +128,7 @@ dsdlib_tag_id3(InputStream &is,
return; return;
const auto count64 = size - tagoffset; const auto count64 = size - tagoffset;
if (count64 < 10 || count64 > 1024 * 1024) if (count64 < 10 || count64 > 4 * 1024 * 1024)
return; return;
if (!dsdlib_skip_to(nullptr, is, tagoffset)) if (!dsdlib_skip_to(nullptr, is, tagoffset))
......
...@@ -484,6 +484,8 @@ static const char *const dsdiff_suffixes[] = { ...@@ -484,6 +484,8 @@ static const char *const dsdiff_suffixes[] = {
static const char *const dsdiff_mime_types[] = { static const char *const dsdiff_mime_types[] = {
"application/x-dff", "application/x-dff",
"audio/x-dff",
"audio/x-dsd",
nullptr nullptr
}; };
......
...@@ -358,6 +358,8 @@ static const char *const dsf_suffixes[] = { ...@@ -358,6 +358,8 @@ static const char *const dsf_suffixes[] = {
static const char *const dsf_mime_types[] = { static const char *const dsf_mime_types[] = {
"application/x-dsf", "application/x-dsf",
"audio/x-dsf",
"audio/x-dsd",
nullptr nullptr
}; };
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#include "config.h" #include "config.h"
#include "FlacCommon.hxx" #include "FlacCommon.hxx"
#include "FlacMetadata.hxx" #include "FlacMetadata.hxx"
#include "util/ConstBuffer.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <stdexcept> #include <stdexcept>
...@@ -143,25 +142,10 @@ FlacDecoder::OnWrite(const FLAC__Frame &frame, ...@@ -143,25 +142,10 @@ FlacDecoder::OnWrite(const FLAC__Frame &frame,
if (!initialized && !OnFirstFrame(frame.header)) if (!initialized && !OnFirstFrame(frame.header))
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; 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); (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; return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
} }
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "FlacInput.hxx" #include "FlacInput.hxx"
#include "FlacPcm.hxx" #include "FlacPcm.hxx"
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "util/ConstBuffer.hxx"
#include <FLAC/stream_decoder.h> #include <FLAC/stream_decoder.h>
...@@ -41,6 +42,12 @@ struct FlacDecoder : public FlacInput { ...@@ -41,6 +42,12 @@ struct FlacDecoder : public FlacInput {
*/ */
bool unsupported = false; bool unsupported = false;
/**
* The kbit_rate parameter for the next
* DecoderBridge::SubmitData() call.
*/
uint16_t kbit_rate;
FlacPcmImport pcm_import; FlacPcmImport pcm_import;
/** /**
...@@ -51,6 +58,13 @@ struct FlacDecoder : public FlacInput { ...@@ -51,6 +58,13 @@ struct FlacDecoder : public FlacInput {
Tag tag; 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) FlacDecoder(DecoderClient &_client, InputStream &_input_stream)
:FlacInput(_input_stream, &_client) {} :FlacInput(_input_stream, &_client) {}
......
...@@ -139,19 +139,40 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) ...@@ -139,19 +139,40 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd)
return data->initialized; 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 static void
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec) flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
{ {
DecoderClient &client = *data->GetClient(); DecoderClient &client = *data->GetClient();
while (true) { while (true) {
DecoderCommand cmd; DecoderCommand cmd = FlacSubmitToClient(client, *data);
if (!data->tag.IsEmpty()) {
cmd = client.SubmitTag(data->GetInputStream(),
std::move(data->tag));
data->tag.Clear();
} else
cmd = client.GetCommand();
if (cmd == DecoderCommand::SEEK) { if (cmd == DecoderCommand::SEEK) {
FLAC__uint64 seek_sample = client.GetSeekFrame(); FLAC__uint64 seek_sample = client.GetSeekFrame();
...@@ -160,6 +181,11 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec) ...@@ -160,6 +181,11 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
client.CommandFinished(); client.CommandFinished();
} else } else
client.SeekError(); 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) } else if (cmd == DecoderCommand::STOP)
break; break;
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/FormatString.hxx" #include "util/StringFormat.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#define SUBTUNE_PREFIX "tune_" #define SUBTUNE_PREFIX "tune_"
...@@ -191,18 +190,15 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count, ...@@ -191,18 +190,15 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
tag_handler_invoke_duration(handler, handler_ctx, tag_handler_invoke_duration(handler, handler_ctx,
SongTime::FromMS(info.play_length)); SongTime::FromMS(info.play_length));
if (track_count > 1) { if (track_count > 1)
char track[16]; tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK,
sprintf(track, "%u", song_num + 1); StringFormat<16>("%u", song_num + 1));
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
}
if (info.song != nullptr) { if (info.song != nullptr) {
if (track_count > 1) { if (track_count > 1) {
/* start numbering subtunes from 1 */ /* start numbering subtunes from 1 */
char tag_title[1024]; const auto tag_title =
snprintf(tag_title, sizeof(tag_title), StringFormat<1024>("%s (%u/%d)",
"%s (%u/%d)",
info.song, song_num + 1, info.song, song_num + 1,
track_count); track_count);
tag_handler_invoke_tag(handler, handler_ctx, tag_handler_invoke_tag(handler, handler_ctx,
...@@ -297,9 +293,9 @@ gme_container_scan(Path path_fs) ...@@ -297,9 +293,9 @@ gme_container_scan(Path path_fs)
ScanMusicEmu(emu, i, ScanMusicEmu(emu, i,
add_tag_handler, &tag_builder); add_tag_handler, &tag_builder);
char track_name[64]; const auto track_name =
snprintf(track_name, sizeof(track_name), StringFormat<64>(SUBTUNE_PREFIX "%03u.%s", i+1,
SUBTUNE_PREFIX "%03u.%s", i+1, subtune_suffix); subtune_suffix);
tail = list.emplace_after(tail, track_name, tail = list.emplace_after(tail, track_name,
tag_builder.Commit()); tag_builder.Commit());
} }
......
...@@ -53,6 +53,14 @@ ScanOneOpusTag(const char *name, const char *value, ...@@ -53,6 +53,14 @@ ScanOneOpusTag(const char *name, const char *value,
long l = strtol(value, &endptr, 10); long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0) if (endptr > value && *endptr == 0)
rgi->track.gain = double(l) / 256.; 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); tag_handler_invoke_pair(handler, ctx, name, value);
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "util/Macros.hxx" #include "util/Macros.hxx"
#include "util/FormatString.hxx" #include "util/StringFormat.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "system/ByteOrder.hxx" #include "system/ByteOrder.hxx"
#include "Log.hxx" #include "Log.hxx"
...@@ -413,9 +413,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks, ...@@ -413,9 +413,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
title = ""; title = "";
if (n_tracks > 1) { if (n_tracks > 1) {
char tag_title[1024]; const auto tag_title =
snprintf(tag_title, sizeof(tag_title), StringFormat<1024>("%s (%u/%u)",
"%s (%u/%u)",
title, track, n_tracks); title, track, n_tracks);
tag_handler_invoke_tag(handler, handler_ctx, tag_handler_invoke_tag(handler, handler_ctx,
TAG_TITLE, tag_title); TAG_TITLE, tag_title);
...@@ -435,9 +434,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks, ...@@ -435,9 +434,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
date); date);
/* track */ /* track */
char track_buffer[16]; tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK,
sprintf(track_buffer, "%d", track); StringFormat<16>("%u", track));
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track_buffer);
} }
static bool static bool
......
...@@ -52,7 +52,7 @@ class OpusEncoder final : public OggEncoder { ...@@ -52,7 +52,7 @@ class OpusEncoder final : public OggEncoder {
ogg_int64_t packetno = 0; ogg_int64_t packetno = 0;
ogg_int64_t granulepos; ogg_int64_t granulepos = 0;
public: public:
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc); OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
#include <algorithm> #include <algorithm>
EventLoop::EventLoop() EventLoop::EventLoop()
:SocketMonitor(*this) :SocketMonitor(*this), quit(false)
{ {
SocketMonitor::Open(wake_fd.Get()); SocketMonitor::Open(wake_fd.Get());
SocketMonitor::Schedule(SocketMonitor::READ); SocketMonitor::Schedule(SocketMonitor::READ);
...@@ -46,7 +46,9 @@ EventLoop::~EventLoop() ...@@ -46,7 +46,9 @@ EventLoop::~EventLoop()
void void
EventLoop::Break() EventLoop::Break()
{ {
quit = true; if (quit.exchange(true))
return;
wake_fd.Write(); wake_fd.Write();
} }
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "SocketMonitor.hxx" #include "SocketMonitor.hxx"
#include <chrono> #include <chrono>
#include <atomic>
#include <list> #include <list>
#include <set> #include <set>
...@@ -82,7 +83,7 @@ class EventLoop final : SocketMonitor ...@@ -82,7 +83,7 @@ class EventLoop final : SocketMonitor
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); 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 * True when the object has been modified and another check is
......
...@@ -53,10 +53,16 @@ public: ...@@ -53,10 +53,16 @@ public:
void Set(const AudioFormat &_out_audio_format); void Set(const AudioFormat &_out_audio_format);
void Reset() override { void Reset() override {
if (IsActive())
state.Reset(); state.Reset();
} }
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override; ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
private:
bool IsActive() const noexcept {
return out_audio_format != in_audio_format;
}
}; };
class PreparedConvertFilter final : public PreparedFilter { class PreparedConvertFilter final : public PreparedFilter {
...@@ -80,7 +86,7 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format) ...@@ -80,7 +86,7 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format)
/* no change */ /* no change */
return; return;
if (out_audio_format != in_audio_format) { if (IsActive()) {
out_audio_format = in_audio_format; out_audio_format = in_audio_format;
state.Close(); state.Close();
} }
...@@ -111,7 +117,7 @@ ConvertFilter::~ConvertFilter() ...@@ -111,7 +117,7 @@ ConvertFilter::~ConvertFilter()
{ {
assert(in_audio_format.IsValid()); assert(in_audio_format.IsValid());
if (out_audio_format != in_audio_format) if (IsActive())
state.Close(); state.Close();
} }
...@@ -120,11 +126,10 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src) ...@@ -120,11 +126,10 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src)
{ {
assert(in_audio_format.IsValid()); assert(in_audio_format.IsValid());
if (out_audio_format == in_audio_format) return IsActive()
? state.Convert(src)
/* optimized special case: no-op */ /* optimized special case: no-op */
return src; : src;
return state.Convert(src);
} }
const FilterPlugin convert_filter_plugin = { const FilterPlugin convert_filter_plugin = {
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "config.h" #include "config.h"
#include "FileOutputStream.hxx" #include "FileOutputStream.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include "util/StringFormat.hxx"
FileOutputStream::FileOutputStream(Path _path, Mode _mode) FileOutputStream::FileOutputStream(Path _path, Mode _mode)
:path(_path), mode(_mode) :path(_path), mode(_mode)
...@@ -212,10 +213,9 @@ FileOutputStream::Commit() ...@@ -212,10 +213,9 @@ FileOutputStream::Commit()
unlink(GetPath().c_str()); unlink(GetPath().c_str());
/* hard-link the temporary file to the final path */ /* hard-link the temporary file to the final path */
char fd_path[64]; if (linkat(AT_FDCWD,
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", StringFormat<64>("/proc/self/fd/%d", fd.Get()),
fd.Get()); AT_FDCWD, path.c_str(),
if (linkat(AT_FDCWD, fd_path, AT_FDCWD, path.c_str(),
AT_SYMLINK_FOLLOW) < 0) AT_SYMLINK_FOLLOW) < 0)
throw FormatErrno("Failed to commit %s", throw FormatErrno("Failed to commit %s",
path.c_str()); path.c_str());
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#include "config.h" #include "config.h"
#include "AsyncInputStream.hxx" #include "AsyncInputStream.hxx"
#include "Domain.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "IOThread.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 * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -17,8 +17,21 @@ ...@@ -17,8 +17,21 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "config.h" #ifndef INPUT_ERROR_HXX
#include "Domain.hxx" #define INPUT_ERROR_HXX
#include "util/Domain.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 @@ ...@@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "InputStream.hxx" #include "InputStream.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include <stdexcept> #include <stdexcept>
...@@ -77,8 +77,8 @@ gcc_pure ...@@ -77,8 +77,8 @@ gcc_pure
static bool static bool
ExpensiveSeeking(const char *uri) noexcept ExpensiveSeeking(const char *uri) noexcept
{ {
return StringStartsWith(uri, "http://") || return StringStartsWithCaseASCII(uri, "http://") ||
StringStartsWith(uri, "https://"); StringStartsWithCaseASCII(uri, "https://");
} }
bool bool
......
...@@ -29,4 +29,10 @@ ...@@ -29,4 +29,10 @@
*/ */
typedef uint64_t offset_type; typedef uint64_t offset_type;
/**
* To format an offset_type with printf(). To use this, include
* <cinttypes>.
*/
#define PRIoffset PRIu64
#endif #endif
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
#include "Registry.hxx" #include "Registry.hxx"
#include "InputPlugin.hxx" #include "InputPlugin.hxx"
#include "LocalOpen.hxx" #include "LocalOpen.hxx"
#include "Domain.hxx"
#include "plugins/RewindInputPlugin.hxx" #include "plugins/RewindInputPlugin.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
......
...@@ -26,8 +26,12 @@ ...@@ -26,8 +26,12 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
ThreadInputStream::~ThreadInputStream() void
ThreadInputStream::Stop() noexcept
{ {
if (!thread.IsDefined())
return;
{ {
const std::lock_guard<Mutex> lock(mutex); const std::lock_guard<Mutex> lock(mutex);
close = true; close = true;
...@@ -42,6 +46,7 @@ ThreadInputStream::~ThreadInputStream() ...@@ -42,6 +46,7 @@ ThreadInputStream::~ThreadInputStream()
buffer->Clear(); buffer->Clear();
HugeFree(buffer->Write().data, buffer_size); HugeFree(buffer->Write().data, buffer_size);
delete buffer; delete buffer;
buffer = nullptr;
} }
} }
...@@ -54,10 +59,10 @@ ThreadInputStream::Start() ...@@ -54,10 +59,10 @@ ThreadInputStream::Start()
assert(p != nullptr); assert(p != nullptr);
buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size); buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
thread.Start(ThreadFunc, this); thread.Start();
} }
inline void void
ThreadInputStream::ThreadFunc() ThreadInputStream::ThreadFunc()
{ {
FormatThreadName("input:%s", plugin); FormatThreadName("input:%s", plugin);
...@@ -68,7 +73,7 @@ ThreadInputStream::ThreadFunc() ...@@ -68,7 +73,7 @@ ThreadInputStream::ThreadFunc()
Open(); Open();
} catch (...) { } catch (...) {
postponed_exception = std::current_exception(); postponed_exception = std::current_exception();
cond.broadcast(); SetReady();
return; return;
} }
...@@ -108,13 +113,6 @@ ThreadInputStream::ThreadFunc() ...@@ -108,13 +113,6 @@ ThreadInputStream::ThreadFunc()
} }
void void
ThreadInputStream::ThreadFunc(void *ctx)
{
ThreadInputStream &tis = *(ThreadInputStream *)ctx;
tis.ThreadFunc();
}
void
ThreadInputStream::Check() ThreadInputStream::Check()
{ {
assert(!thread.IsInside()); assert(!thread.IsInside());
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <exception> #include <exception>
#include <assert.h>
#include <stdint.h> #include <stdint.h>
template<typename T> class CircularBuffer; template<typename T> class CircularBuffer;
...@@ -39,6 +40,11 @@ template<typename T> class CircularBuffer; ...@@ -39,6 +40,11 @@ template<typename T> class CircularBuffer;
* manages the thread and the buffer. * manages the thread and the buffer.
* *
* This works only for "streams": unknown length, no seeking, no tags. * 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 { class ThreadInputStream : public InputStream {
const char *const plugin; const char *const plugin;
...@@ -73,9 +79,16 @@ public: ...@@ -73,9 +79,16 @@ public:
size_t _buffer_size) size_t _buffer_size)
:InputStream(_uri, _mutex, _cond), :InputStream(_uri, _mutex, _cond),
plugin(_plugin), plugin(_plugin),
thread(BIND_THIS_METHOD(ThreadFunc)),
buffer_size(_buffer_size) {} 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. * Initialize the object and start the thread.
...@@ -89,6 +102,12 @@ public: ...@@ -89,6 +102,12 @@ public:
size_t Read(void *ptr, size_t size) override final; size_t Read(void *ptr, size_t size) override final;
protected: 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) { void SetMimeType(const char *_mime) {
assert(thread.IsInside()); assert(thread.IsInside());
...@@ -138,7 +157,6 @@ protected: ...@@ -138,7 +157,6 @@ protected:
private: private:
void ThreadFunc(); void ThreadFunc();
static void ThreadFunc(void *ctx);
}; };
#endif #endif
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/ReusableArray.hxx" #include "util/ReusableArray.hxx"
#include "util/ASCII.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "event/MultiSocketMonitor.hxx" #include "event/MultiSocketMonitor.hxx"
#include "event/DeferredMonitor.hxx" #include "event/DeferredMonitor.hxx"
...@@ -147,7 +147,7 @@ private: ...@@ -147,7 +147,7 @@ private:
inline InputStream * inline InputStream *
AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond) AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
{ {
const char *device = StringAfterPrefix(uri, "alsa://"); const char *device = StringAfterPrefixCaseASCII(uri, "alsa://");
if (device == nullptr) if (device == nullptr)
return nullptr; return nullptr;
...@@ -270,6 +270,12 @@ AlsaInputStream::Recover(int err) ...@@ -270,6 +270,12 @@ AlsaInputStream::Recover(int err)
/* this is no error, so just keep running */ /* this is no error, so just keep running */
err = 0; err = 0;
break; 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 @@ ...@@ -26,7 +26,7 @@
#include "../InputStream.hxx" #include "../InputStream.hxx"
#include "../InputPlugin.hxx" #include "../InputPlugin.hxx"
#include "util/StringUtil.hxx" #include "util/StringUtil.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "system/ByteOrder.hxx" #include "system/ByteOrder.hxx"
...@@ -128,7 +128,7 @@ struct cdio_uri { ...@@ -128,7 +128,7 @@ struct cdio_uri {
static bool static bool
parse_cdio_uri(struct cdio_uri *dest, const char *src) parse_cdio_uri(struct cdio_uri *dest, const char *src)
{ {
if (!StringStartsWith(src, "cdda://")) if (!StringStartsWithCaseASCII(src, "cdda://"))
return false; return false;
src += 7; src += 7;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "CurlInputPlugin.hxx" #include "CurlInputPlugin.hxx"
#include "lib/curl/Error.hxx"
#include "lib/curl/Easy.hxx" #include "lib/curl/Easy.hxx"
#include "lib/curl/Global.hxx" #include "lib/curl/Global.hxx"
#include "lib/curl/Request.hxx" #include "lib/curl/Request.hxx"
...@@ -34,12 +35,15 @@ ...@@ -34,12 +35,15 @@
#include "IOThread.hxx" #include "IOThread.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/StringUtil.hxx" #include "util/StringUtil.hxx"
#include "util/StringFormat.hxx"
#include "util/NumberParser.hxx" #include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "PluginUnavailable.hxx" #include "PluginUnavailable.hxx"
#include <cinttypes>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
...@@ -185,7 +189,9 @@ CurlInputStream::OnHeaders(unsigned status, ...@@ -185,7 +189,9 @@ CurlInputStream::OnHeaders(unsigned status,
assert(!postponed_exception); assert(!postponed_exception);
if (status < 200 || status >= 300) 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); const std::lock_guard<Mutex> protect(mutex);
...@@ -371,13 +377,10 @@ CurlInputStream::InitEasy() ...@@ -371,13 +377,10 @@ CurlInputStream::InitEasy()
if (proxy_port > 0) if (proxy_port > 0)
request->SetOption(CURLOPT_PROXYPORT, (long)proxy_port); request->SetOption(CURLOPT_PROXYPORT, (long)proxy_port);
if (proxy_user != nullptr && proxy_password != nullptr) { if (proxy_user != nullptr && proxy_password != nullptr)
char proxy_auth_str[1024]; request->SetOption(CURLOPT_PROXYUSERPWD,
snprintf(proxy_auth_str, sizeof(proxy_auth_str), StringFormat<1024>("%s:%s", proxy_user,
"%s:%s", proxy_password).c_str());
proxy_user, proxy_password);
request->SetOption(CURLOPT_PROXYUSERPWD, proxy_auth_str);
}
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l); request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l); request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
...@@ -414,16 +417,10 @@ CurlInputStream::SeekInternal(offset_type new_offset) ...@@ -414,16 +417,10 @@ CurlInputStream::SeekInternal(offset_type new_offset)
/* send the "Range" header */ /* send the "Range" header */
if (offset > 0) { if (offset > 0)
char range[32]; request->SetOption(CURLOPT_RANGE,
#ifdef _WIN32 StringFormat<40>("%" PRIoffset "-",
// TODO: what can we use on Windows to format 64 bit? offset).c_str());
sprintf(range, "%lu-", (long)offset);
#else
sprintf(range, "%llu-", (unsigned long long)offset);
#endif
request->SetOption(CURLOPT_RANGE, range);
}
StartRequest(); StartRequest();
} }
...@@ -461,8 +458,8 @@ CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond) ...@@ -461,8 +458,8 @@ CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond)
static InputStream * static InputStream *
input_curl_open(const char *url, Mutex &mutex, Cond &cond) input_curl_open(const char *url, Mutex &mutex, Cond &cond)
{ {
if (strncmp(url, "http://", 7) != 0 && if (!StringStartsWithCaseASCII(url, "http://") &&
strncmp(url, "https://", 8) != 0) !StringStartsWithCaseASCII(url, "https://"))
return nullptr; return nullptr;
return CurlInputStream::Open(url, mutex, cond); return CurlInputStream::Open(url, mutex, cond);
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
#include "../InputStream.hxx" #include "../InputStream.hxx"
#include "../InputPlugin.hxx" #include "../InputPlugin.hxx"
#include "PluginUnavailable.hxx" #include "PluginUnavailable.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
extern "C" { extern "C" {
#include <libavformat/avio.h> #include <libavformat/avio.h>
...@@ -85,12 +85,12 @@ static InputStream * ...@@ -85,12 +85,12 @@ static InputStream *
input_ffmpeg_open(const char *uri, input_ffmpeg_open(const char *uri,
Mutex &mutex, Cond &cond) Mutex &mutex, Cond &cond)
{ {
if (!StringStartsWith(uri, "gopher://") && if (!StringStartsWithCaseASCII(uri, "gopher://") &&
!StringStartsWith(uri, "rtp://") && !StringStartsWithCaseASCII(uri, "rtp://") &&
!StringStartsWith(uri, "rtsp://") && !StringStartsWithCaseASCII(uri, "rtsp://") &&
!StringStartsWith(uri, "rtmp://") && !StringStartsWithCaseASCII(uri, "rtmp://") &&
!StringStartsWith(uri, "rtmpt://") && !StringStartsWithCaseASCII(uri, "rtmpt://") &&
!StringStartsWith(uri, "rtmps://")) !StringStartsWithCaseASCII(uri, "rtmps://"))
return nullptr; return nullptr;
AVIOContext *h; AVIOContext *h;
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
#include "input/ThreadInputStream.hxx" #include "input/ThreadInputStream.hxx"
#include "input/InputPlugin.hxx" #include "input/InputPlugin.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include <libmms/mmsx.h> #include <libmms/mmsx.h>
...@@ -39,6 +39,10 @@ public: ...@@ -39,6 +39,10 @@ public:
MMS_BUFFER_SIZE) { MMS_BUFFER_SIZE) {
} }
~MmsInputStream() noexcept override {
Stop();
}
protected: protected:
virtual void Open() override; virtual void Open() override;
virtual size_t ThreadRead(void *ptr, size_t size) override; virtual size_t ThreadRead(void *ptr, size_t size) override;
...@@ -70,10 +74,10 @@ static InputStream * ...@@ -70,10 +74,10 @@ static InputStream *
input_mms_open(const char *url, input_mms_open(const char *url,
Mutex &mutex, Cond &cond) Mutex &mutex, Cond &cond)
{ {
if (!StringStartsWith(url, "mms://") && if (!StringStartsWithCaseASCII(url, "mms://") &&
!StringStartsWith(url, "mmsh://") && !StringStartsWithCaseASCII(url, "mmsh://") &&
!StringStartsWith(url, "mmst://") && !StringStartsWithCaseASCII(url, "mmst://") &&
!StringStartsWith(url, "mmsu://")) !StringStartsWithCaseASCII(url, "mmsu://"))
return nullptr; return nullptr;
auto m = new MmsInputStream(url, mutex, cond); auto m = new MmsInputStream(url, mutex, cond);
......
...@@ -23,9 +23,7 @@ ...@@ -23,9 +23,7 @@
#include "../InputPlugin.hxx" #include "../InputPlugin.hxx"
#include "lib/nfs/Glue.hxx" #include "lib/nfs/Glue.hxx"
#include "lib/nfs/FileReader.hxx" #include "lib/nfs/FileReader.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include <string.h>
/** /**
* Do not buffer more than this number of bytes. It should be a * Do not buffer more than this number of bytes. It should be a
...@@ -219,7 +217,7 @@ static InputStream * ...@@ -219,7 +217,7 @@ static InputStream *
input_nfs_open(const char *uri, input_nfs_open(const char *uri,
Mutex &mutex, Cond &cond) Mutex &mutex, Cond &cond)
{ {
if (!StringStartsWith(uri, "nfs://")) if (!StringStartsWithCaseASCII(uri, "nfs://"))
return nullptr; return nullptr;
NfsInputStream *is = new NfsInputStream(uri, mutex, cond); NfsInputStream *is = new NfsInputStream(uri, mutex, cond);
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#include "../InputPlugin.hxx" #include "../InputPlugin.hxx"
#include "PluginUnavailable.hxx" #include "PluginUnavailable.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include <libsmbclient.h> #include <libsmbclient.h>
...@@ -87,7 +87,7 @@ static InputStream * ...@@ -87,7 +87,7 @@ static InputStream *
input_smbclient_open(const char *uri, input_smbclient_open(const char *uri,
Mutex &mutex, Cond &cond) Mutex &mutex, Cond &cond)
{ {
if (!StringStartsWith(uri, "smb://")) if (!StringStartsWithCaseASCII(uri, "smb://"))
return nullptr; return nullptr;
const std::lock_guard<Mutex> protect(smbclient_mutex); 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, ...@@ -62,6 +62,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
easy.SetOption(CURLOPT_NOPROGRESS, 1l); easy.SetOption(CURLOPT_NOPROGRESS, 1l);
easy.SetOption(CURLOPT_NOSIGNAL, 1l); easy.SetOption(CURLOPT_NOSIGNAL, 1l);
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l); easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
easy.SetOption(CURLOPT_URL, url); 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() ...@@ -33,6 +33,9 @@ FfmpegInit()
{ {
av_log_set_callback(FfmpegLogCallback); av_log_set_callback(FfmpegLogCallback);
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
/* deprecated as of FFmpeg 4.0 */
av_register_all(); av_register_all();
#endif
} }
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "Domain.hxx" #include "Domain.hxx"
#include "LogV.hxx" #include "LogV.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/StringFormat.hxx"
extern "C" { extern "C" {
#include <libavutil/log.h> #include <libavutil/log.h>
...@@ -57,9 +58,10 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl) ...@@ -57,9 +58,10 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
cls = *(const AVClass *const*)ptr; cls = *(const AVClass *const*)ptr;
if (cls != nullptr) { if (cls != nullptr) {
char domain[64]; const auto domain =
snprintf(domain, sizeof(domain), "%s/%s", StringFormat<64>("%s/%s",
ffmpeg_domain.GetName(), cls->item_name(ptr)); ffmpeg_domain.GetName(),
cls->item_name(ptr));
const Domain d(domain); const Domain d(domain);
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl); LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
} }
......
...@@ -37,6 +37,18 @@ public: ...@@ -37,6 +37,18 @@ public:
explicit IcuCompare(const char *needle) noexcept; 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(IcuCompare &&) = default;
IcuCompare &operator=(IcuCompare &&) = default; IcuCompare &operator=(IcuCompare &&) = default;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "Connection.hxx" #include "Connection.hxx"
#include "Error.hxx"
#include "Lease.hxx" #include "Lease.hxx"
#include "Callback.hxx" #include "Callback.hxx"
#include "event/Loop.hxx" #include "event/Loop.hxx"
...@@ -31,7 +32,11 @@ extern "C" { ...@@ -31,7 +32,11 @@ extern "C" {
#include <utility> #include <utility>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <poll.h> /* for POLLIN, POLLOUT */ #include <poll.h> /* for POLLIN, POLLOUT */
#endif
static constexpr std::chrono::steady_clock::duration NFS_MOUNT_TIMEOUT = static constexpr std::chrono::steady_clock::duration NFS_MOUNT_TIMEOUT =
std::chrono::minutes(1); std::chrono::minutes(1);
...@@ -135,7 +140,7 @@ NfsConnection::CancellableCallback::Callback(int err, void *data) ...@@ -135,7 +140,7 @@ NfsConnection::CancellableCallback::Callback(int err, void *data)
if (err >= 0) if (err >= 0)
cb.OnNfsCallback((unsigned)err, data); cb.OnNfsCallback((unsigned)err, data);
else 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 { } else {
if (open) { if (open) {
/* a nfs_open_async() call was cancelled - to /* 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 @@ ...@@ -24,14 +24,13 @@
#include "Connection.hxx" #include "Connection.hxx"
#include "event/Call.hxx" #include "event/Call.hxx"
#include "IOThread.hxx" #include "IOThread.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include <utility> #include <utility>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h>
NfsFileReader::NfsFileReader() NfsFileReader::NfsFileReader()
:DeferredMonitor(io_thread_get()), state(State::INITIAL) :DeferredMonitor(io_thread_get()), state(State::INITIAL)
...@@ -94,7 +93,7 @@ NfsFileReader::Open(const char *uri) ...@@ -94,7 +93,7 @@ NfsFileReader::Open(const char *uri)
{ {
assert(state == State::INITIAL); assert(state == State::INITIAL);
if (!StringStartsWith(uri, "nfs://")) if (!StringStartsWithCaseASCII(uri, "nfs://"))
throw std::runtime_error("Malformed nfs:// URI"); throw std::runtime_error("Malformed nfs:// URI");
uri += 6; uri += 6;
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <sys/stat.h>
struct nfsfh; struct nfsfh;
class NfsConnection; class NfsConnection;
......
...@@ -34,7 +34,11 @@ static unsigned upnp_ref; ...@@ -34,7 +34,11 @@ static unsigned upnp_ref;
static void static void
DoInit() 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) if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpInit() failed: %s", throw FormatRuntimeError("UpnpInit() failed: %s",
UpnpGetErrorMessage(code)); UpnpGetErrorMessage(code));
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "ls.hxx" #include "ls.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "util/StringCompare.hxx" #include "util/ASCII.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include <assert.h> #include <assert.h>
...@@ -97,7 +97,7 @@ uri_supported_scheme(const char *uri) noexcept ...@@ -97,7 +97,7 @@ uri_supported_scheme(const char *uri) noexcept
assert(uri_has_scheme(uri)); assert(uri_has_scheme(uri));
while (*urlPrefixes) { while (*urlPrefixes) {
if (StringStartsWith(uri, *urlPrefixes)) if (StringStartsWithCaseASCII(uri, *urlPrefixes))
return true; return true;
urlPrefixes++; urlPrefixes++;
} }
......
...@@ -139,6 +139,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem, ...@@ -139,6 +139,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
return set_raw[ctl_dir](elem, value); 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)) { if (use_linear_dB_scale(min, max)) {
value = lrint_dir(volume * (max - min), dir) + min; value = lrint_dir(volume * (max - min), dir) + min;
return set_dB[ctl_dir](elem, value, dir); return set_dB[ctl_dir](elem, value, dir);
......
...@@ -69,7 +69,8 @@ class SmbclientNeighborExplorer final : public NeighborExplorer { ...@@ -69,7 +69,8 @@ class SmbclientNeighborExplorer final : public NeighborExplorer {
public: public:
SmbclientNeighborExplorer(NeighborListener &_listener) SmbclientNeighborExplorer(NeighborListener &_listener)
:NeighborExplorer(_listener) {} :NeighborExplorer(_listener),
thread(BIND_THIS_METHOD(ThreadFunc)) {}
/* virtual methods from class NeighborExplorer */ /* virtual methods from class NeighborExplorer */
void Open() override; void Open() override;
...@@ -79,14 +80,13 @@ public: ...@@ -79,14 +80,13 @@ public:
private: private:
void Run(); void Run();
void ThreadFunc(); void ThreadFunc();
static void ThreadFunc(void *ctx);
}; };
void void
SmbclientNeighborExplorer::Open() SmbclientNeighborExplorer::Open()
{ {
quit = false; quit = false;
thread.Start(ThreadFunc, this); thread.Start();
} }
void void
...@@ -239,6 +239,8 @@ SmbclientNeighborExplorer::Run() ...@@ -239,6 +239,8 @@ SmbclientNeighborExplorer::Run()
inline void inline void
SmbclientNeighborExplorer::ThreadFunc() SmbclientNeighborExplorer::ThreadFunc()
{ {
SetThreadName("smbclient");
mutex.lock(); mutex.lock();
while (!quit) { while (!quit) {
...@@ -257,15 +259,6 @@ SmbclientNeighborExplorer::ThreadFunc() ...@@ -257,15 +259,6 @@ SmbclientNeighborExplorer::ThreadFunc()
mutex.unlock(); mutex.unlock();
} }
void
SmbclientNeighborExplorer::ThreadFunc(void *ctx)
{
SetThreadName("smbclient");
SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
e.ThreadFunc();
}
static NeighborExplorer * static NeighborExplorer *
smbclient_neighbor_create(gcc_unused EventLoop &loop, smbclient_neighbor_create(gcc_unused EventLoop &loop,
NeighborListener &listener, 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 @@ ...@@ -37,6 +37,7 @@
#include "config/ConfigGlobal.hxx" #include "config/ConfigGlobal.hxx"
#include "config/Block.hxx" #include "config/Block.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringFormat.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <stdexcept> #include <stdexcept>
...@@ -51,7 +52,8 @@ ...@@ -51,7 +52,8 @@
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin, AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
const ConfigBlock &block) const ConfigBlock &block)
:plugin(_plugin) :plugin(_plugin),
thread(BIND_THIS_METHOD(Task))
{ {
assert(plugin.finish != nullptr); assert(plugin.finish != nullptr);
assert(plugin.open != nullptr); assert(plugin.open != nullptr);
......
...@@ -515,7 +515,6 @@ private: ...@@ -515,7 +515,6 @@ private:
* The OutputThread. * The OutputThread.
*/ */
void Task(); void Task();
static void Task(void *arg);
}; };
/** /**
......
...@@ -396,7 +396,7 @@ AudioOutput::Pause() ...@@ -396,7 +396,7 @@ AudioOutput::Pause()
pause = false; pause = false;
} }
inline void void
AudioOutput::Task() AudioOutput::Task()
{ {
FormatThreadName("output:%s", name); FormatThreadName("output:%s", name);
...@@ -513,16 +513,9 @@ AudioOutput::Task() ...@@ -513,16 +513,9 @@ AudioOutput::Task()
} }
void void
AudioOutput::Task(void *arg)
{
AudioOutput *ao = (AudioOutput *)arg;
ao->Task();
}
void
AudioOutput::StartThread() AudioOutput::StartThread()
{ {
assert(command == Command::NONE); assert(command == Command::NONE);
thread.Start(Task, this); thread.Start();
} }
...@@ -846,6 +846,12 @@ AlsaOutput::Recover(int err) ...@@ -846,6 +846,12 @@ AlsaOutput::Recover(int err)
case SND_PCM_STATE_DRAINING: case SND_PCM_STATE_DRAINING:
err = 0; err = 0;
break; 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; return err;
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "../Wrapper.hxx" #include "../Wrapper.hxx"
#include "mixer/MixerList.hxx" #include "mixer/MixerList.hxx"
#include "mixer/plugins/PulseMixerPlugin.hxx" #include "mixer/plugins/PulseMixerPlugin.hxx"
#include "util/ScopeExit.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <pulse/thread-mainloop.h> #include <pulse/thread-mainloop.h>
...@@ -854,7 +855,10 @@ PulseOutput::TestDefaultDevice() ...@@ -854,7 +855,10 @@ PulseOutput::TestDefaultDevice()
try { try {
const ConfigBlock empty; const ConfigBlock empty;
PulseOutput po(empty); PulseOutput po(empty);
po.Enable();
AtScopeExit(&po) { po.Disable(); };
po.WaitConnection(); po.WaitConnection();
return true; return true;
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
return false; return false;
......
...@@ -36,6 +36,8 @@ ...@@ -36,6 +36,8 @@
#include <roaraudio.h> #include <roaraudio.h>
#undef new #undef new
#include <assert.h>
class RoarOutput { class RoarOutput {
friend struct AudioOutputWrapper<RoarOutput>; friend struct AudioOutputWrapper<RoarOutput>;
......
...@@ -122,15 +122,6 @@ HttpdClient::HandleLine(const char *line) ...@@ -122,15 +122,6 @@ HttpdClient::HandleLine(const char *line)
return true; 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 */ /* expect more request headers */
return true; return true;
} }
...@@ -148,22 +139,7 @@ HttpdClient::SendResponse() ...@@ -148,22 +139,7 @@ HttpdClient::SendResponse()
assert(state == RESPONSE); assert(state == RESPONSE);
if (dlna_streaming_requested) { if (metadata_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) {
allocated = allocated =
icy_server_metadata_header(httpd.name, httpd.genre, icy_server_metadata_header(httpd.name, httpd.genre,
httpd.website, httpd.website,
...@@ -202,7 +178,6 @@ HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop, ...@@ -202,7 +178,6 @@ HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
state(REQUEST), state(REQUEST),
queue_size(0), queue_size(0),
head_method(false), head_method(false),
dlna_streaming_requested(false),
metadata_supported(_metadata_supported), metadata_supported(_metadata_supported),
metadata_requested(false), metadata_sent(true), metadata_requested(false), metadata_sent(true),
metaint(8192), /*TODO: just a std value */ metaint(8192), /*TODO: just a std value */
......
...@@ -82,11 +82,6 @@ class HttpdClient final ...@@ -82,11 +82,6 @@ class HttpdClient final
*/ */
bool head_method; bool head_method;
/**
* If DLNA streaming was an option.
*/
bool dlna_streaming_requested;
/* ICY */ /* ICY */
/** /**
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "SoxrResampler.hxx" #include "SoxrResampler.hxx"
#endif #endif
#include <assert.h>
#include <string.h> #include <string.h>
enum class SelectedResampler { enum class SelectedResampler {
......
...@@ -88,7 +88,7 @@ static inline ConstBuffer<V> ...@@ -88,7 +88,7 @@ static inline ConstBuffer<V>
ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer<V> src) ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer<V> src)
{ {
auto dest = buffer.GetT<V>(src.size); 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 }; return { dest, src.size };
} }
......
...@@ -46,19 +46,20 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels, ...@@ -46,19 +46,20 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
assert(audio_valid_channel_count(channels)); assert(audio_valid_channel_count(channels));
assert(_src.size % channels == 0); assert(_src.size % channels == 0);
const unsigned num_src_samples = _src.size; const size_t num_src_samples = _src.size;
const unsigned num_src_frames = num_src_samples / channels; 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 */ elegant, but good enough for now */
const unsigned num_frames = num_src_frames / 2; const size_t num_dop_quads = num_src_frames / 4;
const unsigned num_samples = num_frames * channels; 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), uint32_t *const dest0 = (uint32_t *)buffer.GetT<uint32_t>(num_samples),
*dest = dest0; *dest = dest0;
auto src = _src.data; 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) { for (unsigned c = channels; c > 0; --c) {
/* each 24 bit sample has 16 DSD sample bits /* each 24 bit sample has 16 DSD sample bits
plus the magic 0x05 marker */ plus the magic 0x05 marker */
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
#include "PcmDop.hxx" #include "PcmDop.hxx"
#endif #endif
#include <assert.h>
void void
PcmExport::Open(SampleFormat sample_format, unsigned _channels, PcmExport::Open(SampleFormat sample_format, unsigned _channels,
Params params) Params params)
......
...@@ -37,6 +37,7 @@ PlayerControl::PlayerControl(PlayerListener &_listener, ...@@ -37,6 +37,7 @@ PlayerControl::PlayerControl(PlayerListener &_listener,
buffer_chunks(_buffer_chunks), buffer_chunks(_buffer_chunks),
buffered_before_play(_buffered_before_play), buffered_before_play(_buffered_before_play),
configured_audio_format(_configured_audio_format), configured_audio_format(_configured_audio_format),
thread(BIND_THIS_METHOD(RunThread)),
replay_gain_config(_replay_gain_config) replay_gain_config(_replay_gain_config)
{ {
} }
......
...@@ -537,6 +537,9 @@ public: ...@@ -537,6 +537,9 @@ public:
void ApplyEnabled() override { void ApplyEnabled() override {
LockUpdateAudio(); LockUpdateAudio();
} }
private:
void RunThread();
}; };
#endif #endif
...@@ -584,7 +584,7 @@ Player::SeekDecoder() ...@@ -584,7 +584,7 @@ Player::SeekDecoder()
const SongTime start_time = pc.next_song->GetStartTime(); const SongTime start_time = pc.next_song->GetStartTime();
if (!dc.LockIsCurrentSong(*pc.next_song)) { if (!dc.LockIsSeeakbleCurrentSong(*pc.next_song)) {
/* the decoder is already decoding the "next" song - /* the decoder is already decoding the "next" song -
stop it and start the previous song again */ stop it and start the previous song again */
...@@ -950,6 +950,7 @@ Player::SongBorder() ...@@ -950,6 +950,7 @@ Player::SongBorder()
const bool border_pause = pc.LockApplyBorderPause(); const bool border_pause = pc.LockApplyBorderPause();
if (border_pause) { if (border_pause) {
paused = true; paused = true;
pc.outputs.Pause();
idle_add(IDLE_PLAYER); idle_add(IDLE_PLAYER);
} }
} }
...@@ -1147,91 +1148,89 @@ do_play(PlayerControl &pc, DecoderControl &dc, ...@@ -1147,91 +1148,89 @@ do_play(PlayerControl &pc, DecoderControl &dc,
player.Run(); player.Run();
} }
static void void
player_task(void *arg) PlayerControl::RunThread()
{ {
PlayerControl &pc = *(PlayerControl *)arg;
SetThreadName("player"); SetThreadName("player");
DecoderControl dc(pc.mutex, pc.cond, DecoderControl dc(mutex, cond,
pc.configured_audio_format, configured_audio_format,
pc.replay_gain_config); replay_gain_config);
decoder_thread_start(dc); decoder_thread_start(dc);
MusicBuffer buffer(pc.buffer_chunks); MusicBuffer buffer(buffer_chunks);
pc.Lock(); Lock();
while (1) { while (1) {
switch (pc.command) { switch (command) {
case PlayerCommand::SEEK: case PlayerCommand::SEEK:
case PlayerCommand::QUEUE: case PlayerCommand::QUEUE:
assert(pc.next_song != nullptr); assert(next_song != nullptr);
pc.Unlock(); Unlock();
do_play(pc, dc, buffer); do_play(*this, dc, buffer);
pc.listener.OnPlayerSync(); listener.OnPlayerSync();
pc.Lock(); Lock();
break; break;
case PlayerCommand::STOP: case PlayerCommand::STOP:
pc.Unlock(); Unlock();
pc.outputs.Cancel(); outputs.Cancel();
pc.Lock(); Lock();
/* fall through */ /* fall through */
case PlayerCommand::PAUSE: case PlayerCommand::PAUSE:
delete pc.next_song; delete next_song;
pc.next_song = nullptr; next_song = nullptr;
pc.CommandFinished(); CommandFinished();
break; break;
case PlayerCommand::CLOSE_AUDIO: case PlayerCommand::CLOSE_AUDIO:
pc.Unlock(); Unlock();
pc.outputs.Release(); outputs.Release();
pc.Lock(); Lock();
pc.CommandFinished(); CommandFinished();
assert(buffer.IsEmptyUnsafe()); assert(buffer.IsEmptyUnsafe());
break; break;
case PlayerCommand::UPDATE_AUDIO: case PlayerCommand::UPDATE_AUDIO:
pc.Unlock(); Unlock();
pc.outputs.EnableDisable(); outputs.EnableDisable();
pc.Lock(); Lock();
pc.CommandFinished(); CommandFinished();
break; break;
case PlayerCommand::EXIT: case PlayerCommand::EXIT:
pc.Unlock(); Unlock();
dc.Quit(); dc.Quit();
pc.outputs.Close(); outputs.Close();
pc.LockCommandFinished(); LockCommandFinished();
return; return;
case PlayerCommand::CANCEL: case PlayerCommand::CANCEL:
delete pc.next_song; delete next_song;
pc.next_song = nullptr; next_song = nullptr;
pc.CommandFinished(); CommandFinished();
break; break;
case PlayerCommand::REFRESH: case PlayerCommand::REFRESH:
/* no-op when not playing */ /* no-op when not playing */
pc.CommandFinished(); CommandFinished();
break; break;
case PlayerCommand::NONE: case PlayerCommand::NONE:
pc.Wait(); Wait();
break; break;
} }
} }
...@@ -1242,5 +1241,5 @@ StartPlayerThread(PlayerControl &pc) ...@@ -1242,5 +1241,5 @@ StartPlayerThread(PlayerControl &pc)
{ {
assert(!pc.thread.IsDefined()); assert(!pc.thread.IsDefined());
pc.thread.Start(player_task, &pc); pc.thread.Start();
} }
...@@ -202,6 +202,7 @@ CueParser::Feed2(char *p) noexcept ...@@ -202,6 +202,7 @@ CueParser::Feed2(char *p) noexcept
return; return;
if (strcmp(type, "WAVE") != 0 && if (strcmp(type, "WAVE") != 0 &&
strcmp(type, "FLAC") != 0 && /* non-standard */
strcmp(type, "MP3") != 0 && strcmp(type, "MP3") != 0 &&
strcmp(type, "AIFF") != 0) { strcmp(type, "AIFF") != 0) {
state = IGNORE_FILE; state = IGNORE_FILE;
...@@ -229,6 +230,8 @@ CueParser::Feed2(char *p) noexcept ...@@ -229,6 +230,8 @@ CueParser::Feed2(char *p) noexcept
} }
state = TRACK; state = TRACK;
ignore_index = false;
current.reset(new DetachedSong(filename)); current.reset(new DetachedSong(filename));
assert(!current->GetTag().IsDefined()); assert(!current->GetTag().IsDefined());
...@@ -238,6 +241,9 @@ CueParser::Feed2(char *p) noexcept ...@@ -238,6 +241,9 @@ CueParser::Feed2(char *p) noexcept
} else if (state == IGNORE_TRACK) { } else if (state == IGNORE_TRACK) {
return; return;
} else if (state == TRACK && strcmp(command, "INDEX") == 0) { } else if (state == TRACK && strcmp(command, "INDEX") == 0) {
if (ignore_index)
return;
const char *nr = cue_next_token(&p); const char *nr = cue_next_token(&p);
if (nr == nullptr) if (nr == nullptr)
return; return;
...@@ -255,7 +261,7 @@ CueParser::Feed2(char *p) noexcept ...@@ -255,7 +261,7 @@ CueParser::Feed2(char *p) noexcept
current->SetStartTime(SongTime::FromMS(position_ms)); current->SetStartTime(SongTime::FromMS(position_ms));
if(strcmp(nr, "00") != 0 || previous == nullptr) if(strcmp(nr, "00") != 0 || previous == nullptr)
state = IGNORE_TRACK; ignore_index = true;
} }
} }
......
...@@ -88,6 +88,13 @@ class CueParser { ...@@ -88,6 +88,13 @@ class CueParser {
std::unique_ptr<DetachedSong> finished; std::unique_ptr<DetachedSong> finished;
/** /**
* Ignore "INDEX" lines? Only up the first one after "00" is
* used. If there is a pregap (INDEX 00..01), it is assigned
* to the previous song.
*/
bool ignore_index;
/**
* Tracks whether Finish() has been called. If true, then all * Tracks whether Finish() has been called. If true, then all
* remaining (partial) results will be delivered by Get(). * remaining (partial) results will be delivered by Get().
*/ */
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "config/Block.hxx" #include "config/Block.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "tag/TagBuilder.hxx" #include "tag/TagBuilder.hxx"
#include "util/ASCII.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/Alloc.hxx" #include "util/Alloc.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
...@@ -68,7 +69,7 @@ soundcloud_resolve(const char* uri) ...@@ -68,7 +69,7 @@ soundcloud_resolve(const char* uri)
{ {
char *u, *ru; char *u, *ru;
if (StringStartsWith(uri, "https://")) { if (StringStartsWithCaseASCII(uri, "https://")) {
u = xstrdup(uri); u = xstrdup(uri);
} else if (StringStartsWith(uri, "soundcloud.com")) { } else if (StringStartsWith(uri, "soundcloud.com")) {
u = xstrcatdup("https://", uri); u = xstrcatdup("https://", uri);
...@@ -273,7 +274,7 @@ try { ...@@ -273,7 +274,7 @@ try {
static SongEnumerator * static SongEnumerator *
soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
{ {
assert(strncmp(uri, "soundcloud://", 13) == 0); assert(StringEqualsCaseASCII(uri, "soundcloud://", 13));
uri += 13; uri += 13;
char *u = nullptr; char *u = nullptr;
...@@ -313,7 +314,7 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) ...@@ -313,7 +314,7 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
SoundCloudJsonData data; SoundCloudJsonData data;
yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data); yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
AtScopeExit(hand, &data) { yajl_free(hand); }; AtScopeExit(hand) { yajl_free(hand); };
int ret = soundcloud_parse_json(u, hand, mutex, cond); int ret = soundcloud_parse_json(u, hand, mutex, cond);
......
...@@ -20,9 +20,9 @@ ...@@ -20,9 +20,9 @@
#ifndef MPD_ACK_H #ifndef MPD_ACK_H
#define MPD_ACK_H #define MPD_ACK_H
#include <stdexcept> #include "util/StringFormat.hxx"
#include <stdio.h> #include <stdexcept>
class Domain; class Domain;
...@@ -60,9 +60,9 @@ template<typename... Args> ...@@ -60,9 +60,9 @@ template<typename... Args>
static inline ProtocolError static inline ProtocolError
FormatProtocolError(enum ack code, const char *fmt, Args&&... args) noexcept FormatProtocolError(enum ack code, const char *fmt, Args&&... args) noexcept
{ {
char buffer[256]; return ProtocolError(code,
snprintf(buffer, sizeof(buffer), fmt, std::forward<Args>(args)...); StringFormat<256>(fmt,
return ProtocolError(code, buffer); std::forward<Args>(args)...));
} }
#endif #endif
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "ArgParser.hxx" #include "ArgParser.hxx"
#include "Ack.hxx" #include "Ack.hxx"
#include "Chrono.hxx" #include "Chrono.hxx"
#include "util/NumberParser.hxx"
#include <stdlib.h> #include <stdlib.h>
...@@ -151,7 +152,7 @@ float ...@@ -151,7 +152,7 @@ float
ParseCommandArgFloat(const char *s) ParseCommandArgFloat(const char *s)
{ {
char *endptr; char *endptr;
auto value = strtof(s, &endptr); auto value = ParseFloat(s, &endptr);
if (endptr == s || *endptr != 0) if (endptr == s || *endptr != 0)
throw FormatProtocolError(ACK_ERROR_ARG, throw FormatProtocolError(ACK_ERROR_ARG,
"Float expected: %s", s); "Float expected: %s", s);
...@@ -163,6 +164,10 @@ SongTime ...@@ -163,6 +164,10 @@ SongTime
ParseCommandArgSongTime(const char *s) ParseCommandArgSongTime(const char *s)
{ {
auto value = ParseCommandArgFloat(s); auto value = ParseCommandArgFloat(s);
if (value < 0)
throw FormatProtocolError(ACK_ERROR_ARG,
"Negative value not allowed: %s", s);
return SongTime::FromS(value); return SongTime::FromS(value);
} }
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "util/CharUtil.hxx" #include "util/CharUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/NumberParser.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <string.h> #include <string.h>
...@@ -148,7 +149,7 @@ playlist_state_restore(const char *line, TextFile &file, ...@@ -148,7 +149,7 @@ playlist_state_restore(const char *line, TextFile &file,
while ((line = file.ReadLine()) != nullptr) { while ((line = file.ReadLine()) != nullptr) {
const char *p; const char *p;
if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_TIME))) { if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_TIME))) {
seek_time = SongTime::FromS(atof(p)); seek_time = SongTime::FromS(ParseDouble(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) {
playlist.SetRepeat(pc, StringIsEqual(p, "1")); playlist.SetRepeat(pc, StringIsEqual(p, "1"));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) {
...@@ -158,12 +159,12 @@ playlist_state_restore(const char *line, TextFile &file, ...@@ -158,12 +159,12 @@ playlist_state_restore(const char *line, TextFile &file,
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) {
pc.SetCrossFade(atoi(p)); pc.SetCrossFade(atoi(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) {
pc.SetMixRampDb(atof(p)); pc.SetMixRampDb(ParseFloat(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) {
/* this check discards "nan" which was used /* this check discards "nan" which was used
prior to MPD 0.18 */ prior to MPD 0.18 */
if (IsDigitASCII(*p)) if (IsDigitASCII(*p))
pc.SetMixRampDelay(atof(p)); pc.SetMixRampDelay(ParseFloat(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) {
random_mode = StringIsEqual(p, "1"); random_mode = StringIsEqual(p, "1");
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) {
......
...@@ -33,8 +33,10 @@ ...@@ -33,8 +33,10 @@
#include "event/DeferredMonitor.hxx" #include "event/DeferredMonitor.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "util/ASCII.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/StringFormat.hxx"
#include "util/TimeParser.hxx" #include "util/TimeParser.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
...@@ -296,9 +298,7 @@ public: ...@@ -296,9 +298,7 @@ public:
{ {
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND"); request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
char buffer[40]; request_headers.Append(StringFormat<40>("depth: %u", depth));
sprintf(buffer, "depth: %u", depth);
request_headers.Append(buffer);
request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get()); request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
...@@ -591,8 +591,8 @@ CurlStorage::OpenDirectory(const char *uri_utf8) ...@@ -591,8 +591,8 @@ CurlStorage::OpenDirectory(const char *uri_utf8)
static Storage * static Storage *
CreateCurlStorageURI(EventLoop &event_loop, const char *uri) CreateCurlStorageURI(EventLoop &event_loop, const char *uri)
{ {
if (strncmp(uri, "http://", 7) != 0 && if (!StringStartsWithCaseASCII(uri, "http://") &&
strncmp(uri, "https://", 8) != 0) !StringStartsWithCaseASCII(uri, "https://"))
return nullptr; return nullptr;
return new CurlStorage(event_loop, uri); return new CurlStorage(event_loop, uri);
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "event/Call.hxx" #include "event/Call.hxx"
#include "event/DeferredMonitor.hxx" #include "event/DeferredMonitor.hxx"
#include "event/TimeoutMonitor.hxx" #include "event/TimeoutMonitor.hxx"
#include "util/ASCII.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
extern "C" { extern "C" {
...@@ -219,7 +220,12 @@ UriToNfsPath(const char *_uri_utf8) ...@@ -219,7 +220,12 @@ UriToNfsPath(const char *_uri_utf8)
std::string uri_utf8("/"); std::string uri_utf8("/");
uri_utf8.append(_uri_utf8); uri_utf8.append(_uri_utf8);
#ifdef _WIN32
/* assume UTF-8 when accessing NFS from Windows */
return uri_utf8;
#else
return AllocatedPath::FromUTF8Throw(uri_utf8.c_str()).Steal(); return AllocatedPath::FromUTF8Throw(uri_utf8.c_str()).Steal();
#endif
} }
std::string std::string
...@@ -291,7 +297,7 @@ NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow) ...@@ -291,7 +297,7 @@ NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
gcc_pure gcc_pure
static bool static bool
SkipNameFS(const char *name) noexcept SkipNameFS(PathTraitsFS::const_pointer_type name) noexcept
{ {
return name[0] == '.' && return name[0] == '.' &&
(name[1] == 0 || (name[1] == 0 ||
...@@ -358,7 +364,14 @@ NfsListDirectoryOperation::CollectEntries(struct nfsdir *dir) ...@@ -358,7 +364,14 @@ NfsListDirectoryOperation::CollectEntries(struct nfsdir *dir)
const struct nfsdirent *ent; const struct nfsdirent *ent;
while ((ent = connection.ReadDirectory(dir)) != nullptr) { while ((ent = connection.ReadDirectory(dir)) != nullptr) {
#ifdef _WIN32
/* assume UTF-8 when accessing NFS from Windows */
const auto name_fs = AllocatedPath::FromUTF8Throw(ent->name);
if (name_fs.IsNull())
continue;
#else
const Path name_fs = Path::FromFS(ent->name); const Path name_fs = Path::FromFS(ent->name);
#endif
if (SkipNameFS(name_fs.c_str())) if (SkipNameFS(name_fs.c_str()))
continue; continue;
...@@ -389,11 +402,10 @@ NfsStorage::OpenDirectory(const char *uri_utf8) ...@@ -389,11 +402,10 @@ NfsStorage::OpenDirectory(const char *uri_utf8)
static Storage * static Storage *
CreateNfsStorageURI(EventLoop &event_loop, const char *base) CreateNfsStorageURI(EventLoop &event_loop, const char *base)
{ {
if (strncmp(base, "nfs://", 6) != 0) const char *p = StringAfterPrefixCaseASCII(base, "nfs://");
if (p == nullptr)
return nullptr; return nullptr;
const char *p = base + 6;
const char *mount = strchr(p, '/'); const char *mount = strchr(p, '/');
if (mount == nullptr) if (mount == nullptr)
throw std::runtime_error("Malformed nfs:// URI"); throw std::runtime_error("Malformed nfs:// URI");
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include "util/ASCII.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
...@@ -182,7 +183,7 @@ SmbclientDirectoryReader::GetInfo(gcc_unused bool follow) ...@@ -182,7 +183,7 @@ SmbclientDirectoryReader::GetInfo(gcc_unused bool follow)
static Storage * static Storage *
CreateSmbclientStorageURI(gcc_unused EventLoop &event_loop, const char *base) CreateSmbclientStorageURI(gcc_unused EventLoop &event_loop, const char *base)
{ {
if (strncmp(base, "smb://", 6) != 0) if (!StringStartsWithCaseASCII(base, "smb://"))
return nullptr; return nullptr;
SmbclientInit(); SmbclientInit();
......
...@@ -22,21 +22,6 @@ ...@@ -22,21 +22,6 @@
#include "EPollFD.hxx" #include "EPollFD.hxx"
#include "FatalError.hxx" #include "FatalError.hxx"
#if defined(__BIONIC__) && __ANDROID_API__ < 21
#include <sys/syscall.h>
#include <fcntl.h>
#define EPOLL_CLOEXEC O_CLOEXEC
static inline int
epoll_create1(int flags)
{
return syscall(__NR_epoll_create1, flags);
}
#endif
EPollFD::EPollFD() EPollFD::EPollFD()
:fd(::epoll_create1(EPOLL_CLOEXEC)) :fd(::epoll_create1(EPOLL_CLOEXEC))
{ {
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "VorbisComment.hxx" #include "VorbisComment.hxx"
#include "ReplayGainInfo.hxx" #include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/NumberParser.hxx"
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
...@@ -33,16 +34,16 @@ ParseReplayGainTagTemplate(ReplayGainInfo &info, const T t) ...@@ -33,16 +34,16 @@ ParseReplayGainTagTemplate(ReplayGainInfo &info, const T t)
const char *value; const char *value;
if ((value = t["replaygain_track_gain"]) != nullptr) { if ((value = t["replaygain_track_gain"]) != nullptr) {
info.track.gain = atof(value); info.track.gain = ParseFloat(value);
return true; return true;
} else if ((value = t["replaygain_album_gain"]) != nullptr) { } else if ((value = t["replaygain_album_gain"]) != nullptr) {
info.album.gain = atof(value); info.album.gain = ParseFloat(value);
return true; return true;
} else if ((value = t["replaygain_track_peak"]) != nullptr) { } else if ((value = t["replaygain_track_peak"]) != nullptr) {
info.track.peak = atof(value); info.track.peak = ParseFloat(value);
return true; return true;
} else if ((value = t["replaygain_album_peak"]) != nullptr) { } else if ((value = t["replaygain_album_peak"]) != nullptr) {
info.album.peak = atof(value); info.album.peak = ParseFloat(value);
return true; return true;
} else } else
return false; return false;
......
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
#include "TagHandler.hxx" #include "TagHandler.hxx"
#include "TagBuilder.hxx" #include "TagBuilder.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/StringFormat.hxx"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
static void static void
...@@ -42,11 +42,8 @@ add_tag_tag(TagType type, const char *value, void *ctx) ...@@ -42,11 +42,8 @@ add_tag_tag(TagType type, const char *value, void *ctx)
/* filter out this extra data and leading zeroes */ /* filter out this extra data and leading zeroes */
char *end; char *end;
unsigned n = strtoul(value, &end, 10); unsigned n = strtoul(value, &end, 10);
if (value != end) { if (value != end)
char s[21]; tag.AddItem(type, StringFormat<21>("%u", n));
if (snprintf(s, 21, "%u", n) > 0)
tag.AddItem(type, s);
}
} else } else
tag.AddItem(type, value); tag.AddItem(type, value);
} }
......
...@@ -52,13 +52,11 @@ public: ...@@ -52,13 +52,11 @@ public:
constexpr ThreadId(pthread_t _id):id(_id) {} constexpr ThreadId(pthread_t _id):id(_id) {}
#endif #endif
gcc_const static constexpr ThreadId Null() noexcept {
static ThreadId Null() noexcept {
#ifdef _WIN32 #ifdef _WIN32
return 0; return 0;
#else #else
static ThreadId null; return pthread_t();
return null;
#endif #endif
} }
...@@ -81,11 +79,13 @@ public: ...@@ -81,11 +79,13 @@ public:
gcc_pure gcc_pure
bool operator==(const ThreadId &other) const noexcept { bool operator==(const ThreadId &other) const noexcept {
#ifdef _WIN32 /* note: not using pthread_equal() because that
function "is undefined if either thread ID is not
valid so we can't safely use it on
default-constructed values" (comment from
libstdc++) - and if both libstdc++ and libc++ get
away with this, we can do it as well */
return id == other.id; return id == other.id;
#else
return pthread_equal(id, other.id);
#endif
} }
/** /**
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#endif #endif
#ifdef HAVE_THREAD_NAME #ifdef HAVE_THREAD_NAME
# include <stdio.h> #include "util/StringFormat.hxx"
#endif #endif
static inline void static inline void
...@@ -59,9 +59,7 @@ static inline void ...@@ -59,9 +59,7 @@ static inline void
FormatThreadName(const char *fmt, gcc_unused Args&&... args) FormatThreadName(const char *fmt, gcc_unused Args&&... args)
{ {
#ifdef HAVE_THREAD_NAME #ifdef HAVE_THREAD_NAME
char buffer[16]; SetThreadName(StringFormat<16>(fmt, args...));
snprintf(buffer, sizeof(buffer), fmt, args...);
SetThreadName(buffer);
#else #else
(void)fmt; (void)fmt;
#endif #endif
......
...@@ -25,39 +25,21 @@ ...@@ -25,39 +25,21 @@
#include "java/Global.hxx" #include "java/Global.hxx"
#endif #endif
bool void
Thread::Start(void (*_f)(void *ctx), void *_ctx) Thread::Start()
{ {
assert(!IsDefined()); assert(!IsDefined());
f = _f;
ctx = _ctx;
#ifdef _WIN32 #ifdef _WIN32
handle = ::CreateThread(nullptr, 0, ThreadProc, this, 0, &id); handle = ::CreateThread(nullptr, 0, ThreadProc, this, 0, &id);
if (handle == nullptr) if (handle == nullptr)
throw MakeLastError("Failed to create thread"); throw MakeLastError("Failed to create thread");
#else #else
#ifndef NDEBUG
creating = true;
#endif
int e = pthread_create(&handle, nullptr, ThreadProc, this); int e = pthread_create(&handle, nullptr, ThreadProc, this);
if (e != 0) { if (e != 0)
#ifndef NDEBUG
creating = false;
#endif
throw MakeErrno(e, "Failed to create thread"); throw MakeErrno(e, "Failed to create thread");
}
defined = true;
#ifndef NDEBUG
creating = false;
#endif #endif
#endif
return true;
} }
void void
...@@ -72,7 +54,17 @@ Thread::Join() ...@@ -72,7 +54,17 @@ Thread::Join()
handle = nullptr; handle = nullptr;
#else #else
pthread_join(handle, nullptr); pthread_join(handle, nullptr);
defined = false; handle = pthread_t();
#endif
}
inline void
Thread::Run()
{
f();
#ifdef ANDROID
Java::DetachCurrentThread();
#endif #endif
} }
...@@ -83,7 +75,7 @@ Thread::ThreadProc(LPVOID ctx) ...@@ -83,7 +75,7 @@ Thread::ThreadProc(LPVOID ctx)
{ {
Thread &thread = *(Thread *)ctx; Thread &thread = *(Thread *)ctx;
thread.f(thread.ctx); thread.Run();
return 0; return 0;
} }
...@@ -95,18 +87,10 @@ Thread::ThreadProc(void *ctx) ...@@ -95,18 +87,10 @@ Thread::ThreadProc(void *ctx)
Thread &thread = *(Thread *)ctx; Thread &thread = *(Thread *)ctx;
#ifndef NDEBUG #ifndef NDEBUG
/* this works around a race condition that causes an assertion thread.inside_handle = pthread_self();
failure due to IsInside() spuriously returning false right
after the thread has been created, and the calling thread
hasn't initialised "defined" yet */
thread.defined = true;
#endif #endif
thread.f(thread.ctx); thread.Run();
#ifdef ANDROID
Java::DetachCurrentThread();
#endif
return nullptr; return nullptr;
} }
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#define MPD_THREAD_HXX #define MPD_THREAD_HXX
#include "check.h" #include "check.h"
#include "util/BindMethod.hxx"
#include "Compiler.h" #include "Compiler.h"
#ifdef _WIN32 #ifdef _WIN32
...@@ -32,28 +33,28 @@ ...@@ -32,28 +33,28 @@
#include <assert.h> #include <assert.h>
class Thread { class Thread {
typedef BoundMethod<void()> Function;
const Function f;
#ifdef _WIN32 #ifdef _WIN32
HANDLE handle = nullptr; HANDLE handle = nullptr;
DWORD id; DWORD id;
#else #else
pthread_t handle; pthread_t handle = pthread_t();
bool defined = false;
#ifndef NDEBUG #ifndef NDEBUG
/** /**
* The thread is currently being created. This is a workaround for * This handle is only used by IsInside(), and is set by the
* IsInside(), which may return false until pthread_create() has * thread function. Since #handle is set by pthread_create()
* initialised the #handle. * which is racy, we need this attribute for early checks
* inside the thread function.
*/ */
bool creating = false; pthread_t inside_handle = pthread_t();
#endif #endif
#endif #endif
void (*f)(void *ctx);
void *ctx;
public: public:
Thread() = default; explicit Thread(Function _f):f(_f) {}
Thread(const Thread &) = delete; Thread(const Thread &) = delete;
...@@ -69,10 +70,11 @@ public: ...@@ -69,10 +70,11 @@ public:
#ifdef _WIN32 #ifdef _WIN32
return handle != nullptr; return handle != nullptr;
#else #else
return defined; return handle != pthread_t();
#endif #endif
} }
#ifndef NDEBUG
/** /**
* Check if this thread is the current thread. * Check if this thread is the current thread.
*/ */
...@@ -81,18 +83,23 @@ public: ...@@ -81,18 +83,23 @@ public:
#ifdef _WIN32 #ifdef _WIN32
return GetCurrentThreadId() == id; return GetCurrentThreadId() == id;
#else #else
#ifdef NDEBUG /* note: not using pthread_equal() because that
constexpr bool creating = false; function "is undefined if either thread ID is not
#endif valid so we can't safely use it on
return IsDefined() && (creating || default-constructed values" (comment from
pthread_equal(pthread_self(), handle)); libstdc++) - and if both libstdc++ and libc++ get
away with this, we can do it as well */
return pthread_self() == inside_handle;
#endif #endif
} }
#endif
bool Start(void (*f)(void *ctx), void *ctx); void Start();
void Join(); void Join();
private: private:
void Run();
#ifdef _WIN32 #ifdef _WIN32
static DWORD WINAPI ThreadProc(LPVOID ctx); static DWORD WINAPI ThreadProc(LPVOID ctx);
#else #else
......
...@@ -38,10 +38,12 @@ ...@@ -38,10 +38,12 @@
#include <windows.h> #include <windows.h>
#endif #endif
#if defined(__linux__) && !defined(ANDROID) #ifdef __linux__
#ifndef ANDROID
static int static int
ioprio_set(int which, int who, int ioprio) linux_ioprio_set(int which, int who, int ioprio)
{ {
return syscall(__NR_ioprio_set, which, who, ioprio); return syscall(__NR_ioprio_set, which, who, ioprio);
} }
...@@ -55,7 +57,20 @@ ioprio_set_idle() ...@@ -55,7 +57,20 @@ ioprio_set_idle()
static constexpr int _IOPRIO_IDLE = static constexpr int _IOPRIO_IDLE =
(_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7; (_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7;
ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE); linux_ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
}
#endif /* !ANDROID */
/**
* Wrapper for the "sched_setscheduler" system call. We don't use the
* one from the C library because Musl has an intentionally broken
* implementation.
*/
static int
linux_sched_setscheduler(pid_t pid, int sched, const struct sched_param *param)
{
return syscall(__NR_sched_setscheduler, pid, sched, param);
} }
#endif #endif
...@@ -66,7 +81,7 @@ SetThreadIdlePriority() ...@@ -66,7 +81,7 @@ SetThreadIdlePriority()
#ifdef __linux__ #ifdef __linux__
#ifdef SCHED_IDLE #ifdef SCHED_IDLE
static struct sched_param sched_param; static struct sched_param sched_param;
sched_setscheduler(0, SCHED_IDLE, &sched_param); linux_sched_setscheduler(0, SCHED_IDLE, &sched_param);
#endif #endif
#ifndef ANDROID #ifndef ANDROID
...@@ -92,7 +107,7 @@ SetThreadRealtime() ...@@ -92,7 +107,7 @@ SetThreadRealtime()
policy |= SCHED_RESET_ON_FORK; policy |= SCHED_RESET_ON_FORK;
#endif #endif
if (sched_setscheduler(0, policy, &sched_param) < 0) if (linux_sched_setscheduler(0, policy, &sched_param) < 0)
throw MakeErrno("sched_setscheduler failed"); throw MakeErrno("sched_setscheduler failed");
#endif // __linux__ #endif // __linux__
}; };
/* /*
* Copyright (C) 2013 Max Kellermann <max.kellermann@gmail.com> * Copyright (C) 2013-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#ifndef ASCII_HXX #ifndef ASCII_HXX
#define ASCII_HXX #define ASCII_HXX
#include "StringView.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <assert.h> #include <assert.h>
...@@ -69,4 +70,20 @@ StringEqualsCaseASCII(const char *a, const char *b, size_t n) noexcept ...@@ -69,4 +70,20 @@ StringEqualsCaseASCII(const char *a, const char *b, size_t n) noexcept
return strncasecmp(a, b, n) == 0; return strncasecmp(a, b, n) == 0;
} }
gcc_pure gcc_nonnull_all
static inline bool
StringStartsWithCaseASCII(const char *haystack, StringView needle) noexcept
{
return StringEqualsCaseASCII(haystack, needle.data, needle.size);
}
gcc_pure gcc_nonnull_all
static inline const char *
StringAfterPrefixCaseASCII(const char *haystack, StringView needle) noexcept
{
return StringStartsWithCaseASCII(haystack, needle)
? haystack + needle.size
: nullptr;
}
#endif #endif
...@@ -23,14 +23,9 @@ ...@@ -23,14 +23,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef _WIN32
#include <string.h>
#endif
AllocatedString<> AllocatedString<>
FormatStringV(const char *fmt, va_list args) FormatStringV(const char *fmt, va_list args)
{ {
#ifndef _WIN32
va_list tmp; va_list tmp;
va_copy(tmp, args); va_copy(tmp, args);
const int length = vsnprintf(NULL, 0, fmt, tmp); const int length = vsnprintf(NULL, 0, fmt, tmp);
...@@ -43,22 +38,6 @@ FormatStringV(const char *fmt, va_list args) ...@@ -43,22 +38,6 @@ FormatStringV(const char *fmt, va_list args)
char *buffer = new char[length + 1]; char *buffer = new char[length + 1];
vsnprintf(buffer, length + 1, fmt, args); vsnprintf(buffer, length + 1, fmt, args);
return AllocatedString<>::Donate(buffer); return AllocatedString<>::Donate(buffer);
#else
/* On mingw32, snprintf() expects a 64 bit integer instead of
a "long int" for "%li". This is not consistent with our
expectation, so we're using plain sprintf() here, hoping
the static buffer is large enough. Sorry for this hack,
but WIN32 development is so painful, I'm not in the mood to
do it properly now. */
char buffer[16384];
vsprintf(buffer, fmt, args);
const size_t length = strlen(buffer);
char *p = new char[length + 1];
memcpy(p, buffer, length + 1);
return AllocatedString<>::Donate(p);
#endif
} }
AllocatedString<> AllocatedString<>
......
...@@ -78,7 +78,12 @@ ParseDouble(const char *p, char **endptr=nullptr) ...@@ -78,7 +78,12 @@ ParseDouble(const char *p, char **endptr=nullptr)
static inline float static inline float
ParseFloat(const char *p, char **endptr=nullptr) ParseFloat(const char *p, char **endptr=nullptr)
{ {
#if defined(__BIONIC__) && __ANDROID_API__ < 21
/* strtof() requires API level 21 */
return (float)ParseDouble(p, endptr); return (float)ParseDouble(p, endptr);
#else
return strtof(p, endptr);
#endif
} }
#endif #endif
...@@ -42,11 +42,6 @@ class RefCount { ...@@ -42,11 +42,6 @@ class RefCount {
std::atomic_uint n; std::atomic_uint n;
public: public:
#ifndef _LIBCPP_VERSION
/* the "constexpr" is missing in libc++'s "atomic"
implementation */
constexpr
#endif
RefCount():n(1) {} RefCount():n(1) {}
void Increment() { void Increment() {
......
/*
* Copyright (C) 2010-2015 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef STRING_FORMAT_HXX
#define STRING_FORMAT_HXX
#include "StringBuffer.hxx"
#include <stdio.h>
template<typename... Args>
static inline void
StringFormat(char *buffer, size_t size,
const char *fmt, Args&&... args) noexcept
{
snprintf(buffer, size, fmt, args...);
}
template<size_t CAPACITY, typename... Args>
static inline void
StringFormat(StringBuffer<CAPACITY> &buffer,
const char *fmt, Args&&... args) noexcept
{
StringFormat(buffer.data(), buffer.capacity(), fmt, args...);
}
template<size_t CAPACITY, typename... Args>
static inline StringBuffer<CAPACITY>
StringFormat(const char *fmt, Args&&... args) noexcept
{
StringBuffer<CAPACITY> result;
StringFormat(result, fmt, args...);
return result;
}
template<typename... Args>
static inline void
StringFormatUnsafe(char *buffer, const char *fmt, Args&&... args) noexcept
{
sprintf(buffer, fmt, args...);
}
#endif
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
*/ */
#include "UriUtil.hxx" #include "UriUtil.hxx"
#include "StringCompare.hxx" #include "ASCII.hxx"
#include "CharUtil.hxx" #include "CharUtil.hxx"
#include <assert.h> #include <assert.h>
...@@ -169,7 +169,7 @@ SkipUriScheme(const char *uri) noexcept ...@@ -169,7 +169,7 @@ SkipUriScheme(const char *uri) noexcept
{ {
const char *const schemes[] = { "http://", "https://", "ftp://" }; const char *const schemes[] = { "http://", "https://", "ftp://" };
for (auto scheme : schemes) { for (auto scheme : schemes) {
auto result = StringAfterPrefix(uri, scheme); auto result = StringAfterPrefixCaseASCII(uri, scheme);
if (result != nullptr) if (result != nullptr)
return result; return result;
} }
......
/* /*
* Copyright 2003-2017 The Music Player Daemon Project * Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -17,11 +17,14 @@ ...@@ -17,11 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef MPD_INPUT_DOMAIN_HXX #ifndef NULL_MIXER_LISTENER_HXX
#define MPD_INPUT_DOMAIN_HXX #define NULL_MIXER_LISTENER_HXX
class Domain; #include "mixer/Listener.hxx"
extern const Domain input_domain; class NullMixerListener : public MixerListener {
public:
void OnMixerVolumeChanged(Mixer &, int) override {}
};
#endif #endif
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
*/ */
#include "config.h" #include "config.h"
#include "NullMixerListener.hxx"
#include "mixer/MixerControl.hxx" #include "mixer/MixerControl.hxx"
#include "mixer/MixerList.hxx" #include "mixer/MixerList.hxx"
#include "filter/FilterRegistry.hxx" #include "filter/FilterRegistry.hxx"
...@@ -50,9 +51,14 @@ try { ...@@ -50,9 +51,14 @@ try {
EventLoop event_loop; EventLoop event_loop;
NullMixerListener mixer_listener;
Mixer *mixer = mixer_new(event_loop, alsa_mixer_plugin, Mixer *mixer = mixer_new(event_loop, alsa_mixer_plugin,
*(AudioOutput *)nullptr, /* ugly dangerous dummy pointer to
*(MixerListener *)nullptr, make the compiler happy; this
works with most mixers, because
they don't need the AudioOutput */
*(AudioOutput *)0x1,
mixer_listener,
ConfigBlock()); ConfigBlock());
mixer_open(mixer); mixer_open(mixer);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
*/ */
#include "config.h" #include "config.h"
#include "NullMixerListener.hxx"
#include "output/Internal.hxx" #include "output/Internal.hxx"
#include "output/OutputPlugin.hxx" #include "output/OutputPlugin.hxx"
#include "output/Client.hxx" #include "output/Client.hxx"
...@@ -44,6 +45,8 @@ ...@@ -44,6 +45,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
void AudioOutput::Task() {}
class DummyAudioOutputClient final : public AudioOutputClient { class DummyAudioOutputClient final : public AudioOutputClient {
public: public:
/* virtual methods from AudioOutputClient */ /* virtual methods from AudioOutputClient */
...@@ -62,7 +65,9 @@ filter_plugin_by_name(gcc_unused const char *name) noexcept ...@@ -62,7 +65,9 @@ filter_plugin_by_name(gcc_unused const char *name) noexcept
} }
static AudioOutput * static AudioOutput *
load_audio_output(EventLoop &event_loop, AudioOutputClient &client, load_audio_output(EventLoop &event_loop,
NullMixerListener &mixer_listener,
AudioOutputClient &client,
const char *name) const char *name)
{ {
const auto *param = config_find_block(ConfigBlockOption::AUDIO_OUTPUT, const auto *param = config_find_block(ConfigBlockOption::AUDIO_OUTPUT,
...@@ -72,7 +77,7 @@ load_audio_output(EventLoop &event_loop, AudioOutputClient &client, ...@@ -72,7 +77,7 @@ load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
name); name);
return audio_output_new(event_loop, ReplayGainConfig(), *param, return audio_output_new(event_loop, ReplayGainConfig(), *param,
*(MixerListener *)nullptr, mixer_listener,
client); client);
} }
...@@ -142,8 +147,10 @@ try { ...@@ -142,8 +147,10 @@ try {
/* initialize the audio output */ /* initialize the audio output */
NullMixerListener mixer_listener;
DummyAudioOutputClient client; DummyAudioOutputClient client;
AudioOutput *ao = load_audio_output(event_loop, client, argv[2]); AudioOutput *ao = load_audio_output(event_loop, mixer_listener,
client, argv[2]);
/* parse the audio format */ /* parse the audio format */
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "storage/Registry.hxx" #include "storage/Registry.hxx"
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#include "storage/FileInfo.hxx" #include "storage/FileInfo.hxx"
#include "net/Init.hxx"
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
...@@ -70,7 +71,12 @@ Ls(Storage &storage, const char *path) ...@@ -70,7 +71,12 @@ Ls(Storage &storage, const char *path)
char mtime_buffer[32]; char mtime_buffer[32];
const char *mtime = " "; const char *mtime = " ";
if (info.mtime > 0) { if (info.mtime > 0) {
strftime(mtime_buffer, sizeof(mtime_buffer), "%F", strftime(mtime_buffer, sizeof(mtime_buffer),
#ifdef _WIN32
"%Y-%m-%d",
#else
"%F",
#endif
gmtime(&info.mtime)); gmtime(&info.mtime));
mtime = mtime_buffer; mtime = mtime_buffer;
} }
...@@ -95,6 +101,7 @@ try { ...@@ -95,6 +101,7 @@ try {
const char *const command = argv[1]; const char *const command = argv[1];
const char *const storage_uri = argv[2]; const char *const storage_uri = argv[2];
const ScopeNetInit net_init;
const ScopeIOThread io_thread; const ScopeIOThread io_thread;
if (strcmp(command, "ls") == 0) { if (strcmp(command, "ls") == 0) {
......
...@@ -50,20 +50,22 @@ class CrossGccToolchain: ...@@ -50,20 +50,22 @@ class CrossGccToolchain:
self.nm = os.path.join(toolchain_bin, arch + '-nm') self.nm = os.path.join(toolchain_bin, arch + '-nm')
self.strip = os.path.join(toolchain_bin, arch + '-strip') self.strip = os.path.join(toolchain_bin, arch + '-strip')
common_flags = '' common_flags = '-O2 -g'
if not x64: if not x64:
# enable SSE support which is required for LAME # enable SSE support which is required for LAME
common_flags += ' -march=pentium3' common_flags += ' -march=pentium3'
self.cflags = '-O2 -g ' + common_flags self.cflags = common_flags
self.cxxflags = '-O2 -g ' + common_flags self.cxxflags = common_flags
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
self.ldflags = '-L' + os.path.join(install_prefix, 'lib') self.ldflags = '-L' + os.path.join(install_prefix, 'lib')
self.libs = '' self.libs = ''
self.is_arm = arch.startswith('arm') self.is_arm = arch.startswith('arm')
self.is_armv7 = self.is_arm and 'armv7' in self.cflags self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = arch == 'aarch64'
self.is_windows = 'mingw32' in arch self.is_windows = 'mingw32' in arch
self.env = dict(os.environ) self.env = dict(os.environ)
...@@ -75,6 +77,7 @@ class CrossGccToolchain: ...@@ -75,6 +77,7 @@ class CrossGccToolchain:
# a list of third-party libraries to be used by MPD on Android # a list of third-party libraries to be used by MPD on Android
from build.libs import * from build.libs import *
thirdparty_libs = [ thirdparty_libs = [
libmpdclient,
libogg, libogg,
libvorbis, libvorbis,
opus, opus,
...@@ -84,6 +87,8 @@ thirdparty_libs = [ ...@@ -84,6 +87,8 @@ thirdparty_libs = [
liblame, liblame,
ffmpeg, ffmpeg,
curl, curl,
libexpat,
libnfs,
boost, boost,
] ]
......
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