aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-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--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
160 files changed, 4098 insertions, 2589 deletions
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/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