aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--INSTALL3
-rw-r--r--Makefile.am203
-rw-r--r--NEWS21
-rwxr-xr-xandroid/build.py30
-rw-r--r--configure.ac809
-rw-r--r--doc/mpdconf.example26
-rw-r--r--doc/protocol.xml45
-rw-r--r--doc/user.xml64
-rw-r--r--m4/faad.m473
-rw-r--r--m4/mpd_auto.m4149
-rw-r--r--m4/mpd_define_conditional.m48
-rw-r--r--m4/mpd_func.m42
-rw-r--r--m4/mpd_with_flags.m423
-rw-r--r--m4/pretty_print.m412
-rw-r--r--src/Chrono.hxx2
-rw-r--r--src/CommandLine.cxx87
-rw-r--r--src/Compiler.h24
-rw-r--r--src/Instance.cxx3
-rw-r--r--src/Main.cxx35
-rw-r--r--src/ReplayGainInfo.hxx8
-rw-r--r--src/SongPrint.cxx5
-rw-r--r--src/TagPrint.cxx7
-rw-r--r--src/archive/ArchiveList.cxx6
-rw-r--r--src/archive/plugins/Bzip2ArchivePlugin.cxx2
-rw-r--r--src/command/AllCommands.cxx136
-rw-r--r--src/command/DatabaseCommands.cxx74
-rw-r--r--src/command/DatabaseCommands.hxx21
-rw-r--r--src/command/FileCommands.cxx8
-rw-r--r--src/command/FileCommands.hxx3
-rw-r--r--src/command/MessageCommands.cxx36
-rw-r--r--src/command/MessageCommands.hxx11
-rw-r--r--src/command/NeighborCommands.cxx4
-rw-r--r--src/command/NeighborCommands.hxx3
-rw-r--r--src/command/OtherCommands.cxx83
-rw-r--r--src/command/OtherCommands.hxx33
-rw-r--r--src/command/OutputCommands.cxx24
-rw-r--r--src/command/OutputCommands.hxx9
-rw-r--r--src/command/PlayerCommands.cxx90
-rw-r--r--src/command/PlayerCommands.hxx43
-rw-r--r--src/command/PlaylistCommands.cxx86
-rw-r--r--src/command/PlaylistCommands.hxx28
-rw-r--r--src/command/QueueCommands.cxx114
-rw-r--r--src/command/QueueCommands.hxx41
-rw-r--r--src/command/StickerCommands.cxx95
-rw-r--r--src/command/StickerCommands.hxx3
-rw-r--r--src/command/StorageCommands.cxx13
-rw-r--r--src/command/StorageCommands.hxx7
-rw-r--r--src/command/TagCommands.cxx17
-rw-r--r--src/command/TagCommands.hxx5
-rw-r--r--src/config/ConfigData.hxx2
-rw-r--r--src/config/ConfigGlobal.cxx1
-rw-r--r--src/config/ConfigParser.cxx4
-rw-r--r--src/config/ConfigTemplates.cxx4
-rw-r--r--src/db/DatabasePrint.cxx29
-rw-r--r--src/db/DatabasePrint.hxx6
-rw-r--r--src/db/Helpers.cxx4
-rw-r--r--src/db/Registry.cxx4
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.cxx18
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.hxx2
-rw-r--r--src/db/plugins/upnp/Directory.cxx60
-rw-r--r--src/db/plugins/upnp/Object.hxx27
-rw-r--r--src/db/plugins/upnp/UpnpDatabasePlugin.cxx16
-rw-r--r--src/db/update/InotifySource.hxx4
-rw-r--r--src/decoder/DecoderList.cxx26
-rw-r--r--src/decoder/plugins/DsdLib.cxx4
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx6
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx8
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx597
-rw-r--r--src/decoder/plugins/FfmpegIo.cxx98
-rw-r--r--src/decoder/plugins/FfmpegIo.hxx57
-rw-r--r--src/decoder/plugins/FfmpegMetaData.cxx38
-rw-r--r--src/decoder/plugins/FfmpegMetaData.hxx14
-rw-r--r--src/decoder/plugins/FlacMetadata.cxx4
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx4
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.cxx188
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.cxx22
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.cxx180
-rw-r--r--src/decoder/plugins/VorbisComments.cxx4
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.cxx6
-rw-r--r--src/decoder/plugins/XiphTags.cxx1
-rw-r--r--src/encoder/EncoderList.cxx10
-rw-r--r--src/encoder/plugins/VorbisEncoderPlugin.cxx141
-rw-r--r--src/event/ServerSocket.cxx2
-rw-r--r--src/fs/AllocatedPath.cxx25
-rw-r--r--src/fs/AllocatedPath.hxx7
-rw-r--r--src/fs/Charset.cxx95
-rw-r--r--src/fs/Charset.hxx19
-rw-r--r--src/fs/Config.cxx34
-rw-r--r--src/fs/Config.hxx7
-rw-r--r--src/fs/Path.hxx18
-rw-r--r--src/fs/Path2.cxx28
-rw-r--r--src/fs/io/TextFile.cxx6
-rw-r--r--src/fs/io/TextFile.hxx2
-rw-r--r--src/input/AsyncInputStream.hxx4
-rw-r--r--src/input/Registry.cxx8
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.cxx3
-rw-r--r--src/input/plugins/FfmpegInputPlugin.cxx4
-rw-r--r--src/input/plugins/NfsInputPlugin.cxx4
-rw-r--r--src/lib/despotify/DespotifyUtils.cxx77
-rw-r--r--src/lib/despotify/DespotifyUtils.hxx17
-rw-r--r--src/lib/ffmpeg/Buffer.hxx72
-rw-r--r--src/lib/ffmpeg/Init.cxx38
-rw-r--r--src/lib/ffmpeg/Init.hxx26
-rw-r--r--src/lib/ffmpeg/LogCallback.cxx66
-rw-r--r--src/lib/ffmpeg/LogCallback.hxx30
-rw-r--r--src/lib/ffmpeg/LogError.cxx45
-rw-r--r--src/lib/ffmpeg/LogError.hxx29
-rw-r--r--src/lib/ffmpeg/Time.hxx110
-rw-r--r--src/lib/icu/Collate.cxx45
-rw-r--r--src/lib/icu/Converter.cxx169
-rw-r--r--src/lib/icu/Converter.hxx95
-rw-r--r--src/lib/icu/Util.cxx72
-rw-r--r--src/lib/icu/Util.hxx44
-rw-r--r--src/lib/sqlite/Domain.cxx24
-rw-r--r--src/lib/sqlite/Domain.hxx27
-rw-r--r--src/lib/sqlite/Util.hxx188
-rw-r--r--src/ls.cxx12
-rw-r--r--src/mixer/MixerList.hxx1
-rw-r--r--src/mixer/MixerType.cxx12
-rw-r--r--src/mixer/MixerType.hxx23
-rw-r--r--src/mixer/plugins/NullMixerPlugin.cxx67
-rw-r--r--src/neighbor/Registry.cxx2
-rw-r--r--src/neighbor/plugins/SmbclientNeighborPlugin.cxx2
-rw-r--r--src/output/Init.cxx18
-rw-r--r--src/output/Internal.hxx52
-rw-r--r--src/output/OutputControl.cxx26
-rw-r--r--src/output/OutputThread.cxx36
-rw-r--r--src/output/Registry.cxx10
-rw-r--r--src/output/plugins/AoOutputPlugin.cxx20
-rw-r--r--src/output/plugins/JackOutputPlugin.cxx675
-rw-r--r--src/output/plugins/OssOutputPlugin.cxx6
-rw-r--r--src/output/plugins/PulseOutputPlugin.cxx6
-rw-r--r--src/output/plugins/WinmmOutputPlugin.cxx25
-rw-r--r--src/output/plugins/httpd/HttpdInternal.hxx2
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.cxx56
-rw-r--r--src/pcm/ConfiguredResampler.cxx16
-rw-r--r--src/playlist/PlaylistRegistry.cxx7
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.cxx2
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.cxx193
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx2
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.cxx102
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.cxx2
-rw-r--r--src/queue/Playlist.cxx82
-rw-r--r--src/queue/Playlist.hxx29
-rw-r--r--src/queue/Queue.cxx5
-rw-r--r--src/queue/Queue.hxx5
-rw-r--r--src/sticker/Match.hxx49
-rw-r--r--src/sticker/SongSticker.cxx39
-rw-r--r--src/sticker/SongSticker.hxx18
-rw-r--r--src/sticker/StickerDatabase.cxx382
-rw-r--r--src/sticker/StickerDatabase.hxx26
-rw-r--r--src/storage/CompositeStorage.cxx2
-rw-r--r--src/system/FatalError.cxx27
-rw-r--r--src/system/FatalError.hxx12
-rw-r--r--src/system/fd_util.h2
-rw-r--r--src/tag/ApeTag.cxx1
-rw-r--r--src/tag/Set.cxx2
-rw-r--r--src/tag/TagId3.cxx5
-rw-r--r--src/tag/TagId3.hxx2
-rw-r--r--src/tag/TagPool.cxx2
-rw-r--r--src/util/Alloc.cxx84
-rw-r--r--src/util/Alloc.hxx19
-rw-r--r--src/util/Cast.hxx4
-rw-r--r--src/util/DivideString.cxx48
-rw-r--r--src/util/DivideString.hxx75
-rw-r--r--src/util/Error.cxx8
-rw-r--r--src/util/Manual.hxx37
-rw-r--r--src/util/SplitString.cxx42
-rw-r--r--src/util/SplitString.hxx56
-rw-r--r--src/util/StringUtil.cxx20
-rw-r--r--src/util/StringUtil.hxx8
-rw-r--r--src/win32/Win32Main.cxx24
-rw-r--r--test/DivideStringTest.hxx54
-rw-r--r--test/DumpDatabase.cxx14
-rw-r--r--test/SplitStringTest.hxx65
-rw-r--r--test/TestCircularBuffer.hxx7
-rw-r--r--test/TestIcu.cxx82
-rw-r--r--test/UriUtilTest.hxx66
-rw-r--r--test/dump_playlist.cxx12
-rw-r--r--test/dump_text_file.cxx12
-rw-r--r--test/read_mixer.cxx10
-rw-r--r--test/read_tags.cxx10
-rw-r--r--test/run_decoder.cxx10
-rw-r--r--test/run_filter.cxx12
-rw-r--r--test/run_input.cxx12
-rw-r--r--test/run_output.cxx10
-rw-r--r--test/run_storage.cxx12
-rw-r--r--test/test_util.cxx62
-rw-r--r--test/visit_archive.cxx12
-rwxr-xr-xwin32/build.py398
-rw-r--r--win32/res/mpd.ico (renamed from src/win32/mpd.ico)bin353118 -> 353118 bytes
-rw-r--r--win32/res/mpd.rc.in (renamed from src/win32/mpd_win32_rc.rc.in)2
192 files changed, 5346 insertions, 3669 deletions
diff --git a/INSTALL b/INSTALL
index 29a23d361..29142a891 100644
--- a/INSTALL
+++ b/INSTALL
@@ -18,9 +18,6 @@ Any other C++11 compliant compiler should also work.
Boost 1.46 - http://www.boost.org/
-GLib 2.28 - http://www.gtk.org/
-General-purpose utility library.
-
Optional Output Dependencies
----------------------------
diff --git a/Makefile.am b/Makefile.am
index e8f459454..e1c19bb13 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,6 +5,7 @@ AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS) $(BOOST_CPPFLAGS)
AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
+APK_NAME = mpd
if ANDROID
else
bin_PROGRAMS = src/mpd
@@ -57,9 +58,9 @@ src_mpd_LDADD = \
libevent.a \
libthread.a \
libsystem.a \
- $(ICU_LDADD) \
libutil.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
$(SYSTEMD_DAEMON_LIBS) \
$(GLIB_LIBS)
@@ -186,9 +187,14 @@ if ANDROID
else
libmpd_a_SOURCES += \
src/unix/SignalHandlers.cxx src/unix/SignalHandlers.hxx \
- src/unix/Daemon.cxx src/unix/Daemon.hxx \
- src/unix/PidFile.hxx \
src/CommandLine.cxx src/CommandLine.hxx
+
+if ENABLE_DAEMON
+libmpd_a_SOURCES += \
+ src/unix/Daemon.cxx src/unix/Daemon.hxx \
+ src/unix/PidFile.hxx
+endif
+
endif
if ENABLE_DATABASE
@@ -268,7 +274,7 @@ libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
src_mpd_LDADD += libandroid.a libjava.a
-all-local: android/build/bin/Main-debug.apk
+all-local: android/build/bin/$(APK_NAME)-debug.apk
clean-local:
rm -rf android/build
@@ -280,8 +286,8 @@ android/build/build.xml: android/AndroidManifest.xml
mkdir -p android/build/include android/build/res android/build/src/org
ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
- ln -s $(abs_srcdir)/android/res/values android/build/res
- $(ANDROID_SDK)/tools/android update project --path android/build --target android-17
+ ln -s $(abs_srcdir)/android/res/values $(abs_srcdir)/android/res/layout android/build/res
+ $(ANDROID_SDK)/tools/android update project --path android/build --target android-17 --name $(APK_NAME)
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml
cd android/build && ant compile-jni-classes
@@ -305,18 +311,18 @@ APK_DEPS = android/build/res/drawable/icon.png \
$(wildcard $(srcdir)/android/src/*.java) \
android/build/build.xml
-android/build/bin/Main-debug.apk: $(APK_DEPS)
+android/build/bin/$(APK_NAME)-debug.apk: $(APK_DEPS)
cd android/build && ant nodeps debug
-android/build/bin/Main-release-unsigned.apk: $(APK_DEPS)
+android/build/bin/$(APK_NAME)-release-unsigned.apk: $(APK_DEPS)
cd android/build && ant nodeps release
-android/build/bin/Main-release-unaligned.apk: android/build/bin/Main-release-unsigned.apk
+android/build/bin/$(APK_NAME)-release-unaligned.apk: android/build/bin/$(APK_NAME)-release-unsigned.apk
jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
-android/build/bin/Main.apk: android/build/bin/Main-release-unaligned.apk
+android/build/bin/$(APK_NAME).apk: android/build/bin/$(APK_NAME)-release-unaligned.apk
$(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
endif
@@ -325,14 +331,14 @@ endif
# Windows resource file
#
-src/win32/mpd_win32_rc.$(OBJEXT): src/win32/mpd_win32_rc.rc
+win32/res/mpd.$(OBJEXT): %.$(OBJEXT): %.rc
$(WINDRES) -i $< -o $@
if HAVE_WINDOWS
-noinst_DATA = src/win32/mpd_win32_rc.rc
+noinst_DATA = win32/res/mpd.rc
-EXTRA_src_mpd_DEPENDENCIES = src/win32/mpd_win32_rc.$(OBJEXT)
-src_mpd_LDFLAGS = -Wl,src/win32/mpd_win32_rc.$(OBJEXT)
+EXTRA_src_mpd_DEPENDENCIES = win32/res/mpd.$(OBJEXT)
+src_mpd_LDFLAGS = -Wl,win32/res/mpd.$(OBJEXT)
endif
if ENABLE_DATABASE
@@ -348,6 +354,9 @@ endif
if ENABLE_SQLITE
libmpd_a_SOURCES += \
src/command/StickerCommands.cxx src/command/StickerCommands.hxx \
+ src/lib/sqlite/Domain.cxx src/lib/sqlite/Domain.hxx \
+ src/lib/sqlite/Util.hxx \
+ src/sticker/Match.hxx \
src/sticker/StickerDatabase.cxx src/sticker/StickerDatabase.hxx \
src/sticker/StickerPrint.cxx src/sticker/StickerPrint.hxx \
src/sticker/SongSticker.cxx src/sticker/SongSticker.hxx
@@ -369,6 +378,7 @@ libutil_a_SOURCES = \
src/util/CharUtil.hxx \
src/util/NumberParser.hxx \
src/util/StringUtil.cxx src/util/StringUtil.hxx \
+ src/util/DivideString.cxx src/util/DivideString.hxx \
src/util/SplitString.cxx src/util/SplitString.hxx \
src/util/FormatString.cxx src/util/FormatString.hxx \
src/util/Tokenizer.cxx src/util/Tokenizer.hxx \
@@ -447,10 +457,12 @@ libevent_a_SOURCES = \
libicu_a_SOURCES = \
src/lib/icu/Collate.cxx src/lib/icu/Collate.hxx \
+ src/lib/icu/Converter.cxx src/lib/icu/Converter.hxx \
src/lib/icu/Error.cxx src/lib/icu/Error.hxx
if HAVE_ICU
libicu_a_SOURCES += \
+ src/lib/icu/Util.cxx src/lib/icu/Util.hxx \
src/lib/icu/Init.cxx src/lib/icu/Init.hxx
endif
@@ -459,6 +471,11 @@ libicu_a_CPPFLAGS = $(AM_CPPFLAGS) \
ICU_LDADD = libicu.a $(ICU_LIBS)
+if HAVE_ICU
+else
+ICU_LDADD += $(GLIB_LIBS)
+endif
+
# PCM library
libpcm_a_SOURCES = \
@@ -487,12 +504,12 @@ libpcm_a_SOURCES = \
src/pcm/PcmUtils.hxx
libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(SOXR_CFLAGS) \
- $(SAMPLERATE_CFLAGS)
+ $(LIBSAMPLERATE_CFLAGS)
PCM_LIBS = \
libpcm.a \
$(SOXR_LIBS) \
- $(SAMPLERATE_LIBS)
+ $(LIBSAMPLERATE_LIBS)
if ENABLE_DSD
libpcm_a_SOURCES += \
@@ -500,12 +517,12 @@ libpcm_a_SOURCES += \
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
endif
-if HAVE_LIBSAMPLERATE
+if ENABLE_LIBSAMPLERATE
libpcm_a_SOURCES += \
src/pcm/LibsamplerateResampler.cxx src/pcm/LibsamplerateResampler.hxx
endif
-if HAVE_SOXR
+if ENABLE_SOXR
libpcm_a_SOURCES += \
src/pcm/SoxrResampler.cxx src/pcm/SoxrResampler.hxx
endif
@@ -529,7 +546,7 @@ libfs_a_SOURCES = \
src/fs/Traits.cxx src/fs/Traits.hxx \
src/fs/Config.cxx src/fs/Config.hxx \
src/fs/Charset.cxx src/fs/Charset.hxx \
- src/fs/Path.cxx src/fs/Path.hxx \
+ src/fs/Path.cxx src/fs/Path2.cxx src/fs/Path.hxx \
src/fs/AllocatedPath.cxx src/fs/AllocatedPath.hxx \
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
@@ -537,7 +554,7 @@ libfs_a_SOURCES = \
src/fs/DirectoryReader.hxx
libfs_a_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS)
-if HAVE_ZLIB
+if ENABLE_ZLIB
libfs_a_SOURCES += \
src/lib/zlib/Domain.cxx src/lib/zlib/Domain.hxx \
src/fs/io/GunzipReader.cxx src/fs/io/GunzipReader.hxx \
@@ -633,7 +650,7 @@ NEIGHBOR_LIBS = \
$(SMBCLIENT_LIBS) \
libneighbor.a
-if HAVE_LIBUPNP
+if ENABLE_UPNP
libneighbor_a_SOURCES += \
$(UPNP_SOURCES) \
src/neighbor/plugins/UpnpNeighborPlugin.cxx src/neighbor/plugins/UpnpNeighborPlugin.hxx
@@ -672,7 +689,7 @@ libdb_plugins_a_SOURCES = \
src/db/plugins/simple/SimpleDatabasePlugin.cxx \
src/db/plugins/simple/SimpleDatabasePlugin.hxx
-if HAVE_LIBMPDCLIENT
+if ENABLE_LIBMPDCLIENT
libdb_plugins_a_SOURCES += \
src/db/plugins/ProxyDatabasePlugin.cxx src/db/plugins/ProxyDatabasePlugin.hxx
endif
@@ -681,7 +698,7 @@ DB_LIBS = \
libdb_plugins.a \
$(LIBMPDCLIENT_LIBS)
-if HAVE_LIBUPNP
+if ENABLE_UPNP
libdb_plugins_a_SOURCES += \
$(UPNP_SOURCES) \
src/db/plugins/upnp/UpnpDatabasePlugin.cxx src/db/plugins/upnp/UpnpDatabasePlugin.hxx \
@@ -724,19 +741,19 @@ ARCHIVE_LIBS = \
$(ISO9660_LIBS) \
$(ZZIP_LIBS)
-if HAVE_BZ2
+if ENABLE_BZ2
libarchive_a_SOURCES += \
src/archive/plugins/Bzip2ArchivePlugin.cxx \
src/archive/plugins/Bzip2ArchivePlugin.hxx
endif
-if HAVE_ZZIP
+if ENABLE_ZZIP
libarchive_a_SOURCES += \
src/archive/plugins/ZzipArchivePlugin.cxx \
src/archive/plugins/ZzipArchivePlugin.hxx
endif
-if HAVE_ISO9660
+if ENABLE_ISO9660
libarchive_a_SOURCES += \
src/archive/plugins/Iso9660ArchivePlugin.cxx \
src/archive/plugins/Iso9660ArchivePlugin.hxx
@@ -787,7 +804,7 @@ libtag_a_SOURCES =\
src/tag/ApeReplayGain.cxx src/tag/ApeReplayGain.hxx \
src/tag/ApeTag.cxx src/tag/ApeTag.hxx
-if HAVE_ID3TAG
+if ENABLE_ID3TAG
libtag_a_SOURCES += \
src/tag/TagId3.cxx src/tag/TagId3.hxx \
src/tag/TagRva2.cxx src/tag/TagRva2.hxx \
@@ -797,9 +814,14 @@ endif
# ffmpeg
-if HAVE_FFMPEG
+if ENABLE_FFMPEG
noinst_LIBRARIES += libffmpeg.a
libffmpeg_a_SOURCES = \
+ src/lib/ffmpeg/Init.cxx src/lib/ffmpeg/Init.hxx \
+ src/lib/ffmpeg/Time.hxx \
+ src/lib/ffmpeg/Buffer.hxx \
+ src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \
+ src/lib/ffmpeg/LogCallback.cxx src/lib/ffmpeg/LogCallback.hxx \
src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \
src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx
libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \
@@ -863,25 +885,25 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/DsdLib.hxx
endif
-if HAVE_MAD
+if ENABLE_MAD
libdecoder_a_SOURCES += \
src/decoder/plugins/MadDecoderPlugin.cxx \
src/decoder/plugins/MadDecoderPlugin.hxx
endif
-if HAVE_MPG123
+if ENABLE_MPG123
libdecoder_a_SOURCES += \
src/decoder/plugins/Mpg123DecoderPlugin.cxx \
src/decoder/plugins/Mpg123DecoderPlugin.hxx
endif
-if HAVE_MPCDEC
+if ENABLE_MPCDEC
libdecoder_a_SOURCES += \
src/decoder/plugins/MpcdecDecoderPlugin.cxx \
src/decoder/plugins/MpcdecDecoderPlugin.hxx
endif
-if HAVE_OPUS
+if ENABLE_OPUS
libdecoder_a_SOURCES += \
src/decoder/plugins/OggUtil.cxx \
src/decoder/plugins/OggUtil.hxx \
@@ -897,19 +919,19 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/OpusDecoderPlugin.h
endif
-if HAVE_WAVPACK
+if ENABLE_WAVPACK
libdecoder_a_SOURCES += \
src/decoder/plugins/WavpackDecoderPlugin.cxx \
src/decoder/plugins/WavpackDecoderPlugin.hxx
endif
-if HAVE_ADPLUG
+if ENABLE_ADPLUG
libdecoder_a_SOURCES += \
src/decoder/plugins/AdPlugDecoderPlugin.cxx \
src/decoder/plugins/AdPlugDecoderPlugin.h
endif
-if HAVE_FAAD
+if ENABLE_FAAD
libdecoder_a_SOURCES += \
src/decoder/plugins/FaadDecoderPlugin.cxx src/decoder/plugins/FaadDecoderPlugin.hxx
endif
@@ -927,7 +949,7 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/VorbisDecoderPlugin.cxx src/decoder/plugins/VorbisDecoderPlugin.h
endif
-if HAVE_FLAC
+if ENABLE_FLAC
libdecoder_a_SOURCES += \
src/decoder/plugins/FlacInput.cxx src/decoder/plugins/FlacInput.hxx \
src/decoder/plugins/FlacIOHandle.cxx src/decoder/plugins/FlacIOHandle.hxx \
@@ -939,7 +961,7 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/FlacDecoderPlugin.h
endif
-if HAVE_AUDIOFILE
+if ENABLE_AUDIOFILE
libdecoder_a_SOURCES += \
src/decoder/plugins/AudiofileDecoderPlugin.cxx \
src/decoder/plugins/AudiofileDecoderPlugin.hxx
@@ -951,7 +973,7 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/MikmodDecoderPlugin.hxx
endif
-if HAVE_MODPLUG
+if ENABLE_MODPLUG
libmodplug_decoder_plugin_a_SOURCES = \
src/decoder/plugins/ModplugDecoderPlugin.cxx \
src/decoder/plugins/ModplugDecoderPlugin.hxx
@@ -979,8 +1001,10 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/WildmidiDecoderPlugin.hxx
endif
-if HAVE_FFMPEG
+if ENABLE_FFMPEG
libdecoder_a_SOURCES += \
+ src/decoder/plugins/FfmpegIo.cxx \
+ src/decoder/plugins/FfmpegIo.hxx \
src/decoder/plugins/FfmpegMetaData.cxx \
src/decoder/plugins/FfmpegMetaData.hxx \
src/decoder/plugins/FfmpegDecoderPlugin.cxx \
@@ -993,7 +1017,7 @@ libdecoder_a_SOURCES += \
src/decoder/plugins/SndfileDecoderPlugin.hxx
endif
-if HAVE_GME
+if ENABLE_GME
libdecoder_a_SOURCES += \
src/decoder/plugins/GmeDecoderPlugin.cxx src/decoder/plugins/GmeDecoderPlugin.hxx
endif
@@ -1042,25 +1066,25 @@ libencoder_plugins_a_SOURCES += \
src/encoder/plugins/WaveEncoderPlugin.hxx
endif
-if ENABLE_VORBIS_ENCODER
+if ENABLE_VORBISENC
libencoder_plugins_a_SOURCES += \
src/encoder/plugins/VorbisEncoderPlugin.cxx \
src/encoder/plugins/VorbisEncoderPlugin.hxx
endif
-if HAVE_OPUS
+if ENABLE_OPUS
libencoder_plugins_a_SOURCES += \
src/encoder/plugins/OpusEncoderPlugin.cxx \
src/encoder/plugins/OpusEncoderPlugin.hxx
endif
-if ENABLE_LAME_ENCODER
+if ENABLE_LAME
libencoder_plugins_a_SOURCES += \
src/encoder/plugins/LameEncoderPlugin.cxx \
src/encoder/plugins/LameEncoderPlugin.hxx
endif
-if ENABLE_TWOLAME_ENCODER
+if ENABLE_TWOLAME
libencoder_plugins_a_SOURCES += \
src/encoder/plugins/TwolameEncoderPlugin.cxx \
src/encoder/plugins/TwolameEncoderPlugin.hxx
@@ -1072,7 +1096,7 @@ libencoder_plugins_a_SOURCES += \
src/encoder/plugins/FlacEncoderPlugin.hxx
endif
-if ENABLE_SHINE_ENCODER
+if ENABLE_SHINE
libencoder_plugins_a_SOURCES += \
src/encoder/plugins/ShineEncoderPlugin.cxx \
src/encoder/plugins/ShineEncoderPlugin.hxx
@@ -1138,7 +1162,7 @@ INPUT_LIBS = \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
-if HAVE_ALSA
+if ENABLE_ALSA
libinput_a_SOURCES += \
src/input/plugins/AlsaInputPlugin.cxx \
src/input/plugins/AlsaInputPlugin.hxx
@@ -1171,7 +1195,7 @@ libinput_a_SOURCES += \
src/input/plugins/CdioParanoiaInputPlugin.hxx
endif
-if HAVE_FFMPEG
+if ENABLE_FFMPEG
libinput_a_SOURCES += \
src/input/plugins/FfmpegInputPlugin.cxx src/input/plugins/FfmpegInputPlugin.hxx
endif
@@ -1244,13 +1268,14 @@ MIXER_API_SRC = \
src/mixer/MixerInternal.hxx
libmixer_plugins_a_SOURCES = \
+ src/mixer/plugins/NullMixerPlugin.cxx \
src/mixer/plugins/SoftwareMixerPlugin.cxx \
src/mixer/plugins/SoftwareMixerPlugin.hxx
libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(ALSA_CFLAGS) \
$(PULSE_CFLAGS)
-if HAVE_ALSA
+if ENABLE_ALSA
liboutput_plugins_a_SOURCES += \
src/output/plugins/AlsaOutputPlugin.cxx \
src/output/plugins/AlsaOutputPlugin.hxx
@@ -1268,14 +1293,14 @@ liboutput_plugins_a_SOURCES += \
OUTPUT_LIBS += -lOpenSLES
endif
-if HAVE_ROAR
+if ENABLE_ROAR
liboutput_plugins_a_SOURCES += \
src/output/plugins/RoarOutputPlugin.cxx \
src/output/plugins/RoarOutputPlugin.hxx
libmixer_plugins_a_SOURCES += src/mixer/plugins/RoarMixerPlugin.cxx
endif
-if HAVE_AO
+if ENABLE_AO
liboutput_plugins_a_SOURCES += \
src/output/plugins/AoOutputPlugin.cxx \
src/output/plugins/AoOutputPlugin.hxx
@@ -1293,7 +1318,7 @@ liboutput_plugins_a_SOURCES += \
src/output/plugins/PipeOutputPlugin.hxx
endif
-if HAVE_JACK
+if ENABLE_JACK
liboutput_plugins_a_SOURCES += \
src/output/plugins/JackOutputPlugin.cxx \
src/output/plugins/JackOutputPlugin.hxx
@@ -1318,7 +1343,7 @@ liboutput_plugins_a_SOURCES += \
src/output/plugins/OSXOutputPlugin.hxx
endif
-if HAVE_PULSE
+if ENABLE_PULSE
liboutput_plugins_a_SOURCES += \
src/output/plugins/PulseOutputPlugin.cxx \
src/output/plugins/PulseOutputPlugin.hxx
@@ -1374,15 +1399,12 @@ libplaylist_plugins_a_SOURCES = \
src/playlist/CloseSongEnumerator.hxx \
src/playlist/MemorySongEnumerator.cxx \
src/playlist/MemorySongEnumerator.hxx \
- src/playlist/cue/CueParser.cxx src/playlist/cue/CueParser.hxx \
src/playlist/plugins/ExtM3uPlaylistPlugin.cxx \
src/playlist/plugins/ExtM3uPlaylistPlugin.hxx \
src/playlist/plugins/M3uPlaylistPlugin.cxx \
src/playlist/plugins/M3uPlaylistPlugin.hxx \
- src/playlist/plugins/CuePlaylistPlugin.cxx \
- src/playlist/plugins/CuePlaylistPlugin.hxx \
- src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \
- src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx \
+ src/playlist/plugins/PlsPlaylistPlugin.cxx \
+ src/playlist/plugins/PlsPlaylistPlugin.hxx \
src/playlist/PlaylistRegistry.cxx src/playlist/PlaylistRegistry.hxx
libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(EXPAT_CFLAGS) \
@@ -1394,6 +1416,15 @@ PLAYLIST_LIBS = \
$(EXPAT_LIBS) \
$(FLAC_LIBS)
+if ENABLE_CUE
+libplaylist_plugins_a_SOURCES += \
+ src/playlist/cue/CueParser.cxx src/playlist/cue/CueParser.hxx \
+ src/playlist/plugins/CuePlaylistPlugin.cxx \
+ src/playlist/plugins/CuePlaylistPlugin.hxx \
+ src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \
+ src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
+endif
+
if ENABLE_DESPOTIFY
libplaylist_plugins_a_SOURCES += \
src/lib/despotify/DespotifyUtils.cxx \
@@ -1409,7 +1440,7 @@ libplaylist_plugins_a_SOURCES += \
PLAYLIST_LIBS += $(YAJL_LIBS)
endif
-if HAVE_EXPAT
+if ENABLE_EXPAT
libplaylist_plugins_a_SOURCES += \
src/lib/expat/ExpatParser.cxx src/lib/expat/ExpatParser.hxx \
src/playlist/plugins/XspfPlaylistPlugin.cxx \
@@ -1420,12 +1451,6 @@ libplaylist_plugins_a_SOURCES += \
src/playlist/plugins/RssPlaylistPlugin.hxx
endif
-if HAVE_GLIB
-libplaylist_plugins_a_SOURCES += \
- src/playlist/plugins/PlsPlaylistPlugin.cxx \
- src/playlist/plugins/PlsPlaylistPlugin.hxx
-endif
-
#
# Filter plugins
#
@@ -1477,7 +1502,8 @@ C_TESTS = \
test/test_mixramp \
test/test_pcm \
test/test_protocol \
- test/test_queue_priority
+ test/test_queue_priority \
+ test/TestIcu
if ENABLE_CURL
C_TESTS += test/test_icy_parser
@@ -1525,11 +1551,11 @@ if ENABLE_ARCHIVE
noinst_PROGRAMS += test/visit_archive
endif
-if HAVE_ID3TAG
+if ENABLE_ID3TAG
noinst_PROGRAMS += test/dump_rva2
endif
-if HAVE_ALSA
+if ENABLE_ALSA
# this debug program is still ALSA specific
noinst_PROGRAMS += test/read_mixer
endif
@@ -1537,6 +1563,7 @@ endif
test_read_conf_LDADD = \
libconf.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
@@ -1577,13 +1604,14 @@ test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
src/TagSave.cxx \
src/SongFilter.cxx
-if HAVE_LIBUPNP
+if ENABLE_UPNP
test_DumpDatabase_SOURCES += src/lib/expat/ExpatParser.cxx
endif
test_run_storage_LDADD = \
$(STORAGE_LIBS) \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libevent.a \
libthread.a \
libsystem.a \
@@ -1606,6 +1634,7 @@ test_run_input_LDADD = \
libevent.a \
libthread.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.cxx \
@@ -1630,11 +1659,12 @@ test_run_neighbor_explorer_LDADD = $(AM_LDADD) \
libconf.a \
libevent.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libthread.a \
libutil.a
-if HAVE_LIBUPNP
+if ENABLE_UPNP
test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx
endif
@@ -1657,6 +1687,7 @@ test_visit_archive_LDADD = \
libevent.a \
libthread.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
$(GLIB_LIBS)
test_visit_archive_SOURCES = test/visit_archive.cxx \
@@ -1667,7 +1698,7 @@ test_visit_archive_SOURCES = test/visit_archive.cxx \
endif
-if HAVE_ZLIB
+if ENABLE_ZLIB
noinst_PROGRAMS += test/run_gzip test/run_gunzip
@@ -1682,6 +1713,7 @@ test_run_gunzip_LDADD = \
$(GLIB_LIBS) \
libutil.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a
endif
@@ -1693,6 +1725,7 @@ test_dump_text_file_LDADD = \
libconf.a \
libevent.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libthread.a \
libutil.a \
@@ -1714,6 +1747,7 @@ test_dump_playlist_LDADD = \
libevent.a \
libthread.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
libpcm.a \
@@ -1729,7 +1763,7 @@ test_dump_playlist_SOURCES = test/dump_playlist.cxx \
src/AudioFormat.cxx src/CheckAudioFormat.cxx \
src/DetachedSong.cxx
-if HAVE_FLAC
+if ENABLE_FLAC
test_dump_playlist_SOURCES += \
src/ReplayGainInfo.cxx \
src/decoder/plugins/FlacMetadata.cxx
@@ -1745,6 +1779,7 @@ test_run_decoder_LDADD = \
libevent.a \
libthread.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
@@ -1771,6 +1806,7 @@ test_read_tags_LDADD = \
libevent.a \
libthread.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
@@ -1783,7 +1819,7 @@ test_read_tags_SOURCES = test/read_tags.cxx \
src/AudioFormat.cxx src/CheckAudioFormat.cxx \
$(DECODER_SRC)
-if HAVE_ID3TAG
+if ENABLE_ID3TAG
test_dump_rva2_LDADD = \
$(TAG_LIBS) \
libutil.a \
@@ -1797,6 +1833,7 @@ test_run_filter_LDADD = \
$(FILTER_LIBS) \
libconf.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
@@ -1825,12 +1862,13 @@ test_run_encoder_LDADD = \
libpcm.a \
libthread.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
endif
-if ENABLE_VORBIS_ENCODER
+if ENABLE_VORBISENC
noinst_PROGRAMS += test/test_vorbis_encoder
test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \
test/stdbin.h \
@@ -1847,6 +1885,7 @@ test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \
$(TAG_LIBS) \
libconf.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
@@ -1886,6 +1925,7 @@ test_run_normalize_LDADD = \
$(GLIB_LIBS)
test_run_convert_SOURCES = test/run_convert.cxx \
+ src/config/ConfigError.cxx \
src/Log.cxx src/LogBackend.cxx \
src/AudioFormat.cxx \
src/CheckAudioFormat.cxx \
@@ -1905,6 +1945,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
libconf.a \
libevent.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libthread.a \
libutil.a \
@@ -1934,6 +1975,7 @@ test_read_mixer_LDADD = \
libconf.a \
libevent.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS)
@@ -1971,6 +2013,9 @@ test_run_inotify_LDADD = \
endif
test_test_util_SOURCES = \
+ test/DivideStringTest.hxx \
+ test/SplitStringTest.hxx \
+ test/UriUtilTest.hxx \
test/TestCircularBuffer.hxx \
test/test_util.cxx
test_test_util_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
@@ -2069,6 +2114,7 @@ test_test_translate_song_LDADD = \
$(STORAGE_LIBS) \
libtag.a \
$(FS_LIBS) \
+ $(ICU_LDADD) \
libsystem.a \
libutil.a \
$(GLIB_LIBS) \
@@ -2097,6 +2143,15 @@ test_test_queue_priority_LDADD = \
libutil.a \
$(CPPUNIT_LIBS)
+test_TestIcu_SOURCES = \
+ test/TestIcu.cxx
+test_TestIcu_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
+test_TestIcu_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
+test_TestIcu_LDADD = \
+ $(ICU_LDADD) \
+ libutil.a \
+ $(CPPUNIT_LIBS)
+
if ENABLE_DSD
noinst_PROGRAMS += src/pcm/dsd2pcm/dsd2pcm
@@ -2186,4 +2241,4 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
android/src/Bridge.java \
android/src/Loader.java \
android/src/Main.java \
- src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico
+ win32/res/mpd.rc.in win32/res/mpd.ico
diff --git a/NEWS b/NEWS
index 14676a5a7..c675aef18 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,24 @@
+ver 0.20 (not yet released)
+* protocol
+ - "commands" returns playlist commands only if playlist_directory configured
+ - "search"/"find" have a "window" parameter
+ - report song duration with milliseconds precision
+ - "sticker find" can match sticker values
+* tags
+ - ape, ogg: drop support for non-standard tag "album artist"
+ affected filetypes: vorbis, flac, opus & all files with ape2 tags
+ (most importantly some mp3s)
+* decoder
+ - ffmpeg: support ReplayGain and MixRamp
+ - ffmpeg: support stream tags
+* output
+ - jack: reduce CPU usage
+ - pulse: set channel map to WAVE-EX
+* mixer
+ - null: new plugin
+* reset song priority on playback
+* remove dependency on GLib
+
ver 0.19.8 (not yet released)
* input
- mms: reduce delay at the beginning of playback
diff --git a/android/build.py b/android/build.py
index 8455dd6a9..d937e8d39 100755
--- a/android/build.py
+++ b/android/build.py
@@ -22,6 +22,16 @@ if not os.path.isdir(ndk_path):
print("NDK not found in", ndk_path, file=sys.stderr)
sys.exit(1)
+# select the NDK target
+ndk_arch = 'arm'
+host_arch = 'arm-linux-androideabi'
+android_abi = 'armeabi-v7a'
+ndk_platform = 'android-14'
+
+# select the NDK compiler
+gcc_version = '4.9'
+llvm_version = '3.5'
+
# the path to the MPD sources
mpd_path = os.path.dirname(os.path.dirname(sys.argv[0]))
@@ -29,8 +39,9 @@ mpd_path = os.path.dirname(os.path.dirname(sys.argv[0]))
lib_path = os.path.abspath('lib')
tarball_path = lib_path
src_path = os.path.join(lib_path, 'src')
-build_path = os.path.join(lib_path, 'build')
-root_path = os.path.join(lib_path, 'root')
+arch_path = os.path.join(lib_path, host_arch)
+build_path = os.path.join(arch_path, 'build')
+root_path = os.path.join(arch_path, 'root')
# build host configuration
build_arch = 'linux-x86_64'
@@ -39,16 +50,6 @@ build_arch = 'linux-x86_64'
# one on the build host
os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
-# select the NDK compiler
-gcc_version = '4.9'
-llvm_version = '3.5'
-
-# select the NDK target
-ndk_arch = 'arm'
-host_arch = 'arm-linux-androideabi'
-android_abi = 'armeabi-v7a'
-ndk_platform = 'android-14'
-
# set up the NDK toolchain
gcc_toolchain = os.path.join(ndk_path, 'toolchains', host_arch + '-' + gcc_version, 'prebuilt', build_arch)
@@ -349,7 +350,6 @@ thirdparty_libs = [
'--enable-gpl',
'--enable-small',
'--disable-pthreads',
- '--disable-runtime-cpudetect',
'--disable-programs',
'--disable-doc',
'--disable-avdevice',
@@ -423,10 +423,6 @@ configure = [
'--disable-glib',
'--disable-icu',
- # disabled for now because these features require GLib:
- '--disable-httpd-output',
- '--disable-vorbis-encoder',
-
] + configure_args
subprocess.check_call(configure)
diff --git a/configure.ac b/configure.ac
index 5b4d577ae..79a4952b0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,10 +1,10 @@
AC_PREREQ(2.60)
-AC_INIT(mpd, 0.19.8, musicpd-dev-team@lists.sourceforge.net)
+AC_INIT(mpd, 0.20, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
-VERSION_MINOR=19
-VERSION_REVISION=8
+VERSION_MINOR=20
+VERSION_REVISION=0
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
@@ -97,7 +97,7 @@ linux*)
mingw32* | windows*)
AC_CONFIG_FILES([
- src/win32/mpd_win32_rc.rc
+ win32/res/mpd.rc
])
AC_CHECK_TOOL(WINDRES, windres)
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"
@@ -193,10 +193,27 @@ dnl ---------------------------------------------------------------------------
dnl Header/Library Checks
dnl ---------------------------------------------------------------------------
+AX_PTHREAD
+LIBS="$PTHREAD_LIBS $LIBS"
+AM_CFLAGS="$AM_CFLAGS $PTHREAD_CFLAGS"
+AM_CXXFLAGS="$AM_CXXFLAGS $PTHREAD_CFLAGS"
+
+MPD_WITH_LIBRARY([PTHREAD],
+ [AC_CHECK_FUNCS([pthread_setname_np])])
+
AC_SEARCH_LIBS([clock_gettime], [rt])
-AC_SEARCH_LIBS([syslog], [bsd socket inet],
- [AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])])
+AC_ARG_ENABLE(syslog,
+ AS_HELP_STRING([--enable-syslog],
+ [enable syslog support (default: auto)]),,
+ enable_syslog=auto)
+MPD_AUTO(syslog, [syslog support], [syslog() not available],
+ [AC_SEARCH_LIBS([syslog], [bsd socket inet],
+ [found_syslog=yes],
+ [found_syslog=no])])
+if test x$enable_syslog = xyes; then
+ AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])
+fi
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl])
@@ -220,18 +237,6 @@ AC_CHECK_HEADERS(valgrind/memcheck.h)
AC_CHECK_HEADERS([sys/prctl.h], AC_CHECK_FUNCS([prctl]))
-AX_PTHREAD
-LIBS="$PTHREAD_LIBS $LIBS"
-AM_CFLAGS="$AM_CFLAGS $PTHREAD_CFLAGS"
-AM_CXXFLAGS="$AM_CXXFLAGS $PTHREAD_CFLAGS"
-
-AC_CHECK_LIB([pthread], [pthread_setname_np],
- [have_pthread_setname_np=yes],
- [have_pthread_setname_np=no])
-if test x$have_pthread_setname_np = xyes; then
- AC_DEFINE(HAVE_PTHREAD_SETNAME_NP, 1, [Is pthread_setname_np() available?])
-fi
-
dnl ---------------------------------------------------------------------------
dnl Event loop selection
dnl ---------------------------------------------------------------------------
@@ -282,90 +287,23 @@ AC_ARG_ENABLE(database,
AS_HELP_STRING([--enable-database],
[enable support for the music database]),,
enable_database=yes)
-AM_CONDITIONAL(ENABLE_DATABASE, test x$enable_database = xyes)
+MPD_DEFINE_CONDITIONAL(enable_database, ENABLE_DATABASE,
+ [the music database])
if test x$enable_database = xyes; then
database_auto=auto
- AC_DEFINE(ENABLE_DATABASE, 1, [Define to enable the music database])
else
database_auto=no
fi
-AC_ARG_ENABLE(libmpdclient,
- AS_HELP_STRING([--enable-libmpdclient],
- [enable support for the MPD client]),,
- enable_libmpdclient=auto)
-MPD_DEPENDS([enable_libmpdclient], [enable_database],
- [Cannot use --enable-libmpdclient with --disable-database])
-
-AC_ARG_ENABLE(expat,
- AS_HELP_STRING([--enable-expat],
- [enable the expat XML parser]),,
- enable_expat=auto)
-
-AC_ARG_ENABLE(upnp,
- AS_HELP_STRING([--enable-upnp],
- [enable UPnP client support (default: auto)]),,
- enable_upnp=auto)
-MPD_DEPENDS([enable_upnp], [enable_database],
- [Cannot use --enable-upnp with --disable-database])
-
-AC_ARG_ENABLE(adplug,
- AS_HELP_STRING([--enable-adplug],
- [enable the AdPlug decoder plugin (default: auto)]),,
- enable_adplug=auto)
-
-AC_ARG_ENABLE(alsa,
- AS_HELP_STRING([--enable-alsa], [enable ALSA support]),,
- [enable_alsa=$linux_auto])
-
-AC_ARG_ENABLE(roar,
- AS_HELP_STRING([--enable-roar],
- [enable support for RoarAudio]),,
- [enable_roar=auto])
-
-AC_ARG_ENABLE(ao,
- AS_HELP_STRING([--enable-ao],
- [enable support for libao]),,
- enable_ao=auto)
-MPD_DEPENDS([enable_ao], [enable_glib],
- [Cannot use --enable-ao with --disable-glib])
-
-AC_ARG_ENABLE(audiofile,
- AS_HELP_STRING([--enable-audiofile],
- [enable audiofile support (WAV and others)]),,
- enable_audiofile=auto)
-
-AC_ARG_ENABLE(zlib,
- AS_HELP_STRING([--enable-zlib],
- [enable zlib support (default: auto)]),,
- enable_zlib=auto)
-
-AC_ARG_ENABLE(bzip2,
- AS_HELP_STRING([--enable-bzip2],
- [enable bzip2 archive support (default: auto)]),,
- enable_bzip2=auto)
-
-AC_ARG_ENABLE(cdio-paranoia,
- AS_HELP_STRING([--enable-cdio-paranoia],
- [enable support for audio CD support]),,
- enable_cdio_paranoia=auto)
-MPD_DEPENDS([enable_cdio_paranoia], [enable_glib],
- [Cannot use --enable-cdio-paranoia with --disable-glib])
-
-AC_ARG_ENABLE(curl,
- AS_HELP_STRING([--enable-curl],
- [enable support for libcurl HTTP streaming (default: auto)]),,
- [enable_curl=auto])
-
-AC_ARG_ENABLE(smbclient,
- AS_HELP_STRING([--enable-smbclient],
- [enable support for libsmbclient (default: auto)]),,
- [enable_smbclient=auto])
-
-AC_ARG_ENABLE(nfs,
- AS_HELP_STRING([--enable-nfs],
- [enable support for libnfs (default: auto)]),,
- [enable_nfs=auto])
+default_enable_daemon=yes
+if test x$host_is_android = xyes || test x$host_is_android = xyes; then
+ default_enable_daemon=no
+fi
+AC_ARG_ENABLE(daemon,
+ AS_HELP_STRING([--enable-daemon],
+ [enable daemonization (default: enabled)]),,
+ enable_daemon=$default_enable_daemon)
+MPD_DEFINE_CONDITIONAL(enable_daemon, ENABLE_DAEMON, [Enable daemonization?])
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
@@ -382,44 +320,15 @@ AC_ARG_ENABLE(dsd,
[enable DSD decoder (default: enable)]),,
[enable_dsd=yes])
-AC_ARG_ENABLE(ffmpeg,
- AS_HELP_STRING([--enable-ffmpeg],
- [enable FFMPEG support]),,
- enable_ffmpeg=auto)
-
AC_ARG_ENABLE(fifo,
AS_HELP_STRING([--disable-fifo],
[disable support for writing audio to a FIFO (default: enable)]),,
enable_fifo=yes)
-AC_ARG_ENABLE(flac,
- AS_HELP_STRING([--enable-flac],
- [enable FLAC decoder]),,
- enable_flac=auto)
-
-AC_ARG_ENABLE(fluidsynth,
- AS_HELP_STRING([--enable-fluidsynth],
- [enable MIDI support via fluidsynth (default: auto)]),,
- enable_fluidsynth=auto)
-
-AC_ARG_ENABLE(gme,
- AS_HELP_STRING([--enable-gme],
- [enable Blargg's game music emulator plugin]),,
- enable_gme=auto)
-MPD_DEPENDS([enable_gme], [enable_glib],
- [Cannot use --enable-gme with --disable-glib])
-
AC_ARG_ENABLE(httpd-output,
AS_HELP_STRING([--enable-httpd-output],
[enables the HTTP server output]),,
[enable_httpd_output=auto])
-MPD_DEPENDS([enable_httpd_output], [enable_glib],
- [Cannot use --enable-httpd-output with --disable-glib])
-
-AC_ARG_ENABLE(id3,
- AS_HELP_STRING([--enable-id3],
- [enable id3 support]),,
- enable_id3=auto)
AC_ARG_ENABLE(inotify,
AS_HELP_STRING([--disable-inotify],
@@ -431,91 +340,27 @@ AC_ARG_ENABLE(ipv6,
[disable IPv6 support (default: enable)]),,
[enable_ipv6=yes])
-AC_ARG_ENABLE(iso9660,
- AS_HELP_STRING([--enable-iso9660],
- [enable iso9660 archive support (default: disabled)]),,
- enable_iso9660=no)
-
-AC_ARG_ENABLE(jack,
- AS_HELP_STRING([--enable-jack],
- [enable jack support]),,
- enable_jack=auto)
-MPD_DEPENDS([enable_jack], [enable_glib],
- [Cannot use --enable-jack with --disable-glib])
-
AC_SYS_LARGEFILE
-AC_ARG_ENABLE(despotify,
- AS_HELP_STRING([--enable-despotify],
- [enable support for despotify (default: disable)]),,
- [enable_despotify=no])
-
AC_ARG_ENABLE(soundcloud,
AS_HELP_STRING([--enable-soundcloud],
[enable support for soundcloud.com]),,
[enable_soundcloud=auto])
-MPD_DEPENDS([enable_soundcloud], [enable_glib],
- [Cannot use --enable-soundcloud with --disable-glib])
-
-AC_ARG_ENABLE(lame-encoder,
- AS_HELP_STRING([--enable-lame-encoder],
- [enable the LAME mp3 encoder]),,
- enable_lame_encoder=auto)
AC_ARG_ENABLE([libwrap],
AS_HELP_STRING([--enable-libwrap], [use libwrap]),,
[enable_libwrap=auto])
-AC_ARG_ENABLE(lsr,
- AS_HELP_STRING([--enable-lsr],
- [enable libsamplerate support]),,
- enable_lsr=auto)
-
-AC_ARG_ENABLE(soxr,
- AS_HELP_STRING([--enable-soxr],
- [enable the libsoxr resampler]),,
- enable_soxr=auto)
-
-AC_ARG_ENABLE(mad,
- AS_HELP_STRING([--enable-mad],
- [enable libmad mp3 decoder plugin]),,
- enable_mad=auto)
-
AC_ARG_ENABLE(mikmod,
AS_HELP_STRING([--enable-mikmod],
[enable the mikmod decoder (default: disable)]),,
enable_mikmod=no)
-AC_ARG_ENABLE(mms,
- AS_HELP_STRING([--enable-mms],
- [enable the MMS protocol with libmms]),,
- [enable_mms=auto])
-
-AC_ARG_ENABLE(modplug,
- AS_HELP_STRING([--enable-modplug],
- [enable modplug decoder plugin]),,
- enable_modplug=auto)
-
-AC_ARG_ENABLE(mpc,
- AS_HELP_STRING([--enable-mpc],
- [disable musepack (MPC) support (default: auto)]),,
- enable_mpc=auto)
-
-AC_ARG_ENABLE(mpg123,
- AS_HELP_STRING([--enable-mpg123],
- [enable libmpg123 decoder plugin]),,
- enable_mpg123=auto)
-
AC_ARG_ENABLE(openal,
AS_HELP_STRING([--enable-openal],
[enable OpenAL support (default: auto)]),,
enable_openal=auto)
-AC_ARG_ENABLE(opus,
- AS_HELP_STRING([--enable-opus],
- [enable Opus codec support (default: auto)]),,
- enable_opus=auto)
-
AC_ARG_ENABLE(oss,
AS_HELP_STRING([--disable-oss],
[disable OSS support (default: enable)]),,
@@ -531,11 +376,6 @@ AC_ARG_ENABLE(pipe-output,
[enable support for writing audio to a pipe (default: disable)]),,
enable_pipe_output=no)
-AC_ARG_ENABLE(pulse,
- AS_HELP_STRING([--enable-pulse],
- [enable support for the PulseAudio sound server]),,
- enable_pulse=auto)
-
AC_ARG_ENABLE(recorder-output,
AS_HELP_STRING([--enable-recorder-output],
[enables the recorder file output plugin (default: disable)]),,
@@ -545,41 +385,17 @@ AC_ARG_ENABLE(sidplay,
AS_HELP_STRING([--enable-sidplay],
[enable C64 SID support via libsidplay2]),,
enable_sidplay=auto)
-MPD_DEPENDS([enable_sidplay], [enable_glib],
- [Cannot use --enable-sidplay with --disable-glib])
-
-AC_ARG_ENABLE(shine-encoder,
- AS_HELP_STRING([--enable-shine-encoder],
- [enables shine encoder]),,
- [enable_shine_encoder=auto])
AC_ARG_ENABLE(shout,
AS_HELP_STRING([--enable-shout],
[enables the shoutcast streaming output]),,
[enable_shout=auto])
-AC_ARG_ENABLE(sndfile,
- AS_HELP_STRING([--enable-sndfile],
- [enable sndfile support]),,
- enable_sndfile=auto)
-
AC_ARG_ENABLE(solaris_output,
AS_HELP_STRING([--enable-solaris-output],
[enables the Solaris /dev/audio output]),,
[enable_solaris_output=$host_is_solaris])
-AC_ARG_ENABLE(sqlite,
- AS_HELP_STRING([--enable-sqlite],
- [enable support for the SQLite database]),,
- [enable_sqlite=$database_auto])
-MPD_DEPENDS([enable_sqlite], [enable_glib],
- [Cannot use --enable-sqlite with --disable-glib])
-
-AC_ARG_ENABLE(systemd-daemon,
- AS_HELP_STRING([--enable-systemd-daemon],
- [use the systemd daemon library (default=auto)]),,
- [enable_systemd_daemon=$linux_auto])
-
AC_ARG_ENABLE(tcp,
AS_HELP_STRING([--disable-tcp],
[disable support for clients connecting via TCP (default: enable)]),,
@@ -590,16 +406,6 @@ AC_ARG_ENABLE(test,
[build the test programs (default: disabled)]),,
enable_test=no)
-AC_ARG_WITH(tremor,
- AS_HELP_STRING([--with-tremor=PFX],
- [use Tremor (vorbisidec) integer Ogg Vorbis decoder (with optional prefix)]),,
- with_tremor=no)
-
-AC_ARG_ENABLE(twolame-encoder,
- AS_HELP_STRING([--enable-twolame-encoder],
- [enable the TwoLAME mp2 encoder]),,
- enable_twolame_encoder=auto)
-
AC_ARG_ENABLE(un,
AS_HELP_STRING([--disable-un],
[disable support for clients connecting via unix domain sockets (default: enable)]),,
@@ -610,56 +416,21 @@ AC_ARG_ENABLE(vorbis,
[enable Ogg Vorbis decoder]),,
enable_vorbis=auto)
-AC_ARG_ENABLE(vorbis-encoder,
- AS_HELP_STRING([--enable-vorbis-encoder],
- [enable the Ogg Vorbis encoder]),,
- [enable_vorbis_encoder=auto])
-MPD_DEPENDS([enable_vorbis_encoder], [enable_glib],
- [Cannot use --enable-vorbis-encoder with --disable-glib])
-
AC_ARG_ENABLE(wave-encoder,
AS_HELP_STRING([--enable-wave-encoder],
[enable the PCM wave encoder]),,
enable_wave_encoder=yes)
-AC_ARG_ENABLE(wavpack,
- AS_HELP_STRING([--enable-wavpack],
- [enable WavPack support]),,
- enable_wavpack=auto)
-MPD_DEPENDS([enable_wavpack], [enable_glib],
- [Cannot use --enable-wavpack with --disable-glib])
-
AC_ARG_ENABLE(werror,
AS_HELP_STRING([--enable-werror],
[treat warnings as errors (default: disabled)]),,
enable_werror=no)
-AC_ARG_ENABLE(wildmidi,
- AS_HELP_STRING([--enable-wildmidi],
- [enable MIDI support via wildmidi (default: auto)]),,
- enable_wildmidi=auto)
-
AC_ARG_WITH(zeroconf,
AS_HELP_STRING([--with-zeroconf=@<:@auto|avahi|bonjour|no@:>@],
[enable zeroconf backend (default=auto)]),,
with_zeroconf="auto")
-AC_ARG_ENABLE(zzip,
- AS_HELP_STRING([--enable-zzip],
- [enable zip archive support (default: disabled)]),,
- enable_zzip=no)
-
-
-AC_ARG_WITH(tremor-libraries,
- AS_HELP_STRING([--with-tremor-libraries=DIR],
- [directory where Tremor library is installed (optional)]),,
- tremor_libraries="")
-
-AC_ARG_WITH(tremor-includes,
- AS_HELP_STRING([--with-tremor-includes=DIR],
- [directory where Tremor header files are installed (optional)]),,
- tremor_includes="")
-
dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries
dnl ---------------------------------------------------------------------------
@@ -693,28 +464,31 @@ AC_ARG_ENABLE(icu,
if test x$enable_icu = xyes; then
PKG_CHECK_MODULES([ICU], [icu-i18n],,
[AC_MSG_ERROR([libicu not found])])
-
- AC_DEFINE(HAVE_ICU, 1, [Define if libicu is used])
fi
-AM_CONDITIONAL(HAVE_ICU, test x$enable_icu = xyes)
+
+MPD_DEFINE_CONDITIONAL(enable_icu, HAVE_ICU, [libicu])
AC_ARG_ENABLE(glib,
AS_HELP_STRING([--enable-glib],
- [enable GLib usage (default: enabled)]),,
- enable_glib=yes)
+ [enable GLib (default: auto)]),,
+ enable_glib=auto)
-if test x$enable_glib = xyes; then
- PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
- [AC_MSG_ERROR([GLib 2.28 is required])])
+if test x$enable_glib != xno; then
+ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32],
+ [found_glib=yes],
+ [found_glib=no])
+
+ MPD_AUTO_RESULT([glib], [Glib], [GLib not found])
+fi
+if test x$enable_glib = xyes; then
if test x$GCC = xyes; then
# suppress warnings in the GLib headers
GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
fi
-
- AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used])
fi
-AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
+
+MPD_DEFINE_CONDITIONAL(enable_glib, HAVE_GLIB, [GLib])
dnl ---------------------------------------------------------------------------
dnl Protocol Options
@@ -765,12 +539,9 @@ if
AC_MSG_ERROR([No client interfaces configured!])
fi
-MPD_AUTO_PKG(systemd_daemon, SYSTEMD_DAEMON, libsystemd-daemon,
- [systemd activation], [libsystemd-daemon not found])
-AM_CONDITIONAL(ENABLE_SYSTEMD_DAEMON, test x$enable_systemd_daemon = xyes)
-if test x$enable_systemd_daemon = xyes; then
- AC_DEFINE([ENABLE_SYSTEMD_DAEMON], 1, [Define to use the systemd daemon library])
-fi
+MPD_ENABLE_AUTO_PKG(systemd_daemon, SYSTEMD_DAEMON, libsystemd-daemon,
+ [systemd socket activation], [libsystemd-daemon not found],
+ [$linux_auto])
dnl ---------------------------------------------------------------------------
dnl LIBC Features
@@ -784,22 +555,14 @@ dnl Miscellaneous Libraries
dnl ---------------------------------------------------------------------------
dnl -------------------------------- libmpdclient --------------------------------
-MPD_AUTO_PKG(libmpdclient, LIBMPDCLIENT, [libmpdclient >= 2.2],
- [MPD client library], [libmpdclient not found])
-if test x$enable_libmpdclient = xyes; then
- AC_DEFINE(HAVE_LIBMPDCLIENT, 1, [Define to use libmpdclient])
-fi
-
-AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes)
+MPD_ENABLE_AUTO_PKG_DEPENDS(libmpdclient, LIBMPDCLIENT,
+ [libmpdclient >= 2.2],
+ [MPD client library], [libmpdclient not found], [],
+ [enable_database], [Cannot use --enable-libmpdclient with --disable-database])
dnl -------------------------------- expat --------------------------------
-MPD_AUTO_PKG(expat, EXPAT, [expat],
+MPD_ENABLE_AUTO_PKG(expat, EXPAT, [expat],
[expat XML parser], [expat not found])
-if test x$enable_expat = xyes; then
- AC_DEFINE(HAVE_EXPAT, 1, [Define to use the expat XML parser])
-fi
-
-AM_CONDITIONAL(HAVE_EXPAT, test x$enable_expat = xyes)
dnl --------------------------------- inotify ---------------------------------
AC_CHECK_FUNCS(inotify_init inotify_init1)
@@ -808,10 +571,7 @@ if test x$ac_cv_func_inotify_init = xno; then
enable_inotify=no
fi
-if test x$enable_inotify = xyes; then
- AC_DEFINE([ENABLE_INOTIFY], 1, [Define to enable inotify support])
-fi
-AM_CONDITIONAL(ENABLE_INOTIFY, test x$enable_inotify = xyes)
+MPD_DEFINE_CONDITIONAL(enable_inotify, ENABLE_INOTIFY, [inotify support])
dnl --------------------------------- libwrap ---------------------------------
if test x$enable_libwrap != xno; then
@@ -851,13 +611,9 @@ dnl Metadata Plugins
dnl ---------------------------------------------------------------------------
dnl -------------------------------- libid3tag --------------------------------
-MPD_AUTO_PKG_LIB(id3, ID3TAG, id3tag, id3tag, id3_file_open, [-lid3tag -lz], [],
- [id3tag], [libid3tag not found])
-if test x$enable_id3 = xyes; then
- AC_DEFINE(HAVE_ID3TAG, 1, [Define to use id3tag])
-fi
-
-AM_CONDITIONAL(HAVE_ID3TAG, test x$enable_id3 = xyes)
+MPD_ENABLE_AUTO_PKG_LIB(id3, ID3TAG,
+ id3tag, id3tag, id3_file_open, [-lid3tag -lz], [],
+ [ID3 support using libid3tag], [libid3tag not found])
dnl ---------------------------------------------------------------------------
dnl Autodiscovery
@@ -883,11 +639,10 @@ esac
MPD_AUTO_PKG(avahi, AVAHI, [avahi-client dbus-1],
[avahi client library], [avahi-client not found])
if test x$enable_avahi = xyes; then
- AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])
with_zeroconf=avahi
fi
-AM_CONDITIONAL(HAVE_AVAHI, test x$enable_avahi = xyes)
+MPD_DEFINE_CONDITIONAL(enable_avahi, HAVE_AVAHI, [Avahi Zeroconf])
enable_bounjour=no
if test x$with_zeroconf != xno; then
@@ -920,107 +675,66 @@ dnl ---------------------------------------------------------------------------
dnl ---------------------------------- sqlite ---------------------------------
-MPD_AUTO_PKG(sqlite, SQLITE, [sqlite3],
- [SQLite database support], [sqlite not found])
-if test x$enable_sqlite = xyes; then
- AC_DEFINE([ENABLE_SQLITE], 1, [Define to enable sqlite database support])
-fi
-
-AM_CONDITIONAL(ENABLE_SQLITE, test x$enable_sqlite = xyes)
+MPD_ENABLE_AUTO_PKG(sqlite, SQLITE, [sqlite3 >= 3.7.3],
+ [SQLite database support], [sqlite not found],
+ [$database_auto])
dnl ---------------------------------------------------------------------------
dnl Converter Plugins
dnl ---------------------------------------------------------------------------
dnl ------------------------------ libsamplerate ------------------------------
-MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.1.3],
+MPD_ENABLE_AUTO_PKG(lsr, LIBSAMPLERATE, [samplerate >= 0.1.3],
[libsamplerate resampling], [libsamplerate not found])
-if test x$enable_lsr = xyes; then
- AC_DEFINE([HAVE_LIBSAMPLERATE], 1,
- [Define to enable libsamplerate])
-fi
-AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes)
dnl ------------------------------ libsoxr ------------------------------------
-MPD_AUTO_PKG(soxr, SOXR, [soxr],
+MPD_ENABLE_AUTO_PKG(soxr, SOXR, [soxr],
[libsoxr resampler], [libsoxr not found])
-if test x$enable_soxr = xyes; then
- AC_DEFINE([HAVE_SOXR], 1, [Define to enable libsoxr])
-fi
-
-AM_CONDITIONAL(HAVE_SOXR, test x$enable_soxr = xyes)
dnl ---------------------------------------------------------------------------
dnl Input Plugins
dnl ---------------------------------------------------------------------------
dnl ----------------------------------- CURL ----------------------------------
-MPD_AUTO_PKG(curl, CURL, [libcurl >= 7.18],
+MPD_ENABLE_AUTO_PKG(curl, CURL, [libcurl >= 7.18],
[libcurl HTTP streaming], [libcurl not found])
-if test x$enable_curl = xyes; then
- AC_DEFINE(ENABLE_CURL, 1, [Define when libcurl is used for HTTP streaming])
-fi
-AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes)
dnl ----------------------------------- smbclient -----------------------------
-MPD_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
+MPD_ENABLE_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
[smbclient], [smbc_init], [-lsmbclient], [],
[smbclient input plugin], [libsmbclient not found])
-if test x$enable_smbclient = xyes; then
- AC_DEFINE(ENABLE_SMBCLIENT, 1, [Define when libsmbclient is used])
-fi
-AM_CONDITIONAL(ENABLE_SMBCLIENT, test x$enable_smbclient = xyes)
dnl ----------------------------------- NFS -----------------------------
-MPD_AUTO_PKG(nfs, NFS, [libnfs],
+MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs],
[NFS input plugin], [libnfs not found])
-if test x$enable_nfs = xyes; then
- AC_DEFINE(ENABLE_NFS, 1, [Define when libnfs is used])
-fi
-AM_CONDITIONAL(ENABLE_NFS, test x$enable_nfs = xyes)
dnl --------------------------------- Despotify ---------------------------------
-MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
+MPD_ENABLE_AUTO_PKG(despotify, DESPOTIFY, [despotify],
[Despotify support], [despotify not found])
-if test x$enable_despotify = xyes; then
- AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled])
-fi
-AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes)
dnl --------------------------------- Soundcloud ------------------------------
-if test x$enable_soundcloud != xno; then
- PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
+MPD_AUTO([soundcloud], [soundcloud.com support], [libyajl not found],
+ [PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
[found_soundcloud=yes],
- AC_CHECK_LIB([yajl], [yajl_parse_complete],
- [found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl],
- [found_soundcloud=no]))
-fi
-MPD_AUTO_RESULT([soundcloud], [soundcloud.com support], [libyajl not found])
-if test x$enable_soundcloud = xyes; then
- AC_DEFINE(ENABLE_SOUNDCLOUD, 1, [Define when soundcloud is enabled])
-fi
-AM_CONDITIONAL(ENABLE_SOUNDCLOUD, test x$enable_soundcloud = xyes)
-AC_SUBST(YAJL_LIBS)
+ [found_soundcloud=no])])
+MPD_DEFINE_CONDITIONAL(enable_soundcloud, ENABLE_SOUNDCLOUD,
+ [soundcloud.com support])
dnl ---------------------------------- cdio ---------------------------------
-MPD_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia],
- [libcdio_paranoia audio CD library], [libcdio_paranoia not found])
+MPD_ENABLE_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia],
+ [libcdio_paranoia input plugin], [libcdio_paranoia not found])
if test x$enable_cdio_paranoia = xyes; then
- AC_DEFINE([ENABLE_CDIO_PARANOIA], 1,
- [Define to enable libcdio_paranoia support])
AC_CHECK_HEADERS(cdio/paranoia/paranoia.h)
fi
-AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes)
-
-dnl ---------------------------------- libmms ---------------------------------
-MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4],
+MPD_ENABLE_AUTO_PKG(mms, MMS, [libmms >= 0.4],
[libmms mms:// protocol support], [libmms not found])
-if test x$enable_mms = xyes; then
- AC_DEFINE(ENABLE_MMS, 1,
- [Define when libmms is used for the MMS protocol])
-fi
-AM_CONDITIONAL(ENABLE_MMS, test x$enable_mms = xyes)
+
+dnl ---------------------------------------------------------------------------
+dnl Playlist Plugins
+dnl ---------------------------------------------------------------------------
+
+MPD_ARG_ENABLE(cue, CUE, [CUE sheet parser], yes)
dnl ---------------------------------------------------------------------------
dnl Neighbor Plugins
@@ -1040,24 +754,18 @@ if test x$enable_neighbor_plugins = xauto; then
fi
fi
-if test x$enable_neighbor_plugins = xyes; then
- AC_DEFINE(ENABLE_NEIGHBOR_PLUGINS, 1,
- [Define to enable support for neighbor discovery])
-fi
-AM_CONDITIONAL(ENABLE_NEIGHBOR_PLUGINS, test x$enable_neighbor_plugins = xyes)
+MPD_DEFINE_CONDITIONAL(enable_neighbor_plugins, ENABLE_NEIGHBOR_PLUGINS,
+ [neighbor discovery])
dnl ---------------------------------------------------------------------------
dnl Archive Plugins
dnl ---------------------------------------------------------------------------
dnl --------------------------------- iso9660 ---------------------------------
-MPD_AUTO_PKG(iso9660, ISO9660, [libiso9660],
- [libiso9660 archive library], [libiso9660 not found])
+MPD_ENABLE_AUTO_PKG(iso9660, ISO9660, [libiso9660],
+ [libiso9660 archive plugin], [libiso9660 not found])
-AM_CONDITIONAL(HAVE_ISO9660, test x$enable_iso9660 = xyes)
if test x$enable_iso9660 = xyes; then
- AC_DEFINE(HAVE_ISO9660, 1, [Define to have ISO9660 archive support])
-
AC_PATH_PROG(MKISOFS, mkisofs, no)
else
MKISOFS="no"
@@ -1067,23 +775,15 @@ AM_CONDITIONAL(ENABLE_ISO9660_TEST, test x$MKISOFS != xno)
dnl ---------------------------------- zlib ---------------------------------
-MPD_AUTO_PKG(zlib, ZLIB, [zlib],
+MPD_ENABLE_AUTO_PKG(zlib, ZLIB, [zlib],
[zlib support], [zlib not found])
-AM_CONDITIONAL(HAVE_ZLIB, test x$enable_zlib = xyes)
-if test x$enable_zlib = xyes; then
- AC_DEFINE(HAVE_ZLIB, 1, [Define to enable zlib support])
-fi
-
dnl ---------------------------------- libbz2 ---------------------------------
-MPD_AUTO_LIB(bzip2, BZ2, bz2, BZ2_bzDecompressInit, [-lbz2], [],
- [bzip2], [libbz2 not found])
+MPD_ENABLE_AUTO_LIB(bzip2, BZ2, bz2, BZ2_bzDecompressInit, [-lbz2], [],
+ [bzip2 archive plugin], [libbz2 not found])
-AM_CONDITIONAL(HAVE_BZ2, test x$enable_bzip2 = xyes)
if test x$enable_bzip2 = xyes; then
- AC_DEFINE(HAVE_BZ2, 1, [Define to have bz2 archive support])
-
AC_PATH_PROG(BZIP2, bzip2, no)
else
BZIP2="no"
@@ -1102,21 +802,16 @@ if test x$enable_expat = xno; then
fi
fi
-MPD_AUTO_PKG(upnp, UPNP, [libupnp],
- [UPnP client support], [libupnp not found])
-if test x$enable_upnp = xyes; then
- AC_DEFINE(HAVE_LIBUPNP, 1, [Define when libupnp is used])
-fi
-AM_CONDITIONAL(HAVE_LIBUPNP, test x$enable_upnp = xyes)
+MPD_ENABLE_AUTO_PKG_DEPENDS(upnp, UPNP, [libupnp],
+ [UPnP client support], [libupnp not found], [],
+ [enable_database], [Cannot use --enable-upnp with --disable-database])
dnl --------------------------------- libzzip ---------------------------------
-MPD_AUTO_PKG(zzip, ZZIP, [zziplib >= 0.13],
- [libzzip archive library], [libzzip not found])
+MPD_ENABLE_AUTO_PKG(zzip, ZZIP, [zziplib >= 0.13],
+ [libzzip archive library], [libzzip not found],
+ [no])
-AM_CONDITIONAL(HAVE_ZZIP, test x$enable_zzip = xyes)
if test x$enable_zzip = xyes; then
- AC_DEFINE(HAVE_ZZIP, 1, [Define to have zip archive support])
-
AC_PATH_PROG(ZIP, zip, no)
else
ZIP="no"
@@ -1130,96 +825,59 @@ if
test x$enable_zzip = xyes ||
test x$enable_iso9660 = xyes; then
enable_archive=yes
- AC_DEFINE(ENABLE_ARCHIVE, 1, [The archive API is available])
else
enable_archive=no
fi
-AM_CONDITIONAL(ENABLE_ARCHIVE, test x$enable_archive = xyes)
+MPD_DEFINE_CONDITIONAL(enable_archive, ENABLE_ARCHIVE, [the archive API])
dnl ---------------------------------------------------------------------------
dnl Decoder Plugins
dnl ---------------------------------------------------------------------------
dnl -------------------------------- libadplug --------------------------------
-MPD_AUTO_PKG(adplug, ADPLUG, [adplug],
+MPD_ENABLE_AUTO_PKG(adplug, ADPLUG, [adplug],
[AdPlug decoder plugin], [libadplug not found])
-if test x$enable_adplug = xyes; then
- AC_DEFINE(HAVE_ADPLUG, 1, [Define to use libadplug])
-fi
-AM_CONDITIONAL(HAVE_ADPLUG, test x$enable_adplug = xyes)
dnl -------------------------------- audiofile --------------------------------
-MPD_AUTO_PKG(audiofile, AUDIOFILE, [audiofile >= 0.3],
+MPD_ENABLE_AUTO_PKG(audiofile, AUDIOFILE, [audiofile >= 0.3],
[audiofile decoder plugin], [libaudiofile not found])
-AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
-if test x$enable_audiofile = xyes; then
- AC_DEFINE(HAVE_AUDIOFILE, 1, [Define for audiofile support])
-fi
dnl ----------------------------------- DSD -----------------------------------
-if test x$enable_dsd = xyes; then
- AC_DEFINE(ENABLE_DSD, 1, [Define for the DSD decoder])
-fi
-
-AM_CONDITIONAL(ENABLE_DSD, test x$enable_dsd = xyes)
+MPD_DEFINE_CONDITIONAL(enable_dsd, ENABLE_DSD, [DSD decoder])
dnl ----------------------------------- FAAD ----------------------------------
-AM_PATH_FAAD()
-
-AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes)
+MPD_ENABLE_AUTO_LIB(aac, FAAD, faad, NeAACDecOpen, [-lfaad], [],
+ [FAAD decoder plugin], [libfaad not found])
dnl ---------------------------------- ffmpeg ---------------------------------
-MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 53.17 libavcodec >= 53.25 libavutil >= 51.17],
+MPD_ENABLE_AUTO_PKG(ffmpeg, FFMPEG,
+ [libavformat >= 53.17 libavcodec >= 53.25 libavutil >= 51.17],
[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
-if test x$enable_ffmpeg = xyes; then
- AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support])
-fi
-
-AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes)
-
dnl ----------------------------------- FLAC ----------------------------------
-MPD_AUTO_PKG(flac, FLAC, [flac >= 1.2],
+MPD_ENABLE_AUTO_PKG(flac, FLAC, [flac >= 1.2],
[FLAC decoder], [libFLAC not found])
-if test x$enable_flac = xyes; then
- AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support])
-fi
-
-AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes)
-
enable_flac_encoder=$enable_flac
dnl -------------------------------- FluidSynth -------------------------------
-MPD_AUTO_PKG(fluidsynth, FLUIDSYNTH, [fluidsynth >= 1.1],
- [fluidsynth decoder], [fluidsynth not found])
-
-if test x$enable_fluidsynth = xyes; then
- AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support])
-fi
-
-AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes)
+MPD_ENABLE_AUTO_PKG(fluidsynth, FLUIDSYNTH, [fluidsynth >= 1.1],
+ [fluidsynth MIDI decoder plugin], [fluidsynth not found])
dnl ---------------------------------- libgme ---------------------------------
-MPD_AUTO_PKG_LIB(gme, GME, [libgme], gme, gme_open_file, [-lgme -lstdc++], [],
- [gme decoder plugin], [libgme not found])
-AM_CONDITIONAL(HAVE_GME, test x$enable_gme = xyes)
-if test x$enable_gme = xyes; then
- AC_DEFINE(HAVE_GME, 1, [Define for gme support])
-fi
+
+MPD_ENABLE_AUTO_PKG_LIB(gme, GME, [libgme],
+ gme, gme_open_file, [-lgme -lstdc++], [],
+ [Game Music Emulator decoder plugin], [libgme not found])
dnl ---------------------------------- libmad ---------------------------------
-MPD_AUTO_PKG_LIB(mad, MAD, [mad],
+MPD_ENABLE_AUTO_PKG_LIB(mad, MAD, [mad],
mad, mad_stream_init, [-lmad], [],
[libmad MP3 decoder plugin], [libmad not found])
-if test x$enable_mad = xyes; then
- AC_DEFINE(HAVE_MAD, 1, [Define to use libmad])
-fi
-AM_CONDITIONAL(HAVE_MAD, test x$enable_mad = xyes)
enable_shout2="$enable_shout"
MPD_AUTO_PKG(shout, SHOUT, [shout],
@@ -1229,12 +887,8 @@ if test x$enable_shout = xyes && test x$enable_shout2 = xauto; then
fi
dnl -------------------------------- libmpg123 --------------------------------
-MPD_AUTO_PKG(mpg123, MPG123, [libmpg123],
+MPD_ENABLE_AUTO_PKG(mpg123, MPG123, [libmpg123],
[libmpg123 decoder plugin], [libmpg123 not found])
-if test x$enable_mpg123 = xyes; then
- AC_DEFINE(HAVE_MPG123, 1, [Define to use libmpg123])
-fi
-AM_CONDITIONAL(HAVE_MPG123, test x$enable_mpg123 = xyes)
dnl -------------------------------- libmikmod --------------------------------
if test x$enable_mikmod = xyes; then
@@ -1251,72 +905,57 @@ fi
AM_CONDITIONAL(ENABLE_MIKMOD_DECODER, test x$enable_mikmod = xyes)
dnl -------------------------------- libmodplug -------------------------------
-MPD_AUTO_PKG(modplug, MODPLUG, [libmodplug],
+MPD_ENABLE_AUTO_PKG(modplug, MODPLUG, [libmodplug],
[modplug decoder plugin], [libmodplug not found])
-if test x$enable_modplug = xyes; then
- AC_DEFINE(HAVE_MODPLUG, 1, [Define for modplug support])
-fi
-AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
-
dnl -------------------------------- libopus ----------------------------------
-MPD_AUTO_PKG(opus, OPUS, [opus ogg],
+MPD_ENABLE_AUTO_PKG(opus, OPUS, [opus ogg],
[opus decoder plugin], [libopus not found])
-if test x$enable_opus = xyes; then
- AC_DEFINE(HAVE_OPUS, 1, [Define to use libopus])
-fi
-AM_CONDITIONAL(HAVE_OPUS, test x$enable_opus = xyes)
dnl -------------------------------- libsndfile -------------------------------
dnl See above test, which may disable this.
-MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
+MPD_ENABLE_AUTO_PKG(sndfile, SNDFILE, [sndfile],
[libsndfile decoder plugin], [libsndfile not found])
-if test x$enable_sndfile = xyes; then
- AC_DEFINE(ENABLE_SNDFILE, 1, [Define to enable the sndfile decoder plugin])
-fi
-AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
-
dnl --------------------------------- musepack --------------------------------
-MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, mpc_demux_init, [-lmpcdec], [],
- [mpcdec], [libmpcdec not found])
-if test x$enable_mpc = xyes; then
- AC_DEFINE(HAVE_MPCDEC, 1, [Define to use libmpcdec for MPC decoding])
-fi
-AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
+MPD_ENABLE_AUTO_LIB(mpc, MPCDEC, mpcdec, mpc_demux_init, [-lmpcdec], [],
+ [Musepack decoder plugin], [libmpcdec not found])
dnl -------------------------------- Ogg Tremor -------------------------------
+
+AC_ARG_WITH(tremor,
+ AS_HELP_STRING([--with-tremor=PFX],
+ [use Tremor (vorbisidec) integer Ogg Vorbis decoder (with optional prefix)]),,
+ with_tremor=no)
+
+AC_ARG_VAR([TREMOR_CFLAGS], [C compiler flags for Tremor])
+AC_ARG_VAR([TREMOR_LIBS], [linker flags for Tremor])
+
if test x$with_tremor = xyes || test x$with_tremor = xno; then
enable_tremor="$with_tremor"
+ tremor_prefix=""
else
tremor_prefix="$with_tremor"
enable_tremor=yes
fi
if test x$enable_tremor = xyes; then
- if test "x$tremor_libraries" != "x" ; then
- TREMOR_LIBS="-L$tremor_libraries"
- elif test "x$tremor_prefix" != "x" ; then
- TREMOR_LIBS="-L$tremor_prefix/lib"
- fi
- TREMOR_LIBS="$TREMOR_LIBS -lvorbisidec"
- if test "x$tremor_includes" != "x" ; then
- TREMOR_CFLAGS="-I$tremor_includes"
- elif test "x$tremor_prefix" != "x" ; then
+ if test x$TREMOR_CFLAGS = x && test x$tremor_prefix != x; then
TREMOR_CFLAGS="-I$tremor_prefix/include"
fi
- ac_save_CFLAGS="$CFLAGS"
- ac_save_LIBS="$LIBS"
- CFLAGS="$CFLAGS $TREMOR_CFLAGS"
- LIBS="$LIBS $TREMOR_LIBS"
- AC_CHECK_LIB(vorbisidec,ov_read,,enable_tremor=no;
- AC_MSG_WARN([vorbisidec lib needed for ogg support with tremor -- disabling ogg support]))
- CFLAGS="$ac_save_CFLAGS"
- LIBS="$ac_save_LIBS"
-fi
+ if test x$TREMOR_LIBS = x; then
+ TREMOR_LIBS="-lvorbisidec"
+
+ if test x$tremor_prefix != x; then
+ TREMOR_LIBS="-L$tremor_prefix/lib $TREMOR_LIBS"
+ fi
+ fi
+
+ MPD_WITH_LIBRARY([TREMOR],
+ [AC_CHECK_FUNC([ov_read],,
+ [AC_MSG_ERROR([libvorbisidec not found])])])
-if test x$enable_tremor = xyes; then
AC_DEFINE(HAVE_TREMOR,1,
[Define to use tremor (libvorbisidec) for ogg support])
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
@@ -1325,9 +964,6 @@ else
TREMOR_LIBS=
fi
-AC_SUBST(TREMOR_CFLAGS)
-AC_SUBST(TREMOR_LIBS)
-
dnl -------------------------------- Ogg Vorbis -------------------------------
if test x$enable_tremor = xyes; then
@@ -1382,21 +1018,12 @@ fi
AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes)
dnl --------------------------------- wavpack ---------------------------------
-MPD_AUTO_PKG(wavpack, WAVPACK, [wavpack],
+MPD_ENABLE_AUTO_PKG(wavpack, WAVPACK, [wavpack],
[WavPack decoder plugin], [libwavpack not found])
-AM_CONDITIONAL(HAVE_WAVPACK, test x$enable_wavpack = xyes)
-if test x$enable_wavpack = xyes; then
- AC_DEFINE([HAVE_WAVPACK], 1, [Define to enable WavPack support])
-fi
dnl --------------------------------- WildMidi --------------------------------
-MPD_AUTO_LIB(wildmidi, WILDMIDI, WildMidi, WildMidi_Init, [-lWildMidi], [],
- [wildmidi], [libwildmidi not found])
-if test x$enable_wildmidi = xyes; then
- AC_DEFINE(ENABLE_WILDMIDI, 1, [Define for wildmidi support])
-fi
-
-AM_CONDITIONAL(ENABLE_WILDMIDI, test x$enable_wildmidi = xyes)
+MPD_ENABLE_AUTO_LIB(wildmidi, WILDMIDI, WildMidi, WildMidi_Init, [-lWildMidi], [],
+ [WildMidi decoder plugin], [libwildmidi not found])
dnl ------------------------ Post Decoder Plugins Tests -----------------------
@@ -1431,59 +1058,30 @@ else
fi
dnl ------------------------------- FLAC Encoder ------------------------------
-if test x$enable_flac_encoder = xyes; then
- AC_DEFINE(ENABLE_FLAC_ENCODER, 1,
- [Define to enable the FLAC encoder plugin])
-fi
-AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
+MPD_DEFINE_CONDITIONAL(enable_flac_encoder, ENABLE_FLAC_ENCODER,
+ [FLAC encoder plugin])
dnl ------------------------------- Shine Encoder ------------------------------
-MPD_AUTO_PKG(shine_encoder, SHINE, [shine >= 3.1],
+MPD_ENABLE_AUTO_PKG(shine_encoder, SHINE, [shine >= 3.1],
[shine encoder], [libshine not found])
-if test x$enable_shine_encoder = xyes; then
- AC_DEFINE(ENABLE_SHINE_ENCODER, 1,
- [Define to enable the shine encoder plugin])
-fi
-AM_CONDITIONAL(ENABLE_SHINE_ENCODER, test x$enable_shine_encoder = xyes)
-
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
-MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
+MPD_ENABLE_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
[Ogg Vorbis encoder], [libvorbisenc not found])
-if test x$enable_vorbis_encoder = xyes; then
- AC_DEFINE(ENABLE_VORBIS_ENCODER, 1,
- [Define to enable the vorbis encoder plugin])
-fi
-AM_CONDITIONAL(ENABLE_VORBIS_ENCODER, test x$enable_vorbis_encoder = xyes)
-
dnl ------------------------------- LAME Encoder ------------------------------
-MPD_AUTO_LIB(lame_encoder, LAME, mp3lame, lame_init, [-lmp3lame], [],
- [libmp3lame], [libmp3lame not found])
-if test x$enable_lame_encoder = xyes; then
- AC_DEFINE(ENABLE_LAME_ENCODER, 1,
- [Define to enable the lame encoder plugin])
-fi
-AM_CONDITIONAL(ENABLE_LAME_ENCODER, test x$enable_lame_encoder = xyes)
+MPD_ENABLE_AUTO_LIB(lame_encoder, LAME, mp3lame, lame_init, [-lmp3lame], [],
+ [LAME encoder plugin], [libmp3lame not found])
dnl ----------------------------- TwoLAME Encoder -----------------------------
-MPD_AUTO_PKG(twolame_encoder, TWOLAME, [twolame],
- [TwoLAME encoder], [libtwolame not found])
-
-if test x$enable_twolame_encoder = xyes; then
- AC_DEFINE(ENABLE_TWOLAME_ENCODER, 1,
- [Define to enable the TwoLAME encoder plugin])
-fi
-AM_CONDITIONAL(ENABLE_TWOLAME_ENCODER, test x$enable_twolame_encoder = xyes)
+MPD_ENABLE_AUTO_PKG(twolame_encoder, TWOLAME, [twolame],
+ [TwoLAME encoder plugin], [libtwolame not found])
dnl ------------------------------- WAVE Encoder ------------------------------
-AM_CONDITIONAL(ENABLE_WAVE_ENCODER, test x$enable_wave_encoder = xyes)
-if test x$enable_wave_encoder = xyes; then
- AC_DEFINE(ENABLE_WAVE_ENCODER, 1,
- [Define to enable the PCM wave encoder plugin])
-fi
+MPD_DEFINE_CONDITIONAL(enable_wave_encoder, ENABLE_WAVE_ENCODER,
+ [PCM wave encoder plugin])
dnl --------------------------- encoder plugins test --------------------------
if test x$enable_vorbis_encoder != xno ||
@@ -1504,11 +1102,9 @@ else
fi
fi
-if test x$enable_encoder = xyes; then
- AC_DEFINE(ENABLE_ENCODER, 1,
- [Define to enable the encoder plugins])
-fi
-AM_CONDITIONAL(ENABLE_ENCODER, test x$enable_encoder = xyes)
+MPD_DEFINE_CONDITIONAL(enable_encoder, ENABLE_ENCODER,
+ [the encoder plugins])
+
AM_CONDITIONAL(HAVE_OGG_ENCODER, test x$enable_vorbis_encoder = xyes || test x$enable_opus = xyes)
dnl ---------------------------------------------------------------------------
@@ -1516,34 +1112,23 @@ dnl Audio Output Plugins
dnl ---------------------------------------------------------------------------
dnl ----------------------------------- ALSA ----------------------------------
-MPD_AUTO_PKG(alsa, ALSA, [alsa >= 0.9.0],
- [ALSA output plugin], [libasound not found])
-
-if test x$enable_alsa = xyes; then
- AC_DEFINE(HAVE_ALSA, 1, [Define to enable ALSA support])
-fi
-
-AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
+MPD_ENABLE_AUTO_PKG(alsa, ALSA, [alsa >= 0.9.0],
+ [ALSA output plugin], [libasound not found],
+ [$linux_auto])
dnl ----------------------------------- ROAR ----------------------------------
-MPD_AUTO_PKG(roar, ROAR, [libroar >= 0.4.0],
- [ROAR output plugin], [libroar not found])
-
-if test x$enable_roar = xyes; then
- AC_DEFINE(HAVE_ROAR, 1, [Define to enable ROAR support])
-fi
-
-AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes)
+MPD_ENABLE_AUTO_PKG(roar, ROAR, [libroar >= 0.4.0],
+ [RoarAudio output plugin], [libroar not found])
dnl ----------------------------------- FIFO ----------------------------------
if test x$enable_fifo = xyes; then
AC_CHECK_FUNC([mkfifo],
- [enable_fifo=yes;AC_DEFINE([HAVE_FIFO], 1,
- [Define to enable support for writing audio to a FIFO])],
+ [enable_fifo=yes],
[enable_fifo=no;AC_MSG_WARN([mkfifo not found -- disabling support for writing audio to a FIFO])])
fi
-AM_CONDITIONAL(HAVE_FIFO, test x$enable_fifo = xyes)
+MPD_DEFINE_CONDITIONAL(enable_fifo, HAVE_FIFO,
+ [support for writing audio to a FIFO])
dnl ------------------------------- HTTPD Output ------------------------------
if test x$enable_httpd_output = xauto; then
@@ -1557,17 +1142,12 @@ if test x$enable_httpd_output = xauto; then
fi
fi
-if test x$enable_httpd_output = xyes; then
- AC_DEFINE(ENABLE_HTTPD_OUTPUT, 1, [Define to enable the HTTP server output])
-fi
-AM_CONDITIONAL(ENABLE_HTTPD_OUTPUT, test x$enable_httpd_output = xyes)
+MPD_DEFINE_CONDITIONAL(enable_httpd_output, ENABLE_HTTPD_OUTPUT,
+ [the HTTP server output])
dnl ----------------------------------- JACK ----------------------------------
-MPD_AUTO_PKG(jack, JACK, [jack >= 0.100],
+MPD_ENABLE_AUTO_PKG(jack, JACK, [jack >= 0.100],
[JACK output plugin], [libjack not found])
-if test x$enable_jack = xyes; then
- AC_DEFINE([HAVE_JACK], 1, [Define to enable JACK support])
-fi
if test x$enable_jack = xyes; then
# check whether jack_set_info_function() is available
@@ -1579,16 +1159,9 @@ if test x$enable_jack = xyes; then
LIBS=$old_LIBS
fi
-AM_CONDITIONAL(HAVE_JACK, test x$enable_jack = xyes)
-
dnl ---------------------------------- libao ----------------------------------
-MPD_AUTO_PKG(ao, AO, [ao],
+MPD_ENABLE_AUTO_PKG(ao, AO, [ao],
[libao output plugin], [libao not found])
-if test x$enable_ao = xyes; then
- AC_DEFINE(HAVE_AO, 1, [Define to play with ao])
-fi
-
-AM_CONDITIONAL(HAVE_AO, test x$enable_ao = xyes)
dnl ---------------------------------- OpenAL ---------------------------------
AC_SUBST(OPENAL_CFLAGS,"")
@@ -1610,21 +1183,17 @@ else
[OpenAL output plugin], [OpenAL not found])
fi
-if test x$enable_openal = xyes; then
- AC_DEFINE(HAVE_OPENAL, 1, [Define for OpenAL support])
-fi
-
-AM_CONDITIONAL(HAVE_OPENAL, test x$enable_openal = xyes)
+MPD_DEFINE_CONDITIONAL(enable_openal, HAVE_OPENAL, [OpenAL support])
dnl ---------------------------- Open Sound System ----------------------------
if test x$enable_oss = xyes; then
AC_CHECK_HEADER(sys/soundcard.h,
- [enable_oss=yes;AC_DEFINE(HAVE_OSS,1,[Define to enable OSS])],
+ [enable_oss=yes],
[AC_MSG_WARN(Soundcard headers not found -- disabling OSS support);
enable_oss=no])
fi
-AM_CONDITIONAL(HAVE_OSS, test x$enable_oss = xyes)
+MPD_DEFINE_CONDITIONAL(enable_oss, HAVE_OSS, [Open Sound System])
dnl ----------------------------------- OSX -----------------------------------
if test x$enable_osx = xyes; then
@@ -1635,21 +1204,12 @@ fi
AM_CONDITIONAL(HAVE_OSX, test x$enable_osx = xyes)
dnl ------------------------------- Pipe Output -------------------------------
-if test x$enable_pipe_output = xyes; then
- AC_DEFINE([ENABLE_PIPE_OUTPUT], 1,
- [Define to enable support for writing audio to a pipe])
-fi
-AM_CONDITIONAL(ENABLE_PIPE_OUTPUT, test x$enable_pipe_output = xyes)
+MPD_DEFINE_CONDITIONAL(enable_pipe_output, ENABLE_PIPE_OUTPUT,
+ [support for writing audio to a pipe])
dnl -------------------------------- PulseAudio -------------------------------
-MPD_AUTO_PKG(pulse, PULSE, [libpulse >= 0.9.16],
+MPD_ENABLE_AUTO_PKG(pulse, PULSE, [libpulse >= 0.9.16],
[PulseAudio output plugin], [libpulse not found])
-if test x$enable_pulse = xyes; then
- AC_DEFINE([HAVE_PULSE], 1,
- [Define to enable PulseAudio support])
-fi
-
-AM_CONDITIONAL(HAVE_PULSE, test x$enable_pulse = xyes)
dnl --------------------------------- Recorder --------------------------------
if test x$enable_recorder_output = xauto; then
@@ -1663,10 +1223,8 @@ if test x$enable_recorder_output = xauto; then
fi
fi
-if test x$enable_recorder_output = xyes; then
- AC_DEFINE(ENABLE_RECORDER_OUTPUT, 1, [Define to enable the recorder output])
-fi
-AM_CONDITIONAL(ENABLE_RECORDER_OUTPUT, test x$enable_recorder_output = xyes)
+MPD_DEFINE_CONDITIONAL(enable_recorder_output, ENABLE_RECORDER_OUTPUT,
+ [the recorder output])
dnl -------------------------------- SHOUTcast --------------------------------
if test x$enable_shout = xauto; then
@@ -1680,18 +1238,13 @@ if test x$enable_shout = xauto; then
fi
fi
-if test x$enable_shout = xyes; then
- AC_DEFINE(HAVE_SHOUT, 1, [Define to enable the shoutcast output])
-fi
-AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes)
+MPD_DEFINE_CONDITIONAL(enable_shout, HAVE_SHOUT,
+ [shoutcast output])
dnl --------------------------------- Solaris ---------------------------------
-if test x$enable_solaris_output = xyes; then
- AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support])
-fi
-
-AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
+MPD_DEFINE_CONDITIONAL(enable_solaris_output, ENABLE_SOLARIS_OUTPUT,
+ [Solaris /dev/audio support])
dnl --------------------------------- WinMM ---------------------------------
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index 4b55f8801..7a2db5d16 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -385,29 +385,3 @@ input {
#id3v1_encoding "ISO-8859-1"
#
###############################################################################
-
-
-# SIDPlay decoder #############################################################
-#
-# songlength_database:
-# Location of your songlengths file, as distributed with the HVSC.
-# The sidplay plugin checks this for matching MD5 fingerprints.
-# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
-#
-# default_songlength:
-# This is the default playing time in seconds for songs not in the
-# songlength database, or in case you're not using a database.
-# A value of 0 means play indefinitely.
-#
-# filter:
-# Turns the SID filter emulation on or off.
-#
-#decoder {
-# plugin "sidplay"
-# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
-# default_songlength "120"
-# filter "true"
-#}
-#
-###############################################################################
-
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 05468e535..d56b09fdd 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -240,7 +240,8 @@
bigger than the priority of the current song. Decreasing the
priority of a song will moved it farther to the end of the
sequence. Changing the priority of the current song has no
- effect on the sequence.
+ effect on the sequence. During playback, a song's priority is
+ reset to zero.
</para>
</section>
</chapter>
@@ -517,6 +518,15 @@
</listitem>
<listitem>
<para>
+ <varname>duration</varname>:
+ <footnote id="since_0_20"><simpara>Introduced with <application>MPD</application> 0.20</simpara></footnote>
+ <returnvalue>
+ Duration of the current song in seconds.
+ </returnvalue>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
<varname>bitrate</varname>:
<returnvalue>instantaneous bitrate in
kbps</returnvalue>
@@ -1559,6 +1569,7 @@ OK
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
+ <arg choice="opt">window <replaceable>START</replaceable>:<replaceable>END</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
@@ -1603,6 +1614,13 @@ OK
<para>
<varname>WHAT</varname> is what to find.
</para>
+
+ <para>
+ <varname>window</varname> can be used to query only a
+ portion of the real response. The parameter is two
+ zero-based record numbers; a start number and an end
+ number.
+ </para>
</listitem>
</varlistentry>
<varlistentry id="command_findadd">
@@ -1795,6 +1813,7 @@ OK
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
+ <arg choice="opt">window <replaceable>START</replaceable>:<replaceable>END</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
@@ -2119,6 +2138,30 @@ OK
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="command_sticker_find_equals">
+ <term>
+ <cmdsynopsis>
+ <command>sticker</command>
+ <arg choice="plain">find</arg>
+ <arg choice="req"><replaceable>TYPE</replaceable></arg>
+ <arg choice="req"><replaceable>URI</replaceable></arg>
+ <arg choice="req"><replaceable>NAME</replaceable></arg>
+ <arg choice="plain">=</arg>
+ <arg choice="req"><replaceable>VALUE</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Searches for stickers with the given value.
+ </para>
+
+ <para>
+ Other supported operators are:
+ "<function>&lt;</function>", "<function>&gt;</function>"
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</section>
diff --git a/doc/user.xml b/doc/user.xml
index a0bd861e8..e5b89e27d 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -574,7 +574,7 @@ systemctl start mpd.socket</programlisting>
<row>
<entry>
<varname>mixer_type</varname>
- <parameter>hardware|software|none</parameter>
+ <parameter>hardware|software|null|none</parameter>
</entry>
<entry>
Specifies which mixer should be used for this audio
@@ -582,7 +582,9 @@ systemctl start mpd.socket</programlisting>
linkend="alsa_output">ALSA</link>, <link
linkend="oss_output">OSS</link> and <link
linkend="pulse_output">PulseAudio</link>), the
- software mixer or no mixer
+ software mixer, the "null" mixer
+ (<parameter>null</parameter>; allows setting the
+ volume, but with no effect) or no mixer
(<parameter>none</parameter>). By default, the
hardware mixer is used for devices which support it,
and none for the others.
@@ -2017,6 +2019,64 @@ buffer_size: 16384</programlisting>
</informaltable>
</section>
+ <section id="sidplay_decoder">
+ <title><varname>sidplay</varname></title>
+
+ <para>
+ C64 SID decoder based on
+ <application>libsidplay</application>.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>songlength_database</varname>
+ <parameter>PATH</parameter>
+ </entry>
+ <entry>
+ Location of your songlengths file, as distributed
+ with the HVSC. The <varname>sidplay</varname>
+ plugin checks this for matching MD5 fingerprints.
+ See <ulink
+ url="http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq">http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq</ulink>.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>default_songlength</varname>
+ <parameter>SECONDS</parameter>
+ </entry>
+ <entry>
+ This is the default playing time in seconds for
+ songs not in the songlength database, or in case
+ you're not using a database. A value of 0 means
+ play indefinitely.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>filter</varname>
+ <parameter>yes|no</parameter>
+ </entry>
+ <entry>
+ Turns the SID filter emulation on or off.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
<section>
<title><varname>wildmidi</varname></title>
diff --git a/m4/faad.m4 b/m4/faad.m4
deleted file mode 100644
index 9dcb1ccab..000000000
--- a/m4/faad.m4
+++ /dev/null
@@ -1,73 +0,0 @@
-AC_DEFUN([AM_PATH_FAAD],
-[dnl ##
-dnl faad
-dnl ##
-
-AC_ARG_ENABLE(aac,
- AS_HELP_STRING([--disable-aac],
- [disable AAC support (default: enable)]),,
- enable_aac=yes)
-
-if test x$enable_aac = xyes; then
- FAAD_LIBS="-lfaad"
- FAAD_CFLAGS=""
-
- oldcflags=$CFLAGS
- oldlibs=$LIBS
- oldcppflags=$CPPFLAGS
- CFLAGS="$CFLAGS $FAAD_CFLAGS"
- LIBS="$LIBS $FAAD_LIBS"
- CPPFLAGS=$CFLAGS
- AC_CHECK_HEADER(faad.h,,enable_aac=no)
- if test x$enable_aac = xyes; then
- AC_CHECK_DECL(FAAD2_VERSION,,enable_aac=no,[#include <faad.h>])
- fi
- if test x$enable_aac = xyes; then
- AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no)
- fi
- if test x$enable_aac = xyes; then
- AC_MSG_CHECKING(that FAAD2 can even be used)
- AC_COMPILE_IFELSE([AC_LANG_SOURCE([
-#include <faad.h>
-
-int main() {
- char buffer;
- NeAACDecHandle decoder;
- NeAACDecFrameInfo frameInfo;
- NeAACDecConfigurationPtr config;
- unsigned char channels;
- long sampleRate;
- long bufferlen = 0;
-
- decoder = NeAACDecOpen();
- config = NeAACDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
- NeAACDecSetConfiguration(decoder,config);
- NeAACDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels);
- NeAACDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels);
- NeAACDecDecode(decoder,&frameInfo,&buffer,bufferlen);
- NeAACDecClose(decoder);
-
- return 0;
-}
-])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
- fi
- if test x$enable_aac = xyes; then
- AC_DEFINE(HAVE_FAAD,1,[Define to use FAAD2 for AAC decoding])
- else
- AC_MSG_WARN([faad2 lib needed for MP4/AAC support -- disabling MP4/AAC support])
- fi
- CFLAGS=$oldcflags
- LIBS=$oldlibs
- CPPFLAGS=$oldcppflags
-fi
-
-if test x$enable_aac = xno; then
- FAAD_LIBS=""
- FAAD_CFLAGS=""
-fi
-
-AC_SUBST(FAAD_CFLAGS)
-AC_SUBST(FAAD_LIBS)
-
-])
diff --git a/m4/mpd_auto.m4 b/m4/mpd_auto.m4
index ff922fadc..a57ee2d5a 100644
--- a/m4/mpd_auto.m4
+++ b/m4/mpd_auto.m4
@@ -1,23 +1,16 @@
AC_DEFUN([MPD_AUTO_ENABLED], [
- var="enable_$1"
- feature="$2"
-
- if eval "test x`echo '$'$var` = xauto"; then
- AC_MSG_NOTICE([auto-detected $feature])
- eval "$var=yes"
+ if test x$[]enable_$1 = xauto; then
+ AC_MSG_NOTICE([auto-detected $2])
+ enable_$1=yes
fi
])
AC_DEFUN([MPD_AUTO_DISABLED], [
- var="enable_$1"
- feature="$2"
- msg="$3"
-
- if eval "test x`echo '$'$var` = xauto"; then
- AC_MSG_WARN([$msg -- disabling $feature])
- eval "$var=no"
- elif eval "test x`echo '$'$var` = xyes"; then
- AC_MSG_ERROR([$feature: $msg])
+ if test x$[]enable_$1 = xauto; then
+ AC_MSG_WARN([$3 -- disabling $2])
+ enable_$1=no
+ elif test x$[]enable_$1 = xyes; then
+ AC_MSG_ERROR([$2: $3])
fi
])
@@ -25,59 +18,49 @@ dnl Check whether a prerequisite for a feature was found. This is
dnl very similar to MPD_AUTO_RESULT, but does not finalize the
dnl detection; it assumes that more checks will follow.
AC_DEFUN([MPD_AUTO_PRE], [
- name="$1"
- var="enable_$1"
- found="found_$name"
- feature="$2"
- msg="$3"
-
- if eval "test x`echo '$'$var` != xno" && eval "test x`echo '$'$found` = xno"; then
- MPD_AUTO_DISABLED([$name], [$feature], [$msg])
+ if test x$[]enable_$1 != xno && test x$[]found_$1 = xno; then
+ MPD_AUTO_DISABLED([$1], [$2], [$3])
fi
])
AC_DEFUN([MPD_AUTO_RESULT], [
- name="$1"
- var="enable_$1"
- found="found_$name"
- feature="$2"
- msg="$3"
-
- if eval "test x`echo '$'$var` = xno"; then
- eval "$found=no"
+ if test x$[]enable_$1 = xno; then
+ found_$1=no
fi
- if eval "test x`echo '$'$found` = xyes"; then
- MPD_AUTO_ENABLED([$name], [$feature])
+ if test x$[]found_$1 = xyes; then
+ MPD_AUTO_ENABLED([$1], [$2])
else
- MPD_AUTO_DISABLED([$name], [$feature], [$msg])
+ MPD_AUTO_DISABLED([$1], [$2], [$3])
fi
])
-AC_DEFUN([MPD_AUTO_PKG], [
- if eval "test x`echo '$'enable_$1` != xno"; then
- PKG_CHECK_MODULES([$2], [$3],
- [eval "found_$1=yes"],
- [eval "found_$1=no"])
+dnl Parameters: varname1, description, errmsg, check
+AC_DEFUN([MPD_AUTO], [
+ if test x$[]enable_$1 != xno; then
+ $4
fi
+ MPD_AUTO_RESULT([$1], [$2], [$3])
+])
- MPD_AUTO_RESULT([$1], [$4], [$5])
+AC_DEFUN([MPD_AUTO_PKG], [
+ MPD_AUTO([$1], [$4], [$5],
+ [PKG_CHECK_MODULES([$2], [$3],
+ [found_$1=yes],
+ [found_$1=no])])
])
dnl Check with pkg-config first, fall back to AC_CHECK_LIB.
dnl
dnl Parameters: varname1, varname2, pkgname, libname, symname, libs, cflags, description, errmsg
AC_DEFUN([MPD_AUTO_PKG_LIB], [
- if eval "test x`echo '$'enable_$1` != xno"; then
- PKG_CHECK_MODULES([$2], [$3],
- [eval "found_$1=yes"],
+ MPD_AUTO([$1], [$8], [$9],
+ [PKG_CHECK_MODULES([$2], [$3],
+ [found_$1=yes],
AC_CHECK_LIB($4, $5,
- [eval "found_$1=yes $2_LIBS='$6' $2_CFLAGS='$7'"],
- [eval "found_$1=no"],
- [$6]))
- fi
-
- MPD_AUTO_RESULT([$1], [$8], [$9])
+ [found_$1=yes $2_LIBS='$6' $2_CFLAGS='$7'],
+ [found_$1=no],
+ [$6]))])
])
dnl Wrapper for AC_CHECK_LIB.
@@ -87,12 +70,66 @@ AC_DEFUN([MPD_AUTO_LIB], [
AC_SUBST([$2_LIBS], [])
AC_SUBST([$2_CFLAGS], [])
- if eval "test x`echo '$'enable_$1` != xno"; then
- AC_CHECK_LIB($3, $4,
- [eval "found_$1=yes $2_LIBS='$5' $2_CFLAGS='$6'"],
- [eval "found_$1=no"],
- [$5])
- fi
+ MPD_AUTO([$1], [$7], [$8],
+ [AC_CHECK_LIB($3, $4,
+ [found_$1=yes $2_LIBS='$5' $2_CFLAGS='$6'],
+ [found_$1=no],
+ [$5])])
+])
+
+AC_DEFUN([MPD_FORMAT_DEFAULT],
+ [ifelse([$1], [], [auto],
+ index([$1], [$]), [-1], [$1],
+ [auto])])
+
+dnl Wrapper for AC_ARG_ENABLE, AC_DEFINE and AM_CONDITIONAL
+dnl
+dnl Parameters: varname1, varname2, description, default, check
+AC_DEFUN([MPD_ARG_ENABLE], [
+ AC_ARG_ENABLE(translit([$1], [_], [-]),
+ AS_HELP_STRING([--enable-]translit([$1], [_], [-]),
+ [enable $3 (default: ]MPD_FORMAT_DEFAULT([$4])[)]),,
+ [enable_$1=]ifelse([$4], [], [auto], [$4]))
+
+ $5
- MPD_AUTO_RESULT([$1], [$7], [$8])
+ MPD_DEFINE_CONDITIONAL(enable_$1, ENABLE_$2, [$3])
+])
+
+dnl Wrapper for AC_ARG_ENABLE and MPD_AUTO_PKG
+dnl
+dnl Parameters: varname1, varname2, pkg, description, errmsg, default, pre
+AC_DEFUN([MPD_ENABLE_AUTO_PKG], [
+ MPD_ARG_ENABLE([$1], [$2], [$4], [$6], [
+ $7
+ MPD_AUTO_PKG($1, $2, $3, $4, $5)
+ ])
+])
+
+dnl Wrapper for AC_ARG_ENABLE and MPD_AUTO_PKG_LIB
+dnl
+dnl Parameters: varname1, varname2, pkg, libname, symname, libs, cflags, description, errmsg, default, pre
+AC_DEFUN([MPD_ENABLE_AUTO_PKG_LIB], [
+ MPD_ARG_ENABLE([$1], [$2], [$8], [$10], [
+ $11
+ MPD_AUTO_PKG_LIB($1, $2, $3, $4, $5, $6, $7, $8, $9)
+ ])
+])
+
+dnl Wrapper for AC_ARG_ENABLE and MPD_AUTO_LIB
+dnl
+dnl Parameters: varname1, varname2, libname, symname, libs, cflags, description, errmsg, default, pre
+AC_DEFUN([MPD_ENABLE_AUTO_LIB], [
+ MPD_ARG_ENABLE([$1], [$2], [$7], [$9], [
+ $10
+ MPD_AUTO_LIB($1, $2, $3, $4, $5, $6, $7, $8)
+ ])
+])
+
+dnl Wrapper for MPD_ENABLE_AUTO_PKG and MPD_DEPENDS
+dnl
+dnl Parameters: varname1, varname2, pkg, description, errmsg, default, dep_variable, dep_errmsg
+AC_DEFUN([MPD_ENABLE_AUTO_PKG_DEPENDS], [
+ MPD_ENABLE_AUTO_PKG([$1], [$2], [$3], [$4], [$5], [$6],
+ [MPD_DEPENDS([enable_$1], [$7], [$8])])
])
diff --git a/m4/mpd_define_conditional.m4 b/m4/mpd_define_conditional.m4
new file mode 100644
index 000000000..4bc849984
--- /dev/null
+++ b/m4/mpd_define_conditional.m4
@@ -0,0 +1,8 @@
+dnl Wrapper for AC_DEFINE and AM_CONDITIONAL
+dnl
+dnl Parameters: varname1, varname2, description
+AC_DEFUN([MPD_DEFINE_CONDITIONAL], [dnl
+ AM_CONDITIONAL($2, test x$[]$1 = xyes)
+ if test x$[]$1 = xyes; then
+ AC_DEFINE($2, 1, [Define to enable $3])
+ fi])
diff --git a/m4/mpd_func.m4 b/m4/mpd_func.m4
index 5f2bf8f3d..757cd3b6f 100644
--- a/m4/mpd_func.m4
+++ b/m4/mpd_func.m4
@@ -6,7 +6,7 @@ AC_DEFUN([MPD_OPTIONAL_FUNC], [
AC_ARG_ENABLE([$1],
AS_HELP_STRING([--enable-$1],
[use the function "$1" (default: auto)]),
- [test xenable_$1 = xyes && AC_DEFINE([$3], 1, [Define to use $1])],
+ [test x$[]enable_$1 = xyes && AC_DEFINE([$3], 1, [Define to use $1])],
[AC_CHECK_FUNC([$2],
[AC_DEFINE([$3], 1, [Define to use $1])],)])
])
diff --git a/m4/mpd_with_flags.m4 b/m4/mpd_with_flags.m4
new file mode 100644
index 000000000..4f82b7973
--- /dev/null
+++ b/m4/mpd_with_flags.m4
@@ -0,0 +1,23 @@
+dnl Run code with the specified CFLAGS/CXXFLAGS and LIBS appended.
+dnl Restores the old values afterwards.
+dnl
+dnl Parameters: cflags, libs, code
+AC_DEFUN([MPD_WITH_FLAGS], [
+ ac_save_CFLAGS="$[]CFLAGS"
+ ac_save_CXXFLAGS="$[]CXXFLAGS"
+ ac_save_LIBS="$[]LIBS"
+ CFLAGS="$[]CFLAGS $1"
+ CXXFLAGS="$[]CXXFLAGS $1"
+ LIBS="$[]LIBS $2"
+ $3
+ CFLAGS="$[]ac_save_CFLAGS"
+ CXXFLAGS="$[]ac_save_CXXFLAGS"
+ LIBS="$[]ac_save_LIBS"
+])
+
+dnl Run code with the specified library's CFLAGS/CXXFLAGS and LIBS
+dnl appended. Restores the old values afterwards.
+dnl
+dnl Parameters: libname, code
+AC_DEFUN([MPD_WITH_LIBRARY],
+ [MPD_WITH_FLAGS([$[]$1_CFLAGS], [$[]$1_LIBS], [$2])])
diff --git a/m4/pretty_print.m4 b/m4/pretty_print.m4
index 687dceefe..75df7228e 100644
--- a/m4/pretty_print.m4
+++ b/m4/pretty_print.m4
@@ -1,16 +1,6 @@
AC_DEFUN([results], [
- dnl This is a hack to allow "with" names, otherwise "enable".
- num=`expr $1 : 'with'`
- if test "$num" != "0"; then
- var="`echo '$'$1`"
- else
- var="`echo '$'enable_$1`"
- fi
-
printf '('
- if eval "test x$var = xyes"; then
- printf '+'
- elif test -n "$3" && eval "test x$var = x$3"; then
+ if test x$[]enable_$1 = xyes; then
printf '+'
else
printf '-'
diff --git a/src/Chrono.hxx b/src/Chrono.hxx
index cc87c5ba1..960a6364c 100644
--- a/src/Chrono.hxx
+++ b/src/Chrono.hxx
@@ -26,7 +26,7 @@
#include <utility>
#include <cstdint>
-#if defined(__GNUC__) && !GCC_CHECK_VERSION(4,7) && !defined(__clang__)
+#if GCC_OLDER_THAN(4,7)
/* std::chrono::duration operators are "constexpr" since gcc 4.7 */
#define chrono_constexpr gcc_pure
#else
diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index c6e9c69c5..385729cf6 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -98,40 +98,44 @@ static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn
static void version(void)
{
- puts("Music Player Daemon " VERSION
+ printf("Music Player Daemon " VERSION
#ifdef GIT_COMMIT
- " (" GIT_COMMIT ")"
+ " (" GIT_COMMIT ")"
#endif
- "\n"
- "\n"
- "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
- "Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n"
- "This is free software; see the source for copying conditions. There is NO\n"
- "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
+ "\n"
+ "\n"
+ "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
+ "Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n"
+ "This is free software; see the source for copying conditions. There is NO\n"
+ "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
#ifdef ENABLE_DATABASE
- puts("\n"
- "Database plugins:");
+ "\n"
+ "Database plugins:\n");
for (auto i = database_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
- puts("\n\n"
- "Storage plugins:");
+ printf("\n\n"
+ "Storage plugins:\n");
for (auto i = storage_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
+
+ printf("\n"
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS
- puts("\n\n"
- "Neighbor plugins:");
+ "\n"
+ "Neighbor plugins:\n");
for (auto i = neighbor_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
+
+ printf("\n"
#endif
- puts("\n\n"
- "Decoders plugins:");
+ "\n"
+ "Decoders plugins:\n");
decoder_plugins_for_each([](const DecoderPlugin &plugin){
printf(" [%s]", plugin.name);
@@ -141,26 +145,31 @@ static void version(void)
for (; *suffixes != nullptr; ++suffixes)
printf(" %s", *suffixes);
- puts("");
+ printf("\n");
});
- puts("\n"
- "Output plugins:");
+ printf("\n"
+ "Tag plugins:\n"
+#ifdef ENABLE_ID3TAG
+ " id3tag"
+#endif
+ "\n\n"
+ "Output plugins:\n");
audio_output_plugins_for_each(plugin)
printf(" %s", plugin->name);
- puts("");
+ printf("\n"
#ifdef ENABLE_ENCODER
- puts("\n"
- "Encoder plugins:");
+ "\n"
+ "Encoder plugins:\n");
encoder_plugins_for_each(plugin)
printf(" %s", plugin->name);
- puts("");
+ printf("\n"
#endif
#ifdef ENABLE_ARCHIVE
- puts("\n"
- "Archive plugins:");
+ "\n"
+ "Archive plugins:\n");
archive_plugins_for_each(plugin) {
printf(" [%s]", plugin->name);
@@ -169,22 +178,24 @@ static void version(void)
for (; *suffixes != nullptr; ++suffixes)
printf(" %s", *suffixes);
- puts("");
+ printf("\n");
}
+
+ printf(""
#endif
- puts("\n"
- "Input plugins:");
+ "\n"
+ "Input plugins:\n");
input_plugins_for_each(plugin)
printf(" %s", plugin->name);
- puts("\n\n"
- "Playlist plugins:");
+ printf("\n\n"
+ "Playlist plugins:\n");
playlist_plugins_for_each(plugin)
printf(" %s", plugin->name);
- puts("\n\n"
- "Protocols:");
+ printf("\n\n"
+ "Protocols:\n");
print_supported_uri_schemes_to_fp(stdout);
exit(EXIT_SUCCESS);
@@ -206,12 +217,12 @@ static void PrintOption(const OptionDef &opt)
gcc_noreturn
static void help(void)
{
- puts("Usage:\n"
- " mpd [OPTION...] [path/to/mpd.conf]\n"
- "\n"
- "Music Player Daemon - a daemon for playing music.\n"
- "\n"
- "Options:");
+ printf("Usage:\n"
+ " mpd [OPTION...] [path/to/mpd.conf]\n"
+ "\n"
+ "Music Player Daemon - a daemon for playing music.\n"
+ "\n"
+ "Options:\n");
PrintOption(opt_help);
PrintOption(opt_kill);
diff --git a/src/Compiler.h b/src/Compiler.h
index fea971526..dc3de5af9 100644
--- a/src/Compiler.h
+++ b/src/Compiler.h
@@ -28,8 +28,20 @@
#define GCC_VERSION 0
#endif
+/**
+ * Are we building with the specified version of gcc (not clang or any
+ * other compiler) or newer?
+ */
#define GCC_CHECK_VERSION(major, minor) \
- (defined(__GNUC__) && GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
+ (defined(__GNUC__) && !defined(__clang__) && \
+ GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
+
+/**
+ * Are we building with clang (any version) or at least the specified
+ * gcc version?
+ */
+#define CLANG_OR_GCC_VERSION(major, minor) \
+ (defined(__clang__) || GCC_CHECK_VERSION(major, minor))
/**
* Are we building with gcc (not clang or any other compiler) and a
@@ -59,7 +71,7 @@
(defined(__clang__) && \
CLANG_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
-#if GCC_CHECK_VERSION(4,0)
+#if CLANG_OR_GCC_VERSION(4,0)
/* GCC 4.x */
@@ -119,7 +131,7 @@
#endif
-#if GCC_CHECK_VERSION(4,3)
+#if CLANG_OR_GCC_VERSION(4,3)
#define gcc_hot __attribute__((hot))
#define gcc_cold __attribute__((cold))
@@ -131,7 +143,7 @@
#endif /* ! GCC_UNUSED >= 40300 */
-#if GCC_CHECK_VERSION(4,6) && !defined(__clang__)
+#if GCC_CHECK_VERSION(4,6)
#define gcc_flatten __attribute__((flatten))
#else
#define gcc_flatten
@@ -140,7 +152,7 @@
#ifndef __cplusplus
/* plain C99 has "restrict" */
#define gcc_restrict restrict
-#elif GCC_CHECK_VERSION(4,0)
+#elif CLANG_OR_GCC_VERSION(4,0)
/* "__restrict__" is a GCC extension for C++ */
#define gcc_restrict __restrict__
#else
@@ -158,7 +170,7 @@
#define final
#endif
-#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+#if CLANG_OR_GCC_VERSION(4,8)
#define gcc_alignas(T, fallback) alignas(T)
#else
#define gcc_alignas(T, fallback) gcc_aligned(fallback)
diff --git a/src/Instance.cxx b/src/Instance.cxx
index 232cd21df..0f7f52e6e 100644
--- a/src/Instance.cxx
+++ b/src/Instance.cxx
@@ -22,6 +22,7 @@
#include "Partition.hxx"
#include "Idle.hxx"
#include "Stats.hxx"
+#include "util/Error.hxx"
#ifdef ENABLE_DATABASE
#include "db/DatabaseError.hxx"
@@ -76,7 +77,7 @@ Instance::OnDatabaseSongRemoved(const LightSong &song)
#ifdef ENABLE_SQLITE
/* if the song has a sticker, remove it */
if (sticker_enabled())
- sticker_song_delete(song);
+ sticker_song_delete(song, IgnoreError());
#endif
const auto uri = song.GetURI();
diff --git a/src/Main.cxx b/src/Main.cxx
index 26d4e7ae4..9972b5cdd 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -50,7 +50,6 @@
#include "AudioConfig.hxx"
#include "pcm/PcmConvert.hxx"
#include "unix/SignalHandlers.hxx"
-#include "unix/Daemon.hxx"
#include "system/FatalError.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
@@ -65,6 +64,10 @@
#include "config/ConfigError.hxx"
#include "Stats.hxx"
+#ifdef ENABLE_DAEMON
+#include "unix/Daemon.hxx"
+#endif
+
#ifdef ENABLE_DATABASE
#include "db/update/Service.hxx"
#include "db/Configured.hxx"
@@ -133,7 +136,7 @@ Instance *instance;
static StateFile *state_file;
-#ifndef ANDROID
+#ifdef ENABLE_DAEMON
static bool
glue_daemonize_init(const struct options *options, Error &error)
@@ -422,9 +425,11 @@ int mpd_main(int argc, char *argv[])
struct options options;
Error error;
-#ifndef ANDROID
+#ifdef ENABLE_DAEMON
daemonize_close_stdin();
+#endif
+#ifndef ANDROID
#ifdef HAVE_LOCALE_H
/* initialize locale */
setlocale(LC_CTYPE,"");
@@ -432,11 +437,6 @@ int mpd_main(int argc, char *argv[])
#ifdef HAVE_GLIB
g_set_application_name("Music Player Daemon");
-
-#if !GLIB_CHECK_VERSION(2,32,0)
- /* enable GLib's thread safety code */
- g_thread_init(nullptr);
-#endif
#endif
#endif
@@ -470,7 +470,9 @@ int mpd_main(int argc, char *argv[])
LogError(error);
return EXIT_FAILURE;
}
+#endif
+#ifdef ENABLE_DAEMON
if (!glue_daemonize_init(&options, error)) {
LogError(error);
return EXIT_FAILURE;
@@ -512,7 +514,7 @@ int mpd_main(int argc, char *argv[])
return EXIT_FAILURE;
}
-#ifndef ANDROID
+#ifdef ENABLE_DAEMON
daemonize_set_user();
daemonize_begin(options.daemon);
#endif
@@ -544,7 +546,10 @@ static int mpd_main_after_fork(struct options options)
GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted);
#endif
- ConfigureFS();
+ if (!ConfigureFS(error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
if (!glue_mapper_init(error)) {
LogError(error);
@@ -585,9 +590,11 @@ static int mpd_main_after_fork(struct options options)
playlist_list_global_init();
-#ifndef ANDROID
+#ifdef ENABLE_DAEMON
daemonize_commit();
+#endif
+#ifndef ANDROID
setup_log_output(options.log_stderr);
SignalHandlersInit(*instance->event_loop);
@@ -710,6 +717,8 @@ static int mpd_main_after_fork(struct options options)
mapper_finish();
#endif
+ DeinitFS();
+
delete instance->partition;
command_finish();
decoder_plugin_deinit_all();
@@ -724,9 +733,11 @@ static int mpd_main_after_fork(struct options options)
delete instance->event_loop;
delete instance;
instance = nullptr;
-#ifndef ANDROID
+
+#ifdef ENABLE_DAEMON
daemonize_finish();
#endif
+
#ifdef WIN32
WSACleanup();
#endif
diff --git a/src/ReplayGainInfo.hxx b/src/ReplayGainInfo.hxx
index 37815c933..9b702c701 100644
--- a/src/ReplayGainInfo.hxx
+++ b/src/ReplayGainInfo.hxx
@@ -39,8 +39,7 @@ struct ReplayGainTuple {
peak = 0.0;
}
- gcc_pure
- bool IsDefined() const {
+ constexpr bool IsDefined() const {
return gain > -100;
}
@@ -52,6 +51,11 @@ struct ReplayGainTuple {
struct ReplayGainInfo {
ReplayGainTuple tuples[2];
+ constexpr bool IsDefined() const {
+ return tuples[REPLAY_GAIN_ALBUM].IsDefined() ||
+ tuples[REPLAY_GAIN_TRACK].IsDefined();
+ }
+
void Clear() {
tuples[REPLAY_GAIN_ALBUM].Clear();
tuples[REPLAY_GAIN_TRACK].Clear();
diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx
index 05d462b6d..07b9458ca 100644
--- a/src/SongPrint.cxx
+++ b/src/SongPrint.cxx
@@ -122,5 +122,8 @@ song_print_info(Client &client, const DetachedSong &song, bool base)
const auto duration = song.GetDuration();
if (!duration.IsNegative())
- client_printf(client, "Time: %u\n", duration.RoundS());
+ client_printf(client, "Time: %i\n"
+ "duration: %1.3f\n",
+ duration.RoundS(),
+ duration.ToDoubleS());
}
diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx
index 4937fa622..51ee80d83 100644
--- a/src/TagPrint.cxx
+++ b/src/TagPrint.cxx
@@ -23,8 +23,6 @@
#include "tag/TagSettings.h"
#include "client/Client.hxx"
-#define SONG_TIME "Time: "
-
void tag_print_types(Client &client)
{
int i;
@@ -53,7 +51,10 @@ tag_print_values(Client &client, const Tag &tag)
void tag_print(Client &client, const Tag &tag)
{
if (!tag.duration.IsNegative())
- client_printf(client, SONG_TIME "%i\n", tag.duration.RoundS());
+ client_printf(client, "Time: %i\n"
+ "duration: %1.3f\n",
+ tag.duration.RoundS(),
+ tag.duration.ToDoubleS());
tag_print_values(client, tag);
}
diff --git a/src/archive/ArchiveList.cxx b/src/archive/ArchiveList.cxx
index 79c3a16fe..904f640c3 100644
--- a/src/archive/ArchiveList.cxx
+++ b/src/archive/ArchiveList.cxx
@@ -29,13 +29,13 @@
#include <string.h>
const ArchivePlugin *const archive_plugins[] = {
-#ifdef HAVE_BZ2
+#ifdef ENABLE_BZ2
&bz2_archive_plugin,
#endif
-#ifdef HAVE_ZZIP
+#ifdef ENABLE_ZZIP
&zzip_archive_plugin,
#endif
-#ifdef HAVE_ISO9660
+#ifdef ENABLE_ISO9660
&iso9660_archive_plugin,
#endif
nullptr
diff --git a/src/archive/plugins/Bzip2ArchivePlugin.cxx b/src/archive/plugins/Bzip2ArchivePlugin.cxx
index 2b92049dd..8548cb1e8 100644
--- a/src/archive/plugins/Bzip2ArchivePlugin.cxx
+++ b/src/archive/plugins/Bzip2ArchivePlugin.cxx
@@ -53,7 +53,7 @@ public:
Bzip2ArchiveFile(Path path, InputStream *_is)
:ArchiveFile(bz2_archive_plugin),
- name(PathTraitsFS::GetBase(path.c_str())),
+ name(path.GetBase().c_str()),
istream(_is) {
// remove .bz2 suffix
const size_t len = name.length();
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 6a4b18198..e0b16d77d 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -35,8 +35,10 @@
#include "protocol/Result.hxx"
#include "Partition.hxx"
#include "client/Client.hxx"
+#include "util/Macros.hxx"
#include "util/Tokenizer.hxx"
#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
#ifdef ENABLE_SQLITE
#include "StickerCommands.hxx"
@@ -60,22 +62,22 @@ struct command {
unsigned permission;
int min;
int max;
- CommandResult (*handler)(Client &client, unsigned argc, char **argv);
+ CommandResult (*handler)(Client &client, ConstBuffer<const char *> args);
};
/* don't be fooled, this is the command handler for "commands" command */
static CommandResult
-handle_commands(Client &client, unsigned argc, char *argv[]);
+handle_commands(Client &client, ConstBuffer<const char *> args);
static CommandResult
-handle_not_commands(Client &client, unsigned argc, char *argv[]);
+handle_not_commands(Client &client, ConstBuffer<const char *> args);
/**
* The command registry.
*
* This array must be sorted!
*/
-static const struct command commands[] = {
+static constexpr struct command commands[] = {
{ "add", PERMISSION_ADD, 1, 1, handle_add },
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
@@ -194,7 +196,7 @@ static const struct command commands[] = {
{ "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
};
-static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
+static constexpr unsigned num_commands = ARRAY_SIZE(commands);
static bool
command_available(gcc_unused const Partition &partition,
@@ -210,19 +212,27 @@ command_available(gcc_unused const Partition &partition,
return neighbor_commands_available(partition.instance);
#endif
+ if (strcmp(cmd->cmd, "save") == 0 ||
+ strcmp(cmd->cmd, "rm") == 0 ||
+ strcmp(cmd->cmd, "rename") == 0 ||
+ strcmp(cmd->cmd, "playlistdelete") == 0 ||
+ strcmp(cmd->cmd, "playlistmove") == 0 ||
+ strcmp(cmd->cmd, "playlistclear") == 0 ||
+ strcmp(cmd->cmd, "playlistadd") == 0 ||
+ strcmp(cmd->cmd, "listplaylists") == 0)
+ return playlist_commands_available();
+
return true;
}
/* don't be fooled, this is the command handler for "commands" command */
static CommandResult
-handle_commands(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_commands(Client &client, gcc_unused ConstBuffer<const char *> args)
{
const unsigned permission = client.GetPermission();
- const struct command *cmd;
for (unsigned i = 0; i < num_commands; ++i) {
- cmd = &commands[i];
+ const struct command *cmd = &commands[i];
if (cmd->permission == (permission & cmd->permission) &&
command_available(client.partition, cmd))
@@ -233,14 +243,12 @@ handle_commands(Client &client,
}
static CommandResult
-handle_not_commands(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_not_commands(Client &client, gcc_unused ConstBuffer<const char *> args)
{
const unsigned permission = client.GetPermission();
- const struct command *cmd;
for (unsigned i = 0; i < num_commands; ++i) {
- cmd = &commands[i];
+ const struct command *cmd = &commands[i];
if (cmd->permission != (permission & cmd->permission))
client_printf(client, "command: %s\n", cmd->cmd);
@@ -266,13 +274,12 @@ static const struct command *
command_lookup(const char *name)
{
unsigned a = 0, b = num_commands, i;
- int cmp;
/* binary search */
do {
i = (a + b) / 2;
- cmp = strcmp(name, commands[i].cmd);
+ const auto cmp = strcmp(name, commands[i].cmd);
if (cmp == 0)
return &commands[i];
else if (cmp < 0)
@@ -286,11 +293,8 @@ command_lookup(const char *name)
static bool
command_check_request(const struct command *cmd, Client &client,
- unsigned permission, unsigned argc, char *argv[])
+ unsigned permission, ConstBuffer<const char *> args)
{
- const unsigned min = cmd->min + 1;
- const unsigned max = cmd->max + 1;
-
if (cmd->permission != (permission & cmd->permission)) {
command_error(client, ACK_ERROR_PERMISSION,
"you don't have permission for \"%s\"",
@@ -298,21 +302,24 @@ command_check_request(const struct command *cmd, Client &client,
return false;
}
- if (min == 0)
+ const int min = cmd->min;
+ const int max = cmd->max;
+
+ if (min < 0)
return true;
- if (min == max && max != argc) {
+ if (min == max && unsigned(max) != args.size) {
command_error(client, ACK_ERROR_ARG,
"wrong number of arguments for \"%s\"",
- argv[0]);
+ cmd->cmd);
return false;
- } else if (argc < min) {
+ } else if (args.size < unsigned(min)) {
command_error(client, ACK_ERROR_ARG,
- "too few arguments for \"%s\"", argv[0]);
+ "too few arguments for \"%s\"", cmd->cmd);
return false;
- } else if (argc > max && max /* != 0 */ ) {
+ } else if (max >= 0 && args.size > unsigned(max)) {
command_error(client, ACK_ERROR_ARG,
- "too many arguments for \"%s\"", argv[0]);
+ "too many arguments for \"%s\"", cmd->cmd);
return false;
} else
return true;
@@ -320,25 +327,20 @@ command_check_request(const struct command *cmd, Client &client,
static const struct command *
command_checked_lookup(Client &client, unsigned permission,
- unsigned argc, char *argv[])
+ const char *cmd_name, ConstBuffer<const char *> args)
{
- const struct command *cmd;
-
current_command = "";
- if (argc == 0)
- return nullptr;
-
- cmd = command_lookup(argv[0]);
+ const struct command *cmd = command_lookup(cmd_name);
if (cmd == nullptr) {
command_error(client, ACK_ERROR_UNKNOWN,
- "unknown command \"%s\"", argv[0]);
+ "unknown command \"%s\"", cmd_name);
return nullptr;
}
current_command = cmd->cmd;
- if (!command_check_request(cmd, client, permission, argc, argv))
+ if (!command_check_request(cmd, client, permission, args))
return nullptr;
return cmd;
@@ -348,17 +350,18 @@ CommandResult
command_process(Client &client, unsigned num, char *line)
{
Error error;
- char *argv[COMMAND_ARGV_MAX] = { nullptr };
- const struct command *cmd;
- CommandResult ret = CommandResult::ERROR;
command_list_num = num;
/* get the command name (first word on the line) */
+ /* we have to set current_command because command_error()
+ expects it to be set */
Tokenizer tokenizer(line);
- argv[0] = tokenizer.NextWord(error);
- if (argv[0] == nullptr) {
+
+ const char *const cmd_name = current_command =
+ tokenizer.NextWord(error);
+ if (cmd_name == nullptr) {
current_command = "";
if (tokenizer.IsEnd())
command_error(client, ACK_ERROR_UNKNOWN,
@@ -374,38 +377,41 @@ command_process(Client &client, unsigned num, char *line)
return CommandResult::FINISH;
}
- unsigned argc = 1;
+ char *argv[COMMAND_ARGV_MAX];
+ ConstBuffer<const char *> args(argv, 0);
/* now parse the arguments (quoted or unquoted) */
- while (argc < COMMAND_ARGV_MAX &&
- (argv[argc] =
- tokenizer.NextParam(error)) != nullptr)
- ++argc;
-
- /* some error checks; we have to set current_command because
- command_error() expects it to be set */
-
- current_command = argv[0];
-
- if (argc >= COMMAND_ARGV_MAX) {
- command_error(client, ACK_ERROR_ARG, "Too many arguments");
- current_command = nullptr;
- return CommandResult::ERROR;
- }
-
- if (!tokenizer.IsEnd()) {
- command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage());
- current_command = nullptr;
- return CommandResult::ERROR;
+ while (true) {
+ if (args.size == COMMAND_ARGV_MAX) {
+ command_error(client, ACK_ERROR_ARG,
+ "Too many arguments");
+ current_command = nullptr;
+ return CommandResult::ERROR;
+ }
+
+ char *a = tokenizer.NextParam(error);
+ if (a == nullptr) {
+ if (tokenizer.IsEnd())
+ break;
+
+ command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage());
+ current_command = nullptr;
+ return CommandResult::ERROR;
+ }
+
+ argv[args.size++] = a;
}
/* look up and invoke the command handler */
- cmd = command_checked_lookup(client, client.GetPermission(),
- argc, argv);
- if (cmd)
- ret = cmd->handler(client, argc, argv);
+ const struct command *cmd =
+ command_checked_lookup(client, client.GetPermission(),
+ cmd_name, args);
+
+ CommandResult ret = cmd
+ ? cmd->handler(client, args)
+ : CommandResult::ERROR;
current_command = nullptr;
command_list_num = 0;
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index a3ea8d0ae..2d1e1f69b 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -32,6 +32,7 @@
#include "util/Error.hxx"
#include "SongFilter.hxx"
#include "protocol/Result.hxx"
+#include "protocol/ArgParser.hxx"
#include "BulkEdit.hxx"
#include <string.h>
@@ -49,12 +50,10 @@ handle_listfiles_db(Client &client, const char *uri)
}
CommandResult
-handle_lsinfo2(Client &client, unsigned argc, char *argv[])
+handle_lsinfo2(Client &client, ConstBuffer<const char *> args)
{
- const char *const uri = argc == 2
- ? argv[1]
- /* default is root directory */
- : "";
+ /* default is root directory */
+ const char *const uri = args.IsEmpty() ? "" : args.front();
const DatabaseSelection selection(uri, false);
@@ -66,9 +65,17 @@ handle_lsinfo2(Client &client, unsigned argc, char *argv[])
}
static CommandResult
-handle_match(Client &client, unsigned argc, char *argv[], bool fold_case)
+handle_match(Client &client, ConstBuffer<const char *> args, bool fold_case)
{
- ConstBuffer<const char *> args(argv + 1, argc - 1);
+ unsigned window_start = 0, window_end = std::numeric_limits<int>::max();
+ if (args.size >= 2 && strcmp(args[args.size - 2], "window") == 0) {
+ if (!check_range(client, &window_start, &window_end,
+ args.back()))
+ return CommandResult::ERROR;
+
+ args.pop_back();
+ args.pop_back();
+ }
SongFilter filter;
if (!filter.Parse(args, fold_case)) {
@@ -79,28 +86,27 @@ handle_match(Client &client, unsigned argc, char *argv[], bool fold_case)
const DatabaseSelection selection("", true, &filter);
Error error;
- return db_selection_print(client, selection, true, false, error)
+ return db_selection_print(client, selection, true, false,
+ window_start, window_end, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_find(Client &client, unsigned argc, char *argv[])
+handle_find(Client &client, ConstBuffer<const char *> args)
{
- return handle_match(client, argc, argv, false);
+ return handle_match(client, args, false);
}
CommandResult
-handle_search(Client &client, unsigned argc, char *argv[])
+handle_search(Client &client, ConstBuffer<const char *> args)
{
- return handle_match(client, argc, argv, true);
+ return handle_match(client, args, true);
}
static CommandResult
-handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case)
+handle_match_add(Client &client, ConstBuffer<const char *> args, bool fold_case)
{
- ConstBuffer<const char *> args(argv + 1, argc - 1);
-
SongFilter filter;
if (!filter.Parse(args, fold_case)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
@@ -117,21 +123,20 @@ handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case)
}
CommandResult
-handle_findadd(Client &client, unsigned argc, char *argv[])
+handle_findadd(Client &client, ConstBuffer<const char *> args)
{
- return handle_match_add(client, argc, argv, false);
+ return handle_match_add(client, args, false);
}
CommandResult
-handle_searchadd(Client &client, unsigned argc, char *argv[])
+handle_searchadd(Client &client, ConstBuffer<const char *> args)
{
- return handle_match_add(client, argc, argv, true);
+ return handle_match_add(client, args, true);
}
CommandResult
-handle_searchaddpl(Client &client, unsigned argc, char *argv[])
+handle_searchaddpl(Client &client, ConstBuffer<const char *> args)
{
- ConstBuffer<const char *> args(argv + 1, argc - 1);
const char *playlist = args.shift();
SongFilter filter;
@@ -152,10 +157,8 @@ handle_searchaddpl(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_count(Client &client, unsigned argc, char *argv[])
+handle_count(Client &client, ConstBuffer<const char *> args)
{
- ConstBuffer<const char *> args(argv + 1, argc - 1);
-
TagType group = TAG_NUM_OF_ITEM_TYPES;
if (args.size >= 2 && strcmp(args[args.size - 2], "group") == 0) {
const char *s = args[args.size - 1];
@@ -183,24 +186,21 @@ handle_count(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_listall(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_listall(Client &client, ConstBuffer<const char *> args)
{
- const char *directory = "";
-
- if (argc == 2)
- directory = argv[1];
+ /* default is root directory */
+ const char *const uri = args.IsEmpty() ? "" : args.front();
Error error;
- return db_selection_print(client, DatabaseSelection(directory, true),
+ return db_selection_print(client, DatabaseSelection(uri, true),
false, false, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_list(Client &client, unsigned argc, char *argv[])
+handle_list(Client &client, ConstBuffer<const char *> args)
{
- ConstBuffer<const char *> args(argv + 1, argc - 1);
const char *tag_name = args.shift();
unsigned tagType = locate_parse_type(tag_name);
@@ -271,15 +271,13 @@ handle_list(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_listallinfo(Client &client, ConstBuffer<const char *> args)
{
- const char *directory = "";
-
- if (argc == 2)
- directory = argv[1];
+ /* default is root directory */
+ const char *const uri = args.IsEmpty() ? "" : args.front();
Error error;
- return db_selection_print(client, DatabaseSelection(directory, true),
+ return db_selection_print(client, DatabaseSelection(uri, true),
true, false, error)
? CommandResult::OK
: print_error(client, error);
diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx
index 7abf89e0c..0f6e2700a 100644
--- a/src/command/DatabaseCommands.hxx
+++ b/src/command/DatabaseCommands.hxx
@@ -23,38 +23,39 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
handle_listfiles_db(Client &client, const char *uri);
CommandResult
-handle_lsinfo2(Client &client, unsigned argc, char *argv[]);
+handle_lsinfo2(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_find(Client &client, unsigned argc, char *argv[]);
+handle_find(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_findadd(Client &client, unsigned argc, char *argv[]);
+handle_findadd(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_search(Client &client, unsigned argc, char *argv[]);
+handle_search(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_searchadd(Client &client, unsigned argc, char *argv[]);
+handle_searchadd(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_searchaddpl(Client &client, unsigned argc, char *argv[]);
+handle_searchaddpl(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_count(Client &client, unsigned argc, char *argv[]);
+handle_count(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_listall(Client &client, unsigned argc, char *argv[]);
+handle_listall(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_list(Client &client, unsigned argc, char *argv[]);
+handle_list(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_listallinfo(Client &client, unsigned argc, char *argv[]);
+handle_listallinfo(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx
index 1b6a11cf5..acf71eca4 100644
--- a/src/command/FileCommands.cxx
+++ b/src/command/FileCommands.cxx
@@ -25,6 +25,7 @@
#include "protocol/Ack.hxx"
#include "protocol/Result.hxx"
#include "client/Client.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/CharUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
@@ -202,11 +203,10 @@ read_file_comments(Client &client, const Path path_fs)
}
CommandResult
-handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_read_comments(Client &client, ConstBuffer<const char *> args)
{
- assert(argc == 2);
-
- const char *const uri = argv[1];
+ assert(args.size == 1);
+ const char *const uri = args.front();
if (memcmp(uri, "file:///", 8) == 0) {
/* read comments from arbitrary local file */
diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx
index 62835a82c..b77157e3f 100644
--- a/src/command/FileCommands.hxx
+++ b/src/command/FileCommands.hxx
@@ -23,11 +23,12 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
handle_listfiles_local(Client &client, const char *path_utf8);
CommandResult
-handle_read_comments(Client &client, unsigned argc, char *argv[]);
+handle_read_comments(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx
index a86bdf30c..4bf22abcc 100644
--- a/src/command/MessageCommands.cxx
+++ b/src/command/MessageCommands.cxx
@@ -24,6 +24,7 @@
#include "Instance.hxx"
#include "Partition.hxx"
#include "protocol/Result.hxx"
+#include "util/ConstBuffer.hxx"
#include <set>
#include <string>
@@ -31,11 +32,12 @@
#include <assert.h>
CommandResult
-handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_subscribe(Client &client, ConstBuffer<const char *> args)
{
- assert(argc == 2);
+ assert(args.size == 1);
+ const char *const channel_name = args[0];
- switch (client.Subscribe(argv[1])) {
+ switch (client.Subscribe(channel_name)) {
case Client::SubscribeResult::OK:
return CommandResult::OK;
@@ -61,11 +63,12 @@ handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_unsubscribe(Client &client, ConstBuffer<const char *> args)
{
- assert(argc == 2);
+ assert(args.size == 1);
+ const char *const channel_name = args[0];
- if (client.Unsubscribe(argv[1]))
+ if (client.Unsubscribe(channel_name))
return CommandResult::OK;
else {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -75,10 +78,9 @@ handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_channels(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_channels(Client &client, gcc_unused ConstBuffer<const char *> args)
{
- assert(argc == 1);
+ assert(args.IsEmpty());
std::set<std::string> channels;
for (const auto &c : *client.partition.instance.client_list)
@@ -93,9 +95,9 @@ handle_channels(Client &client,
CommandResult
handle_read_messages(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+ gcc_unused ConstBuffer<const char *> args)
{
- assert(argc == 1);
+ assert(args.IsEmpty());
while (!client.messages.empty()) {
const ClientMessage &msg = client.messages.front();
@@ -109,19 +111,21 @@ handle_read_messages(Client &client,
}
CommandResult
-handle_send_message(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_send_message(Client &client, ConstBuffer<const char *> args)
{
- assert(argc == 3);
+ assert(args.size == 2);
- if (!client_message_valid_channel_name(argv[1])) {
+ const char *const channel_name = args[0];
+ const char *const message_text = args[1];
+
+ if (!client_message_valid_channel_name(channel_name)) {
command_error(client, ACK_ERROR_ARG,
"invalid channel name");
return CommandResult::ERROR;
}
bool sent = false;
- const ClientMessage msg(argv[1], argv[2]);
+ const ClientMessage msg(channel_name, message_text);
for (auto &c : *client.partition.instance.client_list)
if (c.PushMessage(msg))
sent = true;
diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx
index ac8afe2fb..b10863277 100644
--- a/src/command/MessageCommands.hxx
+++ b/src/command/MessageCommands.hxx
@@ -23,20 +23,21 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_subscribe(Client &client, unsigned argc, char *argv[]);
+handle_subscribe(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_unsubscribe(Client &client, unsigned argc, char *argv[]);
+handle_unsubscribe(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_channels(Client &client, unsigned argc, char *argv[]);
+handle_channels(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_read_messages(Client &client, unsigned argc, char *argv[]);
+handle_read_messages(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_send_message(Client &client, unsigned argc, char *argv[]);
+handle_send_message(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx
index 22e8adf9e..3efae7883 100644
--- a/src/command/NeighborCommands.cxx
+++ b/src/command/NeighborCommands.cxx
@@ -25,6 +25,7 @@
#include "protocol/Result.hxx"
#include "neighbor/Glue.hxx"
#include "neighbor/Info.hxx"
+#include "util/ConstBuffer.hxx"
#include <set>
#include <string>
@@ -38,8 +39,7 @@ neighbor_commands_available(const Instance &instance)
}
CommandResult
-handle_listneighbors(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_listneighbors(Client &client, gcc_unused ConstBuffer<const char *> args)
{
const NeighborGlue *const neighbors =
client.partition.instance.neighbors;
diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx
index 7fb309aeb..e07f26925 100644
--- a/src/command/NeighborCommands.hxx
+++ b/src/command/NeighborCommands.hxx
@@ -25,12 +25,13 @@
struct Instance;
class Client;
+template<typename T> struct ConstBuffer;
gcc_pure
bool
neighbor_commands_available(const Instance &instance);
CommandResult
-handle_listneighbors(Client &client, unsigned argc, char *argv[]);
+handle_listneighbors(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx
index a924f77b5..6328acc4c 100644
--- a/src/command/OtherCommands.cxx
+++ b/src/command/OtherCommands.cxx
@@ -37,6 +37,7 @@
#include "mixer/Volume.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
#include "fs/AllocatedPath.hxx"
#include "Stats.hxx"
#include "Permission.hxx"
@@ -68,8 +69,7 @@ print_spl_list(Client &client, const PlaylistVector &list)
}
CommandResult
-handle_urlhandlers(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_urlhandlers(Client &client, gcc_unused ConstBuffer<const char *> args)
{
if (client.IsLocal())
client_puts(client, "handler: file://\n");
@@ -78,31 +78,27 @@ handle_urlhandlers(Client &client,
}
CommandResult
-handle_decoders(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_decoders(Client &client, gcc_unused ConstBuffer<const char *> args)
{
decoder_list_print(client);
return CommandResult::OK;
}
CommandResult
-handle_tagtypes(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_tagtypes(Client &client, gcc_unused ConstBuffer<const char *> args)
{
tag_print_types(client);
return CommandResult::OK;
}
CommandResult
-handle_kill(gcc_unused Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_kill(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
{
return CommandResult::KILL;
}
CommandResult
-handle_close(gcc_unused Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_close(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
{
return CommandResult::FINISH;
}
@@ -116,12 +112,10 @@ print_tag(TagType type, const char *value, void *ctx)
}
CommandResult
-handle_listfiles(Client &client, unsigned argc, char *argv[])
+handle_listfiles(Client &client, ConstBuffer<const char *> args)
{
- const char *const uri = argc == 2
- ? argv[1]
- /* default is root directory */
- : "";
+ /* default is root directory */
+ const char *const uri = args.IsEmpty() ? "" : args.front();
if (memcmp(uri, "file:///", 8) == 0)
/* list local directory */
@@ -157,12 +151,10 @@ static constexpr tag_handler print_tag_handler = {
};
CommandResult
-handle_lsinfo(Client &client, unsigned argc, char *argv[])
+handle_lsinfo(Client &client, ConstBuffer<const char *> args)
{
- const char *const uri = argc == 2
- ? argv[1]
- /* default is root directory */
- : "";
+ /* default is root directory */
+ const char *const uri = args.IsEmpty() ? "" : args.front();
if (memcmp(uri, "file:///", 8) == 0) {
/* print information about an arbitrary local file */
@@ -207,7 +199,7 @@ handle_lsinfo(Client &client, unsigned argc, char *argv[])
}
#ifdef ENABLE_DATABASE
- CommandResult result = handle_lsinfo2(client, argc, argv);
+ CommandResult result = handle_lsinfo2(client, args);
if (result != CommandResult::OK)
return result;
#endif
@@ -265,14 +257,14 @@ handle_update(Client &client, Database &db,
#endif
static CommandResult
-handle_update(Client &client, unsigned argc, char *argv[], bool discard)
+handle_update(Client &client, ConstBuffer<const char *> args, bool discard)
{
#ifdef ENABLE_DATABASE
const char *path = "";
- assert(argc <= 2);
- if (argc == 2) {
- path = argv[1];
+ assert(args.size <= 1);
+ if (!args.IsEmpty()) {
+ path = args.front();
if (*path == 0 || strcmp(path, "/") == 0)
/* backwards compatibility with MPD 0.15 */
@@ -292,8 +284,7 @@ handle_update(Client &client, unsigned argc, char *argv[], bool discard)
if (db != nullptr)
return handle_update(client, *db, path, discard);
#else
- (void)argc;
- (void)argv;
+ (void)args;
(void)discard;
#endif
@@ -302,24 +293,24 @@ handle_update(Client &client, unsigned argc, char *argv[], bool discard)
}
CommandResult
-handle_update(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_update(Client &client, gcc_unused ConstBuffer<const char *> args)
{
- return handle_update(client, argc, argv, false);
+ return handle_update(client, args, false);
}
CommandResult
-handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rescan(Client &client, gcc_unused ConstBuffer<const char *> args)
{
- return handle_update(client, argc, argv, true);
+ return handle_update(client, args, true);
}
CommandResult
-handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_setvol(Client &client, ConstBuffer<const char *> args)
{
unsigned level;
bool success;
- if (!check_unsigned(client, &level, argv[1]))
+ if (!check_unsigned(client, &level, args.front()))
return CommandResult::ERROR;
if (level > 100) {
@@ -338,10 +329,10 @@ handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_volume(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_volume(Client &client, ConstBuffer<const char *> args)
{
int relative;
- if (!check_int(client, &relative, argv[1]))
+ if (!check_int(client, &relative, args.front()))
return CommandResult::ERROR;
if (relative < -100 || relative > 100) {
@@ -372,26 +363,24 @@ handle_volume(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_stats(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_stats(Client &client, gcc_unused ConstBuffer<const char *> args)
{
stats_print(client);
return CommandResult::OK;
}
CommandResult
-handle_ping(gcc_unused Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_ping(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
{
return CommandResult::OK;
}
CommandResult
-handle_password(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_password(Client &client, ConstBuffer<const char *> args)
{
unsigned permission = 0;
- if (getPermissionFromPassword(argv[1], &permission) < 0) {
+ if (getPermissionFromPassword(args.front(), &permission) < 0) {
command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
return CommandResult::ERROR;
}
@@ -402,8 +391,7 @@ handle_password(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_config(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_config(Client &client, gcc_unused ConstBuffer<const char *> args)
{
if (!client.IsLocal()) {
command_error(client, ACK_ERROR_PERMISSION,
@@ -423,17 +411,16 @@ handle_config(Client &client,
}
CommandResult
-handle_idle(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_idle(Client &client, ConstBuffer<const char *> args)
{
unsigned flags = 0;
- for (unsigned i = 1; i < argc; ++i) {
- unsigned event = idle_parse_name(argv[i]);
+ for (const char *i : args) {
+ unsigned event = idle_parse_name(i);
if (event == 0) {
command_error(client, ACK_ERROR_ARG,
"Unrecognized idle event: %s",
- argv[i]);
+ i);
return CommandResult::ERROR;
}
diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx
index 7cfa35dfb..a0076954e 100644
--- a/src/command/OtherCommands.hxx
+++ b/src/command/OtherCommands.hxx
@@ -23,53 +23,54 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_urlhandlers(Client &client, unsigned argc, char *argv[]);
+handle_urlhandlers(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_decoders(Client &client, unsigned argc, char *argv[]);
+handle_decoders(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_tagtypes(Client &client, unsigned argc, char *argv[]);
+handle_tagtypes(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_kill(Client &client, unsigned argc, char *argv[]);
+handle_kill(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_close(Client &client, unsigned argc, char *argv[]);
+handle_close(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_listfiles(Client &client, unsigned argc, char *argv[]);
+handle_listfiles(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_lsinfo(Client &client, unsigned argc, char *argv[]);
+handle_lsinfo(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_update(Client &client, unsigned argc, char *argv[]);
+handle_update(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_rescan(Client &client, unsigned argc, char *argv[]);
+handle_rescan(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_setvol(Client &client, unsigned argc, char *argv[]);
+handle_setvol(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_volume(Client &client, unsigned argc, char *argv[]);
+handle_volume(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_stats(Client &client, unsigned argc, char *argv[]);
+handle_stats(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_ping(Client &client, unsigned argc, char *argv[]);
+handle_ping(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_password(Client &client, unsigned argc, char *argv[]);
+handle_password(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_config(Client &client, unsigned argc, char *argv[]);
+handle_config(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_idle(Client &client, unsigned argc, char *argv[]);
+handle_idle(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index c69a0dd65..5b0894310 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -25,12 +25,15 @@
#include "protocol/ArgParser.hxx"
#include "client/Client.hxx"
#include "Partition.hxx"
+#include "util/ConstBuffer.hxx"
CommandResult
-handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_enableoutput(Client &client, ConstBuffer<const char *> args)
{
+ assert(args.size == 1);
+
unsigned device;
- if (!check_unsigned(client, &device, argv[1]))
+ if (!check_unsigned(client, &device, args.front()))
return CommandResult::ERROR;
if (!audio_output_enable_index(client.partition.outputs, device)) {
@@ -43,10 +46,12 @@ handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_disableoutput(Client &client, ConstBuffer<const char *> args)
{
+ assert(args.size == 1);
+
unsigned device;
- if (!check_unsigned(client, &device, argv[1]))
+ if (!check_unsigned(client, &device, args.front()))
return CommandResult::ERROR;
if (!audio_output_disable_index(client.partition.outputs, device)) {
@@ -59,10 +64,12 @@ handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_toggleoutput(Client &client, ConstBuffer<const char *> args)
{
+ assert(args.size == 1);
+
unsigned device;
- if (!check_unsigned(client, &device, argv[1]))
+ if (!check_unsigned(client, &device, args.front()))
return CommandResult::ERROR;
if (!audio_output_toggle_index(client.partition.outputs, device)) {
@@ -75,9 +82,10 @@ handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_devices(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_devices(Client &client, gcc_unused ConstBuffer<const char *> args)
{
+ assert(args.IsEmpty());
+
printAudioDevices(client, client.partition.outputs);
return CommandResult::OK;
diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx
index 8d6be0511..d550791ac 100644
--- a/src/command/OutputCommands.hxx
+++ b/src/command/OutputCommands.hxx
@@ -23,17 +23,18 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_enableoutput(Client &client, unsigned argc, char *argv[]);
+handle_enableoutput(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_disableoutput(Client &client, unsigned argc, char *argv[]);
+handle_disableoutput(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_toggleoutput(Client &client, unsigned argc, char *argv[]);
+handle_toggleoutput(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_devices(Client &client, unsigned argc, char *argv[]);
+handle_devices(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx
index cd7f42289..c24088f9a 100644
--- a/src/command/PlayerCommands.cxx
+++ b/src/command/PlayerCommands.cxx
@@ -30,6 +30,7 @@
#include "protocol/ArgParser.hxx"
#include "AudioFormat.hxx"
#include "ReplayGainConfig.hxx"
+#include "util/ConstBuffer.hxx"
#ifdef ENABLE_DATABASE
#include "db/update/Service.hxx"
@@ -56,22 +57,22 @@
#define COMMAND_STATUS_UPDATING_DB "updating_db"
CommandResult
-handle_play(Client &client, unsigned argc, char *argv[])
+handle_play(Client &client, ConstBuffer<const char *> args)
{
int song = -1;
- if (argc == 2 && !check_int(client, &song, argv[1]))
+ if (!args.IsEmpty() && !check_int(client, &song, args.front()))
return CommandResult::ERROR;
PlaylistResult result = client.partition.PlayPosition(song);
return print_playlist_result(client, result);
}
CommandResult
-handle_playid(Client &client, unsigned argc, char *argv[])
+handle_playid(Client &client, ConstBuffer<const char *> args)
{
int id = -1;
- if (argc == 2 && !check_int(client, &id, argv[1]))
+ if (!args.IsEmpty() && !check_int(client, &id, args.front()))
return CommandResult::ERROR;
PlaylistResult result = client.partition.PlayId(id);
@@ -79,28 +80,25 @@ handle_playid(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_stop(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_stop(Client &client, gcc_unused ConstBuffer<const char *> args)
{
client.partition.Stop();
return CommandResult::OK;
}
CommandResult
-handle_currentsong(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_currentsong(Client &client, gcc_unused ConstBuffer<const char *> args)
{
playlist_print_current(client, client.playlist);
return CommandResult::OK;
}
CommandResult
-handle_pause(Client &client,
- unsigned argc, char *argv[])
+handle_pause(Client &client, ConstBuffer<const char *> args)
{
- if (argc == 2) {
+ if (!args.IsEmpty()) {
bool pause_flag;
- if (!check_bool(client, &pause_flag, argv[1]))
+ if (!check_bool(client, &pause_flag, args.front()))
return CommandResult::ERROR;
client.player_control.SetPause(pause_flag);
@@ -111,8 +109,7 @@ handle_pause(Client &client,
}
CommandResult
-handle_status(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_status(Client &client, gcc_unused ConstBuffer<const char *> args)
{
const char *state = nullptr;
int song;
@@ -182,6 +179,10 @@ handle_status(Client &client,
player_status.elapsed_time.ToDoubleS(),
player_status.bit_rate);
+ if (!player_status.total_time.IsNegative())
+ client_printf(client, "duration: %1.3f\n",
+ player_status.total_time.ToDoubleS());
+
if (player_status.audio_format.IsDefined()) {
struct audio_format_string af_string;
@@ -222,8 +223,7 @@ handle_status(Client &client,
}
CommandResult
-handle_next(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_next(Client &client, gcc_unused ConstBuffer<const char *> args)
{
playlist &playlist = client.playlist;
@@ -239,18 +239,17 @@ handle_next(Client &client,
}
CommandResult
-handle_previous(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_previous(Client &client, gcc_unused ConstBuffer<const char *> args)
{
client.partition.PlayPrevious();
return CommandResult::OK;
}
CommandResult
-handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_repeat(Client &client, ConstBuffer<const char *> args)
{
bool status;
- if (!check_bool(client, &status, argv[1]))
+ if (!check_bool(client, &status, args.front()))
return CommandResult::ERROR;
client.partition.SetRepeat(status);
@@ -258,10 +257,10 @@ handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_single(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_single(Client &client, ConstBuffer<const char *> args)
{
bool status;
- if (!check_bool(client, &status, argv[1]))
+ if (!check_bool(client, &status, args.front()))
return CommandResult::ERROR;
client.partition.SetSingle(status);
@@ -269,10 +268,10 @@ handle_single(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_consume(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_consume(Client &client, ConstBuffer<const char *> args)
{
bool status;
- if (!check_bool(client, &status, argv[1]))
+ if (!check_bool(client, &status, args.front()))
return CommandResult::ERROR;
client.partition.SetConsume(status);
@@ -280,10 +279,10 @@ handle_consume(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_random(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_random(Client &client, ConstBuffer<const char *> args)
{
bool status;
- if (!check_bool(client, &status, argv[1]))
+ if (!check_bool(client, &status, args.front()))
return CommandResult::ERROR;
client.partition.SetRandom(status);
@@ -292,22 +291,21 @@ handle_random(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_clearerror(gcc_unused Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_clearerror(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
{
client.player_control.ClearError();
return CommandResult::OK;
}
CommandResult
-handle_seek(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_seek(Client &client, ConstBuffer<const char *> args)
{
unsigned song;
SongTime seek_time;
- if (!check_unsigned(client, &song, argv[1]))
+ if (!check_unsigned(client, &song, args[0]))
return CommandResult::ERROR;
- if (!ParseCommandArg(client, seek_time, argv[2]))
+ if (!ParseCommandArg(client, seek_time, args[1]))
return CommandResult::ERROR;
PlaylistResult result =
@@ -316,14 +314,14 @@ handle_seek(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_seekid(Client &client, ConstBuffer<const char *> args)
{
unsigned id;
SongTime seek_time;
- if (!check_unsigned(client, &id, argv[1]))
+ if (!check_unsigned(client, &id, args[0]))
return CommandResult::ERROR;
- if (!ParseCommandArg(client, seek_time, argv[2]))
+ if (!ParseCommandArg(client, seek_time, args[1]))
return CommandResult::ERROR;
PlaylistResult result =
@@ -332,9 +330,9 @@ handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_seekcur(Client &client, ConstBuffer<const char *> args)
{
- const char *p = argv[1];
+ const char *p = args.front();
bool relative = *p == '+' || *p == '-';
SignedSongTime seek_time;
if (!ParseCommandArg(client, seek_time, p))
@@ -346,11 +344,11 @@ handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_crossfade(Client &client, ConstBuffer<const char *> args)
{
unsigned xfade_time;
- if (!check_unsigned(client, &xfade_time, argv[1]))
+ if (!check_unsigned(client, &xfade_time, args.front()))
return CommandResult::ERROR;
client.player_control.SetCrossFade(xfade_time);
@@ -358,11 +356,11 @@ handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_mixrampdb(Client &client, ConstBuffer<const char *> args)
{
float db;
- if (!check_float(client, &db, argv[1]))
+ if (!check_float(client, &db, args.front()))
return CommandResult::ERROR;
client.player_control.SetMixRampDb(db);
@@ -370,11 +368,11 @@ handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_mixrampdelay(Client &client, ConstBuffer<const char *> args)
{
float delay_secs;
- if (!check_float(client, &delay_secs, argv[1]))
+ if (!check_float(client, &delay_secs, args.front()))
return CommandResult::ERROR;
client.player_control.SetMixRampDelay(delay_secs);
@@ -382,10 +380,9 @@ handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_replay_gain_mode(Client &client,
- gcc_unused unsigned argc, char *argv[])
+handle_replay_gain_mode(Client &client, ConstBuffer<const char *> args)
{
- if (!replay_gain_set_mode_string(argv[1])) {
+ if (!replay_gain_set_mode_string(args.front())) {
command_error(client, ACK_ERROR_ARG,
"Unrecognized replay gain mode");
return CommandResult::ERROR;
@@ -396,8 +393,7 @@ handle_replay_gain_mode(Client &client,
}
CommandResult
-handle_replay_gain_status(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_replay_gain_status(Client &client, gcc_unused ConstBuffer<const char *> args)
{
client_printf(client, "replay_gain_mode: %s\n",
replay_gain_get_mode_string());
diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx
index da7083f1e..492a4aced 100644
--- a/src/command/PlayerCommands.hxx
+++ b/src/command/PlayerCommands.hxx
@@ -23,68 +23,69 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_play(Client &client, unsigned argc, char *argv[]);
+handle_play(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playid(Client &client, unsigned argc, char *argv[]);
+handle_playid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_stop(Client &client, unsigned argc, char *argv[]);
+handle_stop(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_currentsong(Client &client, unsigned argc, char *argv[]);
+handle_currentsong(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_pause(Client &client, unsigned argc, char *argv[]);
+handle_pause(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_status(Client &client, unsigned argc, char *argv[]);
+handle_status(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_next(Client &client, unsigned argc, char *argv[]);
+handle_next(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_previous(Client &client, unsigned argc, char *avg[]);
+handle_previous(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_repeat(Client &client, unsigned argc, char *argv[]);
+handle_repeat(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_single(Client &client, unsigned argc, char *argv[]);
+handle_single(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_consume(Client &client, unsigned argc, char *argv[]);
+handle_consume(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_random(Client &client, unsigned argc, char *argv[]);
+handle_random(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_clearerror(Client &client, unsigned argc, char *argv[]);
+handle_clearerror(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_seek(Client &client, unsigned argc, char *argv[]);
+handle_seek(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_seekid(Client &client, unsigned argc, char *argv[]);
+handle_seekid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_seekcur(Client &client, unsigned argc, char *argv[]);
+handle_seekcur(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_crossfade(Client &client, unsigned argc, char *argv[]);
+handle_crossfade(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_mixrampdb(Client &client, unsigned argc, char *argv[]);
+handle_mixrampdb(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_mixrampdelay(Client &client, unsigned argc, char *argv[]);
+handle_mixrampdelay(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]);
+handle_replay_gain_mode(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_replay_gain_status(Client &client, unsigned argc, char *argv[]);
+handle_replay_gain_status(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx
index c2b18064c..4abc88031 100644
--- a/src/command/PlaylistCommands.cxx
+++ b/src/command/PlaylistCommands.cxx
@@ -35,8 +35,17 @@
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
+#include "Mapper.hxx"
+#include "fs/AllocatedPath.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
+
+bool
+playlist_commands_available()
+{
+ return !map_spl_path().IsNull();
+}
static void
print_spl_list(Client &client, const PlaylistVector &list)
@@ -50,28 +59,28 @@ print_spl_list(Client &client, const PlaylistVector &list)
}
CommandResult
-handle_save(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_save(Client &client, ConstBuffer<const char *> args)
{
- PlaylistResult result = spl_save_playlist(argv[1], client.playlist);
+ PlaylistResult result = spl_save_playlist(args.front(), client.playlist);
return print_playlist_result(client, result);
}
CommandResult
-handle_load(Client &client, unsigned argc, char *argv[])
+handle_load(Client &client, ConstBuffer<const char *> args)
{
unsigned start_index, end_index;
- if (argc < 3) {
+ if (args.size < 2) {
start_index = 0;
end_index = unsigned(-1);
- } else if (!check_range(client, &start_index, &end_index, argv[2]))
+ } else if (!check_range(client, &start_index, &end_index, args[1]))
return CommandResult::ERROR;
const ScopeBulkEdit bulk_edit(client.partition);
Error error;
const SongLoader loader(client);
- if (!playlist_open_into_queue(argv[1],
+ if (!playlist_open_into_queue(args.front(),
start_index, end_index,
client.playlist,
client.player_control, loader, error))
@@ -81,94 +90,104 @@ handle_load(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_listplaylist(Client &client, ConstBuffer<const char *> args)
{
- if (playlist_file_print(client, argv[1], false))
+ const char *const name = args.front();
+
+ if (playlist_file_print(client, name, false))
return CommandResult::OK;
Error error;
- return spl_print(client, argv[1], false, error)
+ return spl_print(client, name, false, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_listplaylistinfo(Client &client,
- gcc_unused unsigned argc, char *argv[])
+handle_listplaylistinfo(Client &client, ConstBuffer<const char *> args)
{
- if (playlist_file_print(client, argv[1], true))
+ const char *const name = args.front();
+
+ if (playlist_file_print(client, name, true))
return CommandResult::OK;
Error error;
- return spl_print(client, argv[1], true, error)
+ return spl_print(client, name, true, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_rm(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rm(Client &client, ConstBuffer<const char *> args)
{
+ const char *const name = args.front();
+
Error error;
- return spl_delete(argv[1], error)
+ return spl_delete(name, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_rename(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rename(Client &client, ConstBuffer<const char *> args)
{
+ const char *const old_name = args[0];
+ const char *const new_name = args[1];
+
Error error;
- return spl_rename(argv[1], argv[2], error)
+ return spl_rename(old_name, new_name, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_playlistdelete(Client &client,
- gcc_unused unsigned argc, char *argv[]) {
- char *playlist = argv[1];
+handle_playlistdelete(Client &client, ConstBuffer<const char *> args)
+{
+ const char *const name = args[0];
unsigned from;
- if (!check_unsigned(client, &from, argv[2]))
+ if (!check_unsigned(client, &from, args[1]))
return CommandResult::ERROR;
Error error;
- return spl_remove_index(playlist, from, error)
+ return spl_remove_index(name, from, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_playlistmove(Client &client, ConstBuffer<const char *> args)
{
- char *playlist = argv[1];
+ const char *const name = args.front();
unsigned from, to;
- if (!check_unsigned(client, &from, argv[2]))
+ if (!check_unsigned(client, &from, args[1]))
return CommandResult::ERROR;
- if (!check_unsigned(client, &to, argv[3]))
+ if (!check_unsigned(client, &to, args[2]))
return CommandResult::ERROR;
Error error;
- return spl_move_index(playlist, from, to, error)
+ return spl_move_index(name, from, to, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_playlistclear(Client &client, ConstBuffer<const char *> args)
{
+ const char *const name = args.front();
+
Error error;
- return spl_clear(argv[1], error)
+ return spl_clear(name, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_playlistadd(Client &client, ConstBuffer<const char *> args)
{
- char *playlist = argv[1];
- char *uri = argv[2];
+ const char *const playlist = args[0];
+ const char *const uri = args[1];
bool success;
Error error;
@@ -199,8 +218,7 @@ handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_listplaylists(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_listplaylists(Client &client, gcc_unused ConstBuffer<const char *> args)
{
Error error;
const auto list = ListPlaylistFiles(error);
diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx
index fba4e1318..91721899c 100644
--- a/src/command/PlaylistCommands.hxx
+++ b/src/command/PlaylistCommands.hxx
@@ -21,40 +21,46 @@
#define MPD_PLAYLIST_COMMANDS_HXX
#include "CommandResult.hxx"
+#include "Compiler.h"
class Client;
+template<typename T> struct ConstBuffer;
+
+gcc_const
+bool
+playlist_commands_available();
CommandResult
-handle_save(Client &client, unsigned argc, char *argv[]);
+handle_save(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_load(Client &client, unsigned argc, char *argv[]);
+handle_load(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_listplaylist(Client &client, unsigned argc, char *argv[]);
+handle_listplaylist(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]);
+handle_listplaylistinfo(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_rm(Client &client, unsigned argc, char *argv[]);
+handle_rm(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_rename(Client &client, unsigned argc, char *argv[]);
+handle_rename(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistdelete(Client &client, unsigned argc, char *argv[]);
+handle_playlistdelete(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistmove(Client &client, unsigned argc, char *argv[]);
+handle_playlistmove(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistclear(Client &client, unsigned argc, char *argv[]);
+handle_playlistclear(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistadd(Client &client, unsigned argc, char *argv[]);
+handle_playlistadd(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_listplaylists(Client &client, unsigned argc, char *argv[]);
+handle_listplaylists(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx
index d0b789eb1..e94c7bc8b 100644
--- a/src/command/QueueCommands.cxx
+++ b/src/command/QueueCommands.cxx
@@ -59,9 +59,9 @@ translate_uri(Client &client, const char *uri)
}
CommandResult
-handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_add(Client &client, ConstBuffer<const char *> args)
{
- const char *uri = argv[1];
+ const char *uri = args.front();
if (memcmp(uri, "/", 2) == 0)
/* this URI is malformed, but some clients are buggy
and use "add /" to add the whole database, which
@@ -99,9 +99,9 @@ handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_addid(Client &client, unsigned argc, char *argv[])
+handle_addid(Client &client, ConstBuffer<const char *> args)
{
- const char *const uri = translate_uri(client, argv[1]);
+ const char *const uri = translate_uri(client, args.front());
if (uri == nullptr)
return CommandResult::ERROR;
@@ -111,9 +111,9 @@ handle_addid(Client &client, unsigned argc, char *argv[])
if (added_id == 0)
return print_error(client, error);
- if (argc == 3) {
+ if (args.size == 2) {
unsigned to;
- if (!check_unsigned(client, &to, argv[2]))
+ if (!check_unsigned(client, &to, args[1]))
return CommandResult::ERROR;
PlaylistResult result = client.partition.MoveId(added_id, to);
if (result != PlaylistResult::SUCCESS) {
@@ -160,14 +160,14 @@ parse_time_range(const char *p, SongTime &start_r, SongTime &end_r)
}
CommandResult
-handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rangeid(Client &client, ConstBuffer<const char *> args)
{
unsigned id;
- if (!check_unsigned(client, &id, argv[1]))
+ if (!check_unsigned(client, &id, args.front()))
return CommandResult::ERROR;
SongTime start, end;
- if (!parse_time_range(argv[2], start, end)) {
+ if (!parse_time_range(args[1], start, end)) {
command_error(client, ACK_ERROR_ARG, "Bad range");
return CommandResult::ERROR;
}
@@ -182,11 +182,11 @@ handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_delete(Client &client, ConstBuffer<const char *> args)
{
unsigned start, end;
- if (!check_range(client, &start, &end, argv[1]))
+ if (!check_range(client, &start, &end, args.front()))
return CommandResult::ERROR;
PlaylistResult result = client.partition.DeleteRange(start, end);
@@ -194,11 +194,11 @@ handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_deleteid(Client &client, ConstBuffer<const char *> args)
{
unsigned id;
- if (!check_unsigned(client, &id, argv[1]))
+ if (!check_unsigned(client, &id, args.front()))
return CommandResult::ERROR;
PlaylistResult result = client.partition.DeleteId(id);
@@ -206,19 +206,17 @@ handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_playlist(Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_playlist(Client &client, gcc_unused ConstBuffer<const char *> args)
{
playlist_print_uris(client, client.playlist);
return CommandResult::OK;
}
CommandResult
-handle_shuffle(gcc_unused Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_shuffle(gcc_unused Client &client, ConstBuffer<const char *> args)
{
unsigned start = 0, end = client.playlist.queue.GetLength();
- if (argc == 2 && !check_range(client, &start, &end, argv[1]))
+ if (args.size == 1 && !check_range(client, &start, &end, args.front()))
return CommandResult::ERROR;
client.partition.Shuffle(start, end);
@@ -226,19 +224,18 @@ handle_shuffle(gcc_unused Client &client,
}
CommandResult
-handle_clear(gcc_unused Client &client,
- gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_clear(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
{
client.partition.ClearQueue();
return CommandResult::OK;
}
CommandResult
-handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_plchanges(Client &client, ConstBuffer<const char *> args)
{
uint32_t version;
- if (!check_uint32(client, &version, argv[1]))
+ if (!check_uint32(client, &version, args.front()))
return CommandResult::ERROR;
playlist_print_changes_info(client, client.playlist, version);
@@ -246,11 +243,11 @@ handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_plchangesposid(Client &client, ConstBuffer<const char *> args)
{
uint32_t version;
- if (!check_uint32(client, &version, argv[1]))
+ if (!check_uint32(client, &version, args.front()))
return CommandResult::ERROR;
playlist_print_changes_position(client, client.playlist, version);
@@ -258,12 +255,12 @@ handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_playlistinfo(Client &client, unsigned argc, char *argv[])
+handle_playlistinfo(Client &client, ConstBuffer<const char *> args)
{
unsigned start = 0, end = std::numeric_limits<unsigned>::max();
bool ret;
- if (argc == 2 && !check_range(client, &start, &end, argv[1]))
+ if (args.size == 1 && !check_range(client, &start, &end, args.front()))
return CommandResult::ERROR;
ret = playlist_print_info(client, client.playlist, start, end);
@@ -275,11 +272,11 @@ handle_playlistinfo(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_playlistid(Client &client, unsigned argc, char *argv[])
+handle_playlistid(Client &client, ConstBuffer<const char *> args)
{
- if (argc >= 2) {
+ if (!args.IsEmpty()) {
unsigned id;
- if (!check_unsigned(client, &id, argv[1]))
+ if (!check_unsigned(client, &id, args.front()))
return CommandResult::ERROR;
bool ret = playlist_print_id(client, client.playlist, id);
@@ -295,11 +292,9 @@ handle_playlistid(Client &client, unsigned argc, char *argv[])
}
static CommandResult
-handle_playlist_match(Client &client, unsigned argc, char *argv[],
+handle_playlist_match(Client &client, ConstBuffer<const char *> args,
bool fold_case)
{
- ConstBuffer<const char *> args(argv + 1, argc - 1);
-
SongFilter filter;
if (!filter.Parse(args, fold_case)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
@@ -311,35 +306,35 @@ handle_playlist_match(Client &client, unsigned argc, char *argv[],
}
CommandResult
-handle_playlistfind(Client &client, unsigned argc, char *argv[])
+handle_playlistfind(Client &client, ConstBuffer<const char *> args)
{
- return handle_playlist_match(client, argc, argv, false);
+ return handle_playlist_match(client, args, false);
}
CommandResult
-handle_playlistsearch(Client &client, unsigned argc, char *argv[])
+handle_playlistsearch(Client &client, ConstBuffer<const char *> args)
{
- return handle_playlist_match(client, argc, argv, true);
+ return handle_playlist_match(client, args, true);
}
CommandResult
-handle_prio(Client &client, unsigned argc, char *argv[])
+handle_prio(Client &client, ConstBuffer<const char *> args)
{
+ const char *const priority_string = args.shift();
unsigned priority;
- if (!check_unsigned(client, &priority, argv[1]))
+ if (!check_unsigned(client, &priority, priority_string))
return CommandResult::ERROR;
if (priority > 0xff) {
command_error(client, ACK_ERROR_ARG,
- "Priority out of range: %s", argv[1]);
+ "Priority out of range: %s", priority_string);
return CommandResult::ERROR;
}
- for (unsigned i = 2; i < argc; ++i) {
+ for (const char *i : args) {
unsigned start_position, end_position;
- if (!check_range(client, &start_position, &end_position,
- argv[i]))
+ if (!check_range(client, &start_position, &end_position, i))
return CommandResult::ERROR;
PlaylistResult result =
@@ -354,22 +349,23 @@ handle_prio(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_prioid(Client &client, unsigned argc, char *argv[])
+handle_prioid(Client &client, ConstBuffer<const char *> args)
{
+ const char *const priority_string = args.shift();
unsigned priority;
- if (!check_unsigned(client, &priority, argv[1]))
+ if (!check_unsigned(client, &priority, priority_string))
return CommandResult::ERROR;
if (priority > 0xff) {
command_error(client, ACK_ERROR_ARG,
- "Priority out of range: %s", argv[1]);
+ "Priority out of range: %s", priority_string);
return CommandResult::ERROR;
}
- for (unsigned i = 2; i < argc; ++i) {
+ for (const char *i : args) {
unsigned song_id;
- if (!check_unsigned(client, &song_id, argv[i]))
+ if (!check_unsigned(client, &song_id, i))
return CommandResult::ERROR;
PlaylistResult result =
@@ -382,14 +378,14 @@ handle_prioid(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_move(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_move(Client &client, ConstBuffer<const char *> args)
{
unsigned start, end;
int to;
- if (!check_range(client, &start, &end, argv[1]))
+ if (!check_range(client, &start, &end, args[0]))
return CommandResult::ERROR;
- if (!check_int(client, &to, argv[2]))
+ if (!check_int(client, &to, args[1]))
return CommandResult::ERROR;
PlaylistResult result =
@@ -398,27 +394,27 @@ handle_move(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_moveid(Client &client, ConstBuffer<const char *> args)
{
unsigned id;
int to;
- if (!check_unsigned(client, &id, argv[1]))
+ if (!check_unsigned(client, &id, args[0]))
return CommandResult::ERROR;
- if (!check_int(client, &to, argv[2]))
+ if (!check_int(client, &to, args[1]))
return CommandResult::ERROR;
PlaylistResult result = client.partition.MoveId(id, to);
return print_playlist_result(client, result);
}
CommandResult
-handle_swap(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_swap(Client &client, ConstBuffer<const char *> args)
{
unsigned song1, song2;
- if (!check_unsigned(client, &song1, argv[1]))
+ if (!check_unsigned(client, &song1, args[0]))
return CommandResult::ERROR;
- if (!check_unsigned(client, &song2, argv[2]))
+ if (!check_unsigned(client, &song2, args[1]))
return CommandResult::ERROR;
PlaylistResult result =
@@ -427,13 +423,13 @@ handle_swap(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_swapid(Client &client, ConstBuffer<const char *> args)
{
unsigned id1, id2;
- if (!check_unsigned(client, &id1, argv[1]))
+ if (!check_unsigned(client, &id1, args[0]))
return CommandResult::ERROR;
- if (!check_unsigned(client, &id2, argv[2]))
+ if (!check_unsigned(client, &id2, args[1]))
return CommandResult::ERROR;
PlaylistResult result = client.partition.SwapIds(id1, id2);
diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx
index f98f7bad2..5193e8b65 100644
--- a/src/command/QueueCommands.hxx
+++ b/src/command/QueueCommands.hxx
@@ -23,65 +23,66 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_add(Client &client, unsigned argc, char *argv[]);
+handle_add(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_addid(Client &client, unsigned argc, char *argv[]);
+handle_addid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_rangeid(Client &client, unsigned argc, char *argv[]);
+handle_rangeid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_delete(Client &client, unsigned argc, char *argv[]);
+handle_delete(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_deleteid(Client &client, unsigned argc, char *argv[]);
+handle_deleteid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlist(Client &client, unsigned argc, char *argv[]);
+handle_playlist(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_shuffle(Client &client, unsigned argc, char *argv[]);
+handle_shuffle(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_clear(Client &client, unsigned argc, char *argv[]);
+handle_clear(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_plchanges(Client &client, unsigned argc, char *argv[]);
+handle_plchanges(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_plchangesposid(Client &client, unsigned argc, char *argv[]);
+handle_plchangesposid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistinfo(Client &client, unsigned argc, char *argv[]);
+handle_playlistinfo(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistid(Client &client, unsigned argc, char *argv[]);
+handle_playlistid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistfind(Client &client, unsigned argc, char *argv[]);
+handle_playlistfind(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_playlistsearch(Client &client, unsigned argc, char *argv[]);
+handle_playlistsearch(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_prio(Client &client, unsigned argc, char *argv[]);
+handle_prio(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_prioid(Client &client, unsigned argc, char *argv[]);
+handle_prioid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_move(Client &client, unsigned argc, char *argv[]);
+handle_move(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_moveid(Client &client, unsigned argc, char *argv[]);
+handle_moveid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_swap(Client &client, unsigned argc, char *argv[]);
+handle_swap(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_swapid(Client &client, unsigned argc, char *argv[]);
+handle_swapid(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx
index 37506d51b..fce53e162 100644
--- a/src/command/StickerCommands.cxx
+++ b/src/command/StickerCommands.cxx
@@ -31,6 +31,7 @@
#include "Partition.hxx"
#include "Instance.hxx"
#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
#include <string.h>
@@ -51,53 +52,64 @@ sticker_song_find_print_cb(const LightSong &song, const char *value,
}
static CommandResult
-handle_sticker_song(Client &client, unsigned argc, char *argv[])
+handle_sticker_song(Client &client, ConstBuffer<const char *> args)
{
Error error;
const Database *db = client.GetDatabase(error);
if (db == nullptr)
return print_error(client, error);
+ const char *const cmd = args.front();
+
/* get song song_id key */
- if (argc == 5 && strcmp(argv[1], "get") == 0) {
- const LightSong *song = db->GetSong(argv[3], error);
+ if (args.size == 4 && strcmp(cmd, "get") == 0) {
+ const LightSong *song = db->GetSong(args[2], error);
if (song == nullptr)
return print_error(client, error);
- const auto value = sticker_song_get_value(*song, argv[4]);
+ const auto value = sticker_song_get_value(*song, args[3],
+ error);
db->ReturnSong(song);
if (value.empty()) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
command_error(client, ACK_ERROR_NO_EXIST,
"no such sticker");
return CommandResult::ERROR;
}
- sticker_print_value(client, argv[4], value.c_str());
+ sticker_print_value(client, args[3], value.c_str());
return CommandResult::OK;
/* list song song_id */
- } else if (argc == 4 && strcmp(argv[1], "list") == 0) {
- const LightSong *song = db->GetSong(argv[3], error);
+ } else if (args.size == 3 && strcmp(cmd, "list") == 0) {
+ const LightSong *song = db->GetSong(args[2], error);
if (song == nullptr)
return print_error(client, error);
- sticker *sticker = sticker_song_get(*song);
+ sticker *sticker = sticker_song_get(*song, error);
db->ReturnSong(song);
if (sticker) {
sticker_print(client, *sticker);
sticker_free(sticker);
- }
+ } else if (error.IsDefined())
+ return print_error(client, error);
return CommandResult::OK;
/* set song song_id id key */
- } else if (argc == 6 && strcmp(argv[1], "set") == 0) {
- const LightSong *song = db->GetSong(argv[3], error);
+ } else if (args.size == 5 && strcmp(cmd, "set") == 0) {
+ const LightSong *song = db->GetSong(args[2], error);
if (song == nullptr)
return print_error(client, error);
- bool ret = sticker_song_set_value(*song, argv[4], argv[5]);
+ bool ret = sticker_song_set_value(*song, args[3], args[4],
+ error);
db->ReturnSong(song);
if (!ret) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
command_error(client, ACK_ERROR_SYSTEM,
"failed to set sticker value");
return CommandResult::ERROR;
@@ -105,17 +117,20 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
return CommandResult::OK;
/* delete song song_id [key] */
- } else if ((argc == 4 || argc == 5) &&
- strcmp(argv[1], "delete") == 0) {
- const LightSong *song = db->GetSong(argv[3], error);
+ } else if ((args.size == 3 || args.size == 4) &&
+ strcmp(cmd, "delete") == 0) {
+ const LightSong *song = db->GetSong(args[2], error);
if (song == nullptr)
return print_error(client, error);
- bool ret = argc == 4
- ? sticker_song_delete(*song)
- : sticker_song_delete_value(*song, argv[4]);
+ bool ret = args.size == 3
+ ? sticker_song_delete(*song, error)
+ : sticker_song_delete_value(*song, args[3], error);
db->ReturnSong(song);
if (!ret) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
command_error(client, ACK_ERROR_SYSTEM,
"no such sticker");
return CommandResult::ERROR;
@@ -123,20 +138,48 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
return CommandResult::OK;
/* find song dir key */
- } else if (argc == 5 && strcmp(argv[1], "find") == 0) {
+ } else if ((args.size == 4 || args.size == 6) &&
+ strcmp(cmd, "find") == 0) {
/* "sticker find song a/directory name" */
- const char *const base_uri = argv[3];
+ const char *const base_uri = args[2];
+
+ StickerOperator op = StickerOperator::EXISTS;
+ const char *value = nullptr;
+
+ if (args.size == 6) {
+ /* match the value */
+
+ const char *op_s = args[4];
+ value = args[5];
+
+ if (strcmp(op_s, "=") == 0)
+ op = StickerOperator::EQUALS;
+ else if (strcmp(op_s, "<") == 0)
+ op = StickerOperator::LESS_THAN;
+ else if (strcmp(op_s, ">") == 0)
+ op = StickerOperator::GREATER_THAN;
+ else {
+ command_error(client, ACK_ERROR_ARG,
+ "bad operator");
+ return CommandResult::ERROR;
+ }
+ }
bool success;
struct sticker_song_find_data data = {
client,
- argv[4],
+ args[3],
};
success = sticker_song_find(*db, base_uri, data.name,
- sticker_song_find_print_cb, &data);
+ op, value,
+ sticker_song_find_print_cb, &data,
+ error);
if (!success) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
command_error(client, ACK_ERROR_SYSTEM,
"failed to set search sticker database");
return CommandResult::ERROR;
@@ -150,9 +193,9 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
}
CommandResult
-handle_sticker(Client &client, unsigned argc, char *argv[])
+handle_sticker(Client &client, ConstBuffer<const char *> args)
{
- assert(argc >= 4);
+ assert(args.size >= 3);
if (!sticker_enabled()) {
command_error(client, ACK_ERROR_UNKNOWN,
@@ -160,8 +203,8 @@ handle_sticker(Client &client, unsigned argc, char *argv[])
return CommandResult::ERROR;
}
- if (strcmp(argv[2], "song") == 0)
- return handle_sticker_song(client, argc, argv);
+ if (strcmp(args[1], "song") == 0)
+ return handle_sticker_song(client, args);
else {
command_error(client, ACK_ERROR_ARG,
"unknown sticker domain");
diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx
index cf46cd034..0e8d765fa 100644
--- a/src/command/StickerCommands.hxx
+++ b/src/command/StickerCommands.hxx
@@ -23,8 +23,9 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_sticker(Client &client, unsigned argc, char *argv[]);
+handle_sticker(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx
index ee51c573e..510e9f1b1 100644
--- a/src/command/StorageCommands.cxx
+++ b/src/command/StorageCommands.cxx
@@ -25,6 +25,7 @@
#include "protocol/Result.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
#include "fs/Traits.hxx"
#include "client/Client.hxx"
#include "Partition.hxx"
@@ -167,7 +168,7 @@ print_storage_uri(Client &client, const Storage &storage)
}
CommandResult
-handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_listmounts(Client &client, gcc_unused ConstBuffer<const char *> args)
{
Storage *_composite = client.partition.instance.storage;
if (_composite == nullptr) {
@@ -189,7 +190,7 @@ handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *arg
}
CommandResult
-handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_mount(Client &client, ConstBuffer<const char *> args)
{
Storage *_composite = client.partition.instance.storage;
if (_composite == nullptr) {
@@ -199,8 +200,8 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
CompositeStorage &composite = *(CompositeStorage *)_composite;
- const char *const local_uri = argv[1];
- const char *const remote_uri = argv[2];
+ const char *const local_uri = args[0];
+ const char *const remote_uri = args[1];
if (*local_uri == 0) {
command_error(client, ACK_ERROR_ARG, "Bad mount point");
@@ -252,7 +253,7 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_unmount(Client &client, ConstBuffer<const char *> args)
{
Storage *_composite = client.partition.instance.storage;
if (_composite == nullptr) {
@@ -262,7 +263,7 @@ handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[])
CompositeStorage &composite = *(CompositeStorage *)_composite;
- const char *const local_uri = argv[1];
+ const char *const local_uri = args.front();
if (*local_uri == 0) {
command_error(client, ACK_ERROR_ARG, "Bad mount point");
diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx
index a3636d54a..ce3622c4b 100644
--- a/src/command/StorageCommands.hxx
+++ b/src/command/StorageCommands.hxx
@@ -24,6 +24,7 @@
class Client;
class Storage;
+template<typename T> struct ConstBuffer;
CommandResult
handle_listfiles_storage(Client &client, Storage &storage, const char *uri);
@@ -32,12 +33,12 @@ CommandResult
handle_listfiles_storage(Client &client, const char *uri);
CommandResult
-handle_listmounts(Client &client, unsigned argc, char *argv[]);
+handle_listmounts(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_mount(Client &client, unsigned argc, char *argv[]);
+handle_mount(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_unmount(Client &client, unsigned argc, char *argv[]);
+handle_unmount(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx
index 2d537671c..6f821f451 100644
--- a/src/command/TagCommands.cxx
+++ b/src/command/TagCommands.cxx
@@ -25,15 +25,16 @@
#include "protocol/Result.hxx"
#include "tag/Tag.hxx"
#include "Partition.hxx"
+#include "util/ConstBuffer.hxx"
CommandResult
-handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_addtagid(Client &client, ConstBuffer<const char *> args)
{
unsigned song_id;
- if (!check_unsigned(client, &song_id, argv[1]))
+ if (!check_unsigned(client, &song_id, args.front()))
return CommandResult::ERROR;
- const char *const tag_name = argv[2];
+ const char *const tag_name = args[1];
const TagType tag_type = tag_name_parse_i(tag_name);
if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
command_error(client, ACK_ERROR_ARG,
@@ -41,7 +42,7 @@ handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
return CommandResult::ERROR;
}
- const char *const value = argv[3];
+ const char *const value = args[2];
Error error;
if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value,
@@ -52,15 +53,15 @@ handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
}
CommandResult
-handle_cleartagid(Client &client, unsigned argc, char *argv[])
+handle_cleartagid(Client &client, ConstBuffer<const char *> args)
{
unsigned song_id;
- if (!check_unsigned(client, &song_id, argv[1]))
+ if (!check_unsigned(client, &song_id, args.front()))
return CommandResult::ERROR;
TagType tag_type = TAG_NUM_OF_ITEM_TYPES;
- if (argc >= 3) {
- const char *const tag_name = argv[2];
+ if (args.size >= 2) {
+ const char *const tag_name = args[1];
tag_type = tag_name_parse_i(tag_name);
if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
command_error(client, ACK_ERROR_ARG,
diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx
index 748838e68..bc813b151 100644
--- a/src/command/TagCommands.hxx
+++ b/src/command/TagCommands.hxx
@@ -23,11 +23,12 @@
#include "CommandResult.hxx"
class Client;
+template<typename T> struct ConstBuffer;
CommandResult
-handle_addtagid(Client &client, unsigned argc, char *argv[]);
+handle_addtagid(Client &client, ConstBuffer<const char *> args);
CommandResult
-handle_cleartagid(Client &client, unsigned argc, char *argv[]);
+handle_cleartagid(Client &client, ConstBuffer<const char *> args);
#endif
diff --git a/src/config/ConfigData.hxx b/src/config/ConfigData.hxx
index e42d674ba..1a16fae7f 100644
--- a/src/config/ConfigData.hxx
+++ b/src/config/ConfigData.hxx
@@ -109,7 +109,7 @@ struct config_param {
const char *default_value=nullptr) const;
/**
- * Same as config_dup_path(), but looks up the setting in the
+ * Same as config_get_path(), but looks up the setting in the
* specified block.
*/
AllocatedPath GetBlockPath(const char *name, const char *default_value,
diff --git a/src/config/ConfigGlobal.cxx b/src/config/ConfigGlobal.cxx
index 9bc83398c..06b41de80 100644
--- a/src/config/ConfigGlobal.cxx
+++ b/src/config/ConfigGlobal.cxx
@@ -38,6 +38,7 @@ void config_global_finish(void)
{
for (auto i : config_data.params)
delete i;
+ config_data.params.fill(0);
}
void config_global_init(void)
diff --git a/src/config/ConfigParser.cxx b/src/config/ConfigParser.cxx
index 3535c9a13..da7d58e49 100644
--- a/src/config/ConfigParser.cxx
+++ b/src/config/ConfigParser.cxx
@@ -23,8 +23,8 @@
bool
get_bool(const char *value, bool *value_r)
{
- static const char *t[] = { "yes", "true", "1", nullptr };
- static const char *f[] = { "no", "false", "0", nullptr };
+ static const char *const t[] = { "yes", "true", "1", nullptr };
+ static const char *const f[] = { "no", "false", "0", nullptr };
if (string_array_contains(t, value)) {
*value_r = true;
diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx
index 58ee56425..801ee8b20 100644
--- a/src/config/ConfigTemplates.cxx
+++ b/src/config/ConfigTemplates.cxx
@@ -19,6 +19,7 @@
#include "ConfigTemplates.hxx"
#include "ConfigOption.hxx"
+#include "util/Macros.hxx"
#include <string.h>
@@ -81,8 +82,7 @@ const ConfigTemplate config_templates[] = {
{ "neighbors", true, true },
};
-static constexpr unsigned n_config_templates =
- sizeof(config_templates) / sizeof(config_templates[0]);
+static constexpr unsigned n_config_templates = ARRAY_SIZE(config_templates);
static_assert(n_config_templates == unsigned(CONF_MAX),
"Wrong number of config_templates");
diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx
index 498aedf97..c38f1c277 100644
--- a/src/db/DatabasePrint.cxx
+++ b/src/db/DatabasePrint.cxx
@@ -147,27 +147,50 @@ PrintPlaylistFull(Client &client, bool base,
bool
db_selection_print(Client &client, const DatabaseSelection &selection,
- bool full, bool base, Error &error)
+ bool full, bool base,
+ unsigned window_start, unsigned window_end,
+ Error &error)
{
const Database *db = client.GetDatabase(error);
if (db == nullptr)
return false;
+ unsigned i = 0;
+
using namespace std::placeholders;
const auto d = selection.filter == nullptr
? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief,
std::ref(client), base, _1)
: VisitDirectory();
- const auto s = std::bind(full ? PrintSongFull : PrintSongBrief,
- std::ref(client), base, _1);
+ VisitSong s = std::bind(full ? PrintSongFull : PrintSongBrief,
+ std::ref(client), base, _1);
const auto p = selection.filter == nullptr
? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief,
std::ref(client), base, _1, _2)
: VisitPlaylist();
+ if (window_start > 0 ||
+ window_end < (unsigned)std::numeric_limits<int>::max())
+ s = [s, window_start, window_end, &i](const LightSong &song,
+ Error &error2){
+ const bool in_window = i >= window_start && i < window_end;
+ ++i;
+ return !in_window || s(song, error2);
+ };
+
return db->Visit(selection, d, s, p, error);
}
+bool
+db_selection_print(Client &client, const DatabaseSelection &selection,
+ bool full, bool base,
+ Error &error)
+{
+ return db_selection_print(client, selection, full, base,
+ 0, std::numeric_limits<int>::max(),
+ error);
+}
+
static bool
PrintSongURIVisitor(Client &client, const LightSong &song)
{
diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx
index 2ab5e703d..7e4dd8572 100644
--- a/src/db/DatabasePrint.hxx
+++ b/src/db/DatabasePrint.hxx
@@ -38,6 +38,12 @@ db_selection_print(Client &client, const DatabaseSelection &selection,
bool full, bool base, Error &error);
bool
+db_selection_print(Client &client, const DatabaseSelection &selection,
+ bool full, bool base,
+ unsigned window_start, unsigned window_end,
+ Error &error);
+
+bool
PrintUniqueTags(Client &client, unsigned type, uint32_t group_mask,
const SongFilter *filter,
Error &error);
diff --git a/src/db/Helpers.cxx b/src/db/Helpers.cxx
index add4bb98e..27bfb7c01 100644
--- a/src/db/Helpers.cxx
+++ b/src/db/Helpers.cxx
@@ -46,7 +46,7 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
for (const auto &item : tag) {
switch (item.type) {
case TAG_ARTIST:
-#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+#if CLANG_OR_GCC_VERSION(4,8)
artists.emplace(item.value);
#else
artists.insert(item.value);
@@ -54,7 +54,7 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
break;
case TAG_ALBUM:
-#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+#if CLANG_OR_GCC_VERSION(4,8)
albums.emplace(item.value);
#else
albums.insert(item.value);
diff --git a/src/db/Registry.cxx b/src/db/Registry.cxx
index 5681a9b82..a7d6dc05e 100644
--- a/src/db/Registry.cxx
+++ b/src/db/Registry.cxx
@@ -28,10 +28,10 @@
const DatabasePlugin *const database_plugins[] = {
&simple_db_plugin,
-#ifdef HAVE_LIBMPDCLIENT
+#ifdef ENABLE_LIBMPDCLIENT
&proxy_db_plugin,
#endif
-#ifdef HAVE_LIBUPNP
+#ifdef ENABLE_UPNP
&upnp_db_plugin,
#endif
nullptr
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
index d6ad5e91f..00eb078bc 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -41,7 +41,7 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
#include "fs/io/GzipOutputStream.hxx"
#endif
@@ -52,21 +52,21 @@ static constexpr Domain simple_db_domain("simple_db");
inline SimpleDatabase::SimpleDatabase()
:Database(simple_db_plugin),
path(AllocatedPath::Null()),
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
compress(true),
#endif
cache_path(AllocatedPath::Null()),
prefixed_light_song(nullptr) {}
inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path,
-#ifndef HAVE_ZLIB
+#ifndef ENABLE_ZLIB
gcc_unused
#endif
bool _compress)
:Database(simple_db_plugin),
path(std::move(_path)),
path_utf8(path.ToUTF8()),
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
compress(_compress),
#endif
cache_path(AllocatedPath::Null()),
@@ -104,7 +104,7 @@ SimpleDatabase::Configure(const config_param &param, Error &error)
if (path.IsNull() && error.IsDefined())
return false;
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
compress = param.GetBlockValue("compress", compress);
#endif
@@ -389,7 +389,7 @@ SimpleDatabase::Save(Error &error)
OutputStream *os = &fos;
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
GzipOutputStream *gzip = nullptr;
if (compress) {
gzip = new GzipOutputStream(*os, error);
@@ -407,13 +407,13 @@ SimpleDatabase::Save(Error &error)
db_save_internal(bos, *root);
if (!bos.Flush(error)) {
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
delete gzip;
#endif
return false;
}
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
if (gzip != nullptr) {
bool success = gzip->Flush(error);
delete gzip;
@@ -487,7 +487,7 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri,
std::string name(storage_uri);
std::replace_if(name.begin(), name.end(), IsUnsafeChar, '_');
-#ifndef HAVE_ZLIB
+#ifndef ENABLE_ZLIB
constexpr bool compress = false;
#endif
auto db = new SimpleDatabase(AllocatedPath::Build(cache_path,
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx
index d82225f8c..dec2a3a7c 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.hxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx
@@ -39,7 +39,7 @@ class SimpleDatabase : public Database {
AllocatedPath path;
std::string path_utf8;
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
bool compress;
#endif
diff --git a/src/db/plugins/upnp/Directory.cxx b/src/db/plugins/upnp/Directory.cxx
index e94a1a997..55f2693ae 100644
--- a/src/db/plugins/upnp/Directory.cxx
+++ b/src/db/plugins/upnp/Directory.cxx
@@ -89,18 +89,18 @@ ParseDuration(const char *duration)
* this. Twonky returns directory names (titles) like 'Artist/Album'.
*/
gcc_pure
-static std::string
-titleToPathElt(std::string &&s)
+static std::string &&
+TitleToPathSegment(std::string &&s)
{
std::replace(s.begin(), s.end(), '/', '_');
- return s;
+ return std::move(s);
}
/**
* An XML parser which builds directory contents from DIDL lite input.
*/
class UPnPDirParser final : public CommonExpatParser {
- UPnPDirContent &m_dir;
+ UPnPDirContent &directory;
enum {
NONE,
@@ -120,22 +120,22 @@ class UPnPDirParser final : public CommonExpatParser {
*/
std::string value;
- UPnPDirObject m_tobj;
+ UPnPDirObject object;
TagBuilder tag;
public:
- UPnPDirParser(UPnPDirContent& dir)
- :m_dir(dir),
+ UPnPDirParser(UPnPDirContent &_directory)
+ :directory(_directory),
state(NONE),
tag_type(TAG_NUM_OF_ITEM_TYPES)
{
- m_tobj.clear();
+ object.Clear();
}
protected:
virtual void StartElement(const XML_Char *name, const XML_Char **attrs)
{
- if (m_tobj.type != UPnPDirObject::Type::UNKNOWN &&
+ if (object.type != UPnPDirObject::Type::UNKNOWN &&
tag_type == TAG_NUM_OF_ITEM_TYPES) {
tag_type = tag_table_lookup(upnp_tags, name);
if (tag_type != TAG_NUM_OF_ITEM_TYPES)
@@ -147,31 +147,31 @@ protected:
switch (name[0]) {
case 'c':
if (!strcmp(name, "container")) {
- m_tobj.clear();
- m_tobj.type = UPnPDirObject::Type::CONTAINER;
+ object.Clear();
+ object.type = UPnPDirObject::Type::CONTAINER;
const char *id = GetAttribute(attrs, "id");
if (id != nullptr)
- m_tobj.m_id = id;
+ object.id = id;
const char *pid = GetAttribute(attrs, "parentID");
if (pid != nullptr)
- m_tobj.m_pid = pid;
+ object.parent_id = pid;
}
break;
case 'i':
if (!strcmp(name, "item")) {
- m_tobj.clear();
- m_tobj.type = UPnPDirObject::Type::ITEM;
+ object.Clear();
+ object.type = UPnPDirObject::Type::ITEM;
const char *id = GetAttribute(attrs, "id");
if (id != nullptr)
- m_tobj.m_id = id;
+ object.id = id;
const char *pid = GetAttribute(attrs, "parentID");
if (pid != nullptr)
- m_tobj.m_pid = pid;
+ object.parent_id = pid;
}
break;
@@ -197,25 +197,15 @@ protected:
}
}
- bool checkobjok() {
- if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() ||
- m_tobj.name.empty() ||
- (m_tobj.type == UPnPDirObject::Type::ITEM &&
- m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN))
- return false;
-
- return true;
- }
-
virtual void EndElement(const XML_Char *name)
{
if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
- assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN);
+ assert(object.type != UPnPDirObject::Type::UNKNOWN);
tag.AddItem(tag_type, value.c_str());
if (tag_type == TAG_TITLE)
- m_tobj.name = titleToPathElt(std::move(value));
+ object.name = TitleToPathSegment(std::move(value));
value.clear();
tag_type = TAG_NUM_OF_ITEM_TYPES;
@@ -223,9 +213,9 @@ protected:
}
if ((!strcmp(name, "container") || !strcmp(name, "item")) &&
- checkobjok()) {
- tag.Commit(m_tobj.tag);
- m_dir.objects.emplace_back(std::move(m_tobj));
+ object.Check()) {
+ tag.Commit(object.tag);
+ directory.objects.emplace_back(std::move(object));
}
state = NONE;
@@ -234,7 +224,7 @@ protected:
virtual void CharacterData(const XML_Char *s, int len)
{
if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
- assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN);
+ assert(object.type != UPnPDirObject::Type::UNKNOWN);
value.append(s, len);
return;
@@ -245,11 +235,11 @@ protected:
break;
case RES:
- m_tobj.url.assign(s, len);
+ object.url.assign(s, len);
break;
case CLASS:
- m_tobj.item_class = ParseItemClass(s, len);
+ object.item_class = ParseItemClass(s, len);
break;
}
}
diff --git a/src/db/plugins/upnp/Object.hxx b/src/db/plugins/upnp/Object.hxx
index 16a66c774..6d71c158b 100644
--- a/src/db/plugins/upnp/Object.hxx
+++ b/src/db/plugins/upnp/Object.hxx
@@ -21,6 +21,7 @@
#define MPD_UPNP_OBJECT_HXX
#include "tag/Tag.hxx"
+#include "Compiler.h"
#include <string>
@@ -50,8 +51,16 @@ public:
PLAYLIST,
};
- std::string m_id; // ObjectId
- std::string m_pid; // Parent ObjectId
+ /**
+ * ObjectId
+ */
+ std::string id;
+
+ /**
+ * Parent's ObjectId
+ */
+ std::string parent_id;
+
std::string url;
/**
@@ -71,15 +80,21 @@ public:
UPnPDirObject &operator=(UPnPDirObject &&) = default;
- void clear()
- {
- m_id.clear();
- m_pid.clear();
+ void Clear() {
+ id.clear();
+ parent_id.clear();
url.clear();
type = Type::UNKNOWN;
item_class = ItemClass::UNKNOWN;
tag.Clear();
}
+
+ gcc_pure
+ bool Check() const {
+ return !id.empty() && !parent_id.empty() && !name.empty() &&
+ (type != UPnPDirObject::Type::ITEM ||
+ item_class != UPnPDirObject::ItemClass::UNKNOWN);
+ }
};
#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */
diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
index 9970cdcf3..aea97aa8e 100644
--- a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
+++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
@@ -414,7 +414,7 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
// So we return synthetic and ugly paths based on the object id,
// which we later have to detect.
const std::string path = songPath(server.getFriendlyName(),
- dirent.m_id);
+ dirent.id);
if (!visitSong(std::move(dirent), path.c_str(),
selection, visit_song,
error))
@@ -449,13 +449,13 @@ UpnpDatabase::BuildPath(const ContentDirectoryService &server,
std::string &path,
Error &error) const
{
- const char *pid = idirent.m_id.c_str();
+ const char *pid = idirent.id.c_str();
path.clear();
UPnPDirObject dirent;
while (strcmp(pid, rootid) != 0) {
if (!ReadNode(server, pid, dirent, error))
return false;
- pid = dirent.m_pid.c_str();
+ pid = dirent.parent_id.c_str();
if (path.empty())
path = dirent.name;
@@ -511,7 +511,7 @@ UpnpDatabase::Namei(const ContentDirectoryService &server,
return false;
}
- objid = std::move(child->m_id);
+ objid = std::move(child->id);
}
}
@@ -623,7 +623,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server,
}
std::string path = songPath(server.getFriendlyName(),
- dirent.m_id);
+ dirent.id);
if (!visitSong(std::move(dirent), path.c_str(),
selection,
visit_song, error))
@@ -642,7 +642,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server,
recursion (1-deep) here, which will handle the "add dir"
case. */
if (selection.recursive && selection.filter)
- return SearchSongs(server, tdirent.m_id.c_str(), selection,
+ return SearchSongs(server, tdirent.id.c_str(), selection,
visit_song, error);
const char *const base_uri = selection.uri.empty()
@@ -660,7 +660,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server,
and loop here, but it's not useful as mpd will only return
data to the client when we're done anyway. */
UPnPDirContent dirbuf;
- if (!server.readDir(handle, tdirent.m_id.c_str(), dirbuf,
+ if (!server.readDir(handle, tdirent.id.c_str(), dirbuf,
error))
return false;
@@ -749,7 +749,7 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
const char *value = dirent.tag.GetValue(tag);
if (value != nullptr) {
-#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+#if CLANG_OR_GCC_VERSION(4,8)
values.emplace(value);
#else
values.insert(value);
diff --git a/src/db/update/InotifySource.hxx b/src/db/update/InotifySource.hxx
index 2557680a0..22b05bf4e 100644
--- a/src/db/update/InotifySource.hxx
+++ b/src/db/update/InotifySource.hxx
@@ -41,8 +41,8 @@ public:
}
/**
- * Creates a new inotify source and registers it in the GLib main
- * loop.
+ * Creates a new inotify source and registers it in the
+ * #EventLoop.
*
* @param a callback invoked for events received from the kernel
*/
diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx
index cd6881ce2..cd1665e58 100644
--- a/src/decoder/DecoderList.cxx
+++ b/src/decoder/DecoderList.cxx
@@ -48,44 +48,42 @@
#include <string.h>
const struct DecoderPlugin *const decoder_plugins[] = {
-#ifdef HAVE_MAD
+#ifdef ENABLE_MAD
&mad_decoder_plugin,
#endif
-#ifdef HAVE_MPG123
+#ifdef ENABLE_MPG123
&mpg123_decoder_plugin,
#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
-#if defined(HAVE_FLAC)
+#ifdef ENABLE_FLAC
&oggflac_decoder_plugin,
-#endif
-#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
-#ifdef HAVE_OPUS
+#ifdef ENABLE_OPUS
&opus_decoder_plugin,
#endif
#ifdef ENABLE_SNDFILE
&sndfile_decoder_plugin,
#endif
-#ifdef HAVE_AUDIOFILE
+#ifdef ENABLE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
#ifdef ENABLE_DSD
&dsdiff_decoder_plugin,
&dsf_decoder_plugin,
#endif
-#ifdef HAVE_FAAD
+#ifdef ENABLE_FAAD
&faad_decoder_plugin,
#endif
-#ifdef HAVE_MPCDEC
+#ifdef ENABLE_MPCDEC
&mpcdec_decoder_plugin,
#endif
-#ifdef HAVE_WAVPACK
+#ifdef ENABLE_WAVPACK
&wavpack_decoder_plugin,
#endif
-#ifdef HAVE_MODPLUG
+#ifdef ENABLE_MODPLUG
&modplug_decoder_plugin,
#endif
#ifdef ENABLE_MIKMOD_DECODER
@@ -100,13 +98,13 @@ const struct DecoderPlugin *const decoder_plugins[] = {
#ifdef ENABLE_FLUIDSYNTH
&fluidsynth_decoder_plugin,
#endif
-#ifdef HAVE_ADPLUG
+#ifdef ENABLE_ADPLUG
&adplug_decoder_plugin,
#endif
-#ifdef HAVE_FFMPEG
+#ifdef ENABLE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
-#ifdef HAVE_GME
+#ifdef ENABLE_GME
&gme_decoder_plugin,
#endif
&pcm_decoder_plugin,
diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx
index 7321261f6..9da08f3eb 100644
--- a/src/decoder/plugins/DsdLib.cxx
+++ b/src/decoder/plugins/DsdLib.cxx
@@ -34,7 +34,7 @@
#include <string.h>
#include <stdlib.h>
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
#include <id3tag.h>
#endif
@@ -103,7 +103,7 @@ dsdlib_valid_freq(uint32_t samplefreq)
}
}
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
void
dsdlib_tag_id3(InputStream &is,
const struct tag_handler *handler,
diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
index b6c79e11e..33f433330 100644
--- a/src/decoder/plugins/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
@@ -244,7 +244,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
/** offset for title tag */
offset_type title_offset = 0;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
offset_type id3_offset = 0;
#endif
@@ -269,7 +269,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
chunk_size = chunk_header->GetSize();
title_offset = is.GetOffset();
}
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
/* 'ID3 ' chunk, offspec. Used by sacdextract */
if (chunk_header->id.Equals("ID3 ")) {
chunk_size = chunk_header->GetSize();
@@ -283,7 +283,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
/* done processing chunk headers, process tags if any */
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
if (id3_offset != 0) {
/* a ID3 tag has preference over the other tags, do not process
other tags if we have one */
diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx
index 690616d15..b8ae837f7 100644
--- a/src/decoder/plugins/DsfDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsfDecoderPlugin.cxx
@@ -47,7 +47,7 @@ struct DsfMetaData {
unsigned sample_rate, channels;
bool bitreverse;
offset_type n_blocks;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
offset_type id3_offset;
#endif
};
@@ -111,7 +111,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
if (sizeof(dsf_header) != chunk_size)
return false;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
const offset_type metadata_offset = dsf_header.pmeta.Read();
#endif
@@ -174,7 +174,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
metadata->n_blocks = data_size / block_size;
metadata->channels = channels;
metadata->sample_rate = samplefreq;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
metadata->id3_offset = metadata_offset;
#endif
/* check bits per sample format, determine if bitreverse is needed */
@@ -352,7 +352,7 @@ dsf_scan_stream(InputStream &is,
audio_format.sample_rate);
tag_handler_invoke_duration(handler, handler_ctx, songtime);
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
/* Add available tags from the ID3 tag */
dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset);
#endif
diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
index 722f954e2..85e852fb4 100644
--- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -20,14 +20,24 @@
/* necessary because libavutil/common.h uses UINT64_C */
#define __STDC_CONSTANT_MACROS
+#include "lib/ffmpeg/Time.hxx"
#include "config.h"
#include "FfmpegDecoderPlugin.hxx"
#include "lib/ffmpeg/Domain.hxx"
+#include "lib/ffmpeg/Error.hxx"
+#include "lib/ffmpeg/LogError.hxx"
+#include "lib/ffmpeg/Init.hxx"
+#include "lib/ffmpeg/Buffer.hxx"
#include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx"
+#include "FfmpegIo.hxx"
+#include "tag/TagBuilder.hxx"
#include "tag/TagHandler.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "LogV.hxx"
@@ -38,7 +48,6 @@ extern "C" {
#include <libavformat/avio.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
-#include <libavutil/mathematics.h>
#if LIBAVUTIL_VERSION_MAJOR >= 53
#include <libavutil/frame.h>
@@ -48,205 +57,50 @@ extern "C" {
#include <assert.h>
#include <string.h>
-/* suppress the ffmpeg compatibility macro */
-#ifdef SampleFormat
-#undef SampleFormat
-#endif
-
-static LogLevel
-import_ffmpeg_level(int level)
-{
- if (level <= AV_LOG_FATAL)
- return LogLevel::ERROR;
-
- if (level <= AV_LOG_WARNING)
- return LogLevel::WARNING;
-
- if (level <= AV_LOG_INFO)
- return LogLevel::INFO;
-
- return LogLevel::DEBUG;
-}
-
-static void
-mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level,
- const char *fmt, va_list vl)
-{
- const AVClass * cls = nullptr;
-
- if (ptr != nullptr)
- cls = *(const AVClass *const*)ptr;
-
- if (cls != nullptr) {
- char domain[64];
- snprintf(domain, sizeof(domain), "%s/%s",
- ffmpeg_domain.GetName(), cls->item_name(ptr));
- const Domain d(domain);
- LogFormatV(d, import_ffmpeg_level(level), fmt, vl);
- }
-}
-
-struct AvioStream {
- Decoder *const decoder;
- InputStream &input;
-
- AVIOContext *io;
-
- unsigned char buffer[8192];
-
- AvioStream(Decoder *_decoder, InputStream &_input)
- :decoder(_decoder), input(_input), io(nullptr) {}
-
- ~AvioStream() {
- if (io != nullptr)
- av_free(io);
- }
-
- bool Open();
-};
-
-static int
-mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
-{
- AvioStream *stream = (AvioStream *)opaque;
-
- return decoder_read(stream->decoder, stream->input,
- (void *)buf, size);
-}
-
-static int64_t
-mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
-{
- AvioStream *stream = (AvioStream *)opaque;
-
- switch (whence) {
- case SEEK_SET:
- break;
-
- case SEEK_CUR:
- pos += stream->input.GetOffset();
- break;
-
- case SEEK_END:
- if (!stream->input.KnownSize())
- return -1;
-
- pos += stream->input.GetSize();
- break;
-
- case AVSEEK_SIZE:
- if (!stream->input.KnownSize())
- return -1;
-
- return stream->input.GetSize();
-
- default:
- return -1;
- }
-
- if (!stream->input.LockSeek(pos, IgnoreError()))
- return -1;
-
- return stream->input.GetOffset();
-}
-
-bool
-AvioStream::Open()
-{
- io = avio_alloc_context(buffer, sizeof(buffer),
- false, this,
- mpd_ffmpeg_stream_read, nullptr,
- input.IsSeekable()
- ? mpd_ffmpeg_stream_seek : nullptr);
- return io != nullptr;
-}
-
-/**
- * API compatibility wrapper for av_open_input_stream() and
- * avformat_open_input().
- */
-static int
-mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
- AVIOContext *pb,
- const char *filename,
- AVInputFormat *fmt)
+static AVFormatContext *
+FfmpegOpenInput(AVIOContext *pb,
+ const char *filename,
+ AVInputFormat *fmt)
{
AVFormatContext *context = avformat_alloc_context();
if (context == nullptr)
- return AVERROR(ENOMEM);
+ return nullptr;
context->pb = pb;
- *ic_ptr = context;
- return avformat_open_input(ic_ptr, filename, fmt, nullptr);
+
+ avformat_open_input(&context, filename, fmt, nullptr);
+ return context;
}
static bool
ffmpeg_init(gcc_unused const config_param &param)
{
- av_log_set_callback(mpd_ffmpeg_log_callback);
-
- av_register_all();
+ FfmpegInit();
return true;
}
+gcc_pure
static int
-ffmpeg_find_audio_stream(const AVFormatContext *format_context)
+ffmpeg_find_audio_stream(const AVFormatContext &format_context)
{
- for (unsigned i = 0; i < format_context->nb_streams; ++i)
- if (format_context->streams[i]->codec->codec_type ==
+ for (unsigned i = 0; i < format_context.nb_streams; ++i)
+ if (format_context.streams[i]->codec->codec_type ==
AVMEDIA_TYPE_AUDIO)
return i;
return -1;
}
-gcc_const
-static double
-time_from_ffmpeg(int64_t t, const AVRational time_base)
-{
- assert(t != (int64_t)AV_NOPTS_VALUE);
-
- return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
- / (double)1024;
-}
-
-template<typename Ratio>
-static constexpr AVRational
-RatioToAVRational()
-{
- return { Ratio::num, Ratio::den };
-}
-
-gcc_const
-static int64_t
-time_to_ffmpeg(SongTime t, const AVRational time_base)
-{
- return av_rescale_q(t.count(),
- RatioToAVRational<SongTime::period>(),
- time_base);
-}
-
-/**
- * Replace #AV_NOPTS_VALUE with the given fallback.
- */
-static constexpr int64_t
-timestamp_fallback(int64_t t, int64_t fallback)
-{
- return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
- ? t
- : fallback;
-}
-
/**
* Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
* zero. We can't use AV_NOPTS_VALUE in calculations, and we simply
* assume that the stream's start time is zero, which appears to be
* the best way out of that situation.
*/
-static int64_t
+static constexpr int64_t
start_time_fallback(const AVStream &stream)
{
- return timestamp_fallback(stream.start_time, 0);
+ return FfmpegTimestampFallback(stream.start_time, 0);
}
static void
@@ -264,99 +118,103 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src,
}
/**
- * Copy PCM data from a AVFrame to an interleaved buffer.
+ * Copy PCM data from a non-empty AVFrame to an interleaved buffer.
*/
-static int
-copy_interleave_frame(const AVCodecContext *codec_context,
- const AVFrame *frame,
- uint8_t **output_buffer,
- uint8_t **global_buffer, int *global_buffer_size)
+static ConstBuffer<void>
+copy_interleave_frame(const AVCodecContext &codec_context,
+ const AVFrame &frame,
+ FfmpegBuffer &global_buffer,
+ Error &error)
{
+ assert(frame.nb_samples > 0);
+
int plane_size;
const int data_size =
av_samples_get_buffer_size(&plane_size,
- codec_context->channels,
- frame->nb_samples,
- codec_context->sample_fmt, 1);
- if (data_size <= 0)
- return data_size;
-
- if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
- codec_context->channels > 1) {
- if(*global_buffer_size < data_size) {
- av_freep(global_buffer);
-
- *global_buffer = (uint8_t*)av_malloc(data_size);
-
- if (!*global_buffer)
- /* Not enough memory - shouldn't happen */
- return AVERROR(ENOMEM);
- *global_buffer_size = data_size;
+ codec_context.channels,
+ frame.nb_samples,
+ codec_context.sample_fmt, 1);
+ assert(data_size != 0);
+ if (data_size < 0) {
+ SetFfmpegError(error, data_size);
+ return 0;
+ }
+
+ void *output_buffer;
+ if (av_sample_fmt_is_planar(codec_context.sample_fmt) &&
+ codec_context.channels > 1) {
+ output_buffer = global_buffer.GetT<uint8_t>(data_size);
+ if (output_buffer == nullptr) {
+ /* Not enough memory - shouldn't happen */
+ error.SetErrno(ENOMEM);
+ return 0;
}
- *output_buffer = *global_buffer;
- copy_interleave_frame2(*output_buffer, frame->extended_data,
- frame->nb_samples,
- codec_context->channels,
- av_get_bytes_per_sample(codec_context->sample_fmt));
+
+ copy_interleave_frame2((uint8_t *)output_buffer,
+ frame.extended_data,
+ frame.nb_samples,
+ codec_context.channels,
+ av_get_bytes_per_sample(codec_context.sample_fmt));
} else {
- *output_buffer = frame->extended_data[0];
+ output_buffer = frame.extended_data[0];
}
- return data_size;
+ return { output_buffer, (size_t)data_size };
}
+/**
+ * Decode an #AVPacket and send the resulting PCM data to the decoder
+ * API.
+ */
static DecoderCommand
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
- const AVPacket *packet,
- AVCodecContext *codec_context,
- const AVStream *stream,
- AVFrame *frame,
- uint8_t **buffer, int *buffer_size)
+ AVPacket packet,
+ AVCodecContext &codec_context,
+ const AVStream &stream,
+ AVFrame &frame,
+ FfmpegBuffer &buffer)
{
- if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) {
- auto start = start_time_fallback(*stream);
- if (packet->pts >= start)
+ if (packet.pts >= 0 && packet.pts != (int64_t)AV_NOPTS_VALUE) {
+ auto start = start_time_fallback(stream);
+ if (packet.pts >= start)
decoder_timestamp(decoder,
- time_from_ffmpeg(packet->pts - start,
- stream->time_base));
+ FfmpegTimeToDouble(packet.pts - start,
+ stream.time_base));
}
- AVPacket packet2 = *packet;
-
- uint8_t *output_buffer;
+ Error error;
DecoderCommand cmd = DecoderCommand::NONE;
- while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
- int audio_size = 0;
+ while (packet.size > 0 && cmd == DecoderCommand::NONE) {
int got_frame = 0;
- int len = avcodec_decode_audio4(codec_context,
- frame, &got_frame,
- &packet2);
- if (len >= 0 && got_frame) {
- audio_size = copy_interleave_frame(codec_context,
- frame,
- &output_buffer,
- buffer, buffer_size);
- if (audio_size < 0)
- len = audio_size;
- }
-
+ int len = avcodec_decode_audio4(&codec_context,
+ &frame, &got_frame,
+ &packet);
if (len < 0) {
/* if error, we skip the frame */
- LogDefault(ffmpeg_domain,
- "decoding failed, frame skipped");
+ LogFfmpegError(len, "decoding failed, frame skipped");
break;
}
- packet2.data += len;
- packet2.size -= len;
+ packet.data += len;
+ packet.size -= len;
- if (audio_size <= 0)
+ if (!got_frame || frame.nb_samples <= 0)
continue;
+ auto output_buffer =
+ copy_interleave_frame(codec_context, frame,
+ buffer, error);
+ if (output_buffer.IsNull()) {
+ /* this must be a serious error,
+ e.g. OOM */
+ LogError(error);
+ return DecoderCommand::STOP;
+ }
+
cmd = decoder_data(decoder, is,
- output_buffer, audio_size,
- codec_context->bit_rate / 1000);
+ output_buffer.data, output_buffer.size,
+ codec_context.bit_rate / 1000);
}
return cmd;
}
@@ -399,10 +257,8 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
static AVInputFormat *
ffmpeg_probe(Decoder *decoder, InputStream &is)
{
- enum {
- BUFFER_SIZE = 16384,
- PADDING = 16,
- };
+ constexpr size_t BUFFER_SIZE = 16384;
+ constexpr size_t PADDING = 16;
unsigned char buffer[BUFFER_SIZE];
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
@@ -442,85 +298,163 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
}
static void
-ffmpeg_decode(Decoder &decoder, InputStream &input)
+FfmpegParseMetaData(AVDictionary &dict, ReplayGainInfo &rg, MixRampInfo &mr)
{
- AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
- if (input_format == nullptr)
- return;
+ AVDictionaryEntry *i = nullptr;
- FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
- input_format->name, input_format->long_name);
+ while ((i = av_dict_get(&dict, "", i,
+ AV_DICT_IGNORE_SUFFIX)) != nullptr) {
+ const char *name = i->key;
+ const char *value = i->value;
- AvioStream stream(&decoder, input);
- if (!stream.Open()) {
- LogError(ffmpeg_domain, "Failed to open stream");
- return;
+ if (!ParseReplayGainTag(rg, name, value))
+ ParseMixRampTag(mr, name, value);
}
+}
- //ffmpeg works with ours "fileops" helper
- AVFormatContext *format_context = nullptr;
- if (mpd_ffmpeg_open_input(&format_context, stream.io,
- input.GetURI(),
- input_format) != 0) {
- LogError(ffmpeg_domain, "Open failed");
+static void
+FfmpegParseMetaData(const AVStream &stream,
+ ReplayGainInfo &rg, MixRampInfo &mr)
+{
+ FfmpegParseMetaData(*stream.metadata, rg, mr);
+}
+
+static void
+FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
+ ReplayGainInfo &rg, MixRampInfo &mr)
+{
+ assert(audio_stream >= 0);
+
+ FfmpegParseMetaData(*format_context.metadata, rg, mr);
+ FfmpegParseMetaData(*format_context.streams[audio_stream],
+ rg, mr);
+}
+
+static void
+FfmpegParseMetaData(Decoder &decoder,
+ const AVFormatContext &format_context, int audio_stream)
+{
+ ReplayGainInfo rg;
+ rg.Clear();
+
+ MixRampInfo mr;
+ mr.Clear();
+
+ FfmpegParseMetaData(format_context, audio_stream, rg, mr);
+
+ if (rg.IsDefined())
+ decoder_replay_gain(decoder, &rg);
+
+ if (mr.IsDefined())
+ decoder_mixramp(decoder, std::move(mr));
+}
+
+static void
+FfmpegScanMetadata(const AVStream &stream,
+ const tag_handler &handler, void *handler_ctx)
+{
+ FfmpegScanDictionary(stream.metadata, &handler, handler_ctx);
+}
+
+static void
+FfmpegScanMetadata(const AVFormatContext &format_context, int audio_stream,
+ const tag_handler &handler, void *handler_ctx)
+{
+ assert(audio_stream >= 0);
+
+ FfmpegScanDictionary(format_context.metadata, &handler, handler_ctx);
+ FfmpegScanMetadata(*format_context.streams[audio_stream],
+ handler, handler_ctx);
+}
+
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0)
+
+static void
+FfmpegScanTag(const AVFormatContext &format_context, int audio_stream,
+ TagBuilder &tag)
+{
+ FfmpegScanMetadata(format_context, audio_stream,
+ full_tag_handler, &tag);
+}
+
+/**
+ * Check if a new stream tag was received and pass it to
+ * decoder_tag().
+ */
+static void
+FfmpegCheckTag(Decoder &decoder, InputStream &is,
+ AVFormatContext &format_context, int audio_stream)
+{
+ AVStream &stream = *format_context.streams[audio_stream];
+ if ((stream.event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) == 0)
+ /* no new metadata */
return;
- }
+ /* clear the flag */
+ stream.event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
+
+ TagBuilder tag;
+ FfmpegScanTag(format_context, audio_stream, tag);
+ if (!tag.IsEmpty())
+ decoder_tag(decoder, is, tag.Commit());
+}
+
+#endif
+
+static void
+FfmpegDecode(Decoder &decoder, InputStream &input,
+ AVFormatContext &format_context)
+{
const int find_result =
- avformat_find_stream_info(format_context, nullptr);
+ avformat_find_stream_info(&format_context, nullptr);
if (find_result < 0) {
LogError(ffmpeg_domain, "Couldn't find stream info");
- avformat_close_input(&format_context);
return;
}
int audio_stream = ffmpeg_find_audio_stream(format_context);
if (audio_stream == -1) {
LogError(ffmpeg_domain, "No audio stream inside");
- avformat_close_input(&format_context);
return;
}
- AVStream *av_stream = format_context->streams[audio_stream];
+ AVStream &av_stream = *format_context.streams[audio_stream];
- AVCodecContext *codec_context = av_stream->codec;
+ AVCodecContext &codec_context = *av_stream.codec;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0)
const AVCodecDescriptor *codec_descriptor =
- avcodec_descriptor_get(codec_context->codec_id);
+ avcodec_descriptor_get(codec_context.codec_id);
if (codec_descriptor != nullptr)
FormatDebug(ffmpeg_domain, "codec '%s'",
codec_descriptor->name);
#else
- if (codec_context->codec_name[0] != 0)
+ if (codec_context.codec_name[0] != 0)
FormatDebug(ffmpeg_domain, "codec '%s'",
- codec_context->codec_name);
+ codec_context.codec_name);
#endif
- AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
+ AVCodec *codec = avcodec_find_decoder(codec_context.codec_id);
if (!codec) {
LogError(ffmpeg_domain, "Unsupported audio codec");
- avformat_close_input(&format_context);
return;
}
const SampleFormat sample_format =
- ffmpeg_sample_format(codec_context->sample_fmt);
+ ffmpeg_sample_format(codec_context.sample_fmt);
if (sample_format == SampleFormat::UNDEFINED) {
// (error message already done by ffmpeg_sample_format())
- avformat_close_input(&format_context);
return;
}
Error error;
AudioFormat audio_format;
if (!audio_format_init_checked(audio_format,
- codec_context->sample_rate,
+ codec_context.sample_rate,
sample_format,
- codec_context->channels, error)) {
+ codec_context.channels, error)) {
LogError(error);
- avformat_close_input(&format_context);
return;
}
@@ -529,22 +463,20 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
values into AVCodecContext.channels - a change that will be
reverted later by avcodec_decode_audio3() */
- const int open_result = avcodec_open2(codec_context, codec, nullptr);
+ const int open_result = avcodec_open2(&codec_context, codec, nullptr);
if (open_result < 0) {
LogError(ffmpeg_domain, "Could not open codec");
- avformat_close_input(&format_context);
return;
}
const SignedSongTime total_time =
- format_context->duration != (int64_t)AV_NOPTS_VALUE
- ? SignedSongTime::FromScale<uint64_t>(format_context->duration,
- AV_TIME_BASE)
- : SignedSongTime::Negative();
+ FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base);
decoder_initialized(decoder, audio_format,
input.IsSeekable(), total_time);
+ FfmpegParseMetaData(decoder, format_context, audio_stream);
+
#if LIBAVUTIL_VERSION_MAJOR >= 53
AVFrame *frame = av_frame_alloc();
#else
@@ -552,26 +484,28 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
#endif
if (!frame) {
LogError(ffmpeg_domain, "Could not allocate frame");
- avformat_close_input(&format_context);
return;
}
- uint8_t *interleaved_buffer = nullptr;
- int interleaved_buffer_size = 0;
+ FfmpegBuffer interleaved_buffer;
DecoderCommand cmd;
do {
AVPacket packet;
- if (av_read_frame(format_context, &packet) < 0)
+ if (av_read_frame(&format_context, &packet) < 0)
/* end of file */
break;
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0)
+ FfmpegCheckTag(decoder, input, format_context, audio_stream);
+#endif
+
if (packet.stream_index == audio_stream)
cmd = ffmpeg_send_packet(decoder, input,
- &packet, codec_context,
+ packet, codec_context,
av_stream,
- frame,
- &interleaved_buffer, &interleaved_buffer_size);
+ *frame,
+ interleaved_buffer);
else
cmd = decoder_get_command(decoder);
@@ -579,15 +513,15 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
if (cmd == DecoderCommand::SEEK) {
int64_t where =
- time_to_ffmpeg(decoder_seek_time(decoder),
- av_stream->time_base) +
- start_time_fallback(*av_stream);
+ ToFfmpegTime(decoder_seek_time(decoder),
+ av_stream.time_base) +
+ start_time_fallback(av_stream);
- if (av_seek_frame(format_context, audio_stream, where,
+ if (av_seek_frame(&format_context, audio_stream, where,
AVSEEK_FLAG_ANY) < 0)
decoder_seek_error(decoder);
else {
- avcodec_flush_buffers(codec_context);
+ avcodec_flush_buffers(&codec_context);
decoder_command_finished(decoder);
}
}
@@ -598,15 +532,63 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
avcodec_free_frame(&frame);
#else
- av_freep(&frame);
+ av_free(frame);
#endif
- av_freep(&interleaved_buffer);
- avcodec_close(codec_context);
+ avcodec_close(&codec_context);
+}
+
+static void
+ffmpeg_decode(Decoder &decoder, InputStream &input)
+{
+ AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
+ if (input_format == nullptr)
+ return;
+
+ FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
+ input_format->name, input_format->long_name);
+
+ AvioStream stream(&decoder, input);
+ if (!stream.Open()) {
+ LogError(ffmpeg_domain, "Failed to open stream");
+ return;
+ }
+
+ AVFormatContext *format_context =
+ FfmpegOpenInput(stream.io, input.GetURI(), input_format);
+ if (format_context == nullptr) {
+ LogError(ffmpeg_domain, "Open failed");
+ return;
+ }
+
+ FfmpegDecode(decoder, input, *format_context);
avformat_close_input(&format_context);
}
-//no tag reading in ffmpeg, check if playable
+static bool
+FfmpegScanStream(AVFormatContext &format_context,
+ const struct tag_handler &handler, void *handler_ctx)
+{
+ const int find_result =
+ avformat_find_stream_info(&format_context, nullptr);
+ if (find_result < 0)
+ return false;
+
+ const int audio_stream = ffmpeg_find_audio_stream(format_context);
+ if (audio_stream < 0)
+ return false;
+
+ const AVStream &stream = *format_context.streams[audio_stream];
+ if (stream.duration != (int64_t)AV_NOPTS_VALUE)
+ tag_handler_invoke_duration(&handler, handler_ctx,
+ FromFfmpegTime(stream.duration,
+ stream.time_base));
+
+ FfmpegScanMetadata(format_context, audio_stream, handler, handler_ctx);
+
+ return true;
+}
+
static bool
ffmpeg_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
@@ -619,33 +601,14 @@ ffmpeg_scan_stream(InputStream &is,
if (!stream.Open())
return false;
- AVFormatContext *f = nullptr;
- if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(),
- input_format) != 0)
+ AVFormatContext *f =
+ FfmpegOpenInput(stream.io, is.GetURI(), input_format);
+ if (f == nullptr)
return false;
- const int find_result =
- avformat_find_stream_info(f, nullptr);
- if (find_result < 0) {
- avformat_close_input(&f);
- return false;
- }
-
- if (f->duration != (int64_t)AV_NOPTS_VALUE) {
- const auto duration =
- SongTime::FromScale<uint64_t>(f->duration,
- AV_TIME_BASE);
- tag_handler_invoke_duration(handler, handler_ctx, duration);
- }
-
- ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
- int idx = ffmpeg_find_audio_stream(f);
- if (idx >= 0)
- ffmpeg_scan_dictionary(f->streams[idx]->metadata,
- handler, handler_ctx);
-
+ bool result = FfmpegScanStream(*f, *handler, handler_ctx);
avformat_close_input(&f);
- return true;
+ return result;
}
/**
diff --git a/src/decoder/plugins/FfmpegIo.cxx b/src/decoder/plugins/FfmpegIo.cxx
new file mode 100644
index 000000000..d47e575bc
--- /dev/null
+++ b/src/decoder/plugins/FfmpegIo.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegIo.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.hxx"
+
+AvioStream::~AvioStream()
+{
+ av_free(io);
+}
+
+inline int
+AvioStream::Read(void *dest, int size)
+{
+ return decoder_read(decoder, input, dest, size);
+}
+
+inline int64_t
+AvioStream::Seek(int64_t pos, int whence)
+{
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ pos += input.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!input.KnownSize())
+ return -1;
+
+ pos += input.GetSize();
+ break;
+
+ case AVSEEK_SIZE:
+ if (!input.KnownSize())
+ return -1;
+
+ return input.GetSize();
+
+ default:
+ return -1;
+ }
+
+ if (!input.LockSeek(pos, IgnoreError()))
+ return -1;
+
+ return input.GetOffset();
+}
+
+int
+AvioStream::_Read(void *opaque, uint8_t *buf, int size)
+{
+ AvioStream &stream = *(AvioStream *)opaque;
+
+ return stream.Read(buf, size);
+}
+
+int64_t
+AvioStream::_Seek(void *opaque, int64_t pos, int whence)
+{
+ AvioStream &stream = *(AvioStream *)opaque;
+
+ return stream.Seek(pos, whence);
+}
+
+bool
+AvioStream::Open()
+{
+ io = avio_alloc_context(buffer, sizeof(buffer),
+ false, this,
+ _Read, nullptr,
+ input.IsSeekable() ? _Seek : nullptr);
+ return io != nullptr;
+}
diff --git a/src/decoder/plugins/FfmpegIo.hxx b/src/decoder/plugins/FfmpegIo.hxx
new file mode 100644
index 000000000..bbd3a5b62
--- /dev/null
+++ b/src/decoder/plugins/FfmpegIo.hxx
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_IO_HXX
+#define MPD_FFMPEG_IO_HXX
+
+#include "check.h"
+
+extern "C" {
+#include "libavformat/avio.h"
+}
+
+#include <stdint.h>
+
+class InputStream;
+struct Decoder;
+
+struct AvioStream {
+ Decoder *const decoder;
+ InputStream &input;
+
+ AVIOContext *io;
+
+ uint8_t buffer[8192];
+
+ AvioStream(Decoder *_decoder, InputStream &_input)
+ :decoder(_decoder), input(_input), io(nullptr) {}
+
+ ~AvioStream();
+
+ bool Open();
+
+private:
+ int Read(void *buffer, int size);
+ int64_t Seek(int64_t pos, int whence);
+
+ static int _Read(void *opaque, uint8_t *buf, int size);
+ static int64_t _Seek(void *opaque, int64_t pos, int whence);
+};
+
+#endif
diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx
index a39466945..b8f7a12a3 100644
--- a/src/decoder/plugins/FfmpegMetaData.cxx
+++ b/src/decoder/plugins/FfmpegMetaData.cxx
@@ -25,7 +25,11 @@
#include "tag/TagTable.hxx"
#include "tag/TagHandler.hxx"
-static const struct tag_table ffmpeg_tags[] = {
+extern "C" {
+#include <libavutil/dict.h>
+}
+
+static constexpr struct tag_table ffmpeg_tags[] = {
{ "year", TAG_DATE },
{ "author-sort", TAG_ARTIST_SORT },
{ "album_artist", TAG_ALBUM_ARTIST },
@@ -36,9 +40,9 @@ static const struct tag_table ffmpeg_tags[] = {
};
static void
-ffmpeg_copy_metadata(TagType type,
- AVDictionary *m, const char *name,
- const struct tag_handler *handler, void *handler_ctx)
+FfmpegScanTag(TagType type,
+ AVDictionary *m, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
{
AVDictionaryEntry *mt = nullptr;
@@ -48,8 +52,8 @@ ffmpeg_copy_metadata(TagType type,
}
static void
-ffmpeg_scan_pairs(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx)
+FfmpegScanPairs(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
{
AVDictionaryEntry *i = nullptr;
@@ -59,18 +63,20 @@ ffmpeg_scan_pairs(AVDictionary *dict,
}
void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx)
+FfmpegScanDictionary(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
{
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i],
- handler, handler_ctx);
+ if (handler->tag != nullptr) {
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ FfmpegScanTag(TagType(i), dict, tag_item_names[i],
+ handler, handler_ctx);
- for (const struct tag_table *i = ffmpeg_tags;
- i->name != nullptr; ++i)
- ffmpeg_copy_metadata(i->type, dict, i->name,
- handler, handler_ctx);
+ for (const struct tag_table *i = ffmpeg_tags;
+ i->name != nullptr; ++i)
+ FfmpegScanTag(i->type, dict, i->name,
+ handler, handler_ctx);
+ }
if (handler->pair != nullptr)
- ffmpeg_scan_pairs(dict, handler, handler_ctx);
+ FfmpegScanPairs(dict, handler, handler_ctx);
}
diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx
index 5eb41db68..4b77adf5d 100644
--- a/src/decoder/plugins/FfmpegMetaData.hxx
+++ b/src/decoder/plugins/FfmpegMetaData.hxx
@@ -20,19 +20,11 @@
#ifndef MPD_FFMPEG_METADATA_HXX
#define MPD_FFMPEG_METADATA_HXX
-extern "C" {
-#include <libavutil/dict.h>
-}
-
-/* suppress the ffmpeg compatibility macro */
-#ifdef SampleFormat
-#undef SampleFormat
-#endif
-
+struct AVDictionary;
struct tag_handler;
void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const tag_handler *handler, void *handler_ctx);
+FfmpegScanDictionary(AVDictionary *dict,
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/decoder/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx
index 03e276dce..86c6da04b 100644
--- a/src/decoder/plugins/FlacMetadata.cxx
+++ b/src/decoder/plugins/FlacMetadata.cxx
@@ -30,7 +30,7 @@
#include "tag/MixRamp.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
-#include "util/SplitString.hxx"
+#include "util/DivideString.hxx"
bool
flac_parse_replay_gain(ReplayGainInfo &rgi,
@@ -97,7 +97,7 @@ flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
{
if (handler->pair != nullptr) {
const char *comment = (const char *)entry->entry;
- const SplitString split(comment, '=');
+ const DivideString split(comment, '=');
if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
split.GetFirst(),
diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
index f19ac5bf4..55247549b 100644
--- a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
@@ -35,7 +35,7 @@ static unsigned sample_rate;
static const char *soundfont_path;
/**
- * Convert a fluidsynth log level to a GLib log level.
+ * Convert a fluidsynth log level to a MPD log level.
*/
static LogLevel
fluidsynth_level_to_mpd(enum fluid_log_level level)
@@ -61,7 +61,7 @@ fluidsynth_level_to_mpd(enum fluid_log_level level)
}
/**
- * The fluidsynth logging callback. It forwards messages to the GLib
+ * The fluidsynth logging callback. It forwards messages to the MPD
* logging library.
*/
static void
diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx
index cc6ce5e5d..8d3116ae1 100644
--- a/src/decoder/plugins/GmeDecoderPlugin.cxx
+++ b/src/decoder/plugins/GmeDecoderPlugin.cxx
@@ -23,6 +23,7 @@
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "fs/Path.hxx"
+#include "fs/AllocatedPath.hxx"
#include "util/Alloc.hxx"
#include "util/FormatString.hxx"
#include "util/UriUtil.hxx"
@@ -30,7 +31,6 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
@@ -47,65 +47,42 @@ static constexpr unsigned GME_BUFFER_FRAMES = 2048;
static constexpr unsigned GME_BUFFER_SAMPLES =
GME_BUFFER_FRAMES * GME_CHANNELS;
-/**
- * returns the file path stripped of any /tune_xxx.* subtune
- * suffix
- */
-static char *
-get_container_name(Path path_fs)
+struct GmeContainerPath {
+ AllocatedPath path;
+ unsigned track;
+};
+
+gcc_pure
+static unsigned
+ParseSubtuneName(const char *base)
{
- const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
- char *path_container = xstrdup(path_fs.c_str());
-
- char pat[64];
- snprintf(pat, sizeof(pat), "%s%s",
- "*/" SUBTUNE_PREFIX "???.",
- subtune_suffix);
- GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
- if (!g_pattern_match(path_with_subtune,
- strlen(path_container), path_container, nullptr)) {
- g_pattern_spec_free(path_with_subtune);
- return path_container;
- }
+ if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
+ return 0;
- char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
- if (ptr != nullptr)
- *ptr='\0';
+ base += sizeof(SUBTUNE_PREFIX) - 1;
- g_pattern_spec_free(path_with_subtune);
- return path_container;
+ char *endptr;
+ auto track = strtoul(base, &endptr, 10);
+ if (endptr == base || *endptr != '.')
+ return 0;
+
+ return track;
}
/**
- * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
- * is appended.
+ * returns the file path stripped of any /tune_xxx.* subtune suffix
+ * and the track number (or 0 if no "tune_xxx" suffix is present).
*/
-static int
-get_song_num(Path path_fs)
+static GmeContainerPath
+ParseContainerPath(Path path_fs)
{
- const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
+ const Path base = path_fs.GetBase();
+ unsigned track;
+ if (base.IsNull() ||
+ (track = ParseSubtuneName(base.c_str())) < 1)
+ return { AllocatedPath(path_fs), 0 };
- char pat[64];
- snprintf(pat, sizeof(pat), "%s%s",
- "*/" SUBTUNE_PREFIX "???.",
- subtune_suffix);
- GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
-
- if (g_pattern_match(path_with_subtune,
- path_fs.length(), path_fs.data(), nullptr)) {
- char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX);
- g_pattern_spec_free(path_with_subtune);
- if (!sub)
- return 0;
-
- sub += strlen("/" SUBTUNE_PREFIX);
- int song_num = strtol(sub, nullptr, 10);
-
- return song_num - 1;
- } else {
- g_pattern_spec_free(path_with_subtune);
- return 0;
- }
+ return { path_fs.GetDirectoryName(), track - 1 };
}
static char *
@@ -136,20 +113,18 @@ gme_container_scan(Path path_fs, const unsigned int tnum)
static void
gme_file_decode(Decoder &decoder, Path path_fs)
{
- char *path_container = get_container_name(path_fs);
+ const auto container = ParseContainerPath(path_fs);
Music_Emu *emu;
const char *gme_err =
- gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- free(path_container);
+ gme_open_file(container.path.c_str(), &emu, GME_SAMPLE_RATE);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
return;
}
gme_info_t *ti;
- const int song_num = get_song_num(path_fs);
- gme_err = gme_track_info(emu, &ti, song_num);
+ gme_err = gme_track_info(emu, &ti, container.track);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
gme_delete(emu);
@@ -175,7 +150,7 @@ gme_file_decode(Decoder &decoder, Path path_fs)
decoder_initialized(decoder, audio_format, true, song_len);
- gme_err = gme_start_track(emu, song_num);
+ gme_err = gme_start_track(emu, container.track);
if (gme_err != nullptr)
LogWarning(gme_domain, gme_err);
@@ -209,72 +184,85 @@ gme_file_decode(Decoder &decoder, Path path_fs)
gme_delete(emu);
}
-static bool
-gme_scan_file(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx)
+static void
+ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
+ const struct tag_handler *handler, void *handler_ctx)
{
- char *path_container = get_container_name(path_fs);
-
- Music_Emu *emu;
- const char *gme_err =
- gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- free(path_container);
- if (gme_err != nullptr) {
- LogWarning(gme_domain, gme_err);
- return false;
- }
-
- const int song_num = get_song_num(path_fs);
-
- gme_info_t *ti;
- gme_err = gme_track_info(emu, &ti, song_num);
- if (gme_err != nullptr) {
- LogWarning(gme_domain, gme_err);
- gme_delete(emu);
- return false;
- }
-
- assert(ti != nullptr);
-
- if (ti->length > 0)
+ if (info.length > 0)
tag_handler_invoke_duration(handler, handler_ctx,
- SongTime::FromMS(ti->length));
+ SongTime::FromMS(info.length));
- if (ti->song != nullptr) {
- if (gme_track_count(emu) > 1) {
+ if (info.song != nullptr) {
+ if (track_count > 1) {
/* start numbering subtunes from 1 */
char tag_title[1024];
snprintf(tag_title, sizeof(tag_title),
- "%s (%d/%d)",
- ti->song, song_num + 1,
- gme_track_count(emu));
+ "%s (%u/%d)",
+ info.song, song_num + 1,
+ track_count);
tag_handler_invoke_tag(handler, handler_ctx,
TAG_TITLE, tag_title);
} else
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, ti->song);
+ TAG_TITLE, info.song);
}
- if (ti->author != nullptr)
+ if (info.author != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ARTIST, ti->author);
+ TAG_ARTIST, info.author);
- if (ti->game != nullptr)
+ if (info.game != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ALBUM, ti->game);
+ TAG_ALBUM, info.game);
- if (ti->comment != nullptr)
+ if (info.comment != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_COMMENT, ti->comment);
+ TAG_COMMENT, info.comment);
- if (ti->copyright != nullptr)
+ if (info.copyright != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_DATE, ti->copyright);
+ TAG_DATE, info.copyright);
+}
+
+static bool
+ScanMusicEmu(Music_Emu *emu, unsigned song_num,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ gme_info_t *ti;
+ const char *gme_err = gme_track_info(emu, &ti, song_num);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return false;
+ }
+
+ assert(ti != nullptr);
+
+ ScanGmeInfo(*ti, song_num, gme_track_count(emu),
+ handler, handler_ctx);
gme_free_info(ti);
+ return true;
+}
+
+static bool
+gme_scan_file(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const auto container = ParseContainerPath(path_fs);
+
+ Music_Emu *emu;
+ const char *gme_err =
+ gme_open_file(container.path.c_str(), &emu, GME_SAMPLE_RATE);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return false;
+ }
+
+ const bool result = ScanMusicEmu(emu, container.track, handler, handler_ctx);
+
gme_delete(emu);
- return true;
+ return result;
}
static const char *const gme_suffixes[] = {
diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx
index de6c9b127..669b57817 100644
--- a/src/decoder/plugins/MadDecoderPlugin.cxx
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -36,7 +36,7 @@
#include <mad.h>
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
#include <id3tag.h>
#endif
@@ -251,7 +251,7 @@ MadDecoder::FillBuffer()
return true;
}
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
static bool
parse_id3_replay_gain_info(ReplayGainInfo &rgi,
struct id3_tag *tag)
@@ -285,7 +285,7 @@ parse_id3_replay_gain_info(ReplayGainInfo &rgi,
}
#endif
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
gcc_pure
static MixRampInfo
parse_id3_mixramp(struct id3_tag *tag)
@@ -317,7 +317,7 @@ parse_id3_mixramp(struct id3_tag *tag)
inline void
MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
{
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
id3_byte_t *allocated = nullptr;
const id3_length_t count = stream.bufend - stream.this_frame;
@@ -369,7 +369,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
id3_tag_delete(id3_tag);
delete[] allocated;
-#else /* !HAVE_ID3TAG */
+#else /* !ENABLE_ID3TAG */
(void)mpd_tag;
/* This code is enabled when libid3tag is disabled. Instead
@@ -386,7 +386,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
#endif
}
-#ifndef HAVE_ID3TAG
+#ifndef ENABLE_ID3TAG
/**
* This function emulates libid3tag when it is disabled. Instead of
* doing a real analyzation of the frame, it just checks whether the
@@ -402,7 +402,7 @@ id3_tag_query(const void *p0, size_t length)
? (p[8] << 7) + p[9] + 10
: 0;
}
-#endif /* !HAVE_ID3TAG */
+#endif /* !ENABLE_ID3TAG */
static enum mp3_action
RecoverFrameError(struct mad_stream &stream)
@@ -504,10 +504,10 @@ struct xing {
enum xing_magic magic; /* header magic */
};
-static const unsigned XING_FRAMES = 1;
-static const unsigned XING_BYTES = 2;
-static const unsigned XING_TOC = 4;
-static const unsigned XING_SCALE = 8;
+static constexpr unsigned XING_FRAMES = 1;
+static constexpr unsigned XING_BYTES = 2;
+static constexpr unsigned XING_TOC = 4;
+static constexpr unsigned XING_SCALE = 8;
struct lame_version {
unsigned major;
diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx
index 8435f095f..395945157 100644
--- a/src/decoder/plugins/SidplayDecoderPlugin.cxx
+++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx
@@ -22,67 +22,44 @@
#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "fs/Path.hxx"
+#include "fs/AllocatedPath.hxx"
#include "util/FormatString.hxx"
#include "util/Domain.hxx"
+#include "util/Error.hxx"
#include "system/ByteOrder.hxx"
+#include "system/FatalError.hxx"
#include "Log.hxx"
-#include <errno.h>
-#include <stdlib.h>
#include <string.h>
-#include <glib.h>
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
#include <sidplay/utils/SidTuneMod.h>
+#include <sidplay/utils/SidDatabase.h>
#define SUBTUNE_PREFIX "tune_"
static constexpr Domain sidplay_domain("sidplay");
-static GPatternSpec *path_with_subtune;
-static const char *songlength_file;
-static GKeyFile *songlength_database;
+static SidDatabase *songlength_database;
static bool all_files_are_containers;
static unsigned default_songlength;
static bool filter_setting;
-static GKeyFile *
-sidplay_load_songlength_db(const char *path)
+static SidDatabase *
+sidplay_load_songlength_db(const Path path)
{
- GError *error = nullptr;
- gchar *data;
- gsize size;
-
- if (!g_file_get_contents(path, &data, &size, &error)) {
+ SidDatabase *db = new SidDatabase();
+ if (db->open(path.c_str()) < 0) {
FormatError(sidplay_domain,
"unable to read songlengths file %s: %s",
- path, error->message);
- g_error_free(error);
- return nullptr;
- }
-
- /* replace any ; comment characters with # */
- for (gsize i = 0; i < size; i++)
- if (data[i] == ';')
- data[i] = '#';
-
- GKeyFile *db = g_key_file_new();
- bool success = g_key_file_load_from_data(db, data, size,
- G_KEY_FILE_NONE, &error);
- g_free(data);
- if (!success) {
- FormatError(sidplay_domain,
- "unable to parse songlengths file %s: %s",
- path, error->message);
- g_error_free(error);
- g_key_file_free(db);
+ path.c_str(), db->error());
+ delete db;
return nullptr;
}
- g_key_file_set_list_separator(db, ' ');
return db;
}
@@ -90,18 +67,18 @@ static bool
sidplay_init(const config_param &param)
{
/* read the songlengths database file */
- songlength_file = param.GetBlockValue("songlength_database");
- if (songlength_file != nullptr)
- songlength_database = sidplay_load_songlength_db(songlength_file);
+ Error error;
+ const auto database_path = param.GetBlockPath("songlength_database", error);
+ if (!database_path.IsNull())
+ songlength_database = sidplay_load_songlength_db(database_path);
+ else if (error.IsDefined())
+ FatalError(error);
default_songlength = param.GetBlockValue("default_songlength", 0u);
all_files_are_containers =
param.GetBlockValue("all_files_are_containers", true);
- path_with_subtune=g_pattern_spec_new(
- "*/" SUBTUNE_PREFIX "???.sid");
-
filter_setting = param.GetBlockValue("filter", true);
return true;
@@ -110,96 +87,61 @@ sidplay_init(const config_param &param)
static void
sidplay_finish()
{
- g_pattern_spec_free(path_with_subtune);
-
- if(songlength_database)
- g_key_file_free(songlength_database);
+ delete songlength_database;
}
-/**
- * returns the file path stripped of any /tune_xxx.sid subtune
- * suffix
- */
-static char *
-get_container_name(Path path_fs)
+struct SidplayContainerPath {
+ AllocatedPath path;
+ unsigned track;
+};
+
+gcc_pure
+static unsigned
+ParseSubtuneName(const char *base)
{
- char *path_container = strdup(path_fs.c_str());
+ if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
+ return 0;
- if(!g_pattern_match(path_with_subtune,
- strlen(path_container), path_container, nullptr))
- return path_container;
+ base += sizeof(SUBTUNE_PREFIX) - 1;
- char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
- if(ptr) *ptr='\0';
+ char *endptr;
+ auto track = strtoul(base, &endptr, 10);
+ if (endptr == base || *endptr != '.')
+ return 0;
- return path_container;
+ return track;
}
/**
- * returns tune number from file.sid/tune_xxx.sid style path or 1 if
- * no subtune is appended
+ * returns the file path stripped of any /tune_xxx.* subtune suffix
+ * and the track number (or 1 if no "tune_xxx" suffix is present).
*/
-static unsigned
-get_song_num(const char *path_fs)
+static SidplayContainerPath
+ParseContainerPath(Path path_fs)
{
- if(g_pattern_match(path_with_subtune,
- strlen(path_fs), path_fs, nullptr)) {
- char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
- if(!sub) return 1;
-
- sub+=strlen("/" SUBTUNE_PREFIX);
- int song_num=strtol(sub, nullptr, 10);
-
- if (errno == EINVAL)
- return 1;
- else
- return song_num;
- } else
- return 1;
+ const Path base = path_fs.GetBase();
+ unsigned track;
+ if (base.IsNull() ||
+ (track = ParseSubtuneName(base.c_str())) < 1)
+ return { AllocatedPath(path_fs), 1 };
+
+ return { path_fs.GetDirectoryName(), track };
}
/* get the song length in seconds */
static SignedSongTime
-get_song_length(Path path_fs)
+get_song_length(SidTuneMod &tune)
{
+ assert(tune);
+
if (songlength_database == nullptr)
return SignedSongTime::Negative();
- char *sid_file = get_container_name(path_fs);
- SidTuneMod tune(sid_file);
- free(sid_file);
- if(!tune) {
- LogWarning(sidplay_domain,
- "failed to load file for calculating md5 sum");
+ const auto length = songlength_database->length(tune);
+ if (length < 0)
return SignedSongTime::Negative();
- }
- char md5sum[SIDTUNE_MD5_LENGTH+1];
- tune.createMD5(md5sum);
-
- const unsigned song_num = get_song_num(path_fs.c_str());
- gsize num_items;
- gchar **values=g_key_file_get_string_list(songlength_database,
- "Database", md5sum, &num_items, nullptr);
- if(!values || song_num>num_items) {
- g_strfreev(values);
- return SignedSongTime::Negative();
- }
-
- int minutes=strtol(values[song_num-1], nullptr, 10);
- if(errno==EINVAL) minutes=0;
-
- int seconds;
- char *ptr=strchr(values[song_num-1], ':');
- if(ptr) {
- seconds=strtol(ptr+1, nullptr, 10);
- if(errno==EINVAL) seconds=0;
- } else
- seconds=0;
-
- g_strfreev(values);
-
- return SignedSongTime::FromS((minutes * 60) + seconds);
+ return SignedSongTime::FromS(length);
}
static void
@@ -209,18 +151,17 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
/* load the tune */
- char *path_container=get_container_name(path_fs);
- SidTune tune(path_container, nullptr, true);
- free(path_container);
+ const auto container = ParseContainerPath(path_fs);
+ SidTuneMod tune(container.path.c_str());
if (!tune) {
LogWarning(sidplay_domain, "failed to load file");
return;
}
- const int song_num = get_song_num(path_fs.c_str());
+ const int song_num = container.track;
tune.selectSong(song_num);
- auto duration = get_song_length(path_fs);
+ auto duration = get_song_length(tune);
if (duration.IsNegative() && default_songlength > 0)
duration = SongTime::FromS(default_songlength);
@@ -347,14 +288,15 @@ static bool
sidplay_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
- const int song_num = get_song_num(path_fs.c_str());
- char *path_container=get_container_name(path_fs);
+ const auto container = ParseContainerPath(path_fs);
+ const unsigned song_num = container.track;
- SidTune tune(path_container, nullptr, true);
- free(path_container);
+ SidTuneMod tune(container.path.c_str());
if (!tune)
return false;
+ tune.selectSong(song_num);
+
const SidTuneInfo &info = tune.getInfo();
/* title */
@@ -385,7 +327,7 @@ sidplay_scan_file(Path path_fs,
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
/* time */
- const auto duration = get_song_length(path_fs);
+ const auto duration = get_song_length(tune);
if (!duration.IsNegative())
tag_handler_invoke_duration(handler, handler_ctx,
SongTime(duration));
diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx
index 062f46acf..10652f129 100644
--- a/src/decoder/plugins/VorbisComments.cxx
+++ b/src/decoder/plugins/VorbisComments.cxx
@@ -27,7 +27,7 @@
#include "tag/ReplayGain.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
-#include "util/SplitString.hxx"
+#include "util/DivideString.hxx"
#include <stddef.h>
#include <stdlib.h>
@@ -74,7 +74,7 @@ vorbis_scan_comment(const char *comment,
const struct tag_handler *handler, void *handler_ctx)
{
if (handler->pair != nullptr) {
- const SplitString split(comment, '=');
+ const DivideString split(comment, '=');
if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
split.GetFirst(),
diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx
index 67859bbd2..0b41d052e 100644
--- a/src/decoder/plugins/WavpackDecoderPlugin.cxx
+++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx
@@ -28,10 +28,10 @@
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
+#include "util/Alloc.hxx"
#include "Log.hxx"
#include <wavpack/wavpack.h>
-#include <glib.h>
#include <assert.h>
#include <stdio.h>
@@ -484,10 +484,10 @@ wavpack_open_wvc(Decoder &decoder, const char *uri)
if (uri == nullptr)
return nullptr;
- char *wvc_url = g_strconcat(uri, "c", nullptr);
+ char *wvc_url = xstrcatdup(uri, "c");
InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError());
- g_free(wvc_url);
+ free(wvc_url);
if (is_wvc == nullptr)
return nullptr;
diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx
index 11a0bcd42..db5f6c5e1 100644
--- a/src/decoder/plugins/XiphTags.cxx
+++ b/src/decoder/plugins/XiphTags.cxx
@@ -27,7 +27,6 @@
const struct tag_table xiph_tags[] = {
{ "tracknumber", TAG_TRACK },
{ "discnumber", TAG_DISC },
- { "album artist", TAG_ALBUM_ARTIST },
{ "description", TAG_COMMENT },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};
diff --git a/src/encoder/EncoderList.cxx b/src/encoder/EncoderList.cxx
index 4bca5a4fe..79c045328 100644
--- a/src/encoder/EncoderList.cxx
+++ b/src/encoder/EncoderList.cxx
@@ -33,16 +33,16 @@
const EncoderPlugin *const encoder_plugins[] = {
&null_encoder_plugin,
-#ifdef ENABLE_VORBIS_ENCODER
+#ifdef ENABLE_VORBISENC
&vorbis_encoder_plugin,
#endif
-#ifdef HAVE_OPUS
+#ifdef ENABLE_OPUS
&opus_encoder_plugin,
#endif
-#ifdef ENABLE_LAME_ENCODER
+#ifdef ENABLE_LAME
&lame_encoder_plugin,
#endif
-#ifdef ENABLE_TWOLAME_ENCODER
+#ifdef ENABLE_TWOLAME
&twolame_encoder_plugin,
#endif
#ifdef ENABLE_WAVE_ENCODER
@@ -51,7 +51,7 @@ const EncoderPlugin *const encoder_plugins[] = {
#ifdef ENABLE_FLAC_ENCODER
&flac_encoder_plugin,
#endif
-#ifdef ENABLE_SHINE_ENCODER
+#ifdef ENABLE_SHINE
&shine_encoder_plugin,
#endif
nullptr
diff --git a/src/encoder/plugins/VorbisEncoderPlugin.cxx b/src/encoder/plugins/VorbisEncoderPlugin.cxx
index ecc784a47..01c9910a0 100644
--- a/src/encoder/plugins/VorbisEncoderPlugin.cxx
+++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx
@@ -25,14 +25,13 @@
#include "tag/Tag.hxx"
#include "AudioFormat.hxx"
#include "config/ConfigError.hxx"
+#include "util/StringUtil.hxx"
#include "util/NumberParser.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include <vorbis/vorbisenc.h>
-#include <glib.h>
-
struct vorbis_encoder {
/** the base class */
Encoder encoder;
@@ -58,7 +57,7 @@ struct vorbis_encoder {
static constexpr Domain vorbis_encoder_domain("vorbis_encoder");
static bool
-vorbis_encoder_configure(struct vorbis_encoder *encoder,
+vorbis_encoder_configure(struct vorbis_encoder &encoder,
const config_param &param, Error &error)
{
const char *value = param.GetBlockValue("quality");
@@ -66,10 +65,10 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder,
/* a quality was configured (VBR) */
char *endptr;
- encoder->quality = ParseDouble(value, &endptr);
+ encoder.quality = ParseDouble(value, &endptr);
- if (*endptr != '\0' || encoder->quality < -1.0 ||
- encoder->quality > 10.0) {
+ if (*endptr != '\0' || encoder.quality < -1.0 ||
+ encoder.quality > 10.0) {
error.Format(config_domain,
"quality \"%s\" is not a number in the "
"range -1 to 10",
@@ -92,11 +91,11 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder,
return false;
}
- encoder->quality = -2.0;
+ encoder.quality = -2.0;
char *endptr;
- encoder->bitrate = ParseInt(value, &endptr);
- if (*endptr != '\0' || encoder->bitrate <= 0) {
+ encoder.bitrate = ParseInt(value, &endptr);
+ if (*endptr != '\0' || encoder.bitrate <= 0) {
error.Set(config_domain,
"bitrate should be a positive integer");
return false;
@@ -112,7 +111,7 @@ vorbis_encoder_init(const config_param &param, Error &error)
vorbis_encoder *encoder = new vorbis_encoder();
/* load configuration from "param" */
- if (!vorbis_encoder_configure(encoder, param, error)) {
+ if (!vorbis_encoder_configure(*encoder, param, error)) {
/* configuration has failed, roll back and return error */
delete encoder;
return nullptr;
@@ -132,63 +131,63 @@ vorbis_encoder_finish(Encoder *_encoder)
}
static bool
-vorbis_encoder_reinit(struct vorbis_encoder *encoder, Error &error)
+vorbis_encoder_reinit(struct vorbis_encoder &encoder, Error &error)
{
- vorbis_info_init(&encoder->vi);
+ vorbis_info_init(&encoder.vi);
- if (encoder->quality >= -1.0) {
+ if (encoder.quality >= -1.0) {
/* a quality was configured (VBR) */
- if (0 != vorbis_encode_init_vbr(&encoder->vi,
- encoder->audio_format.channels,
- encoder->audio_format.sample_rate,
- encoder->quality * 0.1)) {
+ if (0 != vorbis_encode_init_vbr(&encoder.vi,
+ encoder.audio_format.channels,
+ encoder.audio_format.sample_rate,
+ encoder.quality * 0.1)) {
error.Set(vorbis_encoder_domain,
"error initializing vorbis vbr");
- vorbis_info_clear(&encoder->vi);
+ vorbis_info_clear(&encoder.vi);
return false;
}
} else {
/* a bit rate was configured */
- if (0 != vorbis_encode_init(&encoder->vi,
- encoder->audio_format.channels,
- encoder->audio_format.sample_rate, -1.0,
- encoder->bitrate * 1000, -1.0)) {
+ if (0 != vorbis_encode_init(&encoder.vi,
+ encoder.audio_format.channels,
+ encoder.audio_format.sample_rate, -1.0,
+ encoder.bitrate * 1000, -1.0)) {
error.Set(vorbis_encoder_domain,
"error initializing vorbis encoder");
- vorbis_info_clear(&encoder->vi);
+ vorbis_info_clear(&encoder.vi);
return false;
}
}
- vorbis_analysis_init(&encoder->vd, &encoder->vi);
- vorbis_block_init(&encoder->vd, &encoder->vb);
- encoder->stream.Initialize(GenerateOggSerial());
+ vorbis_analysis_init(&encoder.vd, &encoder.vi);
+ vorbis_block_init(&encoder.vd, &encoder.vb);
+ encoder.stream.Initialize(GenerateOggSerial());
return true;
}
static void
-vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc)
+vorbis_encoder_headerout(struct vorbis_encoder &encoder, vorbis_comment &vc)
{
ogg_packet packet, comments, codebooks;
- vorbis_analysis_headerout(&encoder->vd, vc,
+ vorbis_analysis_headerout(&encoder.vd, &vc,
&packet, &comments, &codebooks);
- encoder->stream.PacketIn(packet);
- encoder->stream.PacketIn(comments);
- encoder->stream.PacketIn(codebooks);
+ encoder.stream.PacketIn(packet);
+ encoder.stream.PacketIn(comments);
+ encoder.stream.PacketIn(codebooks);
}
static void
-vorbis_encoder_send_header(struct vorbis_encoder *encoder)
+vorbis_encoder_send_header(struct vorbis_encoder &encoder)
{
vorbis_comment vc;
vorbis_comment_init(&vc);
- vorbis_encoder_headerout(encoder, &vc);
+ vorbis_encoder_headerout(encoder, vc);
vorbis_comment_clear(&vc);
}
@@ -197,11 +196,11 @@ vorbis_encoder_open(Encoder *_encoder,
AudioFormat &audio_format,
Error &error)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
audio_format.format = SampleFormat::FLOAT;
- encoder->audio_format = audio_format;
+ encoder.audio_format = audio_format;
if (!vorbis_encoder_reinit(encoder, error))
return false;
@@ -212,70 +211,70 @@ vorbis_encoder_open(Encoder *_encoder,
}
static void
-vorbis_encoder_clear(struct vorbis_encoder *encoder)
+vorbis_encoder_clear(struct vorbis_encoder &encoder)
{
- encoder->stream.Deinitialize();
- vorbis_block_clear(&encoder->vb);
- vorbis_dsp_clear(&encoder->vd);
- vorbis_info_clear(&encoder->vi);
+ encoder.stream.Deinitialize();
+ vorbis_block_clear(&encoder.vb);
+ vorbis_dsp_clear(&encoder.vd);
+ vorbis_info_clear(&encoder.vi);
}
static void
vorbis_encoder_close(Encoder *_encoder)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
vorbis_encoder_clear(encoder);
}
static void
-vorbis_encoder_blockout(struct vorbis_encoder *encoder)
+vorbis_encoder_blockout(struct vorbis_encoder &encoder)
{
- while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) {
- vorbis_analysis(&encoder->vb, nullptr);
- vorbis_bitrate_addblock(&encoder->vb);
+ while (vorbis_analysis_blockout(&encoder.vd, &encoder.vb) == 1) {
+ vorbis_analysis(&encoder.vb, nullptr);
+ vorbis_bitrate_addblock(&encoder.vb);
ogg_packet packet;
- while (vorbis_bitrate_flushpacket(&encoder->vd, &packet))
- encoder->stream.PacketIn(packet);
+ while (vorbis_bitrate_flushpacket(&encoder.vd, &packet))
+ encoder.stream.PacketIn(packet);
}
}
static bool
vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
- encoder->stream.Flush();
+ encoder.stream.Flush();
return true;
}
static bool
vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
- vorbis_analysis_wrote(&encoder->vd, 0);
+ vorbis_analysis_wrote(&encoder.vd, 0);
vorbis_encoder_blockout(encoder);
/* reinitialize vorbis_dsp_state and vorbis_block to reset the
end-of-stream marker */
- vorbis_block_clear(&encoder->vb);
- vorbis_dsp_clear(&encoder->vd);
- vorbis_analysis_init(&encoder->vd, &encoder->vi);
- vorbis_block_init(&encoder->vd, &encoder->vb);
+ vorbis_block_clear(&encoder.vb);
+ vorbis_dsp_clear(&encoder.vd);
+ vorbis_analysis_init(&encoder.vd, &encoder.vi);
+ vorbis_block_init(&encoder.vd, &encoder.vb);
- encoder->stream.Flush();
+ encoder.stream.Flush();
return true;
}
static void
-copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag *tag)
+copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag &tag)
{
- for (const auto &item : *tag) {
- char *name = g_ascii_strup(tag_item_names[item.type], -1);
+ for (const auto &item : tag) {
+ char name[64];
+ ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
vorbis_comment_add_tag(vc, name, item.value);
- g_free(name);
}
}
@@ -283,21 +282,21 @@ static bool
vorbis_encoder_tag(Encoder *_encoder, const Tag *tag,
gcc_unused Error &error)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
vorbis_comment comment;
/* write the vorbis_comment object */
vorbis_comment_init(&comment);
- copy_tag_to_vorbis_comment(&comment, tag);
+ copy_tag_to_vorbis_comment(&comment, *tag);
/* reset ogg_stream_state and begin a new stream */
- encoder->stream.Reinitialize(GenerateOggSerial());
+ encoder.stream.Reinitialize(GenerateOggSerial());
/* send that vorbis_comment to the ogg_stream_state */
- vorbis_encoder_headerout(encoder, &comment);
+ vorbis_encoder_headerout(encoder, comment);
vorbis_comment_clear(&comment);
return true;
@@ -317,19 +316,19 @@ vorbis_encoder_write(Encoder *_encoder,
const void *data, size_t length,
gcc_unused Error &error)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
- unsigned num_frames = length / encoder->audio_format.GetFrameSize();
+ unsigned num_frames = length / encoder.audio_format.GetFrameSize();
/* this is for only 16-bit audio */
- interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd,
+ interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder.vd,
num_frames),
(const float *)data,
num_frames,
- encoder->audio_format.channels);
+ encoder.audio_format.channels);
- vorbis_analysis_wrote(&encoder->vd, num_frames);
+ vorbis_analysis_wrote(&encoder.vd, num_frames);
vorbis_encoder_blockout(encoder);
return true;
}
@@ -337,9 +336,9 @@ vorbis_encoder_write(Encoder *_encoder,
static size_t
vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length)
{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+ struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
- return encoder->stream.PageOut(dest, length);
+ return encoder.stream.PageOut(dest, length);
}
static const char *
diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx
index 313f0a6cf..0ea1d7179 100644
--- a/src/event/ServerSocket.cxx
+++ b/src/event/ServerSocket.cxx
@@ -198,7 +198,7 @@ OneServerSocket::Open(Error &error)
if (!path.IsNull())
chmod(path.c_str(), 0666);
- /* register in the GLib main loop */
+ /* register in the EventLoop */
SetFD(_fd);
diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx
index ceaad73ea..bd026db74 100644
--- a/src/fs/AllocatedPath.cxx
+++ b/src/fs/AllocatedPath.cxx
@@ -24,33 +24,14 @@
#include "util/Error.hxx"
#include "Compiler.h"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
-#include <string.h>
-
-#ifdef HAVE_GLIB
-
-inline AllocatedPath::AllocatedPath(Donate, pointer _value)
- :value(_value) {
- g_free(_value);
-}
-
-#endif
-
/* no inlining, please */
AllocatedPath::~AllocatedPath() {}
AllocatedPath
AllocatedPath::FromUTF8(const char *path_utf8)
{
-#ifdef HAVE_GLIB
- char *path = ::PathFromUTF8(path_utf8);
- if (path == nullptr)
- return AllocatedPath::Null();
-
- return AllocatedPath(Donate(), path);
+#ifdef HAVE_FS_CHARSET
+ return AllocatedPath(::PathFromUTF8(path_utf8));
#else
return FromFS(path_utf8);
#endif
@@ -111,7 +92,7 @@ AllocatedPath::ChopSeparators()
while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) {
--l;
-#if GCC_CHECK_VERSION(4,7) && !defined(__clang__)
+#if GCC_CHECK_VERSION(4,7)
value.pop_back();
#else
value.erase(value.end() - 1, value.end());
diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx
index c345470c8..73b6891ad 100644
--- a/src/fs/AllocatedPath.hxx
+++ b/src/fs/AllocatedPath.hxx
@@ -44,13 +44,6 @@ class AllocatedPath {
string value;
- struct Donate {};
-
- /**
- * Donate the allocated pointer to a new #AllocatedPath object.
- */
- AllocatedPath(Donate, pointer _value);
-
AllocatedPath(const_pointer _value):value(_value) {}
AllocatedPath(string &&_value):value(std::move(_value)) {}
diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx
index c634c9340..4d562b59f 100644
--- a/src/fs/Charset.cxx
+++ b/src/fs/Charset.cxx
@@ -21,66 +21,52 @@
#include "Charset.hxx"
#include "Domain.hxx"
#include "Limits.hxx"
-#include "system/FatalError.hxx"
#include "Log.hxx"
#include "Traits.hxx"
-
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
+#include "lib/icu/Converter.hxx"
+#include "util/Error.hxx"
#include <algorithm>
#include <assert.h>
#include <string.h>
-#ifdef HAVE_GLIB
-
-/**
- * Maximal number of bytes required to represent path name in UTF-8
- * (including nul-terminator).
- * This value is a rought estimate of upper bound.
- * It's based on path name limit in bytes (MPD_PATH_MAX)
- * and assumption that some weird encoding could represent some UTF-8 4 byte
- * sequences with single byte.
- */
-static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1;
+#ifdef HAVE_FS_CHARSET
static std::string fs_charset;
-gcc_pure
-static bool
-IsSupportedCharset(const char *charset)
-{
- /* convert a space to check if the charset is valid */
- char *test = g_convert(" ", 1, charset, "UTF-8", nullptr, nullptr, nullptr);
- if (test == nullptr)
- return false;
-
- g_free(test);
- return true;
-}
+static IcuConverter *fs_converter;
-void
-SetFSCharset(const char *charset)
+bool
+SetFSCharset(const char *charset, Error &error)
{
assert(charset != nullptr);
+ assert(fs_converter == nullptr);
- if (!IsSupportedCharset(charset))
- FormatFatalError("invalid filesystem charset: %s", charset);
-
- fs_charset = charset;
+ fs_converter = IcuConverter::Create(charset, error);
+ if (fs_converter == nullptr)
+ return false;
FormatDebug(path_domain,
"SetFSCharset: fs charset is: %s", fs_charset.c_str());
+ return true;
}
#endif
+void
+DeinitFSCharset()
+{
+#ifdef HAVE_ICU_CONVERTER
+ delete fs_converter;
+ fs_converter = nullptr;
+#endif
+}
+
const char *
GetFSCharset()
{
-#ifdef HAVE_GLIB
+#ifdef HAVE_FS_CHARSET
return fs_charset.empty() ? "UTF-8" : fs_charset.c_str();
#else
return "UTF-8";
@@ -108,43 +94,24 @@ PathToUTF8(const char *path_fs)
assert(path_fs != nullptr);
#endif
-#ifdef HAVE_GLIB
- if (fs_charset.empty()) {
+#ifdef HAVE_FS_CHARSET
+ if (fs_converter == nullptr) {
#endif
auto result = std::string(path_fs);
FixSeparators(result);
return result;
-#ifdef HAVE_GLIB
+#ifdef HAVE_FS_CHARSET
}
- GIConv conv = g_iconv_open("utf-8", fs_charset.c_str());
- if (conv == reinterpret_cast<GIConv>(-1))
- return std::string();
-
- // g_iconv() does not need nul-terminator,
- // std::string could be created without it too.
- char path_utf8[MPD_PATH_MAX_UTF8 - 1];
- char *in = const_cast<char *>(path_fs);
- char *out = path_utf8;
- size_t in_left = strlen(path_fs);
- size_t out_left = sizeof(path_utf8);
-
- size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left);
-
- g_iconv_close(conv);
-
- if (ret == static_cast<size_t>(-1) || in_left > 0)
- return std::string();
-
- auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left);
+ auto result_path = fs_converter->ToUTF8(path_fs);
FixSeparators(result_path);
return result_path;
#endif
}
-#ifdef HAVE_GLIB
+#ifdef HAVE_FS_CHARSET
-char *
+std::string
PathFromUTF8(const char *path_utf8)
{
#if !CLANG_CHECK_VERSION(3,6)
@@ -152,12 +119,10 @@ PathFromUTF8(const char *path_utf8)
assert(path_utf8 != nullptr);
#endif
- if (fs_charset.empty())
- return g_strdup(path_utf8);
+ if (fs_converter == nullptr)
+ return path_utf8;
- return g_convert(path_utf8, -1,
- fs_charset.c_str(), "utf-8",
- nullptr, nullptr, nullptr);
+ return fs_converter->FromUTF8(path_utf8);
}
#endif
diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx
index 0a71d7c58..f1d5f3bbf 100644
--- a/src/fs/Charset.hxx
+++ b/src/fs/Charset.hxx
@@ -25,6 +25,12 @@
#include <string>
+#if defined(HAVE_ICU) || defined(HAVE_GLIB)
+#define HAVE_FS_CHARSET
+#endif
+
+class Error;
+
/**
* Gets file system character set name.
*/
@@ -32,8 +38,11 @@ gcc_const
const char *
GetFSCharset();
+bool
+SetFSCharset(const char *charset, Error &error);
+
void
-SetFSCharset(const char *charset);
+DeinitFSCharset();
/**
* Convert the path to UTF-8.
@@ -43,8 +52,12 @@ gcc_pure gcc_nonnull_all
std::string
PathToUTF8(const char *path_fs);
-gcc_malloc gcc_nonnull_all
-char *
+/**
+ * Convert the path from UTF-8.
+ * Returns empty string on error.
+ */
+gcc_pure gcc_nonnull_all
+std::string
PathFromUTF8(const char *path_utf8);
#endif
diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx
index 6aa23005c..7b0da061a 100644
--- a/src/fs/Config.cxx
+++ b/src/fs/Config.cxx
@@ -29,21 +29,15 @@
#include <glib.h>
#endif
-void
-ConfigureFS()
+bool
+ConfigureFS(Error &error)
{
-#if defined(HAVE_GLIB) || defined(WIN32)
+#ifdef HAVE_FS_CHARSET
const char *charset = nullptr;
charset = config_get_string(CONF_FS_CHARSET, nullptr);
if (charset == nullptr) {
-#ifndef WIN32
- const gchar **encodings;
- g_get_filename_charsets(&encodings);
-
- if (encodings[0] != nullptr && *encodings[0] != '\0')
- charset = encodings[0];
-#else
+#ifdef WIN32
/* Glib claims that file system encoding is always utf-8
* on native Win32 (i.e. not Cygwin).
* However this is true only if <gstdio.h> helpers are used.
@@ -52,10 +46,26 @@ ConfigureFS()
static char win_charset[13];
sprintf(win_charset, "cp%u", GetACP());
charset = win_charset;
+#elif defined(HAVE_GLIB)
+ const gchar **encodings;
+ g_get_filename_charsets(&encodings);
+
+ if (encodings[0] != nullptr && *encodings[0] != '\0')
+ charset = encodings[0];
#endif
}
- if (charset != nullptr)
- SetFSCharset(charset);
+ return charset == nullptr || SetFSCharset(charset, error);
+#else
+ (void)error;
+ return true;
+#endif
+}
+
+void
+DeinitFS()
+{
+#ifdef HAVE_FS_CHARSET
+ DeinitFSCharset();
#endif
}
diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx
index d4f1709f5..403c07685 100644
--- a/src/fs/Config.hxx
+++ b/src/fs/Config.hxx
@@ -22,10 +22,15 @@
#include "check.h"
+class Error;
+
/**
* Performs global one-time initialization of this class.
*/
+bool
+ConfigureFS(Error &error);
+
void
-ConfigureFS();
+DeinitFS();
#endif
diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx
index 9e0fa5aeb..4a3ae815f 100644
--- a/src/fs/Path.hxx
+++ b/src/fs/Path.hxx
@@ -29,6 +29,8 @@
#include <assert.h>
#include <string.h>
+class AllocatedPath;
+
/**
* A path name in the native file system character set.
*
@@ -129,6 +131,22 @@ public:
std::string ToUTF8() const;
/**
+ * Determine the "base" file name.
+ * The return value points inside this object.
+ */
+ gcc_pure
+ Path GetBase() const {
+ return FromFS(PathTraitsFS::GetBase(value));
+ }
+
+ /**
+ * Gets directory name of this path.
+ * Returns a "nulled" instance on error.
+ */
+ gcc_pure
+ AllocatedPath GetDirectoryName() const;
+
+ /**
* Determine the relative part of the given path to this
* object, not including the directory separator. Returns an
* empty string if the given path equals this object or
diff --git a/src/fs/Path2.cxx b/src/fs/Path2.cxx
new file mode 100644
index 000000000..966e34dad
--- /dev/null
+++ b/src/fs/Path2.cxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 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 "Path.hxx"
+#include "AllocatedPath.hxx"
+
+AllocatedPath
+Path::GetDirectoryName() const
+{
+ return AllocatedPath::FromFS(PathTraitsFS::GetParent(c_str()));
+}
diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx
index 28d6dabcb..1710e0e89 100644
--- a/src/fs/io/TextFile.cxx
+++ b/src/fs/io/TextFile.cxx
@@ -28,14 +28,14 @@
TextFile::TextFile(Path path_fs, Error &error)
:file_reader(new FileReader(path_fs, error)),
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
gunzip_reader(file_reader->IsDefined()
? new AutoGunzipReader(*file_reader)
: nullptr),
#endif
buffered_reader(file_reader->IsDefined()
? new BufferedReader(*
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
gunzip_reader
#else
file_reader
@@ -48,7 +48,7 @@ TextFile::TextFile(Path path_fs, Error &error)
TextFile::~TextFile()
{
delete buffered_reader;
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
delete gunzip_reader;
#endif
delete file_reader;
diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx
index 5577363e7..425797ce7 100644
--- a/src/fs/io/TextFile.hxx
+++ b/src/fs/io/TextFile.hxx
@@ -34,7 +34,7 @@ class BufferedReader;
class TextFile {
FileReader *const file_reader;
-#ifdef HAVE_ZLIB
+#ifdef ENABLE_ZLIB
AutoGunzipReader *const gunzip_reader;
#endif
diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx
index d1f0c3b9d..b717087e7 100644
--- a/src/input/AsyncInputStream.hxx
+++ b/src/input/AsyncInputStream.hxx
@@ -62,6 +62,10 @@ protected:
Error postponed_error;
public:
+ /**
+ * @param _buffer a buffer allocated with HugeAllocate(); the
+ * destructor will free it using HugeFree()
+ */
AsyncInputStream(const char *_url,
Mutex &_mutex, Cond &_cond,
void *_buffer, size_t _buffer_size,
diff --git a/src/input/Registry.cxx b/src/input/Registry.cxx
index 2b981df1c..6be3233e4 100644
--- a/src/input/Registry.cxx
+++ b/src/input/Registry.cxx
@@ -22,7 +22,7 @@
#include "util/Macros.hxx"
#include "plugins/FileInputPlugin.hxx"
-#ifdef HAVE_ALSA
+#ifdef ENABLE_ALSA
#include "plugins/AlsaInputPlugin.hxx"
#endif
@@ -34,7 +34,7 @@
#include "plugins/CurlInputPlugin.hxx"
#endif
-#ifdef HAVE_FFMPEG
+#ifdef ENABLE_FFMPEG
#include "plugins/FfmpegInputPlugin.hxx"
#endif
@@ -60,7 +60,7 @@
const InputPlugin *const input_plugins[] = {
&input_plugin_file,
-#ifdef HAVE_ALSA
+#ifdef ENABLE_ALSA
&input_plugin_alsa,
#endif
#ifdef ENABLE_ARCHIVE
@@ -69,7 +69,7 @@ const InputPlugin *const input_plugins[] = {
#ifdef ENABLE_CURL
&input_plugin_curl,
#endif
-#ifdef HAVE_FFMPEG
+#ifdef ENABLE_FFMPEG
&input_plugin_ffmpeg,
#endif
#ifdef ENABLE_SMBCLIENT
diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx
index f847b35c1..2b45a34aa 100644
--- a/src/input/plugins/CdioParanoiaInputPlugin.cxx
+++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx
@@ -39,7 +39,6 @@
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
-#include <glib.h>
#include <assert.h>
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
@@ -149,7 +148,7 @@ parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error)
const char *slash = strrchr(src, '/');
if (slash == nullptr) {
/* play the whole CD in the specified drive */
- g_strlcpy(dest->device, src, sizeof(dest->device));
+ CopyString(dest->device, src, sizeof(dest->device));
dest->track = -1;
return true;
}
diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx
index 669f8d403..701beb313 100644
--- a/src/input/plugins/FfmpegInputPlugin.cxx
+++ b/src/input/plugins/FfmpegInputPlugin.cxx
@@ -22,6 +22,7 @@
#include "config.h"
#include "FfmpegInputPlugin.hxx"
+#include "lib/ffmpeg/Init.hxx"
#include "lib/ffmpeg/Domain.hxx"
#include "lib/ffmpeg/Error.hxx"
#include "../InputStream.hxx"
@@ -31,7 +32,6 @@
extern "C" {
#include <libavformat/avio.h>
-#include <libavformat/avformat.h>
}
struct FfmpegInputStream final : public InputStream {
@@ -75,7 +75,7 @@ static InputPlugin::InitResult
input_ffmpeg_init(gcc_unused const config_param &param,
Error &error)
{
- av_register_all();
+ FfmpegInit();
/* disable this plugin if there's no registered protocol */
if (!input_ffmpeg_supported()) {
diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx
index c6c0970b9..c1f7a48ce 100644
--- a/src/input/plugins/NfsInputPlugin.cxx
+++ b/src/input/plugins/NfsInputPlugin.cxx
@@ -28,10 +28,6 @@
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
-extern "C" {
-#include <nfsc/libnfs.h>
-}
-
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
diff --git a/src/lib/despotify/DespotifyUtils.cxx b/src/lib/despotify/DespotifyUtils.cxx
index f67679c50..aae16000c 100644
--- a/src/lib/despotify/DespotifyUtils.cxx
+++ b/src/lib/despotify/DespotifyUtils.cxx
@@ -23,6 +23,7 @@
#include "config/ConfigGlobal.hxx"
#include "config/ConfigOption.hxx"
#include "util/Domain.hxx"
+#include "util/Macros.hxx"
#include "Log.hxx"
extern "C" {
@@ -42,24 +43,21 @@ static void
callback(struct despotify_session* ds, int sig,
void *data, gcc_unused void *callback_data)
{
- size_t i;
-
- for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(registered_callbacks); ++i) {
void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
void *cb_data = registered_callback_data[i];
- if (cb)
+ if (cb != nullptr)
cb(ds, sig, data, cb_data);
}
}
-bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
- void *cb_data)
+bool
+mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int,
+ void *, void *),
+ void *cb_data)
{
- size_t i;
-
- for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
-
+ for (size_t i = 0; i < ARRAY_SIZE(registered_callbacks); ++i) {
if (!registered_callbacks[i]) {
registered_callbacks[i] = cb;
registered_callback_data[i] = cb_data;
@@ -71,12 +69,11 @@ bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int,
return false;
}
-void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
+void
+mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int,
+ void *, void *))
{
- size_t i;
-
- for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
-
+ for (size_t i = 0; i < ARRAY_SIZE(registered_callbacks); ++i) {
if (registered_callbacks[i] == cb) {
registered_callbacks[i] = nullptr;
}
@@ -86,42 +83,50 @@ void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, in
Tag
mpd_despotify_tag_from_track(const ds_track &track)
{
- char tracknum[20];
- char comment[80];
- char date[20];
-
if (!track.has_meta_data)
return Tag();
TagBuilder tag;
- snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber);
- snprintf(date, sizeof(date), "%d", track.year);
- snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
- track.file_bitrate / 1000,
- track.geo_restricted ? "" : "not ");
+
+ {
+ char tracknum[20];
+ snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber);
+ tag.AddItem(TAG_TRACK, tracknum);
+ }
+
+ {
+ char date[20];
+ snprintf(date, sizeof(date), "%d", track.year);
+ tag.AddItem(TAG_DATE, date);
+ }
+
+ {
+ char comment[80];
+ snprintf(comment, sizeof(comment),
+ "Bitrate %d Kbps, %sgeo restricted",
+ track.file_bitrate / 1000,
+ track.geo_restricted ? "" : "not ");
+ tag.AddItem(TAG_COMMENT, comment);
+ }
+
tag.AddItem(TAG_TITLE, track.title);
tag.AddItem(TAG_ARTIST, track.artist->name);
- tag.AddItem(TAG_TRACK, tracknum);
tag.AddItem(TAG_ALBUM, track.album);
- tag.AddItem(TAG_DATE, date);
- tag.AddItem(TAG_COMMENT, comment);
tag.SetDuration(SignedSongTime::FromMS(track.length));
return tag.Commit();
}
-struct despotify_session *mpd_despotify_get_session(void)
+struct despotify_session *
+mpd_despotify_get_session()
{
- const char *user;
- const char *passwd;
- bool high_bitrate;
-
if (g_session)
return g_session;
- user = config_get_string(CONF_DESPOTIFY_USER, nullptr);
- passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
- high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
+ const char *const user =
+ config_get_string(CONF_DESPOTIFY_USER, nullptr);
+ const char *const passwd =
+ config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
if (user == nullptr || passwd == nullptr) {
LogDebug(despotify_domain,
@@ -134,6 +139,8 @@ struct despotify_session *mpd_despotify_get_session(void)
return nullptr;
}
+ const bool high_bitrate =
+ config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
g_session = despotify_init_client(callback, nullptr,
high_bitrate, true);
if (!g_session) {
diff --git a/src/lib/despotify/DespotifyUtils.hxx b/src/lib/despotify/DespotifyUtils.hxx
index 835b901a2..a21632bb8 100644
--- a/src/lib/despotify/DespotifyUtils.hxx
+++ b/src/lib/despotify/DespotifyUtils.hxx
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_DESPOTIFY_H
-#define MPD_DESPOTIFY_H
+#ifndef MPD_DESPOTIFY_UTILS_HXX
+#define MPD_DESPOTIFY_UTILS_HXX
struct Tag;
struct despotify_session;
@@ -35,7 +35,8 @@ extern const class Domain despotify_domain;
* @return a pointer to the despotify session, or nullptr if it can't
* be initialized (e.g., if the configuration isn't supplied)
*/
-struct despotify_session *mpd_despotify_get_session(void);
+struct despotify_session *
+mpd_despotify_get_session();
/**
* Create a MPD tags structure from a spotify track
@@ -57,15 +58,19 @@ mpd_despotify_tag_from_track(const ds_track &track);
*
* @return true if the callback could be registered
*/
-bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
- void *cb_data);
+bool
+mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int,
+ void *, void *),
+ void *cb_data);
/**
* Unregister a despotify callback.
*
* @param cb the callback to unregister.
*/
-void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
+void
+mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int,
+ void *, void *));
#endif
diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx
new file mode 100644
index 000000000..50a702f59
--- /dev/null
+++ b/src/lib/ffmpeg/Buffer.hxx
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_BUFFER_HXX
+#define MPD_FFMPEG_BUFFER_HXX
+
+extern "C" {
+#include <libavutil/mem.h>
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 18, 0)
+#define HAVE_AV_FAST_MALLOC
+#else
+#include <libavcodec/avcodec.h>
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 25, 0)
+#define HAVE_AV_FAST_MALLOC
+#endif
+#endif
+}
+
+#include <stddef.h>
+
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
+class FfmpegBuffer {
+ void *data;
+ unsigned size;
+
+public:
+ FfmpegBuffer():data(nullptr), size(0) {}
+
+ ~FfmpegBuffer() {
+ av_free(data);
+ }
+
+ void *Get(size_t min_size) {
+#ifdef HAVE_AV_FAST_MALLOC
+ av_fast_malloc(&data, &size, min_size);
+#else
+ void *new_data = av_fast_realloc(data, &size, min_size);
+ if (new_data == nullptr)
+ return AVERROR(ENOMEM);
+ data = new_data;
+#endif
+ return data;
+ }
+
+ template<typename T>
+ T *GetT(size_t n) {
+ return (T *)Get(n * sizeof(T));
+ }
+};
+
+#endif
diff --git a/src/lib/ffmpeg/Init.cxx b/src/lib/ffmpeg/Init.cxx
new file mode 100644
index 000000000..24f4ab238
--- /dev/null
+++ b/src/lib/ffmpeg/Init.cxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "Init.hxx"
+#include "LogCallback.hxx"
+
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+void
+FfmpegInit()
+{
+ av_log_set_callback(FfmpegLogCallback);
+
+ av_register_all();
+}
+
diff --git a/src/lib/ffmpeg/Init.hxx b/src/lib/ffmpeg/Init.hxx
new file mode 100644
index 000000000..bcc4805a9
--- /dev/null
+++ b/src/lib/ffmpeg/Init.hxx
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_INIT_HXX
+#define MPD_FFMPEG_INIT_HXX
+
+void
+FfmpegInit();
+
+#endif
diff --git a/src/lib/ffmpeg/LogCallback.cxx b/src/lib/ffmpeg/LogCallback.cxx
new file mode 100644
index 000000000..799ba2f34
--- /dev/null
+++ b/src/lib/ffmpeg/LogCallback.cxx
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003-2014 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "LogCallback.hxx"
+#include "Domain.hxx"
+#include "LogV.hxx"
+#include "util/Domain.hxx"
+
+extern "C" {
+#include <libavutil/log.h>
+}
+
+#include <stdio.h>
+
+gcc_const
+static LogLevel
+FfmpegImportLogLevel(int level)
+{
+ if (level <= AV_LOG_FATAL)
+ return LogLevel::ERROR;
+
+ if (level <= AV_LOG_WARNING)
+ return LogLevel::WARNING;
+
+ if (level <= AV_LOG_INFO)
+ return LogLevel::INFO;
+
+ return LogLevel::DEBUG;
+}
+
+void
+FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
+{
+ const AVClass * cls = nullptr;
+
+ if (ptr != nullptr)
+ cls = *(const AVClass *const*)ptr;
+
+ if (cls != nullptr) {
+ char domain[64];
+ snprintf(domain, sizeof(domain), "%s/%s",
+ ffmpeg_domain.GetName(), cls->item_name(ptr));
+ const Domain d(domain);
+ LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
+ }
+}
diff --git a/src/lib/ffmpeg/LogCallback.hxx b/src/lib/ffmpeg/LogCallback.hxx
new file mode 100644
index 000000000..cb4b2ccf5
--- /dev/null
+++ b/src/lib/ffmpeg/LogCallback.hxx
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_LOG_CALLBACK_HXX
+#define MPD_FFMPEG_LOG_CALLBACK_HXX
+
+#include "check.h"
+
+#include <stdarg.h>
+
+void
+FfmpegLogCallback(void *ptr, int level, const char *fmt, va_list vl);
+
+#endif
diff --git a/src/lib/ffmpeg/LogError.cxx b/src/lib/ffmpeg/LogError.cxx
new file mode 100644
index 000000000..da761f35a
--- /dev/null
+++ b/src/lib/ffmpeg/LogError.cxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 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 "LogError.hxx"
+#include "Domain.hxx"
+#include "Log.hxx"
+
+#include <cstdint> /* needed due to libavutil bug */
+
+extern "C" {
+#include <libavutil/error.h>
+}
+
+void
+LogFfmpegError(int errnum)
+{
+ char msg[256];
+ av_strerror(errnum, msg, sizeof(msg));
+ LogError(ffmpeg_domain, msg);
+}
+
+void
+LogFfmpegError(int errnum, const char *prefix)
+{
+ char msg[256];
+ av_strerror(errnum, msg, sizeof(msg));
+ FormatError(ffmpeg_domain, "%s: %s", prefix, msg);
+}
diff --git a/src/lib/ffmpeg/LogError.hxx b/src/lib/ffmpeg/LogError.hxx
new file mode 100644
index 000000000..ccafc6f94
--- /dev/null
+++ b/src/lib/ffmpeg/LogError.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_LOG_ERROR_HXX
+#define MPD_FFMPEG_LOG_ERROR_HXX
+
+void
+LogFfmpegError(int errnum);
+
+void
+LogFfmpegError(int errnum, const char *prefix);
+
+#endif
diff --git a/src/lib/ffmpeg/Time.hxx b/src/lib/ffmpeg/Time.hxx
new file mode 100644
index 000000000..7f2146016
--- /dev/null
+++ b/src/lib/ffmpeg/Time.hxx
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_TIME_HXX
+#define MPD_FFMPEG_TIME_HXX
+
+#include "Chrono.hxx"
+#include "Compiler.h"
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libavutil/mathematics.h>
+}
+
+#include <assert.h>
+#include <stdint.h>
+
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
+/**
+ * Convert a FFmpeg time stamp to a floating point value (in seconds).
+ */
+gcc_const
+static inline double
+FfmpegTimeToDouble(int64_t t, const AVRational time_base)
+{
+ assert(t != (int64_t)AV_NOPTS_VALUE);
+
+ return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
+ / (double)1024;
+}
+
+/**
+ * Convert a std::ratio to a #AVRational.
+ */
+template<typename Ratio>
+static inline constexpr AVRational
+RatioToAVRational()
+{
+ return { Ratio::num, Ratio::den };
+}
+
+/**
+ * Convert a FFmpeg time stamp to a #SongTime.
+ */
+gcc_const
+static inline SongTime
+FromFfmpegTime(int64_t t, const AVRational time_base)
+{
+ assert(t != (int64_t)AV_NOPTS_VALUE);
+
+ return SongTime::FromMS(av_rescale_q(t, time_base,
+ (AVRational){1, 1000}));
+}
+
+/**
+ * Convert a FFmpeg time stamp to a #SignedSongTime.
+ */
+gcc_const
+static inline SignedSongTime
+FromFfmpegTimeChecked(int64_t t, const AVRational time_base)
+{
+ return t != (int64_t)AV_NOPTS_VALUE
+ ? SignedSongTime(FromFfmpegTime(t, time_base))
+ : SignedSongTime::Negative();
+}
+
+/**
+ * Convert a #SongTime to a FFmpeg time stamp with the given base.
+ */
+gcc_const
+static inline int64_t
+ToFfmpegTime(SongTime t, const AVRational time_base)
+{
+ return av_rescale_q(t.count(),
+ RatioToAVRational<SongTime::period>(),
+ time_base);
+}
+
+/**
+ * Replace #AV_NOPTS_VALUE with the given fallback.
+ */
+static constexpr int64_t
+FfmpegTimestampFallback(int64_t t, int64_t fallback)
+{
+ return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
+ ? t
+ : fallback;
+}
+
+#endif
diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx
index 17b536b37..f2ffb7b74 100644
--- a/src/lib/icu/Collate.cxx
+++ b/src/lib/icu/Collate.cxx
@@ -21,6 +21,7 @@
#include "Collate.hxx"
#ifdef HAVE_ICU
+#include "Util.hxx"
#include "Error.hxx"
#include "util/WritableBuffer.hxx"
#include "util/ConstBuffer.hxx"
@@ -71,50 +72,6 @@ IcuCollateFinish()
ucol_close(collator);
}
-static WritableBuffer<UChar>
-UCharFromUTF8(const char *src)
-{
- assert(src != nullptr);
-
- const size_t src_length = strlen(src);
- const size_t dest_capacity = src_length;
- UChar *dest = new UChar[dest_capacity];
-
- UErrorCode error_code = U_ZERO_ERROR;
- int32_t dest_length;
- u_strFromUTF8(dest, dest_capacity, &dest_length,
- src, src_length,
- &error_code);
- if (U_FAILURE(error_code)) {
- delete[] dest;
- return nullptr;
- }
-
- return { dest, size_t(dest_length) };
-}
-
-static WritableBuffer<char>
-UCharToUTF8(ConstBuffer<UChar> src)
-{
- assert(!src.IsNull());
-
- /* worst-case estimate */
- size_t dest_capacity = 4 * src.size;
-
- char *dest = new char[dest_capacity];
-
- UErrorCode error_code = U_ZERO_ERROR;
- int32_t dest_length;
- u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size,
- &error_code);
- if (U_FAILURE(error_code)) {
- delete[] dest;
- return nullptr;
- }
-
- return { dest, size_t(dest_length) };
-}
-
#endif
gcc_pure
diff --git a/src/lib/icu/Converter.cxx b/src/lib/icu/Converter.cxx
new file mode 100644
index 000000000..bb170a071
--- /dev/null
+++ b/src/lib/icu/Converter.cxx
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2003-2014 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 "Converter.hxx"
+#include "Error.hxx"
+#include "util/Error.hxx"
+#include "util/Macros.hxx"
+#include "util/WritableBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <string.h>
+
+#ifdef HAVE_ICU
+#include "Util.hxx"
+#include <unicode/ucnv.h>
+#elif defined(HAVE_GLIB)
+#include "util/Domain.hxx"
+static constexpr Domain g_iconv_domain("g_iconv");
+#endif
+
+#ifdef HAVE_ICU
+
+IcuConverter::~IcuConverter()
+{
+ ucnv_close(converter);
+}
+
+#endif
+
+#ifdef HAVE_ICU_CONVERTER
+
+IcuConverter *
+IcuConverter::Create(const char *charset, Error &error)
+{
+#ifdef HAVE_ICU
+ UErrorCode code = U_ZERO_ERROR;
+ UConverter *converter = ucnv_open(charset, &code);
+ if (converter == nullptr) {
+ error.Format(icu_domain, int(code),
+ "Failed to initialize charset '%s': %s",
+ charset, u_errorName(code));
+ return nullptr;
+ }
+
+ return new IcuConverter(converter);
+#elif defined(HAVE_GLIB)
+ GIConv to = g_iconv_open("utf-8", charset);
+ GIConv from = g_iconv_open(charset, "utf-8");
+ if (to == (GIConv)-1 || from == (GIConv)-1) {
+ if (to != (GIConv)-1)
+ g_iconv_close(to);
+ if (from != (GIConv)-1)
+ g_iconv_close(from);
+ error.Format(g_iconv_domain,
+ "Failed to initialize charset '%s'", charset);
+ return nullptr;
+ }
+
+ return new IcuConverter(to, from);
+#endif
+}
+
+#ifdef HAVE_ICU
+#elif defined(HAVE_GLIB)
+
+static std::string
+DoConvert(GIConv conv, const char *src)
+{
+ // TODO: dynamic buffer?
+ char buffer[4096];
+ char *in = const_cast<char *>(src);
+ char *out = buffer;
+ size_t in_left = strlen(src);
+ size_t out_left = sizeof(buffer);
+
+ size_t n = g_iconv(conv, &in, &in_left, &out, &out_left);
+
+ if (n == static_cast<size_t>(-1) || in_left > 0)
+ return std::string();
+
+ return std::string(buffer, sizeof(buffer) - out_left);
+}
+
+#endif
+
+std::string
+IcuConverter::ToUTF8(const char *s) const
+{
+#ifdef HAVE_ICU
+ const ScopeLock protect(mutex);
+
+ ucnv_resetToUnicode(converter);
+
+ // TODO: dynamic buffer?
+ UChar buffer[4096], *target = buffer;
+ const char *source = s;
+
+ UErrorCode code = U_ZERO_ERROR;
+
+ ucnv_toUnicode(converter, &target, buffer + ARRAY_SIZE(buffer),
+ &source, source + strlen(source),
+ nullptr, true, &code);
+ if (code != U_ZERO_ERROR)
+ return std::string();
+
+ const size_t target_length = target - buffer;
+ const auto u = UCharToUTF8({buffer, target_length});
+ if (u.IsNull())
+ return std::string();
+
+ std::string result(u.data, u.size);
+ delete[] u.data;
+ return result;
+
+#elif defined(HAVE_GLIB)
+ return DoConvert(to_utf8, s);
+#endif
+}
+
+std::string
+IcuConverter::FromUTF8(const char *s) const
+{
+#ifdef HAVE_ICU
+ const ScopeLock protect(mutex);
+
+ const auto u = UCharFromUTF8(s);
+ if (u.IsNull())
+ return std::string();
+
+ ucnv_resetFromUnicode(converter);
+
+ // TODO: dynamic buffer?
+ char buffer[4096], *target = buffer;
+ const UChar *source = u.data;
+ UErrorCode code = U_ZERO_ERROR;
+
+ ucnv_fromUnicode(converter, &target, buffer + ARRAY_SIZE(buffer),
+ &source, u.end(),
+ nullptr, true, &code);
+ delete[] u.data;
+
+ if (code != U_ZERO_ERROR)
+ return std::string();
+
+ return std::string(buffer, target);
+
+#elif defined(HAVE_GLIB)
+ return DoConvert(from_utf8, s);
+#endif
+}
+
+#endif
diff --git a/src/lib/icu/Converter.hxx b/src/lib/icu/Converter.hxx
new file mode 100644
index 000000000..26eccfe94
--- /dev/null
+++ b/src/lib/icu/Converter.hxx
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICU_CONVERTER_HXX
+#define MPD_ICU_CONVERTER_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#ifdef HAVE_ICU
+#include "thread/Mutex.hxx"
+#define HAVE_ICU_CONVERTER
+#elif defined(HAVE_GLIB)
+#include <glib.h>
+#define HAVE_ICU_CONVERTER
+#endif
+
+#ifdef HAVE_ICU_CONVERTER
+
+#include <string>
+
+class Error;
+
+#ifdef HAVE_ICU
+struct UConverter;
+#endif
+
+/**
+ * This class can convert strings with a certain character set to and
+ * from UTF-8.
+ */
+class IcuConverter {
+#ifdef HAVE_ICU
+ /**
+ * ICU's UConverter class is not thread-safe. This mutex
+ * serializes simultaneous calls.
+ */
+ mutable Mutex mutex;
+
+ UConverter *const converter;
+
+ IcuConverter(UConverter *_converter):converter(_converter) {}
+#elif defined(HAVE_GLIB)
+ const GIConv to_utf8, from_utf8;
+
+ IcuConverter(GIConv _to, GIConv _from)
+ :to_utf8(_to), from_utf8(_from) {}
+#endif
+
+public:
+#ifdef HAVE_ICU
+ ~IcuConverter();
+#elif defined(HAVE_GLIB)
+ ~IcuConverter() {
+ g_iconv_close(to_utf8);
+ g_iconv_close(from_utf8);
+ }
+#endif
+
+ static IcuConverter *Create(const char *charset, Error &error);
+
+ /**
+ * Convert the string to UTF-8.
+ * Returns empty string on error.
+ */
+ gcc_pure gcc_nonnull_all
+ std::string ToUTF8(const char *s) const;
+
+ /**
+ * Convert the string from UTF-8.
+ * Returns empty string on error.
+ */
+ gcc_pure gcc_nonnull_all
+ std::string FromUTF8(const char *s) const;
+};
+
+#endif
+
+#endif
diff --git a/src/lib/icu/Util.cxx b/src/lib/icu/Util.cxx
new file mode 100644
index 000000000..a18043c03
--- /dev/null
+++ b/src/lib/icu/Util.cxx
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2014 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 "Util.hxx"
+#include "util/WritableBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <unicode/ustring.h>
+
+#include <assert.h>
+#include <string.h>
+
+WritableBuffer<UChar>
+UCharFromUTF8(const char *src)
+{
+ assert(src != nullptr);
+
+ const size_t src_length = strlen(src);
+ const size_t dest_capacity = src_length;
+ UChar *dest = new UChar[dest_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ int32_t dest_length;
+ u_strFromUTF8(dest, dest_capacity, &dest_length,
+ src, src_length,
+ &error_code);
+ if (U_FAILURE(error_code)) {
+ delete[] dest;
+ return nullptr;
+ }
+
+ return { dest, size_t(dest_length) };
+}
+
+WritableBuffer<char>
+UCharToUTF8(ConstBuffer<UChar> src)
+{
+ assert(!src.IsNull());
+
+ /* worst-case estimate */
+ size_t dest_capacity = 4 * src.size;
+
+ char *dest = new char[dest_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ int32_t dest_length;
+ u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size,
+ &error_code);
+ if (U_FAILURE(error_code)) {
+ delete[] dest;
+ return nullptr;
+ }
+
+ return { dest, size_t(dest_length) };
+}
diff --git a/src/lib/icu/Util.hxx b/src/lib/icu/Util.hxx
new file mode 100644
index 000000000..ce80bb3fd
--- /dev/null
+++ b/src/lib/icu/Util.hxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICU_UTIL_HXX
+#define MPD_ICU_UTIL_HXX
+
+#include "check.h"
+
+#include <unicode/utypes.h>
+
+template<typename T> struct WritableBuffer;
+template<typename T> struct ConstBuffer;
+
+/**
+ * Wrapper for u_strFromUTF8(). The returned pointer must be freed
+ * with delete[].
+ */
+WritableBuffer<UChar>
+UCharFromUTF8(const char *src);
+
+/**
+ * Wrapper for u_strToUTF8(). The returned pointer must be freed with
+ * delete[].
+ */
+WritableBuffer<char>
+UCharToUTF8(ConstBuffer<UChar> src);
+
+#endif
diff --git a/src/lib/sqlite/Domain.cxx b/src/lib/sqlite/Domain.cxx
new file mode 100644
index 000000000..82c99d718
--- /dev/null
+++ b/src/lib/sqlite/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 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 "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain sqlite_domain("sqlite");
diff --git a/src/lib/sqlite/Domain.hxx b/src/lib/sqlite/Domain.hxx
new file mode 100644
index 000000000..12d8ccf72
--- /dev/null
+++ b/src/lib/sqlite/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SQLITE_DOMAIN_HXX
+#define MPD_SQLITE_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain sqlite_domain;
+
+#endif
diff --git a/src/lib/sqlite/Util.hxx b/src/lib/sqlite/Util.hxx
new file mode 100644
index 000000000..da74d1c3c
--- /dev/null
+++ b/src/lib/sqlite/Util.hxx
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SQLITE_UTIL_HXX
+#define MPD_SQLITE_UTIL_HXX
+
+#include "Domain.hxx"
+#include "util/Error.hxx"
+
+#include <sqlite3.h>
+
+#include <assert.h>
+
+static void
+SetError(Error &error, sqlite3 *db, int code, const char *msg)
+{
+ error.Format(sqlite_domain, code, "%s: %s",
+ msg, sqlite3_errmsg(db));
+}
+
+static void
+SetError(Error &error, sqlite3_stmt *stmt, int code, const char *msg)
+{
+ SetError(error, sqlite3_db_handle(stmt), code, msg);
+}
+
+static bool
+Bind(sqlite3_stmt *stmt, unsigned i, const char *value, Error &error)
+{
+ int result = sqlite3_bind_text(stmt, i, value, -1, nullptr);
+ if (result != SQLITE_OK) {
+ SetError(error, stmt, result, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ return true;
+}
+
+template<typename... Args>
+static bool
+BindAll2(gcc_unused Error &error, gcc_unused sqlite3_stmt *stmt,
+ gcc_unused unsigned i)
+{
+ assert(int(i - 1) == sqlite3_bind_parameter_count(stmt));
+
+ return true;
+}
+
+template<typename... Args>
+static bool
+BindAll2(Error &error, sqlite3_stmt *stmt, unsigned i,
+ const char *value, Args&&... args)
+{
+ return Bind(stmt, i, value, error) &&
+ BindAll2(error, stmt, i + 1, std::forward<Args>(args)...);
+}
+
+template<typename... Args>
+static bool
+BindAll(Error &error, sqlite3_stmt *stmt, Args&&... args)
+{
+ assert(int(sizeof...(args)) == sqlite3_bind_parameter_count(stmt));
+
+ return BindAll2(error, stmt, 1, std::forward<Args>(args)...);
+}
+
+/**
+ * Wrapper for BindAll() that returns the specified sqlite3_stmt* on
+ * success and nullptr on error.
+ */
+template<typename... Args>
+static sqlite3_stmt *
+BindAllOrNull(Error &error, sqlite3_stmt *stmt, Args&&... args)
+{
+ return BindAll(error, stmt, std::forward<Args>(args)...)
+ ? stmt
+ : nullptr;
+}
+
+/**
+ * Call sqlite3_stmt() repepatedly until something other than
+ * SQLITE_BUSY is returned.
+ */
+static int
+ExecuteBusy(sqlite3_stmt *stmt)
+{
+ int result;
+ do {
+ result = sqlite3_step(stmt);
+ } while (result == SQLITE_BUSY);
+
+ return result;
+}
+
+/**
+ * Wrapper for ExecuteBusy() that returns true on SQLITE_ROW.
+ */
+static bool
+ExecuteRow(sqlite3_stmt *stmt, Error &error)
+{
+ int result = ExecuteBusy(stmt);
+ if (result == SQLITE_ROW)
+ return true;
+
+ if (result != SQLITE_DONE)
+ SetError(error, stmt, result, "sqlite3_step() failed");
+
+ return false;
+}
+
+/**
+ * Wrapper for ExecuteBusy() that interprets everything other than
+ * SQLITE_DONE as error.
+ */
+static bool
+ExecuteCommand(sqlite3_stmt *stmt, Error &error)
+{
+ int result = ExecuteBusy(stmt);
+ if (result != SQLITE_DONE) {
+ SetError(error, stmt, result, "sqlite3_step() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wrapper for ExecuteCommand() that returns the number of rows
+ * modified via sqlite3_changes(). Returns -1 on error.
+ */
+static inline int
+ExecuteChanges(sqlite3_stmt *stmt, Error &error)
+{
+ if (!ExecuteCommand(stmt, error))
+ return -1;
+
+ return sqlite3_changes(sqlite3_db_handle(stmt));
+}
+
+/**
+ * Wrapper for ExecuteChanges() that returns true if at least one row
+ * was modified. Returns false if nothing was modified or if an error
+ * occurred.
+ */
+static inline bool
+ExecuteModified(sqlite3_stmt *stmt, Error &error)
+{
+ return ExecuteChanges(stmt, error) > 0;
+}
+
+template<typename F>
+static inline bool
+ExecuteForEach(sqlite3_stmt *stmt, Error &error, F &&f)
+{
+ while (true) {
+ int result = ExecuteBusy(stmt);
+ switch (result) {
+ case SQLITE_ROW:
+ f();
+ break;
+
+ case SQLITE_DONE:
+ return true;
+
+ default:
+ SetError(error, stmt, result, "sqlite3_step() failed");
+ return false;
+ }
+ }
+}
+
+#endif
diff --git a/src/ls.cxx b/src/ls.cxx
index 96c9f60e5..fe70cdc11 100644
--- a/src/ls.cxx
+++ b/src/ls.cxx
@@ -30,7 +30,7 @@
* is detected at runtime and displayed as a urlhandler if the client is
* connected by IPC socket.
*/
-static const char *remoteUrlPrefixes[] = {
+static const char *const remoteUrlPrefixes[] = {
#if defined(ENABLE_CURL)
"http://",
"https://",
@@ -41,7 +41,7 @@ static const char *remoteUrlPrefixes[] = {
"mmst://",
"mmsu://",
#endif
-#ifdef HAVE_FFMPEG
+#ifdef ENABLE_FFMPEG
"gopher://",
"rtp://",
"rtsp://",
@@ -61,7 +61,7 @@ static const char *remoteUrlPrefixes[] = {
#ifdef ENABLE_DESPOTIFY
"spt://",
#endif
-#ifdef HAVE_ALSA
+#ifdef ENABLE_ALSA
"alsa://",
#endif
NULL
@@ -69,7 +69,7 @@ static const char *remoteUrlPrefixes[] = {
void print_supported_uri_schemes_to_fp(FILE *fp)
{
- const char **prefixes = remoteUrlPrefixes;
+ const char *const*prefixes = remoteUrlPrefixes;
#ifdef HAVE_UN
fprintf(fp, " file://");
@@ -83,7 +83,7 @@ void print_supported_uri_schemes_to_fp(FILE *fp)
void print_supported_uri_schemes(Client &client)
{
- const char **prefixes = remoteUrlPrefixes;
+ const char *const *prefixes = remoteUrlPrefixes;
while (*prefixes) {
client_printf(client, "handler: %s\n", *prefixes);
@@ -93,7 +93,7 @@ void print_supported_uri_schemes(Client &client)
bool uri_supported_scheme(const char *uri)
{
- const char **urlPrefixes = remoteUrlPrefixes;
+ const char *const*urlPrefixes = remoteUrlPrefixes;
assert(uri_has_scheme(uri));
diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx
index e75b2e6ff..8be74adf2 100644
--- a/src/mixer/MixerList.hxx
+++ b/src/mixer/MixerList.hxx
@@ -27,6 +27,7 @@
struct MixerPlugin;
+extern const MixerPlugin null_mixer_plugin;
extern const MixerPlugin software_mixer_plugin;
extern const MixerPlugin alsa_mixer_plugin;
extern const MixerPlugin oss_mixer_plugin;
diff --git a/src/mixer/MixerType.cxx b/src/mixer/MixerType.cxx
index cd45db0d9..8972118e5 100644
--- a/src/mixer/MixerType.cxx
+++ b/src/mixer/MixerType.cxx
@@ -23,17 +23,19 @@
#include <assert.h>
#include <string.h>
-enum mixer_type
+MixerType
mixer_type_parse(const char *input)
{
assert(input != NULL);
if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
- return MIXER_TYPE_NONE;
+ return MixerType::NONE;
else if (strcmp(input, "hardware") == 0)
- return MIXER_TYPE_HARDWARE;
+ return MixerType::HARDWARE;
else if (strcmp(input, "software") == 0)
- return MIXER_TYPE_SOFTWARE;
+ return MixerType::SOFTWARE;
+ else if (strcmp(input, "null") == 0)
+ return MixerType::NULL_;
else
- return MIXER_TYPE_UNKNOWN;
+ return MixerType::UNKNOWN;
}
diff --git a/src/mixer/MixerType.hxx b/src/mixer/MixerType.hxx
index bfa2637b7..40f60203d 100644
--- a/src/mixer/MixerType.hxx
+++ b/src/mixer/MixerType.hxx
@@ -20,28 +20,31 @@
#ifndef MPD_MIXER_TYPE_HXX
#define MPD_MIXER_TYPE_HXX
-enum mixer_type {
+enum class MixerType {
/** parser error */
- MIXER_TYPE_UNKNOWN,
+ UNKNOWN,
/** mixer disabled */
- MIXER_TYPE_NONE,
+ NONE,
+
+ /** "null" mixer (virtual fake) */
+ NULL_,
/** software mixer with pcm_volume() */
- MIXER_TYPE_SOFTWARE,
+ SOFTWARE,
/** hardware mixer (output's plugin) */
- MIXER_TYPE_HARDWARE,
+ HARDWARE,
};
/**
- * Parses a "mixer_type" setting from the configuration file.
+ * Parses a #MixerType setting from the configuration file.
*
- * @param input the configured string value; must not be NULL
- * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could
- * not be parsed
+ * @param input the configured string value; must not be NULL @return
+ * a #MixerType value; #MixerType::UNKNOWN means #input could not be
+ * parsed
*/
-enum mixer_type
+MixerType
mixer_type_parse(const char *input);
#endif
diff --git a/src/mixer/plugins/NullMixerPlugin.cxx b/src/mixer/plugins/NullMixerPlugin.cxx
new file mode 100644
index 000000000..b8894d443
--- /dev/null
+++ b/src/mixer/plugins/NullMixerPlugin.cxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 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 "mixer/MixerInternal.hxx"
+
+class NullMixer final : public Mixer {
+ /**
+ * The current volume in percent (0..100).
+ */
+ unsigned volume;
+
+public:
+ NullMixer(MixerListener &_listener)
+ :Mixer(null_mixer_plugin, _listener),
+ volume(100)
+ {
+ }
+
+ /* virtual methods from class Mixer */
+ bool Open(gcc_unused Error &error) override {
+ return true;
+ }
+
+ void Close() override {
+ }
+
+ int GetVolume(gcc_unused Error &error) override {
+ return volume;
+ }
+
+ bool SetVolume(unsigned _volume, gcc_unused Error &error) override {
+ volume = _volume;
+ return true;
+ }
+};
+
+static Mixer *
+null_mixer_init(gcc_unused EventLoop &event_loop,
+ gcc_unused AudioOutput &ao,
+ MixerListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new NullMixer(listener);
+}
+
+const MixerPlugin null_mixer_plugin = {
+ null_mixer_init,
+ true,
+};
diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx
index f6d1f97b3..6f0651423 100644
--- a/src/neighbor/Registry.cxx
+++ b/src/neighbor/Registry.cxx
@@ -29,7 +29,7 @@ const NeighborPlugin *const neighbor_plugins[] = {
#ifdef ENABLE_SMBCLIENT
&smbclient_neighbor_plugin,
#endif
-#ifdef HAVE_LIBUPNP
+#ifdef ENABLE_UPNP
&upnp_neighbor_plugin,
#endif
nullptr
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
index 2701b0ccd..0e31c6e52 100644
--- a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
@@ -216,7 +216,7 @@ SmbclientNeighborExplorer::Run()
prev = i;
} else {
/* can't see it anymore: move to "lost" */
-#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#if CLANG_OR_GCC_VERSION(4,7)
lost.splice_after(lost.before_begin(), list, prev);
#else
/* the forward_list::splice_after() lvalue
diff --git a/src/output/Init.cxx b/src/output/Init.cxx
index 79ef4f998..f9648e0d4 100644
--- a/src/output/Init.cxx
+++ b/src/output/Init.cxx
@@ -58,7 +58,7 @@ AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin)
filter(nullptr),
replay_gain_filter(nullptr),
other_replay_gain_filter(nullptr),
- command(AO_COMMAND_NONE)
+ command(Command::NONE)
{
assert(plugin.finish != nullptr);
assert(plugin.open != nullptr);
@@ -94,7 +94,7 @@ audio_output_detect(Error &error)
* mixer_enabled, if the mixer_type setting is not configured.
*/
gcc_pure
-static enum mixer_type
+static MixerType
audio_output_mixer_type(const config_param &param)
{
/* read the local "mixer_type" setting */
@@ -104,7 +104,7 @@ audio_output_mixer_type(const config_param &param)
/* try the local "mixer_enabled" setting next (deprecated) */
if (!param.GetBlockValue("mixer_enabled", true))
- return MIXER_TYPE_NONE;
+ return MixerType::NONE;
/* fall back to the global "mixer_type" setting (also
deprecated) */
@@ -123,18 +123,22 @@ audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao,
Mixer *mixer;
switch (audio_output_mixer_type(param)) {
- case MIXER_TYPE_NONE:
- case MIXER_TYPE_UNKNOWN:
+ case MixerType::NONE:
+ case MixerType::UNKNOWN:
return nullptr;
- case MIXER_TYPE_HARDWARE:
+ case MixerType::NULL_:
+ return mixer_new(event_loop, null_mixer_plugin, ao, listener,
+ param, error);
+
+ case MixerType::HARDWARE:
if (plugin == nullptr)
return nullptr;
return mixer_new(event_loop, *plugin, ao, listener,
param, error);
- case MIXER_TYPE_SOFTWARE:
+ case MixerType::SOFTWARE:
mixer = mixer_new(event_loop, software_mixer_plugin, ao,
listener,
config_param(),
diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx
index 6e6ffb442..d2ca39585 100644
--- a/src/output/Internal.hxx
+++ b/src/output/Internal.hxx
@@ -40,32 +40,32 @@ struct config_param;
struct PlayerControl;
struct AudioOutputPlugin;
-enum audio_output_command {
- AO_COMMAND_NONE = 0,
- AO_COMMAND_ENABLE,
- AO_COMMAND_DISABLE,
- AO_COMMAND_OPEN,
+struct AudioOutput {
+ enum class Command {
+ NONE,
+ ENABLE,
+ DISABLE,
+ OPEN,
- /**
- * This command is invoked when the input audio format
- * changes.
- */
- AO_COMMAND_REOPEN,
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ REOPEN,
- AO_COMMAND_CLOSE,
- AO_COMMAND_PAUSE,
+ CLOSE,
+ PAUSE,
- /**
- * Drains the internal (hardware) buffers of the device. This
- * operation may take a while to complete.
- */
- AO_COMMAND_DRAIN,
+ /**
+ * Drains the internal (hardware) buffers of the device. This
+ * operation may take a while to complete.
+ */
+ DRAIN,
- AO_COMMAND_CANCEL,
- AO_COMMAND_KILL
-};
+ CANCEL,
+ KILL
+ };
-struct AudioOutput {
/**
* The device's configured display name.
*/
@@ -231,7 +231,7 @@ struct AudioOutput {
/**
* The next command to be performed by the output thread.
*/
- enum audio_output_command command;
+ Command command;
/**
* The music pipe which provides music chunks to be played.
@@ -284,7 +284,7 @@ struct AudioOutput {
}
bool IsCommandFinished() const {
- return command == AO_COMMAND_NONE;
+ return command == Command::NONE;
}
/**
@@ -299,7 +299,7 @@ struct AudioOutput {
*
* Caller must lock the mutex.
*/
- void CommandAsync(audio_output_command cmd);
+ void CommandAsync(Command cmd);
/**
* Sends a command to the #AudioOutput object and waits for
@@ -307,13 +307,13 @@ struct AudioOutput {
*
* Caller must lock the mutex.
*/
- void CommandWait(audio_output_command cmd);
+ void CommandWait(Command cmd);
/**
* Lock the #AudioOutput object and execute the command
* synchronously.
*/
- void LockCommandWait(audio_output_command cmd);
+ void LockCommandWait(Command cmd);
/**
* Enables the device.
diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx
index 89428fa87..7581d4d58 100644
--- a/src/output/OutputControl.cxx
+++ b/src/output/OutputControl.cxx
@@ -46,7 +46,7 @@ AudioOutput::WaitForCommand()
}
void
-AudioOutput::CommandAsync(audio_output_command cmd)
+AudioOutput::CommandAsync(Command cmd)
{
assert(IsCommandFinished());
@@ -55,14 +55,14 @@ AudioOutput::CommandAsync(audio_output_command cmd)
}
void
-AudioOutput::CommandWait(audio_output_command cmd)
+AudioOutput::CommandWait(Command cmd)
{
CommandAsync(cmd);
WaitForCommand();
}
void
-AudioOutput::LockCommandWait(audio_output_command cmd)
+AudioOutput::LockCommandWait(Command cmd)
{
const ScopeLock protect(mutex);
CommandWait(cmd);
@@ -92,7 +92,7 @@ AudioOutput::LockEnableWait()
StartThread();
}
- LockCommandWait(AO_COMMAND_ENABLE);
+ LockCommandWait(Command::ENABLE);
}
void
@@ -109,7 +109,7 @@ AudioOutput::LockDisableWait()
return;
}
- LockCommandWait(AO_COMMAND_DISABLE);
+ LockCommandWait(Command::DISABLE);
}
inline bool
@@ -134,7 +134,7 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
- CommandWait(AO_COMMAND_CANCEL);
+ CommandWait(Command::CANCEL);
}
return true;
@@ -148,7 +148,9 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
if (!thread.IsDefined())
StartThread();
- CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
+ CommandWait(open
+ ? Command::REOPEN
+ : Command::OPEN);
const bool open2 = open;
if (open2 && mixer != nullptr) {
@@ -172,7 +174,7 @@ AudioOutput::CloseWait()
assert(!open || !fail_timer.IsDefined());
if (open)
- CommandWait(AO_COMMAND_CLOSE);
+ CommandWait(Command::CLOSE);
else
fail_timer.Reset();
}
@@ -219,7 +221,7 @@ AudioOutput::LockPauseAsync()
assert(allow_play);
if (IsOpen())
- CommandAsync(AO_COMMAND_PAUSE);
+ CommandAsync(Command::PAUSE);
}
void
@@ -229,7 +231,7 @@ AudioOutput::LockDrainAsync()
assert(allow_play);
if (IsOpen())
- CommandAsync(AO_COMMAND_DRAIN);
+ CommandAsync(Command::DRAIN);
}
void
@@ -239,7 +241,7 @@ AudioOutput::LockCancelAsync()
if (IsOpen()) {
allow_play = false;
- CommandAsync(AO_COMMAND_CANCEL);
+ CommandAsync(Command::CANCEL);
}
}
@@ -277,7 +279,7 @@ AudioOutput::StopThread()
assert(thread.IsDefined());
assert(allow_play);
- LockCommandWait(AO_COMMAND_KILL);
+ LockCommandWait(Command::KILL);
thread.Join();
}
diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx
index 2ec0670c1..eb9277d04 100644
--- a/src/output/OutputThread.cxx
+++ b/src/output/OutputThread.cxx
@@ -45,8 +45,8 @@
void
AudioOutput::CommandFinished()
{
- assert(command != AO_COMMAND_NONE);
- command = AO_COMMAND_NONE;
+ assert(command != Command::NONE);
+ command = Command::NONE;
mutex.unlock();
audio_output_client_notify.Signal();
@@ -342,7 +342,7 @@ AudioOutput::WaitForDelay()
(void)cond.timed_wait(mutex, delay);
- if (command != AO_COMMAND_NONE)
+ if (command != Command::NONE)
return false;
}
}
@@ -471,7 +471,7 @@ AudioOutput::PlayChunk(const MusicChunk *chunk)
Error error;
- while (!data.IsEmpty() && command == AO_COMMAND_NONE) {
+ while (!data.IsEmpty() && command == Command::NONE) {
if (!WaitForDelay())
break;
@@ -529,7 +529,7 @@ AudioOutput::Play()
assert(!in_playback_loop);
in_playback_loop = true;
- while (chunk != nullptr && command == AO_COMMAND_NONE) {
+ while (chunk != nullptr && command == Command::NONE) {
assert(!current_chunk_finished);
current_chunk = chunk;
@@ -577,7 +577,7 @@ AudioOutput::Pause()
Close(false);
break;
}
- } while (command == AO_COMMAND_NONE);
+ } while (command == Command::NONE);
pause = false;
}
@@ -594,30 +594,30 @@ AudioOutput::Task()
while (1) {
switch (command) {
- case AO_COMMAND_NONE:
+ case Command::NONE:
break;
- case AO_COMMAND_ENABLE:
+ case Command::ENABLE:
Enable();
CommandFinished();
break;
- case AO_COMMAND_DISABLE:
+ case Command::DISABLE:
Disable();
CommandFinished();
break;
- case AO_COMMAND_OPEN:
+ case Command::OPEN:
Open();
CommandFinished();
break;
- case AO_COMMAND_REOPEN:
+ case Command::REOPEN:
Reopen();
CommandFinished();
break;
- case AO_COMMAND_CLOSE:
+ case Command::CLOSE:
assert(open);
assert(pipe != nullptr);
@@ -625,7 +625,7 @@ AudioOutput::Task()
CommandFinished();
break;
- case AO_COMMAND_PAUSE:
+ case Command::PAUSE:
if (!open) {
/* the output has failed after
audio_output_all_pause() has
@@ -642,7 +642,7 @@ AudioOutput::Task()
the new command first */
continue;
- case AO_COMMAND_DRAIN:
+ case Command::DRAIN:
if (open) {
assert(current_chunk == nullptr);
assert(pipe->Peek() == nullptr);
@@ -655,7 +655,7 @@ AudioOutput::Task()
CommandFinished();
continue;
- case AO_COMMAND_CANCEL:
+ case Command::CANCEL:
current_chunk = nullptr;
if (open) {
@@ -667,7 +667,7 @@ AudioOutput::Task()
CommandFinished();
continue;
- case AO_COMMAND_KILL:
+ case Command::KILL:
current_chunk = nullptr;
CommandFinished();
mutex.unlock();
@@ -679,7 +679,7 @@ AudioOutput::Task()
chunks in the pipe */
continue;
- if (command == AO_COMMAND_NONE) {
+ if (command == Command::NONE) {
woken_for_play = false;
cond.wait(mutex);
}
@@ -696,7 +696,7 @@ AudioOutput::Task(void *arg)
void
AudioOutput::StartThread()
{
- assert(command == AO_COMMAND_NONE);
+ assert(command == Command::NONE);
Error error;
if (!thread.Start(Task, this, error))
diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx
index 566f6b6a8..2ce844179 100644
--- a/src/output/Registry.cxx
+++ b/src/output/Registry.cxx
@@ -54,13 +54,13 @@ const AudioOutputPlugin *const audio_output_plugins[] = {
#ifdef ENABLE_PIPE_OUTPUT
&pipe_output_plugin,
#endif
-#ifdef HAVE_ALSA
+#ifdef ENABLE_ALSA
&alsa_output_plugin,
#endif
-#ifdef HAVE_ROAR
+#ifdef ENABLE_ROAR
&roar_output_plugin,
#endif
-#ifdef HAVE_AO
+#ifdef ENABLE_AO
&ao_output_plugin,
#endif
#ifdef HAVE_OSS
@@ -75,10 +75,10 @@ const AudioOutputPlugin *const audio_output_plugins[] = {
#ifdef ENABLE_SOLARIS_OUTPUT
&solaris_output_plugin,
#endif
-#ifdef HAVE_PULSE
+#ifdef ENABLE_PULSE
&pulse_output_plugin,
#endif
-#ifdef HAVE_JACK
+#ifdef ENABLE_JACK
&jack_output_plugin,
#endif
#ifdef ENABLE_HTTPD_OUTPUT
diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
index af8c88fa1..4b98f4a77 100644
--- a/src/output/plugins/AoOutputPlugin.cxx
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -20,12 +20,13 @@
#include "config.h"
#include "AoOutputPlugin.hxx"
#include "../OutputAPI.hxx"
+#include "util/DivideString.hxx"
+#include "util/SplitString.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <ao/ao.h>
-#include <glib.h>
#include <string.h>
@@ -126,25 +127,18 @@ AoOutput::Configure(const config_param &param, Error &error)
value = param.GetBlockValue("options", nullptr);
if (value != nullptr) {
- gchar **_options = g_strsplit(value, ";", 0);
+ for (const auto &i : SplitString(value, ';')) {
+ const DivideString ss(i.c_str(), '=', true);
- for (unsigned i = 0; _options[i] != nullptr; ++i) {
- gchar **key_value = g_strsplit(_options[i], "=", 2);
-
- if (key_value[0] == nullptr || key_value[1] == nullptr) {
+ if (!ss.IsDefined()) {
error.Format(ao_output_domain,
"problems parsing options \"%s\"",
- _options[i]);
+ i.c_str());
return false;
}
- ao_append_option(&options, key_value[0],
- key_value[1]);
-
- g_strfreev(key_value);
+ ao_append_option(&options, ss.GetFirst(), ss.GetSecond());
}
-
- g_strfreev(_options);
}
return true;
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index e1dad7893..ba05e6c5e 100644
--- a/src/output/plugins/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -21,25 +21,25 @@
#include "JackOutputPlugin.hxx"
#include "../OutputAPI.hxx"
#include "config/ConfigError.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/SplitString.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <assert.h>
-#include <glib.h>
#include <jack/jack.h>
#include <jack/types.h>
#include <jack/ringbuffer.h>
+#include <unistd.h> /* for usleep() */
#include <stdlib.h>
#include <string.h>
-enum {
- MAX_PORTS = 16,
-};
+static constexpr unsigned MAX_PORTS = 16;
-static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
struct JackOutput {
AudioOutput base;
@@ -55,10 +55,10 @@ struct JackOutput {
/* configuration */
- char *source_ports[MAX_PORTS];
+ std::string source_ports[MAX_PORTS];
unsigned num_source_ports;
- char *destination_ports[MAX_PORTS];
+ std::string destination_ports[MAX_PORTS];
unsigned num_destination_ports;
size_t ringbuffer_size;
@@ -82,24 +82,53 @@ struct JackOutput {
JackOutput()
:base(jack_output_plugin) {}
- bool Initialize(const config_param &param, Error &error_r) {
- return base.Configure(param, error_r);
+ bool Configure(const config_param &param, Error &error);
+
+ bool Connect(Error &error);
+
+ /**
+ * Disconnect the JACK client.
+ */
+ void Disconnect();
+
+ void Shutdown() {
+ shutdown = true;
}
+
+ bool Enable(Error &error);
+ void Disable();
+
+ bool Open(AudioFormat &new_audio_format, Error &error);
+
+ bool Start(Error &error);
+ void Stop();
+
+ /**
+ * Determine the number of frames guaranteed to be available
+ * on all channels.
+ */
+ gcc_pure
+ jack_nframes_t GetAvailable() const;
+
+ void Process(jack_nframes_t nframes);
+
+ /**
+ * @return the number of frames that were written
+ */
+ size_t WriteSamples(const float *src, size_t n_frames);
+
+ size_t Play(const void *chunk, size_t size, Error &error);
};
static constexpr Domain jack_output_domain("jack_output");
-/**
- * Determine the number of frames guaranteed to be available on all
- * channels.
- */
-static jack_nframes_t
-mpd_jack_available(const JackOutput *jd)
+inline jack_nframes_t
+JackOutput::GetAvailable() const
{
- size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
+ size_t min = jack_ringbuffer_read_space(ringbuffer[0]);
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+ for (unsigned i = 1; i < audio_format.channels; ++i) {
+ size_t current = jack_ringbuffer_read_space(ringbuffer[i]);
if (current < min)
min = current;
}
@@ -109,85 +138,121 @@ mpd_jack_available(const JackOutput *jd)
return min / jack_sample_size;
}
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
+/**
+ * Call jack_ringbuffer_read_advance() on all buffers in the list.
+ */
+static void
+MultiReadAdvance(ConstBuffer<jack_ringbuffer_t *> buffers,
+ size_t size)
{
- JackOutput *jd = (JackOutput *) arg;
+ for (auto *i : buffers)
+ jack_ringbuffer_read_advance(i, size);
+}
+
+/**
+ * Write a specific amount of "silence" to the given port.
+ */
+static void
+WriteSilence(jack_port_t &port, jack_nframes_t nframes)
+{
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(&port, nframes);
+ if (out == nullptr)
+ /* workaround for libjack1 bug: if the server
+ connection fails, the process callback is invoked
+ anyway, but unable to get a buffer */
+ return;
+
+ std::fill_n(out, nframes, 0.0);
+}
+
+/**
+ * Write a specific amount of "silence" to all ports in the list.
+ */
+static void
+MultiWriteSilence(ConstBuffer<jack_port_t *> ports, jack_nframes_t nframes)
+{
+ for (auto *i : ports)
+ WriteSilence(*i, nframes);
+}
+
+/**
+ * Copy data from the buffer to the port. If the buffer underruns,
+ * fill with silence.
+ */
+static void
+Copy(jack_port_t &dest, jack_nframes_t nframes,
+ jack_ringbuffer_t &src, jack_nframes_t available)
+{
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(&dest, nframes);
+ if (out == nullptr)
+ /* workaround for libjack1 bug: if the server
+ connection fails, the process callback is
+ invoked anyway, but unable to get a
+ buffer */
+ return;
+ /* copy from buffer to port */
+ jack_ringbuffer_read(&src, (char *)out,
+ available * jack_sample_size);
+
+ /* ringbuffer underrun, fill with silence */
+ std::fill(out + available, out + nframes, 0.0);
+}
+
+inline void
+JackOutput::Process(jack_nframes_t nframes)
+{
if (nframes <= 0)
- return 0;
+ return;
- if (jd->pause) {
+ jack_nframes_t available = GetAvailable();
+
+ const unsigned n_channels = audio_format.channels;
+
+ if (pause) {
/* empty the ring buffers */
- const jack_nframes_t available = mpd_jack_available(jd);
- for (unsigned i = 0; i < jd->audio_format.channels; ++i)
- jack_ringbuffer_read_advance(jd->ringbuffer[i],
- available * jack_sample_size);
+ MultiReadAdvance({ringbuffer, n_channels},
+ available * jack_sample_size);
/* generate silence while MPD is paused */
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
- jack_default_audio_sample_t *out =
- (jack_default_audio_sample_t *)
- jack_port_get_buffer(jd->ports[i], nframes);
+ MultiWriteSilence({ports, n_channels}, nframes);
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
-
- return 0;
+ return;
}
- jack_nframes_t available = mpd_jack_available(jd);
if (available > nframes)
available = nframes;
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
- jack_default_audio_sample_t *out =
- (jack_default_audio_sample_t *)
- jack_port_get_buffer(jd->ports[i], nframes);
- if (out == nullptr)
- /* workaround for libjack1 bug: if the server
- connection fails, the process callback is
- invoked anyway, but unable to get a
- buffer */
- continue;
-
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out, available * jack_sample_size);
-
- for (jack_nframes_t f = available; f < nframes; ++f)
- /* ringbuffer underrun, fill with silence */
- out[f] = 0.0;
- }
+ for (unsigned i = 0; i < n_channels; ++i)
+ Copy(*ports[i], nframes, *ringbuffer[i], available);
/* generate silence for the unused source ports */
- for (unsigned i = jd->audio_format.channels;
- i < jd->num_source_ports; ++i) {
- jack_default_audio_sample_t *out =
- (jack_default_audio_sample_t *)
- jack_port_get_buffer(jd->ports[i], nframes);
- if (out == nullptr)
- /* workaround for libjack1 bug: if the server
- connection fails, the process callback is
- invoked anyway, but unable to get a
- buffer */
- continue;
-
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
+ MultiWriteSilence({ports + n_channels, num_source_ports - n_channels},
+ nframes);
+}
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+ JackOutput &jo = *(JackOutput *) arg;
+
+ jo.Process(nframes);
return 0;
}
static void
mpd_jack_shutdown(void *arg)
{
- JackOutput *jd = (JackOutput *) arg;
- jd->shutdown = true;
+ JackOutput &jo = *(JackOutput *) arg;
+
+ jo.Shutdown();
}
static void
@@ -200,9 +265,10 @@ set_audioformat(JackOutput *jd, AudioFormat &audio_format)
else if (audio_format.channels > jd->num_source_ports)
audio_format.channels = 2;
- if (audio_format.format != SampleFormat::S16 &&
- audio_format.format != SampleFormat::S24_P32)
- audio_format.format = SampleFormat::S24_P32;
+ /* JACK uses 32 bit float in the range [-1 .. 1] - just like
+ MPD's SampleFormat::FLOAT*/
+ static_assert(jack_sample_size == sizeof(float), "Expected float32");
+ audio_format.format = SampleFormat::FLOAT;
}
static void
@@ -219,55 +285,47 @@ mpd_jack_info(const char *msg)
}
#endif
-/**
- * Disconnect the JACK client.
- */
-static void
-mpd_jack_disconnect(JackOutput *jd)
+void
+JackOutput::Disconnect()
{
- assert(jd != nullptr);
- assert(jd->client != nullptr);
+ assert(client != nullptr);
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = nullptr;
+ jack_deactivate(client);
+ jack_client_close(client);
+ client = nullptr;
}
/**
* Connect the JACK client and performs some basic setup
* (e.g. register callbacks).
*/
-static bool
-mpd_jack_connect(JackOutput *jd, Error &error)
+bool
+JackOutput::Connect(Error &error)
{
- jack_status_t status;
+ shutdown = false;
- assert(jd != nullptr);
-
- jd->shutdown = false;
-
- jd->client = jack_client_open(jd->name, jd->options, &status,
- jd->server_name);
- if (jd->client == nullptr) {
+ jack_status_t status;
+ client = jack_client_open(name, options, &status, server_name);
+ if (client == nullptr) {
error.Format(jack_output_domain, status,
"Failed to connect to JACK server, status=%d",
status);
return false;
}
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+ jack_set_process_callback(client, mpd_jack_process, this);
+ jack_on_shutdown(client, mpd_jack_shutdown, this);
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- jd->ports[i] = jack_port_register(jd->client,
- jd->source_ports[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == nullptr) {
+ for (unsigned i = 0; i < num_source_ports; ++i) {
+ ports[i] = jack_port_register(client,
+ source_ports[i].c_str(),
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (ports[i] == nullptr) {
error.Format(jack_output_domain,
"Cannot register output port \"%s\"",
- jd->source_ports[i]);
- mpd_jack_disconnect(jd);
+ source_ports[i].c_str());
+ Disconnect();
return false;
}
}
@@ -282,23 +340,19 @@ mpd_jack_test_default_device(void)
}
static unsigned
-parse_port_list(const char *source, char **dest, Error &error)
+parse_port_list(const char *source, std::string dest[], Error &error)
{
- char **list = g_strsplit(source, ",", 0);
unsigned n = 0;
-
- for (n = 0; list[n] != nullptr; ++n) {
+ for (auto &&i : SplitString(source, ',')) {
if (n >= MAX_PORTS) {
error.Set(config_domain,
"too many port names");
return 0;
}
- dest[n] = list[n];
+ dest[n++] = std::move(i);
}
- g_free(list);
-
if (n == 0) {
error.Format(config_domain,
"at least one port name expected");
@@ -308,41 +362,34 @@ parse_port_list(const char *source, char **dest, Error &error)
return n;
}
-static AudioOutput *
-mpd_jack_init(const config_param &param, Error &error)
+bool
+JackOutput::Configure(const config_param &param, Error &error)
{
- JackOutput *jd = new JackOutput();
-
- if (!jd->Initialize(param, error)) {
- delete jd;
- return nullptr;
- }
-
- const char *value;
+ if (!base.Configure(param, error))
+ return false;
- jd->options = JackNullOption;
+ options = JackNullOption;
- jd->name = param.GetBlockValue("client_name", nullptr);
- if (jd->name != nullptr)
- jd->options = jack_options_t(jd->options | JackUseExactName);
+ name = param.GetBlockValue("client_name", nullptr);
+ if (name != nullptr)
+ options = jack_options_t(options | JackUseExactName);
else
/* if there's a no configured client name, we don't
care about the JackUseExactName option */
- jd->name = "Music Player Daemon";
+ name = "Music Player Daemon";
- jd->server_name = param.GetBlockValue("server_name", nullptr);
- if (jd->server_name != nullptr)
- jd->options = jack_options_t(jd->options | JackServerName);
+ server_name = param.GetBlockValue("server_name", nullptr);
+ if (server_name != nullptr)
+ options = jack_options_t(options | JackServerName);
if (!param.GetBlockValue("autostart", false))
- jd->options = jack_options_t(jd->options | JackNoStartServer);
+ options = jack_options_t(options | JackNoStartServer);
/* configure the source ports */
- value = param.GetBlockValue("source_ports", "left,right");
- jd->num_source_ports = parse_port_list(value,
- jd->source_ports, error);
- if (jd->num_source_ports == 0)
+ const char *value = param.GetBlockValue("source_ports", "left,right");
+ num_source_ports = parse_port_list(value, source_ports, error);
+ if (num_source_ports == 0)
return nullptr;
/* configure the destination ports */
@@ -358,24 +405,59 @@ mpd_jack_init(const config_param &param, Error &error)
}
if (value != nullptr) {
- jd->num_destination_ports =
- parse_port_list(value,
- jd->destination_ports, error);
- if (jd->num_destination_ports == 0)
+ num_destination_ports =
+ parse_port_list(value, destination_ports, error);
+ if (num_destination_ports == 0)
return nullptr;
} else {
- jd->num_destination_ports = 0;
+ num_destination_ports = 0;
}
- if (jd->num_destination_ports > 0 &&
- jd->num_destination_ports != jd->num_source_ports)
+ if (num_destination_ports > 0 &&
+ num_destination_ports != num_source_ports)
FormatWarning(jack_output_domain,
"number of source ports (%u) mismatches the "
"number of destination ports (%u) in line %d",
- jd->num_source_ports, jd->num_destination_ports,
+ num_source_ports, num_destination_ports,
param.line);
- jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
+ ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
+
+ return true;
+}
+
+inline bool
+JackOutput::Enable(Error &error)
+{
+ for (unsigned i = 0; i < num_source_ports; ++i)
+ ringbuffer[i] = nullptr;
+
+ return Connect(error);
+}
+
+inline void
+JackOutput::Disable()
+{
+ if (client != nullptr)
+ Disconnect();
+
+ for (unsigned i = 0; i < num_source_ports; ++i) {
+ if (ringbuffer[i] != nullptr) {
+ jack_ringbuffer_free(ringbuffer[i]);
+ ringbuffer[i] = nullptr;
+ }
+ }
+}
+
+static AudioOutput *
+mpd_jack_init(const config_param &param, Error &error)
+{
+ JackOutput *jd = new JackOutput();
+
+ if (!jd->Configure(param, error)) {
+ delete jd;
+ return nullptr;
+ }
jack_set_error_function(mpd_jack_error);
@@ -391,160 +473,134 @@ mpd_jack_finish(AudioOutput *ao)
{
JackOutput *jd = (JackOutput *)ao;
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- g_free(jd->source_ports[i]);
-
- for (unsigned i = 0; i < jd->num_destination_ports; ++i)
- g_free(jd->destination_ports[i]);
-
delete jd;
}
static bool
mpd_jack_enable(AudioOutput *ao, Error &error)
{
- JackOutput *jd = (JackOutput *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- jd->ringbuffer[i] = nullptr;
+ JackOutput &jo = *(JackOutput *)ao;
- return mpd_jack_connect(jd, error);
+ return jo.Enable(error);
}
static void
mpd_jack_disable(AudioOutput *ao)
{
- JackOutput *jd = (JackOutput *)ao;
-
- if (jd->client != nullptr)
- mpd_jack_disconnect(jd);
+ JackOutput &jo = *(JackOutput *)ao;
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] != nullptr) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = nullptr;
- }
- }
+ jo.Disable();
}
/**
* Stops the playback on the JACK connection.
*/
-static void
-mpd_jack_stop(JackOutput *jd)
+void
+JackOutput::Stop()
{
- assert(jd != nullptr);
-
- if (jd->client == nullptr)
+ if (client == nullptr)
return;
- if (jd->shutdown)
+ if (shutdown)
/* the connection has failed; close it */
- mpd_jack_disconnect(jd);
+ Disconnect();
else
/* the connection is alive: just stop playback */
- jack_deactivate(jd->client);
+ jack_deactivate(client);
}
-static bool
-mpd_jack_start(JackOutput *jd, Error &error)
+inline bool
+JackOutput::Start(Error &error)
{
- const char *destination_ports[MAX_PORTS], **jports;
- const char *duplicate_port = nullptr;
- unsigned num_destination_ports;
-
- assert(jd->client != nullptr);
- assert(jd->audio_format.channels <= jd->num_source_ports);
+ assert(client != nullptr);
+ assert(audio_format.channels <= num_source_ports);
/* allocate the ring buffers on the first open(); these
persist until MPD exits. It's too unsafe to delete them
because we can never know when mpd_jack_process() gets
called */
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] == nullptr)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
+ for (unsigned i = 0; i < num_source_ports; ++i) {
+ if (ringbuffer[i] == nullptr)
+ ringbuffer[i] =
+ jack_ringbuffer_create(ringbuffer_size);
/* clear the ring buffer to be sure that data from
previous playbacks are gone */
- jack_ringbuffer_reset(jd->ringbuffer[i]);
+ jack_ringbuffer_reset(ringbuffer[i]);
}
- if ( jack_activate(jd->client) ) {
+ if ( jack_activate(client) ) {
error.Set(jack_output_domain, "cannot activate client");
- mpd_jack_stop(jd);
+ Stop();
return false;
}
- if (jd->num_destination_ports == 0) {
+ const char *dports[MAX_PORTS], **jports;
+ unsigned num_dports;
+ if (num_destination_ports == 0) {
/* no output ports were configured - ask libjack for
defaults */
- jports = jack_get_ports(jd->client, nullptr, nullptr,
+ jports = jack_get_ports(client, nullptr, nullptr,
JackPortIsPhysical | JackPortIsInput);
if (jports == nullptr) {
error.Set(jack_output_domain, "no ports found");
- mpd_jack_stop(jd);
+ Stop();
return false;
}
assert(*jports != nullptr);
- for (num_destination_ports = 0;
- num_destination_ports < MAX_PORTS &&
- jports[num_destination_ports] != nullptr;
- ++num_destination_ports) {
+ for (num_dports = 0; num_dports < MAX_PORTS &&
+ jports[num_dports] != nullptr;
+ ++num_dports) {
FormatDebug(jack_output_domain,
"destination_port[%u] = '%s'\n",
- num_destination_ports,
- jports[num_destination_ports]);
- destination_ports[num_destination_ports] =
- jports[num_destination_ports];
+ num_dports,
+ jports[num_dports]);
+ dports[num_dports] = jports[num_dports];
}
} else {
/* use the configured output ports */
- num_destination_ports = jd->num_destination_ports;
- memcpy(destination_ports, jd->destination_ports,
- num_destination_ports * sizeof(*destination_ports));
+ num_dports = num_destination_ports;
+ for (unsigned i = 0; i < num_dports; ++i)
+ dports[i] = destination_ports[i].c_str();
jports = nullptr;
}
- assert(num_destination_ports > 0);
+ assert(num_dports > 0);
- if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+ const char *duplicate_port = nullptr;
+ if (audio_format.channels >= 2 && num_dports == 1) {
/* mix stereo signal on one speaker */
- while (num_destination_ports < jd->audio_format.channels)
- destination_ports[num_destination_ports++] =
- destination_ports[0];
- } else if (num_destination_ports > jd->audio_format.channels) {
- if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ std::fill(dports + num_dports, dports + audio_format.channels,
+ dports[0]);
+ } else if (num_dports > audio_format.channels) {
+ if (audio_format.channels == 1 && num_dports > 2) {
/* mono input file: connect the one source
channel to the both destination channels */
- duplicate_port = destination_ports[1];
- num_destination_ports = 1;
+ duplicate_port = dports[1];
+ num_dports = 1;
} else
/* connect only as many ports as we need */
- num_destination_ports = jd->audio_format.channels;
+ num_dports = audio_format.channels;
}
- assert(num_destination_ports <= jd->num_source_ports);
-
- for (unsigned i = 0; i < num_destination_ports; ++i) {
- int ret;
+ assert(num_dports <= num_source_ports);
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- destination_ports[i]);
+ for (unsigned i = 0; i < num_dports; ++i) {
+ int ret = jack_connect(client, jack_port_name(ports[i]),
+ dports[i]);
if (ret != 0) {
error.Format(jack_output_domain,
- "Not a valid JACK port: %s",
- destination_ports[i]);
+ "Not a valid JACK port: %s", dports[i]);
if (jports != nullptr)
free(jports);
- mpd_jack_stop(jd);
+ Stop();
return false;
}
}
@@ -554,7 +610,7 @@ mpd_jack_start(JackOutput *jd, Error &error)
the both destination channels */
int ret;
- ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+ ret = jack_connect(client, jack_port_name(ports[0]),
duplicate_port);
if (ret != 0) {
error.Format(jack_output_domain,
@@ -564,7 +620,7 @@ mpd_jack_start(JackOutput *jd, Error &error)
if (jports != nullptr)
free(jports);
- mpd_jack_stop(jd);
+ Stop();
return false;
}
}
@@ -575,37 +631,38 @@ mpd_jack_start(JackOutput *jd, Error &error)
return true;
}
-static bool
-mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format,
- Error &error)
+inline bool
+JackOutput::Open(AudioFormat &new_audio_format, Error &error)
{
- JackOutput *jd = (JackOutput *)ao;
-
- assert(jd != nullptr);
-
- jd->pause = false;
+ pause = false;
- if (jd->client != nullptr && jd->shutdown)
- mpd_jack_disconnect(jd);
+ if (client != nullptr && shutdown)
+ Disconnect();
- if (jd->client == nullptr && !mpd_jack_connect(jd, error))
+ if (client == nullptr && !Connect(error))
return false;
- set_audioformat(jd, audio_format);
- jd->audio_format = audio_format;
+ set_audioformat(this, new_audio_format);
+ audio_format = new_audio_format;
- if (!mpd_jack_start(jd, error))
- return false;
+ return Start(error);
+}
- return true;
+static bool
+mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ JackOutput &jo = *(JackOutput *)ao;
+
+ return jo.Open(audio_format, error);
}
static void
-mpd_jack_close(gcc_unused AudioOutput *ao)
+mpd_jack_close(AudioOutput *ao)
{
- JackOutput *jd = (JackOutput *)ao;
+ JackOutput &jo = *(JackOutput *)ao;
- mpd_jack_stop(jd);
+ jo.Stop();
}
static unsigned
@@ -618,116 +675,82 @@ mpd_jack_delay(AudioOutput *ao)
: 0;
}
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
+inline size_t
+JackOutput::WriteSamples(const float *src, size_t n_frames)
{
- return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
+ assert(n_frames > 0);
-static void
-mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
- unsigned i;
-
- while (num_samples-- > 0) {
- for (i = 0; i < jd->audio_format.channels; ++i) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[i],
- (const char *)&sample,
- sizeof(sample));
- }
- }
-}
+ const unsigned n_channels = audio_format.channels;
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
+ float *dest[MAX_CHANNELS];
+ size_t space = -1;
+ for (unsigned i = 0; i < n_channels; ++i) {
+ jack_ringbuffer_data_t d[2];
+ jack_ringbuffer_get_write_vector(ringbuffer[i], d);
-static void
-mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
- unsigned i;
-
- while (num_samples-- > 0) {
- for (i = 0; i < jd->audio_format.channels; ++i) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[i],
- (const char *)&sample,
- sizeof(sample));
- }
- }
-}
+ /* choose the first non-empty writable area */
+ const jack_ringbuffer_data_t &e = d[d[0].len == 0];
-static void
-mpd_jack_write_samples(JackOutput *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.format) {
- case SampleFormat::S16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case SampleFormat::S24_P32:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- gcc_unreachable();
+ if (e.len < space)
+ /* send data symmetrically */
+ space = e.len;
+
+ dest[i] = (float *)e.buf;
}
+
+ space /= jack_sample_size;
+ if (space == 0)
+ return 0;
+
+ const size_t result = n_frames = std::min(space, n_frames);
+
+ while (n_frames-- > 0)
+ for (unsigned i = 0; i < n_channels; ++i)
+ *dest[i]++ = *src++;
+
+ const size_t per_channel_advance = result * jack_sample_size;
+ for (unsigned i = 0; i < n_channels; ++i)
+ jack_ringbuffer_write_advance(ringbuffer[i],
+ per_channel_advance);
+
+ return result;
}
-static size_t
-mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+JackOutput::Play(const void *chunk, size_t size, Error &error)
{
- JackOutput *jd = (JackOutput *)ao;
- const size_t frame_size = jd->audio_format.GetFrameSize();
- size_t space = 0, space1;
-
- jd->pause = false;
+ pause = false;
+ const size_t frame_size = audio_format.GetFrameSize();
assert(size % frame_size == 0);
size /= frame_size;
while (true) {
- if (jd->shutdown) {
+ if (shutdown) {
error.Set(jack_output_domain,
"Refusing to play, because "
"there is no client thread");
return 0;
}
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
- }
-
- if (space >= jack_sample_size)
- break;
+ size_t frames_written =
+ WriteSamples((const float *)chunk, size);
+ if (frames_written > 0)
+ return frames_written * frame_size;
/* XXX do something more intelligent to
synchronize */
- g_usleep(1000);
+ usleep(1000);
}
+}
- space /= jack_sample_size;
- if (space < size)
- size = space;
+static size_t
+mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ JackOutput &jo = *(JackOutput *)ao;
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
+ return jo.Play(chunk, size, error);
}
static bool
diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
index 39d87fc35..fc31a1511 100644
--- a/src/output/plugins/OssOutputPlugin.cxx
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -124,7 +124,7 @@ oss_stat_device(const char *device, int *errno_r)
return OSS_STAT_NO_ERROR;
}
-static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
+static const char *const default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
static bool
oss_output_test_default_device(void)
@@ -380,7 +380,7 @@ oss_setup_sample_rate(int fd, AudioFormat &audio_format,
break;
}
- static const int sample_rates[] = { 48000, 44100, 0 };
+ static constexpr int sample_rates[] = { 48000, 44100, 0 };
for (unsigned i = 0; sample_rates[i] != 0; ++i) {
sample_rate = sample_rates[i];
if (sample_rate == (int)audio_format.sample_rate)
@@ -572,7 +572,7 @@ oss_setup_sample_format(int fd, AudioFormat &audio_format,
/* the requested sample format is not available - probe for
other formats supported by MPD */
- static const SampleFormat sample_formats[] = {
+ static constexpr SampleFormat sample_formats[] = {
SampleFormat::S24_P32,
SampleFormat::S32,
SampleFormat::S16,
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
index 120bad090..5dc733383 100644
--- a/src/output/plugins/PulseOutputPlugin.cxx
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -523,7 +523,11 @@ pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
assert(po != nullptr);
assert(po->context != nullptr);
- po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
+ /* WAVE-EX is been adopted as the speaker map for most media files */
+ pa_channel_map chan_map;
+ pa_channel_map_init_auto(&chan_map, ss->channels,
+ PA_CHANNEL_MAP_WAVEEX);
+ po->stream = pa_stream_new(po->context, po->name, ss, &chan_map);
if (po->stream == nullptr) {
SetError(error, po->context, "pa_stream_new() has failed");
return false;
diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
index e5c5a6f0c..b7af0b0f8 100644
--- a/src/output/plugins/WinmmOutputPlugin.cxx
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -56,6 +56,18 @@ struct WinmmOutput {
static constexpr Domain winmm_output_domain("winmm_output");
+static void
+SetWaveOutError(Error &error, MMRESULT result, const char *prefix)
+{
+ char buffer[256];
+ if (waveOutGetErrorTextA(result, buffer,
+ ARRAY_SIZE(buffer)) == MMSYSERR_NOERROR)
+ error.Format(winmm_output_domain, int(result),
+ "%s: %s", prefix, buffer);
+ else
+ error.Set(winmm_output_domain, int(result), prefix);
+}
+
HWAVEOUT
winmm_output_get_handle(WinmmOutput &output)
{
@@ -179,7 +191,7 @@ winmm_output_open(AudioOutput *ao, AudioFormat &audio_format,
(DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
if (result != MMSYSERR_NOERROR) {
CloseHandle(wo->event);
- error.Set(winmm_output_domain, "waveOutOpen() failed");
+ SetWaveOutError(error, result, "waveOutOpen() failed");
return false;
}
@@ -225,8 +237,8 @@ winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
sizeof(buffer->hdr));
if (result != MMSYSERR_NOERROR) {
- error.Set(winmm_output_domain, result,
- "waveOutPrepareHeader() failed");
+ SetWaveOutError(error, result,
+ "waveOutPrepareHeader() failed");
return false;
}
@@ -251,8 +263,8 @@ winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
if (result == MMSYSERR_NOERROR)
return true;
else if (result != WAVERR_STILLPLAYING) {
- error.Set(winmm_output_domain, result,
- "waveOutUnprepareHeader() failed");
+ SetWaveOutError(error, result,
+ "waveOutUnprepareHeader() failed");
return false;
}
@@ -278,8 +290,7 @@ winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error)
if (result != MMSYSERR_NOERROR) {
waveOutUnprepareHeader(wo->handle, &buffer->hdr,
sizeof(buffer->hdr));
- error.Set(winmm_output_domain, result,
- "waveOutWrite() failed");
+ SetWaveOutError(error, result, "waveOutWrite() failed");
return 0;
}
diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
index 20ff15e42..303170268 100644
--- a/src/output/plugins/httpd/HttpdInternal.hxx
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -153,7 +153,7 @@ public:
HttpdOutput(EventLoop &_loop);
~HttpdOutput();
-#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#if CLANG_OR_GCC_VERSION(4,7)
constexpr
#endif
static HttpdOutput *Cast(AudioOutput *ao) {
diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx
index 146df23d1..108d4e7ec 100644
--- a/src/output/plugins/httpd/IcyMetaDataServer.cxx
+++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx
@@ -22,8 +22,8 @@
#include "Page.hxx"
#include "tag/Tag.hxx"
#include "util/FormatString.hxx"
-
-#include <glib.h>
+#include "util/StringUtil.hxx"
+#include "util/Macros.hxx"
#include <string.h>
@@ -57,16 +57,13 @@ icy_server_metadata_header(const char *name,
static char *
icy_server_metadata_string(const char *stream_title, const char* stream_url)
{
- gchar *icy_metadata;
- guint meta_length;
-
// The leading n is a placeholder for the length information
- icy_metadata = FormatNew("nStreamTitle='%s';"
- "StreamUrl='%s';",
- stream_title,
- stream_url);
+ char *icy_metadata = FormatNew("nStreamTitle='%s';"
+ "StreamUrl='%s';",
+ stream_title,
+ stream_url);
- meta_length = strlen(icy_metadata);
+ size_t meta_length = strlen(icy_metadata);
meta_length--; // subtract placeholder
@@ -85,43 +82,30 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
Page *
icy_server_metadata_page(const Tag &tag, const TagType *types)
{
- const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES];
- gint last_item, item;
- guint position;
- gchar *icy_string;
- gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
- // "StreamTitle='';StreamUrl='';"
- // = 4081 - 28
- stream_title[0] = '\0';
-
- last_item = -1;
+ const char *tag_items[TAG_NUM_OF_ITEM_TYPES];
+ int last_item = -1;
while (*types != TAG_NUM_OF_ITEM_TYPES) {
- const gchar *tag_item = tag.GetValue(*types++);
+ const char *tag_item = tag.GetValue(*types++);
if (tag_item)
tag_items[++last_item] = tag_item;
}
- position = item = 0;
- while (position < sizeof(stream_title) && item <= last_item) {
- gint length = 0;
-
- length = g_strlcpy(stream_title + position,
- tag_items[item++],
- sizeof(stream_title) - position);
+ int item = 0;
- position += length;
+ // Length + Metadata - "StreamTitle='';StreamUrl='';" = 4081 - 28
+ char stream_title[(1 + 255 - 28) * 16];
+ char *p = stream_title, *const end = stream_title + ARRAY_SIZE(stream_title);
+ stream_title[0] = '\0';
- if (item <= last_item) {
- length = g_strlcpy(stream_title + position,
- " - ",
- sizeof(stream_title) - position);
+ while (p < end && item <= last_item) {
+ p = CopyString(p, tag_items[item++], end - p);
- position += length;
- }
+ if (item <= last_item)
+ p = CopyString(p, " - ", end - p);
}
- icy_string = icy_server_metadata_string(stream_title, "");
+ char *icy_string = icy_server_metadata_string(stream_title, "");
if (icy_string == nullptr)
return nullptr;
diff --git a/src/pcm/ConfiguredResampler.cxx b/src/pcm/ConfiguredResampler.cxx
index f6aec3f95..a65ec8702 100644
--- a/src/pcm/ConfiguredResampler.cxx
+++ b/src/pcm/ConfiguredResampler.cxx
@@ -25,11 +25,11 @@
#include "config/ConfigError.hxx"
#include "util/Error.hxx"
-#ifdef HAVE_LIBSAMPLERATE
+#ifdef ENABLE_LIBSAMPLERATE
#include "LibsamplerateResampler.hxx"
#endif
-#ifdef HAVE_SOXR
+#ifdef ENABLE_SOXR
#include "SoxrResampler.hxx"
#endif
@@ -38,11 +38,11 @@
enum class SelectedResampler {
FALLBACK,
-#ifdef HAVE_LIBSAMPLERATE
+#ifdef ENABLE_LIBSAMPLERATE
LIBSAMPLERATE,
#endif
-#ifdef HAVE_SOXR
+#ifdef ENABLE_SOXR
SOXR,
#endif
};
@@ -58,14 +58,14 @@ pcm_resampler_global_init(Error &error)
if (strcmp(converter, "internal") == 0)
return true;
-#ifdef HAVE_SOXR
+#ifdef ENABLE_SOXR
if (memcmp(converter, "soxr", 4) == 0) {
selected_resampler = SelectedResampler::SOXR;
return pcm_resample_soxr_global_init(converter, error);
}
#endif
-#ifdef HAVE_LIBSAMPLERATE
+#ifdef ENABLE_LIBSAMPLERATE
selected_resampler = SelectedResampler::LIBSAMPLERATE;
return pcm_resample_lsr_global_init(converter, error);
#endif
@@ -86,12 +86,12 @@ pcm_resampler_create()
case SelectedResampler::FALLBACK:
return new FallbackPcmResampler();
-#ifdef HAVE_LIBSAMPLERATE
+#ifdef ENABLE_LIBSAMPLERATE
case SelectedResampler::LIBSAMPLERATE:
return new LibsampleratePcmResampler();
#endif
-#ifdef HAVE_SOXR
+#ifdef ENABLE_SOXR
case SelectedResampler::SOXR:
return new SoxrPcmResampler();
#endif
diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx
index 4e9ef890e..ad0afa628 100644
--- a/src/playlist/PlaylistRegistry.cxx
+++ b/src/playlist/PlaylistRegistry.cxx
@@ -45,11 +45,8 @@
const struct playlist_plugin *const playlist_plugins[] = {
&extm3u_playlist_plugin,
&m3u_playlist_plugin,
-#ifdef HAVE_GLIB
- // TODO: enable without GLib
&pls_playlist_plugin,
-#endif
-#ifdef HAVE_EXPAT
+#ifdef ENABLE_EXPAT
&xspf_playlist_plugin,
&asx_playlist_plugin,
&rss_playlist_plugin,
@@ -60,8 +57,10 @@ const struct playlist_plugin *const playlist_plugins[] = {
#ifdef ENABLE_SOUNDCLOUD
&soundcloud_playlist_plugin,
#endif
+#ifdef ENABLE_CUE
&cue_playlist_plugin,
&embcue_playlist_plugin,
+#endif
nullptr
};
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx
index 3185a8144..59943681a 100644
--- a/src/playlist/plugins/AsxPlaylistPlugin.cxx
+++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx
@@ -28,7 +28,7 @@
#include "Log.hxx"
/**
- * This is the state object for the GLib XML parser.
+ * This is the state object for our XML parser.
*/
struct AsxParser {
/**
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx
index f7724f522..4f7c5a1ff 100644
--- a/src/playlist/plugins/PlsPlaylistPlugin.cxx
+++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx
@@ -21,121 +21,142 @@
#include "PlsPlaylistPlugin.hxx"
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
-#include "input/InputStream.hxx"
+#include "input/TextInputStream.hxx"
#include "DetachedSong.hxx"
#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/StringUtil.hxx"
+#include "util/DivideString.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
#include <string>
-#include <stdio.h>
-#include <stdio.h>
+#include <stdlib.h>
static constexpr Domain pls_domain("pls");
-static void
-pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
+static bool
+FindPlaylistSection(TextInputStream &is)
{
- gchar *value;
- GError *error = nullptr;
- int num_entries = g_key_file_get_integer(keyfile, "playlist",
- "NumberOfEntries", &error);
- if (error) {
- FormatError(pls_domain,
- "Invalid PLS file: '%s'", error->message);
- g_error_free(error);
- error = nullptr;
-
- /* Hack to work around shoutcast failure to comform to spec */
- num_entries = g_key_file_get_integer(keyfile, "playlist",
- "numberofentries", &error);
- if (error) {
- g_error_free(error);
- error = nullptr;
- }
+ char *line;
+ while ((line = is.ReadLine()) != nullptr) {
+ line = Strip(line);
+ if (StringEqualsCaseASCII(line, "[playlist]"))
+ return true;
}
- for (; num_entries > 0; --num_entries) {
- char key[64];
- sprintf(key, "File%u", num_entries);
- char *uri = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error) {
- FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
- key, error->message);
- g_error_free(error);
- return;
- }
+ return false;
+}
- TagBuilder tag;
+static bool
+ParsePls(TextInputStream &is, std::forward_list<DetachedSong> &songs)
+{
+ assert(songs.empty());
- sprintf(key, "Title%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- nullptr);
- if (value != nullptr)
- tag.AddItem(TAG_TITLE, value);
+ if (!FindPlaylistSection(is))
+ return false;
- g_free(value);
+ unsigned n_entries = 0;
- sprintf(key, "Length%u", num_entries);
- int length = g_key_file_get_integer(keyfile, "playlist", key,
- nullptr);
- if (length > 0)
- tag.SetDuration(SignedSongTime::FromS(length));
+ struct Entry {
+ std::string file, title;
+ int length;
- songs.emplace_front(uri, tag.Commit());
- g_free(uri);
- }
+ Entry():length(-1) {}
+ };
-}
+ static constexpr unsigned MAX_ENTRIES = 65536;
-static SongEnumerator *
-pls_open_stream(InputStream &is)
-{
- GError *error = nullptr;
- Error error2;
-
- std::string kf_data;
-
- do {
- char buffer[1024];
- size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- LogError(error2);
- return nullptr;
- }
+ std::vector<Entry> entries;
+
+ char *line;
+ while ((line = is.ReadLine()) != nullptr) {
+ line = Strip(line);
+ if (*line == 0 || *line == ';')
+ continue;
+
+ if (*line == '[')
+ /* another section starts; we only want
+ [Playlist], so stop here */
break;
+
+ const DivideString ds(line, '=', true);
+ if (!ds.IsDefined())
+ continue;
+
+ const char *const name = ds.GetFirst();
+ const char *const value = ds.GetSecond();
+
+ if (StringEqualsCaseASCII(name, "NumberOfEntries")) {
+ n_entries = strtoul(value, nullptr, 10);
+ if (n_entries == 0)
+ /* empty file - nothing remains to be
+ done */
+ return true;
+
+ if (n_entries > MAX_ENTRIES)
+ n_entries = MAX_ENTRIES;
+ entries.resize(n_entries);
+ } else if (StringEqualsCaseASCII(name, "File", 4)) {
+ unsigned i = strtoul(name + 4, nullptr, 10);
+ if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) {
+ if (entries.size() < i)
+ entries.resize(i);
+ entries[i - 1].file = value;
+ }
+ } else if (StringEqualsCaseASCII(name, "Title", 5)) {
+ unsigned i = strtoul(name + 5, nullptr, 10);
+ if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) {
+ if (entries.size() < i)
+ entries.resize(i);
+ entries[i - 1].title = value;
+ }
+ } else if (StringEqualsCaseASCII(name, "Length", 6)) {
+ unsigned i = strtoul(name + 6, nullptr, 10);
+ if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) {
+ if (entries.size() < i)
+ entries.resize(i);
+ entries[i - 1].length = atoi(value);
+ }
}
+ }
- kf_data.append(buffer, nbytes);
- /* Limit to 64k */
- } while (kf_data.length() < 65536);
+ if (n_entries == 0)
+ /* no "NumberOfEntries" found */
+ return false;
- if (kf_data.empty()) {
- LogWarning(pls_domain, "KeyFile parser failed: No Data");
- return nullptr;
- }
+ auto i = songs.before_begin();
+ for (const auto &entry : entries) {
+ const char *uri = entry.file.c_str();
- GKeyFile *keyfile = g_key_file_new();
- if (!g_key_file_load_from_data(keyfile,
- kf_data.data(), kf_data.length(),
- G_KEY_FILE_NONE, &error)) {
- FormatError(pls_domain,
- "KeyFile parser failed: %s", error->message);
- g_error_free(error);
- g_key_file_free(keyfile);
- return nullptr;
+ TagBuilder tag;
+ if (!entry.title.empty())
+ tag.AddItem(TAG_TITLE, entry.title.c_str());
+
+ if (entry.length > 0)
+ tag.SetDuration(SignedSongTime::FromS(entry.length));
+
+ i = songs.emplace_after(i, uri, tag.Commit());
}
+ return true;
+}
+
+static bool
+ParsePls(InputStream &is, std::forward_list<DetachedSong> &songs)
+{
+ TextInputStream tis(is);
+ return ParsePls(tis, songs);
+}
+
+static SongEnumerator *
+pls_open_stream(InputStream &is)
+{
std::forward_list<DetachedSong> songs;
- pls_parser(keyfile, songs);
- g_key_file_free(keyfile);
+ if (!ParsePls(is, songs))
+ return nullptr;
return new MemorySongEnumerator(std::move(songs));
}
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
index 6f9aad54b..3d32b29ca 100644
--- a/src/playlist/plugins/RssPlaylistPlugin.cxx
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -28,7 +28,7 @@
#include "Log.hxx"
/**
- * This is the state object for the GLib XML parser.
+ * This is the state object for the our XML parser.
*/
struct RssParser {
/**
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
index ec4d240a5..6be9c65ab 100644
--- a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -25,16 +25,17 @@
#include "input/InputStream.hxx"
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
+#include "util/Alloc.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <yajl/yajl_parse.h>
#include <string>
#include <string.h>
+#include <stdlib.h>
static struct {
std::string apikey;
@@ -60,7 +61,7 @@ soundcloud_init(const config_param &param)
/**
* Construct a full soundcloud resolver URL from the given fragment.
* @param uri uri of a soundcloud page (or just the path)
- * @return Constructed URL. Must be freed with g_free.
+ * @return Constructed URL. Must be freed with free().
*/
static char *
soundcloud_resolve(const char* uri)
@@ -68,18 +69,18 @@ soundcloud_resolve(const char* uri)
char *u, *ru;
if (StringStartsWith(uri, "https://")) {
- u = g_strdup(uri);
+ u = xstrdup(uri);
} else if (StringStartsWith(uri, "soundcloud.com")) {
- u = g_strconcat("https://", uri, nullptr);
+ u = xstrcatdup("https://", uri);
} else {
/* assume it's just a path on soundcloud.com */
- u = g_strconcat("https://soundcloud.com/", uri, nullptr);
+ u = xstrcatdup("https://soundcloud.com/", uri);
}
- ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
- u, "&client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- g_free(u);
+ ru = xstrcatdup("https://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=",
+ soundcloud_config.apikey.c_str());
+ free(u);
return ru;
}
@@ -111,12 +112,7 @@ struct parse_data {
};
static int
-handle_integer(void *ctx,
- long
-#ifndef HAVE_YAJL1
- long
-#endif
- intval)
+handle_integer(void *ctx, long long intval)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -132,25 +128,19 @@ handle_integer(void *ctx,
}
static int
-handle_string(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
+handle_string(void *ctx, const unsigned char *stringval, size_t stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
const char *s = (const char *) stringval;
switch (data->key) {
case Title:
- g_free(data->title);
- data->title = g_strndup(s, stringlen);
+ free(data->title);
+ data->title = xstrndup(s, stringlen);
break;
case Stream_URL:
- g_free(data->stream_url);
- data->stream_url = g_strndup(s, stringlen);
+ free(data->stream_url);
+ data->stream_url = xstrndup(s, stringlen);
data->got_url = 1;
break;
default:
@@ -161,13 +151,7 @@ handle_string(void *ctx, const unsigned char* stringval,
}
static int
-handle_mapkey(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
+handle_mapkey(void *ctx, const unsigned char *stringval, size_t stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -211,8 +195,8 @@ handle_end_map(void *ctx)
/* got_url == 1, track finished, make it into a song */
data->got_url = 0;
- char *u = g_strconcat(data->stream_url, "?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ char *u = xstrcatdup(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str());
TagBuilder tag;
tag.SetDuration(SignedSongTime::FromMS(data->duration));
@@ -220,7 +204,7 @@ handle_end_map(void *ctx)
tag.AddItem(TAG_NAME, data->title);
data->songs.emplace_front(u, tag.Commit());
- g_free(u);
+ free(u);
return 1;
}
@@ -282,20 +266,11 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
}
if (done) {
-#ifdef HAVE_YAJL1
- stat = yajl_parse_complete(hand);
-#else
stat = yajl_complete_parse(hand);
-#endif
} else
stat = yajl_parse(hand, ubuffer, nbytes);
- if (stat != yajl_status_ok
-#ifdef HAVE_YAJL1
- && stat != yajl_status_insufficient_data
-#endif
- )
- {
+ if (stat != yajl_status_ok) {
unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
LogError(soundcloud_domain, (const char *)str);
yajl_free_error(hand, str);
@@ -325,24 +300,24 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
char *u = nullptr;
if (memcmp(uri, "track/", 6) == 0) {
const char *rest = uri + 6;
- u = g_strconcat("https://api.soundcloud.com/tracks/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ u = xstrcatdup("https://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str());
} else if (memcmp(uri, "playlist/", 9) == 0) {
const char *rest = uri + 9;
- u = g_strconcat("https://api.soundcloud.com/playlists/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ u = xstrcatdup("https://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str());
} else if (memcmp(uri, "user/", 5) == 0) {
const char *rest = uri + 5;
- u = g_strconcat("https://api.soundcloud.com/users/",
- rest, "/tracks.json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ u = xstrcatdup("https://api.soundcloud.com/users/",
+ rest, "/tracks.json?client_id=",
+ soundcloud_config.apikey.c_str());
} else if (memcmp(uri, "search/", 7) == 0) {
const char *rest = uri + 7;
- u = g_strconcat("https://api.soundcloud.com/tracks.json?q=",
- rest, "&client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ u = xstrcatdup("https://api.soundcloud.com/tracks.json?q=",
+ rest, "&client_id=",
+ soundcloud_config.apikey.c_str());
} else if (memcmp(uri, "url/", 4) == 0) {
const char *rest = uri + 4;
/* Translate to soundcloud resolver call. libcurl will automatically
@@ -359,19 +334,14 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
data.got_url = 0;
data.title = nullptr;
data.stream_url = nullptr;
-#ifdef HAVE_YAJL1
- yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
- &data);
-#else
yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
-#endif
int ret = soundcloud_parse_json(u, hand, mutex, cond);
- g_free(u);
+ free(u);
yajl_free(hand);
- g_free(data.title);
- g_free(data.stream_url);
+ free(data.title);
+ free(data.stream_url);
if (ret == -1)
return nullptr;
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
index 5b6010b53..01a157ba8 100644
--- a/src/playlist/plugins/XspfPlaylistPlugin.cxx
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -34,7 +34,7 @@
static constexpr Domain xspf_domain("xspf");
/**
- * This is the state object for the GLib XML parser.
+ * This is the state object for our XML parser.
*/
struct XspfParser {
/**
diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx
index b2fd673b4..60588ab90 100644
--- a/src/queue/Playlist.cxx
+++ b/src/queue/Playlist.cxx
@@ -44,45 +44,52 @@ playlist::TagModified(DetachedSong &&song)
idle_add(IDLE_PLAYLIST);
}
-/**
- * Queue a song, addressed by its order number.
- */
-static void
-playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
- unsigned order)
+inline void
+playlist::QueueSongOrder(PlayerControl &pc, unsigned order)
+
{
- assert(playlist.queue.IsValidOrder(order));
+ assert(queue.IsValidOrder(order));
- playlist.queued = order;
+ queued = order;
- const DetachedSong &song = playlist.queue.GetOrder(order);
+ const DetachedSong &song = queue.GetOrder(order);
FormatDebug(playlist_domain, "queue song %i:\"%s\"",
- playlist.queued, song.GetURI());
+ queued, song.GetURI());
pc.EnqueueSong(new DetachedSong(song));
}
-/**
- * Called if the player thread has started playing the "queued" song.
- */
-static void
-playlist_song_started(playlist &playlist, PlayerControl &pc)
+void
+playlist::SongStarted()
+{
+ assert(current >= 0);
+
+ /* reset a song's "priority" when playback starts */
+ if (queue.SetPriority(queue.OrderToPosition(current), 0, -1, false))
+ OnModified();
+}
+
+inline void
+playlist::QueuedSongStarted(PlayerControl &pc)
{
assert(pc.next_song == nullptr);
- assert(playlist.queued >= -1);
+ assert(queued >= -1);
+ assert(current >= 0);
/* queued song has started: copy queued to current,
and notify the clients */
- int current = playlist.current;
- playlist.current = playlist.queued;
- playlist.queued = -1;
+ const int old_current = current;
+ current = queued;
+ queued = -1;
- if(playlist.queue.consume)
- playlist.DeleteOrder(pc, current);
+ if (queue.consume)
+ DeleteOrder(pc, old_current);
idle_add(IDLE_PLAYER);
+
+ SongStarted();
}
const DetachedSong *
@@ -139,7 +146,7 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
if (next_order >= 0) {
if (next_song != prev)
- playlist_queue_song_order(*this, pc, next_order);
+ QueueSongOrder(pc, next_order);
else
queued = next_order;
}
@@ -157,10 +164,9 @@ playlist::PlayOrder(PlayerControl &pc, int order)
pc.Play(new DetachedSong(song));
current = order;
-}
-static void
-playlist_resume_playback(playlist &playlist, PlayerControl &pc);
+ SongStarted();
+}
void
playlist::SyncWithPlayer(PlayerControl &pc)
@@ -180,12 +186,12 @@ playlist::SyncWithPlayer(PlayerControl &pc)
should be restarted with the next song. That can
happen if the playlist isn't filling the queue fast
enough */
- playlist_resume_playback(*this, pc);
+ ResumePlayback(pc);
else {
/* check if the player thread has already started
playing the queued song */
if (pc_next_song == nullptr && queued != -1)
- playlist_song_started(*this, pc);
+ QueuedSongStarted(pc);
pc.Lock();
pc_next_song = pc.next_song;
@@ -198,31 +204,27 @@ playlist::SyncWithPlayer(PlayerControl &pc)
}
}
-/**
- * The player has stopped for some reason. Check the error, and
- * decide whether to re-start playback
- */
-static void
-playlist_resume_playback(playlist &playlist, PlayerControl &pc)
+inline void
+playlist::ResumePlayback(PlayerControl &pc)
{
- assert(playlist.playing);
+ assert(playing);
assert(pc.GetState() == PlayerState::STOP);
const auto error = pc.GetErrorType();
if (error == PlayerError::NONE)
- playlist.error_count = 0;
+ error_count = 0;
else
- ++playlist.error_count;
+ ++error_count;
- if ((playlist.stop_on_error && error != PlayerError::NONE) ||
+ if ((stop_on_error && error != PlayerError::NONE) ||
error == PlayerError::OUTPUT ||
- playlist.error_count >= playlist.queue.GetLength())
+ error_count >= queue.GetLength())
/* too many errors, or critical error: stop
playback */
- playlist.Stop(pc);
+ Stop(pc);
else
/* continue playback at the next song */
- playlist.PlayNext(pc);
+ PlayNext(pc);
}
void
diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx
index ea19d9bba..ab8fbe2d4 100644
--- a/src/queue/Playlist.hxx
+++ b/src/queue/Playlist.hxx
@@ -135,6 +135,17 @@ protected:
void OnModified();
/**
+ * Called when playback of a new song starts. Unlike
+ * QueuedSongStarted(), this also gets called when the user
+ * manually switches to another song. It may be used for
+ * playlist fixups.
+ *
+ * The song being started is specified by the #current
+ * attribute.
+ */
+ void SongStarted();
+
+ /**
* Updates the "queued song". Calculates the next song
* according to the current one (if MPD isn't playing, it
* takes the first song), and queues this song. Clears the
@@ -145,6 +156,24 @@ protected:
*/
void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev);
+ /**
+ * Queue a song, addressed by its order number.
+ */
+ void QueueSongOrder(PlayerControl &pc, unsigned order);
+
+ /**
+ * Called when the player thread has started playing the
+ * "queued" song, i.e. it has switched from one song to the
+ * next automatically.
+ */
+ void QueuedSongStarted(PlayerControl &pc);
+
+ /**
+ * The player has stopped for some reason. Check the error,
+ * and decide whether to re-start playback.
+ */
+ void ResumePlayback(PlayerControl &pc);
+
public:
void BeginBulk();
void CommitBulk(PlayerControl &pc);
diff --git a/src/queue/Queue.cxx b/src/queue/Queue.cxx
index 99b545ab1..039af096e 100644
--- a/src/queue/Queue.cxx
+++ b/src/queue/Queue.cxx
@@ -402,7 +402,8 @@ Queue::CountSamePriority(unsigned start_order, uint8_t priority) const
}
bool
-Queue::SetPriority(unsigned position, uint8_t priority, int after_order)
+Queue::SetPriority(unsigned position, uint8_t priority, int after_order,
+ bool reorder)
{
assert(position < length);
@@ -414,7 +415,7 @@ Queue::SetPriority(unsigned position, uint8_t priority, int after_order)
item->version = version;
item->priority = priority;
- if (!random)
+ if (!random || !reorder)
/* don't reorder if not in random mode */
return true;
diff --git a/src/queue/Queue.hxx b/src/queue/Queue.hxx
index 016619e65..32acac689 100644
--- a/src/queue/Queue.hxx
+++ b/src/queue/Queue.hxx
@@ -340,10 +340,13 @@ struct Queue {
/**
* Shuffles a (position) range in the queue. The songs are physically
* shuffled, not by using the "order" mapping.
+ *
+ * @param reorder false to suppress updating the order list
*/
void ShuffleRange(unsigned start, unsigned end);
- bool SetPriority(unsigned position, uint8_t priority, int after_order);
+ bool SetPriority(unsigned position, uint8_t priority, int after_order,
+ bool reorder=true);
bool SetPriorityRange(unsigned start_position, unsigned end_position,
uint8_t priority, int after_order);
diff --git a/src/sticker/Match.hxx b/src/sticker/Match.hxx
new file mode 100644
index 000000000..a10a3a805
--- /dev/null
+++ b/src/sticker/Match.hxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STICKER_MATCH_HXX
+#define MPD_STICKER_MATCH_HXX
+
+enum class StickerOperator {
+ /**
+ * Matches if a sticker with the specified name exists. The
+ * "value" parameter is ignored (must be nullptr).
+ */
+ EXISTS,
+
+ /**
+ * Matches if a sticker with the specified name and value
+ * exists.
+ */
+ EQUALS,
+
+ /**
+ * Matches if a sticker with the specified name exists with a
+ * value smaller than the specified one.
+ */
+ LESS_THAN,
+
+ /**
+ * Matches if a sticker with the specified name exists with a
+ * value bigger than the specified one.
+ */
+ GREATER_THAN,
+};
+
+#endif
diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx
index b6f46f167..5e5793b70 100644
--- a/src/sticker/SongSticker.cxx
+++ b/src/sticker/SongSticker.cxx
@@ -23,46 +23,48 @@
#include "db/LightSong.hxx"
#include "db/Interface.hxx"
#include "util/Error.hxx"
-
-#include <glib.h>
+#include "util/Alloc.hxx"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
std::string
-sticker_song_get_value(const LightSong &song, const char *name)
+sticker_song_get_value(const LightSong &song, const char *name, Error &error)
{
const auto uri = song.GetURI();
- return sticker_load_value("song", uri.c_str(), name);
+ return sticker_load_value("song", uri.c_str(), name, error);
}
bool
sticker_song_set_value(const LightSong &song,
- const char *name, const char *value)
+ const char *name, const char *value,
+ Error &error)
{
const auto uri = song.GetURI();
- return sticker_store_value("song", uri.c_str(), name, value);
+ return sticker_store_value("song", uri.c_str(), name, value, error);
}
bool
-sticker_song_delete(const LightSong &song)
+sticker_song_delete(const LightSong &song, Error &error)
{
const auto uri = song.GetURI();
- return sticker_delete("song", uri.c_str());
+ return sticker_delete("song", uri.c_str(), error);
}
bool
-sticker_song_delete_value(const LightSong &song, const char *name)
+sticker_song_delete_value(const LightSong &song, const char *name,
+ Error &error)
{
const auto uri = song.GetURI();
- return sticker_delete_value("song", uri.c_str(), name);
+ return sticker_delete_value("song", uri.c_str(), name, error);
}
struct sticker *
-sticker_song_get(const LightSong &song)
+sticker_song_get(const LightSong &song, Error &error)
{
const auto uri = song.GetURI();
- return sticker_load("song", uri.c_str());
+ return sticker_load("song", uri.c_str(), error);
}
struct sticker_song_find_data {
@@ -95,9 +97,11 @@ sticker_song_find_cb(const char *uri, const char *value, void *user_data)
bool
sticker_song_find(const Database &db, const char *base_uri, const char *name,
+ StickerOperator op, const char *value,
void (*func)(const LightSong &song, const char *value,
void *user_data),
- void *user_data)
+ void *user_data,
+ Error &error)
{
struct sticker_song_find_data data;
data.db = &db;
@@ -109,16 +113,17 @@ sticker_song_find(const Database &db, const char *base_uri, const char *name,
if (*data.base_uri != 0)
/* append slash to base_uri */
data.base_uri = allocated =
- g_strconcat(data.base_uri, "/", nullptr);
+ xstrcatdup(data.base_uri, "/");
else
/* searching in root directory - no trailing slash */
allocated = nullptr;
data.base_uri_length = strlen(data.base_uri);
- bool success = sticker_find("song", data.base_uri, name,
- sticker_song_find_cb, &data);
- g_free(allocated);
+ bool success = sticker_find("song", data.base_uri, name, op, value,
+ sticker_song_find_cb, &data,
+ error);
+ free(allocated);
return success;
}
diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx
index 5956cd6f9..ec4f1b344 100644
--- a/src/sticker/SongSticker.hxx
+++ b/src/sticker/SongSticker.hxx
@@ -20,6 +20,7 @@
#ifndef MPD_SONG_STICKER_HXX
#define MPD_SONG_STICKER_HXX
+#include "Match.hxx"
#include "Compiler.h"
#include <string>
@@ -27,6 +28,7 @@
struct LightSong;
struct sticker;
class Database;
+class Error;
/**
* Returns one value from a song's sticker record. The caller must
@@ -34,7 +36,7 @@ class Database;
*/
gcc_pure
std::string
-sticker_song_get_value(const LightSong &song, const char *name);
+sticker_song_get_value(const LightSong &song, const char *name, Error &error);
/**
* Sets a sticker value in the specified song. Overwrites existing
@@ -42,20 +44,22 @@ sticker_song_get_value(const LightSong &song, const char *name);
*/
bool
sticker_song_set_value(const LightSong &song,
- const char *name, const char *value);
+ const char *name, const char *value,
+ Error &error);
/**
* Deletes a sticker from the database. All values are deleted.
*/
bool
-sticker_song_delete(const LightSong &song);
+sticker_song_delete(const LightSong &song, Error &error);
/**
* Deletes a sticker value. Does nothing if the sticker did not
* exist.
*/
bool
-sticker_song_delete_value(const LightSong &song, const char *name);
+sticker_song_delete_value(const LightSong &song, const char *name,
+ Error &error);
/**
* Loads the sticker for the specified song.
@@ -64,7 +68,7 @@ sticker_song_delete_value(const LightSong &song, const char *name);
* @return a sticker object, or NULL on error or if there is no sticker
*/
sticker *
-sticker_song_get(const LightSong &song);
+sticker_song_get(const LightSong &song, Error &error);
/**
* Finds stickers with the specified name below the specified
@@ -79,8 +83,10 @@ sticker_song_get(const LightSong &song);
*/
bool
sticker_song_find(const Database &db, const char *base_uri, const char *name,
+ StickerOperator op, const char *value,
void (*func)(const LightSong &song, const char *value,
void *user_data),
- void *user_data);
+ void *user_data,
+ Error &error);
#endif
diff --git a/src/sticker/StickerDatabase.cxx b/src/sticker/StickerDatabase.cxx
index 93eaa900d..df6dc22dd 100644
--- a/src/sticker/StickerDatabase.cxx
+++ b/src/sticker/StickerDatabase.cxx
@@ -19,23 +19,18 @@
#include "config.h"
#include "StickerDatabase.hxx"
+#include "lib/sqlite/Domain.hxx"
+#include "lib/sqlite/Util.hxx"
#include "fs/Path.hxx"
#include "Idle.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include "util/Macros.hxx"
-#include "Log.hxx"
#include <string>
#include <map>
-#include <sqlite3.h>
#include <assert.h>
-#if SQLITE_VERSION_NUMBER < 3003009
-#define sqlite3_prepare_v2 sqlite3_prepare
-#endif
-
struct sticker {
std::map<std::string, std::string> table;
};
@@ -48,6 +43,9 @@ enum sticker_sql {
STICKER_SQL_DELETE,
STICKER_SQL_DELETE_VALUE,
STICKER_SQL_FIND,
+ STICKER_SQL_FIND_VALUE,
+ STICKER_SQL_FIND_LT,
+ STICKER_SQL_FIND_GT,
};
static const char *const sticker_sql[] = {
@@ -65,6 +63,15 @@ static const char *const sticker_sql[] = {
"DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
//[STICKER_SQL_FIND] =
"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
+
+ //[STICKER_SQL_FIND_VALUE] =
+ "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?",
+
+ //[STICKER_SQL_FIND_LT] =
+ "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value<?",
+
+ //[STICKER_SQL_FIND_GT] =
+ "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value>?",
};
static const char sticker_sql_create[] =
@@ -81,23 +88,13 @@ static const char sticker_sql_create[] =
static sqlite3 *sticker_db;
static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)];
-static constexpr Domain sticker_domain("sticker");
-
-static void
-LogError(sqlite3 *db, const char *msg)
-{
- FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db));
-}
-
static sqlite3_stmt *
sticker_prepare(const char *sql, Error &error)
{
- int ret;
sqlite3_stmt *stmt;
-
- ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr);
+ int ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr);
if (ret != SQLITE_OK) {
- error.Format(sticker_domain, ret,
+ error.Format(sqlite_domain, ret,
"sqlite3_prepare_v2() failed: %s",
sqlite3_errmsg(sticker_db));
return nullptr;
@@ -118,9 +115,9 @@ sticker_global_init(Path path, Error &error)
ret = sqlite3_open(path.c_str(), &sticker_db);
if (ret != SQLITE_OK) {
const std::string utf8 = path.ToUTF8();
- error.Format(sticker_domain, ret,
- "Failed to open sqlite database '%s': %s",
- utf8.c_str(), sqlite3_errmsg(sticker_db));
+ error.Format(sqlite_domain, ret,
+ "Failed to open sqlite database '%s': %s",
+ utf8.c_str(), sqlite3_errmsg(sticker_db));
return false;
}
@@ -129,7 +126,7 @@ sticker_global_init(Path path, Error &error)
ret = sqlite3_exec(sticker_db, sticker_sql_create,
nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
- error.Format(sticker_domain, ret,
+ error.Format(sqlite_domain, ret,
"Failed to create sticker table: %s",
sqlite3_errmsg(sticker_db));
return false;
@@ -149,7 +146,7 @@ sticker_global_init(Path path, Error &error)
}
void
-sticker_global_finish(void)
+sticker_global_finish()
{
if (sticker_db == nullptr)
/* not configured */
@@ -165,16 +162,16 @@ sticker_global_finish(void)
}
bool
-sticker_enabled(void)
+sticker_enabled()
{
return sticker_db != nullptr;
}
std::string
-sticker_load_value(const char *type, const char *uri, const char *name)
+sticker_load_value(const char *type, const char *uri, const char *name,
+ Error &error)
{
sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
- int ret;
assert(sticker_enabled());
assert(type != nullptr);
@@ -184,40 +181,12 @@ sticker_load_value(const char *type, const char *uri, const char *name)
if (*name == 0)
return std::string();
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return std::string();
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return std::string();
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ if (!BindAll(error, stmt, type, uri, name))
return std::string();
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
std::string value;
- if (ret == SQLITE_ROW) {
- /* record found */
+ if (ExecuteRow(stmt, error))
value = (const char*)sqlite3_column_text(stmt, 0);
- } else if (ret == SQLITE_DONE) {
- /* no record found */
- } else {
- /* error */
- LogError(sticker_db, "sqlite3_step() failed");
- }
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
@@ -227,63 +196,36 @@ sticker_load_value(const char *type, const char *uri, const char *name)
static bool
sticker_list_values(std::map<std::string, std::string> &table,
- const char *type, const char *uri)
+ const char *type, const char *uri,
+ Error &error)
{
sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST];
- int ret;
assert(type != nullptr);
assert(uri != nullptr);
assert(sticker_enabled());
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ if (!BindAll(error, stmt, type, uri))
return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- switch (ret) {
- const char *name, *value;
-
- case SQLITE_ROW:
- name = (const char*)sqlite3_column_text(stmt, 0);
- value = (const char*)sqlite3_column_text(stmt, 1);
+ const bool success = ExecuteForEach(stmt, error, [stmt, &table](){
+ const char *name = (const char *)sqlite3_column_text(stmt, 0);
+ const char *value = (const char *)sqlite3_column_text(stmt, 1);
table.insert(std::make_pair(name, value));
- break;
- case SQLITE_DONE:
- break;
- case SQLITE_BUSY:
- /* no op */
- break;
- default:
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
- } while (ret != SQLITE_DONE);
+ });
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
- return true;
+ return success;
}
static bool
sticker_update_value(const char *type, const char *uri,
- const char *name, const char *value)
+ const char *name, const char *value,
+ Error &error)
{
sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE];
- int ret;
assert(type != nullptr);
assert(uri != nullptr);
@@ -293,56 +235,25 @@ sticker_update_value(const char *type, const char *uri,
assert(sticker_enabled());
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ if (!BindAll(error, stmt, value, type, uri, name))
return false;
- }
- ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
-
- ret = sqlite3_changes(sticker_db);
+ bool modified = ExecuteModified(stmt, error);
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
- idle_add(IDLE_STICKER);
- return ret > 0;
+ if (modified)
+ idle_add(IDLE_STICKER);
+ return modified;
}
static bool
sticker_insert_value(const char *type, const char *uri,
- const char *name, const char *value)
+ const char *name, const char *value,
+ Error &error)
{
sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT];
- int ret;
assert(type != nullptr);
assert(uri != nullptr);
@@ -352,52 +263,23 @@ sticker_insert_value(const char *type, const char *uri,
assert(sticker_enabled());
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ if (!BindAll(error, stmt, type, uri, name, value))
return false;
- }
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
+ bool success = ExecuteCommand(stmt, error);
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
-
- idle_add(IDLE_STICKER);
- return true;
+ if (success)
+ idle_add(IDLE_STICKER);
+ return success;
}
bool
sticker_store_value(const char *type, const char *uri,
- const char *name, const char *value)
+ const char *name, const char *value,
+ Error &error)
{
assert(sticker_enabled());
assert(type != nullptr);
@@ -408,96 +290,53 @@ sticker_store_value(const char *type, const char *uri,
if (*name == 0)
return false;
- return sticker_update_value(type, uri, name, value) ||
- sticker_insert_value(type, uri, name, value);
+ return sticker_update_value(type, uri, name, value, error) ||
+ sticker_insert_value(type, uri, name, value, error);
}
bool
-sticker_delete(const char *type, const char *uri)
+sticker_delete(const char *type, const char *uri, Error &error)
{
sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE];
- int ret;
assert(sticker_enabled());
assert(type != nullptr);
assert(uri != nullptr);
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ if (!BindAll(error, stmt, type, uri))
return false;
- }
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
+ bool modified = ExecuteModified(stmt, error);
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
- idle_add(IDLE_STICKER);
- return true;
+ if (modified)
+ idle_add(IDLE_STICKER);
+ return modified;
}
bool
-sticker_delete_value(const char *type, const char *uri, const char *name)
+sticker_delete_value(const char *type, const char *uri, const char *name,
+ Error &error)
{
sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE];
- int ret;
assert(sticker_enabled());
assert(type != nullptr);
assert(uri != nullptr);
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ if (!BindAll(error, stmt, type, uri, name))
return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
-
- ret = sqlite3_changes(sticker_db);
+ bool modified = ExecuteModified(stmt, error);
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
- idle_add(IDLE_STICKER);
- return ret > 0;
+ if (modified)
+ idle_add(IDLE_STICKER);
+ return modified;
}
void
@@ -527,11 +366,11 @@ sticker_foreach(const sticker &sticker,
}
struct sticker *
-sticker_load(const char *type, const char *uri)
+sticker_load(const char *type, const char *uri, Error &error)
{
sticker s;
- if (!sticker_list_values(s.table, type, uri))
+ if (!sticker_list_values(s.table, type, uri, error))
return nullptr;
if (s.table.empty())
@@ -541,64 +380,67 @@ sticker_load(const char *type, const char *uri)
return new sticker(std::move(s));
}
-bool
-sticker_find(const char *type, const char *base_uri, const char *name,
- void (*func)(const char *uri, const char *value,
- void *user_data),
- void *user_data)
+static sqlite3_stmt *
+BindFind(const char *type, const char *base_uri, const char *name,
+ StickerOperator op, const char *value,
+ Error &error)
{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND];
- int ret;
-
assert(type != nullptr);
assert(name != nullptr);
- assert(func != nullptr);
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
if (base_uri == nullptr)
base_uri = "";
- ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
+ switch (op) {
+ case StickerOperator::EXISTS:
+ return BindAllOrNull(error, sticker_stmt[STICKER_SQL_FIND],
+ type, base_uri, name);
+
+ case StickerOperator::EQUALS:
+ return BindAllOrNull(error,
+ sticker_stmt[STICKER_SQL_FIND_VALUE],
+ type, base_uri, name, value);
+
+ case StickerOperator::LESS_THAN:
+ return BindAllOrNull(error,
+ sticker_stmt[STICKER_SQL_FIND_LT],
+ type, base_uri, name, value);
+
+ case StickerOperator::GREATER_THAN:
+ return BindAllOrNull(error,
+ sticker_stmt[STICKER_SQL_FIND_GT],
+ type, base_uri, name, value);
}
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
+ assert(false);
+ gcc_unreachable();
+}
+
+bool
+sticker_find(const char *type, const char *base_uri, const char *name,
+ StickerOperator op, const char *value,
+ void (*func)(const char *uri, const char *value,
+ void *user_data),
+ void *user_data,
+ Error &error)
+{
+ assert(func != nullptr);
+ assert(sticker_enabled());
+
+ sqlite3_stmt *const stmt = BindFind(type, base_uri, name, op, value,
+ error);
+ if (stmt == nullptr)
return false;
- }
- do {
- ret = sqlite3_step(stmt);
- switch (ret) {
- case SQLITE_ROW:
+ const bool success = ExecuteForEach(stmt, error,
+ [stmt, func, user_data](){
func((const char*)sqlite3_column_text(stmt, 0),
(const char*)sqlite3_column_text(stmt, 1),
user_data);
- break;
- case SQLITE_DONE:
- break;
- case SQLITE_BUSY:
- /* no op */
- break;
- default:
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
- } while (ret != SQLITE_DONE);
+ });
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
- return true;
+ return success;
}
diff --git a/src/sticker/StickerDatabase.hxx b/src/sticker/StickerDatabase.hxx
index 8993489c4..d3cfe885e 100644
--- a/src/sticker/StickerDatabase.hxx
+++ b/src/sticker/StickerDatabase.hxx
@@ -42,6 +42,7 @@
#ifndef MPD_STICKER_DATABASE_HXX
#define MPD_STICKER_DATABASE_HXX
+#include "Match.hxx"
#include "Compiler.h"
#include <string>
@@ -62,21 +63,22 @@ sticker_global_init(Path path, Error &error);
* Close the sticker database.
*/
void
-sticker_global_finish(void);
+sticker_global_finish();
/**
* Returns true if the sticker database is configured and available.
*/
gcc_const
bool
-sticker_enabled(void);
+sticker_enabled();
/**
* Returns one value from an object's sticker record. Returns an
* empty string if the value doesn't exist.
*/
std::string
-sticker_load_value(const char *type, const char *uri, const char *name);
+sticker_load_value(const char *type, const char *uri, const char *name,
+ Error &error);
/**
* Sets a sticker value in the specified object. Overwrites existing
@@ -84,21 +86,24 @@ sticker_load_value(const char *type, const char *uri, const char *name);
*/
bool
sticker_store_value(const char *type, const char *uri,
- const char *name, const char *value);
+ const char *name, const char *value,
+ Error &error);
/**
* Deletes a sticker from the database. All sticker values of the
* specified object are deleted.
*/
bool
-sticker_delete(const char *type, const char *uri);
+sticker_delete(const char *type, const char *uri,
+ Error &error);
/**
* Deletes a sticker value. Fails if no sticker with this name
* exists.
*/
bool
-sticker_delete_value(const char *type, const char *uri, const char *name);
+sticker_delete_value(const char *type, const char *uri, const char *name,
+ Error &error);
/**
* Frees resources held by the sticker object.
@@ -140,7 +145,8 @@ sticker_foreach(const sticker &sticker,
* @return a sticker object, or nullptr on error or if there is no sticker
*/
sticker *
-sticker_load(const char *type, const char *uri);
+sticker_load(const char *type, const char *uri,
+ Error &error);
/**
* Finds stickers with the specified name below the specified URI.
@@ -149,13 +155,17 @@ sticker_load(const char *type, const char *uri);
* @param base_uri the URI prefix of the resources, or nullptr if all
* resources should be searched
* @param name the name of the sticker
+ * @param op the comparison operator
+ * @param value the operand
* @return true on success (even if no sticker was found), false on
* failure
*/
bool
sticker_find(const char *type, const char *base_uri, const char *name,
+ StickerOperator op, const char *value,
void (*func)(const char *uri, const char *value,
void *user_data),
- void *user_data);
+ void *user_data,
+ Error &error);
#endif
diff --git a/src/storage/CompositeStorage.cxx b/src/storage/CompositeStorage.cxx
index 89a2fc756..ce9c1e8b1 100644
--- a/src/storage/CompositeStorage.cxx
+++ b/src/storage/CompositeStorage.cxx
@@ -137,7 +137,7 @@ CompositeStorage::Directory::Make(const char *uri)
Directory *directory = this;
while (*uri != 0) {
const std::string name = NextSegment(uri);
-#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+#if CLANG_OR_GCC_VERSION(4,8)
auto i = directory->children.emplace(std::move(name),
Directory());
#else
diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx
index 35e94f169..cb5d7160a 100644
--- a/src/system/FatalError.cxx
+++ b/src/system/FatalError.cxx
@@ -23,10 +23,6 @@
#include "util/Domain.hxx"
#include "LogV.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
@@ -78,18 +74,31 @@ FatalError(const char *msg, const Error &error)
FormatFatalError("%s: %s", msg, error.GetMessage());
}
+#ifdef WIN32
+
+void
+FatalSystemError(const char *msg, DWORD code)
+{
+ char buffer[256];
+ FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, code, 0,
+ buffer, sizeof(buffer), nullptr);
+ FormatFatalError("%s: %s", msg, buffer);
+}
+
+#endif
+
void
FatalSystemError(const char *msg)
{
- const char *system_error;
#ifdef WIN32
- system_error = g_win32_error_message(GetLastError());
+ FatalSystemError(msg, GetLastError());
#else
- system_error = strerror(errno);
-#endif
-
+ const char *system_error = strerror(errno);
FormatError(fatal_error_domain, "%s: %s", msg, system_error);
Abort();
+#endif
}
void
diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx
index d4698b3d9..2cf693bc3 100644
--- a/src/system/FatalError.hxx
+++ b/src/system/FatalError.hxx
@@ -23,6 +23,10 @@
#include "check.h"
#include "Compiler.h"
+#ifdef WIN32
+#include <windef.h>
+#endif
+
class Error;
/**
@@ -53,6 +57,14 @@ gcc_noreturn
void
FatalSystemError(const char *msg);
+#ifdef WIN32
+
+gcc_noreturn
+void
+FatalSystemError(const char *msg, DWORD code);
+
+#endif
+
gcc_noreturn
void
FormatFatalSystemError(const char *fmt, ...);
diff --git a/src/system/fd_util.h b/src/system/fd_util.h
index f4a940e91..6d6fe1e69 100644
--- a/src/system/fd_util.h
+++ b/src/system/fd_util.h
@@ -103,7 +103,7 @@ socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]);
#endif
-#ifdef HAVE_LIBMPDCLIENT
+#ifdef ENABLE_LIBMPDCLIENT
/* Avoid symbol conflict with statically linked libmpdclient */
#define socket_cloexec_nonblock socket_cloexec_nonblock_noconflict
#endif
diff --git a/src/tag/ApeTag.cxx b/src/tag/ApeTag.cxx
index f714a1624..4e3a8b187 100644
--- a/src/tag/ApeTag.cxx
+++ b/src/tag/ApeTag.cxx
@@ -30,7 +30,6 @@
#include <string.h>
const struct tag_table ape_tags[] = {
- { "album artist", TAG_ALBUM_ARTIST },
{ "year", TAG_DATE },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};
diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx
index 6a55a450f..157060d0c 100644
--- a/src/tag/Set.cxx
+++ b/src/tag/Set.cxx
@@ -75,7 +75,7 @@ TagSet::InsertUnique(const Tag &src, TagType type, const char *value,
else
builder.AddItem(type, value);
CopyTagMask(builder, src, group_mask);
-#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+#if CLANG_OR_GCC_VERSION(4,8)
emplace(builder.Commit());
#else
insert(builder.Commit());
diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx
index 02dc58364..2f10dfb53 100644
--- a/src/tag/TagId3.cxx
+++ b/src/tag/TagId3.cxx
@@ -66,12 +66,14 @@
static constexpr Domain id3_domain("id3");
+gcc_pure
static inline bool
tag_is_id3v1(struct id3_tag *tag)
{
return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
}
+gcc_pure
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
@@ -249,10 +251,11 @@ tag_id3_import_comment(struct id3_tag *tag, const char *id, TagType type,
* Parse a TXXX name, and convert it to a TagType enum value.
* Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
*/
+gcc_pure
static TagType
tag_id3_parse_txxx_name(const char *name)
{
- static const struct tag_table txxx_tags[] = {
+ static constexpr struct tag_table txxx_tags[] = {
{ "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT },
{ "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID },
{ "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID },
diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx
index 1928d539d..ece7a599b 100644
--- a/src/tag/TagId3.hxx
+++ b/src/tag/TagId3.hxx
@@ -29,7 +29,7 @@ struct Tag;
struct id3_tag;
class Error;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
bool
tag_id3_scan(Path path_fs,
diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx
index 29f605337..88211be1b 100644
--- a/src/tag/TagPool.cxx
+++ b/src/tag/TagPool.cxx
@@ -87,7 +87,7 @@ calc_hash(TagType type, const char *p)
return hash ^ type;
}
-#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#if CLANG_OR_GCC_VERSION(4,7)
constexpr
#endif
static inline TagPoolSlot *
diff --git a/src/util/Alloc.cxx b/src/util/Alloc.cxx
index ec3579470..0533a58f8 100644
--- a/src/util/Alloc.cxx
+++ b/src/util/Alloc.cxx
@@ -74,3 +74,87 @@ xstrndup(const char *s, size_t n)
return p;
}
+
+#if CLANG_OR_GCC_VERSION(4,7)
+
+template<typename... Args>
+static inline size_t
+FillLengths(size_t *lengths, const char *a, Args&&... args)
+{
+ return FillLengths(lengths, a) + FillLengths(lengths + 1, args...);
+}
+
+template<>
+inline size_t
+FillLengths(size_t *lengths, const char *a)
+{
+ return *lengths = strlen(a);
+}
+
+template<typename... Args>
+static inline void
+StringCat(char *p, const size_t *lengths, const char *a, Args&&... args)
+{
+ StringCat(p, lengths, a);
+ StringCat(p + *lengths, lengths + 1, args...);
+}
+
+template<>
+inline void
+StringCat(char *p, const size_t *lengths, const char *a)
+{
+ memcpy(p, a, *lengths);
+}
+
+#endif
+
+template<typename... Args>
+gcc_malloc gcc_nonnull_all
+static inline char *
+t_xstrcatdup(Args&&... args)
+{
+#if CLANG_OR_GCC_VERSION(4,7)
+ constexpr size_t n = sizeof...(args);
+
+ size_t lengths[n];
+ const size_t total = FillLengths(lengths, args...);
+
+ char *p = (char *)xalloc(total + 1);
+ StringCat(p, lengths, args...);
+ p[total] = 0;
+ return p;
+#else
+ /* fallback implementation for gcc 4.6, because that old
+ compiler is too buggy to compile the above template
+ functions */
+ const char *const argv[] = { args... };
+
+ size_t total = 0;
+ for (auto i : argv)
+ total += strlen(i);
+
+ char *p = (char *)xalloc(total + 1), *q = p;
+ for (auto i : argv)
+ q = stpcpy(q, i);
+
+ return p;
+#endif
+}
+
+char *
+xstrcatdup(const char *a, const char *b)
+{
+ return t_xstrcatdup(a, b);
+}
+
+char *
+xstrcatdup(const char *a, const char *b, const char *c)
+{
+ return t_xstrcatdup(a, b, c);
+}
+
+char *
+xstrcatdup(const char *a, const char *b, const char *c, const char *d)
+{
+ return t_xstrcatdup(a, b, c, d);
+}
diff --git a/src/util/Alloc.hxx b/src/util/Alloc.hxx
index 15c123b7a..654b7d0fe 100644
--- a/src/util/Alloc.hxx
+++ b/src/util/Alloc.hxx
@@ -64,4 +64,23 @@ gcc_malloc gcc_nonnull_all
char *
xstrndup(const char *s, size_t n);
+/**
+ * Concatenate two strings, returning a new allocation. Use free() to
+ * free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+char *
+xstrcatdup(const char *a, const char *b);
+
+gcc_malloc gcc_nonnull_all
+char *
+xstrcatdup(const char *a, const char *b, const char *c);
+
+gcc_malloc gcc_nonnull_all
+char *
+xstrcatdup(const char *a, const char *b, const char *c, const char *d);
+
#endif
diff --git a/src/util/Cast.hxx b/src/util/Cast.hxx
index 887137da4..647171970 100644
--- a/src/util/Cast.hxx
+++ b/src/util/Cast.hxx
@@ -84,7 +84,7 @@ ContainerAttributeOffset(const A C::*p)
* Cast the given pointer to a struct member to its parent structure.
*/
template<class C, class A>
-#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#if CLANG_OR_GCC_VERSION(4,7)
constexpr
#endif
static inline C &
@@ -97,7 +97,7 @@ ContainerCast(A &a, A C::*member)
* Cast the given pointer to a struct member to its parent structure.
*/
template<class C, class A>
-#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#if CLANG_OR_GCC_VERSION(4,7)
constexpr
#endif
static inline const C &
diff --git a/src/util/DivideString.cxx b/src/util/DivideString.cxx
new file mode 100644
index 000000000..f781d141f
--- /dev/null
+++ b/src/util/DivideString.cxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 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 "DivideString.hxx"
+#include "StringUtil.hxx"
+
+#include <string.h>
+
+DivideString::DivideString(const char *s, char separator, bool strip)
+ :first(nullptr)
+{
+ const char *x = strchr(s, separator);
+ if (x == nullptr)
+ return;
+
+ size_t length = x - s;
+ second = x + 1;
+
+ if (strip)
+ second = StripLeft(second);
+
+ if (strip) {
+ const char *end = s + length;
+ s = StripLeft(s);
+ end = StripRight(s, end);
+ length = end - s;
+ }
+
+ first = new char[length + 1];
+ memcpy(first, s, length);
+ first[length] = 0;
+}
diff --git a/src/util/DivideString.hxx b/src/util/DivideString.hxx
new file mode 100644
index 000000000..126aa45d1
--- /dev/null
+++ b/src/util/DivideString.hxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DIVIDE_STRING_HXX
+#define MPD_DIVIDE_STRING_HXX
+
+#include "Compiler.h"
+
+#include <assert.h>
+
+/**
+ * Split a given constant string at a separator character. Duplicates
+ * the first part to be able to null-terminate it.
+ */
+class DivideString {
+ char *first;
+ const char *second;
+
+public:
+ /**
+ * @param strip strip the first part and left-strip the second
+ * part?
+ */
+ DivideString(const char *s, char separator, bool strip=false);
+
+ ~DivideString() {
+ delete[] first;
+ }
+
+ /**
+ * Was the separator found?
+ */
+ bool IsDefined() const {
+ return first != nullptr;
+ }
+
+ /**
+ * Is the first part empty?
+ */
+ bool IsEmpty() const {
+ assert(IsDefined());
+
+ return *first == 0;
+ }
+
+ const char *GetFirst() const {
+ assert(IsDefined());
+
+ return first;
+ }
+
+ const char *GetSecond() const {
+ assert(IsDefined());
+
+ return second;
+ }
+};
+
+#endif
diff --git a/src/util/Error.cxx b/src/util/Error.cxx
index 92b2cc5d0..67a1b03fd 100644
--- a/src/util/Error.cxx
+++ b/src/util/Error.cxx
@@ -32,7 +32,7 @@
#include "Domain.hxx"
#ifdef WIN32
-#include <glib.h>
+#include <windows.h>
#endif
#include <errno.h>
@@ -135,7 +135,11 @@ Error::FormatErrno(const char *fmt, ...)
void
Error::SetLastError(DWORD _code, const char *prefix)
{
- const char *msg = g_win32_error_message(_code);
+ char msg[256];
+ FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, _code, 0, msg, sizeof(msg), nullptr);
+
Format(win32_domain, int(_code), "%s: %s", prefix, msg);
}
diff --git a/src/util/Manual.hxx b/src/util/Manual.hxx
index 75cffac06..6ba932bdd 100644
--- a/src/util/Manual.hxx
+++ b/src/util/Manual.hxx
@@ -41,7 +41,7 @@
#include <assert.h>
-#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+#if CLANG_OR_GCC_VERSION(4,7)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
@@ -54,12 +54,7 @@
*/
template<class T>
class Manual {
-#if GCC_OLDER_THAN(4,8)
- /* no alignas() on gcc < 4.8: apply worst-case fallback */
- __attribute__((aligned(8)))
-#else
- alignas(T)
-#endif
+ gcc_alignas(T, 8)
char data[sizeof(T)];
#ifndef NDEBUG
@@ -89,32 +84,46 @@ public:
void Destruct() {
assert(initialized);
- T *t = (T *)data;
- t->T::~T();
+ T &t = Get();
+ t.T::~T();
#ifndef NDEBUG
initialized = false;
#endif
}
+ T &Get() {
+ assert(initialized);
+
+ void *p = static_cast<void *>(data);
+ return *static_cast<T *>(p);
+ }
+
+ const T &Get() const {
+ assert(initialized);
+
+ const void *p = static_cast<const void *>(data);
+ return *static_cast<const T *>(p);
+ }
+
operator T &() {
- return *(T *)data;
+ return Get();
}
operator const T &() const {
- return *(const T *)data;
+ return Get();
}
T *operator->() {
- return (T *)data;
+ return &Get();
}
const T *operator->() const {
- return (T *)data;
+ return &Get();
}
};
-#if defined(__clang__) || GCC_VERSION >= 40700
+#if CLANG_OR_GCC_VERSION(4,7)
#pragma GCC diagnostic pop
#endif
diff --git a/src/util/SplitString.cxx b/src/util/SplitString.cxx
index 75e799279..0bb1e5165 100644
--- a/src/util/SplitString.cxx
+++ b/src/util/SplitString.cxx
@@ -18,20 +18,42 @@
*/
#include "SplitString.hxx"
+#include "StringUtil.hxx"
#include <string.h>
-SplitString::SplitString(const char *s, char separator)
- :first(nullptr)
+std::forward_list<std::string>
+SplitString(const char *s, char separator, bool strip)
{
- const char *x = strchr(s, separator);
- if (x == nullptr)
- return;
+ if (strip)
+ s = StripLeft(s);
- size_t length = x - s;
- second = x + 1;
+ std::forward_list<std::string> list;
+ if (*s == 0)
+ return list;
- first = new char[length + 1];
- memcpy(first, s, length);
- first[length] = 0;
+ auto i = list.before_begin();
+
+ while (true) {
+ const char *next = strchr(s, separator);
+ if (next == nullptr)
+ break;
+
+ const char *end = next++;
+ if (strip)
+ end = StripRight(s, end);
+
+ i = list.emplace_after(i, s, end);
+
+ s = next;
+ if (strip)
+ s = StripLeft(s);
+ }
+
+ const char *end = s + strlen(s);
+ if (strip)
+ end = StripRight(s, end);
+
+ list.emplace_after(i, s, end);
+ return list;
}
diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx
index 96ffb21ec..bc95cff81 100644
--- a/src/util/SplitString.hxx
+++ b/src/util/SplitString.hxx
@@ -20,52 +20,20 @@
#ifndef MPD_SPLIT_STRING_HXX
#define MPD_SPLIT_STRING_HXX
-#include "Compiler.h"
-
-#include <assert.h>
+#include <forward_list>
+#include <string>
/**
- * Split a given constant string at a separator character. Duplicates
- * the first part to be able to null-terminate it.
+ * Split a string at a certain separator character into sub strings
+ * and returns a list of these.
+ *
+ * Two consecutive separator characters result in an empty string in
+ * the list.
+ *
+ * An empty input string, as a special case, results in an empty list
+ * (and not a list with an empty string).
*/
-class SplitString {
- char *first;
- const char *second;
-
-public:
- SplitString(const char *s, char separator);
-
- ~SplitString() {
- delete[] first;
- }
-
- /**
- * Was the separator found?
- */
- bool IsDefined() const {
- return first != nullptr;
- }
-
- /**
- * Is the first part empty?
- */
- bool IsEmpty() const {
- assert(IsDefined());
-
- return *first == 0;
- }
-
- const char *GetFirst() const {
- assert(IsDefined());
-
- return first;
- }
-
- const char *GetSecond() const {
- assert(IsDefined());
-
- return second;
- }
-};
+std::forward_list<std::string>
+SplitString(const char *s, char separator, bool strip=true);
#endif
diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx
index bcade2b3b..f5b4b7a8b 100644
--- a/src/util/StringUtil.cxx
+++ b/src/util/StringUtil.cxx
@@ -120,3 +120,23 @@ string_array_contains(const char *const* haystack, const char *needle)
return false;
}
+
+void
+ToUpperASCII(char *dest, const char *src, size_t size)
+{
+ assert(dest != nullptr);
+ assert(src != nullptr);
+ assert(size > 1);
+
+ char *const end = dest + size - 1;
+
+ do {
+ char ch = *src++;
+ if (ch == 0)
+ break;
+
+ *dest++ = ToUpperASCII(ch);
+ } while (dest < end);
+
+ *dest = 0;
+}
diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx
index 9beda5441..77fe1be18 100644
--- a/src/util/StringUtil.hxx
+++ b/src/util/StringUtil.hxx
@@ -114,4 +114,12 @@ gcc_pure
bool
string_array_contains(const char *const* haystack, const char *needle);
+/**
+ * Convert the specified ASCII string (0x00..0x7f) to upper case.
+ *
+ * @param size the destination buffer size
+ */
+void
+ToUpperASCII(char *dest, const char *src, size_t size);
+
#endif
diff --git a/src/win32/Win32Main.cxx b/src/win32/Win32Main.cxx
index 75a1e9a23..30d94bef5 100644
--- a/src/win32/Win32Main.cxx
+++ b/src/win32/Win32Main.cxx
@@ -29,8 +29,6 @@
#include <cstdlib>
#include <atomic>
-#include <glib.h>
-
#include <windows.h>
static int service_argc;
@@ -42,7 +40,7 @@ static SERVICE_STATUS_HANDLE service_handle;
static void WINAPI
service_main(DWORD argc, CHAR *argv[]);
-static SERVICE_TABLE_ENTRY service_registry[] = {
+static constexpr SERVICE_TABLE_ENTRY service_registry[] = {
{service_name, service_main},
{nullptr, nullptr}
};
@@ -82,19 +80,12 @@ service_dispatcher(gcc_unused DWORD control, gcc_unused DWORD event_type,
static void WINAPI
service_main(gcc_unused DWORD argc, gcc_unused CHAR *argv[])
{
- DWORD error_code;
- gchar* error_message;
-
service_handle =
RegisterServiceCtrlHandlerEx(service_name,
service_dispatcher, nullptr);
- if (service_handle == 0) {
- error_code = GetLastError();
- error_message = g_win32_error_message(error_code);
- FormatFatalError("RegisterServiceCtrlHandlerEx() failed: %s",
- error_message);
- }
+ if (service_handle == 0)
+ FatalSystemError("RegisterServiceCtrlHandlerEx() failed");
service_notify_status(SERVICE_START_PENDING);
mpd_main(service_argc, service_argv);
@@ -131,16 +122,13 @@ console_handler(DWORD event)
int win32_main(int argc, char *argv[])
{
- DWORD error_code;
- gchar* error_message;
-
service_argc = argc;
service_argv = argv;
if (StartServiceCtrlDispatcher(service_registry))
return 0; /* run as service successefully */
- error_code = GetLastError();
+ const DWORD error_code = GetLastError();
if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
/* running as console app */
running.store(false);
@@ -149,9 +137,7 @@ int win32_main(int argc, char *argv[])
return mpd_main(argc, argv);
}
- error_message = g_win32_error_message(error_code);
- FormatFatalError("StartServiceCtrlDispatcher() failed: %s",
- error_message);
+ FatalSystemError("StartServiceCtrlDispatcher() failed", error_code);
}
void win32_app_started()
diff --git a/test/DivideStringTest.hxx b/test/DivideStringTest.hxx
new file mode 100644
index 000000000..f6c01bdde
--- /dev/null
+++ b/test/DivideStringTest.hxx
@@ -0,0 +1,54 @@
+/*
+ * Unit tests for src/util/
+ */
+
+#include "check.h"
+#include "util/DivideString.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string.h>
+
+class DivideStringTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(DivideStringTest);
+ CPPUNIT_TEST(TestBasic);
+ CPPUNIT_TEST(TestEmpty);
+ CPPUNIT_TEST(TestFail);
+ CPPUNIT_TEST(TestStrip);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestBasic() {
+ constexpr char input[] = "foo.bar";
+ const DivideString ds(input, '.');
+ CPPUNIT_ASSERT(ds.IsDefined());
+ CPPUNIT_ASSERT(!ds.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(ds.GetFirst(), "foo"));
+ CPPUNIT_ASSERT_EQUAL(input + 4, ds.GetSecond());
+ }
+
+ void TestEmpty() {
+ constexpr char input[] = ".bar";
+ const DivideString ds(input, '.');
+ CPPUNIT_ASSERT(ds.IsDefined());
+ CPPUNIT_ASSERT(ds.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(ds.GetFirst(), ""));
+ CPPUNIT_ASSERT_EQUAL(input + 1, ds.GetSecond());
+ }
+
+ void TestFail() {
+ constexpr char input[] = "foo!bar";
+ const DivideString ds(input, '.');
+ CPPUNIT_ASSERT(!ds.IsDefined());
+ }
+
+ void TestStrip() {
+ constexpr char input[] = " foo\t.\nbar\r";
+ const DivideString ds(input, '.', true);
+ CPPUNIT_ASSERT(ds.IsDefined());
+ CPPUNIT_ASSERT(!ds.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(ds.GetFirst(), "foo"));
+ CPPUNIT_ASSERT_EQUAL(input + 7, ds.GetSecond());
+ }
+};
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index 07f342319..d75ef36eb 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -33,10 +33,6 @@
#include "event/Loop.hxx"
#include "util/Error.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <iostream>
using std::cout;
using std::cerr;
@@ -44,7 +40,7 @@ using std::endl;
#include <stdlib.h>
-#ifdef HAVE_LIBUPNP
+#ifdef ENABLE_UPNP
#include "input/InputStream.hxx"
size_t
InputStream::LockRead(void *, size_t, Error &)
@@ -107,14 +103,6 @@ main(int argc, char **argv)
return EXIT_FAILURE;
}
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(nullptr);
-#endif
-#endif
-
/* initialize MPD */
config_global_init();
diff --git a/test/SplitStringTest.hxx b/test/SplitStringTest.hxx
new file mode 100644
index 000000000..87ed385ea
--- /dev/null
+++ b/test/SplitStringTest.hxx
@@ -0,0 +1,65 @@
+/*
+ * Unit tests for src/util/
+ */
+
+#include "check.h"
+#include "util/SplitString.hxx"
+#include "util/Macros.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string.h>
+
+class SplitStringTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(SplitStringTest);
+ CPPUNIT_TEST(TestBasic);
+ CPPUNIT_TEST(TestStrip);
+ CPPUNIT_TEST(TestNoStrip);
+ CPPUNIT_TEST(TestEmpty);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestBasic() {
+ constexpr char input[] = "foo.bar";
+ const char *const output[] = { "foo", "bar" };
+ size_t i = 0;
+ for (auto p : SplitString(input, '.')) {
+ CPPUNIT_ASSERT(i < ARRAY_SIZE(output));
+ CPPUNIT_ASSERT(p == output[i]);
+ ++i;
+ }
+
+ CPPUNIT_ASSERT_EQUAL(ARRAY_SIZE(output), i);
+ }
+
+ void TestStrip() {
+ constexpr char input[] = " foo\t.\r\nbar\r\n2";
+ const char *const output[] = { "foo", "bar\r\n2" };
+ size_t i = 0;
+ for (auto p : SplitString(input, '.')) {
+ CPPUNIT_ASSERT(i < ARRAY_SIZE(output));
+ CPPUNIT_ASSERT(p == output[i]);
+ ++i;
+ }
+
+ CPPUNIT_ASSERT_EQUAL(ARRAY_SIZE(output), i);
+ }
+
+ void TestNoStrip() {
+ constexpr char input[] = " foo\t.\r\nbar\r\n2";
+ const char *const output[] = { " foo\t", "\r\nbar\r\n2" };
+ size_t i = 0;
+ for (auto p : SplitString(input, '.', false)) {
+ CPPUNIT_ASSERT(i < ARRAY_SIZE(output));
+ CPPUNIT_ASSERT(p == output[i]);
+ ++i;
+ }
+
+ CPPUNIT_ASSERT_EQUAL(ARRAY_SIZE(output), i);
+ }
+
+ void TestEmpty() {
+ CPPUNIT_ASSERT(SplitString("", '.').empty());
+ }
+};
diff --git a/test/TestCircularBuffer.hxx b/test/TestCircularBuffer.hxx
index c808d85dc..4283be45b 100644
--- a/test/TestCircularBuffer.hxx
+++ b/test/TestCircularBuffer.hxx
@@ -2,17 +2,12 @@
* Unit tests for class CircularBuffer.
*/
-#include "config.h"
+#include "check.h"
#include "util/CircularBuffer.hxx"
#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
-#include <string.h>
-#include <stdlib.h>
-
class TestCircularBuffer : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(TestCircularBuffer);
CPPUNIT_TEST(TestIt);
diff --git a/test/TestIcu.cxx b/test/TestIcu.cxx
new file mode 100644
index 000000000..9d525d698
--- /dev/null
+++ b/test/TestIcu.cxx
@@ -0,0 +1,82 @@
+/*
+ * Unit tests for src/util/
+ */
+
+#include "config.h"
+#include "lib/icu/Converter.hxx"
+#include "util/Error.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_ICU_CONVERTER
+
+static const char *const invalid_utf8[] = {
+ "\xfc",
+};
+
+struct StringPair {
+ const char *utf8, *other;
+};
+
+static constexpr StringPair latin1_tests[] = {
+ { "foo", "foo" },
+ { "\xc3\xbc", "\xfc" },
+};
+
+class TestIcuConverter : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TestIcuConverter);
+ CPPUNIT_TEST(TestInvalidCharset);
+ CPPUNIT_TEST(TestLatin1);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestInvalidCharset() {
+ CPPUNIT_ASSERT_EQUAL((IcuConverter *)nullptr,
+ IcuConverter::Create("doesntexist",
+ IgnoreError()));
+ }
+
+ void TestLatin1() {
+ IcuConverter *const converter =
+ IcuConverter::Create("iso-8859-1", IgnoreError());
+ CPPUNIT_ASSERT(converter != nullptr);
+
+ for (const auto i : invalid_utf8) {
+ auto f = converter->FromUTF8(i);
+ CPPUNIT_ASSERT_EQUAL(true, f.empty());
+ }
+
+ for (const auto i : latin1_tests) {
+ auto f = converter->FromUTF8(i.utf8);
+ CPPUNIT_ASSERT_EQUAL(true, f == i.other);
+
+ auto t = converter->ToUTF8(i.other);
+ CPPUNIT_ASSERT_EQUAL(true, t == i.utf8);
+ }
+
+ delete converter;
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestIcuConverter);
+
+#endif
+
+int
+main(gcc_unused int argc, gcc_unused char **argv)
+{
+#ifdef HAVE_ICU_CONVERTER
+ CppUnit::TextUi::TestRunner runner;
+ auto &registry = CppUnit::TestFactoryRegistry::getRegistry();
+ runner.addTest(registry.makeTest());
+ return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
+#else
+ return EXIT_SUCCESS;
+#endif
+}
diff --git a/test/UriUtilTest.hxx b/test/UriUtilTest.hxx
new file mode 100644
index 000000000..07f52a475
--- /dev/null
+++ b/test/UriUtilTest.hxx
@@ -0,0 +1,66 @@
+/*
+ * Unit tests for src/util/
+ */
+
+#include "check.h"
+#include "util/UriUtil.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string.h>
+
+class UriUtilTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(UriUtilTest);
+ CPPUNIT_TEST(TestSuffix);
+ CPPUNIT_TEST(TestRemoveAuth);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestSuffix() {
+ CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+ uri_get_suffix("/foo/bar"));
+ CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+ uri_get_suffix("/foo.jpg/bar"));
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg"),
+ "jpg"));
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo.png/bar.jpg"),
+ "jpg"));
+ CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+ uri_get_suffix(".jpg"));
+ CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+ uri_get_suffix("/foo/.jpg"));
+
+ /* the first overload does not eliminate the query
+ string */
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string"),
+ "jpg?query_string"));
+
+ /* ... but the second one does */
+ UriSuffixBuffer buffer;
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string",
+ buffer),
+ "jpg"));
+
+ /* repeat some of the above tests with the second overload */
+ CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+ uri_get_suffix("/foo/bar", buffer));
+ CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+ uri_get_suffix("/foo.jpg/bar", buffer));
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg", buffer),
+ "jpg"));
+ }
+
+ void TestRemoveAuth() {
+ CPPUNIT_ASSERT_EQUAL(std::string(),
+ uri_remove_auth("http://www.example.com/"));
+ CPPUNIT_ASSERT_EQUAL(std::string("http://www.example.com/"),
+ uri_remove_auth("http://foo:bar@www.example.com/"));
+ CPPUNIT_ASSERT_EQUAL(std::string("http://www.example.com/"),
+ uri_remove_auth("http://foo@www.example.com/"));
+ CPPUNIT_ASSERT_EQUAL(std::string(),
+ uri_remove_auth("http://www.example.com/f:oo@bar"));
+ CPPUNIT_ASSERT_EQUAL(std::string("ftp://ftp.example.com/"),
+ uri_remove_auth("ftp://foo:bar@ftp.example.com/"));
+ }
+};
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index 0047ef427..2ec3c76ce 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -35,10 +35,6 @@
#include "thread/Cond.hxx"
#include "Log.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <unistd.h>
#include <stdlib.h>
@@ -64,14 +60,6 @@ int main(int argc, char **argv)
const Path config_path = Path::FromFS(argv[1]);
uri = argv[2];
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
/* initialize MPD */
config_global_init();
diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx
index 5bfd316a5..2ab95db5e 100644
--- a/test/dump_text_file.cxx
+++ b/test/dump_text_file.cxx
@@ -32,10 +32,6 @@
#include "archive/ArchiveList.hxx"
#endif
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
@@ -79,14 +75,6 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
/* initialize MPD */
config_global_init();
diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx
index de77a00c4..587a0075e 100644
--- a/test/read_mixer.cxx
+++ b/test/read_mixer.cxx
@@ -28,10 +28,6 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <assert.h>
#include <string.h>
#include <unistd.h>
@@ -52,12 +48,6 @@ int main(int argc, gcc_unused char **argv)
return EXIT_FAILURE;
}
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
EventLoop event_loop;
Error error;
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index 91ac9c674..6d9a718ff 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -32,10 +32,6 @@
#include "thread/Cond.hxx"
#include "Log.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
@@ -90,12 +86,6 @@ int main(int argc, char **argv)
decoder_name = argv[1];
const Path path = Path::FromFS(argv[2]);
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
const ScopeIOThread io_thread;
Error error;
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index 0e9af6a1a..3548af1b6 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -30,10 +30,6 @@
#include "Log.hxx"
#include "stdbin.h"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
@@ -50,12 +46,6 @@ int main(int argc, char **argv)
const char *const decoder_name = argv[1];
const char *const uri = argv[2];
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
const ScopeIOThread io_thread;
Error error;
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index ab99c9a1e..b9ad89ccd 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -33,10 +33,6 @@
#include "system/FatalError.hxx"
#include "Log.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <assert.h>
#include <string.h>
#include <stdlib.h>
@@ -86,14 +82,6 @@ int main(int argc, char **argv)
AudioFormat audio_format(44100, SampleFormat::S16, 2);
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
/* read configuration file (mpd.conf) */
config_global_init();
diff --git a/test/run_input.cxx b/test/run_input.cxx
index 6864a5d64..e124b175f 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -35,10 +35,6 @@
#include "archive/ArchiveList.hxx"
#endif
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <unistd.h>
#include <stdlib.h>
@@ -105,14 +101,6 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
/* initialize MPD */
config_global_init();
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 345127556..934d3fd8c 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -36,10 +36,6 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <assert.h>
#include <string.h>
#include <unistd.h>
@@ -163,12 +159,6 @@ int main(int argc, char **argv)
AudioFormat audio_format(44100, SampleFormat::S16, 2);
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
/* read configuration file (mpd.conf) */
config_global_init();
diff --git a/test/run_storage.cxx b/test/run_storage.cxx
index 9fc6e6e76..fad808e24 100644
--- a/test/run_storage.cxx
+++ b/test/run_storage.cxx
@@ -24,10 +24,6 @@
#include "storage/FileInfo.hxx"
#include "util/Error.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <memory>
#include <unistd.h>
@@ -102,14 +98,6 @@ main(int argc, char **argv)
return EXIT_FAILURE;
}
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
const char *const command = argv[1];
const char *const storage_uri = argv[2];
diff --git a/test/test_util.cxx b/test/test_util.cxx
index 3e79aeca0..c2d73d7d9 100644
--- a/test/test_util.cxx
+++ b/test/test_util.cxx
@@ -3,7 +3,9 @@
*/
#include "config.h"
-#include "util/UriUtil.hxx"
+#include "DivideStringTest.hxx"
+#include "SplitStringTest.hxx"
+#include "UriUtilTest.hxx"
#include "TestCircularBuffer.hxx"
#include <cppunit/TestFixture.h>
@@ -11,64 +13,10 @@
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
-#include <string.h>
#include <stdlib.h>
-class UriUtilTest : public CppUnit::TestFixture {
- CPPUNIT_TEST_SUITE(UriUtilTest);
- CPPUNIT_TEST(TestSuffix);
- CPPUNIT_TEST(TestRemoveAuth);
- CPPUNIT_TEST_SUITE_END();
-
-public:
- void TestSuffix() {
- CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
- uri_get_suffix("/foo/bar"));
- CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
- uri_get_suffix("/foo.jpg/bar"));
- CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg"),
- "jpg"));
- CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo.png/bar.jpg"),
- "jpg"));
- CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
- uri_get_suffix(".jpg"));
- CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
- uri_get_suffix("/foo/.jpg"));
-
- /* the first overload does not eliminate the query
- string */
- CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string"),
- "jpg?query_string"));
-
- /* ... but the second one does */
- UriSuffixBuffer buffer;
- CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string",
- buffer),
- "jpg"));
-
- /* repeat some of the above tests with the second overload */
- CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
- uri_get_suffix("/foo/bar", buffer));
- CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
- uri_get_suffix("/foo.jpg/bar", buffer));
- CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg", buffer),
- "jpg"));
- }
-
- void TestRemoveAuth() {
- CPPUNIT_ASSERT_EQUAL(std::string(),
- uri_remove_auth("http://www.example.com/"));
- CPPUNIT_ASSERT_EQUAL(std::string("http://www.example.com/"),
- uri_remove_auth("http://foo:bar@www.example.com/"));
- CPPUNIT_ASSERT_EQUAL(std::string("http://www.example.com/"),
- uri_remove_auth("http://foo@www.example.com/"));
- CPPUNIT_ASSERT_EQUAL(std::string(),
- uri_remove_auth("http://www.example.com/f:oo@bar"));
- CPPUNIT_ASSERT_EQUAL(std::string("ftp://ftp.example.com/"),
- uri_remove_auth("ftp://foo:bar@ftp.example.com/"));
- }
-};
-
+CPPUNIT_TEST_SUITE_REGISTRATION(DivideStringTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(SplitStringTest);
CPPUNIT_TEST_SUITE_REGISTRATION(UriUtilTest);
CPPUNIT_TEST_SUITE_REGISTRATION(TestCircularBuffer);
diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx
index 1ff3ba484..97b22bec6 100644
--- a/test/visit_archive.cxx
+++ b/test/visit_archive.cxx
@@ -30,10 +30,6 @@
#include "fs/Path.hxx"
#include "util/Error.hxx"
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-
#include <unistd.h>
#include <stdlib.h>
@@ -57,14 +53,6 @@ main(int argc, char **argv)
const char *plugin_name = argv[1];
const Path path = Path::FromFS(argv[2]);
- /* initialize GLib */
-
-#ifdef HAVE_GLIB
-#if !GLIB_CHECK_VERSION(2,32,0)
- g_thread_init(NULL);
-#endif
-#endif
-
/* initialize MPD */
config_global_init();
diff --git a/win32/build.py b/win32/build.py
new file mode 100755
index 000000000..09fb79e38
--- /dev/null
+++ b/win32/build.py
@@ -0,0 +1,398 @@
+#!/usr/bin/env python3
+
+import os, os.path
+import sys, shutil, subprocess
+import urllib.request
+import hashlib
+import re
+
+configure_args = sys.argv[1:]
+
+host_arch = 'i686-w64-mingw32'
+
+if configure_args[0] == '--64':
+ configure_args = configure_args[1:]
+ host_arch = 'x86_64-w64-mingw32'
+
+# the path to the MPD sources
+mpd_path = os.path.dirname(os.path.dirname(sys.argv[0]))
+
+# output directories
+lib_path = os.path.abspath('lib')
+tarball_path = lib_path
+src_path = os.path.join(lib_path, 'src')
+arch_path = os.path.join(lib_path, host_arch)
+build_path = os.path.join(arch_path, 'build')
+root_path = os.path.join(arch_path, 'root')
+
+# redirect pkg-config to use our root directory instead of the default
+# one on the build host
+os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
+
+gcc_toolchain = '/usr'
+
+def select_toolchain():
+ global cc, cxx, ar, nm, strip, cflags, cxxflags, cppflags, ldflags, libs
+
+ target_arch = ''
+ cc = os.path.join(gcc_toolchain, 'bin', host_arch + '-gcc')
+ cxx = os.path.join(gcc_toolchain, 'bin', host_arch + '-g++')
+ ar = os.path.join(gcc_toolchain, 'bin', host_arch + '-ar')
+ nm = os.path.join(gcc_toolchain, 'bin', host_arch + '-nm')
+ strip = os.path.join(gcc_toolchain, 'bin', host_arch + '-strip')
+
+ cflags = '-O2 -g ' + target_arch
+ cxxflags = '-O2 -g ' + target_arch
+ cppflags = '-I' + root_path + '/include'
+ ldflags = '-L' + root_path + '/lib'
+ libs = ''
+
+def file_md5(path):
+ """Calculate the MD5 checksum of a file and return it in hexadecimal notation."""
+
+ with open(path, 'rb') as f:
+ m = hashlib.md5()
+ while True:
+ data = f.read(65536)
+ if len(data) == 0:
+ # end of file
+ return m.hexdigest()
+ m.update(data)
+
+def download_tarball(url, md5):
+ """Download a tarball, verify its MD5 checksum and return the local path."""
+
+ global tarball_path
+ os.makedirs(tarball_path, exist_ok=True)
+ path = os.path.join(tarball_path, os.path.basename(url))
+
+ try:
+ calculated_md5 = file_md5(path)
+ if md5 == calculated_md5: return path
+ os.unlink(path)
+ except FileNotFoundError:
+ pass
+
+ tmp_path = path + '.tmp'
+
+ print("download", url)
+ urllib.request.urlretrieve(url, tmp_path)
+ calculated_md5 = file_md5(tmp_path)
+ if calculated_md5 != md5:
+ os.unlink(tmp_path)
+ raise "MD5 mismatch"
+
+ os.rename(tmp_path, path)
+ return path
+
+class Project:
+ def __init__(self, url, md5, installed, name=None, version=None,
+ base=None):
+ if base is None:
+ basename = os.path.basename(url)
+ m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
+ if not m: raise
+ self.base = m.group(1)
+ else:
+ self.base = base
+
+ if name is None or version is None:
+ m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
+ if name is None: name = m.group(1)
+ if version is None: version = m.group(2)
+
+ self.name = name
+ self.version = version
+
+ self.url = url
+ self.md5 = md5
+ self.installed = installed
+
+ def download(self):
+ return download_tarball(self.url, self.md5)
+
+ def is_installed(self):
+ global root_path
+ tarball = self.download()
+ installed = os.path.join(root_path, self.installed)
+ tarball_mtime = os.path.getmtime(tarball)
+ try:
+ return os.path.getmtime(installed) >= tarball_mtime
+ except FileNotFoundError:
+ return False
+
+ def unpack(self):
+ global src_path
+ tarball = self.download()
+ path = os.path.join(src_path, self.base)
+ try:
+ shutil.rmtree(path)
+ except FileNotFoundError:
+ pass
+ os.makedirs(src_path, exist_ok=True)
+ subprocess.check_call(['/bin/tar', 'xfC', tarball, src_path])
+ return path
+
+ def make_build_path(self):
+ path = os.path.join(build_path, self.base)
+ try:
+ shutil.rmtree(path)
+ except FileNotFoundError:
+ pass
+ os.makedirs(path, exist_ok=True)
+ return path
+
+class AutotoolsProject(Project):
+ def __init__(self, url, md5, installed, configure_args=[],
+ autogen=False,
+ cppflags='',
+ **kwargs):
+ Project.__init__(self, url, md5, installed, **kwargs)
+ self.configure_args = configure_args
+ self.autogen = autogen
+ self.cppflags = cppflags
+
+ def build(self):
+ src = self.unpack()
+ if self.autogen:
+ subprocess.check_call(['/usr/bin/aclocal'], cwd=src)
+ subprocess.check_call(['/usr/bin/automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
+ subprocess.check_call(['/usr/bin/autoconf'], cwd=src)
+ subprocess.check_call(['/usr/bin/libtoolize', '--force'], cwd=src)
+
+ build = self.make_build_path()
+
+ select_toolchain()
+ configure = [
+ os.path.join(src, 'configure'),
+ 'CC=' + cc,
+ 'CXX=' + cxx,
+ 'CFLAGS=' + cflags,
+ 'CXXFLAGS=' + cxxflags,
+ 'CPPFLAGS=' + cppflags + ' ' + self.cppflags,
+ 'LDFLAGS=' + ldflags,
+ 'LIBS=' + libs,
+ 'AR=' + ar,
+ 'STRIP=' + strip,
+ '--host=' + host_arch,
+ '--prefix=' + root_path,
+ '--enable-silent-rules',
+ ] + self.configure_args
+
+ subprocess.check_call(configure, cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'], cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', 'install'], cwd=build)
+
+class ZlibProject(Project):
+ def __init__(self, url, md5, installed,
+ **kwargs):
+ Project.__init__(self, url, md5, installed, **kwargs)
+
+ def build(self):
+ src = self.unpack()
+
+ build = self.make_build_path()
+
+ select_toolchain()
+ subprocess.check_call(['/usr/bin/make', '--quiet',
+ '-f', 'win32/Makefile.gcc',
+ 'PREFIX=' + host_arch + '-',
+ '-j12',
+ 'install',
+ 'DESTDIR=' + root_path + '/',
+ 'INCLUDE_PATH=include',
+ 'LIBRARY_PATH=lib',
+ 'BINARY_PATH=bin', 'SHARED_MODE=1'],
+ cwd=src)
+
+class FfmpegProject(Project):
+ def __init__(self, url, md5, installed, configure_args=[],
+ cppflags='',
+ **kwargs):
+ Project.__init__(self, url, md5, installed, **kwargs)
+ self.configure_args = configure_args
+ self.cppflags = cppflags
+
+ def build(self):
+ src = self.unpack()
+ build = self.make_build_path()
+
+ select_toolchain()
+ configure = [
+ os.path.join(src, 'configure'),
+ '--cc=' + cc,
+ '--cxx=' + cxx,
+ '--nm=' + nm,
+ '--extra-cflags=' + cflags + ' ' + cppflags + ' ' + self.cppflags,
+ '--extra-cxxflags=' + cxxflags + ' ' + cppflags + ' ' + self.cppflags,
+ '--extra-ldflags=' + ldflags,
+ '--extra-libs=' + libs,
+ '--ar=' + ar,
+ '--enable-cross-compile',
+ '--arch=x86',
+ '--target-os=mingw32',
+ '--cross-prefix=' + host_arch + '-',
+ '--prefix=' + root_path,
+ ] + self.configure_args
+
+ subprocess.check_call(configure, cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'], cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', 'install'], cwd=build)
+
+class BoostProject(Project):
+ def __init__(self, url, md5, installed,
+ **kwargs):
+ m = re.match(r'.*/boost_(\d+)_(\d+)_(\d+)\.tar\.bz2$', url)
+ version = "%s.%s.%s" % (m.group(1), m.group(2), m.group(3))
+ Project.__init__(self, url, md5, installed,
+ name='boost', version=version,
+ **kwargs)
+
+ def build(self):
+ src = self.unpack()
+
+ # install the headers manually; don't build any library
+ # (because right now, we only use header-only libraries)
+ includedir = os.path.join(root_path, 'include')
+ for dirpath, dirnames, filenames in os.walk(os.path.join(src, 'boost')):
+ relpath = dirpath[len(src)+1:]
+ destdir = os.path.join(includedir, relpath)
+ try:
+ os.mkdir(destdir)
+ except:
+ pass
+ for name in filenames:
+ if name[-4:] == '.hpp':
+ shutil.copyfile(os.path.join(dirpath, name),
+ os.path.join(destdir, name))
+
+# a list of third-party libraries to be used by MPD on Android
+thirdparty_libs = [
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz',
+ '5c3a34309d8b98640827e5d0991a4015',
+ 'lib/libogg.a',
+ ['--disable-shared', '--enable-static'],
+ ),
+
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.4.tar.xz',
+ '55f2288055e44754275a17c9a2497391',
+ 'lib/libvorbis.a',
+ ['--disable-shared', '--enable-static'],
+ ),
+
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz',
+ 'c5a8cf7c0b066759542bc4ca46817ac6',
+ 'lib/libopus.a',
+ ['--disable-shared', '--enable-static'],
+ ),
+
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz',
+ 'b9922c9a0378c88d3e901b234f852698',
+ 'lib/libFLAC.a',
+ [
+ '--disable-shared', '--enable-static',
+ '--disable-xmms-plugin', '--disable-cpplibs',
+ ],
+ ),
+
+ ZlibProject(
+ 'http://zlib.net/zlib-1.2.8.tar.xz',
+ '28f1205d8dd2001f26fec1e8c2cebe37',
+ 'lib/libz.a',
+ ),
+
+ AutotoolsProject(
+ 'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.1b.tar.gz',
+ 'e5808ad997ba32c498803822078748c3',
+ 'lib/libid3tag.a',
+ ['--disable-shared', '--enable-static'],
+ autogen=True,
+ ),
+
+ FfmpegProject(
+ 'http://ffmpeg.org/releases/ffmpeg-2.5.tar.bz2',
+ '4346fe710cc6bdd981f6534d2420d1ab',
+ 'lib/libavcodec.a',
+ [
+ '--disable-shared', '--enable-static',
+ '--enable-gpl',
+ '--enable-small',
+ '--disable-pthreads',
+ '--disable-programs',
+ '--disable-doc',
+ '--disable-avdevice',
+ '--disable-swresample',
+ '--disable-swscale',
+ '--disable-postproc',
+ '--disable-avfilter',
+ '--disable-network',
+ '--disable-encoders',
+ '--disable-protocols',
+ '--disable-outdevs',
+ '--disable-filters',
+ ],
+ ),
+
+ AutotoolsProject(
+ 'http://curl.haxx.se/download/curl-7.39.0.tar.lzma',
+ 'e9aa6dec29920eba8ef706ea5823bad7',
+ 'lib/libcurl.a',
+ [
+ '--disable-shared', '--enable-static',
+ '--disable-debug',
+ '--enable-http',
+ '--enable-ipv6',
+ '--disable-ftp', '--disable-file',
+ '--disable-ldap', '--disable-ldaps',
+ '--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
+ '--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
+ '--disable-gopher',
+ '--disable-manual',
+ '--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
+ '--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
+ '--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
+ ],
+ ),
+
+ BoostProject(
+ 'http://netcologne.dl.sourceforge.net/project/boost/boost/1.55.0/boost_1_55_0.tar.bz2',
+ 'd6eef4b4cacb2183f2bf265a5a03a354',
+ 'include/boost/version.hpp',
+ ),
+]
+
+# build the third-party libraries
+for x in thirdparty_libs:
+ if not x.is_installed():
+ x.build()
+
+# configure and build MPD
+select_toolchain()
+
+configure = [
+ os.path.join(mpd_path, 'configure'),
+ 'CC=' + cc,
+ 'CXX=' + cxx,
+ 'CFLAGS=' + cflags,
+ 'CXXFLAGS=' + cxxflags,
+ 'CPPFLAGS=' + cppflags,
+ 'LDFLAGS=' + ldflags + ' -static',
+ 'LIBS=' + libs,
+ 'AR=' + ar,
+ 'STRIP=' + strip,
+ '--host=' + host_arch,
+ '--prefix=' + root_path,
+
+ '--enable-silent-rules',
+
+ '--disable-glib',
+ '--disable-icu',
+
+] + configure_args
+
+subprocess.check_call(configure)
+subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'])
diff --git a/src/win32/mpd.ico b/win32/res/mpd.ico
index 86fd9fe43..86fd9fe43 100644
--- a/src/win32/mpd.ico
+++ b/win32/res/mpd.ico
Binary files differ
diff --git a/src/win32/mpd_win32_rc.rc.in b/win32/res/mpd.rc.in
index e5312dc78..3a33e9981 100644
--- a/src/win32/mpd_win32_rc.rc.in
+++ b/win32/res/mpd.rc.in
@@ -3,7 +3,7 @@
#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@
#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@"
-MPD_ICON ICON "@top_srcdir@/src/win32/mpd.ico"
+MPD_ICON ICON "@top_srcdir@/win32/res/mpd.ico"
1 VERSIONINFO
FILETYPE VFT_APP