aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Makefile.am177
-rw-r--r--NEWS20
-rw-r--r--configure.ac238
-rw-r--r--doc/mpd.conf.547
-rw-r--r--doc/protocol.xml72
-rw-r--r--doc/user.xml273
-rw-r--r--m4/mpd_func.m413
-rw-r--r--src/ArchiveFile.hxx2
-rw-r--r--src/ArchiveLookup.cxx1
-rw-r--r--src/ArchivePlugin.hxx2
-rw-r--r--src/AvahiPoll.cxx5
-rw-r--r--src/Client.hxx7
-rw-r--r--src/ClientFile.cxx3
-rw-r--r--src/ClientGlobal.cxx1
-rw-r--r--src/ClientList.cxx10
-rw-r--r--src/ClientNew.cxx21
-rw-r--r--src/ClientRead.cxx1
-rw-r--r--src/ClientSubscribe.cxx5
-rw-r--r--src/CommandLine.cxx247
-rw-r--r--src/CommandLine.hxx10
-rw-r--r--src/ConfigData.cxx3
-rw-r--r--src/ConfigFile.cxx3
-rw-r--r--src/ConfigGlobal.cxx2
-rw-r--r--src/ConfigPath.cxx17
-rw-r--r--src/ConfigTemplates.hxx2
-rw-r--r--src/CrossFade.cxx2
-rw-r--r--src/Daemon.cxx11
-rw-r--r--src/DatabaseGlue.cxx13
-rw-r--r--src/DatabaseGlue.hxx7
-rw-r--r--src/DatabaseHelpers.hxx1
-rw-r--r--src/DatabaseListener.hxx38
-rw-r--r--src/DatabaseLock.cxx1
-rw-r--r--src/DatabasePlaylist.cxx3
-rw-r--r--src/DatabasePlugin.hxx5
-rw-r--r--src/DatabasePrint.cxx19
-rw-r--r--src/DatabasePrint.hxx1
-rw-r--r--src/DatabaseQueue.cxx7
-rw-r--r--src/DatabaseRegistry.cxx4
-rw-r--r--src/DatabaseSave.cxx15
-rw-r--r--src/DecoderAPI.cxx92
-rw-r--r--src/DecoderAPI.hxx23
-rw-r--r--src/DecoderBuffer.cxx59
-rw-r--r--src/DecoderBuffer.hxx18
-rw-r--r--src/DecoderControl.cxx17
-rw-r--r--src/DecoderControl.hxx10
-rw-r--r--src/DecoderInternal.cxx43
-rw-r--r--src/DecoderInternal.hxx48
-rw-r--r--src/DecoderList.cxx67
-rw-r--r--src/DecoderList.hxx25
-rw-r--r--src/DecoderPlugin.hxx1
-rw-r--r--src/DecoderThread.cxx109
-rw-r--r--src/DespotifyUtils.cxx39
-rw-r--r--src/DespotifyUtils.hxx6
-rw-r--r--src/DetachedSong.cxx51
-rw-r--r--src/DetachedSong.hxx184
-rw-r--r--src/Directory.cxx69
-rw-r--r--src/Directory.hxx30
-rw-r--r--src/DirectorySave.cxx40
-rw-r--r--src/EncoderAPI.hxx4
-rw-r--r--src/EncoderList.cxx4
-rw-r--r--src/Expat.cxx92
-rw-r--r--src/Expat.hxx129
-rw-r--r--src/FilterConfig.cxx2
-rw-r--r--src/FilterPlugin.cxx1
-rw-r--r--src/FilterRegistry.cxx1
-rw-r--r--src/GlobalEvents.cxx1
-rw-r--r--src/IcyMetaDataParser.cxx18
-rw-r--r--src/IcyMetaDataServer.cxx1
-rw-r--r--src/InotifyQueue.cxx1
-rw-r--r--src/InotifySource.hxx5
-rw-r--r--src/InputRegistry.cxx14
-rw-r--r--src/InputStream.cxx22
-rw-r--r--src/InputStream.hxx9
-rw-r--r--src/Instance.cxx12
-rw-r--r--src/Instance.hxx10
-rw-r--r--src/Log.cxx53
-rw-r--r--src/Log.hxx37
-rw-r--r--src/LogBackend.cxx193
-rw-r--r--src/LogBackend.hxx40
-rw-r--r--src/LogInit.cxx213
-rw-r--r--src/LogLevel.hxx59
-rw-r--r--src/LogV.hxx2
-rw-r--r--src/Main.cxx32
-rw-r--r--src/Mapper.cxx23
-rw-r--r--src/Mapper.hxx9
-rw-r--r--src/MemorySongEnumerator.cxx4
-rw-r--r--src/MemorySongEnumerator.hxx8
-rw-r--r--src/MixerAll.cxx2
-rw-r--r--src/MixerControl.cxx1
-rw-r--r--src/MixerControl.hxx2
-rw-r--r--src/OutputAPI.hxx4
-rw-r--r--src/OutputAll.cxx11
-rw-r--r--src/OutputAll.hxx2
-rw-r--r--src/OutputCommand.cxx1
-rw-r--r--src/OutputControl.cxx25
-rw-r--r--src/OutputControl.hxx28
-rw-r--r--src/OutputFinish.cxx4
-rw-r--r--src/OutputInit.cxx2
-rw-r--r--src/OutputInternal.hxx12
-rw-r--r--src/OutputState.cxx6
-rw-r--r--src/OutputThread.cxx55
-rw-r--r--src/OutputThread.hxx3
-rw-r--r--src/Page.cxx10
-rw-r--r--src/Page.hxx2
-rw-r--r--src/Partition.cxx6
-rw-r--r--src/Partition.hxx4
-rw-r--r--src/PlayerControl.cxx35
-rw-r--r--src/PlayerControl.hxx36
-rw-r--r--src/PlayerThread.cxx83
-rw-r--r--src/Playlist.cxx44
-rw-r--r--src/Playlist.hxx20
-rw-r--r--src/PlaylistAny.cxx3
-rw-r--r--src/PlaylistControl.cxx7
-rw-r--r--src/PlaylistDatabase.cxx2
-rw-r--r--src/PlaylistEdit.cxx48
-rw-r--r--src/PlaylistFile.cxx35
-rw-r--r--src/PlaylistFile.hxx5
-rw-r--r--src/PlaylistGlobal.cxx1
-rw-r--r--src/PlaylistPrint.cxx9
-rw-r--r--src/PlaylistQueue.cxx13
-rw-r--r--src/PlaylistRegistry.cxx24
-rw-r--r--src/PlaylistSave.cxx14
-rw-r--r--src/PlaylistSave.hxx4
-rw-r--r--src/PlaylistSong.cxx99
-rw-r--r--src/PlaylistSong.hxx6
-rw-r--r--src/PlaylistState.cxx29
-rw-r--r--src/PlaylistState.hxx6
-rw-r--r--src/PlaylistTag.cxx93
-rw-r--r--src/PlaylistUpdate.cxx13
-rw-r--r--src/PlaylistVector.cxx1
-rw-r--r--src/Queue.cxx18
-rw-r--r--src/Queue.hxx10
-rw-r--r--src/QueuePrint.cxx7
-rw-r--r--src/QueueSave.cxx46
-rw-r--r--src/ReplayGainConfig.cxx2
-rw-r--r--src/Song.cxx104
-rw-r--r--src/Song.hxx69
-rw-r--r--src/SongEnumerator.hxx4
-rw-r--r--src/SongFilter.cxx23
-rw-r--r--src/SongFilter.hxx7
-rw-r--r--src/SongPrint.cxx54
-rw-r--r--src/SongPrint.hxx7
-rw-r--r--src/SongSave.cxx68
-rw-r--r--src/SongSave.hxx8
-rw-r--r--src/SongSort.cxx2
-rw-r--r--src/SongSort.hxx2
-rw-r--r--src/SongSticker.cxx35
-rw-r--r--src/SongSticker.hxx12
-rw-r--r--src/SongUpdate.cxx38
-rw-r--r--src/StateFile.cxx4
-rw-r--r--src/Stats.cxx64
-rw-r--r--src/Stats.hxx5
-rw-r--r--src/StickerDatabase.hxx8
-rw-r--r--src/TagFile.cxx96
-rw-r--r--src/TagFile.hxx3
-rw-r--r--src/TagPrint.cxx6
-rw-r--r--src/TagPrint.hxx7
-rw-r--r--src/TagStream.cxx78
-rw-r--r--src/TagStream.hxx41
-rw-r--r--src/TextInputStream.cxx1
-rw-r--r--src/TextInputStream.hxx1
-rw-r--r--src/Timer.cxx15
-rw-r--r--src/Timer.hxx2
-rw-r--r--src/UpdateArchive.hxx1
-rw-r--r--src/UpdateContainer.cxx18
-rw-r--r--src/UpdateContainer.hxx2
-rw-r--r--src/UpdateGlue.cxx3
-rw-r--r--src/UpdateRemove.cxx8
-rw-r--r--src/UpdateSong.cxx11
-rw-r--r--src/UpdateWalk.cxx18
-rw-r--r--src/Volume.cxx20
-rw-r--r--src/Volume.hxx2
-rw-r--r--src/ZeroconfAvahi.cxx3
-rw-r--r--src/ZeroconfBonjour.cxx1
-rw-r--r--src/archive/Bzip2ArchivePlugin.cxx6
-rw-r--r--src/archive/Iso9660ArchivePlugin.cxx81
-rw-r--r--src/archive/ZzipArchivePlugin.cxx2
-rw-r--r--src/command/AllCommands.cxx3
-rw-r--r--src/command/CommandError.cxx7
-rw-r--r--src/command/DatabaseCommands.cxx4
-rw-r--r--src/command/FileCommands.cxx26
-rw-r--r--src/command/MessageCommands.cxx1
-rw-r--r--src/command/OtherCommands.cxx36
-rw-r--r--src/command/OutputCommands.cxx2
-rw-r--r--src/command/PlaylistCommands.cxx3
-rw-r--r--src/command/StickerCommands.cxx12
-rw-r--r--src/command/TagCommands.cxx78
-rw-r--r--src/command/TagCommands.hxx33
-rw-r--r--src/cue/CueParser.cxx77
-rw-r--r--src/cue/CueParser.hxx25
-rw-r--r--src/db/LazyDatabase.cxx103
-rw-r--r--src/db/LazyDatabase.hxx69
-rw-r--r--src/db/ProxyDatabasePlugin.cxx181
-rw-r--r--src/db/SimpleDatabasePlugin.cxx18
-rw-r--r--src/db/SimpleDatabasePlugin.hxx3
-rw-r--r--src/db/UpnpDatabasePlugin.cxx860
-rw-r--r--src/db/UpnpDatabasePlugin.hxx27
-rw-r--r--src/db/upnp/ContentDirectoryService.cxx316
-rw-r--r--src/db/upnp/ContentDirectoryService.hxx119
-rw-r--r--src/db/upnp/Device.cxx134
-rw-r--r--src/db/upnp/Device.hxx92
-rw-r--r--src/db/upnp/Directory.cxx184
-rw-r--r--src/db/upnp/Directory.hxx55
-rw-r--r--src/db/upnp/Discovery.cxx328
-rw-r--r--src/db/upnp/Discovery.hxx90
-rw-r--r--src/db/upnp/Domain.cxx23
-rw-r--r--src/db/upnp/Domain.hxx27
-rw-r--r--src/db/upnp/Object.hxx94
-rw-r--r--src/db/upnp/Util.cxx184
-rw-r--r--src/db/upnp/Util.hxx45
-rw-r--r--src/db/upnp/WorkQueue.hxx327
-rw-r--r--src/db/upnp/ixmlwrap.cxx44
-rw-r--r--src/db/upnp/ixmlwrap.hxx35
-rw-r--r--src/db/upnp/upnpplib.cxx99
-rw-r--r--src/db/upnp/upnpplib.hxx76
-rw-r--r--src/decoder/DsdLib.cxx2
-rw-r--r--src/decoder/DsdiffDecoderPlugin.cxx3
-rw-r--r--src/decoder/DsfDecoderPlugin.cxx3
-rw-r--r--src/decoder/FaadDecoderPlugin.cxx169
-rw-r--r--src/decoder/FfmpegDecoderPlugin.cxx1
-rw-r--r--src/decoder/FfmpegMetaData.hxx4
-rw-r--r--src/decoder/FlacCommon.cxx8
-rw-r--r--src/decoder/FlacCommon.hxx1
-rw-r--r--src/decoder/FlacDecoderPlugin.cxx4
-rw-r--r--src/decoder/FlacMetadata.cxx104
-rw-r--r--src/decoder/FlacMetadata.hxx11
-rw-r--r--src/decoder/GmeDecoderPlugin.cxx5
-rw-r--r--src/decoder/MadDecoderPlugin.cxx51
-rw-r--r--src/decoder/MikmodDecoderPlugin.cxx2
-rw-r--r--src/decoder/MpcdecDecoderPlugin.cxx2
-rw-r--r--src/decoder/Mpg123DecoderPlugin.cxx3
-rw-r--r--src/decoder/OggCodec.cxx1
-rw-r--r--src/decoder/OggCodec.hxx3
-rw-r--r--src/decoder/OggFind.hxx1
-rw-r--r--src/decoder/OpusDecoderPlugin.cxx5
-rw-r--r--src/decoder/OpusHead.cxx1
-rw-r--r--src/decoder/PcmDecoderPlugin.cxx2
-rw-r--r--src/decoder/SidplayDecoderPlugin.cxx7
-rw-r--r--src/decoder/VorbisComments.cxx20
-rw-r--r--src/decoder/VorbisComments.hxx2
-rw-r--r--src/decoder/VorbisDecoderPlugin.cxx3
-rw-r--r--src/decoder/VorbisDomain.hxx4
-rw-r--r--src/encoder/FlacEncoderPlugin.cxx31
-rw-r--r--src/encoder/NullEncoderPlugin.cxx30
-rw-r--r--src/encoder/ShineEncoderPlugin.cxx257
-rw-r--r--src/encoder/ShineEncoderPlugin.hxx25
-rw-r--r--src/encoder/VorbisEncoderPlugin.cxx2
-rw-r--r--src/encoder/WaveEncoderPlugin.cxx37
-rw-r--r--src/event/BufferedSocket.cxx3
-rw-r--r--src/event/BufferedSocket.hxx3
-rw-r--r--src/event/Call.cxx21
-rw-r--r--src/event/DeferredMonitor.cxx51
-rw-r--r--src/event/DeferredMonitor.hxx50
-rw-r--r--src/event/FullyBufferedSocket.cxx9
-rw-r--r--src/event/FullyBufferedSocket.hxx1
-rw-r--r--src/event/IdleMonitor.cxx32
-rw-r--r--src/event/IdleMonitor.hxx33
-rw-r--r--src/event/Loop.cxx199
-rw-r--r--src/event/Loop.hxx158
-rw-r--r--src/event/MultiSocketMonitor.cxx139
-rw-r--r--src/event/MultiSocketMonitor.hxx137
-rw-r--r--src/event/PollGroup.hxx41
-rw-r--r--src/event/PollGroupEPoll.hxx91
-rw-r--r--src/event/PollGroupPoll.cxx89
-rw-r--r--src/event/PollGroupPoll.hxx63
-rw-r--r--src/event/PollGroupWinSelect.cxx161
-rw-r--r--src/event/PollGroupWinSelect.hxx111
-rw-r--r--src/event/PollResultGeneric.hxx57
-rw-r--r--src/event/ServerSocket.cxx44
-rw-r--r--src/event/ServerSocket.hxx3
-rw-r--r--src/event/SignalMonitor.cxx11
-rw-r--r--src/event/SocketMonitor.cxx99
-rw-r--r--src/event/SocketMonitor.hxx87
-rw-r--r--src/event/TimeoutMonitor.cxx31
-rw-r--r--src/event/TimeoutMonitor.hxx31
-rw-r--r--src/filter/AutoConvertFilterPlugin.cxx6
-rw-r--r--src/filter/ConvertFilterPlugin.cxx66
-rw-r--r--src/filter/ConvertFilterPlugin.hxx6
-rw-r--r--src/filter/NormalizeFilterPlugin.cxx1
-rw-r--r--src/filter/ReplayGainFilterPlugin.cxx61
-rw-r--r--src/filter/RouteFilterPlugin.cxx1
-rw-r--r--src/filter/VolumeFilterPlugin.cxx65
-rw-r--r--src/fs/AllocatedPath.cxx15
-rw-r--r--src/fs/AllocatedPath.hxx42
-rw-r--r--src/fs/Charset.cxx29
-rw-r--r--src/fs/Config.cxx6
-rw-r--r--src/fs/FileSystem.hxx35
-rw-r--r--src/fs/Path.cxx4
-rw-r--r--src/fs/Path.hxx10
-rw-r--r--src/fs/StandardDirectory.cxx294
-rw-r--r--src/fs/StandardDirectory.hxx64
-rw-r--r--src/fs/TextFile.cxx (renamed from src/TextFile.cxx)0
-rw-r--r--src/fs/TextFile.hxx (renamed from src/TextFile.hxx)0
-rw-r--r--src/fs/Traits.cxx94
-rw-r--r--src/fs/Traits.hxx148
-rw-r--r--src/input/AlsaInputPlugin.cxx431
-rw-r--r--src/input/AlsaInputPlugin.hxx28
-rw-r--r--src/input/ArchiveInputPlugin.cxx16
-rw-r--r--src/input/CdioParanoiaInputPlugin.cxx3
-rw-r--r--src/input/CurlInputPlugin.cxx16
-rw-r--r--src/input/DespotifyInputPlugin.cxx132
-rw-r--r--src/input/FfmpegInputPlugin.cxx16
-rw-r--r--src/input/FileInputPlugin.cxx4
-rw-r--r--src/input/MmsInputPlugin.cxx13
-rw-r--r--src/input/RewindInputPlugin.cxx1
-rw-r--r--src/input/SmbclientInputPlugin.cxx203
-rw-r--r--src/input/SmbclientInputPlugin.hxx25
-rw-r--r--src/ls.cxx13
-rw-r--r--src/ls.hxx3
-rw-r--r--src/mixer/AlsaMixerPlugin.cxx48
-rw-r--r--src/mixer/OssMixerPlugin.cxx4
-rw-r--r--src/mixer/PulseMixerPlugin.cxx2
-rw-r--r--src/mixer/PulseMixerPlugin.hxx7
-rw-r--r--src/mixer/RoarMixerPlugin.cxx2
-rw-r--r--src/mixer/SoftwareMixerPlugin.cxx2
-rw-r--r--src/output/AlsaOutputPlugin.cxx11
-rw-r--r--src/output/FifoOutputPlugin.cxx3
-rw-r--r--src/output/HttpdClient.cxx71
-rw-r--r--src/output/HttpdClient.hxx25
-rw-r--r--src/output/HttpdInternal.hxx68
-rw-r--r--src/output/HttpdOutputPlugin.cxx218
-rw-r--r--src/output/JackOutputPlugin.cxx4
-rw-r--r--src/output/NullOutputPlugin.cxx2
-rw-r--r--src/output/OSXOutputPlugin.cxx49
-rw-r--r--src/output/PulseOutputPlugin.hxx2
-rw-r--r--src/pcm/ChannelsConverter.cxx97
-rw-r--r--src/pcm/ChannelsConverter.hxx83
-rw-r--r--src/pcm/ConfiguredResampler.cxx101
-rw-r--r--src/pcm/ConfiguredResampler.hxx38
-rw-r--r--src/pcm/Domain.cxx23
-rw-r--r--src/pcm/Domain.hxx27
-rw-r--r--src/pcm/FallbackResampler.cxx147
-rw-r--r--src/pcm/FallbackResampler.hxx45
-rw-r--r--src/pcm/FormatConverter.cxx89
-rw-r--r--src/pcm/FormatConverter.hxx84
-rw-r--r--src/pcm/GlueResampler.cxx85
-rw-r--r--src/pcm/GlueResampler.hxx63
-rw-r--r--src/pcm/LibsamplerateResampler.cxx163
-rw-r--r--src/pcm/LibsamplerateResampler.hxx55
-rw-r--r--src/pcm/PcmBuffer.cxx1
-rw-r--r--src/pcm/PcmBuffer.hxx6
-rw-r--r--src/pcm/PcmChannels.cxx295
-rw-r--r--src/pcm/PcmChannels.hxx33
-rw-r--r--src/pcm/PcmConvert.cxx332
-rw-r--r--src/pcm/PcmConvert.hxx65
-rw-r--r--src/pcm/PcmDither.cxx74
-rw-r--r--src/pcm/PcmDither.hxx44
-rw-r--r--src/pcm/PcmDsd.cxx22
-rw-r--r--src/pcm/PcmDsd.hxx10
-rw-r--r--src/pcm/PcmDsdUsb.cxx2
-rw-r--r--src/pcm/PcmFormat.cxx443
-rw-r--r--src/pcm/PcmFormat.hxx31
-rw-r--r--src/pcm/PcmMix.cxx117
-rw-r--r--src/pcm/PcmMix.hxx4
-rw-r--r--src/pcm/PcmPrng.hxx2
-rw-r--r--src/pcm/PcmResample.cxx173
-rw-r--r--src/pcm/PcmResample.hxx133
-rw-r--r--src/pcm/PcmResampleFallback.cxx106
-rw-r--r--src/pcm/PcmResampleInternal.hxx100
-rw-r--r--src/pcm/PcmResampleLibsamplerate.cxx310
-rw-r--r--src/pcm/PcmUtils.hxx46
-rw-r--r--src/pcm/PcmVolume.cxx188
-rw-r--r--src/pcm/PcmVolume.hxx81
-rw-r--r--src/pcm/Resampler.hxx74
-rw-r--r--src/pcm/SoxrResampler.cxx95
-rw-r--r--src/pcm/SoxrResampler.hxx (renamed from src/SongPointer.hxx)54
-rw-r--r--src/pcm/Traits.hxx153
-rw-r--r--src/pcm/Volume.cxx183
-rw-r--r--src/pcm/Volume.hxx130
-rw-r--r--src/playlist/AsxPlaylistPlugin.cxx175
-rw-r--r--src/playlist/CuePlaylistPlugin.cxx11
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.cxx24
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.cxx24
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.cxx27
-rw-r--r--src/playlist/M3uPlaylistPlugin.cxx8
-rw-r--r--src/playlist/PlsPlaylistPlugin.cxx70
-rw-r--r--src/playlist/RssPlaylistPlugin.cxx172
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.cxx157
-rw-r--r--src/playlist/XspfPlaylistPlugin.cxx147
-rw-r--r--src/system/Clock.cxx48
-rw-r--r--src/system/Clock.hxx18
-rw-r--r--src/system/EPollFD.hxx1
-rw-r--r--src/system/FatalError.cxx9
-rw-r--r--src/system/FatalError.hxx2
-rw-r--r--src/system/PeriodClock.hxx149
-rw-r--r--src/system/Resolver.cxx55
-rw-r--r--src/system/Resolver.hxx18
-rw-r--r--src/system/SignalFD.cxx1
-rw-r--r--src/system/SocketError.cxx4
-rw-r--r--src/system/SocketError.hxx6
-rw-r--r--src/system/SocketUtil.cxx2
-rw-r--r--src/tag/Aiff.cxx2
-rw-r--r--src/tag/ApeLoader.cxx8
-rw-r--r--src/tag/ApeTag.hxx2
-rw-r--r--src/tag/Riff.cxx1
-rw-r--r--src/tag/Tag.cxx105
-rw-r--r--src/tag/Tag.hxx29
-rw-r--r--src/tag/TagBuilder.cxx150
-rw-r--r--src/tag/TagBuilder.hxx40
-rw-r--r--src/tag/TagConfig.cxx7
-rw-r--r--src/tag/TagConfig.hxx2
-rw-r--r--src/tag/TagId3.cxx156
-rw-r--r--src/tag/TagId3.hxx10
-rw-r--r--src/tag/TagPool.cxx71
-rw-r--r--src/tag/TagRva2.cxx4
-rw-r--r--src/tag/TagRva2.hxx2
-rw-r--r--src/tag/TagString.cxx15
-rw-r--r--src/tag/TagTable.cxx10
-rw-r--r--src/tag/TagTable.hxx9
-rw-r--r--src/util/Alloc.cxx76
-rw-r--r--src/util/Alloc.hxx67
-rw-r--r--src/util/Cast.hxx (renamed from src/util/growing_fifo.h)55
-rw-r--r--src/util/Clamp.hxx49
-rw-r--r--src/util/ConstBuffer.hxx118
-rw-r--r--src/util/Domain.hxx40
-rw-r--r--src/util/DynamicFifoBuffer.hxx199
-rw-r--r--src/util/Error.cxx43
-rw-r--r--src/util/Error.hxx42
-rw-r--r--src/util/FormatString.cxx5
-rw-r--r--src/util/OptionDef.hxx63
-rw-r--r--src/util/OptionParser.cxx59
-rw-r--r--src/util/OptionParser.hxx88
-rw-r--r--src/util/PeakBuffer.cxx72
-rw-r--r--src/util/PeakBuffer.hxx11
-rw-r--r--src/util/SplitString.cxx37
-rw-r--r--src/util/SplitString.hxx71
-rw-r--r--src/util/StringUtil.cxx8
-rw-r--r--src/util/StringUtil.hxx4
-rw-r--r--src/util/Tokenizer.cxx5
-rw-r--r--src/util/UriUtil.cxx10
-rw-r--r--src/util/UriUtil.hxx7
-rw-r--r--src/util/VarSize.hxx83
-rw-r--r--src/util/WritableBuffer.hxx93
-rw-r--r--src/util/fifo_buffer.c218
-rw-r--r--src/util/fifo_buffer.h164
-rw-r--r--src/util/growing_fifo.c90
-rw-r--r--test/DumpDatabase.cxx37
-rw-r--r--test/FakeDecoderAPI.cxx144
-rw-r--r--test/dump_playlist.cxx158
-rw-r--r--test/dump_rva2.cxx22
-rw-r--r--test/dump_text_file.cxx40
-rw-r--r--test/read_conf.cxx34
-rw-r--r--test/read_mixer.cxx33
-rw-r--r--test/read_tags.cxx115
-rw-r--r--test/run_convert.cxx43
-rw-r--r--test/run_decoder.cxx87
-rw-r--r--test/run_encoder.cxx42
-rw-r--r--test/run_filter.cxx47
-rw-r--r--test/run_inotify.cxx13
-rw-r--r--test/run_input.cxx40
-rw-r--r--test/run_normalize.cxx5
-rw-r--r--test/run_output.cxx32
-rw-r--r--test/run_resolver.cxx17
-rw-r--r--test/software_volume.cxx35
-rw-r--r--test/test_pcm_channels.cxx49
-rw-r--r--test/test_pcm_dither.cxx2
-rw-r--r--test/test_pcm_format.cxx56
-rw-r--r--test/test_pcm_mix.cxx16
-rw-r--r--test/test_pcm_util.hxx10
-rw-r--r--test/test_pcm_volume.cxx188
-rw-r--r--test/test_queue_priority.cxx41
-rw-r--r--test/test_vorbis_encoder.cxx10
-rw-r--r--test/visit_archive.cxx12
-rw-r--r--valgrind.suppressions22
464 files changed, 15172 insertions, 7953 deletions
diff --git a/Makefile.am b/Makefile.am
index 7240cb3f1..c367ca455 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,6 +82,7 @@ src_mpd_SOURCES = \
src/command/CommandError.cxx src/command/CommandError.hxx \
src/command/AllCommands.cxx src/command/AllCommands.hxx \
src/command/QueueCommands.cxx src/command/QueueCommands.hxx \
+ src/command/TagCommands.cxx src/command/TagCommands.hxx \
src/command/PlayerCommands.cxx src/command/PlayerCommands.hxx \
src/command/PlaylistCommands.cxx src/command/PlaylistCommands.hxx \
src/command/DatabaseCommands.cxx src/command/DatabaseCommands.hxx \
@@ -113,6 +114,7 @@ src_mpd_SOURCES = \
src/DatabaseLock.cxx src/DatabaseLock.hxx \
src/DatabaseSave.cxx src/DatabaseSave.hxx \
src/DatabasePlugin.hxx \
+ src/DatabaseListener.hxx \
src/DatabaseVisitor.hxx \
src/DatabaseSelection.cxx src/DatabaseSelection.hxx \
src/ExcludeList.cxx src/ExcludeList.hxx \
@@ -146,7 +148,9 @@ src_mpd_SOURCES = \
src/ClientFile.cxx src/ClientFile.hxx \
src/Listen.cxx src/Listen.hxx \
src/LogInit.cxx src/LogInit.hxx \
+ src/LogBackend.cxx src/LogBackend.hxx \
src/Log.cxx src/Log.hxx src/LogV.hxx \
+ src/LogLevel.hxx \
src/ls.cxx src/ls.hxx \
src/IOThread.cxx src/IOThread.hxx \
src/Main.cxx src/Main.hxx \
@@ -170,6 +174,7 @@ src_mpd_SOURCES = \
src/PlaylistGlobal.cxx src/PlaylistGlobal.hxx \
src/PlaylistControl.cxx \
src/PlaylistEdit.cxx \
+ src/PlaylistTag.cxx \
src/PlaylistPrint.cxx src/PlaylistPrint.hxx \
src/PlaylistSave.cxx src/PlaylistSave.hxx \
src/PlaylistMapper.cxx src/PlaylistMapper.hxx \
@@ -188,6 +193,7 @@ src_mpd_SOURCES = \
src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \
src/SignalHandlers.cxx src/SignalHandlers.hxx \
+ src/DetachedSong.cxx src/DetachedSong.hxx \
src/Song.cxx src/Song.hxx \
src/SongUpdate.cxx \
src/SongPrint.cxx src/SongPrint.hxx \
@@ -198,11 +204,10 @@ src_mpd_SOURCES = \
src/TagPrint.cxx src/TagPrint.hxx \
src/TagSave.cxx src/TagSave.hxx \
src/TagFile.cxx src/TagFile.hxx \
- src/TextFile.cxx src/TextFile.hxx \
+ src/TagStream.cxx src/TagStream.hxx \
src/TextInputStream.cxx \
src/Volume.cxx src/Volume.hxx \
src/SongFilter.cxx src/SongFilter.hxx \
- src/SongPointer.hxx \
src/PlaylistFile.cxx src/PlaylistFile.hxx \
src/Timer.cxx
@@ -245,6 +250,10 @@ endif
libutil_a_SOURCES = \
src/util/Macros.hxx \
+ src/util/Cast.hxx \
+ src/util/Clamp.hxx \
+ src/util/Alloc.cxx src/util/Alloc.hxx \
+ src/util/VarSize.hxx \
src/util/Error.cxx src/util/Error.hxx \
src/util/Domain.hxx \
src/util/ReusableArray.hxx \
@@ -252,19 +261,22 @@ libutil_a_SOURCES = \
src/util/CharUtil.hxx \
src/util/NumberParser.hxx \
src/util/StringUtil.cxx src/util/StringUtil.hxx \
+ src/util/SplitString.cxx src/util/SplitString.hxx \
src/util/FormatString.cxx src/util/FormatString.hxx \
src/util/Tokenizer.cxx src/util/Tokenizer.hxx \
src/util/UriUtil.cxx src/util/UriUtil.hxx \
src/util/Manual.hxx \
src/util/RefCount.hxx \
- src/util/fifo_buffer.c src/util/fifo_buffer.h \
src/util/FifoBuffer.hxx \
+ src/util/DynamicFifoBuffer.hxx \
+ src/util/ConstBuffer.hxx \
src/util/WritableBuffer.hxx \
- src/util/growing_fifo.c src/util/growing_fifo.h \
src/util/LazyRandomEngine.cxx src/util/LazyRandomEngine.hxx \
src/util/SliceBuffer.hxx \
src/util/HugeAllocator.cxx src/util/HugeAllocator.hxx \
src/util/PeakBuffer.cxx src/util/PeakBuffer.hxx \
+ src/util/OptionParser.cxx OptionParser.hxx \
+ src/util/OptionDef.hxx \
src/util/list.h \
src/util/list_sort.c src/util/list_sort.h \
src/util/ByteReverse.cxx src/util/ByteReverse.hxx \
@@ -297,12 +309,18 @@ libsystem_a_SOURCES = \
src/system/EventFD.cxx src/system/EventFD.hxx \
src/system/SignalFD.cxx src/system/SignalFD.hxx \
src/system/EPollFD.cxx src/system/EPollFD.hxx \
+ src/system/PeriodClock.hxx \
src/system/Clock.cxx src/system/Clock.hxx
# Event loop library
libevent_a_SOURCES = \
src/event/WakeFD.hxx \
+ src/event/PollGroup.hxx \
+ src/event/PollGroupEPoll.hxx \
+ src/event/PollGroupPoll.hxx src/event/PollGroupPoll.cxx \
+ src/event/PollGroupWinSelect.hxx src/event/PollGroupWinSelect.cxx \
+ src/event/PollResultGeneric.hxx \
src/event/SignalMonitor.hxx src/event/SignalMonitor.cxx \
src/event/TimeoutMonitor.hxx src/event/TimeoutMonitor.cxx \
src/event/IdleMonitor.hxx src/event/IdleMonitor.cxx \
@@ -318,32 +336,45 @@ libevent_a_SOURCES = \
# PCM library
libpcm_a_SOURCES = \
+ src/pcm/Domain.cxx src/pcm/Domain.hxx \
+ src/pcm/Traits.hxx \
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \
src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h \
src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
src/pcm/PcmDsdUsb.cxx src/pcm/PcmDsdUsb.hxx \
- src/pcm/PcmVolume.cxx src/pcm/PcmVolume.hxx \
+ src/pcm/Volume.cxx src/pcm/Volume.hxx \
src/pcm/PcmMix.cxx src/pcm/PcmMix.hxx \
src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \
src/pcm/PcmFormat.cxx src/pcm/PcmFormat.hxx \
- src/pcm/PcmResample.cxx src/pcm/PcmResample.hxx \
- src/pcm/PcmResampleFallback.cxx \
- src/pcm/PcmResampleInternal.hxx \
+ src/pcm/FormatConverter.cxx src/pcm/FormatConverter.hxx \
+ src/pcm/ChannelsConverter.cxx src/pcm/ChannelsConverter.hxx \
+ src/pcm/Resampler.hxx \
+ src/pcm/GlueResampler.cxx src/pcm/GlueResampler.hxx \
+ src/pcm/FallbackResampler.cxx src/pcm/FallbackResampler.hxx \
+ src/pcm/ConfiguredResampler.cxx src/pcm/ConfiguredResampler.hxx \
src/pcm/PcmDither.cxx src/pcm/PcmDither.hxx \
src/pcm/PcmPrng.hxx \
src/pcm/PcmUtils.hxx
libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(SOXR_CFLAGS) \
$(SAMPLERATE_CFLAGS)
PCM_LIBS = \
libpcm.a \
+ $(SOXR_LIBS) \
$(SAMPLERATE_LIBS)
if HAVE_LIBSAMPLERATE
-libpcm_a_SOURCES += src/pcm/PcmResampleLibsamplerate.cxx
+libpcm_a_SOURCES += \
+ src/pcm/LibsamplerateResampler.cxx src/pcm/LibsamplerateResampler.hxx
+endif
+
+if HAVE_SOXR
+libpcm_a_SOURCES += \
+ src/pcm/SoxrResampler.cxx src/pcm/SoxrResampler.hxx
endif
# File system library
@@ -356,7 +387,9 @@ libfs_a_SOURCES = \
src/fs/Charset.cxx src/fs/Charset.hxx \
src/fs/Path.cxx src/fs/Path.hxx \
src/fs/AllocatedPath.cxx src/fs/AllocatedPath.hxx \
+ src/fs/TextFile.cxx src/fs/TextFile.hxx \
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
+ src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
src/fs/DirectoryReader.hxx
# database plugins
@@ -364,6 +397,7 @@ libfs_a_SOURCES = \
libdb_plugins_a_SOURCES = \
src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \
src/DatabaseHelpers.cxx src/DatabaseHelpers.hxx \
+ src/db/LazyDatabase.cxx src/db/LazyDatabase.hxx \
src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx
if HAVE_LIBMPDCLIENT
@@ -375,6 +409,24 @@ DB_LIBS = \
libdb_plugins.a \
$(LIBMPDCLIENT_LIBS)
+if HAVE_LIBUPNP
+libdb_plugins_a_SOURCES += \
+ src/db/UpnpDatabasePlugin.cxx src/db/UpnpDatabasePlugin.hxx \
+ src/db/upnp/ContentDirectoryService.cxx src/db/upnp/ContentDirectoryService.hxx \
+ src/db/upnp/Device.cxx src/db/upnp/Device.hxx \
+ src/db/upnp/Directory.cxx src/db/upnp/Directory.hxx \
+ src/db/upnp/Discovery.cxx src/db/upnp/Discovery.hxx \
+ src/db/upnp/Domain.cxx src/db/upnp/Domain.hxx \
+ src/db/upnp/ixmlwrap.cxx src/db/upnp/ixmlwrap.hxx \
+ src/db/upnp/upnpplib.cxx src/db/upnp/upnpplib.hxx \
+ src/db/upnp/Util.cxx src/db/upnp/Util.hxx \
+ src/db/upnp/WorkQueue.hxx \
+ src/db/upnp/Object.hxx
+DB_LIBS += \
+ $(EXPAT_LIBS) \
+ $(UPNP_LIBS)
+endif
+
# archive plugins
if ENABLE_ARCHIVE
@@ -670,6 +722,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(TWOLAME_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(OPUS_CFLAGS) \
+ $(SHINE_CFLAGS) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
@@ -678,6 +731,7 @@ ENCODER_LIBS = \
$(TWOLAME_LIBS) \
$(FLAC_LIBS) \
$(OPUS_LIBS) \
+ $(SHINE_LIBS) \
$(VORBISENC_LIBS)
libencoder_plugins_a_SOURCES = \
@@ -728,6 +782,12 @@ libencoder_plugins_a_SOURCES += \
src/encoder/FlacEncoderPlugin.cxx src/encoder/FlacEncoderPlugin.hxx
endif
+if ENABLE_SHINE_ENCODER
+libencoder_plugins_a_SOURCES += \
+ src/encoder/ShineEncoderPlugin.cxx \
+ src/encoder/ShineEncoderPlugin.hxx
+endif
+
else
ENCODER_LIBS =
endif
@@ -763,6 +823,7 @@ libinput_a_SOURCES = \
libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
+ $(SMBCLIENT_CFLAGS) \
$(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(DESPOTIFY_CFLAGS) \
@@ -771,17 +832,31 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
INPUT_LIBS = \
libinput.a \
$(CURL_LIBS) \
+ $(SMBCLIENT_LIBS) \
$(CDIO_PARANOIA_LIBS) \
$(FFMPEG_LIBS) \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
+if HAVE_ALSA
+libinput_a_SOURCES += \
+ src/input/AlsaInputPlugin.cxx \
+ src/input/AlsaInputPlugin.hxx
+INPUT_LIBS += $(ALSA_LIBS)
+endif
+
+
if ENABLE_CURL
libinput_a_SOURCES += \
src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \
src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx
endif
+if ENABLE_SMBCLIENT
+libinput_a_SOURCES += \
+ src/input/SmbclientInputPlugin.cxx src/input/SmbclientInputPlugin.hxx
+endif
+
if ENABLE_CDIO_PARANOIA
libinput_a_SOURCES += \
src/input/CdioParanoiaInputPlugin.cxx \
@@ -963,25 +1038,19 @@ libplaylist_plugins_a_SOURCES = \
src/playlist/ExtM3uPlaylistPlugin.hxx \
src/playlist/M3uPlaylistPlugin.cxx \
src/playlist/M3uPlaylistPlugin.hxx \
- src/playlist/PlsPlaylistPlugin.cxx \
- src/playlist/PlsPlaylistPlugin.hxx \
- src/playlist/XspfPlaylistPlugin.cxx \
- src/playlist/XspfPlaylistPlugin.hxx \
- src/playlist/AsxPlaylistPlugin.cxx \
- src/playlist/AsxPlaylistPlugin.hxx \
- src/playlist/RssPlaylistPlugin.cxx \
- src/playlist/RssPlaylistPlugin.hxx \
src/playlist/CuePlaylistPlugin.cxx \
src/playlist/CuePlaylistPlugin.hxx \
src/playlist/EmbeddedCuePlaylistPlugin.cxx \
src/playlist/EmbeddedCuePlaylistPlugin.hxx \
src/PlaylistRegistry.cxx src/PlaylistRegistry.hxx
libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(EXPAT_CFLAGS) \
$(YAJL_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS))
PLAYLIST_LIBS = \
libplaylist_plugins.a \
+ $(EXPAT_LIBS) \
$(FLAC_LIBS)
if ENABLE_DESPOTIFY
@@ -997,6 +1066,23 @@ libplaylist_plugins_a_SOURCES += \
PLAYLIST_LIBS += $(YAJL_LIBS)
endif
+if HAVE_EXPAT
+libplaylist_plugins_a_SOURCES += \
+ src/Expat.cxx src/Expat.hxx \
+ src/playlist/XspfPlaylistPlugin.cxx \
+ src/playlist/XspfPlaylistPlugin.hxx \
+ src/playlist/AsxPlaylistPlugin.cxx \
+ src/playlist/AsxPlaylistPlugin.hxx \
+ src/playlist/RssPlaylistPlugin.cxx \
+ src/playlist/RssPlaylistPlugin.hxx
+endif
+
+if HAVE_GLIB
+libplaylist_plugins_a_SOURCES += \
+ src/playlist/PlsPlaylistPlugin.cxx \
+ src/playlist/PlsPlaylistPlugin.hxx
+endif
+
#
# Filter plugins
#
@@ -1110,7 +1196,7 @@ test_read_conf_LDADD = \
libfs.a \
$(GLIB_LIBS)
test_read_conf_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/read_conf.cxx
test_run_resolver_LDADD = \
@@ -1118,7 +1204,7 @@ test_run_resolver_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_run_resolver_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/run_resolver.cxx
test_DumpDatabase_LDADD = \
@@ -1126,12 +1212,13 @@ test_DumpDatabase_LDADD = \
$(TAG_LIBS) \
libconf.a \
libutil.a \
+ libevent.a \
libsystem.a \
libfs.a \
$(GLIB_LIBS)
test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
src/protocol/Ack.cxx \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/DatabaseError.cxx \
src/DatabaseRegistry.cxx \
src/DatabaseSelection.cxx \
@@ -1140,8 +1227,11 @@ test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
src/DatabaseLock.cxx src/DatabaseSave.cxx \
src/Song.cxx src/SongSave.cxx src/SongSort.cxx \
src/TagSave.cxx \
- src/SongFilter.cxx \
- src/TextFile.cxx
+ src/SongFilter.cxx
+
+if HAVE_LIBUPNP
+test_DumpDatabase_SOURCES += src/Expat.cxx
+endif
test_run_input_LDADD = \
$(INPUT_LIBS) \
@@ -1156,7 +1246,7 @@ test_run_input_LDADD = \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/TagSave.cxx
@@ -1174,7 +1264,7 @@ test_visit_archive_LDADD = \
libfs.a \
$(GLIB_LIBS)
test_visit_archive_SOURCES = test/visit_archive.cxx \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/InputStream.cxx
@@ -1197,7 +1287,7 @@ test_dump_text_file_LDADD = \
$(GLIB_LIBS)
test_dump_text_file_SOURCES = test/dump_text_file.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/TextInputStream.cxx
@@ -1217,10 +1307,11 @@ test_dump_playlist_LDADD = \
libpcm.a \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
+ test/FakeDecoderAPI.cxx \
$(DECODER_SRC) \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
- src/Song.cxx src/TagSave.cxx \
+ src/TagSave.cxx \
src/TagFile.cxx \
src/CheckAudioFormat.cxx \
src/TextInputStream.cxx \
@@ -1247,7 +1338,7 @@ test_run_decoder_LDADD = \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/ReplayGainInfo.cxx \
src/AudioFormat.cxx src/CheckAudioFormat.cxx \
@@ -1270,7 +1361,8 @@ test_read_tags_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.cxx \
- src/Log.cxx \
+ test/FakeDecoderAPI.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/ReplayGainInfo.cxx \
src/CheckAudioFormat.cxx \
@@ -1282,7 +1374,7 @@ test_dump_rva2_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_dump_rva2_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/dump_rva2.cxx
endif
@@ -1296,7 +1388,7 @@ test_run_filter_LDADD = \
test_run_filter_SOURCES = test/run_filter.cxx \
test/FakeReplayGainConfig.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/FilterPlugin.cxx src/FilterRegistry.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
@@ -1316,7 +1408,7 @@ if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
src/AudioParser.cxx
@@ -1336,7 +1428,7 @@ if ENABLE_VORBIS_ENCODER
noinst_PROGRAMS += test/test_vorbis_encoder
test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
src/AudioParser.cxx \
@@ -1356,7 +1448,8 @@ endif
test_software_volume_SOURCES = test/software_volume.cxx \
test/stdbin.h \
- src/CheckAudioFormat.cxx \
+ src/Log.cxx src/LogBackend.cxx \
+ src/AudioFormat.cxx src/CheckAudioFormat.cxx \
src/AudioParser.cxx
test_software_volume_LDADD = \
$(PCM_LIBS) \
@@ -1364,7 +1457,7 @@ test_software_volume_LDADD = \
$(GLIB_LIBS)
test_run_avahi_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/ZeroconfAvahi.cxx src/AvahiPoll.cxx \
test/ShutdownHandler.cxx test/ShutdownHandler.hxx \
test/run_avahi.cxx
@@ -1386,7 +1479,7 @@ test_run_normalize_LDADD = \
$(GLIB_LIBS)
test_run_convert_SOURCES = test/run_convert.cxx \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/AudioFormat.cxx \
src/CheckAudioFormat.cxx \
src/AudioParser.cxx
@@ -1412,7 +1505,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
test_run_output_SOURCES = test/run_output.cxx \
test/FakeReplayGainConfig.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
@@ -1440,9 +1533,10 @@ test_read_mixer_LDADD = \
libfs.a \
$(GLIB_LIBS)
test_read_mixer_SOURCES = test/read_mixer.cxx \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/MixerControl.cxx \
src/FilterPlugin.cxx \
+ src/AudioFormat.cxx \
src/filter/VolumeFilterPlugin.cxx
if ENABLE_BZIP2_TEST
@@ -1461,7 +1555,7 @@ if ENABLE_INOTIFY
noinst_PROGRAMS += test/run_inotify
test_run_inotify_SOURCES = test/run_inotify.cxx \
test/ShutdownHandler.cxx test/ShutdownHandler.hxx \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/InotifyDomain.cxx \
src/InotifySource.cxx
test_run_inotify_LDADD = \
@@ -1488,7 +1582,7 @@ test_test_byte_reverse_LDADD = \
$(CPPUNIT_LIBS)
test_test_mixramp_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/test_mixramp.cxx
test_test_mixramp_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_mixramp_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
@@ -1497,6 +1591,7 @@ test_test_mixramp_LDADD = \
$(CPPUNIT_LIBS)
test_test_pcm_SOURCES = \
+ src/AudioFormat.cxx \
test/test_pcm_util.hxx \
test/test_pcm_dither.cxx \
test/test_pcm_pack.cxx \
@@ -1515,7 +1610,7 @@ test_test_pcm_LDADD = \
$(GLIB_LIBS)
test_test_archive_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/test_archive.cxx
test_test_archive_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_archive_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
diff --git a/NEWS b/NEWS
index d59f4614d..a3be05562 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,23 @@
+ver 0.19 (not yet released)
+* protocol
+ - new commands "addtagid", "cleartagid"
+ - "lsinfo" and "readcomments" allowed for remote files
+* database
+ - proxy: forward "idle" events
+ - upnp: new plugin
+* playlist
+ - soundcloud: use https instead of http
+* archive
+ - read tags from songs in an archive
+* input
+ - alsa: new input plugin
+ - smbclient: new input plugin
+* filter
+ - volume: improved software volume dithering
+* encoder:
+ - shine: new encoder plugin
+* new resampler option using libsoxr
+
ver 0.18.7 (2013/01/13)
* playlist
- pls: fix crash after parser error
diff --git a/configure.ac b/configure.ac
index 2cd0c7720..34fb4b1c1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,9 +1,9 @@
AC_PREREQ(2.60)
-AC_INIT(mpd, 0.18.7, musicpd-dev-team@lists.sourceforge.net)
+AC_INIT(mpd, 0.19~git, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
-VERSION_MINOR=18
+VERSION_MINOR=19
VERSION_REVISION=0
VERSION_EXTRA=0
@@ -13,7 +13,7 @@ AM_SILENT_RULES
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_MACRO_DIR([m4])
-AC_DEFINE(PROTOCOL_VERSION, "0.18.0", [The MPD protocol version])
+AC_DEFINE(PROTOCOL_VERSION, "0.19.0", [The MPD protocol version])
dnl ---------------------------------------------------------------------------
@@ -65,9 +65,20 @@ dnl OS Specific Defaults
dnl ---------------------------------------------------------------------------
AC_CANONICAL_HOST
+host_is_unix=yes
+host_is_linux=no
host_is_darwin=no
+host_is_solaris=no
+host_is_windows=no
+
+linux_auto=no
case "$host_os" in
+linux*)
+ host_is_linux=yes
+ linux_auto=auto
+ ;;
+
mingw32* | windows*)
AC_CONFIG_FILES([
src/win/mpd_win32_rc.rc
@@ -76,14 +87,19 @@ mingw32* | windows*)
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"
AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0600 -D_WIN32_WINNT=0x0600"
LIBS="$LIBS -lws2_32"
- HAVE_WINDOWS=1
+ host_is_windows=yes
+ host_is_unix=no
;;
darwin*)
host_is_darwin=yes
;;
+
+solaris*)
+ host_is_solaris=yes
+ ;;
esac
-AM_CONDITIONAL([HAVE_WINDOWS], [test x$HAVE_WINDOWS = x1])
+AM_CONDITIONAL([HAVE_WINDOWS], [test x$host_is_windows = xyes])
if test -z "$prefix" || test "x$prefix" = xNONE; then
local_lib=
@@ -145,10 +161,16 @@ AC_SEARCH_LIBS([syslog], [bsd socket inet],
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl])
-AC_CHECK_FUNCS(pipe2 accept4)
-MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
-MPD_OPTIONAL_FUNC(signalfd, signalfd, USE_SIGNALFD)
-MPD_OPTIONAL_FUNC(epoll, epoll_create1, USE_EPOLL)
+if test x$host_is_linux = xyes; then
+ AC_CHECK_FUNCS(pipe2 accept4)
+fi
+
+AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
+
+if test x$host_is_linux = xyes; then
+ MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
+ MPD_OPTIONAL_FUNC(signalfd, signalfd, USE_SIGNALFD)
+fi
AC_SEARCH_LIBS([exp], [m],,
[AC_MSG_ERROR([exp() not found])])
@@ -157,6 +179,48 @@ AC_CHECK_HEADERS(locale.h)
AC_CHECK_HEADERS(valgrind/memcheck.h)
dnl ---------------------------------------------------------------------------
+dnl Event loop selection
+dnl ---------------------------------------------------------------------------
+
+MPD_OPTIONAL_FUNC_NODEF(poll, poll)
+
+if test x$host_is_linux = xyes; then
+ MPD_OPTIONAL_FUNC_NODEF(epoll, epoll_create1)
+fi
+
+AC_ARG_WITH(pollmethod,
+ AS_HELP_STRING(
+ [--with-pollmethod=@<:@epoll|poll|winselect|auto@:>@],
+ [specify poll method for internal event loop (default=auto)]),,
+ [with_pollmethod=auto])
+
+if test "x$with_pollmethod" = xauto; then
+ if test "x$enable_epoll" = xyes; then
+ with_pollmethod=epoll
+ elif test "x$enable_poll" = xyes; then
+ with_pollmethod=poll
+ elif test "x$host_is_windows" = xyes; then
+ with_pollmethod=winselect
+ else
+ AC_MSG_ERROR([no poll method is available for your platform])
+ fi
+fi
+case "$with_pollmethod" in
+epoll)
+ AC_DEFINE(USE_EPOLL, 1, [Define to poll sockets with epoll])
+ ;;
+poll)
+ AC_DEFINE(USE_POLL, 1, [Define to poll sockets with poll])
+ ;;
+winselect)
+ AC_DEFINE(USE_WINSELECT, 1,
+ [Define to poll sockets with Windows select])
+ ;;
+*)
+ AC_MSG_ERROR([unknown pollmethod option: $with_pollmethod])
+esac
+
+dnl ---------------------------------------------------------------------------
dnl Allow tools to be specifically built
dnl ---------------------------------------------------------------------------
@@ -165,6 +229,16 @@ AC_ARG_ENABLE(libmpdclient,
[enable support for the MPD client]),,
enable_libmpdclient=auto)
+AC_ARG_ENABLE(expat,
+ AS_HELP_STRING([--enable-expat],
+ [enable the expat XML parser]),,
+ enable_expat=auto)
+
+AC_ARG_ENABLE(upnp,
+ AS_HELP_STRING([--enable-upnp],
+ [enable UPnP client support (default: auto)]),,
+ enable_upnp=auto)
+
AC_ARG_ENABLE(adplug,
AS_HELP_STRING([--enable-adplug],
[enable the AdPlug decoder plugin (default: auto)]),,
@@ -172,7 +246,7 @@ AC_ARG_ENABLE(adplug,
AC_ARG_ENABLE(alsa,
AS_HELP_STRING([--enable-alsa], [enable ALSA support]),,
- [enable_alsa=auto])
+ [enable_alsa=$linux_auto])
AC_ARG_ENABLE(roar,
AS_HELP_STRING([--enable-roar],
@@ -204,6 +278,11 @@ AC_ARG_ENABLE(curl,
[enable support for libcurl HTTP streaming (default: auto)]),,
[enable_curl=auto])
+AC_ARG_ENABLE(smbclient,
+ AS_HELP_STRING([--enable-smbclient],
+ [enable support for libsmbclient (default: auto)]),,
+ [enable_smbclient=auto])
+
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
[enable debugging (default: disabled)]),,
@@ -295,6 +374,11 @@ AC_ARG_ENABLE(lsr,
[enable libsamplerate support]),,
enable_lsr=auto)
+AC_ARG_ENABLE(soxr,
+ AS_HELP_STRING([--enable-soxr],
+ [enable the libsoxr resampler]),,
+ enable_soxr=auto)
+
AC_ARG_ENABLE(mad,
AS_HELP_STRING([--enable-mad],
[enable libmad mp3 decoder plugin]),,
@@ -365,6 +449,10 @@ AC_ARG_ENABLE(sidplay,
[enable C64 SID support via libsidplay2]),,
enable_sidplay=auto)
+AC_ARG_ENABLE(shine-encoder,
+ AS_HELP_STRING([--enable-shine-encoder],
+ [enables shine encoder]),,
+ [enable_shine_encoder=auto])
AC_ARG_ENABLE(shout,
AS_HELP_STRING([--enable-shout],
@@ -379,7 +467,7 @@ AC_ARG_ENABLE(sndfile,
AC_ARG_ENABLE(solaris_output,
AS_HELP_STRING([--enable-solaris-output],
[enables the Solaris /dev/audio output]),,
- [enable_solaris_output=auto])
+ [enable_solaris_output=$host_is_solaris])
AC_ARG_ENABLE(sqlite,
AS_HELP_STRING([--enable-sqlite],
@@ -389,7 +477,7 @@ AC_ARG_ENABLE(sqlite,
AC_ARG_ENABLE(systemd-daemon,
AS_HELP_STRING([--enable-systemd-daemon],
[use the systemd daemon library (default=auto)]),,
- [enable_systemd_daemon=auto])
+ [enable_systemd_daemon=$linux_auto])
AC_ARG_ENABLE(tcp,
AS_HELP_STRING([--disable-tcp],
@@ -414,7 +502,7 @@ AC_ARG_ENABLE(twolame-encoder,
AC_ARG_ENABLE(un,
AS_HELP_STRING([--disable-un],
[disable support for clients connecting via unix domain sockets (default: enable)]),,
- [enable_un=yes])
+ [enable_un=$host_is_unix])
AC_ARG_ENABLE(vorbis,
AS_HELP_STRING([--enable-vorbis],
@@ -470,13 +558,24 @@ AC_ARG_WITH(tremor-includes,
dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries
dnl ---------------------------------------------------------------------------
-PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
+
+AC_ARG_ENABLE(glib,
+ AS_HELP_STRING([--enable-glib],
+ [enable GLib usage (default: enabled)]),,
+ enable_glib=yes)
+
+if test x$enable_glib = xyes; then
+ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
[AC_MSG_ERROR([GLib 2.28 is required])])
-if test x$GCC = xyes; then
- # suppress warnings in the GLib headers
- GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
+ if test x$GCC = xyes; then
+ # suppress warnings in the GLib headers
+ GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
+ fi
+
+ AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used])
fi
+AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
dnl ---------------------------------------------------------------------------
dnl Protocol Options
@@ -514,12 +613,6 @@ if test x$enable_tcp = xyes; then
AC_DEFINE(HAVE_TCP, 1, [Define if TCP socket support is enabled])
fi
-case "$host_os" in
-mingw* | windows* | cygwin*)
- enable_un=no
- ;;
-esac
-
if test x$enable_un = xyes; then
AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
STRUCT_UCRED
@@ -560,6 +653,15 @@ fi
AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes)
+dnl -------------------------------- expat --------------------------------
+MPD_AUTO_PKG(expat, EXPAT, [expat],
+ [expat XML parser], [expat not found])
+if test x$enable_expat = xyes; then
+ AC_DEFINE(HAVE_EXPAT, 1, [Define to use the expat XML parser])
+fi
+
+AM_CONDITIONAL(HAVE_EXPAT, test x$enable_expat = xyes)
+
dnl --------------------------------- inotify ---------------------------------
AC_CHECK_FUNCS(inotify_init inotify_init1)
@@ -692,21 +794,22 @@ dnl Converter Plugins
dnl ---------------------------------------------------------------------------
dnl ------------------------------ libsamplerate ------------------------------
-MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.0.15],
+MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.1.3],
[libsamplerate resampling], [libsamplerate not found])
if test x$enable_lsr = xyes; then
AC_DEFINE([HAVE_LIBSAMPLERATE], 1,
[Define to enable libsamplerate])
fi
+AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes)
-if test x$enable_lsr = xyes; then
- PKG_CHECK_MODULES([SAMPLERATE_013],
- [samplerate >= 0.1.3],,
- [AC_DEFINE([HAVE_LIBSAMPLERATE_NOINT], 1,
- [libsamplerate doesn't provide src_int_to_float_array() (<0.1.3)])])
+dnl ------------------------------ libsoxr ------------------------------------
+MPD_AUTO_PKG(soxr, SOXR, [soxr],
+ [libsoxr resampler], [libsoxr not found])
+if test x$enable_soxr = xyes; then
+ AC_DEFINE([HAVE_SOXR], 1, [Define to enable libsoxr])
fi
-AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes)
+AM_CONDITIONAL(HAVE_SOXR, test x$enable_soxr = xyes)
dnl ---------------------------------------------------------------------------
dnl Input Plugins
@@ -720,6 +823,14 @@ if test x$enable_curl = xyes; then
fi
AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes)
+dnl ----------------------------------- smbclient -----------------------------
+MPD_AUTO_PKG(smbclient, SMBCLIENT, [smbclient >= 0.2],
+ [smbclient input plugin], [libsmbclient not found])
+if test x$enable_smbclient = xyes; then
+ AC_DEFINE(ENABLE_SMBCLIENT, 1, [Define when libsmbclient is used])
+fi
+AM_CONDITIONAL(ENABLE_SMBCLIENT, test x$enable_smbclient = xyes)
+
dnl --------------------------------- Despotify ---------------------------------
MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
[Despotify support], [despotify not found])
@@ -798,6 +909,24 @@ fi
AM_CONDITIONAL(ENABLE_BZIP2_TEST, test x$BZIP2 != xno)
+dnl ---------------------------------- libupnp ---------------------------------
+
+if test x$enable_expat = xno; then
+ if test x$enable_upnp = xauto; then
+ AC_MSG_WARN([expat disabled -- disabling UPnP])
+ enable_upnp=no
+ elif test x$enable_upnp = xyes; then
+ AC_MSG_ERROR([expat disabled -- required for UPnP])
+ fi
+fi
+
+MPD_AUTO_PKG(upnp, UPNP, [libupnp],
+ [UPnP client support], [libupnp not found])
+if test x$enable_upnp = xyes; then
+ AC_DEFINE(HAVE_LIBUPNP, 1, [Define when libupnp is used])
+fi
+AM_CONDITIONAL(HAVE_LIBUPNP, test x$enable_upnp = xyes)
+
dnl --------------------------------- libzzip ---------------------------------
MPD_AUTO_PKG(zzip, ZZIP, [zziplib >= 0.13],
[libzzip archive library], [libzzip not found])
@@ -1127,6 +1256,7 @@ else
enable_vorbis_encoder=no
enable_lame_encoder=no
enable_twolame_encoder=no
+ enable_shine_encoder=no
enable_wave_encoder=no
enable_flac_encoder=no
fi
@@ -1138,6 +1268,17 @@ if test x$enable_flac_encoder = xyes; then
fi
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
+dnl ------------------------------- Shine Encoder ------------------------------
+
+MPD_AUTO_PKG(shine_encoder, SHINE, [shine >= 3],
+ [shine encoder], [libshine not found])
+
+if test x$enable_shine_encoder = xyes; then
+ AC_DEFINE(ENABLE_SHINE_ENCODER, 1,
+ [Define to enable the shine encoder plugin])
+fi
+AM_CONDITIONAL(ENABLE_SHINE_ENCODER, test x$enable_shine_encoder = xyes)
+
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc],
[Ogg Vorbis encoder], [libvorbisenc not found])
@@ -1181,6 +1322,7 @@ if test x$enable_vorbis_encoder != xno ||
test x$enable_lame_encoder != xno ||
test x$enable_twolame_encoder != xno ||
test x$enable_flac_encoder != xno ||
+ test x$enable_shine_encoder != xno ||
test x$enable_wave_encoder != xno; then
# at least one encoder plugin is enabled
enable_encoder=yes
@@ -1376,18 +1518,6 @@ AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes)
dnl --------------------------------- Solaris ---------------------------------
-if test x$enable_solaris_output = xauto; then
- case "$host_os" in
- solaris*)
- enable_solaris_output=yes
- ;;
-
- *)
- enable_solaris_output=no
- ;;
- esac
-fi
-
if test x$enable_solaris_output = xyes; then
AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support])
fi
@@ -1396,17 +1526,13 @@ AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
dnl --------------------------------- WinMM ---------------------------------
-case "$host_os" in
- mingw32* | windows*)
- AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
- enable_winmm_output=yes
- LIBS="$LIBS -lwinmm"
- ;;
-
- *)
- enable_winmm_output=no
- ;;
-esac
+if test "x$host_is_windows" = xyes; then
+ AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
+ enable_winmm_output=yes
+ LIBS="$LIBS -lwinmm"
+else
+ enable_winmm_output=no
+fi
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
@@ -1574,6 +1700,7 @@ results(wildmidi, [WildMidi])
printf '\nOther features:\n\t'
results(lsr, [libsamplerate])
+results(soxr, [libsoxr])
results(libmpdclient, [libmpdclient])
results(inotify, [inotify])
results(sqlite, [SQLite])
@@ -1607,6 +1734,7 @@ if
printf '\nStreaming encoder support:\n\t'
results(flac_encoder, [FLAC])
results(lame_encoder, [LAME])
+ results(shine_encoder, [Shine])
results(vorbis_encoder, [Ogg Vorbis])
results(opus, [Opus])
results(twolame_encoder, [TwoLAME])
@@ -1616,11 +1744,15 @@ fi
printf '\nStreaming support:\n\t'
results(cdio_paranoia, [CDIO_PARANOIA])
results(curl,[CURL])
+results(smbclient,[SMBCLIENT])
results(despotify,[Despotify])
results(soundcloud,[Soundcloud])
printf '\n\t'
results(mms,[MMS])
+printf '\nEvent loop:\n\t'
+printf $with_pollmethod
+
printf '\n\n##########################################\n\n'
echo 'Generating files needed for compilation'
diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5
index 6431613d1..6e3bae7b2 100644
--- a/doc/mpd.conf.5
+++ b/doc/mpd.conf.5
@@ -136,53 +136,6 @@ for the format of this parameter. Multiple audio_output sections may be
specified. If no audio_output section is specified, then MPD will scan for a
usable audio output.
.TP
-.B audio_output_format <sample_rate:bits:channels>
-This specifies the sample rate, bits per sample, and number of channels of
-audio that is sent to each audio output. Note that audio outputs may specify
-their own audio format which will be used for actual output to the audio
-device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The
-default is to use the audio format of the input file.
-Any of the three attributes may be an asterisk to specify that this
-attribute should not be enforced
-.TP
-.B samplerate_converter <integer or prefix>
-This specifies the libsamplerate converter to use. The supplied value should
-either be an integer or a prefix of the name of a converter. The default is
-"Fastest Sinc Interpolator".
-
-At the time of this writing, the following converters are available:
-.RS
-.TP
-Best Sinc Interpolator (0)
-
-Band limited sinc interpolation, best quality, 97dB SNR, 96% BW.
-.TP
-Medium Sinc Interpolator (1)
-
-Band limited sinc interpolation, medium quality, 97dB SNR, 90% BW.
-.TP
-Fastest Sinc Interpolator (2)
-
-Band limited sinc interpolation, fastest, 97dB SNR, 80% BW.
-.TP
-ZOH Interpolator (3)
-
-Zero order hold interpolator, very fast, very poor quality with audible
-distortions.
-.TP
-Linear Interpolator (4)
-
-Linear interpolator, very fast, poor quality.
-.TP
-internal
-
-Poor quality, no floating point operations. This is the default (and
-only choice) if MPD was compiled without libsamplerate.
-.RE
-.IP
-For an up-to-date list of available converters, please see the libsamplerate
-documentation (available online at <\fBhttp://www.mega\-nerd.com/SRC/\fP>).
-.TP
.B replaygain <off or album or track or auto>
If specified, mpd will adjust the volume of songs played using ReplayGain tags
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
diff --git a/doc/protocol.xml b/doc/protocol.xml
index abc74e4e6..625fef874 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -1258,6 +1258,44 @@ OK
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="command_addtagid">
+ <term>
+ <cmdsynopsis>
+ <command>addtagid</command>
+ <arg choice="req"><replaceable>SONGID</replaceable></arg>
+ <arg choice="req"><replaceable>TAG</replaceable></arg>
+ <arg choice="req"><replaceable>VALUE</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Adds a tag to the specified song. Editing song tags is
+ only possible for remote songs. This change is
+ volatile: it may be overwritten by tags received from
+ the server, and the data is gone when the song gets
+ removed from the queue.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_cleartagid">
+ <term>
+ <cmdsynopsis>
+ <command>cleartagid</command>
+ <arg choice="req"><replaceable>SONGID</replaceable></arg>
+ <arg choice="opt"><replaceable>TAG</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Removes tags from the specified song. If
+ <varname>TAG</varname> is not specified, then all tag
+ values will be removed. Editing song tags is only
+ possible for remote songs.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</section>
@@ -1580,6 +1618,10 @@ OK
deprecated; use "listplaylists" instead.
</para>
<para>
+ This command may be used to list metadata of remote
+ files (e.g. URI beginning with "http://" or "smb://").
+ </para>
+ <para>
Clients that are connected via UNIX domain socket may
use this command to read the tags of an arbitrary local
file (URI beginning with "file:///").
@@ -1601,6 +1643,10 @@ OK
"file:///foo/bar.ogg".
</para>
<para>
+ This command may be used to list metadata of remote
+ files (e.g. URI beginning with "http://" or "smb://").
+ </para>
+ <para>
The response consists of lines in the form "KEY: VALUE".
Comments with suspicious characters (e.g. newlines) are
ignored silently.
@@ -1952,6 +1998,32 @@ OK
<para>
Shows information about all outputs.
</para>
+ <screen>
+outputid: 0
+outputname: My ALSA Device
+outputenabled: 0
+OK
+ </screen>
+ <para>
+ Return information:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <varname>outputid</varname>: ID of the output. May change between executions
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <varname>outputname</varname>: Name of the output. It can be any.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <varname>outputenabled</varname>: Status of the output. 0 if disabled, 1 if enabled.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/user.xml b/doc/user.xml
index ccc9e7119..a49f025b2 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -393,7 +393,7 @@ systemctl start mpd.socket</programlisting>
a name registered in the PULSE server.
</entry>
</row>
- <row>
+ <row id="ao_format">
<entry>
<varname>format</varname>
</entry>
@@ -417,8 +417,6 @@ systemctl start mpd.socket</programlisting>
(signed 8 bit integer samples),
<varname>16</varname>, <varname>24</varname> (signed
24 bit integer samples padded to 32 bit),
- <varname>24_3</varname> (signed 24 bit integer
- samples, no padding, 3 bytes per sample),
<varname>32</varname> (signed 32 bit integer
samples), <varname>f</varname> (32 bit floating
point, -1.0 to 1.0).
@@ -609,6 +607,165 @@ systemctl start mpd.socket</programlisting>
</tgroup>
</informaltable>
</section>
+
+ <section>
+ <title>Audio Format Settings</title>
+
+ <section>
+ <title>Global Audio Format</title>
+
+ <para>
+ The setting <varname>audio_output_format</varname> forces
+ MPD to use one audio format for all outputs. Doing that is
+ usually not a good idea. The values are the same as in
+ <link linkend="ao_format"><varname>format</varname> in the
+ <varname>audio_output</varname> section</link>.
+ </para>
+ </section>
+
+ <section>
+ <title>Resampler</title>
+
+ <para>
+ Sometimes, music needs to be resampled before it can be
+ played; for example, CDs use a sample rate of 44,100 Hz
+ while many cheap audio chips can only handle 48,000 Hz.
+ Resampling reduces the quality and consumes a lot of CPU.
+ There are different options, some of them optimized for high
+ quality and others for low CPU usage, but you can't have
+ both at the same time. Often, the resampler is the
+ component that is responsible for most of MPD's CPU usage.
+ Since MPD comes with high quality defaults, it may appear
+ that MPD consumes more CPU than other software.
+ </para>
+
+ <para>
+ The following resamplers are available (if enabled at
+ compile time):
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <ulink
+ url="http://www.mega-nerd.com/SRC/">libsamplerate</ulink>
+ a.k.a. Secret Rabbit Code (SRC).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <ulink
+ url="http://sourceforge.net/projects/soxr/">libsoxr</ulink>,
+ the SoX Resampler library
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ internal: low CPU usage, but very poor quality. This is
+ the fallback if MPD was compiled without an external
+ resampler.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The setting <varname>samplerate_converter</varname> controls
+ how MPD shall resample music. Possible values:
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>
+ Value
+ </entry>
+ <entry>
+ Description
+ </entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ "<parameter>internal</parameter>"
+ </entry>
+ <entry>
+ The internal resampler. Low CPU usage, but very
+ poor quality.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>soxr</parameter>"
+ </entry>
+ <entry>
+ Use libsoxr.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Best Sinc Interpolator</parameter>" or
+ "<parameter>0</parameter>"
+ </entry>
+ <entry>
+ libsamplerate: Band limited sinc interpolation, best
+ quality, 97dB SNR, 96% BW.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Medium Sinc Interpolator</parameter>" or
+ "<parameter>1</parameter>"
+ </entry>
+ <entry>
+ libsamplerate: Band limited sinc interpolation,
+ medium quality, 97dB SNR, 90% BW.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Fastest Sinc Interpolator</parameter>" or
+ "<parameter>2</parameter>"
+ </entry>
+ <entry>
+ libsamplerate: Band limited sinc interpolation,
+ fastest, 97dB SNR, 80% BW.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>ZOH Sinc Interpolator</parameter>" or
+ "<parameter>3</parameter>"
+ </entry>
+ <entry>
+ libsamplerate: Zero order hold interpolator, very
+ fast, very poor quality with audible distortions.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Linear Interpolator</parameter>" or
+ "<parameter>4</parameter>"
+ </entry>
+ <entry>
+ libsamplerate: Linear interpolator, very fast, poor
+ quality.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+ </section>
</chapter>
<chapter>
@@ -749,6 +906,14 @@ systemctl start mpd.socket</programlisting>
</tgroup>
</informaltable>
</section>
+
+ <section>
+ <title><varname>upnp</varname></title>
+
+ <para>
+ Provides access to UPnP media servers.
+ </para>
+ </section>
</section>
<section>
@@ -902,6 +1067,38 @@ systemctl start mpd.socket</programlisting>
</tgroup>
</informaltable>
</section>
+
+ <section>
+ <title><varname>smbclient</varname></title>
+
+ <para>
+ Allows MPD to access files on SMB/CIFS servers (e.g. Samba
+ or Microsoft Windows). All URIs with the
+ <filename>smb://</filename> scheme are used. Example:
+ </para>
+
+ <para>
+ <filename>mpc add smb://servername/sharename/filename.ogg</filename>
+ </para>
+ </section>
+
+ <section>
+ <title><varname>alsa</varname></title>
+
+ <para>
+ Allows MPD on Linux to play audio directly from a soundcard using
+ the scheme <filename>alsa://</filename>. Audio is formatted as
+ 44.1 kHz 16-bit stereo (CD format). Examples:
+ </para>
+
+ <para>
+ <filename>mpc add alsa://</filename> plays audio from device hw:0,0
+ </para>
+ <para>
+ <filename>mpc add alsa://hw:1,0</filename> plays audio from device
+ hw:1,0
+ </para>
+ </section>
</section>
<section>
@@ -1173,6 +1370,35 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
+ <title><varname>shine</varname></title>
+
+ <para>
+ Encodes into MP3 using the shine library.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>bitrate</varname>
+ </entry>
+ <entry>
+ Sets the bit rate in kilobit per second.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
<title><varname>twolame</varname></title>
<para>
@@ -2169,6 +2395,47 @@ systemctl start mpd.socket</programlisting>
</para>
</section>
+ <section>
+ <title><varname>soundcloud</varname></title>
+
+ <para>
+ Adds <ulink url="https://www.soundcloud.com/">Soundcloud</ulink>
+ playlists. SoundCloud playlists use the <filename>soundcloud://</filename> URI,
+ and with a number of arguments, you may load different playlists with
+ </para>
+
+ <programlisting>
+mpc load soundcloud://track/TRACK_ID
+mpc load soundcloud://playlist/PLAYLIST_ID
+mpc load soundcloud://user/USERNAME
+mpc load soundcloud://search/SEARCH_QUERY
+mpc load soundcloud://url/https://soundcloud.com/ARTIST/TRACK-NAME
+ </programlisting>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>apikey</varname>
+ <parameter>client_id</parameter>
+ </entry>
+ <entry>
+ User apikey/client_id can override the MPD token provided by SoundCloud.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+
+ </section>
+
</section>
</chapter>
</book>
diff --git a/m4/mpd_func.m4 b/m4/mpd_func.m4
index d12d27062..5f2bf8f3d 100644
--- a/m4/mpd_func.m4
+++ b/m4/mpd_func.m4
@@ -10,3 +10,16 @@ AC_DEFUN([MPD_OPTIONAL_FUNC], [
[AC_CHECK_FUNC([$2],
[AC_DEFINE([$3], 1, [Define to use $1])],)])
])
+
+dnl MPD_OPTIONAL_FUNC_NODEF(name, func)
+dnl
+dnl Allow the user to enable or disable the use of a function.
+dnl Works similar to MPD_OPTIONAL_FUNC, however MPD_OPTIONAL_FUNC_NODEF
+dnl does not invoke AC_DEFINE when function is enabled. Shell variable
+dnl enable_$name is set to "yes" instead.
+AC_DEFUN([MPD_OPTIONAL_FUNC_NODEF], [
+ AC_ARG_ENABLE([$1],
+ AS_HELP_STRING([--enable-$1],
+ [use the function "$1" (default: auto)]),,
+ [AC_CHECK_FUNC([$2], [enable_$1=yes],)])
+])
diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx
index 4bdba62ab..154b4b297 100644
--- a/src/ArchiveFile.hxx
+++ b/src/ArchiveFile.hxx
@@ -23,6 +23,8 @@
class Mutex;
class Cond;
class Error;
+class ArchiveVisitor;
+struct InputStream;
class ArchiveFile {
public:
diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx
index 7a93c136a..65925a6e3 100644
--- a/src/ArchiveLookup.cxx
+++ b/src/ArchiveLookup.cxx
@@ -24,7 +24,6 @@
#include <string.h>
#include <sys/stat.h>
-#include <unistd.h>
#include <errno.h>
gcc_pure
diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx
index 6439c7242..c23826540 100644
--- a/src/ArchivePlugin.hxx
+++ b/src/ArchivePlugin.hxx
@@ -20,9 +20,7 @@
#ifndef MPD_ARCHIVE_PLUGIN_HXX
#define MPD_ARCHIVE_PLUGIN_HXX
-struct InputStream;
class ArchiveFile;
-class ArchiveVisitor;
class Error;
struct archive_plugin {
diff --git a/src/AvahiPoll.cxx b/src/AvahiPoll.cxx
index 0d5a43dad..de0f2b1e3 100644
--- a/src/AvahiPoll.cxx
+++ b/src/AvahiPoll.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "AvahiPoll.hxx"
-#include "event/Loop.hxx"
#include "event/SocketMonitor.hxx"
#include "event/TimeoutMonitor.hxx"
@@ -58,10 +57,6 @@ public:
Schedule(FromAvahiWatchEvent(_event));
}
- ~AvahiWatch() {
- Steal();
- }
-
static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) {
w->Schedule(FromAvahiWatchEvent(event));
}
diff --git a/src/Client.hxx b/src/Client.hxx
index f0bc6b0f7..202cff4da 100644
--- a/src/Client.hxx
+++ b/src/Client.hxx
@@ -82,6 +82,11 @@ public:
Client(EventLoop &loop, Partition &partition,
int fd, int uid, int num);
+ ~Client() {
+ if (FullyBufferedSocket::IsDefined())
+ FullyBufferedSocket::Close();
+ }
+
bool IsConnected() const {
return FullyBufferedSocket::IsDefined();
}
@@ -165,7 +170,7 @@ void client_manager_init(void);
void
client_new(EventLoop &loop, Partition &partition,
- int fd, const struct sockaddr *sa, size_t sa_length, int uid);
+ int fd, const sockaddr *sa, size_t sa_length, int uid);
/**
* Write a C string to the client.
diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx
index 382b76083..d1b00ebbc 100644
--- a/src/ClientFile.cxx
+++ b/src/ClientFile.cxx
@@ -24,11 +24,8 @@
#include "fs/Path.hxx"
#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
#include <unistd.h>
bool
diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx
index e79f3430b..8fb1f8e49 100644
--- a/src/ClientGlobal.cxx
+++ b/src/ClientGlobal.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "ClientInternal.hxx"
-#include "ClientList.hxx"
#include "ConfigGlobal.hxx"
#define CLIENT_TIMEOUT_DEFAULT (60)
diff --git a/src/ClientList.cxx b/src/ClientList.cxx
index 37e6f1289..c530ff54a 100644
--- a/src/ClientList.cxx
+++ b/src/ClientList.cxx
@@ -40,8 +40,14 @@ ClientList::Remove(Client &client)
void
ClientList::CloseAll()
{
- while (!list.empty())
- list.front()->Close();
+ while (!list.empty()) {
+ delete list.front();
+ list.pop_front();
+
+#ifndef NDEBUG
+ --size;
+#endif
+ }
assert(size == 0);
}
diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx
index e84887072..5618e9ece 100644
--- a/src/ClientNew.cxx
+++ b/src/ClientNew.cxx
@@ -28,16 +28,12 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
-#include <sys/types.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif
-#include <unistd.h>
#ifdef HAVE_LIBWRAP
#include <tcpd.h>
@@ -65,15 +61,14 @@ client_new(EventLoop &loop, Partition &partition,
int fd, const struct sockaddr *sa, size_t sa_length, int uid)
{
static unsigned int next_client_num;
- char *remote;
+ const auto remote = sockaddr_to_string(sa, sa_length);
assert(fd >= 0);
#ifdef HAVE_LIBWRAP
if (sa->sa_family != AF_UNIX) {
- char *hostaddr = sockaddr_to_string(sa, sa_length,
- IgnoreError());
- const char *progname = g_get_prgname();
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
struct request_info req;
request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
@@ -84,14 +79,11 @@ client_new(EventLoop &loop, Partition &partition,
/* tcp wrappers says no */
FormatWarning(client_domain,
"libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
+ progname, remote.c_str());
- g_free(hostaddr);
close_socket(fd);
return;
}
-
- g_free(hostaddr);
}
#endif /* HAVE_WRAP */
@@ -109,9 +101,8 @@ client_new(EventLoop &loop, Partition &partition,
client_list.Add(*client);
- remote = sockaddr_to_string(sa, sa_length, IgnoreError());
- FormatInfo(client_domain, "[%u] opened from %s", client->num, remote);
- g_free(remote);
+ FormatInfo(client_domain, "[%u] opened from %s",
+ client->num, remote.c_str());
}
void
diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx
index 22edefe60..205884815 100644
--- a/src/ClientRead.cxx
+++ b/src/ClientRead.cxx
@@ -23,7 +23,6 @@
#include "event/Loop.hxx"
#include "util/CharUtil.hxx"
-#include <assert.h>
#include <string.h>
BufferedSocket::InputResult
diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx
index 3a9f1b19c..f4d9ad316 100644
--- a/src/ClientSubscribe.cxx
+++ b/src/ClientSubscribe.cxx
@@ -22,11 +22,6 @@
#include "Idle.hxx"
#include <assert.h>
-#include <string.h>
-
- bool Unsubscribe(const char *channel);
- void UnsubscribeAll();
- bool PushMessage(const ClientMessage &msg);
Client::SubscribeResult
Client::Subscribe(const char *channel)
diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index 05f0a358c..58d07ddb7 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -34,9 +34,11 @@
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
+#include "fs/StandardDirectory.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
+#include "util/OptionDef.hxx"
+#include "util/OptionParser.hxx"
#ifdef ENABLE_ENCODER
#include "EncoderList.hxx"
@@ -48,19 +50,37 @@
#include "ArchivePlugin.hxx"
#endif
-#include <glib.h>
-
#include <stdio.h>
#include <stdlib.h>
#ifdef WIN32
-#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
+#define CONFIG_FILE_LOCATION "mpd\\mpd.conf"
+#define APP_CONFIG_FILE_LOCATION "conf\\mpd.conf"
#else
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
#define USER_CONFIG_FILE_LOCATION_XDG "mpd/mpd.conf"
#endif
+static const OptionDef opt_kill(
+ "kill", "kill the currently running mpd session");
+static const OptionDef opt_no_config(
+ "no-config", "don't read from config");
+static const OptionDef opt_no_daemon(
+ "no-daemon", "don't detach from console");
+static const OptionDef opt_stdout(
+ "stdout", nullptr); // hidden, compatibility with old versions
+static const OptionDef opt_stderr(
+ "stderr", "print messages to stderr");
+static const OptionDef opt_verbose(
+ "verbose", 'v', "verbose logging");
+static const OptionDef opt_version(
+ "version", 'V', "print version number");
+static const OptionDef opt_help(
+ "help", 'h', "show help options");
+static const OptionDef opt_help_alt(
+ nullptr, '?', nullptr); // hidden, standard alias for --help
+
static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn
@@ -132,122 +152,165 @@ static void version(void)
exit(EXIT_SUCCESS);
}
-static const char *summary =
- "Music Player Daemon - a daemon for playing music.";
+static void PrintOption(const OptionDef &opt)
+{
+ if (opt.HasShortOption())
+ printf(" -%c, --%-12s%s\n",
+ opt.GetShortOption(),
+ opt.GetLongOption(),
+ opt.GetDescription());
+ else
+ printf(" --%-16s%s\n",
+ opt.GetLongOption(),
+ opt.GetDescription());
+}
-gcc_pure
-static AllocatedPath
-PathBuildChecked(const AllocatedPath &a, PathTraits::const_pointer b)
+gcc_noreturn
+static void help(void)
{
- if (a.IsNull())
- return AllocatedPath::Null();
+ puts("Usage:\n"
+ " mpd [OPTION...] [path/to/mpd.conf]\n"
+ "\n"
+ "Music Player Daemon - a daemon for playing music.\n"
+ "\n"
+ "Options:");
- return AllocatedPath::Build(a, b);
+ PrintOption(opt_help);
+ PrintOption(opt_kill);
+ PrintOption(opt_no_config);
+ PrintOption(opt_no_daemon);
+ PrintOption(opt_stderr);
+ PrintOption(opt_verbose);
+ PrintOption(opt_version);
+
+ exit(EXIT_SUCCESS);
+}
+
+class ConfigLoader
+{
+ Error &error;
+ bool result;
+public:
+ ConfigLoader(Error &_error) : error(_error), result(false) { }
+
+ bool GetResult() const { return result; }
+
+ bool TryFile(const Path path);
+ bool TryFile(const AllocatedPath &base_path,
+ PathTraitsFS::const_pointer path);
+};
+
+bool ConfigLoader::TryFile(Path path)
+{
+ if (FileExists(path)) {
+ result = ReadConfigFile(path, error);
+ return true;
+ }
+ return false;
+}
+
+bool ConfigLoader::TryFile(const AllocatedPath &base_path,
+ PathTraitsFS::const_pointer path)
+{
+ if (base_path.IsNull())
+ return false;
+ auto full_path = AllocatedPath::Build(base_path, path);
+ return TryFile(full_path);
}
bool
parse_cmdline(int argc, char **argv, struct options *options,
Error &error)
{
- GOptionContext *context;
- bool ret;
- static gboolean option_version,
- option_no_daemon,
- option_no_config;
- const GOptionEntry entries[] = {
- { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
- "kill the currently running mpd session", nullptr },
- { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
- "don't read from config", nullptr },
- { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
- "don't detach from console", nullptr },
- { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
- nullptr, nullptr },
- { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
- "print messages to stderr", nullptr },
- { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
- "verbose logging", nullptr },
- { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version,
- "print version number", nullptr },
- { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }
- };
-
+ bool use_config_file = true;
options->kill = false;
options->daemon = true;
options->log_stderr = false;
options->verbose = false;
- context = g_option_context_new("[path/to/mpd.conf]");
- g_option_context_add_main_entries(context, entries, nullptr);
-
- g_option_context_set_summary(context, summary);
-
- GError *gerror = nullptr;
- ret = g_option_context_parse(context, &argc, &argv, &gerror);
- g_option_context_free(context);
-
- if (!ret)
- FatalError("option parsing failed", gerror);
+ // First pass: handle command line options
+ OptionParser parser(argc, argv);
+ while (parser.HasEntries()) {
+ if (!parser.ParseNext())
+ continue;
+ if (parser.CheckOption(opt_kill)) {
+ options->kill = true;
+ continue;
+ }
+ if (parser.CheckOption(opt_no_config)) {
+ use_config_file = false;
+ continue;
+ }
+ if (parser.CheckOption(opt_no_daemon)) {
+ options->daemon = false;
+ continue;
+ }
+ if (parser.CheckOption(opt_stderr, opt_stdout)) {
+ options->log_stderr = true;
+ continue;
+ }
+ if (parser.CheckOption(opt_verbose)) {
+ options->verbose = true;
+ continue;
+ }
+ if (parser.CheckOption(opt_version))
+ version();
+ if (parser.CheckOption(opt_help, opt_help_alt))
+ help();
- if (option_version)
- version();
+ error.Format(cmdline_domain, "invalid option: %s",
+ parser.GetOption());
+ return false;
+ }
/* initialize the logging library, so the configuration file
parser can use it already */
log_early_init(options->verbose);
- options->daemon = !option_no_daemon;
-
- if (option_no_config) {
+ if (!use_config_file) {
LogDebug(cmdline_domain,
"Ignoring config, using daemon defaults");
return true;
- } else if (argc <= 1) {
- /* default configuration file path */
+ }
-#ifdef WIN32
- AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()),
- CONFIG_FILE_LOCATION);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- const char *const*system_config_dirs =
- g_get_system_config_dirs();
-
- for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) {
- path = PathBuildChecked(AllocatedPath::FromUTF8(system_config_dirs[i]),
- CONFIG_FILE_LOCATION);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
+ // Second pass: find non-option parameters (i.e. config file)
+ const char *config_file = nullptr;
+ for (int i = 1; i < argc; ++i) {
+ if (OptionParser::IsOption(argv[i]))
+ continue;
+ if (config_file == nullptr) {
+ config_file = argv[i];
+ continue;
}
+ error.Set(cmdline_domain, "too many arguments");
+ return false;
+ }
+
+ if (config_file != nullptr) {
+ /* use specified configuration file */
+ return ReadConfigFile(Path::FromFS(config_file), error);
+ }
+
+ /* use default configuration file path */
+
+ ConfigLoader loader(error);
+
+ bool found =
+#ifdef WIN32
+ loader.TryFile(GetUserConfigDir(), CONFIG_FILE_LOCATION) ||
+ loader.TryFile(GetSystemConfigDir(), CONFIG_FILE_LOCATION) ||
+ loader.TryFile(GetAppBaseDir(), APP_CONFIG_FILE_LOCATION);
#else
- AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()),
- USER_CONFIG_FILE_LOCATION_XDG);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()),
- USER_CONFIG_FILE_LOCATION1);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()),
- USER_CONFIG_FILE_LOCATION2);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- path = AllocatedPath::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
+ loader.TryFile(GetUserConfigDir(),
+ USER_CONFIG_FILE_LOCATION_XDG) ||
+ loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION1) ||
+ loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION2) ||
+ loader.TryFile(Path::FromFS(SYSTEM_CONFIG_FILE_LOCATION));
#endif
-
+ if (!found) {
error.Set(cmdline_domain, "No configuration file found");
return false;
- } else if (argc == 2) {
- /* specified configuration file */
- return ReadConfigFile(Path::FromFS(argv[1]), error);
- } else {
- error.Set(cmdline_domain, "too many arguments");
- return false;
}
+
+ return loader.GetResult();
}
diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx
index 214150eae..3b24a0d77 100644
--- a/src/CommandLine.hxx
+++ b/src/CommandLine.hxx
@@ -20,15 +20,13 @@
#ifndef MPD_COMMAND_LINE_HXX
#define MPD_COMMAND_LINE_HXX
-#include <glib.h>
-
class Error;
struct options {
- gboolean kill;
- gboolean daemon;
- gboolean log_stderr;
- gboolean verbose;
+ bool kill;
+ bool daemon;
+ bool log_stderr;
+ bool verbose;
};
bool
diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx
index c6cabee6e..cd4e34a94 100644
--- a/src/ConfigData.cxx
+++ b/src/ConfigData.cxx
@@ -26,7 +26,6 @@
#include "system/FatalError.hxx"
#include <assert.h>
-#include <string.h>
#include <stdlib.h>
int
@@ -64,7 +63,7 @@ block_param::GetBoolValue() const
}
config_param::config_param(const char *_value, int _line)
- :next(nullptr), value(_value), line(_line) {}
+ :next(nullptr), value(_value), line(_line), used(false) {}
config_param::~config_param()
{
diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx
index 90859a89a..16b23bc0d 100644
--- a/src/ConfigFile.cxx
+++ b/src/ConfigFile.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "ConfigFile.hxx"
-#include "ConfigError.hxx"
#include "ConfigData.hxx"
#include "ConfigTemplates.hxx"
#include "util/Tokenizer.hxx"
@@ -32,9 +31,7 @@
#include "Log.hxx"
#include <assert.h>
-#include <string.h>
#include <stdio.h>
-#include <errno.h>
#define MAX_STRING_SIZE MPD_PATH_MAX+80
diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx
index 49b9c08fb..93929ccb4 100644
--- a/src/ConfigGlobal.cxx
+++ b/src/ConfigGlobal.cxx
@@ -30,8 +30,6 @@
#include "system/FatalError.hxx"
#include "Log.hxx"
-#include <assert.h>
-#include <string.h>
#include <stdlib.h>
static ConfigData config_data;
diff --git a/src/ConfigPath.cxx b/src/ConfigPath.cxx
index b88de3934..a2d7c2bb4 100644
--- a/src/ConfigPath.cxx
+++ b/src/ConfigPath.cxx
@@ -22,11 +22,10 @@
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/Domain.hxx"
+#include "fs/StandardDirectory.hxx"
#include "util/Error.hxx"
#include "ConfigGlobal.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
@@ -39,14 +38,14 @@
static AllocatedPath
GetHome(const char *user, Error &error)
{
- passwd *pw = getpwnam(user);
- if (pw == nullptr) {
+ AllocatedPath result = GetHomeDir(user);
+ if (result.IsNull()) {
error.Format(path_domain,
"no such user: %s", user);
return AllocatedPath::Null();
}
- return AllocatedPath::FromFS(pw->pw_dir);
+ return result;
}
/**
@@ -55,14 +54,14 @@ GetHome(const char *user, Error &error)
static AllocatedPath
GetHome(Error &error)
{
- const char *home = g_get_home_dir();
- if (home == nullptr) {
+ AllocatedPath result = GetHomeDir();
+ if (result.IsNull()) {
error.Set(path_domain,
"problems getting home for current user");
return AllocatedPath::Null();
}
- return AllocatedPath::FromUTF8(home, error);
+ return result;
}
/**
@@ -119,7 +118,7 @@ ParsePath(const char *path, Error &error)
return AllocatedPath::Null();
return AllocatedPath::Build(home, path2);
- } else if (!PathTraits::IsAbsoluteUTF8(path)) {
+ } else if (!PathTraitsUTF8::IsAbsolute(path)) {
error.Format(path_domain,
"not an absolute path: %s", path);
return AllocatedPath::Null();
diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx
index 4f5460460..1fbe3b737 100644
--- a/src/ConfigTemplates.hxx
+++ b/src/ConfigTemplates.hxx
@@ -20,8 +20,6 @@
#ifndef MPD_CONFIG_TEMPLATES_HXX
#define MPD_CONFIG_TEMPLATES_HXX
-#include "ConfigOption.hxx"
-
struct ConfigTemplate {
const char *const name;
const bool repeatable;
diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx
index 601d74dc2..c1dafd951 100644
--- a/src/CrossFade.cxx
+++ b/src/CrossFade.cxx
@@ -26,8 +26,6 @@
#include "Log.hxx"
#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
static constexpr Domain cross_fade_domain("cross_fade");
diff --git a/src/Daemon.cxx b/src/Daemon.cxx
index 557c47777..0a4eea841 100644
--- a/src/Daemon.cxx
+++ b/src/Daemon.cxx
@@ -25,14 +25,9 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <fcntl.h>
#ifndef WIN32
@@ -215,10 +210,10 @@ daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile)
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
- user_name = g_strdup(user);
+ user_name = strdup(user);
/* this is needed by libs such as arts */
- g_setenv("HOME", pwd->pw_dir, true);
+ setenv("HOME", pwd->pw_dir, true);
}
if (group) {
@@ -241,7 +236,7 @@ daemonize_finish(void)
pidfile = AllocatedPath::Null();
}
- g_free(user_name);
+ free(user_name);
}
#endif
diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx
index 013a3e329..d5aad0522 100644
--- a/src/DatabaseGlue.cxx
+++ b/src/DatabaseGlue.cxx
@@ -21,7 +21,6 @@
#include "DatabaseGlue.hxx"
#include "DatabaseSimple.hxx"
#include "DatabaseRegistry.hxx"
-#include "DatabaseSave.hxx"
#include "DatabaseError.hxx"
#include "Directory.hxx"
#include "util/Error.hxx"
@@ -30,20 +29,16 @@
#include "DatabasePlugin.hxx"
#include "db/SimpleDatabasePlugin.hxx"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
#include <assert.h>
#include <string.h>
-#include <errno.h>
-
static Database *db;
static bool db_is_open;
static bool is_simple;
bool
-DatabaseGlobalInit(const config_param &param, Error &error)
+DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error)
{
assert(db == nullptr);
assert(!db_is_open);
@@ -59,7 +54,7 @@ DatabaseGlobalInit(const config_param &param, Error &error)
return false;
}
- db = plugin->create(param, error);
+ db = plugin->create(loop, listener, param, error);
return db != nullptr;
}
@@ -143,8 +138,6 @@ DatabaseGlobalOpen(Error &error)
db_is_open = true;
- stats_update();
-
return true;
}
diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx
index bc05942e0..bf4699ff6 100644
--- a/src/DatabaseGlue.hxx
+++ b/src/DatabaseGlue.hxx
@@ -23,6 +23,8 @@
#include "Compiler.h"
struct config_param;
+class EventLoop;
+class DatabaseListener;
class Database;
class Error;
@@ -32,7 +34,8 @@ class Error;
* @param param the database configuration block
*/
bool
-DatabaseGlobalInit(const config_param &param, Error &error);
+DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error);
void
DatabaseGlobalDeinit(void);
@@ -44,7 +47,7 @@ DatabaseGlobalOpen(Error &error);
* Returns the global #Database instance. May return nullptr if this MPD
* configuration has no database (no music_directory was configured).
*/
-gcc_pure
+gcc_const
const Database *
GetDatabase();
diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx
index d8806bc69..9dc82bcda 100644
--- a/src/DatabaseHelpers.hxx
+++ b/src/DatabaseHelpers.hxx
@@ -22,7 +22,6 @@
#include "DatabaseVisitor.hxx"
#include "tag/TagType.h"
-#include "Compiler.h"
class Error;
class Database;
diff --git a/src/DatabaseListener.hxx b/src/DatabaseListener.hxx
new file mode 100644
index 000000000..4da458866
--- /dev/null
+++ b/src/DatabaseListener.hxx
@@ -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.
+ */
+
+#ifndef MPD_DATABASE_CLIENT_HXX
+#define MPD_DATABASE_CLIENT_HXX
+
+/**
+ * An object that listens to events from the #Database.
+ *
+ * @see #Instance
+ */
+class DatabaseListener {
+public:
+ /**
+ * The database has been modified. This must be called in the
+ * thread that has created the #Database instance and that
+ * runs the #EventLoop.
+ */
+ virtual void OnDatabaseModified() = 0;
+};
+
+#endif
diff --git a/src/DatabaseLock.cxx b/src/DatabaseLock.cxx
index d85f72d3b..660515d4e 100644
--- a/src/DatabaseLock.cxx
+++ b/src/DatabaseLock.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "DatabaseLock.hxx"
-#include "Compiler.h"
Mutex db_mutex;
diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx
index b0cb19589..a29c9f2bc 100644
--- a/src/DatabasePlaylist.cxx
+++ b/src/DatabasePlaylist.cxx
@@ -23,6 +23,7 @@
#include "PlaylistFile.hxx"
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
+#include "DetachedSong.hxx"
#include <functional>
@@ -30,7 +31,7 @@ static bool
AddSong(const char *playlist_path_utf8,
Song &song, Error &error)
{
- return spl_append_song(playlist_path_utf8, song, error);
+ return spl_append_song(playlist_path_utf8, DetachedSong(song), error);
}
bool
diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx
index ccf899389..e42c3bbd3 100644
--- a/src/DatabasePlugin.hxx
+++ b/src/DatabasePlugin.hxx
@@ -37,6 +37,8 @@ struct DatabaseSelection;
struct db_visitor;
struct Song;
class Error;
+class EventLoop;
+class DatabaseListener;
struct DatabaseStats {
/**
@@ -149,7 +151,8 @@ struct DatabasePlugin {
/**
* Allocates and configures a database.
*/
- Database *(*create)(const config_param &param,
+ Database *(*create)(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
Error &error);
};
diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx
index 3732e98f3..feb58a263 100644
--- a/src/DatabasePrint.cxx
+++ b/src/DatabasePrint.cxx
@@ -21,7 +21,6 @@
#include "DatabasePrint.hxx"
#include "DatabaseSelection.hxx"
#include "SongFilter.hxx"
-#include "PlaylistVector.hxx"
#include "SongPrint.hxx"
#include "TimePrint.hxx"
#include "Directory.hxx"
@@ -56,26 +55,24 @@ PrintDirectoryFull(Client &client, const Directory &directory)
static void
print_playlist_in_directory(Client &client,
- const Directory &directory,
+ const Directory *directory,
const char *name_utf8)
{
- if (directory.IsRoot())
+ if (directory == nullptr || directory->IsRoot())
client_printf(client, "playlist: %s\n", name_utf8);
else
client_printf(client, "playlist: %s/%s\n",
- directory.GetPath(), name_utf8);
+ directory->GetPath(), name_utf8);
}
static bool
PrintSongBrief(Client &client, const Song &song)
{
- assert(song.parent != nullptr);
-
song_print_uri(client, song);
if (song.tag != nullptr && song.tag->has_playlist)
/* this song file has an embedded CUE sheet */
- print_playlist_in_directory(client, *song.parent, song.uri);
+ print_playlist_in_directory(client, song.parent, song.uri);
return true;
}
@@ -83,13 +80,11 @@ PrintSongBrief(Client &client, const Song &song)
static bool
PrintSongFull(Client &client, const Song &song)
{
- assert(song.parent != nullptr);
-
song_print_info(client, song);
if (song.tag != nullptr && song.tag->has_playlist)
/* this song file has an embedded CUE sheet */
- print_playlist_in_directory(client, *song.parent, song.uri);
+ print_playlist_in_directory(client, song.parent, song.uri);
return true;
}
@@ -99,7 +94,7 @@ PrintPlaylistBrief(Client &client,
const PlaylistInfo &playlist,
const Directory &directory)
{
- print_playlist_in_directory(client, directory, playlist.name.c_str());
+ print_playlist_in_directory(client, &directory, playlist.name.c_str());
return true;
}
@@ -108,7 +103,7 @@ PrintPlaylistFull(Client &client,
const PlaylistInfo &playlist,
const Directory &directory)
{
- print_playlist_in_directory(client, directory, playlist.name.c_str());
+ print_playlist_in_directory(client, &directory, playlist.name.c_str());
if (playlist.mtime > 0)
time_print(client, "Last-Modified", playlist.mtime);
diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx
index 36a68d87b..0ed7ff1e7 100644
--- a/src/DatabasePrint.hxx
+++ b/src/DatabasePrint.hxx
@@ -24,7 +24,6 @@
class SongFilter;
struct DatabaseSelection;
-struct db_visitor;
class Client;
class Error;
diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx
index 98f80c5b6..3dac54630 100644
--- a/src/DatabaseQueue.cxx
+++ b/src/DatabaseQueue.cxx
@@ -19,19 +19,20 @@
#include "config.h"
#include "DatabaseQueue.hxx"
-#include "DatabaseSelection.hxx"
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
#include "Partition.hxx"
#include "util/Error.hxx"
+#include "DetachedSong.hxx"
#include <functional>
static bool
-AddToQueue(Partition &partition, Song &song, Error &error)
+AddToQueue(Partition &partition, const Song &song, Error &error)
{
PlaylistResult result =
- partition.playlist.AppendSong(partition.pc, &song, nullptr);
+ partition.playlist.AppendSong(partition.pc, DetachedSong(song),
+ nullptr);
if (result != PlaylistResult::SUCCESS) {
error.Set(playlist_domain, int(result), "Playlist error");
return false;
diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx
index 4ce4a62cb..6937d51c1 100644
--- a/src/DatabaseRegistry.cxx
+++ b/src/DatabaseRegistry.cxx
@@ -21,6 +21,7 @@
#include "DatabaseRegistry.hxx"
#include "db/SimpleDatabasePlugin.hxx"
#include "db/ProxyDatabasePlugin.hxx"
+#include "db/UpnpDatabasePlugin.hxx"
#include <string.h>
@@ -29,6 +30,9 @@ const DatabasePlugin *const database_plugins[] = {
#ifdef HAVE_LIBMPDCLIENT
&proxy_db_plugin,
#endif
+#ifdef HAVE_LIBUPNP
+ &upnp_db_plugin,
+#endif
nullptr
};
diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx
index abfd4a34f..f41edefda 100644
--- a/src/DatabaseSave.cxx
+++ b/src/DatabaseSave.cxx
@@ -23,17 +23,14 @@
#include "DatabaseError.hxx"
#include "Directory.hxx"
#include "DirectorySave.hxx"
-#include "Song.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "tag/Tag.hxx"
#include "tag/TagSettings.h"
#include "fs/Charset.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
#include <string.h>
#include <stdlib.h>
@@ -83,16 +80,16 @@ db_load_internal(TextFile &file, Directory &music_root, Error &error)
while ((line = file.ReadLine()) != nullptr &&
strcmp(line, DIRECTORY_INFO_END) != 0) {
- if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
+ if (StringStartsWith(line, DB_FORMAT_PREFIX)) {
format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
- } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
+ } else if (StringStartsWith(line, DIRECTORY_MPD_VERSION)) {
if (found_version) {
error.Set(db_domain, "Duplicate version line");
return false;
}
found_version = true;
- } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
+ } else if (StringStartsWith(line, DIRECTORY_FS_CHARSET)) {
const char *new_charset;
if (found_charset) {
@@ -113,7 +110,7 @@ db_load_internal(TextFile &file, Directory &music_root, Error &error)
new_charset, old_charset);
return false;
}
- } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
+ } else if (StringStartsWith(line, DB_TAG_PREFIX)) {
const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
TagType tag = tag_name_parse(name);
if (tag == TAG_NUM_OF_ITEM_TYPES) {
diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx
index 4fea02bef..148d20005 100644
--- a/src/DecoderAPI.cxx
+++ b/src/DecoderAPI.cxx
@@ -20,6 +20,7 @@
#include "config.h"
#include "DecoderAPI.hxx"
#include "DecoderError.hxx"
+#include "pcm/PcmConvert.hxx"
#include "AudioConfig.hxx"
#include "ReplayGainConfig.hxx"
#include "MusicChunk.hxx"
@@ -27,13 +28,12 @@
#include "MusicPipe.hxx"
#include "DecoderControl.hxx"
#include "DecoderInternal.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include <assert.h>
-#include <stdlib.h>
#include <string.h>
#include <math.h>
@@ -47,6 +47,7 @@ decoder_initialized(Decoder &decoder,
assert(dc.state == DecoderState::START);
assert(dc.pipe != nullptr);
+ assert(decoder.convert == nullptr);
assert(decoder.stream_tag == nullptr);
assert(decoder.decoder_tag == nullptr);
assert(!decoder.seeking);
@@ -59,19 +60,28 @@ decoder_initialized(Decoder &decoder,
dc.seekable = seekable;
dc.total_time = total_time;
- dc.Lock();
- dc.state = DecoderState::DECODE;
- dc.client_cond.signal();
- dc.Unlock();
-
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
audio_format_to_string(dc.in_audio_format, &af_string),
seekable ? "true" : "false");
- if (dc.in_audio_format != dc.out_audio_format)
+ if (dc.in_audio_format != dc.out_audio_format) {
FormatDebug(decoder_domain, "converting to %s",
audio_format_to_string(dc.out_audio_format,
&af_string));
+
+ decoder.convert = new PcmConvert();
+
+ Error error;
+ if (!decoder.convert->Open(dc.in_audio_format,
+ dc.out_audio_format,
+ error))
+ decoder.error = std::move(error);
+ }
+
+ dc.Lock();
+ dc.state = DecoderState::DECODE;
+ dc.client_cond.signal();
+ dc.Unlock();
}
/**
@@ -129,6 +139,10 @@ gcc_pure
static DecoderCommand
decoder_get_virtual_command(Decoder &decoder)
{
+ if (decoder.error.IsDefined())
+ /* an error has occurred: stop the decoder plugin */
+ return DecoderCommand::STOP;
+
const DecoderControl &dc = decoder.dc;
assert(dc.pipe != nullptr);
@@ -292,6 +306,40 @@ decoder_read(Decoder *decoder,
return nbytes;
}
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *_buffer, size_t size)
+{
+ uint8_t *buffer = (uint8_t *)_buffer;
+
+ while (size > 0) {
+ size_t nbytes = decoder_read(decoder, is, buffer, size);
+ if (nbytes == 0)
+ return false;
+
+ buffer += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+ while (size > 0) {
+ char buffer[1024];
+ size_t nbytes = decoder_read(decoder, is, buffer,
+ std::min(sizeof(buffer), size));
+ if (nbytes == 0)
+ return false;
+
+ size -= nbytes;
+ }
+
+ return true;
+}
+
void
decoder_timestamp(Decoder &decoder, double t)
{
@@ -312,12 +360,12 @@ do_send_tag(Decoder &decoder, const Tag &tag)
if (decoder.chunk != nullptr) {
/* there is a partial chunk - flush it, we want the
tag in a new chunk */
- decoder_flush_chunk(decoder);
+ decoder.FlushChunk();
}
assert(decoder.chunk == nullptr);
- chunk = decoder_get_chunk(decoder);
+ chunk = decoder.GetChunk();
if (chunk == nullptr) {
assert(decoder.dc.command != DecoderCommand::NONE);
return decoder.dc.command;
@@ -388,13 +436,13 @@ decoder_data(Decoder &decoder,
return cmd;
}
- if (dc.in_audio_format != dc.out_audio_format) {
+ if (decoder.convert != nullptr) {
+ assert(dc.in_audio_format != dc.out_audio_format);
+
Error error;
- data = decoder.conv_state.Convert(dc.in_audio_format,
- data, length,
- dc.out_audio_format,
- &length,
- error);
+ data = decoder.convert->Convert(data, length,
+ &length,
+ error);
if (data == nullptr) {
/* the PCM conversion has failed - stop
playback, since we have no better way to
@@ -402,13 +450,15 @@ decoder_data(Decoder &decoder,
LogError(error);
return DecoderCommand::STOP;
}
+ } else {
+ assert(dc.in_audio_format == dc.out_audio_format);
}
while (length > 0) {
struct music_chunk *chunk;
bool full;
- chunk = decoder_get_chunk(decoder);
+ chunk = decoder.GetChunk();
if (chunk == nullptr) {
assert(dc.command != DecoderCommand::NONE);
return dc.command;
@@ -417,11 +467,11 @@ decoder_data(Decoder &decoder,
const auto dest =
chunk->Write(dc.out_audio_format,
decoder.timestamp -
- dc.song->start_ms / 1000.0,
+ dc.song->GetStartMS() / 1000.0,
kbit_rate);
if (dest.IsNull()) {
/* the chunk is full, flush it */
- decoder_flush_chunk(decoder);
+ decoder.FlushChunk();
continue;
}
@@ -440,7 +490,7 @@ decoder_data(Decoder &decoder,
full = chunk->Expand(dc.out_audio_format, nbytes);
if (full) {
/* the chunk is full, flush it */
- decoder_flush_chunk(decoder);
+ decoder.FlushChunk();
}
data = (const uint8_t *)data + nbytes;
@@ -532,7 +582,7 @@ decoder_replay_gain(Decoder &decoder,
/* flush the current chunk because the new
replay gain values affect the following
samples */
- decoder_flush_chunk(decoder);
+ decoder.FlushChunk();
}
} else
decoder.replay_gain_serial = 0;
diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx
index 2ee42483c..bb693dbc4 100644
--- a/src/DecoderAPI.hxx
+++ b/src/DecoderAPI.hxx
@@ -27,6 +27,8 @@
#ifndef MPD_DECODER_API_HXX
#define MPD_DECODER_API_HXX
+// IWYU pragma: begin_exports
+
#include "check.h"
#include "DecoderCommand.hxx"
#include "DecoderPlugin.hxx"
@@ -36,6 +38,8 @@
#include "MixRampInfo.hxx"
#include "ConfigData.hxx"
+// IWYU pragma: end_exports
+
/**
* Notify the player thread that it has finished initialization and
* that it has read the song's meta data.
@@ -113,6 +117,25 @@ decoder_read(Decoder &decoder, InputStream &is,
}
/**
+ * Blocking read from the input stream. Attempts to fill the buffer
+ * completely; there is no partial result.
+ *
+ * @return true on success, false on error or command or not enough
+ * data
+ */
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *buffer, size_t size);
+
+/**
+ * Skip data on the #InputStream.
+ *
+ * @return true on success, false on error or command
+ */
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size);
+
+/**
* Sets the time stamp for the next data chunk [seconds]. The MPD
* core automatically counts it up, and a decoder plugin only needs to
* use this function if it thinks that adding to the time stamp based
diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx
index 6aad53cb2..18fd3aa5c 100644
--- a/src/DecoderBuffer.cxx
+++ b/src/DecoderBuffer.cxx
@@ -20,11 +20,12 @@
#include "config.h"
#include "DecoderBuffer.hxx"
#include "DecoderAPI.hxx"
-
-#include <glib.h>
+#include "util/ConstBuffer.hxx"
+#include "util/VarSize.hxx"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
struct DecoderBuffer {
Decoder *decoder;
@@ -42,24 +43,22 @@ struct DecoderBuffer {
/** the actual buffer (dynamic size) */
unsigned char data[sizeof(size_t)];
+
+ DecoderBuffer(Decoder *_decoder, InputStream &_is,
+ size_t _size)
+ :decoder(_decoder), is(&_is),
+ size(_size), length(0), consumed(0) {}
};
DecoderBuffer *
decoder_buffer_new(Decoder *decoder, InputStream &is,
size_t size)
{
- DecoderBuffer *buffer = (DecoderBuffer *)
- g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
-
assert(size > 0);
- buffer->decoder = decoder;
- buffer->is = &is;
- buffer->size = size;
- buffer->length = 0;
- buffer->consumed = 0;
-
- return buffer;
+ return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data),
+ size,
+ decoder, is, size);
}
void
@@ -67,7 +66,7 @@ decoder_buffer_free(DecoderBuffer *buffer)
{
assert(buffer != nullptr);
- g_free(buffer);
+ DeleteVarSize(buffer);
}
bool
@@ -82,6 +81,12 @@ decoder_buffer_is_full(const DecoderBuffer *buffer)
return buffer->consumed == 0 && buffer->length == buffer->size;
}
+void
+decoder_buffer_clear(DecoderBuffer *buffer)
+{
+ buffer->length = buffer->consumed = 0;
+}
+
static void
decoder_buffer_shift(DecoderBuffer *buffer)
{
@@ -118,15 +123,13 @@ decoder_buffer_fill(DecoderBuffer *buffer)
return true;
}
-const void *
-decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
+ConstBuffer<void>
+decoder_buffer_read(const DecoderBuffer *buffer)
{
- if (buffer->consumed >= buffer->length)
- /* buffer is empty */
- return nullptr;
-
- *length_r = buffer->length - buffer->consumed;
- return buffer->data + buffer->consumed;
+ return {
+ buffer->data + buffer->consumed,
+ buffer->length - buffer->consumed
+ };
}
void
@@ -143,19 +146,17 @@ decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes)
bool
decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes)
{
- size_t length;
- const void *data;
bool success;
/* this could probably be optimized by seeking */
while (true) {
- data = decoder_buffer_read(buffer, &length);
- if (data != nullptr) {
- if (length > nbytes)
- length = nbytes;
- decoder_buffer_consume(buffer, length);
- nbytes -= length;
+ auto data = decoder_buffer_read(buffer);
+ if (!data.IsEmpty()) {
+ if (data.size > nbytes)
+ data.size = nbytes;
+ decoder_buffer_consume(buffer, data.size);
+ nbytes -= data.size;
if (nbytes == 0)
return true;
}
diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx
index 92cc31aa4..f298d4429 100644
--- a/src/DecoderBuffer.hxx
+++ b/src/DecoderBuffer.hxx
@@ -20,6 +20,8 @@
#ifndef MPD_DECODER_BUFFER_HXX
#define MPD_DECODER_BUFFER_HXX
+#include "Compiler.h"
+
#include <stddef.h>
/**
@@ -32,6 +34,8 @@ struct DecoderBuffer;
struct Decoder;
struct InputStream;
+template<typename T> struct ConstBuffer;
+
/**
* Creates a new buffer.
*
@@ -50,12 +54,17 @@ decoder_buffer_new(Decoder *decoder, InputStream &is,
void
decoder_buffer_free(DecoderBuffer *buffer);
+gcc_pure
bool
decoder_buffer_is_empty(const DecoderBuffer *buffer);
+gcc_pure
bool
decoder_buffer_is_full(const DecoderBuffer *buffer);
+void
+decoder_buffer_clear(DecoderBuffer *buffer);
+
/**
* Read data from the input_stream and append it to the buffer.
*
@@ -73,13 +82,10 @@ decoder_buffer_fill(DecoderBuffer *buffer);
* decoder_buffer_consume() call.
*
* @param buffer the decoder_buffer object
- * @param length_r pointer to a size_t where you will receive the
- * number of bytes available
- * @return a pointer to the read buffer, or nullptr if there is no data
- * available
*/
-const void *
-decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r);
+gcc_pure
+ConstBuffer<void>
+decoder_buffer_read(const DecoderBuffer *buffer);
/**
* Consume (delete, invalidate) a part of the buffer. The "nbytes"
diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx
index ab460ced0..4e5e894e3 100644
--- a/src/DecoderControl.cxx
+++ b/src/DecoderControl.cxx
@@ -20,9 +20,7 @@
#include "config.h"
#include "DecoderControl.hxx"
#include "MusicPipe.hxx"
-#include "Song.hxx"
-
-#include <glib.h>
+#include "DetachedSong.hxx"
#include <assert.h>
@@ -38,8 +36,7 @@ DecoderControl::~DecoderControl()
{
ClearError();
- if (song != nullptr)
- song->Free();
+ delete song;
}
void
@@ -55,7 +52,7 @@ DecoderControl::WaitForDecoder()
}
bool
-DecoderControl::IsCurrentSong(const Song &_song) const
+DecoderControl::IsCurrentSong(const DetachedSong &_song) const
{
switch (state) {
case DecoderState::STOP:
@@ -64,7 +61,7 @@ DecoderControl::IsCurrentSong(const Song &_song) const
case DecoderState::START:
case DecoderState::DECODE:
- return SongEquals(*song, _song);
+ return song->IsSame(_song);
}
assert(false);
@@ -72,16 +69,14 @@ DecoderControl::IsCurrentSong(const Song &_song) const
}
void
-DecoderControl::Start(Song *_song,
+DecoderControl::Start(DetachedSong *_song,
unsigned _start_ms, unsigned _end_ms,
MusicBuffer &_buffer, MusicPipe &_pipe)
{
assert(_song != nullptr);
assert(_pipe.IsEmpty());
- if (song != nullptr)
- song->Free();
-
+ delete song;
song = _song;
start_ms = _start_ms;
end_ms = _end_ms;
diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx
index 863398dca..bacdad347 100644
--- a/src/DecoderControl.hxx
+++ b/src/DecoderControl.hxx
@@ -36,7 +36,7 @@
#undef ERROR
#endif
-struct Song;
+class DetachedSong;
class MusicBuffer;
class MusicPipe;
@@ -123,7 +123,7 @@ struct DecoderControl {
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
- Song *song;
+ DetachedSong *song;
/**
* The initial seek position (in milliseconds), e.g. to the
@@ -293,10 +293,10 @@ struct DecoderControl {
* Caller must lock the object.
*/
gcc_pure
- bool IsCurrentSong(const Song &_song) const;
+ bool IsCurrentSong(const DetachedSong &_song) const;
gcc_pure
- bool LockIsCurrentSong(const Song &_song) const {
+ bool LockIsCurrentSong(const DetachedSong &_song) const {
Lock();
const bool result = IsCurrentSong(_song);
Unlock();
@@ -360,7 +360,7 @@ public:
* @param pipe the pipe which receives the decoded chunks (owned by
* the caller)
*/
- void Start(Song *song, unsigned start_ms, unsigned end_ms,
+ void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms,
MusicBuffer &buffer, MusicPipe &pipe);
void Stop();
diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx
index d5f40ad48..38e54a36c 100644
--- a/src/DecoderInternal.cxx
+++ b/src/DecoderInternal.cxx
@@ -20,6 +20,7 @@
#include "config.h"
#include "DecoderInternal.hxx"
#include "DecoderControl.hxx"
+#include "pcm/PcmConvert.hxx"
#include "MusicPipe.hxx"
#include "MusicBuffer.hxx"
#include "MusicChunk.hxx"
@@ -32,6 +33,11 @@ Decoder::~Decoder()
/* caller must flush the chunk */
assert(chunk == nullptr);
+ if (convert != nullptr) {
+ convert->Close();
+ delete convert;
+ }
+
delete song_tag;
delete stream_tag;
delete decoder_tag;
@@ -51,24 +57,21 @@ need_chunks(DecoderControl &dc)
}
struct music_chunk *
-decoder_get_chunk(Decoder &decoder)
+Decoder::GetChunk()
{
- DecoderControl &dc = decoder.dc;
DecoderCommand cmd;
- if (decoder.chunk != nullptr)
- return decoder.chunk;
+ if (chunk != nullptr)
+ return chunk;
do {
- decoder.chunk = dc.buffer->Allocate();
- if (decoder.chunk != nullptr) {
- decoder.chunk->replay_gain_serial =
- decoder.replay_gain_serial;
- if (decoder.replay_gain_serial != 0)
- decoder.chunk->replay_gain_info =
- decoder.replay_gain_info;
-
- return decoder.chunk;
+ chunk = dc.buffer->Allocate();
+ if (chunk != nullptr) {
+ chunk->replay_gain_serial = replay_gain_serial;
+ if (replay_gain_serial != 0)
+ chunk->replay_gain_info = replay_gain_info;
+
+ return chunk;
}
dc.Lock();
@@ -80,18 +83,16 @@ decoder_get_chunk(Decoder &decoder)
}
void
-decoder_flush_chunk(Decoder &decoder)
+Decoder::FlushChunk()
{
- DecoderControl &dc = decoder.dc;
-
- assert(decoder.chunk != nullptr);
+ assert(chunk != nullptr);
- if (decoder.chunk->IsEmpty())
- dc.buffer->Return(decoder.chunk);
+ if (chunk->IsEmpty())
+ dc.buffer->Return(chunk);
else
- dc.pipe->Push(decoder.chunk);
+ dc.pipe->Push(chunk);
- decoder.chunk = nullptr;
+ chunk = nullptr;
dc.Lock();
if (dc.client_is_waiting)
diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx
index 46069a561..fbd613a36 100644
--- a/src/DecoderInternal.hxx
+++ b/src/DecoderInternal.hxx
@@ -20,18 +20,21 @@
#ifndef MPD_DECODER_INTERNAL_HXX
#define MPD_DECODER_INTERNAL_HXX
-#include "DecoderCommand.hxx"
-#include "pcm/PcmConvert.hxx"
#include "ReplayGainInfo.hxx"
+#include "util/Error.hxx"
+class PcmConvert;
struct DecoderControl;
-struct InputStream;
struct Tag;
struct Decoder {
DecoderControl &dc;
- PcmConvert conv_state;
+ /**
+ * For converting input data to the configured audio format.
+ * nullptr means no conversion necessary.
+ */
+ PcmConvert *convert;
/**
* The time stamp of the next data chunk, in seconds.
@@ -83,8 +86,15 @@ struct Decoder {
*/
unsigned replay_gain_serial;
+ /**
+ * An error has occurred (in DecoderAPI.cxx), and the plugin
+ * will be asked to stop.
+ */
+ Error error;
+
Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag)
:dc(_dc),
+ convert(nullptr),
timestamp(0),
initial_seek_pending(_initial_seek_pending),
initial_seek_running(false),
@@ -95,23 +105,21 @@ struct Decoder {
}
~Decoder();
-};
-/**
- * Returns the current chunk the decoder writes to, or allocates a new
- * chunk if there is none.
- *
- * @return the chunk, or NULL if we have received a decoder command
- */
-struct music_chunk *
-decoder_get_chunk(Decoder &decoder);
+ /**
+ * Returns the current chunk the decoder writes to, or allocates a new
+ * chunk if there is none.
+ *
+ * @return the chunk, or NULL if we have received a decoder command
+ */
+ music_chunk *GetChunk();
-/**
- * Flushes the current chunk.
- *
- * Caller must not lock the #DecoderControl object.
- */
-void
-decoder_flush_chunk(Decoder &decoder);
+ /**
+ * Flushes the current chunk.
+ *
+ * Caller must not lock the #DecoderControl object.
+ */
+ void FlushChunk();
+};
#endif
diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx
index 834178260..b077da035 100644
--- a/src/DecoderList.cxx
+++ b/src/DecoderList.cxx
@@ -118,65 +118,6 @@ static constexpr unsigned num_decoder_plugins =
/** which plugins have been initialized successfully? */
bool decoder_plugins_enabled[num_decoder_plugins];
-static unsigned
-decoder_plugin_index(const struct DecoderPlugin *plugin)
-{
- unsigned i = 0;
-
- while (decoder_plugins[i] != plugin)
- ++i;
-
- return i;
-}
-
-static unsigned
-decoder_plugin_next_index(const struct DecoderPlugin *plugin)
-{
- return plugin == 0
- ? 0 /* start with first plugin */
- : decoder_plugin_index(plugin) + 1;
-}
-
-const struct DecoderPlugin *
-decoder_plugin_from_suffix(const char *suffix,
- const struct DecoderPlugin *plugin)
-{
- if (suffix == nullptr)
- return nullptr;
-
- for (unsigned i = decoder_plugin_next_index(plugin);
- decoder_plugins[i] != nullptr; ++i) {
- plugin = decoder_plugins[i];
- if (decoder_plugins_enabled[i] &&
- plugin->SupportsSuffix(suffix))
- return plugin;
- }
-
- return nullptr;
-}
-
-const struct DecoderPlugin *
-decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
-{
- static unsigned i = num_decoder_plugins;
-
- if (mimeType == nullptr)
- return nullptr;
-
- if (!next)
- i = 0;
- for (; decoder_plugins[i] != nullptr; ++i) {
- const struct DecoderPlugin *plugin = decoder_plugins[i];
- if (decoder_plugins_enabled[i] &&
- plugin->SupportsMimeType(mimeType)) {
- ++i;
- return plugin;
- }
- }
-
- return nullptr;
-}
-
const struct DecoderPlugin *
decoder_plugin_from_name(const char *name)
{
@@ -235,3 +176,11 @@ void decoder_plugin_deinit_all(void)
plugin.Finish();
});
}
+
+bool
+decoder_plugins_supports_suffix(const char *suffix)
+{
+ return decoder_plugins_try([suffix](const DecoderPlugin &plugin){
+ return plugin.SupportsSuffix(suffix);
+ });
+}
diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx
index fd4b22c63..50848ad4e 100644
--- a/src/DecoderList.hxx
+++ b/src/DecoderList.hxx
@@ -20,6 +20,8 @@
#ifndef MPD_DECODER_LIST_HXX
#define MPD_DECODER_LIST_HXX
+#include "Compiler.h"
+
struct DecoderPlugin;
extern const struct DecoderPlugin *const decoder_plugins[];
@@ -27,20 +29,7 @@ extern bool decoder_plugins_enabled[];
/* interface for using plugins */
-/**
- * Find the next enabled decoder plugin which supports the specified suffix.
- *
- * @param suffix the file name suffix
- * @param plugin the previous plugin, or nullptr to find the first plugin
- * @return a plugin, or nullptr if none matches
- */
-const struct DecoderPlugin *
-decoder_plugin_from_suffix(const char *suffix,
- const struct DecoderPlugin *plugin);
-
-const struct DecoderPlugin *
-decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
-
+gcc_pure
const struct DecoderPlugin *
decoder_plugin_from_name(const char *name);
@@ -89,4 +78,12 @@ decoder_plugins_for_each_enabled(F f)
f(*decoder_plugins[i]);
}
+/**
+ * Is there at least once #DecoderPlugin that supports the specified
+ * file name suffix?
+ */
+gcc_pure gcc_nonnull_all
+bool
+decoder_plugins_supports_suffix(const char *suffix);
+
#endif
diff --git a/src/DecoderPlugin.hxx b/src/DecoderPlugin.hxx
index 6b0340123..41e009bec 100644
--- a/src/DecoderPlugin.hxx
+++ b/src/DecoderPlugin.hxx
@@ -24,7 +24,6 @@
struct config_param;
struct InputStream;
-struct Tag;
struct tag_handler;
/**
diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx
index 72fc3cfb4..7099b3bd4 100644
--- a/src/DecoderThread.cxx
+++ b/src/DecoderThread.cxx
@@ -23,13 +23,12 @@
#include "DecoderInternal.hxx"
#include "DecoderError.hxx"
#include "DecoderPlugin.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "system/FatalError.hxx"
#include "Mapper.hxx"
#include "fs/Traits.hxx"
#include "fs/AllocatedPath.hxx"
#include "DecoderAPI.hxx"
-#include "tag/Tag.hxx"
#include "InputStream.hxx"
#include "DecoderList.hxx"
#include "util/UriUtil.hxx"
@@ -146,7 +145,7 @@ decoder_file_decode(const DecoderPlugin &plugin,
assert(decoder.stream_tag == nullptr);
assert(decoder.decoder_tag == nullptr);
assert(path != nullptr);
- assert(PathTraits::IsAbsoluteFS(path));
+ assert(PathTraitsFS::IsAbsolute(path));
assert(decoder.dc.state == DecoderState::START);
FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
@@ -281,54 +280,66 @@ decoder_load_replay_gain(Decoder &decoder, const char *path_fs)
decoder_replay_gain(decoder, &info);
}
-/**
- * Try decoding a file.
- */
static bool
-decoder_run_file(Decoder &decoder, const char *path_fs)
+TryDecoderFile(Decoder &decoder, const char *path_fs, const char *suffix,
+ const DecoderPlugin &plugin)
{
+ if (!plugin.SupportsSuffix(suffix))
+ return false;
+
DecoderControl &dc = decoder.dc;
- const char *suffix = uri_get_suffix(path_fs);
- const struct DecoderPlugin *plugin = nullptr;
- if (suffix == nullptr)
- return false;
+ if (plugin.file_decode != nullptr) {
+ dc.Lock();
- dc.Unlock();
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
- decoder_load_replay_gain(decoder, path_fs);
+ dc.Unlock();
+ } else if (plugin.stream_decode != nullptr) {
+ InputStream *input_stream =
+ decoder_input_stream_open(dc, path_fs);
+ if (input_stream == nullptr)
+ return false;
- while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != nullptr) {
- if (plugin->file_decode != nullptr) {
- dc.Lock();
+ dc.Lock();
- if (decoder_file_decode(*plugin, decoder, path_fs))
- return true;
+ bool success = decoder_stream_decode(plugin, decoder,
+ *input_stream);
- dc.Unlock();
- } else if (plugin->stream_decode != nullptr) {
- InputStream *input_stream;
- bool success;
+ dc.Unlock();
- input_stream = decoder_input_stream_open(dc, path_fs);
- if (input_stream == nullptr)
- continue;
+ input_stream->Close();
+ if (success) {
dc.Lock();
+ return true;
+ }
+ }
- success = decoder_stream_decode(*plugin, decoder,
- *input_stream);
+ return false;
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(Decoder &decoder, const char *path_fs)
+{
+ const char *suffix = uri_get_suffix(path_fs);
+ if (suffix == nullptr)
+ return false;
- dc.Unlock();
+ DecoderControl &dc = decoder.dc;
+ dc.Unlock();
- input_stream->Close();
+ decoder_load_replay_gain(decoder, path_fs);
- if (success) {
- dc.Lock();
- return true;
- }
- }
- }
+ if (decoder_plugins_try([&decoder, path_fs, suffix](const DecoderPlugin &plugin){
+ return TryDecoderFile(decoder, path_fs, suffix,
+ plugin);
+ }))
+ return true;
dc.Lock();
return false;
@@ -336,18 +347,17 @@ decoder_run_file(Decoder &decoder, const char *path_fs)
static void
decoder_run_song(DecoderControl &dc,
- const Song *song, const char *uri)
+ const DetachedSong &song, const char *uri)
{
Decoder decoder(dc, dc.start_ms > 0,
- song->tag != nullptr && song->IsFile()
- ? new Tag(*song->tag) : nullptr);
+ new Tag(song.GetTag()));
int ret;
dc.state = DecoderState::START;
decoder_command_finished_locked(dc);
- ret = song->IsFile()
+ ret = song.IsFile()
? decoder_run_file(decoder, uri)
: decoder_run_stream(decoder, uri);
@@ -356,16 +366,21 @@ decoder_run_song(DecoderControl &dc,
/* flush the last chunk */
if (decoder.chunk != nullptr)
- decoder_flush_chunk(decoder);
+ decoder.FlushChunk();
dc.Lock();
- if (ret)
+ if (decoder.error.IsDefined()) {
+ /* copy the Error from sruct Decoder to
+ DecoderControl */
+ dc.state = DecoderState::ERROR;
+ dc.error = std::move(decoder.error);
+ } else if (ret)
dc.state = DecoderState::STOP;
else {
dc.state = DecoderState::ERROR;
- const char *error_uri = song->uri;
+ const char *error_uri = song.GetURI();
const std::string allocated = uri_remove_auth(error_uri);
if (!allocated.empty())
error_uri = allocated.c_str();
@@ -382,12 +397,12 @@ decoder_run(DecoderControl &dc)
{
dc.ClearError();
- const Song *song = dc.song;
- assert(song != nullptr);
+ assert(dc.song != nullptr);
+ const DetachedSong &song = *dc.song;
- const std::string uri = song->IsFile()
- ? std::string(map_song_fs(*song).c_str())
- : song->GetURI();
+ const std::string uri = song.IsFile()
+ ? map_song_fs(song).c_str()
+ : song.GetURI();
if (uri.empty()) {
dc.state = DecoderState::ERROR;
diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx
index e91587a7f..8a499d75d 100644
--- a/src/DespotifyUtils.cxx
+++ b/src/DespotifyUtils.cxx
@@ -19,6 +19,7 @@
#include "DespotifyUtils.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "ConfigGlobal.hxx"
#include "ConfigOption.hxx"
#include "util/Domain.hxx"
@@ -82,33 +83,31 @@ void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, in
}
}
-
-Tag *
-mpd_despotify_tag_from_track(struct ds_track *track)
+Tag
+mpd_despotify_tag_from_track(const ds_track &track)
{
char tracknum[20];
char comment[80];
char date[20];
- Tag *tag = new Tag();
-
- if (!track->has_meta_data)
- return tag;
+ if (!track.has_meta_data)
+ return Tag();
- snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber);
- snprintf(date, sizeof(date), "%d", track->year);
+ 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 ");
- 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->time = track->length / 1000;
-
- return tag;
+ track.file_bitrate / 1000,
+ track.geo_restricted ? "" : "not ");
+ 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.SetTime(track.length / 1000);
+
+ return tag.Commit();
}
struct despotify_session *mpd_despotify_get_session(void)
diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx
index c0d4af47c..c8d90afa4 100644
--- a/src/DespotifyUtils.hxx
+++ b/src/DespotifyUtils.hxx
@@ -42,10 +42,10 @@ struct despotify_session *mpd_despotify_get_session(void);
*
* @param track the track to convert
*
- * @return a pointer to the filled in tags structure
+ * @return filled in #Tag structure
*/
-Tag *
-mpd_despotify_tag_from_track(struct ds_track *track);
+Tag
+mpd_despotify_tag_from_track(const ds_track &track);
/**
* Register a despotify callback.
diff --git a/src/DetachedSong.cxx b/src/DetachedSong.cxx
new file mode 100644
index 000000000..4b1d51a41
--- /dev/null
+++ b/src/DetachedSong.cxx
@@ -0,0 +1,51 @@
+/*
+ * 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 "DetachedSong.hxx"
+#include "Song.hxx"
+#include "util/UriUtil.hxx"
+#include "fs/Traits.hxx"
+
+DetachedSong::DetachedSong(const Song &other)
+ :uri(other.GetURI().c_str()),
+ tag(other.tag != nullptr ? *other.tag : Tag()),
+ mtime(other.mtime),
+ start_ms(other.start_ms), end_ms(other.end_ms) {}
+
+bool
+DetachedSong::IsRemote() const
+{
+ return uri_has_scheme(uri.c_str());
+}
+
+bool
+DetachedSong::IsAbsoluteFile() const
+{
+ return PathTraitsUTF8::IsAbsolute(uri.c_str());
+}
+
+double
+DetachedSong::GetDuration() const
+{
+ if (end_ms > 0)
+ return (end_ms - start_ms) / 1000.0;
+
+ return tag.time - start_ms / 1000.0;
+}
diff --git a/src/DetachedSong.hxx b/src/DetachedSong.hxx
new file mode 100644
index 000000000..bee6a73a4
--- /dev/null
+++ b/src/DetachedSong.hxx
@@ -0,0 +1,184 @@
+/*
+ * 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_DETACHED_SONG_HXX
+#define MPD_DETACHED_SONG_HXX
+
+#include "check.h"
+#include "tag/Tag.hxx"
+#include "Compiler.h"
+
+#include <string>
+#include <utility>
+
+#include <time.h>
+
+struct Song;
+
+class DetachedSong {
+ /**
+ * An UTF-8-encoded URI referring to the song file. This can
+ * be one of:
+ *
+ * - an absolute URL with a scheme
+ * (e.g. "http://example.com/foo.mp3")
+ *
+ * - an absolute file name
+ *
+ * - a file name relative to the music directory
+ */
+ std::string uri;
+
+ Tag tag;
+
+ time_t mtime;
+
+ /**
+ * Start of this sub-song within the file in milliseconds.
+ */
+ unsigned start_ms;
+
+ /**
+ * End of this sub-song within the file in milliseconds.
+ * Unused if zero.
+ */
+ unsigned end_ms;
+
+public:
+ explicit DetachedSong(const DetachedSong &other)
+ :uri(other.uri),
+ tag(other.tag),
+ mtime(other.mtime),
+ start_ms(other.start_ms), end_ms(other.end_ms) {}
+
+ explicit DetachedSong(const Song &other);
+
+ explicit DetachedSong(const char *_uri)
+ :uri(_uri),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ explicit DetachedSong(const std::string &_uri)
+ :uri(std::move(_uri)),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ explicit DetachedSong(std::string &&_uri)
+ :uri(std::move(_uri)),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ template<typename U>
+ DetachedSong(U &&_uri, Tag &&_tag)
+ :uri(std::forward<U>(_uri)),
+ tag(std::move(_tag)),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ DetachedSong(DetachedSong &&other)
+ :uri(std::move(other.uri)),
+ tag(std::move(other.tag)),
+ mtime(other.mtime),
+ start_ms(other.start_ms), end_ms(other.end_ms) {}
+
+ gcc_pure
+ const char *GetURI() const {
+ return uri.c_str();
+ }
+
+ template<typename T>
+ void SetURI(T &&_uri) {
+ uri = std::forward<T>(_uri);
+ }
+
+ /**
+ * Returns true if both objects refer to the same physical
+ * song.
+ */
+ gcc_pure
+ bool IsSame(const DetachedSong &other) const {
+ return uri == other.uri;
+ }
+
+ gcc_pure gcc_nonnull_all
+ bool IsURI(const char *other_uri) const {
+ return uri == other_uri;
+ }
+
+ gcc_pure
+ bool IsRemote() const;
+
+ gcc_pure
+ bool IsFile() const {
+ return !IsRemote();
+ }
+
+ gcc_pure
+ bool IsAbsoluteFile() const;
+
+ gcc_pure
+ bool IsInDatabase() const {
+ return IsFile() && !IsAbsoluteFile();
+ }
+
+ const Tag &GetTag() const {
+ return tag;
+ }
+
+ Tag &WritableTag() {
+ return tag;
+ }
+
+ void SetTag(const Tag &_tag) {
+ tag = Tag(_tag);
+ }
+
+ void SetTag(Tag &&_tag) {
+ tag = std::move(_tag);
+ }
+
+ void MoveTagFrom(DetachedSong &&other) {
+ tag = std::move(other.tag);
+ }
+
+ time_t GetLastModified() const {
+ return mtime;
+ }
+
+ void SetLastModified(time_t _value) {
+ mtime = _value;
+ }
+
+ unsigned GetStartMS() const {
+ return start_ms;
+ }
+
+ void SetStartMS(unsigned _value) {
+ start_ms = _value;
+ }
+
+ unsigned GetEndMS() const {
+ return end_ms;
+ }
+
+ void SetEndMS(unsigned _value) {
+ end_ms = _value;
+ }
+
+ gcc_pure
+ double GetDuration() const;
+};
+
+#endif
diff --git a/src/Directory.cxx b/src/Directory.cxx
index b2942588e..28c2c47e1 100644
--- a/src/Directory.cxx
+++ b/src/Directory.cxx
@@ -25,6 +25,7 @@
#include "SongSort.hxx"
#include "Song.hxx"
#include "fs/Traits.hxx"
+#include "util/Alloc.hxx"
#include "util/Error.hxx"
extern "C" {
@@ -37,35 +38,11 @@ extern "C" {
#include <string.h>
#include <stdlib.h>
-inline Directory *
-Directory::Allocate(const char *path)
-{
- assert(path != nullptr);
-
- const size_t path_size = strlen(path) + 1;
- Directory *directory =
- (Directory *)g_malloc0(sizeof(*directory)
- - sizeof(directory->path)
- + path_size);
- new(directory) Directory(path);
-
- return directory;
-}
-
-Directory::Directory()
-{
- INIT_LIST_HEAD(&children);
- INIT_LIST_HEAD(&songs);
-
- path[0] = 0;
-}
-
-Directory::Directory(const char *_path)
+Directory::Directory(const char *_path_utf8, Directory *_parent)
+ :parent(_parent), path(_path_utf8)
{
INIT_LIST_HEAD(&children);
INIT_LIST_HEAD(&songs);
-
- strcpy(path, _path);
}
Directory::~Directory()
@@ -76,27 +53,7 @@ Directory::~Directory()
Directory *child, *n;
directory_for_each_child_safe(child, n, *this)
- child->Free();
-}
-
-Directory *
-Directory::NewGeneric(const char *path, Directory *parent)
-{
- assert(path != nullptr);
- assert((*path == 0) == (parent == nullptr));
-
- Directory *directory = Allocate(path);
-
- directory->parent = parent;
-
- return directory;
-}
-
-void
-Directory::Free()
-{
- this->Directory::~Directory();
- g_free(this);
+ delete child;
}
void
@@ -106,7 +63,7 @@ Directory::Delete()
assert(parent != nullptr);
list_del(&siblings);
- Free();
+ delete this;
}
const char *
@@ -114,7 +71,7 @@ Directory::GetName() const
{
assert(!IsRoot());
- return PathTraits::GetBaseUTF8(path);
+ return PathTraitsUTF8::GetBase(path.c_str());
}
Directory *
@@ -135,7 +92,7 @@ Directory::CreateChild(const char *name_utf8)
path_utf8 = allocated;
}
- Directory *child = NewGeneric(path_utf8, this);
+ Directory *child = new Directory(path_utf8, this);
g_free(allocated);
list_add_tail(&child->siblings, &children);
@@ -178,7 +135,7 @@ Directory::LookupDirectory(const char *uri)
if (isRootDirectory(uri))
return this;
- char *duplicated = g_strdup(uri), *name = duplicated;
+ char *duplicated = xstrdup(uri), *name = duplicated;
Directory *d = this;
while (1) {
@@ -198,7 +155,7 @@ Directory::LookupDirectory(const char *uri)
name = slash + 1;
}
- g_free(duplicated);
+ free(duplicated);
return d;
}
@@ -248,7 +205,7 @@ Directory::LookupSong(const char *uri)
assert(holding_db_lock());
assert(uri != nullptr);
- duplicated = g_strdup(uri);
+ duplicated = xstrdup(uri);
base = strrchr(duplicated, '/');
Directory *d = this;
@@ -256,7 +213,7 @@ Directory::LookupSong(const char *uri)
*base++ = 0;
d = d->LookupDirectory(duplicated);
if (d == nullptr) {
- g_free(duplicated);
+ free(duplicated);
return nullptr;
}
} else
@@ -265,7 +222,7 @@ Directory::LookupSong(const char *uri)
Song *song = d->FindSong(base);
assert(song == nullptr || song->parent == d);
- g_free(duplicated);
+ free(duplicated);
return song;
}
@@ -276,7 +233,7 @@ directory_cmp(gcc_unused void *priv,
{
const Directory *a = (const Directory *)_a;
const Directory *b = (const Directory *)_b;
- return g_utf8_collate(a->path, b->path);
+ return g_utf8_collate(a->path.c_str(), b->path.c_str());
}
void
diff --git a/src/Directory.hxx b/src/Directory.hxx
index 380a6b790..031cca877 100644
--- a/src/Directory.hxx
+++ b/src/Directory.hxx
@@ -26,6 +26,8 @@
#include "DatabaseVisitor.hxx"
#include "PlaylistVector.hxx"
+#include <string>
+
#include <sys/types.h>
#define DEVICE_INARCHIVE (dev_t)(-1)
@@ -82,42 +84,22 @@ struct Directory {
ino_t inode;
dev_t device;
bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */
- char path[sizeof(long)];
-
-protected:
- Directory(const char *path);
- gcc_malloc gcc_nonnull_all
- static Directory *Allocate(const char *path);
+ std::string path;
public:
- /**
- * Default constructor, needed for #detached_root.
- */
- Directory();
+ Directory(const char *_path_utf8, Directory *_parent);
~Directory();
/**
- * Generic constructor for #Directory object.
- */
- gcc_malloc
- static Directory *NewGeneric(const char *path_utf8, Directory *parent);
-
- /**
* Create a new root #Directory object.
*/
gcc_malloc
static Directory *NewRoot() {
- return NewGeneric("", nullptr);
+ return new Directory("", nullptr);
}
/**
- * Free this #Directory object (and the whole object tree within it),
- * assuming it was already removed from the parent.
- */
- void Free();
-
- /**
* Remove this #Directory object from its parent and free it. This
* must not be called with the root Directory.
*
@@ -178,7 +160,7 @@ public:
gcc_pure
const char *GetPath() const {
- return path;
+ return path.c_str();
}
/**
diff --git a/src/DirectorySave.cxx b/src/DirectorySave.cxx
index fa330d126..f8ee5b0bd 100644
--- a/src/DirectorySave.cxx
+++ b/src/DirectorySave.cxx
@@ -22,16 +22,15 @@
#include "Directory.hxx"
#include "Song.hxx"
#include "SongSave.hxx"
+#include "DetachedSong.hxx"
#include "PlaylistDatabase.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
+#include "util/StringUtil.hxx"
#include "util/NumberParser.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
+#include <stddef.h>
#define DIRECTORY_DIR "directory: "
#define DIRECTORY_MTIME "mtime: "
@@ -91,7 +90,7 @@ directory_load_subdir(TextFile &file, Directory &parent, const char *name,
return nullptr;
}
- if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
+ if (StringStartsWith(line, DIRECTORY_MTIME)) {
directory->mtime =
ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1);
@@ -103,7 +102,7 @@ directory_load_subdir(TextFile &file, Directory &parent, const char *name,
}
}
- if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
+ if (!StringStartsWith(line, DIRECTORY_BEGIN)) {
error.Format(directory_domain, "Malformed line: %s", line);
directory->Delete();
return nullptr;
@@ -124,17 +123,16 @@ directory_load(TextFile &file, Directory &directory, Error &error)
const char *line;
while ((line = file.ReadLine()) != nullptr &&
- !g_str_has_prefix(line, DIRECTORY_END)) {
- if (g_str_has_prefix(line, DIRECTORY_DIR)) {
+ !StringStartsWith(line, DIRECTORY_END)) {
+ if (StringStartsWith(line, DIRECTORY_DIR)) {
Directory *subdir =
directory_load_subdir(file, directory,
line + sizeof(DIRECTORY_DIR) - 1,
error);
if (subdir == nullptr)
return false;
- } else if (g_str_has_prefix(line, SONG_BEGIN)) {
+ } else if (StringStartsWith(line, SONG_BEGIN)) {
const char *name = line + sizeof(SONG_BEGIN) - 1;
- Song *song;
if (directory.FindSong(name) != nullptr) {
error.Format(directory_domain,
@@ -142,24 +140,18 @@ directory_load(TextFile &file, Directory &directory, Error &error)
return false;
}
- song = song_load(file, &directory, name, error);
+ DetachedSong *song = song_load(file, name, error);
if (song == nullptr)
return false;
- directory.AddSong(song);
- } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) {
- /* duplicate the name, because
- playlist_metadata_load() will overwrite the
- buffer */
- char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1);
-
+ directory.AddSong(Song::NewFrom(std::move(*song),
+ &directory));
+ delete song;
+ } else if (StringStartsWith(line, PLAYLIST_META_BEGIN)) {
+ const char *name = line + sizeof(PLAYLIST_META_BEGIN) - 1;
if (!playlist_metadata_load(file, directory.playlists,
- name, error)) {
- g_free(name);
+ name, error))
return false;
- }
-
- g_free(name);
} else {
error.Format(directory_domain,
"Malformed line: %s", line);
diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx
index b3397f25c..3ae1879cb 100644
--- a/src/EncoderAPI.hxx
+++ b/src/EncoderAPI.hxx
@@ -25,9 +25,13 @@
#ifndef MPD_ENCODER_API_HXX
#define MPD_ENCODER_API_HXX
+// IWYU pragma: begin_exports
+
#include "EncoderPlugin.hxx"
#include "AudioFormat.hxx"
#include "tag/Tag.hxx"
#include "ConfigData.hxx"
+// IWYU pragma: end_exports
+
#endif
diff --git a/src/EncoderList.cxx b/src/EncoderList.cxx
index 7760a9582..22b83ffa1 100644
--- a/src/EncoderList.cxx
+++ b/src/EncoderList.cxx
@@ -25,6 +25,7 @@
#include "encoder/VorbisEncoderPlugin.hxx"
#include "encoder/OpusEncoderPlugin.hxx"
#include "encoder/FlacEncoderPlugin.hxx"
+#include "encoder/ShineEncoderPlugin.hxx"
#include "encoder/LameEncoderPlugin.hxx"
#include "encoder/TwolameEncoderPlugin.hxx"
@@ -50,6 +51,9 @@ const EncoderPlugin *const encoder_plugins[] = {
#ifdef ENABLE_FLAC_ENCODER
&flac_encoder_plugin,
#endif
+#ifdef ENABLE_SHINE_ENCODER
+ &shine_encoder_plugin,
+#endif
nullptr
};
diff --git a/src/Expat.cxx b/src/Expat.cxx
new file mode 100644
index 000000000..5cee45912
--- /dev/null
+++ b/src/Expat.cxx
@@ -0,0 +1,92 @@
+/*
+ * 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 "Expat.hxx"
+#include "InputStream.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <string.h>
+
+static constexpr Domain expat_domain("expat");
+
+void
+ExpatParser::SetError(Error &error)
+{
+ XML_Error code = XML_GetErrorCode(parser);
+ error.Format(expat_domain, int(code), "XML parser failed: %s",
+ XML_ErrorString(code));
+}
+
+bool
+ExpatParser::Parse(const char *data, size_t length, bool is_final,
+ Error &error)
+{
+ bool success = XML_Parse(parser, data, length,
+ is_final) == XML_STATUS_OK;
+ if (!success)
+ SetError(error);
+
+ return success;
+}
+
+bool
+ExpatParser::Parse(InputStream &is, Error &error)
+{
+ assert(is.ready);
+
+ while (true) {
+ char buffer[4096];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error);
+ if (nbytes == 0)
+ break;
+
+ if (!Parse(buffer, nbytes, false, error))
+ return false;
+ }
+
+ if (error.IsDefined())
+ return false;
+
+ return Parse("", 0, true, error);
+}
+
+const char *
+ExpatParser::GetAttribute(const XML_Char **atts,
+ const char *name)
+{
+ for (unsigned i = 0; atts[i] != nullptr; i += 2)
+ if (strcmp(atts[i], name) == 0)
+ return atts[i + 1];
+
+ return nullptr;
+}
+
+const char *
+ExpatParser::GetAttributeCase(const XML_Char **atts,
+ const char *name)
+{
+ for (unsigned i = 0; atts[i] != nullptr; i += 2)
+ if (StringEqualsCaseASCII(atts[i], name))
+ return atts[i + 1];
+
+ return nullptr;
+}
diff --git a/src/Expat.hxx b/src/Expat.hxx
new file mode 100644
index 000000000..d57a85533
--- /dev/null
+++ b/src/Expat.hxx
@@ -0,0 +1,129 @@
+/*
+ * 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_EXPAT_HXX
+#define MPD_EXPAT_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <expat.h>
+
+struct InputStream;
+class Error;
+
+class ExpatParser final {
+ const XML_Parser parser;
+
+public:
+ ExpatParser(void *userData)
+ :parser(XML_ParserCreate(nullptr)) {
+ XML_SetUserData(parser, userData);
+ }
+
+ ~ExpatParser() {
+ XML_ParserFree(parser);
+ }
+
+ void SetElementHandler(XML_StartElementHandler start,
+ XML_EndElementHandler end) {
+ XML_SetElementHandler(parser, start, end);
+ }
+
+ void SetCharacterDataHandler(XML_CharacterDataHandler charhndl) {
+ XML_SetCharacterDataHandler(parser, charhndl);
+ }
+
+ bool Parse(const char *data, size_t length, bool is_final,
+ Error &error);
+
+ bool Parse(InputStream &is, Error &error);
+
+ gcc_pure
+ static const char *GetAttribute(const XML_Char **atts,
+ const char *name);
+
+ gcc_pure
+ static const char *GetAttributeCase(const XML_Char **atts,
+ const char *name);
+
+private:
+ void SetError(Error &error);
+};
+
+/**
+ * A specialization of #ExpatParser that provides the most common
+ * callbacks as virtual methods.
+ */
+class CommonExpatParser {
+ ExpatParser parser;
+
+public:
+ CommonExpatParser():parser(this) {
+ parser.SetElementHandler(StartElement, EndElement);
+ parser.SetCharacterDataHandler(CharacterData);
+ }
+
+ bool Parse(const char *data, size_t length, bool is_final,
+ Error &error) {
+ return parser.Parse(data, length, is_final, error);
+ }
+
+ bool Parse(InputStream &is, Error &error) {
+ return parser.Parse(is, error);
+ }
+
+ gcc_pure
+ static const char *GetAttribute(const XML_Char **atts,
+ const char *name) {
+ return ExpatParser::GetAttribute(atts, name);
+ }
+
+ gcc_pure
+ static const char *GetAttributeCase(const XML_Char **atts,
+ const char *name) {
+ return ExpatParser::GetAttributeCase(atts, name);
+ }
+
+protected:
+ virtual void StartElement(const XML_Char *name,
+ const XML_Char **atts) = 0;
+ virtual void EndElement(const XML_Char *name) = 0;
+ virtual void CharacterData(const XML_Char *s, int len) = 0;
+
+private:
+ static void XMLCALL StartElement(void *user_data, const XML_Char *name,
+ const XML_Char **atts) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.StartElement(name, atts);
+ }
+
+ static void XMLCALL EndElement(void *user_data, const XML_Char *name) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.EndElement(name);
+ }
+
+ static void XMLCALL CharacterData(void *user_data,
+ const XML_Char *s, int len) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.CharacterData(s, len);
+ }
+};
+
+#endif
diff --git a/src/FilterConfig.cxx b/src/FilterConfig.cxx
index cfac1c756..423b84086 100644
--- a/src/FilterConfig.cxx
+++ b/src/FilterConfig.cxx
@@ -21,8 +21,6 @@
#include "FilterConfig.hxx"
#include "filter/ChainFilterPlugin.hxx"
#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
#include "ConfigData.hxx"
#include "ConfigOption.hxx"
#include "ConfigGlobal.hxx"
diff --git a/src/FilterPlugin.cxx b/src/FilterPlugin.cxx
index 608542f92..d5e3b664a 100644
--- a/src/FilterPlugin.cxx
+++ b/src/FilterPlugin.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
#include "FilterRegistry.hxx"
#include "ConfigData.hxx"
#include "ConfigError.hxx"
diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx
index b3b08505e..427f4e875 100644
--- a/src/FilterRegistry.cxx
+++ b/src/FilterRegistry.cxx
@@ -21,7 +21,6 @@
#include "FilterRegistry.hxx"
#include "FilterPlugin.hxx"
-#include <stddef.h>
#include <string.h>
const struct filter_plugin *const filter_plugins[] = {
diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx
index 86bfb3e2a..b7ba85286 100644
--- a/src/GlobalEvents.cxx
+++ b/src/GlobalEvents.cxx
@@ -21,7 +21,6 @@
#include "GlobalEvents.hxx"
#include "util/Manual.hxx"
#include "event/DeferredMonitor.hxx"
-#include "Compiler.h"
#include <atomic>
diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx
index 8861efb2e..02c0fdc1e 100644
--- a/src/IcyMetaDataParser.cxx
+++ b/src/IcyMetaDataParser.cxx
@@ -20,6 +20,7 @@
#include "config.h"
#include "IcyMetaDataParser.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -37,7 +38,7 @@ IcyMetaDataParser::Reset()
return;
if (data_rest == 0 && meta_size > 0)
- g_free(meta_data);
+ delete[] meta_data;
delete tag;
@@ -66,7 +67,7 @@ IcyMetaDataParser::Data(size_t length)
}
static void
-icy_add_item(Tag &tag, TagType type, const char *value)
+icy_add_item(TagBuilder &tag, TagType type, const char *value)
{
size_t length = strlen(value);
@@ -81,7 +82,7 @@ icy_add_item(Tag &tag, TagType type, const char *value)
}
static void
-icy_parse_tag_item(Tag &tag, const char *item)
+icy_parse_tag_item(TagBuilder &tag, const char *item)
{
gchar **p = g_strsplit(item, "=", 0);
@@ -99,15 +100,16 @@ icy_parse_tag_item(Tag &tag, const char *item)
static Tag *
icy_parse_tag(const char *p)
{
- Tag *tag = new Tag();
+ TagBuilder tag;
+
gchar **items = g_strsplit(p, ";", 0);
for (unsigned i = 0; items[i] != nullptr; ++i)
- icy_parse_tag_item(*tag, items[i]);
+ icy_parse_tag_item(tag, items[i]);
g_strfreev(items);
- return tag;
+ return tag.CommitNew();
}
size_t
@@ -136,7 +138,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
/* initialize metadata reader, allocate enough
memory (+1 for the null terminator) */
meta_position = 0;
- meta_data = (char *)g_malloc(meta_size + 1);
+ meta_data = new char[meta_size + 1];
}
assert(meta_position < meta_size);
@@ -161,7 +163,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
delete tag;
tag = icy_parse_tag(meta_data);
- g_free(meta_data);
+ delete[] meta_data;
/* change back to normal data mode */
diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx
index f5e981c2f..eebe8d0d4 100644
--- a/src/IcyMetaDataServer.cxx
+++ b/src/IcyMetaDataServer.cxx
@@ -25,7 +25,6 @@
#include <glib.h>
-#include <assert.h>
#include <string.h>
char*
diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx
index c24816241..7a652ef01 100644
--- a/src/InotifyQueue.cxx
+++ b/src/InotifyQueue.cxx
@@ -21,7 +21,6 @@
#include "InotifyQueue.hxx"
#include "InotifyDomain.hxx"
#include "UpdateGlue.hxx"
-#include "event/Loop.hxx"
#include "Log.hxx"
#include <string.h>
diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx
index f6ddea966..e2ce9301e 100644
--- a/src/InotifySource.hxx
+++ b/src/InotifySource.hxx
@@ -22,7 +22,6 @@
#include "event/SocketMonitor.hxx"
#include "util/FifoBuffer.hxx"
-#include "Compiler.h"
class Error;
@@ -39,6 +38,10 @@ class InotifySource final : private SocketMonitor {
mpd_inotify_callback_t callback, void *ctx, int fd);
public:
+ ~InotifySource() {
+ Close();
+ }
+
/**
* Creates a new inotify source and registers it in the GLib main
* loop.
diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx
index aa6c06ed1..6e46d3cd7 100644
--- a/src/InputRegistry.cxx
+++ b/src/InputRegistry.cxx
@@ -22,6 +22,10 @@
#include "util/Macros.hxx"
#include "input/FileInputPlugin.hxx"
+#ifdef HAVE_ALSA
+#include "input/AlsaInputPlugin.hxx"
+#endif
+
#ifdef ENABLE_ARCHIVE
#include "input/ArchiveInputPlugin.hxx"
#endif
@@ -34,6 +38,10 @@
#include "input/FfmpegInputPlugin.hxx"
#endif
+#ifdef ENABLE_SMBCLIENT
+#include "input/SmbclientInputPlugin.hxx"
+#endif
+
#ifdef ENABLE_MMS
#include "input/MmsInputPlugin.hxx"
#endif
@@ -48,6 +56,9 @@
const InputPlugin *const input_plugins[] = {
&input_plugin_file,
+#ifdef HAVE_ALSA
+ &input_plugin_alsa,
+#endif
#ifdef ENABLE_ARCHIVE
&input_plugin_archive,
#endif
@@ -57,6 +68,9 @@ const InputPlugin *const input_plugins[] = {
#ifdef HAVE_FFMPEG
&input_plugin_ffmpeg,
#endif
+#ifdef ENABLE_SMBCLIENT
+ &input_plugin_smbclient,
+#endif
#ifdef ENABLE_MMS
&input_plugin_mms,
#endif
diff --git a/src/InputStream.cxx b/src/InputStream.cxx
index 28a0aad1a..7d3ebe615 100644
--- a/src/InputStream.cxx
+++ b/src/InputStream.cxx
@@ -57,6 +57,28 @@ InputStream::Open(const char *url,
return nullptr;
}
+InputStream *
+InputStream::OpenReady(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ InputStream *is = Open(uri, mutex, cond, error);
+ if (is == nullptr)
+ return nullptr;
+
+ mutex.lock();
+ is->WaitReady();
+ bool success = is->Check(error);
+ mutex.unlock();
+
+ if (!success) {
+ is->Close();
+ is = nullptr;
+ }
+
+ return is;
+}
+
bool
InputStream::Check(Error &error)
{
diff --git a/src/InputStream.hxx b/src/InputStream.hxx
index b1bc9c4ab..5ac3bdcae 100644
--- a/src/InputStream.hxx
+++ b/src/InputStream.hxx
@@ -119,6 +119,15 @@ struct InputStream {
Error &error);
/**
+ * Just like Open(), but waits for the stream to become ready.
+ * It is a wrapper for Open(), WaitReady() and Check().
+ */
+ gcc_malloc gcc_nonnull_all
+ static InputStream *OpenReady(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /**
* Close the input stream and free resources.
*
* The caller must not lock the mutex.
diff --git a/src/Instance.cxx b/src/Instance.cxx
index daad94212..e9a308848 100644
--- a/src/Instance.cxx
+++ b/src/Instance.cxx
@@ -21,16 +21,18 @@
#include "Instance.hxx"
#include "Partition.hxx"
#include "Idle.hxx"
+#include "Stats.hxx"
void
-Instance::DeleteSong(const Song &song)
+Instance::DeleteSong(const char *uri)
{
- partition->DeleteSong(song);
+ partition->DeleteSong(uri);
}
void
Instance::DatabaseModified()
{
+ stats_invalidate();
partition->DatabaseModified();
idle_add(IDLE_DATABASE);
}
@@ -46,3 +48,9 @@ Instance::SyncWithPlayer()
{
partition->SyncWithPlayer();
}
+
+void
+Instance::OnDatabaseModified()
+{
+ DatabaseModified();
+}
diff --git a/src/Instance.hxx b/src/Instance.hxx
index a0dfd1b94..160e713b0 100644
--- a/src/Instance.hxx
+++ b/src/Instance.hxx
@@ -21,17 +21,18 @@
#define MPD_INSTANCE_HXX
#include "check.h"
+#include "DatabaseListener.hxx"
+#include "Compiler.h"
class ClientList;
struct Partition;
-struct Song;
-struct Instance {
+struct Instance final : public DatabaseListener {
ClientList *client_list;
Partition *partition;
- void DeleteSong(const Song &song);
+ void DeleteSong(const char *uri);
/**
* The database has been modified. Propagate the change to
@@ -49,6 +50,9 @@ struct Instance {
* Synchronize the player with the play queue.
*/
void SyncWithPlayer();
+
+private:
+ virtual void OnDatabaseModified();
};
#endif
diff --git a/src/Log.cxx b/src/Log.cxx
index a46c0ced8..c58790841 100644
--- a/src/Log.cxx
+++ b/src/Log.cxx
@@ -19,62 +19,19 @@
#include "config.h"
#include "LogV.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "system/fd_util.h"
-#include "system/FatalError.hxx"
-#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
-
-#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <fcntl.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
+#include <string.h>
#include <errno.h>
-static GLogLevelFlags
-ToGLib(LogLevel level)
-{
- switch (level) {
- case LogLevel::DEBUG:
- return G_LOG_LEVEL_DEBUG;
-
- case LogLevel::INFO:
- return G_LOG_LEVEL_INFO;
-
- case LogLevel::DEFAULT:
- return G_LOG_LEVEL_MESSAGE;
-
- case LogLevel::WARNING:
- case LogLevel::ERROR:
- return G_LOG_LEVEL_WARNING;
- }
-
- assert(false);
- gcc_unreachable();
-}
-
-void
-Log(const Domain &domain, LogLevel level, const char *msg)
-{
- g_log(domain.GetName(), ToGLib(level), "%s", msg);
-}
-
void
LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap)
{
- g_logv(domain.GetName(), ToGLib(level), fmt, ap);
+ char msg[1024];
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ Log(domain, level, msg);
}
void
@@ -159,7 +116,7 @@ FormatError(const Error &error, const char *fmt, ...)
void
LogErrno(const Domain &domain, int e, const char *msg)
{
- LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, g_strerror(e));
+ LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
}
void
diff --git a/src/Log.hxx b/src/Log.hxx
index 920a8d8a3..c8e166056 100644
--- a/src/Log.hxx
+++ b/src/Log.hxx
@@ -20,47 +20,12 @@
#ifndef MPD_LOG_HXX
#define MPD_LOG_HXX
+#include "LogLevel.hxx"
#include "Compiler.h"
-#ifdef WIN32
-#include <windows.h>
-/* damn you, windows.h! */
-#ifdef ERROR
-#undef ERROR
-#endif
-#endif
-
class Error;
class Domain;
-enum class LogLevel {
- /**
- * Debug message for developers.
- */
- DEBUG,
-
- /**
- * Unimportant informational message.
- */
- INFO,
-
- /**
- * Interesting informational message.
- */
- DEFAULT,
-
- /**
- * Warning: something may be wrong.
- */
- WARNING,
-
- /**
- * An error has occurred, an operation could not finish
- * successfully.
- */
- ERROR,
-};
-
void
Log(const Domain &domain, LogLevel level, const char *msg);
diff --git a/src/LogBackend.cxx b/src/LogBackend.cxx
new file mode 100644
index 000000000..bddf8db16
--- /dev/null
+++ b/src/LogBackend.cxx
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2013 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 "LogBackend.hxx"
+#include "Log.hxx"
+#include "util/Domain.hxx"
+#include "util/CharUtil.hxx"
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_SYSLOG
+#include <syslog.h>
+#endif
+
+static LogLevel log_threshold = LogLevel::INFO;
+
+#ifdef HAVE_GLIB
+static const char *log_charset;
+#endif
+
+static bool enable_timestamp;
+
+#ifdef HAVE_SYSLOG
+static bool enable_syslog;
+#endif
+
+void
+SetLogThreshold(LogLevel _threshold)
+{
+ log_threshold = _threshold;
+}
+
+#ifdef HAVE_GLIB
+
+void
+SetLogCharset(const char *_charset)
+{
+ log_charset = _charset;
+}
+
+#endif
+
+void
+EnableLogTimestamp()
+{
+#ifdef HAVE_SYSLOG
+ assert(!enable_syslog);
+#endif
+ assert(!enable_timestamp);
+
+ enable_timestamp = true;
+}
+
+static const char *log_date(void)
+{
+ static constexpr size_t LOG_DATE_BUF_SIZE = 16;
+ static char buf[LOG_DATE_BUF_SIZE];
+ time_t t = time(nullptr);
+ strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t));
+ return buf;
+}
+
+/**
+ * Determines the length of the string excluding trailing whitespace
+ * characters.
+ */
+static int
+chomp_length(const char *p)
+{
+ size_t length = strlen(p);
+
+ while (length > 0 && IsWhitespaceOrNull(p[length - 1]))
+ --length;
+
+ return (int)length;
+}
+
+#ifdef HAVE_SYSLOG
+
+static int
+ToSysLogLevel(LogLevel log_level)
+{
+ switch (log_level) {
+ case LogLevel::DEBUG:
+ return LOG_DEBUG;
+
+ case LogLevel::INFO:
+ return LOG_INFO;
+
+ case LogLevel::DEFAULT:
+ return LOG_NOTICE;
+
+ case LogLevel::WARNING:
+ return LOG_WARNING;
+
+ case LogLevel::ERROR:
+ return LOG_ERR;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+static void
+SysLog(const Domain &domain, LogLevel log_level, const char *message)
+{
+ syslog(ToSysLogLevel(log_level), "%s: %.*s",
+ domain.GetName(),
+ chomp_length(message), message);
+}
+
+void
+LogInitSysLog()
+{
+ openlog(PACKAGE, 0, LOG_DAEMON);
+ enable_syslog = true;
+}
+
+void
+LogFinishSysLog()
+{
+ if (enable_syslog)
+ closelog();
+}
+
+#endif
+
+static void
+FileLog(const Domain &domain, const char *message)
+{
+#ifdef HAVE_GLIB
+ char *converted;
+
+ if (log_charset != nullptr) {
+ converted = g_convert_with_fallback(message, -1,
+ log_charset, "utf-8",
+ nullptr, nullptr,
+ nullptr, nullptr);
+ if (converted != nullptr)
+ message = converted;
+ } else
+ converted = nullptr;
+#endif
+
+ fprintf(stderr, "%s%s: %.*s\n",
+ enable_timestamp ? log_date() : "",
+ domain.GetName(),
+ chomp_length(message), message);
+
+#ifdef HAVE_GLIB
+ g_free(converted);
+#endif
+}
+
+void
+Log(const Domain &domain, LogLevel level, const char *msg)
+{
+ if (level < log_threshold)
+ return;
+
+#ifdef HAVE_SYSLOG
+ if (enable_syslog) {
+ SysLog(domain, level, msg);
+ return;
+ }
+#endif
+
+ FileLog(domain, msg);
+}
diff --git a/src/LogBackend.hxx b/src/LogBackend.hxx
new file mode 100644
index 000000000..0d4808e48
--- /dev/null
+++ b/src/LogBackend.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2013 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_LOG_BACKEND_HXX
+#define MPD_LOG_BACKEND_HXX
+
+#include "LogLevel.hxx"
+
+void
+SetLogThreshold(LogLevel _threshold);
+
+void
+SetLogCharset(const char *_charset);
+
+void
+EnableLogTimestamp();
+
+void
+LogInitSysLog();
+
+void
+LogFinishSysLog();
+
+#endif /* LOG_H */
diff --git a/src/LogInit.cxx b/src/LogInit.cxx
index 41d13a5e8..bd858a32c 100644
--- a/src/LogInit.cxx
+++ b/src/LogInit.cxx
@@ -19,48 +19,36 @@
#include "config.h"
#include "LogInit.hxx"
+#include "LogBackend.hxx"
#include "Log.hxx"
#include "ConfigData.hxx"
#include "ConfigGlobal.hxx"
#include "ConfigOption.hxx"
-#include "system/fd_util.h"
#include "system/FatalError.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "util/CharUtil.hxx"
#include "system/FatalError.hxx"
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <string.h>
-#include <stdarg.h>
#include <fcntl.h>
#include <stdio.h>
-#include <stdlib.h>
#include <time.h>
#include <unistd.h>
-#include <errno.h>
-#include <glib.h>
-
-#ifdef HAVE_SYSLOG
-#include <syslog.h>
-#endif
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+#define LOG_LEVEL_SECURE LogLevel::INFO
#define LOG_DATE_BUF_SIZE 16
#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
static constexpr Domain log_domain("log");
-static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE;
-
-static const char *log_charset;
-
-static bool stdout_mode = true;
static int out_fd;
static AllocatedPath out_path = AllocatedPath::Null();
@@ -73,66 +61,6 @@ static void redirect_logs(int fd)
FatalSystemError("Failed to dup2 stderr");
}
-static const char *log_date(void)
-{
- static char buf[LOG_DATE_BUF_SIZE];
- time_t t = time(nullptr);
- strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t));
- return buf;
-}
-
-/**
- * Determines the length of the string excluding trailing whitespace
- * characters.
- */
-static int
-chomp_length(const char *p)
-{
- size_t length = strlen(p);
-
- while (length > 0 && IsWhitespaceOrNull(p[length - 1]))
- --length;
-
- return (int)length;
-}
-
-static void
-file_log_func(const gchar *domain,
- GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- char *converted;
-
- if (log_level > log_threshold)
- return;
-
- if (log_charset != nullptr) {
- converted = g_convert_with_fallback(message, -1,
- log_charset, "utf-8",
- nullptr, nullptr,
- nullptr, nullptr);
- if (converted != nullptr)
- message = converted;
- } else
- converted = nullptr;
-
- if (domain == nullptr)
- domain = "";
-
- fprintf(stderr, "%s%s%s%.*s\n",
- stdout_mode ? "" : log_date(),
- domain, *domain == 0 ? "" : ": ",
- chomp_length(message), message);
-
- g_free(converted);
-}
-
-static void
-log_init_stdout(void)
-{
- g_log_set_default_handler(file_log_func, nullptr);
-}
-
static int
open_log_file(void)
{
@@ -154,85 +82,22 @@ log_init_file(unsigned line, Error &error)
return false;
}
- g_log_set_default_handler(file_log_func, nullptr);
+ EnableLogTimestamp();
return true;
}
-#ifdef HAVE_SYSLOG
-
-static int
-glib_to_syslog_level(GLogLevelFlags log_level)
-{
- switch (log_level & G_LOG_LEVEL_MASK) {
- case G_LOG_LEVEL_ERROR:
- case G_LOG_LEVEL_CRITICAL:
- return LOG_ERR;
-
- case G_LOG_LEVEL_WARNING:
- return LOG_WARNING;
-
- case G_LOG_LEVEL_MESSAGE:
- return LOG_NOTICE;
-
- case G_LOG_LEVEL_INFO:
- return LOG_INFO;
-
- case G_LOG_LEVEL_DEBUG:
- return LOG_DEBUG;
-
- default:
- return LOG_NOTICE;
- }
-}
-
-static void
-syslog_log_func(const gchar *domain,
- GLogLevelFlags log_level, const gchar *message,
- gcc_unused gpointer user_data)
-{
- if (stdout_mode) {
- /* fall back to the file log function during
- startup */
- file_log_func(domain, log_level,
- message, user_data);
- return;
- }
-
- if (log_level > log_threshold)
- return;
-
- if (domain == nullptr)
- domain = "";
-
- syslog(glib_to_syslog_level(log_level), "%s%s%.*s",
- domain, *domain == 0 ? "" : ": ",
- chomp_length(message), message);
-}
-
-static void
-log_init_syslog(void)
-{
- assert(out_path.IsNull());
-
- openlog(PACKAGE, 0, LOG_DAEMON);
- g_log_set_default_handler(syslog_log_func, nullptr);
-}
-
-#endif
-
-static inline GLogLevelFlags
+static inline LogLevel
parse_log_level(const char *value, unsigned line)
{
if (0 == strcmp(value, "default"))
- return G_LOG_LEVEL_MESSAGE;
+ return LogLevel::DEFAULT;
if (0 == strcmp(value, "secure"))
return LOG_LEVEL_SECURE;
else if (0 == strcmp(value, "verbose"))
- return G_LOG_LEVEL_DEBUG;
+ return LogLevel::DEBUG;
else {
FormatFatalError("unknown log level \"%s\" at line %u",
value, line);
- return G_LOG_LEVEL_MESSAGE;
}
}
@@ -240,9 +105,7 @@ void
log_early_init(bool verbose)
{
if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
-
- log_init_stdout();
+ SetLogThreshold(LogLevel::DEBUG);
}
bool
@@ -250,16 +113,19 @@ log_init(bool verbose, bool use_stdout, Error &error)
{
const struct config_param *param;
- g_get_charset(&log_charset);
+#ifdef HAVE_GLIB
+ const char *charset;
+ g_get_charset(&charset);
+ SetLogCharset(charset);
+#endif
if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
+ SetLogThreshold(LogLevel::DEBUG);
else if ((param = config_get_param(CONF_LOG_LEVEL)) != nullptr)
- log_threshold = parse_log_level(param->value.c_str(),
- param->line);
+ SetLogThreshold(parse_log_level(param->value.c_str(),
+ param->line));
if (use_stdout) {
- log_init_stdout();
return true;
} else {
param = config_get_param(CONF_LOG_FILE);
@@ -267,7 +133,7 @@ log_init(bool verbose, bool use_stdout, Error &error)
#ifdef HAVE_SYSLOG
/* no configuration: default to syslog (if
available) */
- log_init_syslog();
+ LogInitSysLog();
return true;
#else
error.Set(log_domain,
@@ -276,7 +142,7 @@ log_init(bool verbose, bool use_stdout, Error &error)
#endif
#ifdef HAVE_SYSLOG
} else if (strcmp(param->value.c_str(), "syslog") == 0) {
- log_init_syslog();
+ LogInitSysLog();
return true;
#endif
} else {
@@ -290,12 +156,8 @@ log_init(bool verbose, bool use_stdout, Error &error)
static void
close_log_files(void)
{
- if (stdout_mode)
- return;
-
#ifdef HAVE_SYSLOG
- if (out_path.IsNull())
- closelog();
+ LogFinishSysLog();
#endif
}
@@ -309,32 +171,35 @@ log_deinit(void)
void setup_log_output(bool use_stdout)
{
+ if (use_stdout)
+ return;
+
fflush(nullptr);
- if (!use_stdout) {
-#ifndef WIN32
- if (out_path.IsNull())
- out_fd = open("/dev/null", O_WRONLY);
+
+ if (out_fd < 0) {
+#ifdef WIN32
+ return;
+#else
+ out_fd = open("/dev/null", O_WRONLY);
+ if (out_fd < 0)
+ return;
#endif
+ }
- if (out_fd >= 0) {
- redirect_logs(out_fd);
- close(out_fd);
- }
+ redirect_logs(out_fd);
+ close(out_fd);
+ out_fd = -1;
- stdout_mode = false;
- log_charset = nullptr;
- }
+ SetLogCharset(nullptr);
}
int cycle_log_files(void)
{
int fd;
- if (stdout_mode || out_path.IsNull())
+ if (out_path.IsNull())
return 0;
- assert(!out_path.IsNull());
-
FormatDebug(log_domain, "Cycling log files");
close_log_files();
diff --git a/src/LogLevel.hxx b/src/LogLevel.hxx
new file mode 100644
index 000000000..dd24cc384
--- /dev/null
+++ b/src/LogLevel.hxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2013 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_LOG_LEVEL_HXX
+#define MPD_LOG_LEVEL_HXX
+
+#ifdef WIN32
+#include <windows.h>
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+enum class LogLevel {
+ /**
+ * Debug message for developers.
+ */
+ DEBUG,
+
+ /**
+ * Unimportant informational message.
+ */
+ INFO,
+
+ /**
+ * Interesting informational message.
+ */
+ DEFAULT,
+
+ /**
+ * Warning: something may be wrong.
+ */
+ WARNING,
+
+ /**
+ * An error has occurred, an operation could not finish
+ * successfully.
+ */
+ ERROR,
+};
+
+#endif
diff --git a/src/LogV.hxx b/src/LogV.hxx
index 4bd4f801d..9f3687ba0 100644
--- a/src/LogV.hxx
+++ b/src/LogV.hxx
@@ -20,7 +20,7 @@
#ifndef MPD_LOGV_HXX
#define MPD_LOGV_HXX
-#include "Log.hxx"
+#include "Log.hxx" // IWYU pragma: export
#include <stdarg.h>
diff --git a/src/Main.cxx b/src/Main.cxx
index b45e2c3ae..624c6216d 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -50,11 +50,12 @@
#include "IOThread.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Config.hxx"
+#include "fs/StandardDirectory.hxx"
#include "PlaylistRegistry.hxx"
#include "ZeroconfGlue.hxx"
#include "DecoderList.hxx"
#include "AudioConfig.hxx"
-#include "pcm/PcmResample.hxx"
+#include "pcm/PcmConvert.hxx"
#include "Daemon.hxx"
#include "system/FatalError.hxx"
#include "util/Error.hxx"
@@ -78,12 +79,11 @@
#include "ArchiveList.hxx"
#endif
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
-#include <unistd.h>
#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
@@ -94,6 +94,8 @@
#include <ws2tcpip.h>
#endif
+#include <limits.h>
+
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
@@ -135,13 +137,9 @@ glue_mapper_init(Error &error)
return false;
if (music_dir.IsNull()) {
- const char *path =
- g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
- if (path != nullptr) {
- music_dir = AllocatedPath::FromUTF8(path, error);
- if (music_dir.IsNull())
- return false;
- }
+ music_dir = GetUserMusicDir();
+ if (music_dir.IsNull())
+ return false;
}
mapper_init(std::move(music_dir), std::move(playlist_dir));
@@ -188,7 +186,7 @@ glue_db_init_and_load(void)
return true;
Error error;
- if (!DatabaseGlobalInit(*param, error))
+ if (!DatabaseGlobalInit(*main_loop, *instance, *param, error))
FatalError(error);
delete allocated;
@@ -365,15 +363,17 @@ int mpd_main(int argc, char *argv[])
setlocale(LC_CTYPE,"");
#endif
+#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
- io_thread_init();
winsock_init();
+ io_thread_init();
config_global_init();
success = parse_cmdline(argc, argv, &options, error);
@@ -431,7 +431,7 @@ int mpd_main(int argc, char *argv[])
archive_plugin_init_all();
#endif
- if (!pcm_resample_global_init(error)) {
+ if (!pcm_convert_global_init(error)) {
LogError(error);
return EXIT_FAILURE;
}
@@ -479,7 +479,7 @@ int mpd_main(int argc, char *argv[])
}
if (!glue_state_file_init(error)) {
- g_printerr("%s\n", error.GetMessage());
+ LogError(error);
return EXIT_FAILURE;
}
@@ -544,7 +544,6 @@ int mpd_main(int argc, char *argv[])
playlist_list_global_finish();
input_stream_global_finish();
audio_output_all_finish();
- volume_finish();
mapper_finish();
delete instance->partition;
command_finish();
@@ -554,7 +553,6 @@ int mpd_main(int argc, char *argv[])
archive_plugin_deinit_all();
#endif
config_global_finish();
- stats_global_finish();
io_thread_deinit();
SignalHandlersFinish();
delete instance;
diff --git a/src/Mapper.cxx b/src/Mapper.cxx
index cbe45daa0..6910b4983 100644
--- a/src/Mapper.cxx
+++ b/src/Mapper.cxx
@@ -25,6 +25,7 @@
#include "Mapper.hxx"
#include "Directory.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/Charset.hxx"
@@ -36,9 +37,7 @@
#include <assert.h>
#include <string.h>
#include <sys/stat.h>
-#include <unistd.h>
#include <errno.h>
-#include <dirent.h>
static constexpr Domain mapper_domain("mapper");
@@ -150,7 +149,7 @@ map_to_relative_path(const char *path_utf8)
return !music_dir_utf8.empty() &&
memcmp(path_utf8, music_dir_utf8.c_str(),
music_dir_utf8_length) == 0 &&
- PathTraits::IsSeparatorUTF8(path_utf8[music_dir_utf8_length])
+ PathTraitsUTF8::IsSeparator(path_utf8[music_dir_utf8_length])
? path_utf8 + music_dir_utf8_length + 1
: path_utf8;
}
@@ -221,20 +220,24 @@ map_detached_song_fs(const char *uri_utf8)
AllocatedPath
map_song_fs(const Song &song)
{
- assert(song.IsFile());
+ return song.parent == nullptr
+ ? map_detached_song_fs(song.uri)
+ : map_directory_child_fs(*song.parent, song.uri);
+}
- if (song.IsInDatabase())
- return song.IsDetached()
- ? map_detached_song_fs(song.uri)
- : map_directory_child_fs(*song.parent, song.uri);
+AllocatedPath
+map_song_fs(const DetachedSong &song)
+{
+ if (song.IsAbsoluteFile())
+ return AllocatedPath::FromUTF8(song.GetURI());
else
- return AllocatedPath::FromUTF8(song.uri);
+ return map_uri_fs(song.GetURI());
}
std::string
map_fs_to_utf8(const char *path_fs)
{
- if (PathTraits::IsSeparatorFS(path_fs[0])) {
+ if (PathTraitsFS::IsSeparator(path_fs[0])) {
path_fs = music_dir_fs.RelativeFS(path_fs);
if (path_fs == nullptr || *path_fs == 0)
return std::string();
diff --git a/src/Mapper.hxx b/src/Mapper.hxx
index 947fd2822..be61ab43a 100644
--- a/src/Mapper.hxx
+++ b/src/Mapper.hxx
@@ -30,10 +30,10 @@
#define PLAYLIST_FILE_SUFFIX ".m3u"
-class Path;
class AllocatedPath;
struct Directory;
struct Song;
+class DetachedSong;
void
mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir);
@@ -117,6 +117,10 @@ gcc_pure
AllocatedPath
map_song_fs(const Song &song);
+gcc_pure
+AllocatedPath
+map_song_fs(const DetachedSong &song);
+
/**
* Maps a file system path (relative to the music directory or
* absolute) to a relative path in UTF-8 encoding.
@@ -138,8 +142,7 @@ map_spl_path(void);
/**
* Maps a playlist name (without the ".m3u" suffix) to a file system
- * path. The return value is allocated on the heap and must be freed
- * with g_free().
+ * path.
*
* @return the path in file system encoding, or nullptr if mapping failed
*/
diff --git a/src/MemorySongEnumerator.cxx b/src/MemorySongEnumerator.cxx
index 7c9d05daa..3bb17083c 100644
--- a/src/MemorySongEnumerator.cxx
+++ b/src/MemorySongEnumerator.cxx
@@ -20,13 +20,13 @@
#include "config.h"
#include "MemorySongEnumerator.hxx"
-Song *
+DetachedSong *
MemorySongEnumerator::NextSong()
{
if (songs.empty())
return nullptr;
- auto result = songs.front().Steal();
+ auto result = new DetachedSong(std::move(songs.front()));
songs.pop_front();
return result;
}
diff --git a/src/MemorySongEnumerator.hxx b/src/MemorySongEnumerator.hxx
index 46086a064..085e16bc6 100644
--- a/src/MemorySongEnumerator.hxx
+++ b/src/MemorySongEnumerator.hxx
@@ -21,18 +21,18 @@
#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
#include "SongEnumerator.hxx"
-#include "SongPointer.hxx"
+#include "DetachedSong.hxx"
#include <forward_list>
class MemorySongEnumerator final : public SongEnumerator {
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
public:
- MemorySongEnumerator(std::forward_list<SongPointer> &&_songs)
+ MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs)
:songs(std::move(_songs)) {}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
#endif
diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx
index 37225fd25..2eb54c232 100644
--- a/src/MixerAll.cxx
+++ b/src/MixerAll.cxx
@@ -23,7 +23,7 @@
#include "MixerInternal.hxx"
#include "MixerList.hxx"
#include "OutputAll.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "OutputInternal.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/MixerControl.cxx b/src/MixerControl.cxx
index dd4f03543..08973a698 100644
--- a/src/MixerControl.cxx
+++ b/src/MixerControl.cxx
@@ -23,7 +23,6 @@
#include "util/Error.hxx"
#include <assert.h>
-#include <stddef.h>
Mixer *
mixer_new(const struct mixer_plugin *plugin, void *ao,
diff --git a/src/MixerControl.hxx b/src/MixerControl.hxx
index c1ee01eec..e7e825e7c 100644
--- a/src/MixerControl.hxx
+++ b/src/MixerControl.hxx
@@ -31,7 +31,7 @@ struct mixer_plugin;
struct config_param;
Mixer *
-mixer_new(const struct mixer_plugin *plugin, void *ao,
+mixer_new(const mixer_plugin *plugin, void *ao,
const config_param &param,
Error &error);
diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx
index 73cbaf183..437141c5c 100644
--- a/src/OutputAPI.hxx
+++ b/src/OutputAPI.hxx
@@ -20,10 +20,14 @@
#ifndef MPD_OUTPUT_API_HXX
#define MPD_OUTPUT_API_HxX
+// IWYU pragma: begin_exports
+
#include "OutputPlugin.hxx"
#include "OutputInternal.hxx"
#include "AudioFormat.hxx"
#include "tag/Tag.hxx"
#include "ConfigData.hxx"
+// IWYU pragma: end_exports
+
#endif
diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx
index 36d41184a..8cbfb6e23 100644
--- a/src/OutputAll.cxx
+++ b/src/OutputAll.cxx
@@ -33,8 +33,6 @@
#include "ConfigOption.hxx"
#include "notify.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
@@ -110,7 +108,7 @@ audio_output_all_init(PlayerControl &pc)
Error error;
num_audio_outputs = audio_output_config_count();
- audio_outputs = g_new(struct audio_output *, num_audio_outputs);
+ audio_outputs = new audio_output *[num_audio_outputs];
const config_param empty;
@@ -160,7 +158,7 @@ audio_output_all_finish(void)
audio_output_finish(audio_outputs[i]);
}
- g_free(audio_outputs);
+ delete[] audio_outputs;
audio_outputs = nullptr;
num_audio_outputs = 0;
}
@@ -225,10 +223,7 @@ audio_output_reset_reopen(struct audio_output *ao)
{
const ScopeLock protect(ao->mutex);
- if (!ao->open && ao->fail_timer != nullptr) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
+ ao->fail_timer.Reset();
}
/**
diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx
index 98061c345..2d9b1477f 100644
--- a/src/OutputAll.hxx
+++ b/src/OutputAll.hxx
@@ -115,7 +115,7 @@ audio_output_all_set_replay_gain_mode(ReplayGainMode mode);
* (all closed then)
*/
bool
-audio_output_all_play(struct music_chunk *chunk, Error &error);
+audio_output_all_play(music_chunk *chunk, Error &error);
/**
* Checks if the output devices have drained their music pipe, and
diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx
index 10b5bb322..efc8081e0 100644
--- a/src/OutputCommand.cxx
+++ b/src/OutputCommand.cxx
@@ -28,7 +28,6 @@
#include "OutputCommand.hxx"
#include "OutputAll.hxx"
#include "OutputInternal.hxx"
-#include "OutputPlugin.hxx"
#include "PlayerControl.hxx"
#include "MixerControl.hxx"
#include "Idle.hxx"
diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx
index 27f280231..bcfd6a8c1 100644
--- a/src/OutputControl.cxx
+++ b/src/OutputControl.cxx
@@ -24,18 +24,13 @@
#include "OutputInternal.hxx"
#include "OutputPlugin.hxx"
#include "OutputError.hxx"
-#include "MixerPlugin.hxx"
#include "MixerControl.hxx"
#include "notify.hxx"
#include "filter/ReplayGainFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
-#include <stdlib.h>
/** after a failure, wait this number of seconds before
automatically reopening the device */
@@ -154,10 +149,7 @@ audio_output_open(struct audio_output *ao,
assert(ao->allow_play);
assert(audio_format.IsValid());
- if (ao->fail_timer != nullptr) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
+ ao->fail_timer.Reset();
if (ao->open && audio_format == ao->in_audio_format) {
assert(ao->pipe == &mp ||
@@ -215,14 +207,12 @@ audio_output_close_locked(struct audio_output *ao)
if (ao->mixer != nullptr)
mixer_auto_close(ao->mixer);
- assert(!ao->open || ao->fail_timer == nullptr);
+ assert(!ao->open || !ao->fail_timer.IsDefined());
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
- else if (ao->fail_timer != nullptr) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
+ else
+ ao->fail_timer.Reset();
}
bool
@@ -233,8 +223,7 @@ audio_output_update(struct audio_output *ao,
const ScopeLock protect(ao->mutex);
if (ao->enabled && ao->really_enabled) {
- if (ao->fail_timer == nullptr ||
- g_timer_elapsed(ao->fail_timer, nullptr) > REOPEN_AFTER) {
+ if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) {
return audio_output_open(ao, audio_format, mp);
}
} else if (audio_output_is_open(ao))
@@ -314,7 +303,7 @@ audio_output_release(struct audio_output *ao)
void audio_output_close(struct audio_output *ao)
{
assert(ao != nullptr);
- assert(!ao->open || ao->fail_timer == nullptr);
+ assert(!ao->open || !ao->fail_timer.IsDefined());
const ScopeLock protect(ao->mutex);
audio_output_close_locked(ao);
@@ -324,7 +313,7 @@ void audio_output_finish(struct audio_output *ao)
{
audio_output_close(ao);
- assert(ao->fail_timer == nullptr);
+ assert(!ao->fail_timer.IsDefined());
if (ao->thread.IsDefined()) {
assert(ao->allow_play);
diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx
index d8f0b432d..c63383293 100644
--- a/src/OutputControl.hxx
+++ b/src/OutputControl.hxx
@@ -30,20 +30,20 @@ struct config_param;
class MusicPipe;
void
-audio_output_set_replay_gain_mode(struct audio_output *ao,
+audio_output_set_replay_gain_mode(audio_output *ao,
ReplayGainMode mode);
/**
* Enables the device.
*/
void
-audio_output_enable(struct audio_output *ao);
+audio_output_enable(audio_output *ao);
/**
* Disables the device.
*/
void
-audio_output_disable(struct audio_output *ao);
+audio_output_disable(audio_output *ao);
/**
* Opens or closes the device, depending on the "enabled" flag.
@@ -51,40 +51,44 @@ audio_output_disable(struct audio_output *ao);
* @return true if the device is open
*/
bool
-audio_output_update(struct audio_output *ao,
+audio_output_update(audio_output *ao,
AudioFormat audio_format,
const MusicPipe &mp);
void
-audio_output_play(struct audio_output *ao);
+audio_output_play(audio_output *ao);
-void audio_output_pause(struct audio_output *ao);
+void
+audio_output_pause(audio_output *ao);
void
-audio_output_drain_async(struct audio_output *ao);
+audio_output_drain_async(audio_output *ao);
/**
* Clear the "allow_play" flag and send the "CANCEL" command
* asynchronously. To finish the operation, the caller has to call
* audio_output_allow_play().
*/
-void audio_output_cancel(struct audio_output *ao);
+void
+audio_output_cancel(audio_output *ao);
/**
* Set the "allow_play" and signal the thread.
*/
void
-audio_output_allow_play(struct audio_output *ao);
+audio_output_allow_play(audio_output *ao);
-void audio_output_close(struct audio_output *ao);
+void
+audio_output_close(audio_output *ao);
/**
* Closes the audio output, but if the "always_on" flag is set, put it
* into pause mode instead.
*/
void
-audio_output_release(struct audio_output *ao);
+audio_output_release(audio_output *ao);
-void audio_output_finish(struct audio_output *ao);
+void
+audio_output_finish(audio_output *ao);
#endif
diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx
index db6599b53..b19f4c402 100644
--- a/src/OutputFinish.cxx
+++ b/src/OutputFinish.cxx
@@ -29,7 +29,7 @@ void
ao_base_finish(struct audio_output *ao)
{
assert(!ao->open);
- assert(ao->fail_timer == nullptr);
+ assert(!ao->fail_timer.IsDefined());
assert(!ao->thread.IsDefined());
if (ao->mixer != nullptr)
@@ -44,7 +44,7 @@ void
audio_output_free(struct audio_output *ao)
{
assert(!ao->open);
- assert(ao->fail_timer == nullptr);
+ assert(!ao->fail_timer.IsDefined());
assert(!ao->thread.IsDefined());
ao_plugin_finish(ao);
diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx
index 28eba1ab2..c9e03e0f2 100644
--- a/src/OutputInit.cxx
+++ b/src/OutputInit.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "OutputInternal.hxx"
-#include "OutputControl.hxx"
#include "OutputList.hxx"
#include "OutputError.hxx"
#include "OutputAPI.hxx"
@@ -173,7 +172,6 @@ ao_base_init(struct audio_output *ao,
ao->allow_play = true;
ao->in_playback_loop = false;
ao->woken_for_play = false;
- ao->fail_timer = nullptr;
/* set up the filter chain */
diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx
index c07cdf856..ab1c1242e 100644
--- a/src/OutputInternal.hxx
+++ b/src/OutputInternal.hxx
@@ -22,18 +22,17 @@
#include "AudioFormat.hxx"
#include "pcm/PcmBuffer.hxx"
+#include "pcm/PcmDither.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "thread/Thread.hxx"
-
-#include <time.h>
+#include "system/PeriodClock.hxx"
class Error;
class Filter;
class MusicPipe;
struct config_param;
struct PlayerControl;
-typedef struct _GTimer GTimer;
enum audio_output_command {
AO_COMMAND_NONE = 0,
@@ -147,7 +146,7 @@ struct audio_output {
* to estimate how long it should stay disabled (unless
* explicitly reopened with "play").
*/
- GTimer *fail_timer;
+ PeriodClock fail_timer;
/**
* The configured audio format.
@@ -174,6 +173,11 @@ struct audio_output {
PcmBuffer cross_fade_buffer;
/**
+ * The dithering state for cross-fading two streams.
+ */
+ PcmDither cross_fade_dither;
+
+ /**
* The filter object of this audio output. This is an
* instance of chain_filter_plugin.
*/
diff --git a/src/OutputState.cxx b/src/OutputState.cxx
index a3650413c..0124e1e33 100644
--- a/src/OutputState.cxx
+++ b/src/OutputState.cxx
@@ -28,12 +28,10 @@
#include "OutputInternal.hxx"
#include "OutputError.hxx"
#include "Log.hxx"
-
-#include <glib.h>
+#include "util/StringUtil.hxx"
#include <assert.h>
#include <stdlib.h>
-#include <string.h>
#define AUDIO_DEVICE_STATE "audio_device_state:"
@@ -62,7 +60,7 @@ audio_output_state_read(const char *line)
const char *name;
struct audio_output *ao;
- if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ if (!StringStartsWith(line, AUDIO_DEVICE_STATE))
return false;
line += sizeof(AUDIO_DEVICE_STATE) - 1;
diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx
index 30d3ba30f..62833e360 100644
--- a/src/OutputThread.cxx
+++ b/src/OutputThread.cxx
@@ -35,8 +35,6 @@
#include "Log.hxx"
#include "Compiler.h"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
@@ -98,10 +96,16 @@ ao_filter_open(struct audio_output *ao, AudioFormat &format,
assert(format.IsValid());
/* the replay_gain filter cannot fail here */
- if (ao->replay_gain_filter != nullptr)
- ao->replay_gain_filter->Open(format, error_r);
- if (ao->other_replay_gain_filter != nullptr)
- ao->other_replay_gain_filter->Open(format, error_r);
+ if (ao->replay_gain_filter != nullptr &&
+ !ao->replay_gain_filter->Open(format, error_r).IsDefined())
+ return AudioFormat::Undefined();
+
+ if (ao->other_replay_gain_filter != nullptr &&
+ !ao->other_replay_gain_filter->Open(format, error_r).IsDefined()) {
+ if (ao->replay_gain_filter != nullptr)
+ ao->replay_gain_filter->Close();
+ return AudioFormat::Undefined();
+ }
const AudioFormat af = ao->filter->Open(format, error_r);
if (!af.IsDefined()) {
@@ -137,14 +141,7 @@ ao_open(struct audio_output *ao)
assert(ao->chunk == nullptr);
assert(ao->in_audio_format.IsValid());
- if (ao->fail_timer != nullptr) {
- /* this can only happen when this
- output thread fails while
- audio_output_open() is run in the
- player thread */
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
+ ao->fail_timer.Reset();
/* enable the device (just in case the last enable has failed) */
@@ -160,7 +157,7 @@ ao_open(struct audio_output *ao)
FormatError(error, "Failed to open filter for \"%s\" [%s]",
ao->name, ao->plugin->name);
- ao->fail_timer = g_timer_new();
+ ao->fail_timer.Update();
return;
}
@@ -180,11 +177,19 @@ ao_open(struct audio_output *ao)
ao->name, ao->plugin->name);
ao_filter_close(ao);
- ao->fail_timer = g_timer_new();
+ ao->fail_timer.Update();
return;
}
- convert_filter_set(ao->convert_filter, ao->out_audio_format);
+ if (!convert_filter_set(ao->convert_filter, ao->out_audio_format,
+ error)) {
+ FormatError(error, "Failed to convert for \"%s\" [%s]",
+ ao->name, ao->plugin->name);
+
+ ao_filter_close(ao);
+ ao->fail_timer.Update();
+ return;
+ }
ao->open = true;
@@ -233,7 +238,9 @@ ao_reopen_filter(struct audio_output *ao)
ao_filter_close(ao);
const AudioFormat filter_audio_format =
ao_filter_open(ao, ao->in_audio_format, error);
- if (!filter_audio_format.IsDefined()) {
+ if (!filter_audio_format.IsDefined() ||
+ !convert_filter_set(ao->convert_filter, ao->out_audio_format,
+ error)) {
FormatError(error,
"Failed to open filter for \"%s\" [%s]",
ao->name, ao->plugin->name);
@@ -246,7 +253,7 @@ ao_reopen_filter(struct audio_output *ao)
ao->chunk = nullptr;
ao->open = false;
- ao->fail_timer = g_timer_new();
+ ao->fail_timer.Update();
ao->mutex.unlock();
ao_plugin_close(ao);
@@ -254,8 +261,6 @@ ao_reopen_filter(struct audio_output *ao)
return;
}
-
- convert_filter_set(ao->convert_filter, ao->out_audio_format);
}
static void
@@ -387,7 +392,7 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
void *dest = ao->cross_fade_buffer.Get(other_length);
memcpy(dest, other_data, other_length);
- if (!pcm_mix(dest, data, length,
+ if (!pcm_mix(ao->cross_fade_dither, dest, data, length,
ao->in_audio_format.format,
1.0 - chunk->mix_ratio)) {
FormatError(output_domain,
@@ -437,7 +442,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
/* don't automatically reopen this device for 10
seconds */
- ao->fail_timer = g_timer_new();
+ ao->fail_timer.Update();
return false;
}
@@ -461,8 +466,8 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
/* don't automatically reopen this device for
10 seconds */
- assert(ao->fail_timer == nullptr);
- ao->fail_timer = g_timer_new();
+ assert(!ao->fail_timer.IsDefined());
+ ao->fail_timer.Update();
return false;
}
diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx
index 1a7932162..980d203fe 100644
--- a/src/OutputThread.hxx
+++ b/src/OutputThread.hxx
@@ -22,6 +22,7 @@
struct audio_output;
-void audio_output_thread_start(struct audio_output *ao);
+void
+audio_output_thread_start(audio_output *ao);
#endif
diff --git a/src/Page.cxx b/src/Page.cxx
index 91033a1ec..c46d743ca 100644
--- a/src/Page.cxx
+++ b/src/Page.cxx
@@ -19,19 +19,19 @@
#include "config.h"
#include "Page.hxx"
-
-#include <glib.h>
+#include "util/Alloc.hxx"
#include <new>
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
Page *
Page::Create(size_t size)
{
- void *p = g_malloc(sizeof(Page) + size -
- sizeof(Page::data));
+ void *p = xalloc(sizeof(Page) + size -
+ sizeof(Page::data));
return ::new(p) Page(size);
}
@@ -63,7 +63,7 @@ Page::Unref()
if (unused) {
this->Page::~Page();
- g_free(this);
+ free(this);
}
return unused;
diff --git a/src/Page.hxx b/src/Page.hxx
index 27c6092cc..4e1302ba5 100644
--- a/src/Page.hxx
+++ b/src/Page.hxx
@@ -27,8 +27,6 @@
#include "util/RefCount.hxx"
-#include <algorithm>
-
#include <stddef.h>
/**
diff --git a/src/Partition.cxx b/src/Partition.cxx
index 55750cfad..9c30ce0a7 100644
--- a/src/Partition.cxx
+++ b/src/Partition.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "Partition.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
void
Partition::DatabaseModified()
@@ -30,10 +30,10 @@ Partition::DatabaseModified()
void
Partition::TagModified()
{
- Song *song = pc.LockReadTaggedSong();
+ DetachedSong *song = pc.LockReadTaggedSong();
if (song != nullptr) {
playlist.TagModified(std::move(*song));
- song->Free();
+ delete song;
}
}
diff --git a/src/Partition.hxx b/src/Partition.hxx
index 512ba3bca..69c6f0175 100644
--- a/src/Partition.hxx
+++ b/src/Partition.hxx
@@ -76,8 +76,8 @@ struct Partition {
return playlist.DeleteRange(pc, start, end);
}
- void DeleteSong(const Song &song) {
- playlist.DeleteSong(pc, song);
+ void DeleteSong(const char *uri) {
+ playlist.DeleteSong(pc, uri);
}
void Shuffle(unsigned start, unsigned end) {
diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx
index f180874b0..baaf7bc3b 100644
--- a/src/PlayerControl.cxx
+++ b/src/PlayerControl.cxx
@@ -20,10 +20,9 @@
#include "config.h"
#include "PlayerControl.hxx"
#include "Idle.hxx"
-#include "Song.hxx"
-#include "DecoderControl.hxx"
+#include "DetachedSong.hxx"
-#include <cmath>
+#include <algorithm>
#include <assert.h>
@@ -43,15 +42,12 @@ PlayerControl::PlayerControl(unsigned _buffer_chunks,
PlayerControl::~PlayerControl()
{
- if (next_song != nullptr)
- next_song->Free();
-
- if (tagged_song != nullptr)
- tagged_song->Free();
+ delete next_song;
+ delete tagged_song;
}
void
-PlayerControl::Play(Song *song)
+PlayerControl::Play(DetachedSong *song)
{
assert(song != nullptr);
@@ -196,26 +192,23 @@ PlayerControl::ClearError()
}
void
-PlayerControl::LockSetTaggedSong(const Song &song)
+PlayerControl::LockSetTaggedSong(const DetachedSong &song)
{
Lock();
- if (tagged_song != nullptr)
- tagged_song->Free();
- tagged_song = song.DupDetached();
+ delete tagged_song;
+ tagged_song = new DetachedSong(song);
Unlock();
}
void
PlayerControl::ClearTaggedSong()
{
- if (tagged_song != nullptr) {
- tagged_song->Free();
- tagged_song = nullptr;
- }
+ delete tagged_song;
+ tagged_song = nullptr;
}
void
-PlayerControl::EnqueueSong(Song *song)
+PlayerControl::EnqueueSong(DetachedSong *song)
{
assert(song != nullptr);
@@ -225,15 +218,13 @@ PlayerControl::EnqueueSong(Song *song)
}
bool
-PlayerControl::Seek(Song *song, float seek_time)
+PlayerControl::Seek(DetachedSong *song, float seek_time)
{
assert(song != nullptr);
Lock();
- if (next_song != nullptr)
- next_song->Free();
-
+ delete next_song;
next_song = song;
seek_where = seek_time;
SynchronousCommand(PlayerCommand::SEEK);
diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx
index 61bb408d2..6919d2cb1 100644
--- a/src/PlayerControl.hxx
+++ b/src/PlayerControl.hxx
@@ -29,7 +29,7 @@
#include <stdint.h>
-struct Song;
+class DetachedSong;
enum class PlayerState : uint8_t {
STOP,
@@ -131,16 +131,16 @@ struct PlayerControl {
Error error;
/**
- * A copy of the current #Song after its tags have been
- * updated by the decoder (for example, a radio stream that
- * has sent a new tag after switching to the next song). This
- * shall be used by the GlobalEvents::TAG handler to update
- * the current #Song in the queue.
+ * A copy of the current #DetachedSong after its tags have
+ * been updated by the decoder (for example, a radio stream
+ * that has sent a new tag after switching to the next song).
+ * This shall be used by the GlobalEvents::TAG handler to
+ * update the current #DetachedSong in the queue.
*
* Protected by #mutex. Set by the PlayerThread and consumed
* by the main thread.
*/
- Song *tagged_song;
+ DetachedSong *tagged_song;
uint16_t bit_rate;
AudioFormat audio_format;
@@ -153,7 +153,7 @@ struct PlayerControl {
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
- Song *next_song;
+ DetachedSong *next_song;
double seek_where;
@@ -299,7 +299,7 @@ public:
* @param song the song to be queued; the given instance will
* be owned and freed by the player
*/
- void Play(Song *song);
+ void Play(DetachedSong *song);
/**
* see PlayerCommand::CANCEL
@@ -371,9 +371,9 @@ public:
/**
* Set the #tagged_song attribute to a newly allocated copy of
- * the given #Song. Locks and unlocks the object.
+ * the given #DetachedSong. Locks and unlocks the object.
*/
- void LockSetTaggedSong(const Song &song);
+ void LockSetTaggedSong(const DetachedSong &song);
void ClearTaggedSong();
@@ -382,8 +382,8 @@ public:
*
* Caller must lock the object.
*/
- Song *ReadTaggedSong() {
- Song *result = tagged_song;
+ DetachedSong *ReadTaggedSong() {
+ DetachedSong *result = tagged_song;
tagged_song = nullptr;
return result;
}
@@ -391,9 +391,9 @@ public:
/**
* Like ReadTaggedSong(), but locks and unlocks the object.
*/
- Song *LockReadTaggedSong() {
+ DetachedSong *LockReadTaggedSong() {
Lock();
- Song *result = ReadTaggedSong();
+ DetachedSong *result = ReadTaggedSong();
Unlock();
return result;
}
@@ -403,7 +403,7 @@ public:
void UpdateAudio();
private:
- void EnqueueSongLocked(Song *song) {
+ void EnqueueSongLocked(DetachedSong *song) {
assert(song != nullptr);
assert(next_song == nullptr);
@@ -416,7 +416,7 @@ public:
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
- void EnqueueSong(Song *song);
+ void EnqueueSong(DetachedSong *song);
/**
* Makes the player thread seek the specified song to a position.
@@ -426,7 +426,7 @@ public:
* @return true on success, false on failure (e.g. if MPD isn't
* playing currently)
*/
- bool Seek(Song *song, float seek_time);
+ bool Seek(DetachedSong *song, float seek_time);
void SetCrossFade(float cross_fade_seconds);
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
index 356559e37..814269b50 100644
--- a/src/PlayerThread.cxx
+++ b/src/PlayerThread.cxx
@@ -24,8 +24,7 @@
#include "MusicPipe.hxx"
#include "MusicBuffer.hxx"
#include "MusicChunk.hxx"
-#include "Song.hxx"
-#include "Main.hxx"
+#include "DetachedSong.hxx"
#include "system/FatalError.hxx"
#include "CrossFade.hxx"
#include "PlayerControl.hxx"
@@ -93,7 +92,7 @@ class Player {
/**
* the song currently being played
*/
- Song *song;
+ DetachedSong *song;
/**
* is cross fading enabled?
@@ -291,12 +290,12 @@ Player::StartDecoder(MusicPipe &_pipe)
assert(queued || pc.command == PlayerCommand::SEEK);
assert(pc.next_song != nullptr);
- unsigned start_ms = pc.next_song->start_ms;
+ unsigned start_ms = pc.next_song->GetStartMS();
if (pc.command == PlayerCommand::SEEK)
start_ms += (unsigned)(pc.seek_where * 1000);
- dc.Start(pc.next_song->DupDetached(),
- start_ms, pc.next_song->end_ms,
+ dc.Start(new DetachedSong(*pc.next_song),
+ start_ms, pc.next_song->GetEndMS(),
buffer, _pipe);
}
@@ -330,7 +329,7 @@ Player::WaitForDecoder()
if (error.IsDefined()) {
pc.SetError(PlayerError::DECODER, std::move(error));
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
pc.Unlock();
@@ -340,9 +339,7 @@ Player::WaitForDecoder()
pc.ClearTaggedSong();
- if (song != nullptr)
- song->Free();
-
+ delete song;
song = pc.next_song;
elapsed_time = 0.0;
@@ -371,19 +368,20 @@ Player::WaitForDecoder()
* indicated by the decoder plugin.
*/
static double
-real_song_duration(const Song *song, double decoder_duration)
+real_song_duration(const DetachedSong &song, double decoder_duration)
{
- assert(song != nullptr);
-
if (decoder_duration <= 0.0)
/* the decoder plugin didn't provide information; fall
back to Song::GetDuration() */
- return song->GetDuration();
+ return song.GetDuration();
- if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration)
- return (song->end_ms - song->start_ms) / 1000.0;
+ const unsigned start_ms = song.GetStartMS();
+ const unsigned end_ms = song.GetEndMS();
- return decoder_duration - song->start_ms / 1000.0;
+ if (end_ms > 0 && end_ms / 1000.0 < decoder_duration)
+ return (end_ms - start_ms) / 1000.0;
+
+ return decoder_duration - start_ms / 1000.0;
}
bool
@@ -451,7 +449,7 @@ Player::CheckDecoderStartup()
return true;
pc.Lock();
- pc.total_time = real_song_duration(dc.song, dc.total_time);
+ pc.total_time = real_song_duration(*dc.song, dc.total_time);
pc.audio_format = dc.in_audio_format;
pc.Unlock();
@@ -461,10 +459,10 @@ Player::CheckDecoderStartup()
decoder_starting = false;
if (!paused && !OpenOutput()) {
- const auto uri = dc.song->GetURI();
FormatError(player_domain,
"problems opening audio device "
- "while playing \"%s\"", uri.c_str());
+ "while playing \"%s\"",
+ dc.song->GetURI());
return true;
}
@@ -519,7 +517,7 @@ Player::SeekDecoder()
{
assert(pc.next_song != nullptr);
- const unsigned start_ms = pc.next_song->start_ms;
+ const unsigned start_ms = pc.next_song->GetStartMS();
if (!dc.LockIsCurrentSong(*pc.next_song)) {
/* the decoder is already decoding the "next" song -
@@ -545,7 +543,7 @@ Player::SeekDecoder()
ClearAndReplacePipe(dc.pipe);
}
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
queued = false;
}
@@ -661,7 +659,7 @@ Player::ProcessCommand()
pc.Lock();
}
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
queued = false;
pc.CommandFinished();
@@ -684,19 +682,16 @@ Player::ProcessCommand()
}
static void
-update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag)
+update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag)
{
- if (song->IsFile())
+ if (song.IsFile())
/* don't update tags of local files, only remote
streams may change tags dynamically */
return;
- Tag *old_tag = song->tag;
- song->tag = new Tag(new_tag);
-
- delete old_tag;
+ song.SetTag(new_tag);
- pc.LockSetTaggedSong(*song);
+ pc.LockSetTaggedSong(song);
/* the main thread will update the playlist version when he
receives this event */
@@ -716,7 +711,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag)
*/
static bool
play_chunk(PlayerControl &pc,
- Song *song, struct music_chunk *chunk,
+ DetachedSong &song, struct music_chunk *chunk,
MusicBuffer &buffer,
const AudioFormat format,
Error &error)
@@ -839,7 +834,7 @@ Player::PlayNextChunk()
/* play the current chunk */
Error error;
- if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) {
+ if (!play_chunk(pc, *song, chunk, buffer, play_audio_format, error)) {
LogError(error);
buffer.Return(chunk);
@@ -883,10 +878,7 @@ Player::SongBorder()
{
xfade_state = CrossFadeState::UNKNOWN;
- {
- const auto uri = song->GetURI();
- FormatDefault(player_domain, "played \"%s\"", uri.c_str());
- }
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
ReplacePipe(dc.pipe);
@@ -1082,9 +1074,8 @@ Player::Run()
delete cross_fade_tag;
if (song != nullptr) {
- const auto uri = song->GetURI();
- FormatDefault(player_domain, "played \"%s\"", uri.c_str());
- song->Free();
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
+ delete song;
}
pc.Lock();
@@ -1093,7 +1084,7 @@ Player::Run()
if (queued) {
assert(pc.next_song != nullptr);
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
}
@@ -1142,10 +1133,8 @@ player_task(void *arg)
/* fall through */
case PlayerCommand::PAUSE:
- if (pc.next_song != nullptr) {
- pc.next_song->Free();
- pc.next_song = nullptr;
- }
+ delete pc.next_song;
+ pc.next_song = nullptr;
pc.CommandFinished();
break;
@@ -1180,10 +1169,8 @@ player_task(void *arg)
return;
case PlayerCommand::CANCEL:
- if (pc.next_song != nullptr) {
- pc.next_song->Free();
- pc.next_song = nullptr;
- }
+ delete pc.next_song;
+ pc.next_song = nullptr;
pc.CommandFinished();
break;
diff --git a/src/Playlist.cxx b/src/Playlist.cxx
index 8d9ab24a3..0720e490d 100644
--- a/src/Playlist.cxx
+++ b/src/Playlist.cxx
@@ -21,24 +21,23 @@
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "DetachedSong.hxx"
#include "Idle.hxx"
#include "Log.hxx"
#include <assert.h>
void
-playlist::TagModified(Song &&song)
+playlist::TagModified(DetachedSong &&song)
{
- if (!playing || song.tag == nullptr)
+ if (!playing)
return;
assert(current >= 0);
- Song &current_song = queue.GetOrder(current);
- if (SongEquals(song, current_song))
- current_song.ReplaceTag(std::move(*song.tag));
+ DetachedSong &current_song = queue.GetOrder(current);
+ if (song.IsSame(current_song))
+ current_song.MoveTagFrom(std::move(song));
queue.ModifyAtOrder(current);
queue.IncrementVersion();
@@ -56,15 +55,12 @@ playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
playlist.queued = order;
- Song *song = playlist.queue.GetOrder(order).DupDetached();
+ const DetachedSong &song = playlist.queue.GetOrder(order);
- {
- const auto uri = song->GetURI();
- FormatDebug(playlist_domain, "queue song %i:\"%s\"",
- playlist.queued, uri.c_str());
- }
+ FormatDebug(playlist_domain, "queue song %i:\"%s\"",
+ playlist.queued, song.GetURI());
- pc.EnqueueSong(song);
+ pc.EnqueueSong(new DetachedSong(song));
}
/**
@@ -89,7 +85,7 @@ playlist_song_started(playlist &playlist, PlayerControl &pc)
idle_add(IDLE_PLAYER);
}
-const Song *
+const DetachedSong *
playlist::GetQueuedSong() const
{
return playing && queued >= 0
@@ -98,7 +94,7 @@ playlist::GetQueuedSong() const
}
void
-playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
+playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
{
if (!playing)
return;
@@ -125,7 +121,7 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
current = queue.PositionToOrder(current_position);
}
- const Song *const next_song = next_order >= 0
+ const DetachedSong *const next_song = next_order >= 0
? &queue.GetOrder(next_order)
: nullptr;
@@ -149,15 +145,11 @@ playlist::PlayOrder(PlayerControl &pc, int order)
playing = true;
queued = -1;
- Song *song = queue.GetOrder(order).DupDetached();
+ const DetachedSong &song = queue.GetOrder(order);
- {
- const auto uri = song->GetURI();
- FormatDebug(playlist_domain, "play %i:\"%s\"",
- order, uri.c_str());
- }
+ FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI());
- pc.Play(song);
+ pc.Play(new DetachedSong(song));
current = order;
}
@@ -174,7 +166,7 @@ playlist::SyncWithPlayer(PlayerControl &pc)
pc.Lock();
const PlayerState pc_state = pc.GetState();
- const Song *pc_next_song = pc.next_song;
+ const DetachedSong *pc_next_song = pc.next_song;
pc.Unlock();
if (pc_state == PlayerState::STOP)
@@ -287,7 +279,7 @@ playlist::SetRandom(PlayerControl &pc, bool status)
if (status == queue.random)
return;
- const Song *const queued_song = GetQueuedSong();
+ const DetachedSong *const queued_song = GetQueuedSong();
queue.random = status;
diff --git a/src/Playlist.hxx b/src/Playlist.hxx
index 5875ff4d8..e578bfcee 100644
--- a/src/Playlist.hxx
+++ b/src/Playlist.hxx
@@ -23,8 +23,10 @@
#include "Queue.hxx"
#include "PlaylistError.hxx"
+enum TagType : uint8_t;
struct PlayerControl;
-struct Song;
+class DetachedSong;
+class Error;
struct playlist {
/**
@@ -98,7 +100,7 @@ struct playlist {
* none if there is none (yet?) or if MPD isn't playing.
*/
gcc_pure
- const Song *GetQueuedSong() const;
+ const DetachedSong *GetQueuedSong() const;
/**
* This is the "PLAYLIST" event handler. It is invoked by the
@@ -123,7 +125,7 @@ protected:
* @param prev the song which was previously queued, as
* determined by playlist_get_queued_song()
*/
- void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
+ void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev);
public:
void Clear(PlayerControl &pc);
@@ -133,7 +135,7 @@ public:
* thread. Apply the given song's tag to the current song if
* the song matches.
*/
- void TagModified(Song &&song);
+ void TagModified(DetachedSong &&song);
/**
* The database has been modified. Pull all updates.
@@ -141,7 +143,7 @@ public:
void DatabaseModified();
PlaylistResult AppendSong(PlayerControl &pc,
- Song *song,
+ DetachedSong &&song,
unsigned *added_id=nullptr);
/**
@@ -160,7 +162,7 @@ public:
protected:
void DeleteInternal(PlayerControl &pc,
- unsigned song, const Song **queued_p);
+ unsigned song, const DetachedSong **queued_p);
public:
PlaylistResult DeletePosition(PlayerControl &pc,
@@ -182,7 +184,7 @@ public:
PlaylistResult DeleteRange(PlayerControl &pc,
unsigned start, unsigned end);
- void DeleteSong(PlayerControl &pc, const Song &song);
+ void DeleteSong(PlayerControl &pc, const char *uri);
void Shuffle(PlayerControl &pc, unsigned start, unsigned end);
@@ -205,6 +207,10 @@ public:
PlaylistResult SetPriorityId(PlayerControl &pc,
unsigned song_id, uint8_t priority);
+ bool AddSongIdTag(unsigned id, TagType tag_type, const char *value,
+ Error &error);
+ bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error);
+
void Stop(PlayerControl &pc);
PlaylistResult PlayPosition(PlayerControl &pc, int position);
diff --git a/src/PlaylistAny.cxx b/src/PlaylistAny.cxx
index 52304700f..083b9a230 100644
--- a/src/PlaylistAny.cxx
+++ b/src/PlaylistAny.cxx
@@ -21,7 +21,6 @@
#include "PlaylistAny.hxx"
#include "PlaylistMapper.hxx"
#include "PlaylistRegistry.hxx"
-#include "PlaylistError.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "InputStream.hxx"
@@ -42,7 +41,7 @@ playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond,
}
Error error;
- InputStream *is = InputStream::Open(uri, mutex, cond, error);
+ InputStream *is = InputStream::OpenReady(uri, mutex, cond, error);
if (is == nullptr) {
if (error.IsDefined())
FormatError(error, "Failed to open %s", uri);
diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx
index 2dbd75d6e..9ba7f7485 100644
--- a/src/PlaylistControl.cxx
+++ b/src/PlaylistControl.cxx
@@ -26,7 +26,7 @@
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Log.hxx"
void
@@ -195,7 +195,7 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
- const Song *queued_song = GetQueuedSong();
+ const DetachedSong *queued_song = GetQueuedSong();
unsigned i = queue.random
? queue.PositionToOrder(song)
@@ -215,8 +215,7 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
queued_song = nullptr;
}
- Song *the_song = queue.GetOrder(i).DupDetached();
- if (!pc.Seek(the_song, seek_time)) {
+ if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) {
UpdateQueuedSong(pc, queued_song);
return PlaylistResult::NOT_PLAYING;
diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx
index a6d15e755..e6cdb922b 100644
--- a/src/PlaylistDatabase.cxx
+++ b/src/PlaylistDatabase.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "PlaylistDatabase.hxx"
#include "PlaylistVector.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
index 668612a1a..a55f9b151 100644
--- a/src/PlaylistEdit.cxx
+++ b/src/PlaylistEdit.cxx
@@ -30,6 +30,7 @@
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Idle.hxx"
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
@@ -64,23 +65,23 @@ playlist::AppendFile(PlayerControl &pc,
if (song == nullptr)
return PlaylistResult::NO_SUCH_SONG;
- const auto result = AppendSong(pc, song, added_id);
+ const auto result = AppendSong(pc, DetachedSong(*song), added_id);
song->Free();
return result;
}
PlaylistResult
playlist::AppendSong(PlayerControl &pc,
- Song *song, unsigned *added_id)
+ DetachedSong &&song, unsigned *added_id)
{
unsigned id;
if (queue.IsFull())
return PlaylistResult::TOO_LARGE;
- const Song *const queued_song = GetQueuedSong();
+ const DetachedSong *const queued_song = GetQueuedSong();
- id = queue.Append(song, 0);
+ id = queue.Append(std::move(song), 0);
if (queue.random) {
/* shuffle the new song into the list of remaining
@@ -110,25 +111,24 @@ playlist::AppendURI(PlayerControl &pc,
{
FormatDebug(playlist_domain, "add to playlist: %s", uri);
- const Database *db = nullptr;
- Song *song;
+ DetachedSong *song;
if (uri_has_scheme(uri)) {
- song = Song::NewRemote(uri);
+ song = new DetachedSong(uri);
} else {
- db = GetDatabase();
+ const Database *db = GetDatabase();
if (db == nullptr)
return PlaylistResult::NO_SUCH_SONG;
- song = db->GetSong(uri, IgnoreError());
- if (song == nullptr)
+ Song *tmp = db->GetSong(uri, IgnoreError());
+ if (tmp == nullptr)
return PlaylistResult::NO_SUCH_SONG;
+
+ song = new DetachedSong(*tmp);
+ db->ReturnSong(tmp);
}
- PlaylistResult result = AppendSong(pc, song, added_id);
- if (db != nullptr)
- db->ReturnSong(song);
- else
- song->Free();
+ PlaylistResult result = AppendSong(pc, std::move(*song), added_id);
+ delete song;
return result;
}
@@ -139,7 +139,7 @@ playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2)
if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
return PlaylistResult::BAD_RANGE;
- const Song *const queued_song = GetQueuedSong();
+ const DetachedSong *const queued_song = GetQueuedSong();
queue.SwapPositions(song1, song2);
@@ -193,7 +193,7 @@ playlist::SetPriorityRange(PlayerControl &pc,
/* remember "current" and "queued" */
const int current_position = GetCurrentPosition();
- const Song *const queued_song = GetQueuedSong();
+ const DetachedSong *const queued_song = GetQueuedSong();
/* apply the priority changes */
@@ -225,7 +225,7 @@ playlist::SetPriorityId(PlayerControl &pc,
void
playlist::DeleteInternal(PlayerControl &pc,
- unsigned song, const Song **queued_p)
+ unsigned song, const DetachedSong **queued_p)
{
assert(song < GetLength());
@@ -275,7 +275,7 @@ playlist::DeletePosition(PlayerControl &pc, unsigned song)
if (song >= queue.GetLength())
return PlaylistResult::BAD_RANGE;
- const Song *queued_song = GetQueuedSong();
+ const DetachedSong *queued_song = GetQueuedSong();
DeleteInternal(pc, song, &queued_song);
@@ -297,7 +297,7 @@ playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end)
if (start >= end)
return PlaylistResult::SUCCESS;
- const Song *queued_song = GetQueuedSong();
+ const DetachedSong *queued_song = GetQueuedSong();
do {
DeleteInternal(pc, --end, &queued_song);
@@ -320,10 +320,10 @@ playlist::DeleteId(PlayerControl &pc, unsigned id)
}
void
-playlist::DeleteSong(PlayerControl &pc, const struct Song &song)
+playlist::DeleteSong(PlayerControl &pc, const char *uri)
{
for (int i = queue.GetLength() - 1; i >= 0; --i)
- if (SongEquals(song, queue.Get(i)))
+ if (queue.Get(i).IsURI(uri))
DeletePosition(pc, i);
}
@@ -341,7 +341,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
/* nothing happens */
return PlaylistResult::SUCCESS;
- const Song *const queued_song = GetQueuedSong();
+ const DetachedSong *const queued_song = GetQueuedSong();
/*
* (to < 0) => move to offset from current song
@@ -401,7 +401,7 @@ playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end)
/* needs at least two entries. */
return;
- const Song *const queued_song = GetQueuedSong();
+ const DetachedSong *const queued_song = GetQueuedSong();
if (playing && current >= 0) {
unsigned current_position = queue.OrderToPosition(current);
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
index e7dae6258..0f84cc4eb 100644
--- a/src/PlaylistFile.cxx
+++ b/src/PlaylistFile.cxx
@@ -24,9 +24,9 @@
#include "PlaylistVector.hxx"
#include "DatabasePlugin.hxx"
#include "DatabaseGlue.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Mapper.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "ConfigGlobal.hxx"
#include "ConfigOption.hxx"
#include "ConfigDefaults.hxx"
@@ -43,10 +43,7 @@
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
#include <string.h>
#include <errno.h>
@@ -159,10 +156,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode))
return false;
- char *name = g_strndup(name_fs_str,
- name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
- std::string name_utf8 = PathToUTF8(name);
- g_free(name);
+ std::string name(name_fs_str,
+ name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
+ std::string name_utf8 = PathToUTF8(name.c_str());
if (name_utf8.empty())
return false;
@@ -250,7 +246,7 @@ LoadPlaylistFile(const char *utf8path, Error &error)
if (!uri_has_scheme(s)) {
uri_utf8 = map_fs_to_utf8(s);
if (uri_utf8.empty()) {
- if (PathTraits::IsAbsoluteFS(s)) {
+ if (PathTraitsFS::IsAbsolute(s)) {
uri_utf8 = PathToUTF8(s);
if (uri_utf8.empty())
continue;
@@ -365,7 +361,7 @@ spl_remove_index(const char *utf8path, unsigned pos, Error &error)
}
bool
-spl_append_song(const char *utf8path, const Song &song, Error &error)
+spl_append_song(const char *utf8path, const DetachedSong &song, Error &error)
{
if (spl_map(error).IsNull())
return false;
@@ -406,22 +402,21 @@ bool
spl_append_uri(const char *url, const char *utf8file, Error &error)
{
if (uri_has_scheme(url)) {
- Song *song = Song::NewRemote(url);
- bool success = spl_append_song(utf8file, *song, error);
- song->Free();
- return success;
+ return spl_append_song(utf8file, DetachedSong(url),
+ error);
} else {
const Database *db = GetDatabase(error);
if (db == nullptr)
return false;
- Song *song = db->GetSong(url, error);
- if (song == nullptr)
+ Song *tmp = db->GetSong(url, error);
+ if (tmp == nullptr)
return false;
- bool success = spl_append_song(utf8file, *song, error);
- db->ReturnSong(song);
- return success;
+ const DetachedSong song(*tmp);
+ db->ReturnSong(tmp);
+
+ return spl_append_song(utf8file, song, error);
}
}
diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx
index f04530bcc..33fe00eb7 100644
--- a/src/PlaylistFile.hxx
+++ b/src/PlaylistFile.hxx
@@ -23,8 +23,7 @@
#include <vector>
#include <string>
-struct Song;
-struct PlaylistInfo;
+class DetachedSong;
class PlaylistVector;
class Error;
@@ -69,7 +68,7 @@ bool
spl_remove_index(const char *utf8path, unsigned pos, Error &error);
bool
-spl_append_song(const char *utf8path, const Song &song, Error &error);
+spl_append_song(const char *utf8path, const DetachedSong &song, Error &error);
bool
spl_append_uri(const char *file, const char *utf8file, Error &error);
diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx
index 97902275b..0e8493687 100644
--- a/src/PlaylistGlobal.cxx
+++ b/src/PlaylistGlobal.cxx
@@ -24,7 +24,6 @@
#include "config.h"
#include "PlaylistGlobal.hxx"
-#include "Playlist.hxx"
#include "Main.hxx"
#include "Instance.hxx"
#include "GlobalEvents.hxx"
diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx
index 8e3beaa47..cee64f859 100644
--- a/src/PlaylistPrint.cxx
+++ b/src/PlaylistPrint.cxx
@@ -23,8 +23,6 @@
#include "PlaylistAny.hxx"
#include "PlaylistSong.hxx"
#include "Playlist.hxx"
-#include "PlaylistRegistry.hxx"
-#include "PlaylistPlugin.hxx"
#include "QueuePrint.hxx"
#include "SongEnumerator.hxx"
#include "SongPrint.hxx"
@@ -33,6 +31,7 @@
#include "Client.hxx"
#include "InputStream.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "fs/Traits.hxx"
#include "util/Error.hxx"
#include "thread/Cond.hxx"
@@ -150,10 +149,10 @@ playlist_provider_print(Client &client, const char *uri,
SongEnumerator &e, bool detail)
{
const std::string base_uri = uri != nullptr
- ? PathTraits::GetParentUTF8(uri)
+ ? PathTraitsUTF8::GetParent(uri)
: std::string(".");
- Song *song;
+ DetachedSong *song;
while ((song = e.NextSong()) != nullptr) {
song = playlist_check_translate_song(song, base_uri.c_str(),
false);
@@ -165,7 +164,7 @@ playlist_provider_print(Client &client, const char *uri,
else
song_print_uri(client, *song);
- song->Free();
+ delete song;
}
}
diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx
index a8359d427..d2dce977a 100644
--- a/src/PlaylistQueue.cxx
+++ b/src/PlaylistQueue.cxx
@@ -19,13 +19,12 @@
#include "config.h"
#include "PlaylistQueue.hxx"
-#include "PlaylistPlugin.hxx"
#include "PlaylistAny.hxx"
#include "PlaylistSong.hxx"
#include "Playlist.hxx"
#include "InputStream.hxx"
#include "SongEnumerator.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "thread/Cond.hxx"
#include "fs/Traits.hxx"
@@ -36,16 +35,16 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
bool secure)
{
const std::string base_uri = uri != nullptr
- ? PathTraits::GetParentUTF8(uri)
+ ? PathTraitsUTF8::GetParent(uri)
: std::string(".");
- Song *song;
+ DetachedSong *song;
for (unsigned i = 0;
i < end_index && (song = e.NextSong()) != nullptr;
++i) {
if (i < start_index) {
/* skip songs before the start index */
- song->Free();
+ delete song;
continue;
}
@@ -54,8 +53,8 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
if (song == nullptr)
continue;
- PlaylistResult result = dest.AppendSong(pc, song);
- song->Free();
+ PlaylistResult result = dest.AppendSong(pc, std::move(*song));
+ delete song;
if (result != PlaylistResult::SUCCESS)
return result;
}
diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx
index 9afbe349d..ddfd39613 100644
--- a/src/PlaylistRegistry.cxx
+++ b/src/PlaylistRegistry.cxx
@@ -40,18 +40,18 @@
#include "system/FatalError.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
const struct playlist_plugin *const playlist_plugins[] = {
&extm3u_playlist_plugin,
&m3u_playlist_plugin,
- &xspf_playlist_plugin,
&pls_playlist_plugin,
+#ifdef HAVE_EXPAT
+ &xspf_playlist_plugin,
&asx_playlist_plugin,
&rss_playlist_plugin,
+#endif
#ifdef ENABLE_DESPOTIFY
&despotify_playlist_plugin,
#endif
@@ -130,13 +130,12 @@ static SongEnumerator *
playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond,
bool *tried)
{
- char *scheme;
SongEnumerator *playlist = nullptr;
assert(uri != nullptr);
- scheme = g_uri_parse_scheme(uri);
- if (scheme == nullptr)
+ const auto scheme = uri_get_scheme(uri);
+ if (scheme.empty())
return nullptr;
for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
@@ -146,7 +145,7 @@ playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond,
if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr &&
plugin->schemes != nullptr &&
- string_array_contains(plugin->schemes, scheme)) {
+ string_array_contains(plugin->schemes, scheme.c_str())) {
playlist = playlist_plugin_open_uri(plugin, uri,
mutex, cond);
if (playlist != nullptr)
@@ -156,7 +155,6 @@ playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond,
}
}
- g_free(scheme);
return playlist;
}
@@ -273,9 +271,7 @@ playlist_list_open_stream_suffix(InputStream &is, const char *suffix)
SongEnumerator *
playlist_list_open_stream(InputStream &is, const char *uri)
{
- const char *suffix;
-
- is.LockWaitReady();
+ assert(is.ready);
const char *const mime = is.GetMimeType();
if (mime != nullptr) {
@@ -284,7 +280,7 @@ playlist_list_open_stream(InputStream &is, const char *uri)
return playlist;
}
- suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
+ const char *suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
if (suffix != nullptr) {
auto playlist = playlist_list_open_stream_suffix(is, suffix);
if (playlist != nullptr)
@@ -321,7 +317,7 @@ playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
return nullptr;
Error error;
- InputStream *is = InputStream::Open(path_fs, mutex, cond, error);
+ InputStream *is = InputStream::OpenReady(path_fs, mutex, cond, error);
if (is == nullptr) {
if (error.IsDefined())
LogError(error);
@@ -329,8 +325,6 @@ playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
return nullptr;
}
- is->LockWaitReady();
-
auto playlist = playlist_list_open_stream_suffix(*is, suffix);
if (playlist != nullptr)
*is_r = is;
diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx
index 481d9bf75..3bc66d16e 100644
--- a/src/PlaylistSave.cxx
+++ b/src/PlaylistSave.cxx
@@ -23,21 +23,21 @@
#include "PlaylistError.hxx"
#include "Playlist.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Mapper.hxx"
#include "Idle.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
+#include "util/Alloc.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <string.h>
void
-playlist_print_song(FILE *file, const Song &song)
+playlist_print_song(FILE *file, const DetachedSong &song)
{
if (playlist_saveAbsolutePaths && song.IsInDatabase()) {
const auto path = map_song_fs(song);
@@ -45,7 +45,7 @@ playlist_print_song(FILE *file, const Song &song)
fprintf(file, "%s\n", path.c_str());
} else {
const auto uri_utf8 = song.GetURI();
- const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8.c_str());
+ const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8);
if (!uri_fs.IsNull())
fprintf(file, "%s\n", uri_fs.c_str());
@@ -56,7 +56,7 @@ void
playlist_print_uri(FILE *file, const char *uri)
{
auto path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- !PathTraits::IsAbsoluteUTF8(uri)
+ !PathTraitsUTF8::IsAbsolute(uri)
? map_uri_fs(uri)
: AllocatedPath::FromUTF8(uri);
@@ -127,7 +127,7 @@ playlist_load_spl(struct playlist &playlist, PlayerControl &pc,
if ((playlist.AppendURI(pc, uri_utf8.c_str())) != PlaylistResult::SUCCESS) {
/* for windows compatibility, convert slashes */
- char *temp2 = g_strdup(uri_utf8.c_str());
+ char *temp2 = xstrdup(uri_utf8.c_str());
char *p = temp2;
while (*p) {
if (*p == '\\')
@@ -139,7 +139,7 @@ playlist_load_spl(struct playlist &playlist, PlayerControl &pc,
FormatError(playlist_domain,
"can't add file \"%s\"", temp2);
- g_free(temp2);
+ free(temp2);
}
}
diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx
index 70b40f3fa..d9126aed2 100644
--- a/src/PlaylistSave.hxx
+++ b/src/PlaylistSave.hxx
@@ -24,14 +24,14 @@
#include <stdio.h>
-struct Song;
struct queue;
struct playlist;
struct PlayerControl;
+class DetachedSong;
class Error;
void
-playlist_print_song(FILE *fp, const Song &song);
+playlist_print_song(FILE *file, const DetachedSong &song);
void
playlist_print_uri(FILE *fp, const char *uri);
diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx
index 60774dc36..2da644bc2 100644
--- a/src/PlaylistSong.cxx
+++ b/src/PlaylistSong.cxx
@@ -24,43 +24,42 @@
#include "DatabaseGlue.hxx"
#include "ls.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
+#include "DetachedSong.hxx"
#include "Song.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
static void
-merge_song_metadata(Song *dest, const Song *base,
- const Song *add)
+merge_song_metadata(DetachedSong *dest, const DetachedSong *base,
+ const DetachedSong *add)
{
- dest->tag = base->tag != nullptr
- ? (add->tag != nullptr
- ? Tag::Merge(*base->tag, *add->tag)
- : new Tag(*base->tag))
- : (add->tag != nullptr
- ? new Tag(*add->tag)
- : nullptr);
-
- dest->mtime = base->mtime;
- dest->start_ms = add->start_ms;
- dest->end_ms = add->end_ms;
+ {
+ TagBuilder builder(add->GetTag());
+ builder.Complement(base->GetTag());
+ dest->SetTag(builder.Commit());
+ }
+
+ dest->SetLastModified(base->GetLastModified());
+ dest->SetStartMS(add->GetStartMS());
+ dest->SetEndMS(add->GetEndMS());
}
-static Song *
-apply_song_metadata(Song *dest, const Song *src)
+static DetachedSong *
+apply_song_metadata(DetachedSong *dest, const DetachedSong *src)
{
- Song *tmp;
+ DetachedSong *tmp;
assert(dest != nullptr);
assert(src != nullptr);
- if (src->tag == nullptr && src->start_ms == 0 && src->end_ms == 0)
+ if (!src->GetTag().IsDefined() &&
+ src->GetStartMS() == 0 && src->GetEndMS() == 0)
return dest;
if (dest->IsInDatabase()) {
@@ -72,37 +71,41 @@ apply_song_metadata(Song *dest, const Song *src)
if (path_utf8.empty())
path_utf8 = path_fs.c_str();
- tmp = Song::NewFile(path_utf8.c_str(), nullptr);
+ tmp = new DetachedSong(std::move(path_utf8));
merge_song_metadata(tmp, dest, src);
} else {
- tmp = Song::NewFile(dest->uri, nullptr);
+ tmp = new DetachedSong(dest->GetURI());
merge_song_metadata(tmp, dest, src);
}
- if (dest->tag != nullptr && dest->tag->time > 0 &&
- src->start_ms > 0 && src->end_ms == 0 &&
- src->start_ms / 1000 < (unsigned)dest->tag->time)
+ if (dest->GetTag().IsDefined() && dest->GetTag().time > 0 &&
+ src->GetStartMS() > 0 && src->GetEndMS() == 0 &&
+ src->GetStartMS() / 1000 < (unsigned)dest->GetTag().time)
/* the range is open-ended, and the playlist plugin
did not know the total length of the song file
(e.g. last track on a CUE file); fix it up here */
- tmp->tag->time = dest->tag->time - src->start_ms / 1000;
+ tmp->WritableTag().time =
+ dest->GetTag().time - src->GetStartMS() / 1000;
- dest->Free();
+ delete dest;
return tmp;
}
-static Song *
-playlist_check_load_song(const Song *song, const char *uri, bool secure)
+static DetachedSong *
+playlist_check_load_song(const DetachedSong *song, const char *uri, bool secure)
{
- Song *dest;
+ DetachedSong *dest;
if (uri_has_scheme(uri)) {
- dest = Song::NewRemote(uri);
- } else if (PathTraits::IsAbsoluteUTF8(uri) && secure) {
- dest = Song::LoadFile(uri, nullptr);
- if (dest == nullptr)
+ dest = new DetachedSong(uri);
+ } else if (PathTraitsUTF8::IsAbsolute(uri) && secure) {
+ Song *tmp = Song::LoadFile(uri, nullptr);
+ if (tmp == nullptr)
return nullptr;
+
+ dest = new DetachedSong(*tmp);
+ delete tmp;
} else {
const Database *db = GetDatabase();
if (db == nullptr)
@@ -113,22 +116,22 @@ playlist_check_load_song(const Song *song, const char *uri, bool secure)
/* not found in database */
return nullptr;
- dest = tmp->DupDetached();
+ dest = new DetachedSong(*tmp);
db->ReturnSong(tmp);
}
return apply_song_metadata(dest, song);
}
-Song *
-playlist_check_translate_song(Song *song, const char *base_uri,
+DetachedSong *
+playlist_check_translate_song(DetachedSong *song, const char *base_uri,
bool secure)
{
if (song->IsInDatabase())
/* already ok */
return song;
- const char *uri = song->uri;
+ const char *uri = song->GetURI();
if (uri_has_scheme(uri)) {
if (uri_supported_scheme(uri))
@@ -136,19 +139,19 @@ playlist_check_translate_song(Song *song, const char *base_uri,
return song;
else {
/* unsupported remote song */
- song->Free();
+ delete song;
return nullptr;
}
}
if (base_uri != nullptr && strcmp(base_uri, ".") == 0)
- /* PathTraits::GetParentUTF8() returns "." when there
+ /* PathTraitsUTF8::GetParent() returns "." when there
is no directory name in the given path; clear that
now, because it would break the database lookup
functions */
base_uri = nullptr;
- if (PathTraits::IsAbsoluteUTF8(uri)) {
+ if (PathTraitsUTF8::IsAbsolute(uri)) {
/* XXX fs_charset vs utf8? */
const char *suffix = map_to_relative_path(uri);
assert(suffix != nullptr);
@@ -158,19 +161,21 @@ playlist_check_translate_song(Song *song, const char *base_uri,
else if (!secure) {
/* local files must be relative to the music
directory when "secure" is enabled */
- song->Free();
+ delete song;
return nullptr;
}
base_uri = nullptr;
}
- char *allocated = nullptr;
- if (base_uri != nullptr)
- uri = allocated = g_build_filename(base_uri, uri, nullptr);
+ std::string full_uri;
+ if (base_uri != nullptr) {
+ full_uri = PathTraitsUTF8::Build(base_uri, uri);
+ uri = full_uri.c_str();
+ }
+
+ DetachedSong *dest = playlist_check_load_song(song, uri, secure);
+ delete song;
- Song *dest = playlist_check_load_song(song, uri, secure);
- song->Free();
- g_free(allocated);
return dest;
}
diff --git a/src/PlaylistSong.hxx b/src/PlaylistSong.hxx
index d0db99868..9cfb09b24 100644
--- a/src/PlaylistSong.hxx
+++ b/src/PlaylistSong.hxx
@@ -20,7 +20,7 @@
#ifndef MPD_PLAYLIST_SONG_HXX
#define MPD_PLAYLIST_SONG_HXX
-struct Song;
+class DetachedSong;
/**
* Verifies the song, returns nullptr if it is unsafe. Translate the
@@ -30,8 +30,8 @@ struct Song;
* @param secure if true, then local files are only allowed if they
* are relative to base_uri
*/
-Song *
-playlist_check_translate_song(Song *song, const char *base_uri,
+DetachedSong *
+playlist_check_translate_song(DetachedSong *song, const char *base_uri,
bool secure);
#endif
diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx
index ac2deebbf..bb776dce7 100644
--- a/src/PlaylistState.cxx
+++ b/src/PlaylistState.cxx
@@ -27,16 +27,15 @@
#include "PlaylistError.hxx"
#include "Playlist.hxx"
#include "QueueSave.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "PlayerControl.hxx"
#include "ConfigGlobal.hxx"
#include "ConfigOption.hxx"
#include "fs/Limits.hxx"
#include "util/CharUtil.hxx"
+#include "util/StringUtil.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <string.h>
#include <stdlib.h>
@@ -112,7 +111,7 @@ playlist_state_load(TextFile &file, struct playlist &playlist)
return;
}
- while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
+ while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
queue_load_song(file, line, playlist.queue);
line = file.ReadLine();
@@ -135,7 +134,7 @@ playlist_state_restore(const char *line, TextFile &file,
int seek_time = 0;
bool random_mode = false;
- if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
+ if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE))
return false;
line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
@@ -149,40 +148,40 @@ playlist_state_restore(const char *line, TextFile &file,
state = PlayerState::STOP;
while ((line = file.ReadLine()) != nullptr) {
- if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) {
+ if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)]));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) {
playlist.SetRepeat(pc,
strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0);
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) {
playlist.SetSingle(pc,
strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0);
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) {
playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
"1") == 0);
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) {
pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY);
/* this check discards "nan" which was used
prior to MPD 0.18 */
if (IsDigitASCII(*p))
pc.SetMixRampDelay(atof(p));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM),
"1") == 0;
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) {
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) {
current = atoi(&(line
[strlen
(PLAYLIST_STATE_FILE_CURRENT)]));
- } else if (g_str_has_prefix(line,
+ } else if (StringStartsWith(line,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
playlist_state_load(file, playlist);
}
diff --git a/src/PlaylistState.hxx b/src/PlaylistState.hxx
index 01cc94d03..408113e49 100644
--- a/src/PlaylistState.hxx
+++ b/src/PlaylistState.hxx
@@ -32,12 +32,12 @@ struct PlayerControl;
class TextFile;
void
-playlist_state_save(FILE *fp, const struct playlist &playlist,
+playlist_state_save(FILE *fp, const playlist &playlist,
PlayerControl &pc);
bool
playlist_state_restore(const char *line, TextFile &file,
- struct playlist &playlist, PlayerControl &pc);
+ playlist &playlist, PlayerControl &pc);
/**
* Generates a hash number for the current state of the playlist and
@@ -46,7 +46,7 @@ playlist_state_restore(const char *line, TextFile &file,
* be saved.
*/
unsigned
-playlist_state_get_hash(const struct playlist &playlist,
+playlist_state_get_hash(const playlist &playlist,
PlayerControl &c);
#endif
diff --git a/src/PlaylistTag.cxx b/src/PlaylistTag.cxx
new file mode 100644
index 000000000..672111ea5
--- /dev/null
+++ b/src/PlaylistTag.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2013 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.
+ */
+
+/*
+ * Functions for editing the playlist (adding, removing, reordering
+ * songs in the queue).
+ *
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "PlaylistError.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+
+bool
+playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value,
+ Error &error)
+{
+ const int position = queue.IdToPosition(id);
+ if (position < 0) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No such song");
+ return false;
+ }
+
+ DetachedSong &song = queue.Get(position);
+ if (song.IsFile()) {
+ error.Set(playlist_domain, int(PlaylistResult::DENIED),
+ "Cannot edit tags of local file");
+ return false;
+ }
+
+ {
+ TagBuilder tag(std::move(song.WritableTag()));
+ tag.AddItem(tag_type, value);
+ song.SetTag(tag.Commit());
+ }
+
+ queue.ModifyAtPosition(position);
+ OnModified();
+ return true;
+}
+
+bool
+playlist::ClearSongIdTag(unsigned id, TagType tag_type,
+ Error &error)
+{
+ const int position = queue.IdToPosition(id);
+ if (position < 0) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No such song");
+ return false;
+ }
+
+ DetachedSong &song = queue.Get(position);
+ if (song.IsFile()) {
+ error.Set(playlist_domain, int(PlaylistResult::DENIED),
+ "Cannot edit tags of local file");
+ return false;
+ }
+
+ {
+ TagBuilder tag(std::move(song.WritableTag()));
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES)
+ tag.RemoveAll();
+ else
+ tag.RemoveType(tag_type);
+ song.SetTag(tag.Commit());
+ }
+
+ queue.ModifyAtPosition(position);
+ OnModified();
+ return true;
+}
diff --git a/src/PlaylistUpdate.cxx b/src/PlaylistUpdate.cxx
index 0e72ef671..91adc01b7 100644
--- a/src/PlaylistUpdate.cxx
+++ b/src/PlaylistUpdate.cxx
@@ -22,35 +22,36 @@
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "util/Error.hxx"
static bool
-UpdatePlaylistSong(const Database &db, Song &song)
+UpdatePlaylistSong(const Database &db, DetachedSong &song)
{
- if (!song.IsInDatabase() || !song.IsDetached())
+ if (!song.IsInDatabase())
/* only update Songs instances that are "detached"
from the Database */
return false;
- Song *original = db.GetSong(song.uri, IgnoreError());
+ Song *original = db.GetSong(song.GetURI(), IgnoreError());
if (original == nullptr)
/* not found - shouldn't happen, because the update
thread should ensure that all stale Song instances
have been purged */
return false;
- if (original->mtime == song.mtime) {
+ if (original->mtime == song.GetLastModified()) {
/* not modified */
db.ReturnSong(original);
return false;
}
- song.mtime = original->mtime;
+ song.SetLastModified(original->mtime);
if (original->tag != nullptr)
- song.ReplaceTag(Tag(*original->tag));
+ song.SetTag(*original->tag);
db.ReturnSong(original);
return true;
diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx
index 5bb0cdf64..701edd896 100644
--- a/src/PlaylistVector.cxx
+++ b/src/PlaylistVector.cxx
@@ -24,7 +24,6 @@
#include <algorithm>
#include <assert.h>
-#include <string.h>
PlaylistVector::iterator
PlaylistVector::find(const char *name)
diff --git a/src/Queue.cxx b/src/Queue.cxx
index 451609438..d4d6531f9 100644
--- a/src/Queue.cxx
+++ b/src/Queue.cxx
@@ -19,9 +19,7 @@
#include "config.h"
#include "Queue.hxx"
-#include "Song.hxx"
-
-#include <stdlib.h>
+#include "DetachedSong.hxx"
queue::queue(unsigned _max_length)
:max_length(_max_length), length(0),
@@ -86,7 +84,7 @@ queue::ModifyAtOrder(unsigned _order)
}
unsigned
-queue::Append(Song *song, uint8_t priority)
+queue::Append(DetachedSong &&song, uint8_t priority)
{
assert(!IsFull());
@@ -94,7 +92,7 @@ queue::Append(Song *song, uint8_t priority)
const unsigned id = id_table.Insert(position);
auto &item = items[position];
- item.song = song->DupDetached();
+ item.song = new DetachedSong(std::move(song));
item.id = id;
item.version = version;
item.priority = priority;
@@ -221,11 +219,7 @@ queue::DeletePosition(unsigned position)
{
assert(position < length);
- {
- Song &song = Get(position);
- assert(!song.IsInDatabase() || song.IsDetached());
- song.Free();
- }
+ delete items[position].song;
const unsigned id = PositionToId(position);
const unsigned _order = PositionToOrder(position);
@@ -259,9 +253,7 @@ queue::Clear()
for (unsigned i = 0; i < length; i++) {
Item *item = &items[i];
- assert(!item->song->IsInDatabase() ||
- item->song->IsDetached());
- item->song->Free();
+ delete item->song;
id_table.Erase(item->id);
}
diff --git a/src/Queue.hxx b/src/Queue.hxx
index da90d4a3d..8f893dbfa 100644
--- a/src/Queue.hxx
+++ b/src/Queue.hxx
@@ -29,7 +29,7 @@
#include <assert.h>
#include <stdint.h>
-struct Song;
+class DetachedSong;
/**
* A queue of songs. This is the backend of the playlist: it contains
@@ -53,7 +53,7 @@ struct queue {
* information attached.
*/
struct Item {
- Song *song;
+ DetachedSong *song;
/** the unique id of this item in the queue */
unsigned id;
@@ -200,7 +200,7 @@ struct queue {
/**
* Returns the song at the specified position.
*/
- Song &Get(unsigned position) const {
+ DetachedSong &Get(unsigned position) const {
assert(position < length);
return *items[position].song;
@@ -209,7 +209,7 @@ struct queue {
/**
* Returns the song at the specified order number.
*/
- Song &GetOrder(unsigned _order) const {
+ DetachedSong &GetOrder(unsigned _order) const {
return Get(OrderToPosition(_order));
}
@@ -268,7 +268,7 @@ struct queue {
*
* @param priority the priority of this new queue item
*/
- unsigned Append(Song *song, uint8_t priority);
+ unsigned Append(DetachedSong &&song, uint8_t priority);
/**
* Swaps two songs, addressed by their position.
diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx
index d5651cde0..ab00331b0 100644
--- a/src/QueuePrint.cxx
+++ b/src/QueuePrint.cxx
@@ -22,13 +22,8 @@
#include "Queue.hxx"
#include "SongFilter.hxx"
#include "SongPrint.hxx"
-#include "Mapper.hxx"
#include "Client.hxx"
-extern "C" {
-#include "Song.hxx"
-}
-
/**
* Send detailed information about a range of songs in the queue to a
* client.
@@ -99,7 +94,7 @@ queue_find(Client &client, const queue &queue,
const SongFilter &filter)
{
for (unsigned i = 0; i < queue.GetLength(); i++) {
- const Song &song = queue.Get(i);
+ const DetachedSong &song = queue.Get(i);
if (filter.Match(song))
queue_print_song_info(client, queue, i);
diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx
index 6a1a51992..b84c051bb 100644
--- a/src/QueueSave.cxx
+++ b/src/QueueSave.cxx
@@ -21,37 +21,35 @@
#include "QueueSave.hxx"
#include "Queue.hxx"
#include "PlaylistError.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "SongSave.hxx"
#include "DatabasePlugin.hxx"
#include "DatabaseGlue.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
+#include "util/StringUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "fs/Traits.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <stdlib.h>
#define PRIO_LABEL "Prio: "
static void
-queue_save_database_song(FILE *fp, int idx, const Song &song)
+queue_save_database_song(FILE *fp, int idx, const DetachedSong &song)
{
- const auto uri = song.GetURI();
- fprintf(fp, "%i:%s\n", idx, uri.c_str());
+ fprintf(fp, "%i:%s\n", idx, song.GetURI());
}
static void
-queue_save_full_song(FILE *fp, const Song &song)
+queue_save_full_song(FILE *fp, const DetachedSong &song)
{
song_save(fp, song);
}
static void
-queue_save_song(FILE *fp, int idx, const Song &song)
+queue_save_song(FILE *fp, int idx, const DetachedSong &song)
{
if (song.IsInDatabase())
queue_save_database_song(fp, idx, song);
@@ -78,7 +76,7 @@ queue_load_song(TextFile &file, const char *line, queue &queue)
return;
uint8_t priority = 0;
- if (g_str_has_prefix(line, PRIO_LABEL)) {
+ if (StringStartsWith(line, PRIO_LABEL)) {
priority = strtoul(line + sizeof(PRIO_LABEL) - 1, nullptr, 10);
line = file.ReadLine();
@@ -86,16 +84,15 @@ queue_load_song(TextFile &file, const char *line, queue &queue)
return;
}
- const Database *db = nullptr;
- Song *song;
+ DetachedSong *song;
- if (g_str_has_prefix(line, SONG_BEGIN)) {
+ if (StringStartsWith(line, SONG_BEGIN)) {
const char *uri = line + sizeof(SONG_BEGIN) - 1;
- if (!uri_has_scheme(uri) && !PathTraits::IsAbsoluteUTF8(uri))
+ if (!uri_has_scheme(uri) && !PathTraitsUTF8::IsAbsolute(uri))
return;
Error error;
- song = song_load(file, nullptr, uri, error);
+ song = song_load(file, uri, error);
if (song == nullptr) {
LogError(error);
return;
@@ -112,22 +109,21 @@ queue_load_song(TextFile &file, const char *line, queue &queue)
const char *uri = endptr + 1;
if (uri_has_scheme(uri)) {
- song = Song::NewRemote(uri);
+ song = new DetachedSong(uri);
} else {
- db = GetDatabase();
+ const Database *db = GetDatabase();
if (db == nullptr)
return;
- song = db->GetSong(uri, IgnoreError());
- if (song == nullptr)
+ Song *tmp = db->GetSong(uri, IgnoreError());
+ if (tmp == nullptr)
return;
+
+ song = new DetachedSong(*tmp);
+ db->ReturnSong(tmp);
}
}
- queue.Append(song, priority);
-
- if (db != nullptr)
- db->ReturnSong(song);
- else
- song->Free();
+ queue.Append(std::move(*song), priority);
+ delete song;
}
diff --git a/src/ReplayGainConfig.cxx b/src/ReplayGainConfig.cxx
index 8f9b0d3f7..00ae5787f 100644
--- a/src/ReplayGainConfig.cxx
+++ b/src/ReplayGainConfig.cxx
@@ -22,12 +22,12 @@
#include "Idle.hxx"
#include "ConfigData.hxx"
#include "ConfigGlobal.hxx"
-#include "Playlist.hxx"
#include "system/FatalError.hxx"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
+#include <math.h>
ReplayGainMode replay_gain_mode = REPLAY_GAIN_OFF;
diff --git a/src/Song.cxx b/src/Song.cxx
index 6213d5e66..bb74a6951 100644
--- a/src/Song.cxx
+++ b/src/Song.cxx
@@ -21,13 +21,12 @@
#include "Song.hxx"
#include "Directory.hxx"
#include "tag/Tag.hxx"
-
-#include <glib.h>
+#include "util/Alloc.hxx"
+#include "DetachedSong.hxx"
#include <assert.h>
#include <string.h>
-
-Directory detached_root;
+#include <stdlib.h>
static Song *
song_alloc(const char *uri, Directory *parent)
@@ -39,7 +38,7 @@ song_alloc(const char *uri, Directory *parent)
assert(uri_length);
Song *song = (Song *)
- g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
+ xalloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
song->tag = nullptr;
memcpy(song->uri, uri, uri_length + 1);
@@ -51,104 +50,27 @@ song_alloc(const char *uri, Directory *parent)
}
Song *
-Song::NewRemote(const char *uri)
+Song::NewFrom(DetachedSong &&other, Directory *parent)
{
- return song_alloc(uri, nullptr);
+ Song *song = song_alloc(other.GetURI(), parent);
+ song->tag = new Tag(std::move(other.WritableTag()));
+ song->mtime = other.GetLastModified();
+ song->start_ms = other.GetStartMS();
+ song->end_ms = other.GetEndMS();
+ return song;
}
Song *
Song::NewFile(const char *path, Directory *parent)
{
- assert((parent == nullptr) == (*path == '/'));
-
return song_alloc(path, parent);
}
-Song *
-Song::ReplaceURI(const char *new_uri)
-{
- Song *new_song = song_alloc(new_uri, parent);
- new_song->tag = tag;
- new_song->mtime = mtime;
- new_song->start_ms = start_ms;
- new_song->end_ms = end_ms;
- g_free(this);
- return new_song;
-}
-
-Song *
-Song::NewDetached(const char *uri)
-{
- assert(uri != nullptr);
-
- return song_alloc(uri, &detached_root);
-}
-
-Song *
-Song::DupDetached() const
-{
- Song *song;
- if (IsInDatabase()) {
- const auto new_uri = GetURI();
- song = NewDetached(new_uri.c_str());
- } else
- song = song_alloc(uri, nullptr);
-
- song->tag = tag != nullptr ? new Tag(*tag) : nullptr;
- song->mtime = mtime;
- song->start_ms = start_ms;
- song->end_ms = end_ms;
-
- return song;
-}
-
void
Song::Free()
{
delete tag;
- g_free(this);
-}
-
-void
-Song::ReplaceTag(Tag &&_tag)
-{
- if (tag == nullptr)
- tag = new Tag();
- *tag = std::move(_tag);
-}
-
-gcc_pure
-static inline bool
-directory_equals(const Directory &a, const Directory &b)
-{
- return strcmp(a.path, b.path) == 0;
-}
-
-gcc_pure
-static inline bool
-directory_is_same(const Directory *a, const Directory *b)
-{
- return a == b ||
- (a != nullptr && b != nullptr &&
- directory_equals(*a, *b));
-
-}
-
-bool
-SongEquals(const Song &a, const Song &b)
-{
- if (a.parent != nullptr && b.parent != nullptr &&
- !directory_equals(*a.parent, *b.parent) &&
- (a.parent == &detached_root || b.parent == &detached_root)) {
- /* must compare the full URI if one of the objects is
- "detached" */
- const auto au = a.GetURI();
- const auto bu = b.GetURI();
- return au == bu;
- }
-
- return directory_is_same(a.parent, b.parent) &&
- strcmp(a.uri, b.uri) == 0;
+ free(this);
}
std::string
@@ -156,7 +78,7 @@ Song::GetURI() const
{
assert(*uri);
- if (!IsInDatabase() || parent->IsRoot())
+ if (parent == nullptr || parent->IsRoot())
return std::string(uri);
else {
const char *path = parent->GetPath();
diff --git a/src/Song.hxx b/src/Song.hxx
index b74690e77..c9719edd7 100644
--- a/src/Song.hxx
+++ b/src/Song.hxx
@@ -26,19 +26,18 @@
#include <string>
#include <assert.h>
-#include <sys/time.h>
+#include <time.h>
#define SONG_FILE "file: "
#define SONG_TIME "Time: "
struct Tag;
+struct Directory;
+class DetachedSong;
/**
- * A dummy #directory instance that is used for "detached" song
- * copies.
+ * A song file inside the configured music directory.
*/
-extern struct Directory detached_root;
-
struct Song {
/**
* Pointers to the siblings of this directory within the
@@ -51,7 +50,14 @@ struct Song {
struct list_head siblings;
Tag *tag;
+
+ /**
+ * The #Directory that contains this song. May be nullptr if
+ * the current database plugin does not manage the parent
+ * directory this way.
+ */
Directory *parent;
+
time_t mtime;
/**
@@ -65,11 +71,14 @@ struct Song {
*/
unsigned end_ms;
+ /**
+ * The file name. If #parent is nullptr, then this is the URI
+ * relative to the music directory.
+ */
char uri[sizeof(int)];
- /** allocate a new song with a remote URL */
gcc_malloc
- static Song *NewRemote(const char *uri);
+ static Song *NewFrom(DetachedSong &&other, Directory *parent);
/** allocate a new song with a local file name */
gcc_malloc
@@ -87,47 +96,8 @@ struct Song {
return LoadFile(path_utf8, &parent);
}
- /**
- * Replaces the URI of a song object. The given song object
- * is destroyed, and a newly allocated one is returned. It
- * does not update the reference within the parent directory;
- * the caller is responsible for doing that.
- */
- gcc_malloc
- Song *ReplaceURI(const char *uri);
-
- /**
- * Creates a "detached" song object.
- */
- gcc_malloc
- static Song *NewDetached(const char *uri);
-
- /**
- * Creates a duplicate of the song object. If the object is
- * in the database, it creates a "detached" copy of this song,
- * see Song::IsDetached().
- */
- gcc_malloc
- Song *DupDetached() const;
-
void Free();
- bool IsInDatabase() const {
- return parent != nullptr;
- }
-
- bool IsFile() const {
- return IsInDatabase() || uri[0] == '/';
- }
-
- bool IsDetached() const {
- assert(IsInDatabase());
-
- return parent == &detached_root;
- }
-
- void ReplaceTag(Tag &&tag);
-
bool UpdateFile();
bool UpdateFileInArchive();
@@ -142,11 +112,4 @@ struct Song {
double GetDuration() const;
};
-/**
- * Returns true if both objects refer to the same physical song.
- */
-gcc_pure
-bool
-SongEquals(const Song &a, const Song &b);
-
#endif
diff --git a/src/SongEnumerator.hxx b/src/SongEnumerator.hxx
index 0e268a31a..2515647d1 100644
--- a/src/SongEnumerator.hxx
+++ b/src/SongEnumerator.hxx
@@ -20,7 +20,7 @@
#ifndef MPD_SONG_ENUMERATOR_HXX
#define MPD_SONG_ENUMERATOR_HXX
-struct Song;
+class DetachedSong;
/**
* An object which provides serial access to a number of #Song
@@ -35,7 +35,7 @@ public:
* freeing the returned #Song object. Returns nullptr if
* there are no more songs.
*/
- virtual Song *NextSong() = 0;
+ virtual DetachedSong *NextSong() = 0;
};
#endif
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
index 8d90c5fc8..53ab784c2 100644
--- a/src/SongFilter.cxx
+++ b/src/SongFilter.cxx
@@ -20,6 +20,7 @@
#include "config.h"
#include "SongFilter.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "util/ASCII.hxx"
#include "util/UriUtil.hxx"
@@ -151,6 +152,18 @@ SongFilter::Item::Match(const Song &song) const
return song.tag != NULL && Match(*song.tag);
}
+bool
+SongFilter::Item::Match(const DetachedSong &song) const
+{
+ if (tag == LOCATE_TAG_BASE_TYPE)
+ return uri_is_child_or_same(value.c_str(), song.GetURI());
+
+ if (tag == LOCATE_TAG_FILE_TYPE)
+ return StringMatch(song.GetURI());
+
+ return Match(song.GetTag());
+}
+
SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
{
items.push_back(Item(tag, value, fold_case));
@@ -203,6 +216,16 @@ SongFilter::Match(const Song &song) const
return true;
}
+bool
+SongFilter::Match(const DetachedSong &song) const
+{
+ for (const auto &i : items)
+ if (!i.Match(song))
+ return false;
+
+ return true;
+}
+
std::string
SongFilter::GetBase() const
{
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
index b15127c07..16970c350 100644
--- a/src/SongFilter.hxx
+++ b/src/SongFilter.hxx
@@ -38,6 +38,7 @@
struct Tag;
struct TagItem;
struct Song;
+class DetachedSong;
class SongFilter {
public:
@@ -80,6 +81,9 @@ public:
gcc_pure
bool Match(const Song &song) const;
+
+ gcc_pure
+ bool Match(const DetachedSong &song) const;
};
private:
@@ -105,6 +109,9 @@ public:
gcc_pure
bool Match(const Song &song) const;
+ gcc_pure
+ bool Match(const DetachedSong &song) const;
+
const std::list<Item> &GetItems() const {
return items;
}
diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx
index ea164d02b..b3e0c7b58 100644
--- a/src/SongPrint.cxx
+++ b/src/SongPrint.cxx
@@ -20,6 +20,7 @@
#include "config.h"
#include "SongPrint.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Directory.hxx"
#include "TimePrint.hxx"
#include "TagPrint.hxx"
@@ -27,21 +28,31 @@
#include "Client.hxx"
#include "util/UriUtil.hxx"
+static void
+song_print_uri(Client &client, const char *uri)
+{
+ const std::string allocated = uri_remove_auth(uri);
+ if (!allocated.empty())
+ uri = allocated.c_str();
+
+ client_printf(client, "%s%s\n", SONG_FILE,
+ map_to_relative_path(uri));
+}
+
void
song_print_uri(Client &client, const Song &song)
{
- if (song.IsInDatabase() && !song.parent->IsRoot()) {
+ if (song.parent != nullptr && !song.parent->IsRoot()) {
client_printf(client, "%s%s/%s\n", SONG_FILE,
song.parent->GetPath(), song.uri);
- } else {
- const char *uri = song.uri;
- const std::string allocated = uri_remove_auth(uri);
- if (!allocated.empty())
- uri = allocated.c_str();
+ } else
+ song_print_uri(client, song.uri);
+}
- client_printf(client, "%s%s\n", SONG_FILE,
- map_to_relative_path(uri));
- }
+void
+song_print_uri(Client &client, const DetachedSong &song)
+{
+ song_print_uri(client, song.GetURI());
}
void
@@ -66,3 +77,28 @@ song_print_info(Client &client, const Song &song)
if (song.tag != nullptr)
tag_print(client, *song.tag);
}
+
+void
+song_print_info(Client &client, const DetachedSong &song)
+{
+ song_print_uri(client, song);
+
+ const unsigned start_ms = song.GetStartMS();
+ const unsigned end_ms = song.GetEndMS();
+
+ if (end_ms > 0)
+ client_printf(client, "Range: %u.%03u-%u.%03u\n",
+ start_ms / 1000,
+ start_ms % 1000,
+ end_ms / 1000,
+ end_ms % 1000);
+ else if (start_ms > 0)
+ client_printf(client, "Range: %u.%03u-\n",
+ start_ms / 1000,
+ start_ms % 1000);
+
+ if (song.GetLastModified() > 0)
+ time_print(client, "Last-Modified", song.GetLastModified());
+
+ tag_print(client, song.GetTag());
+}
diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx
index f8df89d38..5779af4c3 100644
--- a/src/SongPrint.hxx
+++ b/src/SongPrint.hxx
@@ -21,12 +21,19 @@
#define MPD_SONG_PRINT_HXX
struct Song;
+class DetachedSong;
class Client;
void
+song_print_info(Client &client, const DetachedSong &song);
+
+void
song_print_info(Client &client, const Song &song);
void
song_print_uri(Client &client, const Song &song);
+void
+song_print_uri(Client &client, const DetachedSong &song);
+
#endif
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index 63e279a16..d524f327e 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -20,9 +20,9 @@
#include "config.h"
#include "SongSave.hxx"
#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "TagSave.hxx"
-#include "Directory.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
@@ -37,15 +37,21 @@
static constexpr Domain song_save_domain("song_save");
+static void
+range_save(FILE *file, unsigned start_ms, unsigned end_ms)
+{
+ if (end_ms > 0)
+ fprintf(file, "Range: %u-%u\n", start_ms, end_ms);
+ else if (start_ms > 0)
+ fprintf(file, "Range: %u-\n", start_ms);
+}
+
void
song_save(FILE *fp, const Song &song)
{
fprintf(fp, SONG_BEGIN "%s\n", song.uri);
- if (song.end_ms > 0)
- fprintf(fp, "Range: %u-%u\n", song.start_ms, song.end_ms);
- else if (song.start_ms > 0)
- fprintf(fp, "Range: %u-\n", song.start_ms);
+ range_save(fp, song.start_ms, song.end_ms);
if (song.tag != nullptr)
tag_save(fp, *song.tag);
@@ -54,24 +60,33 @@ song_save(FILE *fp, const Song &song)
fprintf(fp, SONG_END "\n");
}
-Song *
-song_load(TextFile &file, Directory *parent, const char *uri,
+void
+song_save(FILE *fp, const DetachedSong &song)
+{
+ fprintf(fp, SONG_BEGIN "%s\n", song.GetURI());
+
+ range_save(fp, song.GetStartMS(), song.GetEndMS());
+
+ tag_save(fp, song.GetTag());
+
+ fprintf(fp, SONG_MTIME ": %li\n", (long)song.GetLastModified());
+ fprintf(fp, SONG_END "\n");
+}
+
+DetachedSong *
+song_load(TextFile &file, const char *uri,
Error &error)
{
- Song *song = parent != nullptr
- ? Song::NewFile(uri, parent)
- : Song::NewRemote(uri);
- char *line, *colon;
- TagType type;
- const char *value;
+ DetachedSong *song = new DetachedSong(uri);
TagBuilder tag;
+ char *line;
while ((line = file.ReadLine()) != nullptr &&
strcmp(line, SONG_END) != 0) {
- colon = strchr(line, ':');
+ char *colon = strchr(line, ':');
if (colon == nullptr || colon == line) {
- song->Free();
+ delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
@@ -79,8 +94,9 @@ song_load(TextFile &file, Directory *parent, const char *uri,
}
*colon++ = 0;
- value = strchug_fast(colon);
+ const char *value = strchug_fast(colon);
+ TagType type;
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) {
@@ -88,15 +104,19 @@ song_load(TextFile &file, Directory *parent, const char *uri,
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {
- song->mtime = atoi(value);
+ song->SetLastModified(atoi(value));
} else if (strcmp(line, "Range") == 0) {
char *endptr;
- song->start_ms = strtoul(value, &endptr, 10);
- if (*endptr == '-')
- song->end_ms = strtoul(endptr + 1, nullptr, 10);
+ unsigned start_ms = strtoul(value, &endptr, 10);
+ unsigned end_ms = *endptr == '-'
+ ? strtoul(endptr + 1, nullptr, 10)
+ : 0;
+
+ song->SetStartMS(start_ms);
+ song->SetEndMS(end_ms);
} else {
- song->Free();
+ delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
@@ -104,8 +124,6 @@ song_load(TextFile &file, Directory *parent, const char *uri,
}
}
- if (tag.IsDefined())
- song->tag = tag.Commit();
-
+ song->SetTag(tag.Commit());
return song;
}
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
index 40fb4abf7..6b458679a 100644
--- a/src/SongSave.hxx
+++ b/src/SongSave.hxx
@@ -26,12 +26,16 @@
struct Song;
struct Directory;
+class DetachedSong;
class TextFile;
class Error;
void
song_save(FILE *fp, const Song &song);
+void
+song_save(FILE *fp, const DetachedSong &song);
+
/**
* Loads a song from the input file. Reading stops after the
* "song_end" line.
@@ -39,8 +43,8 @@ song_save(FILE *fp, const Song &song);
* @param error location to store the error occurring
* @return true on success, false on error
*/
-Song *
-song_load(TextFile &file, Directory *parent, const char *uri,
+DetachedSong *
+song_load(TextFile &file, const char *uri,
Error &error);
#endif
diff --git a/src/SongSort.cxx b/src/SongSort.cxx
index 4d422657a..1c2d01080 100644
--- a/src/SongSort.cxx
+++ b/src/SongSort.cxx
@@ -20,7 +20,6 @@
#include "config.h"
#include "SongSort.hxx"
#include "Song.hxx"
-#include "util/list.h"
#include "tag/Tag.hxx"
extern "C" {
@@ -29,7 +28,6 @@ extern "C" {
#include <glib.h>
-#include <assert.h>
#include <stdlib.h>
static const char *
diff --git a/src/SongSort.hxx b/src/SongSort.hxx
index b3b67b0c0..45ab3dae9 100644
--- a/src/SongSort.hxx
+++ b/src/SongSort.hxx
@@ -23,6 +23,6 @@
struct list_head;
void
-song_list_sort(struct list_head *songs);
+song_list_sort(list_head *songs);
#endif
diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx
index a0c4d3585..b17820fd5 100644
--- a/src/SongSticker.cxx
+++ b/src/SongSticker.cxx
@@ -29,53 +29,38 @@
#include <string.h>
std::string
-sticker_song_get_value(const Song *song, const char *name)
+sticker_song_get_value(const Song &song, const char *name)
{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
+ const auto uri = song.GetURI();
return sticker_load_value("song", uri.c_str(), name);
}
bool
-sticker_song_set_value(const Song *song,
+sticker_song_set_value(const Song &song,
const char *name, const char *value)
{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
+ const auto uri = song.GetURI();
return sticker_store_value("song", uri.c_str(), name, value);
}
bool
-sticker_song_delete(const Song *song)
+sticker_song_delete(const Song &song)
{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
+ const auto uri = song.GetURI();
return sticker_delete("song", uri.c_str());
}
bool
-sticker_song_delete_value(const Song *song, const char *name)
+sticker_song_delete_value(const Song &song, const char *name)
{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
+ const auto uri = song.GetURI();
return sticker_delete_value("song", uri.c_str(), name);
}
struct sticker *
-sticker_song_get(const Song *song)
+sticker_song_get(const Song &song)
{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
+ const auto uri = song.GetURI();
return sticker_load("song", uri.c_str());
}
diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx
index 0923f0c3a..a5de90517 100644
--- a/src/SongSticker.hxx
+++ b/src/SongSticker.hxx
@@ -34,28 +34,28 @@ struct sticker;
*/
gcc_pure
std::string
-sticker_song_get_value(const Song *song, const char *name);
+sticker_song_get_value(const Song &song, const char *name);
/**
* Sets a sticker value in the specified song. Overwrites existing
* values.
*/
bool
-sticker_song_set_value(const Song *song,
+sticker_song_set_value(const Song &song,
const char *name, const char *value);
/**
* Deletes a sticker from the database. All values are deleted.
*/
bool
-sticker_song_delete(const Song *song);
+sticker_song_delete(const Song &song);
/**
* Deletes a sticker value. Does nothing if the sticker did not
* exist.
*/
bool
-sticker_song_delete_value(const Song *song, const char *name);
+sticker_song_delete_value(const Song &song, const char *name);
/**
* Loads the sticker for the specified song.
@@ -63,8 +63,8 @@ sticker_song_delete_value(const Song *song, const char *name);
* @param song the song object
* @return a sticker object, or NULL on error or if there is no sticker
*/
-struct sticker *
-sticker_song_get(const Song *song);
+sticker *
+sticker_song_get(const Song &song);
/**
* Finds stickers with the specified name below the specified
diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx
index 1a873fedc..cba3c6957 100644
--- a/src/SongUpdate.cxx
+++ b/src/SongUpdate.cxx
@@ -20,14 +20,11 @@
#include "config.h" /* must be first for large file support */
#include "Song.hxx"
#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
#include "Directory.hxx"
#include "Mapper.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
-#include "InputStream.hxx"
-#include "DecoderPlugin.hxx"
#include "DecoderList.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
@@ -35,13 +32,11 @@
#include "tag/TagId3.hxx"
#include "tag/ApeTag.hxx"
#include "TagFile.hxx"
-#include "thread/Cond.hxx"
+#include "TagStream.hxx"
#include <assert.h>
#include <string.h>
-#include <sys/types.h>
#include <sys/stat.h>
-#include <stdio.h>
Song *
Song::LoadFile(const char *path_utf8, Directory *parent)
@@ -49,7 +44,7 @@ Song::LoadFile(const char *path_utf8, Directory *parent)
Song *song;
bool ret;
- assert((parent == nullptr) == PathTraits::IsAbsoluteUTF8(path_utf8));
+ assert((parent == nullptr) == PathTraitsUTF8::IsAbsolute(path_utf8));
assert(!uri_has_scheme(path_utf8));
assert(strchr(path_utf8, '\n') == nullptr);
@@ -83,8 +78,6 @@ tag_scan_fallback(Path path,
bool
Song::UpdateFile()
{
- assert(IsFile());
-
const auto path_fs = map_song_fs(*this);
if (path_fs.IsNull())
return false;
@@ -95,7 +88,7 @@ Song::UpdateFile()
TagBuilder tag_builder;
if (!tag_file_scan(path_fs,
- &full_tag_handler, &tag_builder))
+ full_tag_handler, &tag_builder))
return false;
if (tag_builder.IsEmpty())
@@ -105,34 +98,31 @@ Song::UpdateFile()
mtime = st.st_mtime;
delete tag;
- tag = tag_builder.Commit();
+ tag = tag_builder.CommitNew();
return true;
}
bool
Song::UpdateFileInArchive()
{
- const char *suffix;
- const struct DecoderPlugin *plugin;
-
- assert(IsFile());
-
/* check if there's a suffix and a plugin */
- suffix = uri_get_suffix(uri);
+ const char *suffix = uri_get_suffix(uri);
if (suffix == nullptr)
return false;
- plugin = decoder_plugin_from_suffix(suffix, nullptr);
- if (plugin == nullptr)
+ if (!decoder_plugins_supports_suffix(suffix))
return false;
- delete tag;
+ const auto path_fs = map_song_fs(*this);
+ if (path_fs.IsNull())
+ return false;
- //accept every file that has music suffix
- //because we don't support tag reading through
- //input streams
- tag = new Tag();
+ TagBuilder tag_builder;
+ if (!tag_stream_scan(path_fs.c_str(), full_tag_handler, &tag_builder))
+ return false;
+ delete tag;
+ tag = tag_builder.CommitNew();
return true;
}
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index 75cef2c99..7ca8b8946 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -21,10 +21,10 @@
#include "StateFile.hxx"
#include "OutputState.hxx"
#include "PlaylistState.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "Partition.hxx"
#include "Volume.hxx"
-#include "event/Loop.hxx"
+
#include "fs/FileSystem.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
diff --git a/src/Stats.cxx b/src/Stats.cxx
index f224bdf49..1764516ef 100644
--- a/src/Stats.cxx
+++ b/src/Stats.cxx
@@ -26,38 +26,65 @@
#include "DatabasePlugin.hxx"
#include "DatabaseSimple.hxx"
#include "util/Error.hxx"
+#include "system/Clock.hxx"
#include "Log.hxx"
-#include <glib.h>
+#ifndef WIN32
+/**
+ * The monotonic time stamp when MPD was started. It is used to
+ * calculate the uptime.
+ */
+static unsigned start_time;
+#endif
-static GTimer *uptime;
static DatabaseStats stats;
+enum class StatsValidity : uint8_t {
+ INVALID, VALID, FAILED,
+};
+
+static StatsValidity stats_validity = StatsValidity::INVALID;
+
void stats_global_init(void)
{
- uptime = g_timer_new();
+#ifndef WIN32
+ start_time = MonotonicClockS();
+#endif
}
-void stats_global_finish(void)
+void
+stats_invalidate()
{
- g_timer_destroy(uptime);
+ assert(GetDatabase() != nullptr);
+
+ stats_validity = StatsValidity::INVALID;
}
-void stats_update(void)
+static bool
+stats_update()
{
- assert(GetDatabase() != nullptr);
+ switch (stats_validity) {
+ case StatsValidity::INVALID:
+ break;
- Error error;
+ case StatsValidity::VALID:
+ return true;
+
+ case StatsValidity::FAILED:
+ return false;
+ }
- DatabaseStats stats2;
+ Error error;
const DatabaseSelection selection("", true);
- if (GetDatabase()->GetStats(selection, stats2, error)) {
- stats = stats2;
+ if (GetDatabase()->GetStats(selection, stats, error)) {
+ stats_validity = StatsValidity::VALID;
+ return true;
} else {
LogError(error);
- stats.Clear();
+ stats_validity = StatsValidity::FAILED;
+ return true;
}
}
@@ -71,7 +98,10 @@ db_stats_print(Client &client)
database plugin */
/* TODO: move this into the "proxy" database plugin as
an "idle" handler */
- stats_update();
+ stats_invalidate();
+
+ if (!stats_update())
+ return;
client_printf(client,
"artists: %u\n"
@@ -94,9 +124,13 @@ void
stats_print(Client &client)
{
client_printf(client,
- "uptime: %lu\n"
+ "uptime: %u\n"
"playtime: %lu\n",
- (unsigned long)g_timer_elapsed(uptime, NULL),
+#ifdef WIN32
+ GetProcessUptimeS(),
+#else
+ MonotonicClockS() - start_time,
+#endif
(unsigned long)(client.player_control.GetTotalPlayTime() + 0.5));
if (GetDatabase() != nullptr)
diff --git a/src/Stats.hxx b/src/Stats.hxx
index dd131ce19..e11be0d68 100644
--- a/src/Stats.hxx
+++ b/src/Stats.hxx
@@ -24,9 +24,8 @@ class Client;
void stats_global_init(void);
-void stats_global_finish(void);
-
-void stats_update(void);
+void
+stats_invalidate();
void
stats_print(Client &client);
diff --git a/src/StickerDatabase.hxx b/src/StickerDatabase.hxx
index 42522b7b4..75f0fc77b 100644
--- a/src/StickerDatabase.hxx
+++ b/src/StickerDatabase.hxx
@@ -106,7 +106,7 @@ sticker_delete_value(const char *type, const char *uri, const char *name);
* @param sticker the sticker object to be freed
*/
void
-sticker_free(struct sticker *sticker);
+sticker_free(sticker *sticker);
/**
* Determines a single value in a sticker.
@@ -117,7 +117,7 @@ sticker_free(struct sticker *sticker);
*/
gcc_pure
const char *
-sticker_get_value(const struct sticker &sticker, const char *name);
+sticker_get_value(const sticker &sticker, const char *name);
/**
* Iterates over all sticker items in a sticker.
@@ -127,7 +127,7 @@ sticker_get_value(const struct sticker &sticker, const char *name);
* @param user_data an opaque pointer for the callback function
*/
void
-sticker_foreach(const struct sticker &sticker,
+sticker_foreach(const sticker &sticker,
void (*func)(const char *name, const char *value,
void *user_data),
void *user_data);
@@ -139,7 +139,7 @@ sticker_foreach(const struct sticker &sticker,
* @param uri the URI of the resource, e.g. the song path
* @return a sticker object, or nullptr on error or if there is no sticker
*/
-struct sticker *
+sticker *
sticker_load(const char *type, const char *uri);
/**
diff --git a/src/TagFile.cxx b/src/TagFile.cxx
index 785a74987..3a6756008 100644
--- a/src/TagFile.cxx
+++ b/src/TagFile.cxx
@@ -29,58 +29,70 @@
#include <assert.h>
-bool
-tag_file_scan(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- assert(!path_fs.IsNull());
- assert(handler != nullptr);
+class TagFileScan {
+ const Path path_fs;
+ const char *const suffix;
- /* check if there's a suffix and a plugin */
+ const tag_handler &handler;
+ void *handler_ctx;
- const char *suffix = uri_get_suffix(path_fs.c_str());
- if (suffix == nullptr)
- return false;
-
- const struct DecoderPlugin *plugin =
- decoder_plugin_from_suffix(suffix, nullptr);
- if (plugin == nullptr)
- return false;
-
- InputStream *is = nullptr;
Mutex mutex;
Cond cond;
+ InputStream *is;
+
+public:
+ TagFileScan(Path _path_fs, const char *_suffix,
+ const tag_handler &_handler, void *_handler_ctx)
+ :path_fs(_path_fs), suffix(_suffix),
+ handler(_handler), handler_ctx(_handler_ctx) ,
+ is(nullptr) {}
- do {
- /* load file tag */
- if (plugin->ScanFile(path_fs.c_str(),
- *handler, handler_ctx))
- break;
+ ~TagFileScan() {
+ if (is != nullptr)
+ is->Close();
+ }
- /* fall back to stream tag */
- if (plugin->scan_stream != nullptr) {
- /* open the InputStream (if not already
- open) */
+ bool ScanFile(const DecoderPlugin &plugin) {
+ return plugin.ScanFile(path_fs.c_str(), handler, handler_ctx);
+ }
+
+ bool ScanStream(const DecoderPlugin &plugin) {
+ if (plugin.scan_stream == nullptr)
+ return false;
+
+ /* open the InputStream (if not already open) */
+ if (is == nullptr) {
+ is = InputStream::OpenReady(path_fs.c_str(),
+ mutex, cond,
+ IgnoreError());
if (is == nullptr)
- is = InputStream::Open(path_fs.c_str(),
- mutex, cond,
- IgnoreError());
+ return false;
+ } else
+ is->LockRewind(IgnoreError());
+
+ /* now try the stream_tag() method */
+ return plugin.ScanStream(*is, handler, handler_ctx);
+ }
- /* now try the stream_tag() method */
- if (is != nullptr) {
- if (plugin->ScanStream(*is,
- *handler, handler_ctx))
- break;
+ bool Scan(const DecoderPlugin &plugin) {
+ return plugin.SupportsSuffix(suffix) &&
+ (ScanFile(plugin) || ScanStream(plugin));
+ }
+};
- is->LockRewind(IgnoreError());
- }
- }
+bool
+tag_file_scan(Path path_fs, const tag_handler &handler, void *handler_ctx)
+{
+ assert(!path_fs.IsNull());
- plugin = decoder_plugin_from_suffix(suffix, plugin);
- } while (plugin != nullptr);
+ /* check if there's a suffix and a plugin */
- if (is != nullptr)
- is->Close();
+ const char *suffix = uri_get_suffix(path_fs.c_str());
+ if (suffix == nullptr)
+ return false;
- return plugin != nullptr;
+ TagFileScan tfs(path_fs, suffix, handler, handler_ctx);
+ return decoder_plugins_try([&](const DecoderPlugin &plugin){
+ return tfs.Scan(plugin);
+ });
}
diff --git a/src/TagFile.hxx b/src/TagFile.hxx
index 078abebd9..84ac6c259 100644
--- a/src/TagFile.hxx
+++ b/src/TagFile.hxx
@@ -33,7 +33,6 @@ struct tag_handler;
* found)
*/
bool
-tag_file_scan(Path path,
- const struct tag_handler *handler, void *handler_ctx);
+tag_file_scan(Path path, const tag_handler &handler, void *handler_ctx);
#endif
diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx
index 1191bd37c..0b096fdf7 100644
--- a/src/TagPrint.cxx
+++ b/src/TagPrint.cxx
@@ -35,6 +35,12 @@ void tag_print_types(Client &client)
}
}
+void
+tag_print(Client &client, TagType type, const char *value)
+{
+ client_printf(client, "%s: %s\n", tag_item_names[type], value);
+}
+
void tag_print(Client &client, const Tag &tag)
{
if (tag.time >= 0)
diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx
index ccc0c9aa4..48ddc28ec 100644
--- a/src/TagPrint.hxx
+++ b/src/TagPrint.hxx
@@ -20,12 +20,19 @@
#ifndef MPD_TAG_PRINT_HXX
#define MPD_TAG_PRINT_HXX
+#include <stdint.h>
+
+enum TagType : uint8_t;
+
struct Tag;
class Client;
void tag_print_types(Client &client);
void
+tag_print(Client &client, TagType type, const char *value);
+
+void
tag_print(Client &client, const Tag &tag);
#endif
diff --git a/src/TagStream.cxx b/src/TagStream.cxx
new file mode 100644
index 000000000..8fc1f3312
--- /dev/null
+++ b/src/TagStream.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2013 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 "TagStream.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "InputStream.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <assert.h>
+
+/**
+ * Does the #DecoderPlugin support either the suffix or the MIME type?
+ */
+gcc_pure
+static bool
+CheckDecoderPlugin(const DecoderPlugin &plugin,
+ const char *suffix, const char *mime)
+{
+ return (mime != nullptr && plugin.SupportsMimeType(mime)) ||
+ (suffix != nullptr && plugin.SupportsSuffix(suffix));
+}
+
+bool
+tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx)
+{
+ assert(is.ready);
+
+ const char *const suffix = uri_get_suffix(is.uri.c_str());
+ const char *const mime = is.mime.empty() ? nullptr : is.mime.c_str();
+
+ if (suffix == nullptr && mime == nullptr)
+ return false;
+
+ return decoder_plugins_try([suffix, mime, &is,
+ &handler, ctx](const DecoderPlugin &plugin){
+ is.LockRewind(IgnoreError());
+
+ return CheckDecoderPlugin(plugin, suffix, mime) &&
+ plugin.ScanStream(is, handler, ctx);
+ });
+}
+
+bool
+tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx)
+{
+ Mutex mutex;
+ Cond cond;
+
+ InputStream *is = InputStream::OpenReady(uri, mutex, cond,
+ IgnoreError());
+ if (is == nullptr)
+ return false;
+
+ bool success = tag_stream_scan(*is, handler, ctx);
+ is->Close();
+ return success;
+}
diff --git a/src/TagStream.hxx b/src/TagStream.hxx
new file mode 100644
index 000000000..8fe7d4101
--- /dev/null
+++ b/src/TagStream.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2013 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_TAG_STREAM_HXX
+#define MPD_TAG_STREAM_HXX
+
+#include "check.h"
+
+struct InputStream;
+struct tag_handler;
+
+/**
+ * Scan the tags of an #InputStream. Invokes matching decoder
+ * plugins, but does not invoke the special "APE" and "ID3" scanners.
+ *
+ * @return true if the file was recognized (even if no metadata was
+ * found)
+ */
+bool
+tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx);
+
+bool
+tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx);
+
+#endif
diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx
index 36a726aa6..036da052f 100644
--- a/src/TextInputStream.cxx
+++ b/src/TextInputStream.cxx
@@ -21,7 +21,6 @@
#include "TextInputStream.hxx"
#include "InputStream.hxx"
#include "util/CharUtil.hxx"
-#include "util/fifo_buffer.h"
#include "util/Error.hxx"
#include "Log.hxx"
diff --git a/src/TextInputStream.hxx b/src/TextInputStream.hxx
index a6c15f670..350871488 100644
--- a/src/TextInputStream.hxx
+++ b/src/TextInputStream.hxx
@@ -25,7 +25,6 @@
#include <string>
struct InputStream;
-struct fifo_buffer;
class TextInputStream {
InputStream &is;
diff --git a/src/Timer.cxx b/src/Timer.cxx
index 661aa29ee..f6f5cffc2 100644
--- a/src/Timer.cxx
+++ b/src/Timer.cxx
@@ -22,13 +22,9 @@
#include "AudioFormat.hxx"
#include "system/Clock.hxx"
-#include <glib.h>
-
#include <limits>
#include <assert.h>
-#include <limits.h>
-#include <stddef.h>
Timer::Timer(const AudioFormat af)
: time(0),
@@ -69,14 +65,3 @@ unsigned Timer::GetDelay() const
return delay;
}
-
-void Timer::Synchronize() const
-{
- int64_t sleep_duration;
-
- assert(started);
-
- sleep_duration = time - MonotonicClockUS();
- if (sleep_duration > 0)
- g_usleep(sleep_duration);
-}
diff --git a/src/Timer.hxx b/src/Timer.hxx
index c8b756e9c..96e25b27a 100644
--- a/src/Timer.hxx
+++ b/src/Timer.hxx
@@ -42,8 +42,6 @@ public:
* Returns the number of milliseconds to sleep to get back to sync.
*/
unsigned GetDelay() const;
-
- void Synchronize() const;
};
#endif
diff --git a/src/UpdateArchive.hxx b/src/UpdateArchive.hxx
index 8f52ca0b6..35601a844 100644
--- a/src/UpdateArchive.hxx
+++ b/src/UpdateArchive.hxx
@@ -26,7 +26,6 @@
#include <sys/stat.h>
struct Directory;
-struct archive_plugin;
#ifdef ENABLE_ARCHIVE
diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx
index 80f059734..5b1fa2376 100644
--- a/src/UpdateContainer.cxx
+++ b/src/UpdateContainer.cxx
@@ -26,6 +26,7 @@
#include "Directory.hxx"
#include "Song.hxx"
#include "DecoderPlugin.hxx"
+#include "DecoderList.hxx"
#include "Mapper.hxx"
#include "fs/AllocatedPath.hxx"
#include "tag/TagHandler.hxx"
@@ -64,14 +65,25 @@ make_directory_if_modified(Directory &parent, const char *name,
return directory;
}
+static bool
+SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix)
+{
+ return plugin.container_scan != nullptr &&
+ plugin.SupportsSuffix(suffix);
+}
+
bool
update_container_file(Directory &directory,
const char *name,
const struct stat *st,
- const DecoderPlugin &plugin)
+ const char *suffix)
{
- if (plugin.container_scan == nullptr)
+ const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){
+ return SupportsContainerSuffix(plugin, suffix);
+ });
+ if (_plugin == nullptr)
return false;
+ const DecoderPlugin &plugin = *_plugin;
db_lock();
Directory *contdir = make_directory_if_modified(directory, name, st);
@@ -102,7 +114,7 @@ update_container_file(Directory &directory,
add_tag_handler, &tag_builder);
if (tag_builder.IsDefined())
- song->tag = tag_builder.Commit();
+ song->tag = tag_builder.CommitNew();
else
tag_builder.Clear();
diff --git a/src/UpdateContainer.hxx b/src/UpdateContainer.hxx
index 3b54fb39f..b8f0ad39a 100644
--- a/src/UpdateContainer.hxx
+++ b/src/UpdateContainer.hxx
@@ -31,6 +31,6 @@ bool
update_container_file(Directory &directory,
const char *name,
const struct stat *st,
- const DecoderPlugin &plugin);
+ const char *suffix);
#endif
diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx
index 12ea126a9..d2e9402bc 100644
--- a/src/UpdateGlue.cxx
+++ b/src/UpdateGlue.cxx
@@ -29,7 +29,6 @@
#include "GlobalEvents.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include "Stats.hxx"
#include "Main.hxx"
#include "Instance.hxx"
#include "system/FatalError.hxx"
@@ -162,8 +161,6 @@ static void update_finished_event(void)
spawn_update_task(std::move(i));
} else {
progress = UPDATE_PROGRESS_IDLE;
-
- stats_update();
}
}
diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx
index f4043b2f3..374a767b8 100644
--- a/src/UpdateRemove.cxx
+++ b/src/UpdateRemove.cxx
@@ -20,7 +20,6 @@
#include "config.h" /* must be first for large file support */
#include "UpdateRemove.hxx"
#include "UpdateDomain.hxx"
-#include "Playlist.hxx"
#include "GlobalEvents.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
@@ -58,10 +57,13 @@ song_remove_event(void)
#ifdef ENABLE_SQLITE
/* if the song has a sticker, remove it */
if (sticker_enabled())
- sticker_song_delete(removed_song);
+ sticker_song_delete(*removed_song);
#endif
- instance->DeleteSong(*removed_song);
+ {
+ const auto uri = removed_song->GetURI();
+ instance->DeleteSong(uri.c_str());
+ }
/* clear "removed_song" and send signal to update thread */
remove_mutex.lock();
diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx
index bfab5c4a0..dc49c7a84 100644
--- a/src/UpdateSong.cxx
+++ b/src/UpdateSong.cxx
@@ -27,7 +27,6 @@
#include "DatabaseLock.hxx"
#include "Directory.hxx"
#include "Song.hxx"
-#include "DecoderPlugin.hxx"
#include "DecoderList.hxx"
#include "Log.hxx"
@@ -36,7 +35,7 @@
static void
update_song_file2(Directory &directory,
const char *name, const struct stat *st,
- const DecoderPlugin &plugin)
+ const char *suffix)
{
db_lock();
Song *song = directory.FindSong(name);
@@ -57,7 +56,7 @@ update_song_file2(Directory &directory,
if (!(song != nullptr && st->st_mtime == song->mtime &&
!walk_discard) &&
- update_container_file(directory, name, st, plugin)) {
+ update_container_file(directory, name, st, suffix)) {
if (song != nullptr) {
db_lock();
delete_song(directory, song);
@@ -106,11 +105,9 @@ update_song_file(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
{
- const struct DecoderPlugin *plugin =
- decoder_plugin_from_suffix(suffix, nullptr);
- if (plugin == nullptr)
+ if (!decoder_plugins_supports_suffix(suffix))
return false;
- update_song_file2(directory, name, st, *plugin);
+ update_song_file2(directory, name, st, suffix);
return true;
}
diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx
index d4586456b..3afcbbdf2 100644
--- a/src/UpdateWalk.cxx
+++ b/src/UpdateWalk.cxx
@@ -38,16 +38,12 @@
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
#include "fs/DirectoryReader.hxx"
+#include "util/Alloc.hxx"
#include "util/UriUtil.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
-#include <sys/types.h>
#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
@@ -294,7 +290,7 @@ skip_symlink(const Directory *directory, const char *utf8_name)
const char *target_str = target.c_str();
- if (PathTraits::IsAbsoluteFS(target_str)) {
+ if (PathTraitsFS::IsAbsolute(target_str)) {
/* if the symlink points to an absolute path, see if
that path is inside the music directory */
const char *relative = map_to_relative_path(target_str);
@@ -305,7 +301,7 @@ skip_symlink(const Directory *directory, const char *utf8_name)
const char *p = target_str;
while (*p == '.') {
- if (p[1] == '.' && PathTraits::IsSeparatorFS(p[2])) {
+ if (p[1] == '.' && PathTraitsFS::IsSeparator(p[2])) {
/* "../" moves to parent directory */
directory = directory->parent;
if (directory == nullptr) {
@@ -315,7 +311,7 @@ skip_symlink(const Directory *directory, const char *utf8_name)
return !follow_outside_symlinks;
}
p += 3;
- } else if (PathTraits::IsSeparatorFS(p[1]))
+ } else if (PathTraitsFS::IsSeparator(p[1]))
/* eliminate "./" */
p += 2;
else
@@ -430,7 +426,7 @@ static Directory *
directory_make_uri_parent_checked(const char *uri)
{
Directory *directory = db_get_root();
- char *duplicated = g_strdup(uri);
+ char *duplicated = xstrdup(uri);
char *name_utf8 = duplicated, *slash;
while ((slash = strchr(name_utf8, '/')) != nullptr) {
@@ -447,7 +443,7 @@ directory_make_uri_parent_checked(const char *uri)
name_utf8 = slash + 1;
}
- g_free(duplicated);
+ free(duplicated);
return directory;
}
@@ -458,7 +454,7 @@ update_uri(const char *uri)
if (parent == nullptr)
return;
- const char *name = PathTraits::GetBaseUTF8(uri);
+ const char *name = PathTraitsUTF8::GetBase(uri);
struct stat st;
if (!skip_symlink(parent, name) &&
diff --git a/src/Volume.cxx b/src/Volume.cxx
index 6c5f8dc4d..a4112a4cc 100644
--- a/src/Volume.cxx
+++ b/src/Volume.cxx
@@ -22,11 +22,11 @@
#include "MixerAll.hxx"
#include "Idle.hxx"
#include "GlobalEvents.hxx"
+#include "util/StringUtil.hxx"
#include "util/Domain.hxx"
+#include "system/PeriodClock.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <stdlib.h>
@@ -39,7 +39,7 @@ static unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
static int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
-static GTimer *hardware_volume_timer;
+static PeriodClock hardware_volume_clock;
/**
* Handler for #GlobalEvents::MIXER.
@@ -54,29 +54,19 @@ mixer_event_callback(void)
idle_add(IDLE_MIXER);
}
-void volume_finish(void)
-{
- g_timer_destroy(hardware_volume_timer);
-}
-
void volume_init(void)
{
- hardware_volume_timer = g_timer_new();
-
GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback);
}
int volume_level_get(void)
{
- assert(hardware_volume_timer != nullptr);
-
if (last_hardware_volume >= 0 &&
- g_timer_elapsed(hardware_volume_timer, nullptr) < 1.0)
+ !hardware_volume_clock.CheckUpdate(1000))
/* throttle access to hardware mixers */
return last_hardware_volume;
last_hardware_volume = mixer_all_get_volume();
- g_timer_start(hardware_volume_timer);
return last_hardware_volume;
}
@@ -115,7 +105,7 @@ read_sw_volume_state(const char *line)
char *end = nullptr;
long int sv;
- if (!g_str_has_prefix(line, SW_VOLUME_STATE))
+ if (!StringStartsWith(line, SW_VOLUME_STATE))
return false;
line += sizeof(SW_VOLUME_STATE) - 1;
diff --git a/src/Volume.hxx b/src/Volume.hxx
index 6b937aca3..d448cc4e4 100644
--- a/src/Volume.hxx
+++ b/src/Volume.hxx
@@ -26,8 +26,6 @@
void volume_init(void);
-void volume_finish(void);
-
gcc_pure
int volume_level_get(void);
diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx
index 083647b42..f486a2e31 100644
--- a/src/ZeroconfAvahi.cxx
+++ b/src/ZeroconfAvahi.cxx
@@ -29,14 +29,11 @@
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
-#include <avahi-common/watch.h>
#include <avahi-common/alternative.h>
#include <avahi-common/domain.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>
-#include <stddef.h>
-
static constexpr Domain avahi_domain("avahi");
static char *avahiName;
diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx
index 73e84fbc2..7aebd0514 100644
--- a/src/ZeroconfBonjour.cxx
+++ b/src/ZeroconfBonjour.cxx
@@ -43,7 +43,6 @@ public:
}
~BonjourMonitor() {
- Steal();
DNSServiceRefDeallocate(service_ref);
}
diff --git a/src/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx
index d1e6b51af..7734d3f4b 100644
--- a/src/archive/Bzip2ArchivePlugin.cxx
+++ b/src/archive/Bzip2ArchivePlugin.cxx
@@ -35,9 +35,7 @@
#include <bzlib.h>
-#include <stdint.h>
#include <stddef.h>
-#include <string.h>
#ifdef HAVE_OLDER_BZIP2
#define BZ2_bzDecompressInit bzDecompressInit
@@ -53,7 +51,7 @@ public:
Bzip2ArchiveFile(const char *path, InputStream *_is)
:ArchiveFile(bz2_archive_plugin),
- name(PathTraits::GetBaseUTF8(path)),
+ name(PathTraitsUTF8::GetBase(path)),
istream(_is) {
// remove .bz2 suffix
const size_t len = name.length();
@@ -148,7 +146,7 @@ bz2_open(const char *pathname, Error &error)
{
static Mutex mutex;
static Cond cond;
- InputStream *is = InputStream::Open(pathname, mutex, cond, error);
+ InputStream *is = InputStream::OpenReady(pathname, mutex, cond, error);
if (is == nullptr)
return nullptr;
diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx
index ad21d4a3d..c83e38b0d 100644
--- a/src/archive/Iso9660ArchivePlugin.cxx
+++ b/src/archive/Iso9660ArchivePlugin.cxx
@@ -32,7 +32,6 @@
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include <cdio/cdio.h>
#include <cdio/iso9660.h>
#include <stdlib.h>
@@ -41,11 +40,11 @@
#define CEILING(x, y) ((x+(y-1))/y)
class Iso9660ArchiveFile final : public ArchiveFile {
-public:
RefCount ref;
iso9660_t *iso;
+public:
Iso9660ArchiveFile(iso9660_t *_iso)
:ArchiveFile(iso9660_archive_plugin), iso(_iso) {}
@@ -53,11 +52,19 @@ public:
iso9660_close(iso);
}
+ void Ref() {
+ ref.Increment();
+ }
+
void Unref() {
if (ref.Decrement())
delete this;
}
+ long SeekRead(void *ptr, lsn_t start, long int i_size) const {
+ return iso9660_iso_seek_read(iso, ptr, start, i_size);
+ }
+
void Visit(const char *path, ArchiveVisitor &visitor);
virtual void Close() override {
@@ -131,31 +138,35 @@ Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
/* single archive handling */
-struct Iso9660InputStream {
+class Iso9660InputStream {
InputStream base;
- Iso9660ArchiveFile *archive;
+ Iso9660ArchiveFile &archive;
iso9660_stat_t *statbuf;
- size_t max_blocks;
+public:
Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *uri,
Mutex &mutex, Cond &cond,
iso9660_stat_t *_statbuf)
:base(iso9660_input_plugin, uri, mutex, cond),
- archive(&_archive), statbuf(_statbuf),
- max_blocks(CEILING(statbuf->size, ISO_BLOCKSIZE)) {
-
+ archive(_archive), statbuf(_statbuf) {
base.ready = true;
base.size = statbuf->size;
- archive->ref.Increment();
+ archive.Ref();
}
~Iso9660InputStream() {
free(statbuf);
- archive->Unref();
+ archive.Unref();
}
+
+ InputStream *Get() {
+ return &base;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error);
};
InputStream *
@@ -173,7 +184,7 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
Iso9660InputStream *iis =
new Iso9660InputStream(*this, pathname, mutex, cond,
statbuf);
- return &iis->base;
+ return iis->Get();
}
static void
@@ -184,45 +195,49 @@ iso9660_input_close(InputStream *is)
delete iis;
}
-
-static size_t
-iso9660_input_read(InputStream *is, void *ptr, size_t size,
- Error &error)
+inline size_t
+Iso9660InputStream::Read(void *ptr, size_t size, Error &error)
{
- Iso9660InputStream *iis = (Iso9660InputStream *)is;
int readed = 0;
int no_blocks, cur_block;
- size_t left_bytes = iis->statbuf->size - is->offset;
-
- size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
+ size_t left_bytes = statbuf->size - base.offset;
if (left_bytes < size) {
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
} else {
no_blocks = size / ISO_BLOCKSIZE;
}
- if (no_blocks > 0) {
- cur_block = is->offset / ISO_BLOCKSIZE;
+ if (no_blocks == 0)
+ return 0;
- readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
- iis->statbuf->lsn + cur_block, no_blocks);
+ cur_block = base.offset / ISO_BLOCKSIZE;
- if (readed != no_blocks * ISO_BLOCKSIZE) {
- error.Format(iso9660_domain,
- "error reading ISO file at lsn %lu",
- (unsigned long)cur_block);
- return 0;
- }
- if (left_bytes < size) {
- readed = left_bytes;
- }
+ readed = archive.SeekRead(ptr, statbuf->lsn + cur_block,
+ no_blocks);
- is->offset += readed;
+ if (readed != no_blocks * ISO_BLOCKSIZE) {
+ error.Format(iso9660_domain,
+ "error reading ISO file at lsn %lu",
+ (unsigned long)cur_block);
+ return 0;
+ }
+ if (left_bytes < size) {
+ readed = left_bytes;
}
+
+ base.offset += readed;
return readed;
}
+static size_t
+iso9660_input_read(InputStream *is, void *ptr, size_t size,
+ Error &error)
+{
+ Iso9660InputStream *iis = (Iso9660InputStream *)is;
+ return iis->Read(ptr, size, error);
+}
+
static bool
iso9660_input_eof(InputStream *is)
{
diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx
index 973fe91dc..2ad4ea72e 100644
--- a/src/archive/ZzipArchivePlugin.cxx
+++ b/src/archive/ZzipArchivePlugin.cxx
@@ -34,8 +34,6 @@
#include <zzip/zzip.h>
-#include <string.h>
-
class ZzipArchiveFile final : public ArchiveFile {
public:
RefCount ref;
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 74802ced4..23641ddec 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -20,6 +20,7 @@
#include "config.h"
#include "AllCommands.hxx"
#include "QueueCommands.hxx"
+#include "TagCommands.hxx"
#include "PlayerCommands.hxx"
#include "PlaylistCommands.hxx"
#include "DatabaseCommands.hxx"
@@ -74,9 +75,11 @@ handle_not_commands(Client &client, int argc, char *argv[]);
static const struct command commands[] = {
{ "add", PERMISSION_ADD, 1, 1, handle_add },
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
+ { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
{ "channels", PERMISSION_READ, 0, 0, handle_channels },
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
+ { "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid },
{ "close", PERMISSION_NONE, -1, -1, handle_close },
{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
{ "config", PERMISSION_ADMIN, 0, 0, handle_config },
diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx
index fc14d4a5d..c1ef09904 100644
--- a/src/command/CommandError.cxx
+++ b/src/command/CommandError.cxx
@@ -24,9 +24,8 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
+#include <string.h>
#include <errno.h>
CommandResult
@@ -38,7 +37,7 @@ print_playlist_result(Client &client, PlaylistResult result)
case PlaylistResult::ERRNO:
command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(errno));
+ strerror(errno));
return CommandResult::ERROR;
case PlaylistResult::DENIED:
@@ -115,7 +114,7 @@ print_error(Client &client, const Error &error)
}
} else if (error.IsDomain(errno_domain)) {
command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(error.GetCode()));
+ strerror(error.GetCode()));
return CommandResult::ERROR;
}
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index b86cbdae7..55405c1ef 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -26,14 +26,10 @@
#include "CommandError.hxx"
#include "Client.hxx"
#include "tag/Tag.hxx"
-#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "SongFilter.hxx"
#include "protocol/Result.hxx"
-#include <assert.h>
-#include <string.h>
-
CommandResult
handle_lsinfo2(Client &client, int argc, char *argv[])
{
diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx
index eecc3102f..9c7118bcb 100644
--- a/src/command/FileCommands.cxx
+++ b/src/command/FileCommands.cxx
@@ -25,13 +25,16 @@
#include "ClientFile.hxx"
#include "Client.hxx"
#include "util/CharUtil.hxx"
+#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "tag/TagHandler.hxx"
#include "tag/ApeTag.hxx"
#include "tag/TagId3.hxx"
+#include "TagStream.hxx"
#include "TagFile.hxx"
#include "Mapper.hxx"
#include "fs/AllocatedPath.hxx"
+#include "ls.hxx"
#include <assert.h>
@@ -80,6 +83,25 @@ static constexpr tag_handler print_comment_handler = {
print_pair,
};
+static CommandResult
+read_stream_comments(Client &client, const char *uri)
+{
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return CommandResult::ERROR;
+ }
+
+ if (!tag_stream_scan(uri, print_comment_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "Failed to load file");
+ return CommandResult::ERROR;
+ }
+
+ return CommandResult::OK;
+
+}
+
CommandResult
handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
{
@@ -102,6 +124,8 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
Error error;
if (!client_allow_file(client, path_fs, error))
return print_error(client, error);
+ } else if (uri_has_scheme(uri)) {
+ return read_stream_comments(client, uri);
} else if (*uri != '/') {
path_fs = map_uri_fs(uri);
if (path_fs.IsNull()) {
@@ -114,7 +138,7 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
- if (!tag_file_scan(path_fs, &print_comment_handler, &client)) {
+ if (!tag_file_scan(path_fs, print_comment_handler, &client)) {
command_error(client, ACK_ERROR_NO_EXIST,
"Failed to load file");
return CommandResult::ERROR;
diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx
index 7d9893e70..0c2263d59 100644
--- a/src/command/MessageCommands.cxx
+++ b/src/command/MessageCommands.cxx
@@ -24,7 +24,6 @@
#include "Instance.hxx"
#include "Main.hxx"
#include "protocol/Result.hxx"
-#include "protocol/ArgParser.hxx"
#include <set>
#include <string>
diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx
index 7b2cb1331..6ac5ca1c7 100644
--- a/src/command/OtherCommands.cxx
+++ b/src/command/OtherCommands.cxx
@@ -26,6 +26,8 @@
#include "Song.hxx"
#include "SongPrint.hxx"
#include "TagPrint.hxx"
+#include "TagStream.hxx"
+#include "tag/TagHandler.hxx"
#include "TimePrint.hxx"
#include "Mapper.hxx"
#include "DecoderPrint.hxx"
@@ -44,10 +46,6 @@
#include "Client.hxx"
#include "Idle.hxx"
-#ifdef ENABLE_SQLITE
-#include "StickerDatabase.hxx"
-#endif
-
#include <assert.h>
#include <string.h>
@@ -102,6 +100,20 @@ handle_close(gcc_unused Client &client,
return CommandResult::FINISH;
}
+static void
+print_tag(TagType type, const char *value, void *ctx)
+{
+ Client &client = *(Client *)ctx;
+
+ tag_print(client, type, value);
+}
+
+static constexpr tag_handler print_tag_handler = {
+ nullptr,
+ print_tag,
+ nullptr,
+};
+
CommandResult
handle_lsinfo(Client &client, int argc, char *argv[])
{
@@ -140,6 +152,22 @@ handle_lsinfo(Client &client, int argc, char *argv[])
return CommandResult::OK;
}
+ if (uri_has_scheme(uri)) {
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return CommandResult::ERROR;
+ }
+
+ if (!tag_stream_scan(uri, print_tag_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such file");
+ return CommandResult::ERROR;
+ }
+
+ return CommandResult::OK;
+ }
+
CommandResult result = handle_lsinfo2(client, argc, argv);
if (result != CommandResult::OK)
return result;
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index e949448af..d4d699180 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -24,8 +24,6 @@
#include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx"
-#include <string.h>
-
CommandResult
handle_enableoutput(Client &client, gcc_unused int argc, char *argv[])
{
diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx
index d178fa097..20c5a2595 100644
--- a/src/command/PlaylistCommands.cxx
+++ b/src/command/PlaylistCommands.cxx
@@ -35,9 +35,6 @@
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
-#include <assert.h>
-#include <stdlib.h>
-
static void
print_spl_list(Client &client, const PlaylistVector &list)
{
diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx
index b65e6f607..e5451d260 100644
--- a/src/command/StickerCommands.cxx
+++ b/src/command/StickerCommands.cxx
@@ -31,8 +31,6 @@
#include "protocol/Result.hxx"
#include "util/Error.hxx"
-#include <glib.h>
-
#include <string.h>
struct sticker_song_find_data {
@@ -65,7 +63,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
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, argv[4]);
db->ReturnSong(song);
if (value.empty()) {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -82,7 +80,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
if (song == nullptr)
return print_error(client, error);
- sticker *sticker = sticker_song_get(song);
+ sticker *sticker = sticker_song_get(*song);
db->ReturnSong(song);
if (sticker) {
sticker_print(client, *sticker);
@@ -96,7 +94,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
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, argv[4], argv[5]);
db->ReturnSong(song);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
@@ -113,8 +111,8 @@ handle_sticker_song(Client &client, int argc, char *argv[])
return print_error(client, error);
bool ret = argc == 4
- ? sticker_song_delete(song)
- : sticker_song_delete_value(song, argv[4]);
+ ? sticker_song_delete(*song)
+ : sticker_song_delete_value(*song, argv[4]);
db->ReturnSong(song);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx
new file mode 100644
index 000000000..829d34b81
--- /dev/null
+++ b/src/command/TagCommands.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2013 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 "TagCommands.hxx"
+#include "CommandError.hxx"
+#include "Client.hxx"
+#include "protocol/ArgParser.hxx"
+#include "protocol/Result.hxx"
+#include "tag/Tag.hxx"
+#include "Partition.hxx"
+
+CommandResult
+handle_addtagid(Client &client, gcc_unused int argc, char *argv[])
+{
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[1]))
+ return CommandResult::ERROR;
+
+ const char *const tag_name = argv[2];
+ const TagType tag_type = tag_name_parse_i(tag_name);
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", tag_name);
+ return CommandResult::ERROR;
+ }
+
+ const char *const value = argv[3];
+
+ Error error;
+ if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_cleartagid(Client &client, int argc, char *argv[])
+{
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[1]))
+ return CommandResult::ERROR;
+
+ TagType tag_type = TAG_NUM_OF_ITEM_TYPES;
+ if (argc >= 3) {
+ const char *const tag_name = argv[2];
+ tag_type = tag_name_parse_i(tag_name);
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", tag_name);
+ return CommandResult::ERROR;
+ }
+ }
+
+ Error error;
+ if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx
new file mode 100644
index 000000000..8ccafa76a
--- /dev/null
+++ b/src/command/TagCommands.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2013 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_TAG_COMMANDS_HXX
+#define MPD_TAG_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+
+class Client;
+
+CommandResult
+handle_addtagid(Client &client, int argc, char *argv[]);
+
+CommandResult
+handle_cleartagid(Client &client, int argc, char *argv[]);
+
+#endif
diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx
index 60b33b6b4..f05a69ae3 100644
--- a/src/cue/CueParser.cxx
+++ b/src/cue/CueParser.cxx
@@ -19,19 +19,18 @@
#include "config.h"
#include "CueParser.hxx"
+#include "util/Alloc.hxx"
#include "util/StringUtil.hxx"
#include "util/CharUtil.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
#include <stdlib.h>
CueParser::CueParser()
- :state(HEADER), tag(new Tag()),
+ :state(HEADER),
current(nullptr),
previous(nullptr),
finished(nullptr),
@@ -39,16 +38,9 @@ CueParser::CueParser()
CueParser::~CueParser()
{
- delete tag;
-
- if (current != nullptr)
- current->Free();
-
- if (previous != nullptr)
- previous->Free();
-
- if (finished != nullptr)
- finished->Free();
+ delete current;
+ delete previous;
+ delete finished;
}
static const char *
@@ -109,7 +101,7 @@ cue_next_value(char **pp)
}
static void
-cue_add_tag(Tag &tag, TagType type, char *p)
+cue_add_tag(TagBuilder &tag, TagType type, char *p)
{
const char *value = cue_next_value(&p);
if (value != nullptr)
@@ -118,7 +110,7 @@ cue_add_tag(Tag &tag, TagType type, char *p)
}
static void
-cue_parse_rem(char *p, Tag &tag)
+cue_parse_rem(char *p, TagBuilder &tag)
{
const char *type = cue_next_token(&p);
if (type == nullptr)
@@ -129,13 +121,13 @@ cue_parse_rem(char *p, Tag &tag)
cue_add_tag(tag, type2, p);
}
-Tag *
+TagBuilder *
CueParser::GetCurrentTag()
{
if (state == HEADER)
- return tag;
+ return &header_tag;
else if (state == TRACK)
- return current->tag;
+ return &song_tag;
else
return nullptr;
}
@@ -172,6 +164,9 @@ CueParser::Commit()
if (current == nullptr)
return;
+ assert(!current->GetTag().IsDefined());
+ current->SetTag(song_tag.Commit());
+
finished = previous;
previous = current;
current = nullptr;
@@ -188,9 +183,9 @@ CueParser::Feed2(char *p)
return;
if (strcmp(command, "REM") == 0) {
- Tag *current_tag = GetCurrentTag();
- if (current_tag != nullptr)
- cue_parse_rem(p, *current_tag);
+ TagBuilder *tag = GetCurrentTag();
+ if (tag != nullptr)
+ cue_parse_rem(p, *tag);
} else if (strcmp(command, "PERFORMER") == 0) {
/* MPD knows a "performer" tag, but it is not a good
match for this CUE tag; from the Hydrogenaudio
@@ -202,14 +197,14 @@ CueParser::Feed2(char *p)
? TAG_ARTIST
: TAG_ALBUM_ARTIST;
- Tag *current_tag = GetCurrentTag();
- if (current_tag != nullptr)
- cue_add_tag(*current_tag, type, p);
+ TagBuilder *tag = GetCurrentTag();
+ if (tag != nullptr)
+ cue_add_tag(*tag, type, p);
} else if (strcmp(command, "TITLE") == 0) {
if (state == HEADER)
- cue_add_tag(*tag, TAG_ALBUM, p);
+ cue_add_tag(header_tag, TAG_ALBUM, p);
else if (state == TRACK)
- cue_add_tag(*current->tag, TAG_TITLE, p);
+ cue_add_tag(song_tag, TAG_TITLE, p);
} else if (strcmp(command, "FILE") == 0) {
Commit();
@@ -249,10 +244,12 @@ CueParser::Feed2(char *p)
}
state = TRACK;
- current = Song::NewRemote(filename.c_str());
- assert(current->tag == nullptr);
- current->tag = new Tag(*tag);
- current->tag->AddItem(TAG_TRACK, nr);
+ current = new DetachedSong(std::move(filename));
+ assert(!current->GetTag().IsDefined());
+
+ song_tag = header_tag;
+ song_tag.AddItem(TAG_TRACK, nr);
+
last_updated = false;
} else if (state == IGNORE_TRACK) {
return;
@@ -270,14 +267,14 @@ CueParser::Feed2(char *p)
return;
if (!last_updated && previous != nullptr &&
- previous->start_ms < (unsigned)position_ms) {
+ previous->GetStartMS() < (unsigned)position_ms) {
last_updated = true;
- previous->end_ms = position_ms;
- previous->tag->time =
- (previous->end_ms - previous->start_ms + 500) / 1000;
+ previous->SetEndMS(position_ms);
+ previous->WritableTag().time =
+ (previous->GetEndMS() - previous->GetStartMS() + 500) / 1000;
}
- current->start_ms = position_ms;
+ current->SetStartMS(position_ms);
}
}
@@ -287,9 +284,9 @@ CueParser::Feed(const char *line)
assert(!end);
assert(line != nullptr);
- char *allocated = g_strdup(line);
+ char *allocated = xstrdup(line);
Feed2(allocated);
- g_free(allocated);
+ free(allocated);
}
void
@@ -303,7 +300,7 @@ CueParser::Finish()
end = true;
}
-Song *
+DetachedSong *
CueParser::Get()
{
if (finished == nullptr && end) {
@@ -315,7 +312,7 @@ CueParser::Get()
previous = nullptr;
}
- Song *song = finished;
+ DetachedSong *song = finished;
finished = nullptr;
return song;
}
diff --git a/src/cue/CueParser.hxx b/src/cue/CueParser.hxx
index abcceaa2e..9a32a33c4 100644
--- a/src/cue/CueParser.hxx
+++ b/src/cue/CueParser.hxx
@@ -21,11 +21,12 @@
#define MPD_CUE_PARSER_HXX
#include "check.h"
+#include "tag/TagBuilder.hxx"
#include "Compiler.h"
#include <string>
-struct Song;
+class DetachedSong;
struct Tag;
class CueParser {
@@ -56,26 +57,36 @@ class CueParser {
IGNORE_TRACK,
} state;
- Tag *tag;
+ /**
+ * Tags read from the CUE header.
+ */
+ TagBuilder header_tag;
+
+ /**
+ * Tags read for the current song (attribute #current). When
+ * #current gets moved to #previous, TagBuilder::Commit() will
+ * be called.
+ */
+ TagBuilder song_tag;
std::string filename;
/**
* The song currently being edited.
*/
- Song *current;
+ DetachedSong *current;
/**
* The previous song. It is remembered because its end_time
* will be set to the current song's start time.
*/
- Song *previous;
+ DetachedSong *previous;
/**
* A song that is completely finished and can be returned to
* the caller via cue_parser_get().
*/
- Song *finished;
+ DetachedSong *finished;
/**
* Set to true after previous.end_time has been updated to the
@@ -114,11 +125,11 @@ public:
* @return a song object that must be freed by the caller, or NULL if
* no song was finished at this time
*/
- Song *Get();
+ DetachedSong *Get();
private:
gcc_pure
- Tag *GetCurrentTag();
+ TagBuilder *GetCurrentTag();
/**
* Commit the current song. It will be moved to "previous",
diff --git a/src/db/LazyDatabase.cxx b/src/db/LazyDatabase.cxx
new file mode 100644
index 000000000..f767e89cd
--- /dev/null
+++ b/src/db/LazyDatabase.cxx
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2003-2013 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 "LazyDatabase.hxx"
+
+#include <assert.h>
+
+LazyDatabase::~LazyDatabase()
+{
+ assert(!open);
+
+ delete db;
+}
+
+bool
+LazyDatabase::EnsureOpen(Error &error) const
+{
+ if (open)
+ return true;
+
+ if (!db->Open(error))
+ return false;
+
+ open = true;
+ return true;
+}
+
+void
+LazyDatabase::Close()
+{
+ if (open) {
+ open = false;
+ db->Close();
+ }
+}
+
+Song *
+LazyDatabase::GetSong(const char *uri, Error &error) const
+{
+ return EnsureOpen(error)
+ ? db->GetSong(uri, error)
+ : nullptr;
+}
+
+void
+LazyDatabase::ReturnSong(Song *song) const
+{
+ assert(open);
+
+ db->ReturnSong(song);
+}
+
+bool
+LazyDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ return EnsureOpen(error) &&
+ db->Visit(selection, visit_directory, visit_song,
+ visit_playlist, error);
+}
+
+bool
+LazyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type,
+ VisitString visit_string,
+ Error &error) const
+{
+ return EnsureOpen(error) &&
+ db->VisitUniqueTags(selection, tag_type, visit_string, error);
+}
+
+bool
+LazyDatabase::GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats, Error &error) const
+{
+ return EnsureOpen(error) && db->GetStats(selection, stats, error);
+}
+
+time_t
+LazyDatabase::GetUpdateStamp() const
+{
+ return open ? db->GetUpdateStamp() : 0;
+}
diff --git a/src/db/LazyDatabase.hxx b/src/db/LazyDatabase.hxx
new file mode 100644
index 000000000..3910cb7fa
--- /dev/null
+++ b/src/db/LazyDatabase.hxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2013 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_LAZY_DATABASE_PLUGIN_HXX
+#define MPD_LAZY_DATABASE_PLUGIN_HXX
+
+#include "DatabasePlugin.hxx"
+
+/**
+ * A wrapper for a #Database object that gets opened on the first
+ * database access. This works around daemonization problems with
+ * some plugins.
+ */
+class LazyDatabase final : public Database {
+ Database *const db;
+
+ mutable bool open;
+
+public:
+ gcc_nonnull_all
+ LazyDatabase(Database *_db)
+ :db(_db), open(false) {}
+
+ virtual ~LazyDatabase();
+
+ virtual void Close() override;
+
+ virtual Song *GetSong(const char *uri_utf8,
+ Error &error) const override;
+ virtual void ReturnSong(Song *song) const;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type,
+ VisitString visit_string,
+ Error &error) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const override;
+
+ virtual time_t GetUpdateStamp() const override;
+
+private:
+ bool EnsureOpen(Error &error) const;
+};
+
+#endif
diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx
index 00b5d445f..7e8dcf65f 100644
--- a/src/db/ProxyDatabasePlugin.cxx
+++ b/src/db/ProxyDatabasePlugin.cxx
@@ -20,9 +20,9 @@
#include "config.h"
#include "ProxyDatabasePlugin.hxx"
#include "DatabasePlugin.hxx"
+#include "DatabaseListener.hxx"
#include "DatabaseSelection.hxx"
#include "DatabaseError.hxx"
-#include "PlaylistVector.hxx"
#include "Directory.hxx"
#include "Song.hxx"
#include "SongFilter.hxx"
@@ -32,14 +32,21 @@
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "protocol/Ack.hxx"
+#include "Main.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/IdleMonitor.hxx"
+#include "Log.hxx"
#include <mpd/client.h>
+#include <mpd/async.h>
#include <cassert>
#include <string>
#include <list>
-class ProxyDatabase : public Database {
+class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
+ DatabaseListener &listener;
+
std::string host;
unsigned port;
@@ -49,8 +56,25 @@ class ProxyDatabase : public Database {
/* this is mutable because GetStats() must be "const" */
mutable time_t update_stamp;
+ /**
+ * The libmpdclient idle mask that was removed from the other
+ * MPD. This will be handled by the next OnIdle() call.
+ */
+ unsigned idle_received;
+
+ /**
+ * Is the #connection currently "idle"? That is, did we send
+ * the "idle" command to it?
+ */
+ bool is_idle;
+
public:
- static Database *Create(const config_param &param,
+ ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener)
+ :SocketMonitor(_loop), IdleMonitor(_loop),
+ listener(_listener) {}
+
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
Error &error);
virtual bool Open(Error &error) override;
@@ -84,6 +108,14 @@ private:
bool Connect(Error &error);
bool CheckConnection(Error &error);
bool EnsureConnected(Error &error);
+
+ void Disconnect();
+
+ /* virtual methods from SocketMonitor */
+ virtual bool OnSocketReady(unsigned flags) override;
+
+ /* virtual methods from IdleMonitor */
+ virtual void OnIdle() override;
};
static constexpr Domain libmpdclient_domain("libmpdclient");
@@ -217,9 +249,10 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
}
Database *
-ProxyDatabase::Create(const config_param &param, Error &error)
+ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error)
{
- ProxyDatabase *db = new ProxyDatabase();
+ ProxyDatabase *db = new ProxyDatabase(loop, listener);
if (!db->Configure(param, error)) {
delete db;
db = nullptr;
@@ -252,10 +285,10 @@ ProxyDatabase::Open(Error &error)
void
ProxyDatabase::Close()
{
- root->Free();
+ delete root;
if (connection != nullptr)
- mpd_connection_free(connection);
+ Disconnect();
}
bool
@@ -269,11 +302,15 @@ ProxyDatabase::Connect(Error &error)
return false;
}
+ idle_received = unsigned(-1);
+ is_idle = false;
+
+ SocketMonitor::Open(mpd_async_get_fd(mpd_connection_get_async(connection)));
+ IdleMonitor::Schedule();
+
if (!CheckError(connection, error)) {
- if (connection != nullptr) {
- mpd_connection_free(connection);
- connection = nullptr;
- }
+ if (connection != nullptr)
+ Disconnect();
return false;
}
@@ -287,10 +324,22 @@ ProxyDatabase::CheckConnection(Error &error)
assert(connection != nullptr);
if (!mpd_connection_clear_error(connection)) {
- mpd_connection_free(connection);
+ Disconnect();
return Connect(error);
}
+ if (is_idle) {
+ unsigned idle = mpd_run_noidle(connection);
+ if (idle == 0 && !CheckError(connection, error)) {
+ Disconnect();
+ return false;
+ }
+
+ idle_received |= idle;
+ is_idle = false;
+ IdleMonitor::Schedule();
+ }
+
return true;
}
@@ -302,6 +351,79 @@ ProxyDatabase::EnsureConnected(Error &error)
: Connect(error);
}
+void
+ProxyDatabase::Disconnect()
+{
+ assert(connection != nullptr);
+
+ IdleMonitor::Cancel();
+ SocketMonitor::Steal();
+
+ mpd_connection_free(connection);
+ connection = nullptr;
+}
+
+bool
+ProxyDatabase::OnSocketReady(gcc_unused unsigned flags)
+{
+ assert(connection != nullptr);
+
+ if (!is_idle) {
+ // TODO: can this happen?
+ IdleMonitor::Schedule();
+ return false;
+ }
+
+ unsigned idle = (unsigned)mpd_recv_idle(connection, false);
+ if (idle == 0) {
+ Error error;
+ if (!CheckError(connection, error)) {
+ LogError(error);
+ Disconnect();
+ return false;
+ }
+ }
+
+ /* let OnIdle() handle this */
+ idle_received |= idle;
+ is_idle = false;
+ IdleMonitor::Schedule();
+ return false;
+}
+
+void
+ProxyDatabase::OnIdle()
+{
+ assert(connection != nullptr);
+
+ /* handle previous idle events */
+
+ if (idle_received & MPD_IDLE_DATABASE)
+ listener.OnDatabaseModified();
+
+ idle_received = 0;
+
+ /* send a new idle command to the other MPD */
+
+ if (is_idle)
+ // TODO: can this happen?
+ return;
+
+ if (!mpd_send_idle_mask(connection, MPD_IDLE_DATABASE)) {
+ Error error;
+ if (!CheckError(connection, error))
+ LogError(error);
+
+ SocketMonitor::Steal();
+ mpd_connection_free(connection);
+ connection = nullptr;
+ return;
+ }
+
+ is_idle = true;
+ SocketMonitor::ScheduleRead();
+}
+
static Song *
Convert(const struct mpd_song *song);
@@ -341,20 +463,19 @@ void
ProxyDatabase::ReturnSong(Song *song) const
{
assert(song != nullptr);
- assert(song->IsInDatabase());
- assert(song->IsDetached());
+ assert(song->parent == nullptr);
song->Free();
}
static bool
-Visit(struct mpd_connection *connection, const char *uri,
+Visit(struct mpd_connection *connection, Directory &root, const char *uri,
bool recursive, const SongFilter *filter,
VisitDirectory visit_directory, VisitSong visit_song,
VisitPlaylist visit_playlist, Error &error);
static bool
-Visit(struct mpd_connection *connection,
+Visit(struct mpd_connection *connection, Directory &root,
bool recursive, const SongFilter *filter,
const struct mpd_directory *directory,
VisitDirectory visit_directory, VisitSong visit_song,
@@ -362,16 +483,12 @@ Visit(struct mpd_connection *connection,
{
const char *path = mpd_directory_get_path(directory);
- if (visit_directory) {
- Directory *d = Directory::NewGeneric(path, &detached_root);
- bool success = visit_directory(*d, error);
- d->Free();
- if (!success)
- return false;
- }
+ if (visit_directory &&
+ !visit_directory(Directory(path, &root), error))
+ return false;
if (recursive &&
- !Visit(connection, path, recursive, filter,
+ !Visit(connection, root, path, recursive, filter,
visit_directory, visit_song, visit_playlist, error))
return false;
@@ -395,7 +512,7 @@ Copy(TagBuilder &tag, TagType d_tag,
static Song *
Convert(const struct mpd_song *song)
{
- Song *s = Song::NewDetached(mpd_song_get_uri(song));
+ Song *s = Song::NewFile(mpd_song_get_uri(song), nullptr);
s->mtime = mpd_song_get_last_modified(song);
s->start_ms = mpd_song_get_start(song) * 1000;
@@ -407,7 +524,7 @@ Convert(const struct mpd_song *song)
for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
Copy(tag, i->d, song, i->s);
- s->tag = tag.Commit();
+ s->tag = tag.CommitNew();
return s;
}
@@ -435,7 +552,7 @@ Visit(const SongFilter *filter,
}
static bool
-Visit(const struct mpd_playlist *playlist,
+Visit(const struct mpd_playlist *playlist, Directory &root,
VisitPlaylist visit_playlist, Error &error)
{
if (!visit_playlist)
@@ -444,7 +561,7 @@ Visit(const struct mpd_playlist *playlist,
PlaylistInfo p(mpd_playlist_get_path(playlist),
mpd_playlist_get_last_modified(playlist));
- return visit_playlist(p, detached_root, error);
+ return visit_playlist(p, root, error);
}
class ProxyEntity {
@@ -486,7 +603,7 @@ ReceiveEntities(struct mpd_connection *connection)
}
static bool
-Visit(struct mpd_connection *connection, const char *uri,
+Visit(struct mpd_connection *connection, Directory &root, const char *uri,
bool recursive, const SongFilter *filter,
VisitDirectory visit_directory, VisitSong visit_song,
VisitPlaylist visit_playlist, Error &error)
@@ -504,7 +621,7 @@ Visit(struct mpd_connection *connection, const char *uri,
break;
case MPD_ENTITY_TYPE_DIRECTORY:
- if (!Visit(connection, recursive, filter,
+ if (!Visit(connection, root, recursive, filter,
mpd_entity_get_directory(entity),
visit_directory, visit_song, visit_playlist,
error))
@@ -519,7 +636,7 @@ Visit(struct mpd_connection *connection, const char *uri,
break;
case MPD_ENTITY_TYPE_PLAYLIST:
- if (!Visit(mpd_entity_get_playlist(entity),
+ if (!Visit(mpd_entity_get_playlist(entity), root,
visit_playlist, error))
return false;
break;
@@ -578,7 +695,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
return ::SearchSongs(connection, selection, visit_song, error);
/* fall back to recursive walk (slow!) */
- return ::Visit(connection, selection.uri.c_str(),
+ return ::Visit(connection, *root, selection.uri.c_str(),
selection.recursive, selection.filter,
visit_directory, visit_song, visit_playlist,
error);
diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx
index e7ea7a62d..31d4213a5 100644
--- a/src/db/SimpleDatabasePlugin.cxx
+++ b/src/db/SimpleDatabasePlugin.cxx
@@ -26,20 +26,21 @@
#include "DatabaseSave.hxx"
#include "DatabaseLock.hxx"
#include "DatabaseError.hxx"
-#include "TextFile.hxx"
+#include "fs/TextFile.hxx"
#include "ConfigData.hxx"
#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <sys/types.h>
#include <errno.h>
static constexpr Domain simple_db_domain("simple_db");
Database *
-SimpleDatabase::Create(const config_param &param, Error &error)
+SimpleDatabase::Create(gcc_unused EventLoop &loop,
+ gcc_unused DatabaseListener &listener,
+ const config_param &param, Error &error)
{
SimpleDatabase *db = new SimpleDatabase();
if (!db->Configure(param, error)) {
@@ -72,7 +73,7 @@ SimpleDatabase::Check(Error &error) const
assert(!path.IsNull());
/* Check if the file exists */
- if (!CheckAccess(path, F_OK)) {
+ if (!CheckAccess(path)) {
/* If the file doesn't exist, we can't check if we can write
* it, so we are going to try to get the directory path, and
* see if we can write a file in that */
@@ -95,6 +96,7 @@ SimpleDatabase::Check(Error &error) const
return false;
}
+#ifndef WIN32
/* Check if we can write to the directory */
if (!CheckAccess(dirPath, X_OK | W_OK)) {
const int e = errno;
@@ -103,7 +105,7 @@ SimpleDatabase::Check(Error &error) const
dirPath_utf8.c_str());
return false;
}
-
+#endif
return true;
}
@@ -122,12 +124,14 @@ SimpleDatabase::Check(Error &error) const
return false;
}
+#ifndef WIN32
/* And check that we can write to it */
if (!CheckAccess(path, R_OK | W_OK)) {
error.FormatErrno("Can't open db file \"%s\" for reading/writing",
path_utf8.c_str());
return false;
}
+#endif
return true;
}
@@ -166,7 +170,7 @@ SimpleDatabase::Open(Error &error)
#endif
if (!Load(error)) {
- root->Free();
+ delete root;
LogError(error);
error.Clear();
@@ -186,7 +190,7 @@ SimpleDatabase::Close()
assert(root != nullptr);
assert(borrowed_song_count == 0);
- root->Free();
+ delete root;
}
Song *
diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx
index dfe981dd8..98cbb96f0 100644
--- a/src/db/SimpleDatabasePlugin.hxx
+++ b/src/db/SimpleDatabasePlugin.hxx
@@ -53,7 +53,8 @@ public:
bool Save(Error &error);
- static Database *Create(const config_param &param,
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
Error &error);
virtual bool Open(Error &error) override;
diff --git a/src/db/UpnpDatabasePlugin.cxx b/src/db/UpnpDatabasePlugin.cxx
new file mode 100644
index 000000000..68a24cb4f
--- /dev/null
+++ b/src/db/UpnpDatabasePlugin.cxx
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) 2003-2012 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 "UpnpDatabasePlugin.hxx"
+#include "upnp/Domain.hxx"
+#include "upnp/upnpplib.hxx"
+#include "upnp/Discovery.hxx"
+#include "upnp/ContentDirectoryService.hxx"
+#include "upnp/Directory.hxx"
+#include "upnp/Util.hxx"
+#include "LazyDatabase.hxx"
+#include "DatabasePlugin.hxx"
+#include "DatabaseSelection.hxx"
+#include "DatabaseError.hxx"
+#include "PlaylistVector.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "ConfigData.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/TagTable.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+#include "SongFilter.hxx"
+
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+
+#include <assert.h>
+#include <string.h>
+
+static const char *const rootid = "0";
+
+static const struct tag_table upnp_tags[] = {
+ { "upnp:artist", TAG_ARTIST },
+ { "upnp:album", TAG_ALBUM },
+ { "upnp:originalTrackNumber", TAG_TRACK },
+ { "upnp:genre", TAG_GENRE },
+ { "dc:title", TAG_TITLE },
+
+ /* sentinel */
+ { nullptr, TAG_NUM_OF_ITEM_TYPES }
+};
+
+class UpnpDatabase : public Database {
+ LibUPnP *m_lib;
+ UPnPDeviceDirectory *m_superdir;
+ Directory *m_root;
+
+public:
+ UpnpDatabase()
+ : m_lib(0), m_superdir(0), m_root(0)
+ {}
+
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
+ Error &error);
+
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual Song *GetSong(const char *uri_utf8,
+ Error &error) const override;
+ virtual void ReturnSong(Song *song) const;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type,
+ VisitString visit_string,
+ Error &error) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const override;
+ virtual time_t GetUpdateStamp() const {return 0;}
+
+protected:
+ bool Configure(const config_param &param, Error &error);
+
+private:
+ bool VisitServer(ContentDirectoryService* server,
+ const std::vector<std::string> &vpath,
+ const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const;
+
+ /**
+ * Run an UPnP search according to MPD parameters, and
+ * visit_song the results.
+ */
+ bool SearchSongs(ContentDirectoryService* server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ VisitSong visit_song,
+ Error &error) const;
+
+ bool SearchSongs(ContentDirectoryService* server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ UPnPDirContent& dirbuf,
+ Error &error) const;
+
+ bool Namei(ContentDirectoryService* server,
+ const std::vector<std::string> &vpath,
+ std::string &oobjid, UPnPDirObject &dirent,
+ Error &error) const;
+
+ /**
+ * Take server and objid, return metadata.
+ */
+ bool ReadNode(ContentDirectoryService* server,
+ const char *objid, UPnPDirObject& dirent,
+ Error &error) const;
+
+ /**
+ * Get the path for an object Id. This works much like pwd,
+ * except easier cause our inodes have a parent id. Not used
+ * any more actually (see comments in SearchSongs).
+ */
+ bool BuildPath(ContentDirectoryService* server,
+ const UPnPDirObject& dirent, std::string &idpath,
+ Error &error) const;
+};
+
+Database *
+UpnpDatabase::Create(gcc_unused EventLoop &loop,
+ gcc_unused DatabaseListener &listener,
+ const config_param &param, Error &error)
+{
+ UpnpDatabase *db = new UpnpDatabase();
+ if (!db->Configure(param, error)) {
+ delete db;
+ return nullptr;
+ }
+
+ /* libupnp loses its ability to receive multicast messages
+ apparently due to daemonization; using the LazyDatabase
+ wrapper works around this problem */
+ return new LazyDatabase(db);
+}
+
+bool
+UpnpDatabase::Configure(const config_param &, Error &)
+{
+ return true;
+}
+
+bool
+UpnpDatabase::Open(Error &error)
+{
+ if (m_root)
+ return true;
+
+ m_lib = LibUPnP::getLibUPnP(error);
+ if (!m_lib)
+ return false;
+
+ m_superdir = UPnPDeviceDirectory::getTheDir();
+ if (!m_superdir || !m_superdir->ok()) {
+ error.Set(upnp_domain, "Discovery services startup failed");
+ return false;
+ }
+
+ m_root = Directory::NewRoot();
+ // Wait for device answers. This should be consistent with the value set
+ // in the lib (currently 2)
+ sleep(2);
+ return true;
+}
+
+void
+UpnpDatabase::Close()
+{
+ if (m_root)
+ delete m_root;
+ // TBD decide what we do with the lib and superdir objects
+}
+
+void
+UpnpDatabase::ReturnSong(Song *song) const
+{
+ assert(song != nullptr);
+
+ song->Free();
+}
+
+/**
+ * Transform titles to turn '/' into '_' to make them acceptable path
+ * elements. There is a very slight risk of collision in doing
+ * this. Twonky returns directory names (titles) like 'Artist/Album'.
+ */
+gcc_pure
+static std::string
+titleToPathElt(const std::string &in)
+{
+ std::string out;
+ for (auto it = in.begin(); it != in.end(); it++) {
+ if (*it == '/') {
+ out += '_';
+ } else {
+ out += *it;
+ }
+ }
+ return out;
+}
+
+// If uri is empty, we use the object's url instead. This happens
+// when the target of a Visit() is a song, which only happens when
+// "add"ing AFAIK. Visit() calls us with a null uri so that the url
+// appropriate for fetching is used instead.
+static Song *
+upnpItemToSong(const UPnPDirObject &dirent, const char *uri)
+{
+ if (*uri == 0)
+ uri = dirent.url.c_str();
+
+ Song *s = Song::NewFile(uri, nullptr);
+
+ TagBuilder tag;
+
+ if (dirent.duration > 0)
+ tag.SetTime(dirent.duration);
+
+ tag.AddItem(TAG_TITLE, titleToPathElt(dirent.m_title).c_str());
+
+ for (auto i = upnp_tags; i->name != nullptr; ++i) {
+ const char *value = dirent.getprop(i->name);
+ if (value != nullptr)
+ tag.AddItem(i->type, value);
+ }
+
+ s->tag = tag.CommitNew();
+ return s;
+}
+
+// Get song info by path. We can receive either the id path, or the titles
+// one
+Song *
+UpnpDatabase::GetSong(const char *uri, Error &error) const
+{
+ if (!m_superdir || !m_superdir->ok()) {
+ error.Set(upnp_domain,
+ "UpnpDatabase::GetSong() superdir is sick");
+ return nullptr;
+ }
+
+ Song *song = nullptr;
+ auto vpath = stringToTokens(uri, "/", true);
+ if (vpath.size() >= 2) {
+ ContentDirectoryService server;
+ if (!m_superdir->getServer(vpath[0].c_str(), server)) {
+ error.Set(upnp_domain, "server not found");
+ return nullptr;
+ }
+
+ vpath.erase(vpath.begin());
+ UPnPDirObject dirent;
+ if (vpath[0].compare(rootid)) {
+ std::string objid;
+ if (!Namei(&server, vpath, objid, dirent, error))
+ return nullptr;
+ } else {
+ if (!ReadNode(&server, vpath.back().c_str(), dirent,
+ error))
+ return nullptr;
+ }
+ song = upnpItemToSong(dirent, "");
+ }
+ if (song == nullptr)
+ error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri);
+
+ return song;
+}
+
+/**
+ * Retrieve the value for an MPD tag from an object entry.
+ */
+static bool
+getTagValue(UPnPDirObject& dirent, TagType tag,
+ std::string &tagvalue)
+{
+ if (tag == TAG_TITLE) {
+ if (!dirent.m_title.empty()) {
+ tagvalue = titleToPathElt(dirent.m_title);
+ return true;
+ }
+ return false;
+ }
+
+ const char *name = tag_table_lookup(upnp_tags, tag);
+ if (name == nullptr)
+ return false;
+
+ const char *value = dirent.getprop(name);
+ if (value == nullptr)
+ return false;
+
+ tagvalue = value;
+ return true;
+}
+
+/**
+ * Double-quote a string, adding internal backslash escaping.
+ */
+static void
+dquote(std::string &out, const char *in)
+{
+ out.append(1, '"');
+
+ for (; *in != 0; ++in) {
+ switch(*in) {
+ case '\\':
+ case '"':
+ out.append(1, '\\');
+ out.append(1, *in);
+ break;
+
+ default:
+ out.append(1, *in);
+ }
+ }
+
+ out.append(1, '"');
+}
+
+// Run an UPnP search, according to MPD parameters. Return results as
+// UPnP items
+bool
+UpnpDatabase::SearchSongs(ContentDirectoryService* server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ UPnPDirContent &dirbuf,
+ Error &error) const
+{
+ const SongFilter *filter = selection.filter;
+ if (selection.filter == nullptr)
+ return true;
+
+ std::set<std::string> searchcaps;
+ if (!server->getSearchCapabilities(searchcaps, error))
+ return false;
+
+ if (searchcaps.empty())
+ return true;
+
+ std::string cond;
+ for (const auto &item : filter->GetItems()) {
+ switch (auto tag = item.GetTag()) {
+ case LOCATE_TAG_ANY_TYPE:
+ {
+ if (!cond.empty()) {
+ cond += " and ";
+ }
+ cond += "(";
+ bool first(true);
+ for (const auto& cap : searchcaps) {
+ if (first)
+ first = false;
+ else
+ cond += " or ";
+ cond += cap;
+ if (item.GetFoldCase()) {
+ cond += " contains ";
+ } else {
+ cond += " = ";
+ }
+ dquote(cond, item.GetValue().c_str());
+ }
+ cond += ")";
+ }
+ break;
+
+ default:
+ /* Unhandled conditions like
+ LOCATE_TAG_BASE_TYPE or
+ LOCATE_TAG_FILE_TYPE won't have a
+ corresponding upnp prop, so they will be
+ skipped */
+ if (tag == TAG_ALBUM_ARTIST)
+ tag = TAG_ARTIST;
+
+ // TODO: support LOCATE_TAG_ANY_TYPE etc.
+ const char *name = tag_table_lookup(upnp_tags,
+ TagType(tag));
+ if (name == nullptr)
+ continue;
+
+ if (!cond.empty()) {
+ cond += " and ";
+ }
+ cond += name;
+
+ /* FoldCase doubles up as contains/equal
+ switch. UpNP search is supposed to be
+ case-insensitive, but at least some servers
+ have the same convention as mpd (e.g.:
+ minidlna) */
+ if (item.GetFoldCase()) {
+ cond += " contains ";
+ } else {
+ cond += " = ";
+ }
+ dquote(cond, item.GetValue().c_str());
+ }
+ }
+
+ return server->search(objid, cond.c_str(), dirbuf, error);
+}
+
+static bool
+visitSong(const UPnPDirObject& meta, const char *path,
+ const DatabaseSelection &selection,
+ VisitSong visit_song, Error& error)
+{
+ if (!visit_song)
+ return true;
+ Song *s = upnpItemToSong(meta, path);
+ if (!selection.Match(*s))
+ return true;
+ bool success = visit_song(*s, error);
+ s->Free();
+ return success;
+}
+
+/**
+ * Build synthetic path based on object id for search results. The use
+ * of "rootid" is arbitrary, any name that is not likely to be a top
+ * directory name would fit.
+ */
+static const std::string
+songPath(const std::string &servername,
+ const std::string &objid)
+{
+ return servername + "/" + rootid + "/" + objid;
+}
+
+bool
+UpnpDatabase::SearchSongs(ContentDirectoryService* server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ VisitSong visit_song,
+ Error &error) const
+{
+ UPnPDirContent dirbuf;
+ if (!visit_song)
+ return true;
+ if (!SearchSongs(server, objid, selection, dirbuf, error))
+ return false;
+
+ for (const auto &dirent : dirbuf.m_items) {
+ // We get song ids as the result of the UPnP search. But our
+ // client expects paths (e.g. we get 1$4$3788 from minidlna,
+ // but we need to translate to /Music/All_Music/Satisfaction).
+ // We can do this in two ways:
+ // - Rebuild a normal path using BuildPath() which is a kind of pwd
+ // - Build a bogus path based on the song id.
+ // The first method is nice because the returned paths are pretty, but
+ // it has two big problems:
+ // - The song paths are ambiguous: e.g. minidlna returns all search
+ // results as being from the "All Music" directory, which can
+ // contain several songs with the same title (but different objids)
+ // - The performance of BuildPath() is atrocious on very big
+ // directories, even causing timeouts in clients. And of
+ // course, 'All Music' is very big.
+ // So we return synthetic and ugly paths based on the object id,
+ // which we later have to detect.
+ std::string path = songPath(server->getFriendlyName(),
+ dirent.m_id);
+ //BuildPath(server, dirent, path);
+ if (!visitSong(dirent, path.c_str(), selection, visit_song,
+ error))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpDatabase::ReadNode(ContentDirectoryService *server,
+ const char *objid, UPnPDirObject &dirent,
+ Error &error) const
+{
+ UPnPDirContent dirbuf;
+ if (!server->getMetadata(objid, dirbuf, error))
+ return false;
+
+ if (dirbuf.m_containers.size() == 1) {
+ dirent = dirbuf.m_containers[0];
+ } else if (dirbuf.m_items.size() == 1) {
+ dirent = dirbuf.m_items[0];
+ } else {
+ error.Format(upnp_domain, "Bad resource");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpDatabase::BuildPath(ContentDirectoryService *server,
+ const UPnPDirObject& idirent,
+ std::string &path,
+ Error &error) const
+{
+ const char *pid = idirent.m_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();
+ path = titleToPathElt(dirent.m_title) + (path.empty()? "" : "/" + path);
+ }
+ path = std::string(server->getFriendlyName()) + "/" + path;
+ return true;
+}
+
+// Take server and internal title pathname and return objid and metadata.
+bool
+UpnpDatabase::Namei(ContentDirectoryService* server,
+ const std::vector<std::string> &vpath,
+ std::string &oobjid, UPnPDirObject &odirent,
+ Error &error) const
+{
+ oobjid.clear();
+ std::string objid(rootid);
+
+ if (vpath.empty()) {
+ // looking for root info
+ if (!ReadNode(server, rootid, odirent, error))
+ return false;
+
+ oobjid = rootid;
+ return true;
+ }
+
+ // Walk the path elements, read each directory and try to find the next one
+ for (unsigned int i = 0; i < vpath.size(); i++) {
+ UPnPDirContent dirbuf;
+ if (!server->readDir(objid.c_str(), dirbuf, error))
+ return false;
+
+ bool found = false;
+
+ // Look for the name in the sub-container list
+ for (auto& dirent : dirbuf.m_containers) {
+ if (!vpath[i].compare(titleToPathElt(dirent.m_title.c_str()))) {
+ objid = dirent.m_id; // Next readdir target
+ found = true;
+ if (i == vpath.size() - 1) {
+ // The last element in the path was found and it's
+ // a container, we're done
+ oobjid = objid;
+ odirent = dirent;
+ return true;
+ }
+ break;
+ }
+ }
+ if (found)
+ continue;
+
+ // Path elt was not a container, look at the items list
+ for (auto& dirent : dirbuf.m_items) {
+ if (!vpath[i].compare(titleToPathElt(dirent.m_title.c_str()))) {
+ // If this is the last path elt, we found the target,
+ // else it does not exist
+ if (i == vpath.size() - 1) {
+ oobjid = objid;
+ odirent = dirent;
+ return true;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ // Neither container nor item, we're done.
+ if (!found)
+ break;
+ }
+
+ return true;
+}
+
+// vpath is a parsed and writeable version of selection.uri. There is
+// really just one path parameter.
+bool
+UpnpDatabase::VisitServer(ContentDirectoryService* server,
+ const std::vector<std::string> &vpath,
+ const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ /* If the path begins with rootid, we know that this is a
+ song, not a directory (because that's how we set things
+ up). Just visit it. Note that the choice of rootid is
+ arbitrary, any value not likely to be the name of a top
+ directory would be ok. */
+ /* !Note: this *can't* be handled by Namei further down,
+ because the path is not valid for traversal. Besides, it's
+ just faster to access the target node directly */
+ if (!vpath.empty() && !vpath[0].compare(rootid)) {
+ if (visit_song) {
+ UPnPDirObject dirent;
+ if (!ReadNode(server, vpath.back().c_str(), dirent,
+ error))
+ return false;
+
+ if (!visitSong(dirent, "", selection,
+ visit_song, error))
+ return false;
+ }
+ return true;
+ }
+
+ // Translate the target path into an object id and the associated metadata.
+ std::string objid;
+ UPnPDirObject tdirent;
+ if (!Namei(server, vpath, objid, tdirent, error))
+ return false;
+
+ if (objid.empty())
+ // Not found, not a fatal error
+ return true;
+
+ /* If recursive is set, this is a search... No use sending it
+ if the filter is empty. In this case, we implement limited
+ recursion (1-deep) here, which will handle the "add dir"
+ case. */
+ if (selection.recursive && selection.filter)
+ return SearchSongs(server, objid.c_str(), selection,
+ visit_song, error);
+
+ if (tdirent.type == UPnPDirObject::Type::ITEM) {
+ // Target is a song. Not too sure we ever get there actually, maybe
+ // this is always catched by the special uri test above.
+ switch (tdirent.item_class) {
+ case UPnPDirObject::ItemClass::MUSIC:
+ if (visit_song)
+ return visitSong(tdirent, "", selection, visit_song,
+ error);
+ break;
+
+ case UPnPDirObject::ItemClass::PLAYLIST:
+ if (visit_playlist) {
+ /* Note: I've yet to see a playlist
+ item (playlists seem to be usually
+ handled as containers, so I'll decide
+ what to do when I see one... */
+ }
+ break;
+
+ case UPnPDirObject::ItemClass::UNKNOWN:
+ break;
+ }
+
+ return true;
+ }
+
+ /* Target was a a container. Visit it. We could read slices
+ 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(objid.c_str(), dirbuf, error))
+ return false;
+
+ if (visit_directory) {
+ for (auto& dirent : dirbuf.m_containers) {
+ Directory d((selection.uri + "/" +
+ titleToPathElt(dirent.m_title)).c_str(),
+ m_root);
+ if (!visit_directory(d, error))
+ return false;
+ }
+ }
+
+ if (visit_song || visit_playlist) {
+ for (const auto &dirent : dirbuf.m_items) {
+ switch (dirent.item_class) {
+ case UPnPDirObject::ItemClass::MUSIC:
+ if (visit_song) {
+ /* We identify songs by giving
+ them a special path. The Id
+ is enough to fetch them
+ from the server anyway. */
+
+ std::string p;
+ if (!selection.recursive)
+ p = selection.uri + "/" +
+ titleToPathElt(dirent.m_title);
+
+ if (!visitSong(dirent, p.c_str(),
+ selection, visit_song, error))
+ return false;
+ }
+
+ break;
+
+ case UPnPDirObject::ItemClass::PLAYLIST:
+ if (visit_playlist) {
+ /* Note: I've yet to see a
+ playlist item (playlists
+ seem to be usually handled
+ as containers, so I'll
+ decide what to do when I
+ see one... */
+ }
+
+ break;
+
+ case UPnPDirObject::ItemClass::UNKNOWN:
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Deal with the possibly multiple servers, call VisitServer if needed.
+bool
+UpnpDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ std::vector<ContentDirectoryService> servers;
+ if (!m_superdir || !m_superdir->ok() ||
+ !m_superdir->getDirServices(servers)) {
+ error.Set(upnp_domain,
+ "UpnpDatabase::Visit() superdir is sick");
+ return false;
+ }
+
+ auto vpath = stringToTokens(selection.uri, "/", true);
+ if (vpath.empty()) {
+ if (!selection.recursive) {
+ // If the path is empty and recursive is not set, synthetize a
+ // pseudo-directory from the list of servers.
+ if (visit_directory) {
+ for (auto& server : servers) {
+ Directory d(server.getFriendlyName(), m_root);
+ if (!visit_directory(d, error))
+ return false;
+ }
+ }
+ } else {
+ // Recursive is set: visit each server
+ for (auto& server : servers) {
+ if (!VisitServer(&server, std::vector<std::string>(), selection,
+ visit_directory, visit_song, visit_playlist, error))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // We do have a path: the first element selects the server
+ std::string servername(vpath[0]);
+ vpath.erase(vpath.begin());
+
+ ContentDirectoryService* server = 0;
+ for (auto& dir : servers) {
+ if (!servername.compare(dir.getFriendlyName())) {
+ server = &dir;
+ break;
+ }
+ }
+ if (server == 0) {
+ FormatDebug(db_domain, "UpnpDatabase::Visit: server %s not found\n",
+ vpath[0].c_str());
+ return true;
+ }
+ return VisitServer(server, vpath, selection,
+ visit_directory, visit_song, visit_playlist, error);
+}
+
+bool
+UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag,
+ VisitString visit_string,
+ Error &error) const
+{
+ if (!visit_string)
+ return true;
+
+ std::vector<ContentDirectoryService> servers;
+ if (!m_superdir || !m_superdir->ok() ||
+ !m_superdir->getDirServices(servers)) {
+ error.Set(upnp_domain,
+ "UpnpDatabase::Visit() superdir is sick");
+ return false;
+ }
+
+ std::set<std::string> values;
+ for (auto& server : servers) {
+ UPnPDirContent dirbuf;
+ if (!SearchSongs(&server, rootid, selection, dirbuf, error))
+ return false;
+
+ for (auto &dirent : dirbuf.m_items) {
+ std::string tagvalue;
+ if (getTagValue(dirent, tag, tagvalue))
+ values.emplace(std::move(tagvalue));
+ }
+ }
+
+ for (const auto& value : values)
+ if (!visit_string(value.c_str(), error))
+ return false;
+
+ return true;
+}
+
+bool
+UpnpDatabase::GetStats(const DatabaseSelection &,
+ DatabaseStats &stats, Error &) const
+{
+ /* Note: this gets called before the daemonizing so we can't
+ reallyopen this would be a problem if we had real stats */
+ stats.song_count = 0;
+ stats.total_duration = 0;
+ stats.artist_count = 0;
+ stats.album_count = 0;
+ return true;
+}
+
+const DatabasePlugin upnp_db_plugin = {
+ "upnp",
+ UpnpDatabase::Create,
+};
diff --git a/src/db/UpnpDatabasePlugin.hxx b/src/db/UpnpDatabasePlugin.hxx
new file mode 100644
index 000000000..7fe2dde60
--- /dev/null
+++ b/src/db/UpnpDatabasePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2011 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_UPNP_DATABASE_PLUGIN_HXX
+#define MPD_UPNP_DATABASE_PLUGIN_HXX
+
+struct DatabasePlugin;
+
+extern const DatabasePlugin upnp_db_plugin;
+
+#endif
diff --git a/src/db/upnp/ContentDirectoryService.cxx b/src/db/upnp/ContentDirectoryService.cxx
new file mode 100644
index 000000000..b40f55c54
--- /dev/null
+++ b/src/db/upnp/ContentDirectoryService.cxx
@@ -0,0 +1,316 @@
+/*
+ * 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 "ContentDirectoryService.hxx"
+#include "Domain.hxx"
+#include "Device.hxx"
+#include "ixmlwrap.hxx"
+#include "Directory.hxx"
+#include "Util.hxx"
+#include "upnpplib.hxx"
+#include "util/Error.hxx"
+
+#include <stdio.h>
+
+#include <upnp/upnp.h>
+#include <upnp/upnptools.h>
+
+ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service)
+ :m_actionURL(caturl(device.URLBase, service.controlURL)),
+ m_serviceType(service.serviceType),
+ m_deviceId(device.UDN),
+ m_friendlyName(device.friendlyName),
+ m_manufacturer(device.manufacturer),
+ m_modelName(device.modelName),
+ m_rdreqcnt(200)
+{
+ if (!m_modelName.compare("MediaTomb")) {
+ // Readdir by 200 entries is good for most, but MediaTomb likes
+ // them really big. Actually 1000 is better but I don't dare
+ m_rdreqcnt = 500;
+ }
+}
+
+class DirBResFree {
+public:
+ IXML_Document **rqpp, **rspp;
+ DirBResFree(IXML_Document** _rqpp, IXML_Document **_rspp)
+ :rqpp(_rqpp), rspp(_rspp) {}
+ ~DirBResFree()
+ {
+ if (*rqpp)
+ ixmlDocument_free(*rqpp);
+ if (*rspp)
+ ixmlDocument_free(*rspp);
+ }
+};
+
+bool
+ContentDirectoryService::readDirSlice(const char *objectId, int offset,
+ int count, UPnPDirContent &dirbuf,
+ int *didreadp, int *totalp,
+ Error &error)
+{
+ LibUPnP *lib = LibUPnP::getLibUPnP(error);
+ if (lib == nullptr)
+ return false;
+
+ UpnpClient_Handle hdl = lib->getclh();
+
+ IXML_Document *request(0);
+ IXML_Document *response(0);
+ DirBResFree cleaner(&request, &response);
+
+ // Create request
+ char ofbuf[100], cntbuf[100];
+ sprintf(ofbuf, "%d", offset);
+ sprintf(cntbuf, "%d", count);
+ int argcnt = 6;
+ // Some devices require an empty SortCriteria, else bad params
+ request = UpnpMakeAction("Browse", m_serviceType.c_str(), argcnt,
+ "ObjectID", objectId,
+ "BrowseFlag", "BrowseDirectChildren",
+ "Filter", "*",
+ "SortCriteria", "",
+ "StartingIndex", ofbuf,
+ "RequestedCount", cntbuf,
+ nullptr, nullptr);
+ if (request == nullptr) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ int didread = -1;
+ std::string tbuf = ixmlwrap::getFirstElementValue(response, "NumberReturned");
+ if (!tbuf.empty())
+ didread = atoi(tbuf.c_str());
+
+ if (count == -1 || count == 0) {
+ // TODO: what's this?
+ error.Set(upnp_domain, "got -1 or 0 entries");
+ return false;
+ }
+
+ tbuf = ixmlwrap::getFirstElementValue(response, "TotalMatches");
+ if (!tbuf.empty())
+ *totalp = atoi(tbuf.c_str());
+
+ tbuf = ixmlwrap::getFirstElementValue(response, "Result");
+
+ if (!dirbuf.parse(tbuf, error))
+ return false;
+
+ *didreadp = didread;
+ return true;
+}
+
+bool
+ContentDirectoryService::readDir(const char *objectId,
+ UPnPDirContent &dirbuf,
+ Error &error)
+{
+ int offset = 0;
+ int total = 1000;// Updated on first read.
+
+ while (offset < total) {
+ int count;
+ if (!readDirSlice(objectId, offset, m_rdreqcnt, dirbuf,
+ &count, &total, error))
+ return false;
+
+ offset += count;
+ }
+
+ return true;
+}
+
+bool
+ContentDirectoryService::search(const char *objectId,
+ const char *ss,
+ UPnPDirContent &dirbuf,
+ Error &error)
+{
+ LibUPnP *lib = LibUPnP::getLibUPnP(error);
+ if (lib == nullptr)
+ return false;
+
+ UpnpClient_Handle hdl = lib->getclh();
+
+ IXML_Document *request(0);
+ IXML_Document *response(0);
+
+ int offset = 0;
+ int total = 1000;// Updated on first read.
+
+ while (offset < total) {
+ DirBResFree cleaner(&request, &response);
+ char ofbuf[100];
+ sprintf(ofbuf, "%d", offset);
+ // Create request
+ int argcnt = 6;
+ request = UpnpMakeAction("Search", m_serviceType.c_str(), argcnt,
+ "ContainerID", objectId,
+ "SearchCriteria", ss,
+ "Filter", "*",
+ "SortCriteria", "",
+ "StartingIndex", ofbuf,
+ "RequestedCount", "0", // Setting a value here gets twonky into fits
+ nullptr, nullptr);
+ if (request == 0) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ int count = -1;
+ std::string tbuf =
+ ixmlwrap::getFirstElementValue(response, "NumberReturned");
+ if (!tbuf.empty())
+ count = atoi(tbuf.c_str());
+
+ if (count == -1 || count == 0) {
+ // TODO: what's this?
+ error.Set(upnp_domain, "got -1 or 0 entries");
+ return false;
+ }
+
+ offset += count;
+
+ tbuf = ixmlwrap::getFirstElementValue(response, "TotalMatches");
+ if (!tbuf.empty())
+ total = atoi(tbuf.c_str());
+
+ tbuf = ixmlwrap::getFirstElementValue(response, "Result");
+
+ if (!dirbuf.parse(tbuf, error))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ContentDirectoryService::getSearchCapabilities(std::set<std::string> &result,
+ Error &error)
+{
+ LibUPnP *lib = LibUPnP::getLibUPnP(error);
+ if (lib == nullptr)
+ return false;
+
+ UpnpClient_Handle hdl = lib->getclh();
+
+ IXML_Document *request(0);
+ IXML_Document *response(0);
+
+ request = UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(),
+ 0,
+ nullptr, nullptr);
+ if (request == 0) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ std::string tbuf = ixmlwrap::getFirstElementValue(response, "SearchCaps");
+
+ result.clear();
+ if (!tbuf.compare("*")) {
+ result.insert(result.end(), "*");
+ } else if (!tbuf.empty()) {
+ if (!csvToStrings(tbuf, result)) {
+ error.Set(upnp_domain, "Bad response");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+ContentDirectoryService::getMetadata(const char *objectId,
+ UPnPDirContent &dirbuf,
+ Error &error)
+{
+ LibUPnP *lib = LibUPnP::getLibUPnP(error);
+ if (lib == nullptr)
+ return false;
+
+ UpnpClient_Handle hdl = lib->getclh();
+
+ IXML_Document *response(0);
+
+ // Create request
+ int argcnt = 6;
+ IXML_Document *request =
+ UpnpMakeAction("Browse", m_serviceType.c_str(), argcnt,
+ "ObjectID", objectId,
+ "BrowseFlag", "BrowseMetadata",
+ "Filter", "*",
+ "SortCriteria", "",
+ "StartingIndex", "0",
+ "RequestedCount", "1",
+ nullptr, nullptr);
+ DirBResFree cleaner(&request, &response);
+ if (request == nullptr) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ std::string tbuf = ixmlwrap::getFirstElementValue(response, "Result");
+ return dirbuf.parse(tbuf, error);
+}
diff --git a/src/db/upnp/ContentDirectoryService.hxx b/src/db/upnp/ContentDirectoryService.hxx
new file mode 100644
index 000000000..8fc28c382
--- /dev/null
+++ b/src/db/upnp/ContentDirectoryService.hxx
@@ -0,0 +1,119 @@
+/*
+ * 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 _UPNPDIR_HXX_INCLUDED_
+#define _UPNPDIR_HXX_INCLUDED_
+
+#include <string>
+#include <set>
+
+class Error;
+class UPnPDevice;
+struct UPnPService;
+class UPnPDirContent;
+
+/**
+ * Content Directory Service class.
+ *
+ * This stores identity data from a directory service
+ * and the device it belongs to, and has methods to query
+ * the directory, using libupnp for handling the UPnP protocols.
+ *
+ * Note: m_rdreqcnt: number of entries requested per directory read.
+ * 0 means all entries. The device can still return less entries than
+ * requested, depending on its own limits. In general it's not optimal
+ * becauses it triggers issues, and is sometimes actually slower, e.g. on
+ * a D-Link NAS 327
+ *
+ * The value chosen may affect by the UpnpSetMaxContentLength
+ * (2000*1024) done during initialization, but this should be ample
+ */
+class ContentDirectoryService {
+ std::string m_actionURL;
+ std::string m_serviceType;
+ std::string m_deviceId;
+ std::string m_friendlyName;
+ std::string m_manufacturer;
+ std::string m_modelName;
+
+ int m_rdreqcnt; // Slice size to use when reading
+
+public:
+ /**
+ * Construct by copying data from device and service objects.
+ *
+ * The discovery service does this: use getDirServices()
+ */
+ ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service);
+
+ /** An empty one */
+ ContentDirectoryService() = default;
+
+ /** Read a container's children list into dirbuf.
+ *
+ * @param objectId the UPnP object Id for the container. Root has Id "0"
+ * @param[out] dirbuf stores the entries we read.
+ */
+ bool readDir(const char *objectId, UPnPDirContent &dirbuf,
+ Error &error);
+
+ bool readDirSlice(const char *objectId, int offset,
+ int count, UPnPDirContent& dirbuf,
+ int *didread, int *total,
+ Error &error);
+
+ /** Search the content directory service.
+ *
+ * @param objectId the UPnP object Id under which the search
+ * should be done. Not all servers actually support this below
+ * root. Root has Id "0"
+ * @param searchstring an UPnP searchcriteria string. Check the
+ * UPnP document: UPnP-av-ContentDirectory-v1-Service-20020625.pdf
+ * section 2.5.5. Maybe we'll provide an easier way some day...
+ * @param[out] dirbuf stores the entries we read.
+ */
+ bool search(const char *objectId, const char *searchstring,
+ UPnPDirContent &dirbuf,
+ Error &error);
+
+ /** Read metadata for a given node.
+ *
+ * @param objectId the UPnP object Id. Root has Id "0"
+ * @param[out] dirbuf stores the entries we read. At most one entry will be
+ * returned.
+ */
+ bool getMetadata(const char *objectId, UPnPDirContent &dirbuf,
+ Error &error);
+
+ /** Retrieve search capabilities
+ *
+ * @param[out] result an empty vector: no search, or a single '*' element:
+ * any tag can be used in a search, or a list of usable tag names.
+ */
+ bool getSearchCapabilities(std::set<std::string> &result,
+ Error &error);
+
+ /** Retrieve the "friendly name" for this server, useful for display. */
+ const char *getFriendlyName() const {
+ return m_friendlyName.c_str();
+ }
+};
+
+#endif /* _UPNPDIR_HXX_INCLUDED_ */
diff --git a/src/db/upnp/Device.cxx b/src/db/upnp/Device.cxx
new file mode 100644
index 000000000..37d68c232
--- /dev/null
+++ b/src/db/upnp/Device.cxx
@@ -0,0 +1,134 @@
+/*
+ * 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 "Device.hxx"
+#include "Util.hxx"
+#include "Expat.hxx"
+#include "Log.hxx"
+#include "util/Error.hxx"
+
+#include <stdlib.h>
+
+#include <string.h>
+
+/**
+ * An XML parser which constructs an UPnP device object from the
+ * device descriptor.
+ */
+class UPnPDeviceParser final : public CommonExpatParser {
+ UPnPDevice &m_device;
+ std::vector<std::string> m_path;
+ UPnPService m_tservice;
+
+public:
+ UPnPDeviceParser(UPnPDevice& device)
+ :m_device(device) {}
+
+protected:
+ virtual void StartElement(const XML_Char *name, const XML_Char **) {
+ m_path.push_back(name);
+ }
+
+ virtual void EndElement(const XML_Char *name) {
+ if (!strcmp(name, "service")) {
+ m_device.services.push_back(m_tservice);
+ m_tservice.clear();
+ }
+
+ m_path.pop_back();
+ }
+
+ virtual void CharacterData(const XML_Char *s, int len) {
+ std::string str(s, len);
+ trimstring(str);
+ switch (m_path.back()[0]) {
+ case 'c':
+ if (!m_path.back().compare("controlURL"))
+ m_tservice.controlURL += str;
+ break;
+ case 'd':
+ if (!m_path.back().compare("deviceType"))
+ m_device.deviceType += str;
+ break;
+ case 'e':
+ if (!m_path.back().compare("eventSubURL"))
+ m_tservice.eventSubURL += str;
+ break;
+ case 'f':
+ if (!m_path.back().compare("friendlyName"))
+ m_device.friendlyName += str;
+ break;
+ case 'm':
+ if (!m_path.back().compare("manufacturer"))
+ m_device.manufacturer += str;
+ else if (!m_path.back().compare("modelName"))
+ m_device.modelName += str;
+ break;
+ case 's':
+ if (!m_path.back().compare("serviceType"))
+ m_tservice.serviceType = str;
+ else if (!m_path.back().compare("serviceId"))
+ m_tservice.serviceId += str;
+ case 'S':
+ if (!m_path.back().compare("SCPDURL"))
+ m_tservice.SCPDURL = str;
+ break;
+ case 'U':
+ if (!m_path.back().compare("UDN"))
+ m_device.UDN = str;
+ else if (!m_path.back().compare("URLBase"))
+ m_device.URLBase += str;
+ break;
+ }
+ }
+};
+
+UPnPDevice::UPnPDevice(const std::string &url, const std::string &description)
+ :ok(false)
+{
+ UPnPDeviceParser mparser(*this);
+ Error error;
+ if (!mparser.Parse(description.data(), description.length(), true,
+ error)) {
+ // TODO: pass Error to caller
+ LogError(error);
+ return;
+ }
+
+ if (URLBase.empty()) {
+ // The standard says that if the URLBase value is empty, we should use
+ // the url the description was retrieved from. However this is
+ // sometimes something like http://host/desc.xml, sometimes something
+ // like http://host/
+
+ if (url.size() < 8) {
+ // ???
+ URLBase = url;
+ } else {
+ auto hostslash = url.find_first_of("/", 7);
+ if (hostslash == std::string::npos || hostslash == url.size()-1) {
+ URLBase = url;
+ } else {
+ URLBase = path_getfather(url);
+ }
+ }
+ }
+ ok = true;
+}
diff --git a/src/db/upnp/Device.hxx b/src/db/upnp/Device.hxx
new file mode 100644
index 000000000..53e1c4964
--- /dev/null
+++ b/src/db/upnp/Device.hxx
@@ -0,0 +1,92 @@
+/*
+ * 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 _UPNPDEV_HXX_INCLUDED_
+#define _UPNPDEV_HXX_INCLUDED_
+
+#include <vector>
+#include <string>
+
+class Error;
+
+/**
+ * UPnP Description phase: interpreting the device description which we
+ * downloaded from the URL obtained by the discovery phase.
+ */
+
+/**
+ * Data holder for a UPnP service, parsed from the XML description
+ * downloaded after discovery yielded its URL.
+ */
+struct UPnPService {
+ // e.g. urn:schemas-upnp-org:service:ConnectionManager:1
+ std::string serviceType;
+ // Unique Id inside device: e.g here THE ConnectionManager
+ std::string serviceId; // e.g. urn:upnp-org:serviceId:ConnectionManager
+ std::string SCPDURL; // Service description URL. e.g.: cm.xml
+ std::string controlURL; // e.g.: /upnp/control/cm
+ std::string eventSubURL; // e.g.: /upnp/event/cm
+
+ void clear()
+ {
+ serviceType.clear();
+ serviceId.clear();
+ SCPDURL.clear();
+ controlURL.clear();
+ eventSubURL.clear();
+ }
+};
+
+/**
+ * Data holder for a UPnP device, parsed from the XML description obtained
+ * during discovery.
+ * A device may include several services. To be of interest to us,
+ * one of them must be a ContentDirectory.
+ */
+class UPnPDevice {
+public:
+ bool ok;
+ // e.g. urn:schemas-upnp-org:device:MediaServer:1
+ std::string deviceType;
+ // e.g. MediaTomb
+ std::string friendlyName;
+ // Unique device number. This should match the deviceID in the
+ // discovery message. e.g. uuid:a7bdcd12-e6c1-4c7e-b588-3bbc959eda8d
+ std::string UDN;
+ // Base for all relative URLs. e.g. http://192.168.4.4:49152/
+ std::string URLBase;
+ // Manufacturer: e.g. D-Link, PacketVideo ("manufacturer")
+ std::string manufacturer;
+ // Model name: e.g. MediaTomb, DNS-327L ("modelName")
+ std::string modelName;
+ // Services provided by this device.
+ std::vector<UPnPService> services;
+
+ /** Build device from xml description downloaded from discovery
+ * @param url where the description came from
+ * @param description the xml device description
+ */
+ UPnPDevice(const std::string &url, const std::string &description);
+
+ UPnPDevice() : ok(false) {}
+};
+
+typedef std::vector<UPnPService>::iterator DevServIt;
+
+#endif /* _UPNPDEV_HXX_INCLUDED_ */
diff --git a/src/db/upnp/Directory.cxx b/src/db/upnp/Directory.cxx
new file mode 100644
index 000000000..a42719e4d
--- /dev/null
+++ b/src/db/upnp/Directory.cxx
@@ -0,0 +1,184 @@
+/*
+ * 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 "Directory.hxx"
+#include "Util.hxx"
+#include "Expat.hxx"
+
+#include <string>
+#include <vector>
+
+#include <string.h>
+
+static const char *const upnptags[] = {
+ "upnp:artist",
+ "upnp:album",
+ "upnp:genre",
+ "upnp:originalTrackNumber",
+ nullptr,
+};
+
+gcc_pure
+static UPnPDirObject::ItemClass
+ParseItemClass(const char *name)
+{
+ if (strcmp(name, "object.item.audioItem.musicTrack") == 0)
+ return UPnPDirObject::ItemClass::MUSIC;
+ else if (strcmp(name, "object.item.playlistItem") == 0)
+ return UPnPDirObject::ItemClass::PLAYLIST;
+ else
+ return UPnPDirObject::ItemClass::UNKNOWN;
+}
+
+gcc_pure
+static int
+ParseDuration(const std::string &duration)
+{
+ const auto v = stringToTokens(duration, ":");
+ if (v.size() != 3)
+ return 0;
+ return atoi(v[0].c_str())*3600 + atoi(v[1].c_str())*60 + atoi(v[2].c_str());
+}
+
+/**
+ * An XML parser which builds directory contents from DIDL lite input.
+ */
+class UPnPDirParser final : public CommonExpatParser {
+ std::vector<std::string> m_path;
+ UPnPDirObject m_tobj;
+
+public:
+ UPnPDirParser(UPnPDirContent& dir)
+ :m_dir(dir)
+ {
+ }
+ UPnPDirContent& m_dir;
+
+protected:
+ virtual void StartElement(const XML_Char *name, const XML_Char **attrs)
+ {
+ m_path.push_back(name);
+
+ switch (name[0]) {
+ case 'c':
+ if (!strcmp(name, "container")) {
+ m_tobj.clear();
+ m_tobj.type = UPnPDirObject::Type::CONTAINER;
+
+ const char *id = GetAttribute(attrs, "id");
+ if (id != nullptr)
+ m_tobj.m_id = id;
+
+ const char *pid = GetAttribute(attrs, "parentID");
+ if (pid != nullptr)
+ m_tobj.m_pid = pid;
+ }
+ break;
+
+ case 'i':
+ if (!strcmp(name, "item")) {
+ m_tobj.clear();
+ m_tobj.type = UPnPDirObject::Type::ITEM;
+
+ const char *id = GetAttribute(attrs, "id");
+ if (id != nullptr)
+ m_tobj.m_id = id;
+
+ const char *pid = GetAttribute(attrs, "parentID");
+ if (pid != nullptr)
+ m_tobj.m_pid = pid;
+
+ const char *item_class_name =
+ GetAttribute(attrs, "upnp:class");
+ if (item_class_name != nullptr)
+ m_tobj.item_class =
+ ParseItemClass(item_class_name);
+ }
+ break;
+
+ case 'r':
+ if (!strcmp(name, "res")) {
+ // <res protocolInfo="http-get:*:audio/mpeg:*" size="5171496"
+ // bitrate="24576" duration="00:03:35" sampleFrequency="44100"
+ // nrAudioChannels="2">
+
+ const char *duration =
+ GetAttribute(attrs, "duration");
+ if (duration != nullptr)
+ m_tobj.duration = ParseDuration(duration);
+ }
+
+ break;
+ }
+ }
+
+ bool checkobjok() {
+ if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() ||
+ m_tobj.m_title.empty() ||
+ m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN)
+ return false;
+
+ return true;
+ }
+
+ virtual void EndElement(const XML_Char *name)
+ {
+ if (!strcmp(name, "container")) {
+ if (checkobjok()) {
+ m_dir.m_containers.push_back(m_tobj);
+ }
+ } else if (!strcmp(name, "item")) {
+ if (checkobjok()) {
+ m_dir.m_items.push_back(m_tobj);
+ }
+ }
+
+ m_path.pop_back();
+ }
+
+ virtual void CharacterData(const XML_Char *s, int len)
+ {
+ std::string str(s, len);
+ trimstring(str);
+ switch (m_path.back()[0]) {
+ case 'd':
+ if (!m_path.back().compare("dc:title"))
+ m_tobj.m_title += str;
+ break;
+ case 'r':
+ if (!m_path.back().compare("res")) {
+ m_tobj.url = str;
+ }
+ break;
+ case 'u':
+ for (auto i = upnptags; *i != nullptr; ++i)
+ if (!m_path.back().compare(*i))
+ m_tobj.m_props[*i] += str;
+ break;
+ }
+ }
+};
+
+bool
+UPnPDirContent::parse(const std::string &input, Error &error)
+{
+ UPnPDirParser parser(*this);
+ return parser.Parse(input.data(), input.length(), true, error);
+}
diff --git a/src/db/upnp/Directory.hxx b/src/db/upnp/Directory.hxx
new file mode 100644
index 000000000..55dc09c71
--- /dev/null
+++ b/src/db/upnp/Directory.hxx
@@ -0,0 +1,55 @@
+/*
+ * 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_UPNP_DIRECTORY_HXX
+#define MPD_UPNP_DIRECTORY_HXX
+
+#include "Object.hxx"
+
+#include <string>
+#include <vector>
+
+class Error;
+
+/**
+ * Image of a MediaServer Directory Service container (directory),
+ * possibly containing items and subordinate containers.
+ */
+class UPnPDirContent {
+public:
+ std::vector<UPnPDirObject> m_containers;
+ std::vector<UPnPDirObject> m_items;
+
+ /**
+ * Parse from DIDL-Lite XML data.
+ *
+ * Normally only used by ContentDirectoryService::readDir()
+ * This is cumulative: in general, the XML data is obtained in
+ * several documents corresponding to (offset,count) slices of the
+ * directory (container). parse() can be called repeatedly with
+ * the successive XML documents and will accumulate entries in the item
+ * and container vectors. This makes more sense if the different
+ * chunks are from the same container, but given that UPnP Ids are
+ * actually global, nothing really bad will happen if you mix
+ * up...
+ */
+ bool parse(const std::string &didltext, Error &error);
+};
+
+#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */
diff --git a/src/db/upnp/Discovery.cxx b/src/db/upnp/Discovery.cxx
new file mode 100644
index 000000000..a94f29d8c
--- /dev/null
+++ b/src/db/upnp/Discovery.cxx
@@ -0,0 +1,328 @@
+/*
+ * 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 "Discovery.hxx"
+#include "Device.hxx"
+#include "Domain.hxx"
+#include "ContentDirectoryService.hxx"
+#include "WorkQueue.hxx"
+#include "upnpplib.hxx"
+#include "thread/Mutex.hxx"
+
+#include <upnp/upnp.h>
+#include <upnp/upnptools.h>
+
+#include <string.h>
+
+#include <map>
+
+// The service type string we are looking for.
+static const char *const ContentDirectorySType = "urn:schemas-upnp-org:service:ContentDirectory:1";
+
+// We don't include a version in comparisons, as we are satisfied with
+// version 1
+gcc_pure
+static bool
+isCDService(const char *st)
+{
+ const size_t sz = strlen(ContentDirectorySType) - 2;
+ return memcmp(ContentDirectorySType, st, sz) == 0;
+}
+
+// The type of device we're asking for in search
+static const char *const MediaServerDType = "urn:schemas-upnp-org:device:MediaServer:1";
+
+gcc_pure
+static bool
+isMSDevice(const char *st)
+{
+ const size_t sz = strlen(MediaServerDType) - 2;
+ return memcmp(MediaServerDType, st, sz) == 0;
+}
+
+/**
+ * Each appropriate discovery event (executing in a libupnp thread
+ * context) queues the following task object for processing by the
+ * discovery thread.
+ */
+struct DiscoveredTask {
+ bool alive;
+ std::string url;
+ std::string deviceId;
+ int expires; // Seconds valid
+
+ DiscoveredTask(bool _alive, const Upnp_Discovery *disco)
+ : alive(_alive), url(disco->Location),
+ deviceId(disco->DeviceId),
+ expires(disco->Expires) {}
+
+};
+static WorkQueue<DiscoveredTask *> discoveredQueue("DiscoveredQueue");
+
+// Descriptor for one device having a Content Directory service found
+// on the network.
+class ContentDirectoryDescriptor {
+public:
+ ContentDirectoryDescriptor(const std::string &url,
+ const std::string &description,
+ time_t last, int exp)
+ :device(url, description), last_seen(last), expires(exp+20) {}
+ UPnPDevice device;
+ time_t last_seen;
+ int expires; // seconds valid
+};
+
+// A ContentDirectoryPool holds the characteristics of the servers
+// currently on the network.
+// The map is referenced by deviceId (==UDN)
+// The class is instanciated as a static (unenforced) singleton.
+class ContentDirectoryPool {
+public:
+ Mutex m_mutex;
+ std::map<std::string, ContentDirectoryDescriptor> m_directories;
+};
+
+static ContentDirectoryPool contentDirectories;
+
+// Worker routine for the discovery queue. Get messages about devices
+// appearing and disappearing, and update the directory pool
+// accordingly.
+static void *
+discoExplorer(void *)
+{
+ for (;;) {
+ DiscoveredTask *tsk = 0;
+ size_t qsz;
+ if (!discoveredQueue.take(&tsk, &qsz)) {
+ discoveredQueue.workerExit();
+ return (void*)1;
+ }
+
+ const ScopeLock protect(contentDirectories.m_mutex);
+ if (!tsk->alive) {
+ // Device signals it is going off.
+ auto it = contentDirectories.m_directories.find(tsk->deviceId);
+ if (it != contentDirectories.m_directories.end()) {
+ contentDirectories.m_directories.erase(it);
+ }
+ } else {
+ // Device signals its existence and well-being. Perform the
+ // UPnP "description" phase by downloading and decoding the
+ // description document.
+ char *buf;
+ // LINE_SIZE is defined by libupnp's upnp.h...
+ char contentType[LINE_SIZE];
+ int code = UpnpDownloadUrlItem(tsk->url.c_str(), &buf, contentType);
+ if (code != UPNP_E_SUCCESS) {
+ continue;
+ }
+ std::string sdesc(buf);
+
+ // Update or insert the device
+ ContentDirectoryDescriptor d(tsk->url, sdesc,
+ time(0), tsk->expires);
+ if (!d.device.ok) {
+ continue;
+ }
+
+ auto e = contentDirectories.m_directories.emplace(tsk->deviceId, d);
+ if (!e.second)
+ e.first->second = d;
+ }
+ delete tsk;
+ }
+}
+
+// This gets called for all libupnp asynchronous events, in a libupnp
+// thread context.
+// Example: ContentDirectories appearing and disappearing from the network
+// We queue a task for our worker thread(s)
+// It seems that this can get called by several threads. We have a
+// mutex just for clarifying the message printing, the workqueue is
+// mt-safe of course.
+static int
+cluCallBack(Upnp_EventType et, void *evp, void *)
+{
+ static Mutex cblock;
+ const ScopeLock protect(cblock);
+
+ switch (et) {
+ case UPNP_DISCOVERY_SEARCH_RESULT:
+ case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
+ {
+ Upnp_Discovery *disco = (Upnp_Discovery *)evp;
+ if (isMSDevice(disco->DeviceType) ||
+ isCDService(disco->ServiceType)) {
+ DiscoveredTask *tp = new DiscoveredTask(1, disco);
+ if (discoveredQueue.put(tp))
+ return UPNP_E_FINISH;
+ }
+ break;
+ }
+
+ case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
+ {
+ Upnp_Discovery *disco = (Upnp_Discovery *)evp;
+
+ if (isMSDevice(disco->DeviceType) ||
+ isCDService(disco->ServiceType)) {
+ DiscoveredTask *tp = new DiscoveredTask(0, disco);
+ if (discoveredQueue.put(tp))
+ return UPNP_E_FINISH;
+ }
+ break;
+ }
+
+ default:
+ // Ignore other events for now
+ break;
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+void
+UPnPDeviceDirectory::expireDevices()
+{
+ const ScopeLock protect(contentDirectories.m_mutex);
+ time_t now = time(0);
+ bool didsomething = false;
+
+ for (auto it = contentDirectories.m_directories.begin();
+ it != contentDirectories.m_directories.end();) {
+ if (now - it->second.last_seen > it->second.expires) {
+ it = contentDirectories.m_directories.erase(it);
+ didsomething = true;
+ } else {
+ it++;
+ }
+ }
+
+ if (didsomething)
+ search();
+}
+
+UPnPDeviceDirectory::UPnPDeviceDirectory()
+ :m_searchTimeout(2), m_lastSearch(0)
+{
+ if (!discoveredQueue.start(1, discoExplorer, 0)) {
+ error.Set(upnp_domain, "Discover work queue start failed");
+ return;
+ }
+
+ LibUPnP *lib = LibUPnP::getLibUPnP(error);
+ if (lib == nullptr)
+ return;
+
+ lib->registerHandler(UPNP_DISCOVERY_SEARCH_RESULT, cluCallBack, this);
+ lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_ALIVE,
+ cluCallBack, this);
+ lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE,
+ cluCallBack, this);
+
+ search();
+}
+
+bool
+UPnPDeviceDirectory::search()
+{
+ time_t now = time(0);
+ if (now - m_lastSearch < 10)
+ return true;
+ m_lastSearch = now;
+
+ LibUPnP *lib = LibUPnP::getLibUPnP(error);
+ if (lib == nullptr)
+ return false;
+
+ // We search both for device and service just in case.
+ int code = UpnpSearchAsync(lib->getclh(), m_searchTimeout,
+ ContentDirectorySType, lib);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSearchAsync() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ code = UpnpSearchAsync(lib->getclh(), m_searchTimeout,
+ MediaServerDType, lib);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSearchAsync() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ return true;
+}
+
+UPnPDeviceDirectory *UPnPDeviceDirectory::getTheDir()
+{
+ // TODO: elimate static variable
+ static UPnPDeviceDirectory *theDevDir;
+ if (theDevDir == nullptr)
+ theDevDir = new UPnPDeviceDirectory();
+ if (theDevDir && !theDevDir->ok())
+ return 0;
+ return theDevDir;
+}
+
+bool
+UPnPDeviceDirectory::getDirServices(std::vector<ContentDirectoryService> &out)
+{
+ if (!ok())
+ return false;
+
+ // Has locking, do it before our own lock
+ expireDevices();
+
+ const ScopeLock protect(contentDirectories.m_mutex);
+
+ for (auto dit = contentDirectories.m_directories.begin();
+ dit != contentDirectories.m_directories.end(); dit++) {
+ for (const auto &service : dit->second.device.services) {
+ if (isCDService(service.serviceType.c_str())) {
+ out.emplace_back(dit->second.device, service);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+UPnPDeviceDirectory::getServer(const char *friendlyName,
+ ContentDirectoryService &server)
+{
+ std::vector<ContentDirectoryService> ds;
+ if (!getDirServices(ds)) {
+ return false;
+ }
+
+ for (const auto &i : ds) {
+ if (strcmp(friendlyName, i.getFriendlyName()) == 0) {
+ server = i;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/db/upnp/Discovery.hxx b/src/db/upnp/Discovery.hxx
new file mode 100644
index 000000000..899ac83ab
--- /dev/null
+++ b/src/db/upnp/Discovery.hxx
@@ -0,0 +1,90 @@
+/*
+ * 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 _UPNPPDISC_H_X_INCLUDED_
+#define _UPNPPDISC_H_X_INCLUDED_
+
+#include "util/Error.hxx"
+
+#include <vector>
+
+#include <time.h>
+
+class ContentDirectoryService;
+
+/**
+ * Manage UPnP discovery and maintain a directory of active devices. Singleton.
+ *
+ * We are only interested in MediaServers with a ContentDirectory service
+ * for now, but this could be made more general, by removing the filtering.
+ */
+class UPnPDeviceDirectory {
+ Error error;
+
+ /**
+ * The UPnP device search timeout, which should actually be
+ * called delay because it's the base of a random delay that
+ * the devices apply to avoid responding all at the same time.
+ */
+ int m_searchTimeout;
+
+ time_t m_lastSearch;
+
+ UPnPDeviceDirectory();
+public:
+ UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete;
+ UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete;
+
+ /** This class is a singleton. Get the instance here */
+ static UPnPDeviceDirectory *getTheDir();
+
+ /** Retrieve the directory services currently seen on the network */
+ bool getDirServices(std::vector<ContentDirectoryService> &);
+
+ /**
+ * Get server by friendly name. It's a bit wasteful to copy
+ * all servers for this, we could directly walk the list. Otoh
+ * there isn't going to be millions...
+ */
+ bool getServer(const char *friendlyName,
+ ContentDirectoryService &server);
+
+ /** My health */
+ bool ok() const {
+ return !error.IsDefined();
+ }
+
+ /** My diagnostic if health is bad */
+ const Error &GetError() const {
+ return error;
+ }
+
+private:
+ bool search();
+
+ /**
+ * Look at the devices and get rid of those which have not
+ * been seen for too long. We do this when listing the top
+ * directory.
+ */
+ void expireDevices();
+};
+
+
+#endif /* _UPNPPDISC_H_X_INCLUDED_ */
diff --git a/src/db/upnp/Domain.cxx b/src/db/upnp/Domain.cxx
new file mode 100644
index 000000000..12984bd19
--- /dev/null
+++ b/src/db/upnp/Domain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2013 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 "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain upnp_domain("upnp");
diff --git a/src/db/upnp/Domain.hxx b/src/db/upnp/Domain.hxx
new file mode 100644
index 000000000..e249aa5dc
--- /dev/null
+++ b/src/db/upnp/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2013 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_UPNP_DOMAIN_HXX
+#define MPD_UPNP_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain upnp_domain;
+
+#endif
diff --git a/src/db/upnp/Object.hxx b/src/db/upnp/Object.hxx
new file mode 100644
index 000000000..d158ab6f7
--- /dev/null
+++ b/src/db/upnp/Object.hxx
@@ -0,0 +1,94 @@
+/*
+ * 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_UPNP_OBJECT_HXX
+#define MPD_UPNP_OBJECT_HXX
+
+#include <string>
+#include <map>
+
+/**
+ * UpnP Media Server directory entry, converted from XML data.
+ *
+ * This is a dumb data holder class, a struct with helpers.
+ */
+class UPnPDirObject {
+public:
+ enum class Type {
+ UNKNOWN,
+ ITEM,
+ CONTAINER,
+ };
+
+ // There are actually several kinds of containers:
+ // object.container.storageFolder, object.container.person,
+ // object.container.playlistContainer etc., but they all seem to
+ // behave the same as far as we're concerned. Otoh, musicTrack
+ // items are special to us, and so should playlists, but I've not
+ // seen one of the latter yet (servers seem to use containers for
+ // playlists).
+ enum class ItemClass {
+ UNKNOWN,
+ MUSIC,
+ PLAYLIST,
+ };
+
+ std::string m_id; // ObjectId
+ std::string m_pid; // Parent ObjectId
+ std::string url;
+ std::string m_title; // dc:title. Directory name for a container.
+ Type type;
+ ItemClass item_class;
+ // Properties as gathered from the XML document (url, artist, etc.)
+ // The map keys are the XML tag or attribute names.
+ std::map<std::string, std::string> m_props;
+
+ /**
+ * Song duration in seconds. 0 if unknown.
+ */
+ int duration;
+
+ /** Get named property
+ * @param property name (e.g. upnp:artist, upnp:album,
+ * upnp:originalTrackNumber, upnp:genre). Use m_title instead
+ * for dc:title.
+ * @param[out] value
+ * @return true if found.
+ */
+ const char *getprop(const char *name) const {
+ auto it = m_props.find(name);
+ if (it == m_props.end())
+ return nullptr;
+ return it->second.c_str();
+ }
+
+ void clear()
+ {
+ m_id.clear();
+ m_pid.clear();
+ url.clear();
+ m_title.clear();
+ type = Type::UNKNOWN;
+ item_class = ItemClass::UNKNOWN;
+ m_props.clear();
+ duration = -1;
+ }
+};
+
+#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */
diff --git a/src/db/upnp/Util.cxx b/src/db/upnp/Util.cxx
new file mode 100644
index 000000000..afe68e619
--- /dev/null
+++ b/src/db/upnp/Util.cxx
@@ -0,0 +1,184 @@
+/*
+ * 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 "Util.hxx"
+
+#include <string>
+#include <map>
+#include <vector>
+#include <set>
+
+#include <upnp/ixml.h>
+
+/** Get rid of white space at both ends */
+void
+trimstring(std::string &s, const char *ws)
+{
+ auto pos = s.find_first_not_of(ws);
+ if (pos == std::string::npos) {
+ s.clear();
+ return;
+ }
+ s.replace(0, pos, std::string());
+
+ pos = s.find_last_not_of(ws);
+ if (pos != std::string::npos && pos != s.length()-1)
+ s.replace(pos + 1, std::string::npos, std::string());
+}
+
+std::string
+caturl(const std::string &s1, const std::string &s2)
+{
+ std::string out(s1);
+ if (out[out.size()-1] == '/') {
+ if (s2[0] == '/')
+ out.erase(out.size()-1);
+ } else {
+ if (s2[0] != '/')
+ out.push_back('/');
+ }
+ out += s2;
+ return out;
+}
+
+static void
+path_catslash(std::string &s)
+{
+ if (s.empty() || s[s.length() - 1] != '/')
+ s += '/';
+}
+
+std::string
+path_getfather(const std::string &s)
+{
+ std::string father = s;
+
+ // ??
+ if (father.empty())
+ return "./";
+
+ if (father[father.length() - 1] == '/') {
+ // Input ends with /. Strip it, handle special case for root
+ if (father.length() == 1)
+ return father;
+ father.erase(father.length()-1);
+ }
+
+ auto slp = father.rfind('/');
+ if (slp == std::string::npos)
+ return "./";
+
+ father.erase(slp);
+ path_catslash(father);
+ return father;
+}
+
+std::vector<std::string>
+stringToTokens(const std::string &str,
+ const char *delims, bool skipinit)
+{
+ std::vector<std::string> tokens;
+
+ std::string::size_type startPos = 0;
+
+ // Skip initial delims, return empty if this eats all.
+ if (skipinit &&
+ (startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
+ return tokens;
+
+ while (startPos < str.size()) {
+ // Find next delimiter or end of string (end of token)
+ auto pos = str.find_first_of(delims, startPos);
+
+ // Add token to the vector and adjust start
+ if (pos == std::string::npos) {
+ tokens.push_back(str.substr(startPos));
+ break;
+ } else if (pos == startPos) {
+ // Dont' push empty tokens after first
+ if (tokens.empty())
+ tokens.push_back(std::string());
+ startPos = ++pos;
+ } else {
+ tokens.push_back(str.substr(startPos, pos - startPos));
+ startPos = ++pos;
+ }
+ }
+
+ return tokens;
+}
+
+template <class T>
+bool
+csvToStrings(const std::string &s, T &tokens)
+{
+ std::string current;
+ tokens.clear();
+ enum states {TOKEN, ESCAPE};
+ states state = TOKEN;
+ for (unsigned int i = 0; i < s.length(); i++) {
+ switch (s[i]) {
+ case ',':
+ switch(state) {
+ case TOKEN:
+ tokens.insert(tokens.end(), current);
+ current.clear();
+ continue;
+ case ESCAPE:
+ current += ',';
+ state = TOKEN;
+ continue;
+ }
+ break;
+ case '\\':
+ switch(state) {
+ case TOKEN:
+ state=ESCAPE;
+ continue;
+ case ESCAPE:
+ current += '\\';
+ state = TOKEN;
+ continue;
+ }
+ break;
+
+ default:
+ switch(state) {
+ case ESCAPE:
+ state = TOKEN;
+ break;
+ case TOKEN:
+ break;
+ }
+ current += s[i];
+ }
+ }
+ switch(state) {
+ case TOKEN:
+ tokens.insert(tokens.end(), current);
+ break;
+ case ESCAPE:
+ return false;
+ }
+ return true;
+}
+
+//template bool csvToStrings<list<string> >(const string &, list<string> &);
+template bool csvToStrings<std::vector<std::string> >(const std::string &, std::vector<std::string> &);
+template bool csvToStrings<std::set<std::string> >(const std::string &, std::set<std::string> &);
diff --git a/src/db/upnp/Util.hxx b/src/db/upnp/Util.hxx
new file mode 100644
index 000000000..08fe5f497
--- /dev/null
+++ b/src/db/upnp/Util.hxx
@@ -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.
+ */
+
+#ifndef MPD_UPNP_UTIL_HXX
+#define MPD_UPNP_UTIL_HXX
+
+#include "Compiler.h"
+
+#include <string>
+#include <vector>
+
+std::string
+caturl(const std::string& s1, const std::string& s2);
+
+void
+trimstring(std::string &s, const char *ws = " \t\n");
+
+std::string
+path_getfather(const std::string &s);
+
+gcc_pure
+std::vector<std::string>
+stringToTokens(const std::string &str,
+ const char *delims = "/", bool skipinit = true);
+
+template <class T>
+bool csvToStrings(const std::string& s, T &tokens);
+
+#endif /* _UPNPP_H_X_INCLUDED_ */
diff --git a/src/db/upnp/WorkQueue.hxx b/src/db/upnp/WorkQueue.hxx
new file mode 100644
index 000000000..851f81837
--- /dev/null
+++ b/src/db/upnp/WorkQueue.hxx
@@ -0,0 +1,327 @@
+/*
+ * 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 _WORKQUEUE_H_INCLUDED_
+#define _WORKQUEUE_H_INCLUDED_
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <pthread.h>
+#include <time.h>
+
+#include <string>
+#include <queue>
+#include <unordered_map>
+
+//#include "debuglog.h"
+#define LOGINFO(X)
+#define LOGERR(X)
+
+/**
+ * A WorkQueue manages the synchronisation around a queue of work items,
+ * where a number of client threads queue tasks and a number of worker
+ * threads take and execute them. The goal is to introduce some level
+ * of parallelism between the successive steps of a previously single
+ * threaded pipeline. For example data extraction / data preparation / index
+ * update, but this could have other uses.
+ *
+ * There is no individual task status return. In case of fatal error,
+ * the client or worker sets an end condition on the queue. A second
+ * queue could conceivably be used for returning individual task
+ * status.
+ */
+template <class T>
+class WorkQueue {
+ /**
+ * Store per-worker-thread data. Just an initialized timespec,
+ * and used at the moment.
+ */
+ class WQTData {
+ public:
+ WQTData() {wstart.tv_sec = 0; wstart.tv_nsec = 0;}
+ struct timespec wstart;
+ };
+
+ // Configuration
+ std::string m_name;
+ size_t m_high;
+ size_t m_low;
+
+ // Status
+ // Worker threads having called exit
+ unsigned int m_workers_exited;
+ bool m_ok;
+
+ // Per-thread data. The data is not used currently, this could be
+ // a set<pthread_t>
+ std::unordered_map<pthread_t, WQTData> m_worker_threads;
+
+ // Synchronization
+ std::queue<T> m_queue;
+ Cond m_ccond;
+ Cond m_wcond;
+ Mutex m_mutex;
+ // Client/Worker threads currently waiting for a job
+ unsigned int m_clients_waiting;
+ unsigned int m_workers_waiting;
+
+ // Statistics
+ unsigned int m_tottasks;
+ unsigned int m_nowake;
+ unsigned int m_workersleeps;
+ unsigned int m_clientsleeps;
+
+public:
+ /** Create a WorkQueue
+ * @param name for message printing
+ * @param hi number of tasks on queue before clients blocks. Default 0
+ * meaning no limit. hi == -1 means that the queue is disabled.
+ * @param lo minimum count of tasks before worker starts. Default 1.
+ */
+ WorkQueue(const char *name, size_t hi = 0, size_t lo = 1)
+ :m_name(name), m_high(hi), m_low(lo),
+ m_workers_exited(0),
+ m_ok(true),
+ m_clients_waiting(0), m_workers_waiting(0),
+ m_tottasks(0), m_nowake(0), m_workersleeps(0), m_clientsleeps(0)
+ {
+ }
+
+ ~WorkQueue() {
+ setTerminateAndWait();
+ }
+
+ /** Start the worker threads.
+ *
+ * @param nworkers number of threads copies to start.
+ * @param start_routine thread function. It should loop
+ * taking (QueueWorker::take()) and executing tasks.
+ * @param arg initial parameter to thread function.
+ * @return true if ok.
+ */
+ bool start(int nworkers, void *(*workproc)(void *), void *arg)
+ {
+ const ScopeLock protect(m_mutex);
+
+ for (int i = 0; i < nworkers; i++) {
+ int err;
+ pthread_t thr;
+ if ((err = pthread_create(&thr, 0, workproc, arg))) {
+ LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n",
+ m_name.c_str(), err));
+ return false;
+ }
+ m_worker_threads.insert(std::make_pair(thr, WQTData()));
+ }
+ return true;
+ }
+
+ /** Add item to work queue, called from client.
+ *
+ * Sleeps if there are already too many.
+ */
+ bool put(T t)
+ {
+ const ScopeLock protect(m_mutex);
+
+ if (!ok()) {
+ LOGERR(("WorkQueue::put:%s: !ok or mutex_lock failed\n",
+ m_name.c_str()));
+ return false;
+ }
+
+ while (ok() && m_high > 0 && m_queue.size() >= m_high) {
+ m_clientsleeps++;
+ // Keep the order: we test ok() AFTER the sleep...
+ m_clients_waiting++;
+ m_ccond.wait(m_mutex);
+ if (!ok()) {
+ m_clients_waiting--;
+ return false;
+ }
+ m_clients_waiting--;
+ }
+
+ m_queue.push(t);
+ if (m_workers_waiting > 0) {
+ // Just wake one worker, there is only one new task.
+ m_wcond.signal();
+ } else {
+ m_nowake++;
+ }
+
+ return true;
+ }
+
+ /**
+ * Wait until the queue is inactive. Called from client.
+ *
+ * Waits until the task queue is empty and the workers are all
+ * back sleeping. Used by the client to wait for all current work
+ * to be completed, when it needs to perform work that couldn't be
+ * done in parallel with the worker's tasks, or before shutting
+ * down. Work can be resumed after calling this. Note that the
+ * only thread which can call it safely is the client just above
+ * (which can control the task flow), else there could be
+ * tasks in the intermediate queues.
+ * To rephrase: there is no warranty on return that the queue is actually
+ * idle EXCEPT if the caller knows that no jobs are still being created.
+ * It would be possible to transform this into a safe call if some kind
+ * of suspend condition was set on the queue by waitIdle(), to be reset by
+ * some kind of "resume" call. Not currently the case.
+ */
+ bool waitIdle()
+ {
+ const ScopeLock protect(m_mutex);
+
+ if (!ok()) {
+ LOGERR(("WorkQueue::waitIdle:%s: not ok or can't lock\n",
+ m_name.c_str()));
+ return false;
+ }
+
+ // We're done when the queue is empty AND all workers are back
+ // waiting for a task.
+ while (ok() && (m_queue.size() > 0 ||
+ m_workers_waiting != m_worker_threads.size())) {
+ m_clients_waiting++;
+ m_ccond.wait(m_mutex);
+ m_clients_waiting--;
+ }
+
+ return ok();
+ }
+
+
+ /** Tell the workers to exit, and wait for them.
+ *
+ * Does not bother about tasks possibly remaining on the queue, so
+ * should be called after waitIdle() for an orderly shutdown.
+ */
+ void setTerminateAndWait()
+ {
+ const ScopeLock protect(m_mutex);
+
+ if (m_worker_threads.empty())
+ // Already called ?
+ return;
+
+ // Wait for all worker threads to have called workerExit()
+ m_ok = false;
+ while (m_workers_exited < m_worker_threads.size()) {
+ m_wcond.broadcast();
+ m_clients_waiting++;
+ m_ccond.wait(m_mutex);
+ m_clients_waiting--;
+ }
+
+ // Perform the thread joins and compute overall status
+ // Workers return (void*)1 if ok
+ while (!m_worker_threads.empty()) {
+ void *status;
+ auto it = m_worker_threads.begin();
+ pthread_join(it->first, &status);
+ m_worker_threads.erase(it);
+ }
+
+ // Reset to start state.
+ m_workers_exited = m_clients_waiting = m_workers_waiting =
+ m_tottasks = m_nowake = m_workersleeps = m_clientsleeps = 0;
+ m_ok = true;
+ }
+
+ /** Take task from queue. Called from worker.
+ *
+ * Sleeps if there are not enough. Signal if we go to sleep on empty
+ * queue: client may be waiting for our going idle.
+ */
+ bool take(T* tp, size_t *szp = 0)
+ {
+ const ScopeLock protect(m_mutex);
+
+ if (!ok()) {
+ return false;
+ }
+
+ while (ok() && m_queue.size() < m_low) {
+ m_workersleeps++;
+ m_workers_waiting++;
+ if (m_queue.empty())
+ m_ccond.broadcast();
+ m_wcond.wait(m_mutex);
+ if (!ok()) {
+ // !ok is a normal condition when shutting down
+ if (ok()) {
+ LOGERR(("WorkQueue::take:%s: cond_wait failed or !ok\n",
+ m_name.c_str()));
+ }
+ m_workers_waiting--;
+ return false;
+ }
+ m_workers_waiting--;
+ }
+
+ m_tottasks++;
+ *tp = m_queue.front();
+ if (szp)
+ *szp = m_queue.size();
+ m_queue.pop();
+ if (m_clients_waiting > 0) {
+ // No reason to wake up more than one client thread
+ m_ccond.signal();
+ } else {
+ m_nowake++;
+ }
+ return true;
+ }
+
+ /** Advertise exit and abort queue. Called from worker
+ *
+ * This would happen after an unrecoverable error, or when
+ * the queue is terminated by the client. Workers never exit normally,
+ * except when the queue is shut down (at which point m_ok is set to
+ * false by the shutdown code anyway). The thread must return/exit
+ * immediately after calling this.
+ */
+ void workerExit()
+ {
+ const ScopeLock protect(m_mutex);
+
+ m_workers_exited++;
+ m_ok = false;
+ m_ccond.broadcast();
+ }
+
+ size_t qsize()
+ {
+ const ScopeLock protect(m_mutex);
+
+ size_t sz = m_queue.size();
+ return sz;
+ }
+
+private:
+ bool ok()
+ {
+ return m_ok && m_workers_exited == 0 && !m_worker_threads.empty();
+ }
+};
+
+#endif /* _WORKQUEUE_H_INCLUDED_ */
diff --git a/src/db/upnp/ixmlwrap.cxx b/src/db/upnp/ixmlwrap.cxx
new file mode 100644
index 000000000..247c36d5d
--- /dev/null
+++ b/src/db/upnp/ixmlwrap.cxx
@@ -0,0 +1,44 @@
+/* Copyright (C) 2013 J.F.Dockes
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "ixmlwrap.hxx"
+
+namespace ixmlwrap {
+
+std::string
+getFirstElementValue(IXML_Document *doc, const char *name)
+{
+ std::string ret;
+ IXML_NodeList *nodes =
+ ixmlDocument_getElementsByTagName(doc, name);
+
+ if (nodes) {
+ IXML_Node *first = ixmlNodeList_item(nodes, 0);
+ if (first) {
+ IXML_Node *dnode = ixmlNode_getFirstChild(first);
+ if (dnode) {
+ ret = ixmlNode_getNodeValue(dnode);
+ }
+ }
+ }
+
+ if(nodes)
+ ixmlNodeList_free(nodes);
+ return ret;
+}
+
+}
diff --git a/src/db/upnp/ixmlwrap.hxx b/src/db/upnp/ixmlwrap.hxx
new file mode 100644
index 000000000..8677d691a
--- /dev/null
+++ b/src/db/upnp/ixmlwrap.hxx
@@ -0,0 +1,35 @@
+/* Copyright (C) 2013 J.F.Dockes
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef _IXMLWRAP_H_INCLUDED_
+#define _IXMLWRAP_H_INCLUDED_
+
+#include <upnp/ixml.h>
+
+#include <string>
+
+namespace ixmlwrap {
+ /**
+ * Retrieve the text content for the first element of given
+ * name. Returns an empty string if the element does not
+ * contain a text node
+ */
+ std::string getFirstElementValue(IXML_Document *doc,
+ const char *name);
+
+};
+
+#endif /* _IXMLWRAP_H_INCLUDED_ */
diff --git a/src/db/upnp/upnpplib.cxx b/src/db/upnp/upnpplib.cxx
new file mode 100644
index 000000000..d7c65ccf4
--- /dev/null
+++ b/src/db/upnp/upnpplib.cxx
@@ -0,0 +1,99 @@
+/*
+ * 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 "upnpplib.hxx"
+#include "Domain.hxx"
+#include "Log.hxx"
+
+#include <upnp/ixml.h>
+#include <upnp/upnptools.h>
+
+static LibUPnP *theLib;
+
+LibUPnP *
+LibUPnP::getLibUPnP(Error &error)
+{
+ if (theLib == nullptr)
+ theLib = new LibUPnP;
+
+ if (!theLib->ok()) {
+ error.Set(theLib->GetInitError());
+ return nullptr;
+ }
+
+ return theLib;
+}
+
+LibUPnP::LibUPnP()
+{
+ auto code = UpnpInit(0, 0);
+ if (code != UPNP_E_SUCCESS) {
+ init_error.Format(upnp_domain, code,
+ "UpnpInit() failed: %s",
+ UpnpGetErrorMessage(code));
+ return;
+ }
+
+ UpnpSetMaxContentLength(2000*1024);
+
+ code = UpnpRegisterClient(o_callback, (void *)this, &m_clh);
+ if (code != UPNP_E_SUCCESS) {
+ init_error.Format(upnp_domain, code,
+ "UpnpRegisterClient() failed: %s",
+ UpnpGetErrorMessage(code));
+ return;
+ }
+
+ // Servers sometimes make error (e.g.: minidlna returns bad utf-8)
+ ixmlRelaxParser(1);
+}
+
+void
+LibUPnP::registerHandler(Upnp_EventType et, Upnp_FunPtr handler, void *cookie)
+{
+ if (handler == nullptr)
+ m_handlers.erase(et);
+ else
+ m_handlers.emplace(et, Handler(handler, cookie));
+}
+
+int
+LibUPnP::o_callback(Upnp_EventType et, void* evp, void* cookie)
+{
+ LibUPnP *ulib = (LibUPnP *)cookie;
+ if (ulib == nullptr) {
+ // Because the asyncsearch calls uses a null cookie.
+ ulib = theLib;
+ }
+
+ auto it = ulib->m_handlers.find(et);
+ if (it != ulib->m_handlers.end()) {
+ (it->second.handler)(et, evp, it->second.cookie);
+ }
+ return UPNP_E_SUCCESS;
+}
+
+LibUPnP::~LibUPnP()
+{
+ int error = UpnpFinish();
+ if (error != UPNP_E_SUCCESS)
+ FormatError(upnp_domain, "UpnpFinish() failed: %s",
+ UpnpGetErrorMessage(error));
+}
diff --git a/src/db/upnp/upnpplib.hxx b/src/db/upnp/upnpplib.hxx
new file mode 100644
index 000000000..b6ce80212
--- /dev/null
+++ b/src/db/upnp/upnpplib.hxx
@@ -0,0 +1,76 @@
+/*
+ * 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 _LIBUPNP_H_X_INCLUDED_
+#define _LIBUPNP_H_X_INCLUDED_
+
+#include "util/Error.hxx"
+
+#include <map>
+
+#include <upnp/upnp.h>
+
+/** Our link to libupnp. Initialize and keep the handle around */
+class LibUPnP {
+ // A Handler object records the data from registerHandler.
+ class Handler {
+ public:
+ Handler(Upnp_FunPtr h, void *c)
+ : handler(h), cookie(c) {}
+ Upnp_FunPtr handler;
+ void *cookie;
+ };
+
+ Error init_error;
+ UpnpClient_Handle m_clh;
+ std::map<Upnp_EventType, Handler> m_handlers;
+
+ LibUPnP();
+
+ LibUPnP(const LibUPnP &) = delete;
+ LibUPnP &operator=(const LibUPnP &) = delete;
+
+ static int o_callback(Upnp_EventType, void *, void *);
+
+public:
+ ~LibUPnP();
+
+ /** Retrieve the singleton LibUPnP object */
+ static LibUPnP *getLibUPnP(Error &error);
+
+ /** Check state after initialization */
+ bool ok() const
+ {
+ return !init_error.IsDefined();
+ }
+
+ /** Retrieve init error if state not ok */
+ const Error &GetInitError() const {
+ return init_error;
+ }
+
+ void registerHandler(Upnp_EventType et, Upnp_FunPtr handler, void *cookie);
+
+ UpnpClient_Handle getclh()
+ {
+ return m_clh;
+ }
+};
+
+#endif /* _LIBUPNP.H_X_INCLUDED_ */
diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx
index 67cc7e945..b0ba73126 100644
--- a/src/decoder/DsdLib.cxx
+++ b/src/decoder/DsdLib.cxx
@@ -27,8 +27,6 @@
#include "DsdLib.hxx"
#include "DecoderAPI.hxx"
#include "InputStream.hxx"
-#include "util/bit_reverse.h"
-#include "tag/TagHandler.hxx"
#include "tag/TagId3.hxx"
#include "util/Error.hxx"
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx
index a3c0149b9..bc9fc2353 100644
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/DsdiffDecoderPlugin.cxx
@@ -38,9 +38,6 @@
#include "DsdLib.hxx"
#include "Log.hxx"
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
struct DsdiffHeader {
DsdId id;
DffDsdUint64 size;
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx
index 5ef94e647..ab96e8ce6 100644
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ b/src/decoder/DsfDecoderPlugin.cxx
@@ -39,9 +39,6 @@
#include "tag/TagHandler.hxx"
#include "Log.hxx"
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
struct DsfMetaData {
unsigned sample_rate, channels;
bool bitreverse;
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
index 9fd20167d..a37bc88bf 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/FaadDecoderPlugin.cxx
@@ -24,6 +24,7 @@
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -66,16 +67,11 @@ adts_check_frame(const unsigned char *data)
static size_t
adts_find_frame(DecoderBuffer *buffer)
{
- size_t length, frame_length;
- bool ret;
-
while (true) {
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr || length < 8) {
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.size < 8) {
/* not enough data yet */
- ret = decoder_buffer_fill(buffer);
- if (!ret)
+ if (!decoder_buffer_fill(buffer))
/* failed */
return 0;
@@ -83,21 +79,22 @@ adts_find_frame(DecoderBuffer *buffer)
}
/* find the 0xff marker */
- const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
+ const uint8_t *p = (const uint8_t *)
+ memchr(data.data, 0xff, data.size);
if (p == nullptr) {
/* no marker - discard the buffer */
- decoder_buffer_consume(buffer, length);
+ decoder_buffer_clear(buffer);
continue;
}
- if (p > data) {
+ if (p > data.data) {
/* discard data before 0xff */
- decoder_buffer_consume(buffer, p - data);
+ decoder_buffer_consume(buffer, p - data.data);
continue;
}
/* is it a frame? */
- frame_length = adts_check_frame(data);
+ size_t frame_length = adts_check_frame(data.data);
if (frame_length == 0) {
/* it's just some random 0xff byte; discard it
and continue searching */
@@ -105,19 +102,15 @@ adts_find_frame(DecoderBuffer *buffer)
continue;
}
- if (length < frame_length) {
+ if (data.size < frame_length) {
/* available buffer size is smaller than the
frame will be - attempt to read more
data */
- ret = decoder_buffer_fill(buffer);
- if (!ret) {
+ if (!decoder_buffer_fill(buffer)) {
/* not enough data; discard this frame
to prevent a possible buffer
overflow */
- data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data != nullptr)
- decoder_buffer_consume(buffer, length);
+ decoder_buffer_clear(buffer);
}
continue;
@@ -131,31 +124,27 @@ adts_find_frame(DecoderBuffer *buffer)
static float
adts_song_duration(DecoderBuffer *buffer)
{
- unsigned int frames, frame_length;
unsigned sample_rate = 0;
- float frames_per_second;
/* Read all frames to ensure correct time and bitrate */
- for (frames = 0;; frames++) {
- frame_length = adts_find_frame(buffer);
+ unsigned frames = 0;
+ for (;; frames++) {
+ unsigned frame_length = adts_find_frame(buffer);
if (frame_length == 0)
break;
-
if (frames == 0) {
- size_t buffer_length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &buffer_length);
- assert(data != nullptr);
- assert(frame_length <= buffer_length);
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ assert(!data.IsEmpty());
+ assert(frame_length <= data.size);
- sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
+ sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2];
}
decoder_buffer_consume(buffer, frame_length);
}
- frames_per_second = (float)sample_rate / 1024.0;
+ float frames_per_second = (float)sample_rate / 1024.0;
if (frames_per_second <= 0)
return -1;
@@ -165,66 +154,58 @@ adts_song_duration(DecoderBuffer *buffer)
static float
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
{
- size_t fileread;
- size_t tagsize;
- size_t length;
- bool success;
-
const auto size = is.GetSize();
- fileread = size >= 0 ? size : 0;
+ const size_t fileread = size >= 0 ? size : 0;
decoder_buffer_fill(buffer);
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr)
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty())
return -1;
- tagsize = 0;
- if (length >= 10 && !memcmp(data, "ID3", 3)) {
+ size_t tagsize = 0;
+ if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) {
/* skip the ID3 tag */
- tagsize = (data[6] << 21) | (data[7] << 14) |
- (data[8] << 7) | (data[9] << 0);
+ tagsize = (data.data[6] << 21) | (data.data[7] << 14) |
+ (data.data[8] << 7) | (data.data[9] << 0);
tagsize += 10;
- success = decoder_buffer_skip(buffer, tagsize) &&
+ bool success = decoder_buffer_skip(buffer, tagsize) &&
decoder_buffer_fill(buffer);
if (!success)
return -1;
- data = (const uint8_t *)decoder_buffer_read(buffer, &length);
- if (data == nullptr)
+ data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty())
return -1;
}
- if (is.IsSeekable() && length >= 2 &&
- data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
+ if (is.IsSeekable() && data.size >= 2 &&
+ data.data[0] == 0xFF && ((data.data[1] & 0xF6) == 0xF0)) {
/* obtain the duration from the ADTS header */
float song_length = adts_song_duration(buffer);
is.LockSeek(tagsize, SEEK_SET, IgnoreError());
- data = (const uint8_t *)decoder_buffer_read(buffer, &length);
- if (data != nullptr)
- decoder_buffer_consume(buffer, length);
+ decoder_buffer_clear(buffer);
decoder_buffer_fill(buffer);
return song_length;
- } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
+ } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) {
/* obtain the duration from the ADIF header */
unsigned bit_rate;
- size_t skip_size = (data[4] & 0x80) ? 9 : 0;
+ size_t skip_size = (data.data[4] & 0x80) ? 9 : 0;
- if (8 + skip_size > length)
+ if (8 + skip_size > data.size)
/* not enough data yet; skip parsing this
header */
return -1;
- bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
- (data[5 + skip_size] << 11) |
- (data[6 + skip_size] << 3) |
- (data[7 + skip_size] & 0xE0);
+ bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) |
+ (data.data[5 + skip_size] << 11) |
+ (data.data[6 + skip_size] << 3) |
+ (data.data[7 + skip_size] & 0xE0);
if (fileread != 0 && bit_rate != 0)
return fileread * 8.0 / bit_rate;
@@ -242,9 +223,7 @@ static bool
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
AudioFormat &audio_format, Error &error)
{
- int32_t nbytes;
uint32_t sample_rate;
- uint8_t channels;
#ifdef HAVE_FAAD_LONG
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
@@ -254,19 +233,18 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
uint32_t *sample_rate_p = &sample_rate;
#endif
- size_t length;
- const unsigned char *data = (const unsigned char *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr) {
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty()) {
error.Set(faad_decoder_domain, "Empty file");
return false;
}
- nbytes = NeAACDecInit(decoder,
- /* deconst hack, libfaad requires this */
- const_cast<unsigned char *>(data),
- length,
- sample_rate_p, &channels);
+ uint8_t channels;
+ int32_t nbytes = NeAACDecInit(decoder,
+ /* deconst hack, libfaad requires this */
+ const_cast<uint8_t *>(data.data),
+ data.size,
+ sample_rate_p, &channels);
if (nbytes < 0) {
error.Set(faad_decoder_domain, "Not an AAC stream");
return false;
@@ -286,16 +264,14 @@ static const void *
faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
NeAACDecFrameInfo *frame_info)
{
- size_t length;
- const unsigned char *data = (const unsigned char *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr)
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty())
return nullptr;
return NeAACDecDecode(decoder, frame_info,
/* deconst hack, libfaad requires this */
- const_cast<unsigned char *>(data),
- length);
+ const_cast<uint8_t *>(data.data),
+ data.size);
}
/**
@@ -306,17 +282,12 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
static float
faad_get_file_time_float(InputStream &is)
{
- DecoderBuffer *buffer;
- float length;
-
- buffer = decoder_buffer_new(nullptr, is,
- FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- length = faad_song_duration(buffer, is);
+ DecoderBuffer *buffer =
+ decoder_buffer_new(nullptr, is,
+ FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
+ float length = faad_song_duration(buffer, is);
if (length < 0) {
- bool ret;
- AudioFormat audio_format;
-
NeAACDecHandle decoder = NeAACDecOpen();
NeAACDecConfigurationPtr config =
@@ -326,9 +297,9 @@ faad_get_file_time_float(InputStream &is)
decoder_buffer_fill(buffer);
- ret = faad_decoder_init(decoder, buffer, audio_format,
- IgnoreError());
- if (ret)
+ AudioFormat audio_format;
+ if (faad_decoder_init(decoder, buffer, audio_format,
+ IgnoreError()))
length = 0;
NeAACDecClose(decoder);
@@ -359,15 +330,10 @@ faad_get_file_time(InputStream &is)
static void
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
{
- float total_time = 0;
- AudioFormat audio_format;
- bool ret;
- uint16_t bit_rate = 0;
- DecoderBuffer *buffer;
-
- buffer = decoder_buffer_new(&mpd_decoder, is,
- FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- total_time = faad_song_duration(buffer, is);
+ DecoderBuffer *buffer =
+ decoder_buffer_new(&mpd_decoder, is,
+ FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
+ const float total_time = faad_song_duration(buffer, is);
/* create the libfaad decoder */
@@ -389,8 +355,8 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* initialize it */
Error error;
- ret = faad_decoder_init(decoder, buffer, audio_format, error);
- if (!ret) {
+ AudioFormat audio_format;
+ if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
LogError(error);
NeAACDecClose(decoder);
decoder_buffer_free(buffer);
@@ -404,6 +370,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* the decoder loop */
DecoderCommand cmd;
+ unsigned bit_rate = 0;
do {
size_t frame_size;
const void *decoded;
@@ -483,7 +450,7 @@ static const char *const faad_mime_types[] = {
"audio/aac", "audio/aacp", nullptr
};
-const struct DecoderPlugin faad_decoder_plugin = {
+const DecoderPlugin faad_decoder_plugin = {
"faad",
nullptr,
nullptr,
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx
index 47e1a3384..01b551bb1 100644
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/FfmpegDecoderPlugin.cxx
@@ -38,7 +38,6 @@ extern "C" {
#include <libavutil/avutil.h>
#include <libavutil/log.h>
#include <libavutil/mathematics.h>
-#include <libavutil/dict.h>
}
#include <assert.h>
diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx
index 0fd73df04..998cdf5a8 100644
--- a/src/decoder/FfmpegMetaData.hxx
+++ b/src/decoder/FfmpegMetaData.hxx
@@ -21,8 +21,6 @@
#define MPD_FFMPEG_METADATA_HXX
extern "C" {
-#include <libavformat/avformat.h>
-#include <libavutil/avutil.h>
#include <libavutil/dict.h>
}
@@ -35,6 +33,6 @@ struct tag_handler;
void
ffmpeg_scan_dictionary(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx
index e4b906c12..b0921056a 100644
--- a/src/decoder/FlacCommon.cxx
+++ b/src/decoder/FlacCommon.cxx
@@ -25,14 +25,10 @@
#include "FlacCommon.hxx"
#include "FlacMetadata.hxx"
#include "FlacPcm.hxx"
-#include "MixRampInfo.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include "Log.hxx"
-#include <assert.h>
-
flac_data::flac_data(Decoder &_decoder,
InputStream &_input_stream)
:FlacInput(_input_stream, &_decoder),
@@ -107,8 +103,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
decoder_mixramp(data->decoder, flac_parse_mixramp(block));
- flac_vorbis_comments_to_tag(data->tag,
- &block->data.vorbis_comment);
+ data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment);
+ break;
default:
break;
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx
index de000dfa1..0f6b09973 100644
--- a/src/decoder/FlacCommon.hxx
+++ b/src/decoder/FlacCommon.hxx
@@ -29,7 +29,6 @@
#include "pcm/PcmBuffer.hxx"
#include <FLAC/stream_decoder.h>
-#include <FLAC/metadata.h>
struct flac_data : public FlacInput {
PcmBuffer buffer;
diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx
index 1b5734434..2c811a8e0 100644
--- a/src/decoder/FlacDecoderPlugin.cxx
+++ b/src/decoder/FlacDecoderPlugin.cxx
@@ -26,10 +26,6 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
-
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
#error libFLAC is too old
#endif
diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/FlacMetadata.cxx
index 17cc4cd8d..96dbf2db0 100644
--- a/src/decoder/FlacMetadata.cxx
+++ b/src/decoder/FlacMetadata.cxx
@@ -21,43 +21,45 @@
#include "FlacMetadata.hxx"
#include "XiphTags.hxx"
#include "MixRampInfo.hxx"
-#include "tag/Tag.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagTable.hxx"
#include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
-#include <glib.h>
-
-#include <assert.h>
#include <string.h>
+static const char *
+vorbis_comment_value(const FLAC__StreamMetadata *block,
+ const char *name)
+{
+ int offset =
+ FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
+ name);
+ if (offset < 0)
+ return nullptr;
+
+ size_t name_length = strlen(name);
+
+ const FLAC__StreamMetadata_VorbisComment_Entry &vc =
+ block->data.vorbis_comment.comments[offset];
+ const char *comment = (const char *)vc.entry;
+
+ /* 1 is for '=' */
+ return comment + name_length + 1;
+}
+
static bool
flac_find_float_comment(const FLAC__StreamMetadata *block,
const char *cmnt, float *fl)
{
- int offset;
- size_t pos;
- int len;
- unsigned char tmp, *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
- return false;
-
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
+ const char *value = vorbis_comment_value(block, cmnt);
+ if (value == nullptr)
return false;
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- tmp = p[len];
- p[len] = '\0';
- *fl = (float)atof((char *)p);
- p[len] = tmp;
-
+ *fl = (float)atof(value);
return true;
}
@@ -88,23 +90,11 @@ gcc_pure
static std::string
flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt)
{
- int offset;
- size_t pos;
- int len;
- const unsigned char *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
+ const char *value = vorbis_comment_value(block, cmnt);
+ if (value == nullptr)
return std::string();
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
- return std::string();
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- return std::string((const char *)p, len);
+ return std::string(value);
}
MixRampInfo
@@ -118,21 +108,19 @@ flac_parse_mixramp(const FLAC__StreamMetadata *block)
/**
* Checks if the specified name matches the entry's name, and if yes,
- * returns the comment value (not null-temrinated).
+ * returns the comment value;
*/
static const char *
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, size_t *length_r)
+ const char *name)
{
size_t name_length = strlen(name);
const char *comment = (const char*)entry->entry;
- if (entry->length <= name_length ||
- !StringEqualsCaseASCII(comment, name, name_length))
+ if (!StringEqualsCaseASCII(comment, name, name_length))
return nullptr;
if (comment[name_length] == '=') {
- *length_r = entry->length - name_length - 1;
return comment + name_length + 1;
}
@@ -148,14 +136,9 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, TagType tag_type,
const struct tag_handler *handler, void *handler_ctx)
{
- const char *value;
- size_t value_length;
-
- value = flac_comment_value(entry, name, &value_length);
+ const char *value = flac_comment_value(entry, name);
if (value != nullptr) {
- char *p = g_strndup(value, value_length);
- tag_handler_invoke_tag(handler, handler_ctx, tag_type, p);
- g_free(p);
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
return true;
}
@@ -167,16 +150,12 @@ flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const struct tag_handler *handler, void *handler_ctx)
{
if (handler->pair != nullptr) {
- char *name = g_strdup((const char*)entry->entry);
- char *value = strchr(name, '=');
-
- if (value != nullptr && value > name) {
- *value++ = 0;
+ const char *comment = (const char *)entry->entry;
+ const SplitString split(comment, '=');
+ if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
- name, value);
- }
-
- g_free(name);
+ split.GetFirst(),
+ split.GetSecond());
}
for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
@@ -221,13 +200,12 @@ flac_scan_metadata(const FLAC__StreamMetadata *block,
}
}
-void
-flac_vorbis_comments_to_tag(Tag &tag,
- const FLAC__StreamMetadata_VorbisComment *comment)
+Tag
+flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment)
{
TagBuilder tag_builder;
flac_scan_comments(comment, &add_tag_handler, &tag_builder);
- tag_builder.Commit(tag);
+ return tag_builder.Commit();
}
void
diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/FlacMetadata.hxx
index 96c61b8e6..33ef17e72 100644
--- a/src/decoder/FlacMetadata.hxx
+++ b/src/decoder/FlacMetadata.hxx
@@ -27,6 +27,7 @@
#include <assert.h>
+struct tag_handler;
class MixRampInfo;
class FlacMetadataChain {
@@ -81,7 +82,7 @@ public:
return FLAC__Metadata_ChainStatusString[GetStatus()];
}
- void Scan(const struct tag_handler *handler, void *handler_ctx);
+ void Scan(const tag_handler *handler, void *handler_ctx);
};
class FLACMetadataIterator {
@@ -110,7 +111,6 @@ public:
}
};
-struct tag_handler;
struct Tag;
struct ReplayGainInfo;
@@ -130,12 +130,11 @@ flac_parse_replay_gain(ReplayGainInfo &rgi,
MixRampInfo
flac_parse_mixramp(const FLAC__StreamMetadata *block);
-void
-flac_vorbis_comments_to_tag(Tag &tag,
- const FLAC__StreamMetadata_VorbisComment *comment);
+Tag
+flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment);
void
flac_scan_metadata(const FLAC__StreamMetadata *block,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx
index 815fd8d69..aafc8f07d 100644
--- a/src/decoder/GmeDecoderPlugin.cxx
+++ b/src/decoder/GmeDecoderPlugin.cxx
@@ -22,6 +22,7 @@
#include "DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
+#include "util/Alloc.hxx"
#include "util/FormatString.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
@@ -53,7 +54,7 @@ static char *
get_container_name(const char *path_fs)
{
const char *subtune_suffix = uri_get_suffix(path_fs);
- char *path_container = g_strdup(path_fs);
+ char *path_container = xstrdup(path_fs);
char pat[64];
snprintf(pat, sizeof(pat), "%s%s",
@@ -137,7 +138,7 @@ gme_file_decode(Decoder &decoder, const char *path_fs)
Music_Emu *emu;
const char *gme_err =
gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- g_free(path_container);
+ free(path_container);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
return;
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx
index 9dd86c55f..76eead96d 100644
--- a/src/decoder/MadDecoderPlugin.cxx
+++ b/src/decoder/MadDecoderPlugin.cxx
@@ -26,23 +26,23 @@
#include "tag/TagRva2.hxx"
#include "tag/TagHandler.hxx"
#include "CheckAudioFormat.hxx"
+#include "util/StringUtil.hxx"
#include "util/ASCII.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <assert.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <glib.h>
#include <mad.h>
#ifdef HAVE_ID3TAG
#include <id3tag.h>
#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
#define FRAMES_CUSHION 2000
#define READ_BUFFER_SIZE 40960
@@ -349,24 +349,14 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
id3_data = stream.this_frame;
mad_stream_skip(&(stream), tagsize);
} else {
- allocated = (id3_byte_t *)g_malloc(tagsize);
+ allocated = new id3_byte_t[tagsize];
memcpy(allocated, stream.this_frame, count);
mad_stream_skip(&(stream), count);
- while (count < tagsize) {
- size_t len;
-
- len = decoder_read(decoder, input_stream,
- allocated + count, tagsize - count);
- if (len == 0)
- break;
- else
- count += len;
- }
-
- if (count != tagsize) {
+ if (!decoder_read_full(decoder, input_stream,
+ allocated + count, tagsize - count)) {
LogDebug(mad_domain, "error parsing ID3 tag");
- g_free(allocated);
+ delete[] allocated;
return;
}
@@ -375,7 +365,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
id3_tag = id3_tag_parse(id3_data, tagsize);
if (id3_tag == nullptr) {
- g_free(allocated);
+ delete[] allocated;
return;
}
@@ -400,7 +390,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
id3_tag_delete(id3_tag);
- g_free(allocated);
+ delete[] allocated;
#else /* !HAVE_ID3TAG */
(void)mpd_tag;
@@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
mad_stream_skip(&stream, tagsize);
} else {
mad_stream_skip(&stream, count);
-
- while (count < tagsize) {
- size_t len = tagsize - count;
- char ignored[1024];
- if (len > sizeof(ignored))
- len = sizeof(ignored);
-
- len = decoder_read(decoder, input_stream,
- ignored, len);
- if (len == 0)
- break;
- else
- count += len;
- }
+ decoder_skip(decoder, input_stream, tagsize - count);
}
#endif
}
@@ -690,7 +667,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
/* This is technically incorrect, since the encoder might not be lame.
* But there's no other way to determine if this is a lame tag, and we
* wouldn't want to go reading a tag that's not there. */
- if (!g_str_has_prefix(lame->encoder, "LAME"))
+ if (!StringStartsWith(lame->encoder, "LAME"))
return false;
if (sscanf(lame->encoder+4, "%u.%u",
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx
index 34381aafa..93a3cc280 100644
--- a/src/decoder/MikmodDecoderPlugin.cxx
+++ b/src/decoder/MikmodDecoderPlugin.cxx
@@ -25,8 +25,8 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <mikmod.h>
+
#include <assert.h>
static constexpr Domain mikmod_domain("mikmod");
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx
index dc258623c..620bfad30 100644
--- a/src/decoder/MpcdecDecoderPlugin.cxx
+++ b/src/decoder/MpcdecDecoderPlugin.cxx
@@ -30,8 +30,6 @@
#include <mpc/mpcdec.h>
-#include <assert.h>
-#include <unistd.h>
#include <math.h>
struct mpc_decoder_data {
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx
index df23f7847..895c8b865 100644
--- a/src/decoder/Mpg123DecoderPlugin.cxx
+++ b/src/decoder/Mpg123DecoderPlugin.cxx
@@ -26,9 +26,8 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <mpg123.h>
+
#include <stdio.h>
static constexpr Domain mpg123_domain("mpg123");
diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx
index 565dbafcf..cc6cd20c0 100644
--- a/src/decoder/OggCodec.cxx
+++ b/src/decoder/OggCodec.cxx
@@ -23,6 +23,7 @@
#include "config.h"
#include "OggCodec.hxx"
+#include "DecoderAPI.hxx"
#include <string.h>
diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx
index 857871607..13eab2cdb 100644
--- a/src/decoder/OggCodec.hxx
+++ b/src/decoder/OggCodec.hxx
@@ -24,7 +24,8 @@
#ifndef MPD_OGG_CODEC_HXX
#define MPD_OGG_CODEC_HXX
-#include "DecoderAPI.hxx"
+struct Decoder;
+struct InputStream;
enum ogg_codec {
OGG_CODEC_UNKNOWN,
diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx
index ad51ccdf3..a329e42a4 100644
--- a/src/decoder/OggFind.hxx
+++ b/src/decoder/OggFind.hxx
@@ -25,7 +25,6 @@
#include <ogg/ogg.h>
-struct InputStream;
class OggSyncState;
/**
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx
index f3d7b342e..04f549f4f 100644
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ b/src/decoder/OpusDecoderPlugin.cxx
@@ -22,12 +22,10 @@
#include "OpusDomain.hxx"
#include "OpusHead.hxx"
#include "OpusTags.hxx"
-#include "OggUtil.hxx"
#include "OggFind.hxx"
#include "OggSyncState.hxx"
#include "DecoderAPI.hxx"
#include "OggCodec.hxx"
-#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagBuilder.hxx"
#include "InputStream.hxx"
@@ -294,8 +292,7 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
!tag_builder.IsEmpty()) {
decoder_replay_gain(decoder, &rgi);
- Tag tag;
- tag_builder.Commit(tag);
+ Tag tag = tag_builder.Commit();
cmd = decoder_tag(decoder, input_stream, std::move(tag));
} else
cmd = decoder_get_command(decoder);
diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx
index 0417d3905..dd2b125b0 100644
--- a/src/decoder/OpusHead.cxx
+++ b/src/decoder/OpusHead.cxx
@@ -21,7 +21,6 @@
#include "OpusHead.hxx"
#include <stdint.h>
-#include <string.h>
struct OpusHead {
char signature[8];
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx
index dbc38fb76..0bef8dc69 100644
--- a/src/decoder/PcmDecoderPlugin.cxx
+++ b/src/decoder/PcmDecoderPlugin.cxx
@@ -25,8 +25,6 @@
#include "util/ByteReverse.hxx"
#include "Log.hxx"
-#include <glib.h>
-#include <unistd.h>
#include <string.h>
#include <stdio.h> /* for SEEK_SET */
diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/SidplayDecoderPlugin.cxx
index 160337594..9b3a4ad40 100644
--- a/src/decoder/SidplayDecoderPlugin.cxx
+++ b/src/decoder/SidplayDecoderPlugin.cxx
@@ -21,6 +21,7 @@
#include "SidplayDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
+#include "util/Alloc.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "Log.hxx"
@@ -121,7 +122,7 @@ sidplay_finish()
static char *
get_container_name(const char *path_fs)
{
- char *path_container=g_strdup(path_fs);
+ char *path_container = strdup(path_fs);
if(!g_pattern_match(path_with_subtune,
strlen(path_container), path_container, nullptr))
@@ -163,9 +164,9 @@ get_song_length(const char *path_fs)
if (songlength_database == nullptr)
return -1;
- gchar *sid_file=get_container_name(path_fs);
+ char *sid_file = get_container_name(path_fs);
SidTuneMod tune(sid_file);
- g_free(sid_file);
+ free(sid_file);
if(!tune) {
LogWarning(sidplay_domain,
"failed to load file for calculating md5 sum");
diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx
index d4f019b58..e6a99ca6b 100644
--- a/src/decoder/VorbisComments.cxx
+++ b/src/decoder/VorbisComments.cxx
@@ -20,16 +20,13 @@
#include "config.h"
#include "VorbisComments.hxx"
#include "XiphTags.hxx"
-#include "tag/Tag.hxx"
#include "tag/TagTable.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagBuilder.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
-#include <glib.h>
-
-#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
@@ -104,16 +101,11 @@ vorbis_scan_comment(const char *comment,
const struct tag_handler *handler, void *handler_ctx)
{
if (handler->pair != nullptr) {
- char *name = g_strdup((const char*)comment);
- char *value = strchr(name, '=');
-
- if (value != nullptr && value > name) {
- *value++ = 0;
+ const SplitString split(comment, '=');
+ if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
- name, value);
- }
-
- g_free(name);
+ split.GetFirst(),
+ split.GetSecond());
}
for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
@@ -145,5 +137,5 @@ vorbis_comments_to_tag(char **comments)
vorbis_comments_scan(comments, &add_tag_handler, &tag_builder);
return tag_builder.IsEmpty()
? nullptr
- : tag_builder.Commit();
+ : tag_builder.CommitNew();
}
diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx
index e5a48ef6b..f537bf30f 100644
--- a/src/decoder/VorbisComments.hxx
+++ b/src/decoder/VorbisComments.hxx
@@ -31,7 +31,7 @@ vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments);
void
vorbis_comments_scan(char **comments,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
Tag *
vorbis_comments_to_tag(char **comments);
diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx
index 4d3e48528..a637241b1 100644
--- a/src/decoder/VorbisDecoderPlugin.cxx
+++ b/src/decoder/VorbisDecoderPlugin.cxx
@@ -25,9 +25,7 @@
#include "InputStream.hxx"
#include "OggCodec.hxx"
#include "util/Error.hxx"
-#include "util/UriUtil.hxx"
#include "util/Macros.hxx"
-#include "system/ByteOrder.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "Log.hxx"
@@ -48,7 +46,6 @@
#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
#endif /* HAVE_TREMOR */
-#include <assert.h>
#include <errno.h>
struct vorbis_input_stream {
diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx
index a35edd041..7946bf52a 100644
--- a/src/decoder/VorbisDomain.hxx
+++ b/src/decoder/VorbisDomain.hxx
@@ -22,6 +22,8 @@
#include "check.h"
-extern const class Domain vorbis_domain;
+class Domain;
+
+extern const Domain vorbis_domain;
#endif
diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx
index fa7ed992d..91c9382ad 100644
--- a/src/encoder/FlacEncoderPlugin.cxx
+++ b/src/encoder/FlacEncoderPlugin.cxx
@@ -23,16 +23,10 @@
#include "AudioFormat.hxx"
#include "pcm/PcmBuffer.hxx"
#include "ConfigError.hxx"
+#include "util/Manual.hxx"
+#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "util/fifo_buffer.h"
-
-extern "C" {
-#include "util/growing_fifo.h"
-}
-
-#include <assert.h>
-#include <string.h>
#include <FLAC/stream_encoder.h>
@@ -54,7 +48,7 @@ struct flac_encoder {
* This buffer will hold encoded data from libFLAC until it is
* picked up with flac_encoder_read().
*/
- struct fifo_buffer *output_buffer;
+ Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
flac_encoder():encoder(flac_encoder_plugin) {}
};
@@ -141,7 +135,7 @@ flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse,
struct flac_encoder *encoder = (struct flac_encoder *) client_data;
//transfer data to buffer
- growing_fifo_append(&encoder->output_buffer, data, bytes);
+ encoder->output_buffer->Append((const uint8_t *)data, bytes);
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
}
@@ -154,7 +148,7 @@ flac_encoder_close(Encoder *_encoder)
FLAC__stream_encoder_delete(encoder->fse);
encoder->expand_buffer.Clear();
- fifo_buffer_free(encoder->output_buffer);
+ encoder->output_buffer.Destruct();
}
static bool
@@ -196,7 +190,7 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
return false;
}
- encoder->output_buffer = growing_fifo_new();
+ encoder->output_buffer.Construct(8192);
/* this immediately outputs data through callback */
@@ -305,18 +299,7 @@ flac_encoder_read(Encoder *_encoder, void *dest, size_t length)
{
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
- size_t max_length;
- const char *src = (const char *)
- fifo_buffer_read(encoder->output_buffer, &max_length);
- if (src == nullptr)
- return 0;
-
- if (length > max_length)
- length = max_length;
-
- memcpy(dest, src, length);
- fifo_buffer_consume(encoder->output_buffer, length);
- return length;
+ return encoder->output_buffer->Read((uint8_t *)dest, length);
}
static const char *
diff --git a/src/encoder/NullEncoderPlugin.cxx b/src/encoder/NullEncoderPlugin.cxx
index 3b1aae5e2..d35d880d4 100644
--- a/src/encoder/NullEncoderPlugin.cxx
+++ b/src/encoder/NullEncoderPlugin.cxx
@@ -20,21 +20,19 @@
#include "config.h"
#include "NullEncoderPlugin.hxx"
#include "EncoderAPI.hxx"
-#include "util/fifo_buffer.h"
-extern "C" {
-#include "util/growing_fifo.h"
-}
+#include "util/Manual.hxx"
+#include "util/DynamicFifoBuffer.hxx"
#include "Compiler.h"
#include <assert.h>
-#include <string.h>
struct NullEncoder final {
Encoder encoder;
- struct fifo_buffer *buffer;
+ Manual<DynamicFifoBuffer<uint8_t>> buffer;
- NullEncoder():encoder(null_encoder_plugin) {}
+ NullEncoder()
+ :encoder(null_encoder_plugin) {}
};
static Encoder *
@@ -58,7 +56,7 @@ null_encoder_close(Encoder *_encoder)
{
NullEncoder *encoder = (NullEncoder *)_encoder;
- fifo_buffer_free(encoder->buffer);
+ encoder->buffer.Destruct();
}
@@ -68,7 +66,7 @@ null_encoder_open(Encoder *_encoder,
gcc_unused Error &error)
{
NullEncoder *encoder = (NullEncoder *)_encoder;
- encoder->buffer = growing_fifo_new();
+ encoder->buffer.Construct(8192);
return true;
}
@@ -79,7 +77,7 @@ null_encoder_write(Encoder *_encoder,
{
NullEncoder *encoder = (NullEncoder *)_encoder;
- growing_fifo_append(&encoder->buffer, data, length);
+ encoder->buffer->Append((const uint8_t *)data, length);
return length;
}
@@ -88,17 +86,7 @@ null_encoder_read(Encoder *_encoder, void *dest, size_t length)
{
NullEncoder *encoder = (NullEncoder *)_encoder;
- size_t max_length;
- const void *src = fifo_buffer_read(encoder->buffer, &max_length);
- if (src == nullptr)
- return 0;
-
- if (length > max_length)
- length = max_length;
-
- memcpy(dest, src, length);
- fifo_buffer_consume(encoder->buffer, length);
- return length;
+ return encoder->buffer->Read((uint8_t *)dest, length);
}
const EncoderPlugin null_encoder_plugin = {
diff --git a/src/encoder/ShineEncoderPlugin.cxx b/src/encoder/ShineEncoderPlugin.cxx
new file mode 100644
index 000000000..009ffd1b9
--- /dev/null
+++ b/src/encoder/ShineEncoderPlugin.cxx
@@ -0,0 +1,257 @@
+/*
+ * 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 "ShineEncoderPlugin.hxx"
+#include "config.h"
+#include "EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "ConfigError.hxx"
+#include "util/Manual.hxx"
+#include "util/NumberParser.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+extern "C"
+{
+#include <shine/layer3.h>
+}
+
+static constexpr size_t BUFFER_INIT_SIZE = 8192;
+static constexpr unsigned CHANNELS = 2;
+
+struct ShineEncoder {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+
+ shine_t shine;
+
+ shine_config_t config;
+
+ uint16_t frame_size;
+ int16_t buffer[SHINE_MAX_SAMPLES * CHANNELS];
+ int16_t channels[CHANNELS][SHINE_MAX_SAMPLES];
+ int16_t *stereo[CHANNELS];
+
+ Manual<DynamicFifoBuffer<int16_t>> input_buffer;
+ size_t input_size;
+
+ Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
+
+ ShineEncoder()
+ :encoder(shine_encoder_plugin),
+ stereo { channels[0], channels[1] },
+ input_size(0) {}
+
+ bool Configure(const config_param &param, Error &error);
+
+ bool Setup(Error &error);
+
+ bool WriteChunks(bool flush, Error &error);
+};
+
+static constexpr Domain shine_encoder_domain("shine_encoder");
+
+inline bool
+ShineEncoder::Configure(const config_param &param,
+ gcc_unused Error &error)
+{
+ shine_set_config_mpeg_defaults(&config.mpeg);
+ config.mpeg.bitr = param.GetBlockValue("bitrate", 128);
+
+ return true;
+}
+
+static Encoder *
+shine_encoder_init(const config_param &param, Error &error)
+{
+ ShineEncoder *encoder = new ShineEncoder();
+
+ /* load configuration from "param" */
+ if (!encoder->Configure(param, error)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+shine_encoder_finish(Encoder *_encoder)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ delete encoder;
+}
+
+inline bool
+ShineEncoder::Setup(Error &error)
+{
+ input_size = 0;
+ config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO;
+ config.wave.samplerate = audio_format.sample_rate;
+ config.wave.channels = audio_format.channels == 2 ? PCM_STEREO : PCM_MONO;
+
+ if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
+ error.Format(config_domain,
+ "error configuring shine. samplerate %d and bitrate %d configuration not supported.",
+ config.wave.samplerate,
+ config.mpeg.bitr);
+
+ return false;
+ }
+
+ shine = shine_initialise(&config);
+
+ if (!shine) {
+ error.Format(config_domain,
+ "error initializing shine.");
+
+ return false;
+ }
+
+ frame_size = shine_samples_per_pass(shine);
+
+ return true;
+}
+
+static bool
+shine_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ audio_format.format = SampleFormat::S16;
+ audio_format.channels = CHANNELS;
+ encoder->audio_format = audio_format;
+
+ if (!encoder->Setup(error))
+ return false;
+
+ encoder->input_buffer.Construct(BUFFER_INIT_SIZE);
+ encoder->output_buffer.Construct(BUFFER_INIT_SIZE);
+
+ return true;
+}
+
+static void
+shine_encoder_close(Encoder *_encoder)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ shine_close(encoder->shine);
+ encoder->input_buffer.Destruct();
+ encoder->output_buffer.Destruct();
+}
+
+bool
+ShineEncoder::WriteChunks(bool flush, gcc_unused Error &error)
+{
+ const size_t chunk_size = frame_size * audio_format.channels;
+
+ /*
+ * shine requires data in specific sizes, therefore we need to
+ * buffer the received data and encode in chunks.
+ */
+ while (input_size >= chunk_size || (flush && input_size > 0)) {
+ long written;
+
+ const size_t read = input_buffer->Read(buffer, chunk_size);
+ input_size -= read;
+
+ /* de-interleave data */
+ for (size_t i = 0; i < read / audio_format.channels; i++) {
+ channels[0][i] = buffer[i * audio_format.channels];
+ channels[1][i] = buffer[i * audio_format.channels + 1];
+ }
+
+ if (flush) {
+ /* fill remaining with 0s */
+ for (size_t i = read / audio_format.channels; i < frame_size; i++) {
+ channels[0][i] = channels[1][i] = 0;
+ }
+ }
+
+ const uint8_t *out = shine_encode_buffer(shine, stereo, &written);
+
+ if (written > 0)
+ output_buffer->Append((const uint8_t *)out, written);
+ }
+
+ return true;
+}
+
+static bool
+shine_encoder_write(Encoder *_encoder,
+ const void *_data, size_t length,
+ gcc_unused Error &error)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+ const int16_t *data = (const int16_t*)_data;
+
+ encoder->input_buffer->Append(data, length / sizeof(*data));
+ encoder->input_size += length / sizeof(*data);
+
+ return encoder->WriteChunks(false, error);
+}
+
+static bool
+shine_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+ long written;
+
+ encoder->WriteChunks(true, error);
+ const uint8_t *data = shine_flush(encoder->shine, &written);
+
+ if (written > 0)
+ encoder->output_buffer->Append(data, written);
+
+ return true;
+}
+
+static size_t
+shine_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ return encoder->output_buffer->Read((uint8_t *)dest, length);
+}
+
+static const char *
+shine_encoder_get_mime_type(gcc_unused Encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
+const EncoderPlugin shine_encoder_plugin = {
+ "shine",
+ shine_encoder_init,
+ shine_encoder_finish,
+ shine_encoder_open,
+ shine_encoder_close,
+ shine_encoder_flush,
+ shine_encoder_flush,
+ nullptr,
+ nullptr,
+ shine_encoder_write,
+ shine_encoder_read,
+ shine_encoder_get_mime_type,
+};
diff --git a/src/encoder/ShineEncoderPlugin.hxx b/src/encoder/ShineEncoderPlugin.hxx
new file mode 100644
index 000000000..8b1520a74
--- /dev/null
+++ b/src/encoder/ShineEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_ENCODER_SHINE_HXX
+#define MPD_ENCODER_SHINE_HXX
+
+extern const struct EncoderPlugin shine_encoder_plugin;
+
+#endif
diff --git a/src/encoder/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx
index 5b40aaea1..830910288 100644
--- a/src/encoder/VorbisEncoderPlugin.cxx
+++ b/src/encoder/VorbisEncoderPlugin.cxx
@@ -33,8 +33,6 @@
#include <glib.h>
-#include <assert.h>
-
struct vorbis_encoder {
/** the base class */
Encoder encoder;
diff --git a/src/encoder/WaveEncoderPlugin.cxx b/src/encoder/WaveEncoderPlugin.cxx
index acae0be9e..083d25f95 100644
--- a/src/encoder/WaveEncoderPlugin.cxx
+++ b/src/encoder/WaveEncoderPlugin.cxx
@@ -21,10 +21,8 @@
#include "WaveEncoderPlugin.hxx"
#include "EncoderAPI.hxx"
#include "system/ByteOrder.hxx"
-#include "util/fifo_buffer.h"
-extern "C" {
-#include "util/growing_fifo.h"
-}
+#include "util/Manual.hxx"
+#include "util/DynamicFifoBuffer.hxx"
#include <assert.h>
#include <string.h>
@@ -33,7 +31,7 @@ struct WaveEncoder {
Encoder encoder;
unsigned bits;
- struct fifo_buffer *buffer;
+ Manual<DynamicFifoBuffer<uint8_t>> buffer;
WaveEncoder():encoder(wave_encoder_plugin) {}
};
@@ -128,9 +126,11 @@ wave_encoder_open(Encoder *_encoder,
break;
}
- encoder->buffer = growing_fifo_new();
- wave_header *header = (wave_header *)
- growing_fifo_write(&encoder->buffer, sizeof(*header));
+ encoder->buffer.Construct(8192);
+
+ auto range = encoder->buffer->Write();
+ assert(range.size >= sizeof(wave_header));
+ wave_header *header = (wave_header *)range.data;
/* create PCM wave header in initial buffer */
fill_wave_header(header,
@@ -138,7 +138,8 @@ wave_encoder_open(Encoder *_encoder,
encoder->bits,
audio_format.sample_rate,
(encoder->bits / 8) * audio_format.channels);
- fifo_buffer_append(encoder->buffer, sizeof(*header));
+
+ encoder->buffer->Append(sizeof(*header));
return true;
}
@@ -148,7 +149,7 @@ wave_encoder_close(Encoder *_encoder)
{
WaveEncoder *encoder = (WaveEncoder *)_encoder;
- fifo_buffer_free(encoder->buffer);
+ encoder->buffer.Destruct();
}
static size_t
@@ -198,7 +199,7 @@ wave_encoder_write(Encoder *_encoder,
{
WaveEncoder *encoder = (WaveEncoder *)_encoder;
- uint8_t *dst = (uint8_t *)growing_fifo_write(&encoder->buffer, length);
+ uint8_t *dst = encoder->buffer->Write(length);
if (IsLittleEndian()) {
switch (encoder->bits) {
@@ -230,7 +231,7 @@ wave_encoder_write(Encoder *_encoder,
}
}
- fifo_buffer_append(encoder->buffer, length);
+ encoder->buffer->Append(length);
return true;
}
@@ -239,17 +240,7 @@ wave_encoder_read(Encoder *_encoder, void *dest, size_t length)
{
WaveEncoder *encoder = (WaveEncoder *)_encoder;
- size_t max_length;
- const void *src = fifo_buffer_read(encoder->buffer, &max_length);
- if (src == NULL)
- return 0;
-
- if (length > max_length)
- length = max_length;
-
- memcpy(dest, src, length);
- fifo_buffer_consume(encoder->buffer, length);
- return length;
+ return encoder->buffer->Read((uint8_t *)dest, length);
}
static const char *
diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx
index 92e350e85..0f71bd941 100644
--- a/src/event/BufferedSocket.cxx
+++ b/src/event/BufferedSocket.cxx
@@ -22,6 +22,9 @@
#include "system/SocketError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "Compiler.h"
+
+#include <algorithm>
BufferedSocket::ssize_t
BufferedSocket::DirectRead(void *data, size_t length)
diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx
index db920f981..c31d7953e 100644
--- a/src/event/BufferedSocket.hxx
+++ b/src/event/BufferedSocket.hxx
@@ -23,13 +23,12 @@
#include "check.h"
#include "SocketMonitor.hxx"
#include "util/FifoBuffer.hxx"
-#include "Compiler.h"
#include <assert.h>
#include <stdint.h>
-struct fifo_buffer;
class Error;
+class EventLoop;
/**
* A #SocketMonitor specialization that adds an input buffer.
diff --git a/src/event/Call.cxx b/src/event/Call.cxx
index ab1d5ffbd..bc6106bf7 100644
--- a/src/event/Call.cxx
+++ b/src/event/Call.cxx
@@ -28,9 +28,7 @@
#include <assert.h>
class BlockingCallMonitor final
-#ifndef USE_EPOLL
: DeferredMonitor
-#endif
{
const std::function<void()> f;
@@ -40,24 +38,13 @@ class BlockingCallMonitor final
bool done;
public:
-#ifdef USE_EPOLL
- BlockingCallMonitor(EventLoop &loop, std::function<void()> &&_f)
- :f(std::move(_f)), done(false) {
- loop.AddCall([this](){
- this->DoRun();
- });
- }
-#else
BlockingCallMonitor(EventLoop &_loop, std::function<void()> &&_f)
:DeferredMonitor(_loop), f(std::move(_f)), done(false) {}
-#endif
void Run() {
-#ifndef USE_EPOLL
assert(!done);
Schedule();
-#endif
mutex.lock();
while (!done)
@@ -65,16 +52,8 @@ public:
mutex.unlock();
}
-#ifndef USE_EPOLL
private:
virtual void RunDeferred() override {
- DoRun();
- }
-
-#else
-public:
-#endif
- void DoRun() {
assert(!done);
f();
diff --git a/src/event/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx
index 4ffffaa89..ef7a98911 100644
--- a/src/event/DeferredMonitor.cxx
+++ b/src/event/DeferredMonitor.cxx
@@ -24,58 +24,11 @@
void
DeferredMonitor::Cancel()
{
-#ifdef USE_EPOLL
- pending = false;
-#else
- const auto id = source_id.exchange(0);
- if (id != 0)
- g_source_remove(id);
-#endif
+ loop.RemoveDeferred(*this);
}
void
DeferredMonitor::Schedule()
{
-#ifdef USE_EPOLL
- if (!pending.exchange(true))
- fd.Write();
-#else
- const unsigned id = loop.AddIdle(Callback, this);
- const auto old_id = source_id.exchange(id);
- if (old_id != 0)
- g_source_remove(old_id);
-#endif
+ loop.AddDeferred(*this);
}
-
-#ifdef USE_EPOLL
-
-bool
-DeferredMonitor::OnSocketReady(unsigned)
-{
- fd.Read();
-
- if (pending.exchange(false))
- RunDeferred();
-
- return true;
-}
-
-#else
-
-void
-DeferredMonitor::Run()
-{
- const auto id = source_id.exchange(0);
- if (id != 0)
- RunDeferred();
-}
-
-gboolean
-DeferredMonitor::Callback(gpointer data)
-{
- DeferredMonitor &monitor = *(DeferredMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/DeferredMonitor.hxx b/src/event/DeferredMonitor.hxx
index 2380fb66f..293fc5ea2 100644
--- a/src/event/DeferredMonitor.hxx
+++ b/src/event/DeferredMonitor.hxx
@@ -23,61 +23,31 @@
#include "check.h"
#include "Compiler.h"
-#ifdef USE_EPOLL
-#include "SocketMonitor.hxx"
-#include "WakeFD.hxx"
-#else
-#include <glib.h>
-#endif
-
#include <atomic>
class EventLoop;
/**
* Defer execution of an event into an #EventLoop.
+ *
+ * This class is thread-safe.
*/
-class DeferredMonitor
-#ifdef USE_EPOLL
- : private SocketMonitor
-#endif
-{
-#ifdef USE_EPOLL
- std::atomic_bool pending;
- WakeFD fd;
-#else
+class DeferredMonitor {
EventLoop &loop;
- std::atomic<guint> source_id;
-#endif
+ friend class EventLoop;
+ bool pending;
public:
-#ifdef USE_EPOLL
DeferredMonitor(EventLoop &_loop)
- :SocketMonitor(_loop), pending(false) {
- SocketMonitor::Open(fd.Get());
- SocketMonitor::Schedule(SocketMonitor::READ);
- }
-#else
- DeferredMonitor(EventLoop &_loop)
- :loop(_loop), source_id(0) {}
-#endif
+ :loop(_loop), pending(false) {}
~DeferredMonitor() {
-#ifdef USE_EPOLL
- /* avoid closing the WakeFD twice */
- SocketMonitor::Steal();
-#else
Cancel();
-#endif
}
EventLoop &GetEventLoop() {
-#ifdef USE_EPOLL
- return SocketMonitor::GetEventLoop();
-#else
return loop;
-#endif
}
void Schedule();
@@ -85,14 +55,6 @@ public:
protected:
virtual void RunDeferred() = 0;
-
-private:
-#ifdef USE_EPOLL
- virtual bool OnSocketReady(unsigned flags) override final;
-#else
- void Run();
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx
index 8b57b1308..375132e34 100644
--- a/src/event/FullyBufferedSocket.cxx
+++ b/src/event/FullyBufferedSocket.cxx
@@ -20,9 +20,9 @@
#include "config.h"
#include "FullyBufferedSocket.hxx"
#include "system/SocketError.hxx"
-#include "util/fifo_buffer.h"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "Compiler.h"
#include <assert.h>
#include <stdint.h>
@@ -59,15 +59,14 @@ FullyBufferedSocket::Flush()
{
assert(IsDefined());
- size_t length;
- const void *data = output.Read(&length);
- if (data == nullptr) {
+ const auto data = output.Read();
+ if (data.IsEmpty()) {
IdleMonitor::Cancel();
CancelWrite();
return true;
}
- auto nbytes = DirectWrite(data, length);
+ auto nbytes = DirectWrite(data.data, data.size);
if (gcc_unlikely(nbytes <= 0))
return nbytes == 0;
diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx
index c50bb5f61..083fab15e 100644
--- a/src/event/FullyBufferedSocket.hxx
+++ b/src/event/FullyBufferedSocket.hxx
@@ -24,7 +24,6 @@
#include "BufferedSocket.hxx"
#include "IdleMonitor.hxx"
#include "util/PeakBuffer.hxx"
-#include "Compiler.h"
/**
* A #BufferedSocket specialization that adds an output buffer.
diff --git a/src/event/IdleMonitor.cxx b/src/event/IdleMonitor.cxx
index c99c66b26..dd8d64f94 100644
--- a/src/event/IdleMonitor.cxx
+++ b/src/event/IdleMonitor.cxx
@@ -21,38 +21,31 @@
#include "IdleMonitor.hxx"
#include "Loop.hxx"
+#include <assert.h>
+
void
IdleMonitor::Cancel()
{
- assert(loop.IsInside());
+ assert(loop.IsInsideOrNull());
if (!IsActive())
return;
-#ifdef USE_EPOLL
active = false;
loop.RemoveIdle(*this);
-#else
- g_source_remove(source_id);
- source_id = 0;
-#endif
}
void
IdleMonitor::Schedule()
{
- assert(loop.IsInside());
+ assert(loop.IsInsideOrVirgin());
if (IsActive())
/* already scheduled */
return;
-#ifdef USE_EPOLL
active = true;
loop.AddIdle(*this);
-#else
- source_id = loop.AddIdle(Callback, this);
-#endif
}
void
@@ -60,25 +53,8 @@ IdleMonitor::Run()
{
assert(loop.IsInside());
-#ifdef USE_EPOLL
assert(active);
active = false;
-#else
- assert(source_id != 0);
- source_id = 0;
-#endif
OnIdle();
}
-
-#ifndef USE_EPOLL
-
-gboolean
-IdleMonitor::Callback(gpointer data)
-{
- IdleMonitor &monitor = *(IdleMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/IdleMonitor.hxx b/src/event/IdleMonitor.hxx
index c8e79eb1d..dac7ab5b0 100644
--- a/src/event/IdleMonitor.hxx
+++ b/src/event/IdleMonitor.hxx
@@ -22,41 +22,35 @@
#include "check.h"
-#ifndef USE_EPOLL
-#include <glib.h>
-#endif
-
class EventLoop;
/**
* An event that runs when the EventLoop has become idle, before
* waiting for more events. This class is not thread-safe; all
* methods must be run from EventLoop's thread.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
*/
class IdleMonitor {
-#ifdef USE_EPOLL
friend class EventLoop;
-#endif
EventLoop &loop;
-#ifdef USE_EPOLL
bool active;
-#else
- guint source_id;
-#endif
public:
-#ifdef USE_EPOLL
IdleMonitor(EventLoop &_loop)
:loop(_loop), active(false) {}
-#else
- IdleMonitor(EventLoop &_loop)
- :loop(_loop), source_id(0) {}
-#endif
~IdleMonitor() {
- Cancel();
+#ifndef NDEBUG
+ /* this check is redundant, it is only here to avoid
+ the assertion in Cancel() */
+ if (IsActive())
+#endif
+ Cancel();
}
EventLoop &GetEventLoop() const {
@@ -64,11 +58,7 @@ public:
}
bool IsActive() const {
-#ifdef USE_EPOLL
return active;
-#else
- return source_id != 0;
-#endif
}
void Schedule();
@@ -79,9 +69,6 @@ protected:
private:
void Run();
-#ifndef USE_EPOLL
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index 5aa24aea2..9c76c422c 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -20,20 +20,21 @@
#include "config.h"
#include "Loop.hxx"
-#ifdef USE_EPOLL
-
#include "system/Clock.hxx"
#include "TimeoutMonitor.hxx"
#include "SocketMonitor.hxx"
#include "IdleMonitor.hxx"
+#include "DeferredMonitor.hxx"
#include <algorithm>
EventLoop::EventLoop(Default)
:SocketMonitor(*this),
now_ms(::MonotonicClockMS()),
- quit(false),
- n_events(0),
+ quit(false), busy(true),
+#ifndef NDEBUG
+ virgin(true),
+#endif
thread(ThreadId::Null())
{
SocketMonitor::Open(wake_fd.Get());
@@ -45,45 +46,51 @@ EventLoop::~EventLoop()
assert(idle.empty());
assert(timers.empty());
- /* avoid closing the WakeFD twice */
- SocketMonitor::Steal();
+ /* this is necessary to get a well-defined destruction
+ order */
+ SocketMonitor::Cancel();
}
void
EventLoop::Break()
{
- if (IsInside())
- quit = true;
- else
- AddCall([this]() { Break(); });
+ quit = true;
+ wake_fd.Write();
}
-void
-EventLoop::Abandon(SocketMonitor &m)
+bool
+EventLoop::Abandon(int _fd, SocketMonitor &m)
{
- for (unsigned i = 0, n = n_events; i < n; ++i)
- if (events[i].data.ptr == &m)
- events[i].events = 0;
+ assert(IsInsideOrVirgin());
+
+ poll_result.Clear(&m);
+ return poll_group.Abandon(_fd);
}
bool
EventLoop::RemoveFD(int _fd, SocketMonitor &m)
{
- Abandon(m);
- return epoll.Remove(_fd);
+ assert(IsInsideOrNull());
+
+ poll_result.Clear(&m);
+ return poll_group.Remove(_fd);
}
void
EventLoop::AddIdle(IdleMonitor &i)
{
+ assert(IsInsideOrVirgin());
assert(std::find(idle.begin(), idle.end(), &i) == idle.end());
idle.push_back(&i);
+ again = true;
}
void
EventLoop::RemoveIdle(IdleMonitor &i)
{
+ assert(IsInsideOrVirgin());
+
auto it = std::find(idle.begin(), idle.end(), &i);
assert(it != idle.end());
@@ -93,12 +100,19 @@ EventLoop::RemoveIdle(IdleMonitor &i)
void
EventLoop::AddTimer(TimeoutMonitor &t, unsigned ms)
{
+ /* can't use IsInsideOrVirgin() here because libavahi-client
+ modifies the timeout during avahi_client_free() */
+ assert(IsInsideOrNull());
+
timers.insert(TimerRecord(t, now_ms + ms));
+ again = true;
}
void
EventLoop::CancelTimer(TimeoutMonitor &t)
{
+ assert(IsInsideOrNull());
+
for (auto i = timers.begin(), end = timers.end(); i != end; ++i) {
if (&i->timer == &t) {
timers.erase(i);
@@ -107,19 +121,24 @@ EventLoop::CancelTimer(TimeoutMonitor &t)
}
}
-#endif
-
void
EventLoop::Run()
{
assert(thread.IsNull());
+ assert(virgin);
+
+#ifndef NDEBUG
+ virgin = false;
+#endif
+
thread = ThreadId::GetCurrent();
-#ifdef USE_EPOLL
assert(!quit);
+ assert(busy);
do {
now_ms = ::MonotonicClockMS();
+ again = false;
/* invoke timers */
@@ -146,7 +165,6 @@ EventLoop::Run()
/* invoke idle */
- const bool idle_empty = idle.empty();
while (!idle.empty()) {
IdleMonitor &m = *idle.front();
idle.pop_front();
@@ -156,7 +174,14 @@ EventLoop::Run()
return;
}
- if (!idle_empty)
+ /* try to handle DeferredMonitors without WakeFD
+ overhead */
+ mutex.lock();
+ HandleDeferred();
+ busy = false;
+ mutex.unlock();
+
+ if (again)
/* re-evaluate timers because one of the
IdleMonitors may have added a new
timeout */
@@ -164,101 +189,107 @@ EventLoop::Run()
/* wait for new event */
- const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms);
- n_events = std::max(n, 0);
+ poll_group.ReadEvents(poll_result, timeout_ms);
now_ms = ::MonotonicClockMS();
- assert(!quit);
+ mutex.lock();
+ busy = true;
+ mutex.unlock();
/* invoke sockets */
-
- for (int i = 0; i < n; ++i) {
- const auto &e = events[i];
-
- if (e.events != 0) {
- SocketMonitor &m = *(SocketMonitor *)e.data.ptr;
- m.Dispatch(e.events);
-
+ for (int i = 0; i < poll_result.GetSize(); ++i) {
+ auto events = poll_result.GetEvents(i);
+ if (events != 0) {
if (quit)
break;
+
+ auto m = (SocketMonitor *)poll_result.GetObject(i);
+ m->Dispatch(events);
}
}
- n_events = 0;
+ poll_result.Reset();
+
} while (!quit);
-#else
- g_main_loop_run(loop);
-#endif
+#ifndef NDEBUG
+ assert(busy);
assert(thread.IsInside());
+ thread = ThreadId::Null();
+#endif
}
-#ifdef USE_EPOLL
-
void
-EventLoop::AddCall(std::function<void()> &&f)
+EventLoop::AddDeferred(DeferredMonitor &d)
{
mutex.lock();
- calls.push_back(f);
+ if (d.pending) {
+ mutex.unlock();
+ return;
+ }
+
+ assert(std::find(deferred.begin(),
+ deferred.end(), &d) == deferred.end());
+
+ /* we don't need to wake up the EventLoop if another
+ DeferredMonitor has already done it */
+ const bool must_wake = !busy && deferred.empty();
+
+ d.pending = true;
+ deferred.push_back(&d);
+ again = true;
mutex.unlock();
- wake_fd.Write();
+ if (must_wake)
+ wake_fd.Write();
}
-bool
-EventLoop::OnSocketReady(gcc_unused unsigned flags)
+void
+EventLoop::RemoveDeferred(DeferredMonitor &d)
{
- assert(!quit);
+ const ScopeLock protect(mutex);
- wake_fd.Read();
+ if (!d.pending) {
+ assert(std::find(deferred.begin(),
+ deferred.end(), &d) == deferred.end());
+ return;
+ }
- mutex.lock();
+ d.pending = false;
- while (!calls.empty() && !quit) {
- auto f = std::move(calls.front());
- calls.pop_front();
+ auto i = std::find(deferred.begin(), deferred.end(), &d);
+ assert(i != deferred.end());
+
+ deferred.erase(i);
+}
+
+void
+EventLoop::HandleDeferred()
+{
+ while (!deferred.empty() && !quit) {
+ DeferredMonitor &m = *deferred.front();
+ assert(m.pending);
+
+ deferred.pop_front();
+ m.pending = false;
mutex.unlock();
- f();
+ m.RunDeferred();
mutex.lock();
}
-
- mutex.unlock();
-
- return true;
}
-#else
-
-guint
-EventLoop::AddIdle(GSourceFunc function, gpointer data)
+bool
+EventLoop::OnSocketReady(gcc_unused unsigned flags)
{
- GSource *source = g_idle_source_new();
- g_source_set_callback(source, function, data, nullptr);
- guint id = g_source_attach(source, GetContext());
- g_source_unref(source);
- return id;
-}
+ assert(IsInside());
-GSource *
-EventLoop::AddTimeout(guint interval_ms,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new(interval_ms);
- g_source_set_callback(source, function, data, nullptr);
- g_source_attach(source, GetContext());
- return source;
-}
+ wake_fd.Read();
-GSource *
-EventLoop::AddTimeoutSeconds(guint interval_s,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new_seconds(interval_s);
- g_source_set_callback(source, function, data, nullptr);
- g_source_attach(source, GetContext());
- return source;
-}
+ mutex.lock();
+ HandleDeferred();
+ mutex.unlock();
-#endif
+ return true;
+}
diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx
index 62e733747..d03259fb0 100644
--- a/src/event/Loop.hxx
+++ b/src/event/Loop.hxx
@@ -24,33 +24,32 @@
#include "thread/Id.hxx"
#include "Compiler.h"
-#ifdef USE_EPOLL
-#include "system/EPollFD.hxx"
+#include "PollGroup.hxx"
#include "thread/Mutex.hxx"
#include "WakeFD.hxx"
#include "SocketMonitor.hxx"
-#include <functional>
#include <list>
#include <set>
-#else
-#include <glib.h>
-#endif
-#ifdef USE_EPOLL
class TimeoutMonitor;
class IdleMonitor;
+class DeferredMonitor;
class SocketMonitor;
-#endif
#include <assert.h>
-class EventLoop final
-#ifdef USE_EPOLL
- : private SocketMonitor
-#endif
+/**
+ * An event loop that polls for events on file/socket descriptors.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs it, except where explicitly documented as
+ * thread-safe.
+ *
+ * @see SocketMonitor, MultiSocketMonitor, TimeoutMonitor, IdleMonitor
+ */
+class EventLoop final : SocketMonitor
{
-#ifdef USE_EPOLL
struct TimerRecord {
/**
* Projected monotonic_clock_ms() value when this
@@ -73,52 +72,80 @@ class EventLoop final
}
};
- EPollFD epoll;
-
WakeFD wake_fd;
std::multiset<TimerRecord> timers;
std::list<IdleMonitor *> idle;
Mutex mutex;
- std::list<std::function<void()>> calls;
+ std::list<DeferredMonitor *> deferred;
unsigned now_ms;
bool quit;
- static constexpr unsigned MAX_EVENTS = 16;
- unsigned n_events;
- epoll_event events[MAX_EVENTS];
-#else
- GMainContext *context;
- GMainLoop *loop;
+ /**
+ * True when the object has been modified and another check is
+ * necessary before going to sleep via PollGroup::ReadEvents().
+ */
+ bool again;
+
+ /**
+ * True when handling callbacks, false when waiting for I/O or
+ * timeout.
+ *
+ * Protected with #mutex.
+ */
+ bool busy;
+
+#ifndef NDEBUG
+ /**
+ * True if Run() was never called. This is used for assert()
+ * calls.
+ */
+ bool virgin;
#endif
+ PollGroup poll_group;
+ PollResult poll_result;
+
/**
* A reference to the thread that is currently inside Run().
*/
ThreadId thread;
public:
-#ifdef USE_EPOLL
struct Default {};
EventLoop(Default dummy=Default());
~EventLoop();
+ /**
+ * A caching wrapper for MonotonicClockMS().
+ */
unsigned GetTimeMS() const {
+ assert(IsInside());
+
return now_ms;
}
+ /**
+ * Stop execution of this #EventLoop at the next chance. This
+ * method is thread-safe and non-blocking: after returning, it
+ * is not guaranteed that the EventLoop has really stopped.
+ */
void Break();
bool AddFD(int _fd, unsigned flags, SocketMonitor &m) {
- return epoll.Add(_fd, flags, &m);
+ assert(thread.IsNull() || thread.IsInside());
+
+ return poll_group.Add(_fd, flags, &m);
}
bool ModifyFD(int _fd, unsigned flags, SocketMonitor &m) {
- return epoll.Modify(_fd, flags, &m);
+ assert(IsInside());
+
+ return poll_group.Modify(_fd, flags, &m);
}
/**
@@ -126,7 +153,7 @@ public:
* has been closed. This is like RemoveFD(), but does not
* attempt to use #EPOLL_CTL_DEL.
*/
- void Abandon(SocketMonitor &m);
+ bool Abandon(int fd, SocketMonitor &m);
bool RemoveFD(int fd, SocketMonitor &m);
@@ -136,53 +163,38 @@ public:
void AddTimer(TimeoutMonitor &t, unsigned ms);
void CancelTimer(TimeoutMonitor &t);
- void AddCall(std::function<void()> &&f);
+ /**
+ * Schedule a call to DeferredMonitor::RunDeferred().
+ *
+ * This method is thread-safe.
+ */
+ void AddDeferred(DeferredMonitor &d);
+
+ /**
+ * Cancel a pending call to DeferredMonitor::RunDeferred().
+ * However after returning, the call may still be running.
+ *
+ * This method is thread-safe.
+ */
+ void RemoveDeferred(DeferredMonitor &d);
+ /**
+ * The main function of this class. It will loop until
+ * Break() gets called. Can be called only once.
+ */
void Run();
private:
+ /**
+ * Invoke all pending DeferredMonitors.
+ *
+ * Caller must lock the mutex.
+ */
+ void HandleDeferred();
+
virtual bool OnSocketReady(unsigned flags) override;
public:
-#else
- EventLoop()
- :context(g_main_context_new()),
- loop(g_main_loop_new(context, false)),
- thread(ThreadId::Null()) {}
-
- struct Default {};
- EventLoop(gcc_unused Default _dummy)
- :context(g_main_context_ref(g_main_context_default())),
- loop(g_main_loop_new(context, false)),
- thread(ThreadId::Null()) {}
-
- ~EventLoop() {
- g_main_loop_unref(loop);
- g_main_context_unref(context);
- }
-
- GMainContext *GetContext() {
- return context;
- }
-
- void WakeUp() {
- g_main_context_wakeup(context);
- }
-
- void Break() {
- g_main_loop_quit(loop);
- }
-
- void Run();
-
- guint AddIdle(GSourceFunc function, gpointer data);
-
- GSource *AddTimeout(guint interval_ms,
- GSourceFunc function, gpointer data);
-
- GSource *AddTimeoutSeconds(guint interval_s,
- GSourceFunc function, gpointer data);
-#endif
/**
* Are we currently running inside this EventLoop's thread?
@@ -193,6 +205,20 @@ public:
return thread.IsInside();
}
+
+#ifndef NDEBUG
+ gcc_pure
+ bool IsInsideOrVirgin() const {
+ return virgin || IsInside();
+ }
+#endif
+
+#ifndef NDEBUG
+ gcc_pure
+ bool IsInsideOrNull() const {
+ return thread.IsNull() || thread.IsInside();
+ }
+#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx
index bd1aa6fef..5ef8b98af 100644
--- a/src/event/MultiSocketMonitor.cxx
+++ b/src/event/MultiSocketMonitor.cxx
@@ -20,12 +20,12 @@
#include "config.h"
#include "MultiSocketMonitor.hxx"
#include "Loop.hxx"
-#include "system/fd_util.h"
-#include "Compiler.h"
-#include <assert.h>
+#include <algorithm>
-#ifdef USE_EPOLL
+#ifndef WIN32
+#include <poll.h>
+#endif
MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
:IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) {
@@ -37,6 +37,40 @@ MultiSocketMonitor::~MultiSocketMonitor()
}
void
+MultiSocketMonitor::ClearSocketList()
+{
+ assert(GetEventLoop().IsInsideOrNull());
+
+ fds.clear();
+}
+
+#ifndef WIN32
+
+void
+MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n)
+{
+ pollfd *const end = pfds + n;
+
+ UpdateSocketList([pfds, end](int fd) -> unsigned {
+ auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){
+ return pfd.fd == fd;
+ });
+ if (i == end)
+ return 0;
+
+ auto events = i->events;
+ i->events = 0;
+ return events;
+ });
+
+ for (auto i = pfds; i != end; ++i)
+ if (i->events != 0)
+ AddSocket(i->fd, i->events);
+}
+
+#endif
+
+void
MultiSocketMonitor::Prepare()
{
int timeout_ms = PrepareSockets();
@@ -64,100 +98,3 @@ MultiSocketMonitor::OnIdle()
Prepare();
}
}
-
-#else
-
-/**
- * The vtable for our GSource implementation. Unfortunately, we
- * cannot declare it "const", because g_source_new() takes a non-const
- * pointer, for whatever reason.
- */
-static GSourceFuncs multi_socket_monitor_source_funcs = {
- MultiSocketMonitor::Prepare,
- MultiSocketMonitor::Check,
- MultiSocketMonitor::Dispatch,
- nullptr,
- nullptr,
- nullptr,
-};
-
-MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
- :loop(_loop),
- source((Source *)g_source_new(&multi_socket_monitor_source_funcs,
- sizeof(*source))),
- absolute_timeout_us(-1) {
- source->monitor = this;
-
- g_source_attach(&source->base, loop.GetContext());
-}
-
-MultiSocketMonitor::~MultiSocketMonitor()
-{
- g_source_destroy(&source->base);
- g_source_unref(&source->base);
- source = nullptr;
-}
-
-bool
-MultiSocketMonitor::Prepare(gint *timeout_r)
-{
- int timeout_ms = *timeout_r = PrepareSockets();
- absolute_timeout_us = timeout_ms < 0
- ? uint64_t(-1)
- : GetTime() + uint64_t(timeout_ms) * 1000;
-
- return false;
-}
-
-bool
-MultiSocketMonitor::Check() const
-{
- if (GetTime() >= absolute_timeout_us)
- return true;
-
- for (const auto &i : fds)
- if (i.GetReturnedEvents() != 0)
- return true;
-
- return false;
-}
-
-/*
- * GSource methods
- *
- */
-
-gboolean
-MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r)
-{
- Source &source = *(Source *)_source;
- MultiSocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- return monitor.Prepare(timeout_r);
-}
-
-gboolean
-MultiSocketMonitor::Check(GSource *_source)
-{
- const Source &source = *(const Source *)_source;
- const MultiSocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- return monitor.Check();
-}
-
-gboolean
-MultiSocketMonitor::Dispatch(GSource *_source,
- gcc_unused GSourceFunc callback,
- gcc_unused gpointer user_data)
-{
- Source &source = *(Source *)_source;
- MultiSocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- monitor.Dispatch();
- return true;
-}
-
-#endif
diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx
index 8ee81a508..150f7baad 100644
--- a/src/event/MultiSocketMonitor.hxx
+++ b/src/event/MultiSocketMonitor.hxx
@@ -21,40 +21,38 @@
#define MPD_MULTI_SOCKET_MONITOR_HXX
#include "check.h"
-#include "Compiler.h"
-
-#ifdef USE_EPOLL
#include "IdleMonitor.hxx"
#include "TimeoutMonitor.hxx"
#include "SocketMonitor.hxx"
-#else
-#include <glib.h>
-#endif
+#include "Compiler.h"
#include <forward_list>
+#include <iterator>
#include <assert.h>
-#include <stdint.h>
#ifdef WIN32
-/* ERRORis a WIN32 macro that poisons our namespace; this is a
- kludge to allow us to use it anyway */
+/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge
+ to allow us to use it anyway */
#ifdef ERROR
#undef ERROR
#endif
#endif
+#ifndef WIN32
+struct pollfd;
+#endif
+
class EventLoop;
/**
- * Monitor multiple sockets.
+ * Similar to #SocketMonitor, but monitors multiple sockets. To use
+ * it, implement the methods PrepareSockets() and DispatchSockets().
+ * In PrepareSockets(), use UpdateSocketList() and AddSocket().
+ * DispatchSockets() will be called if at least one socket is ready.
*/
-class MultiSocketMonitor
-#ifdef USE_EPOLL
- : private IdleMonitor, private TimeoutMonitor
-#endif
+class MultiSocketMonitor : IdleMonitor, TimeoutMonitor
{
-#ifdef USE_EPOLL
class SingleFD final : public SocketMonitor {
MultiSocketMonitor &multi;
@@ -99,93 +97,45 @@ class MultiSocketMonitor
friend class SingleFD;
bool ready, refresh;
-#else
- struct Source {
- GSource base;
-
- MultiSocketMonitor *monitor;
- };
-
- struct SingleFD {
- GPollFD pfd;
-
- constexpr SingleFD(gcc_unused MultiSocketMonitor &m,
- int fd, unsigned events)
- :pfd{fd, gushort(events), 0} {}
-
- constexpr int GetFD() const {
- return pfd.fd;
- }
-
- constexpr unsigned GetEvents() const {
- return pfd.events;
- }
-
- constexpr unsigned GetReturnedEvents() const {
- return pfd.revents;
- }
-
- void SetEvents(unsigned _events) {
- pfd.events = _events;
- }
- };
-
- EventLoop &loop;
- Source *source;
- uint64_t absolute_timeout_us;
-#endif
std::forward_list<SingleFD> fds;
public:
-#ifdef USE_EPOLL
static constexpr unsigned READ = SocketMonitor::READ;
static constexpr unsigned WRITE = SocketMonitor::WRITE;
static constexpr unsigned ERROR = SocketMonitor::ERROR;
static constexpr unsigned HANGUP = SocketMonitor::HANGUP;
-#else
- static constexpr unsigned READ = G_IO_IN;
- static constexpr unsigned WRITE = G_IO_OUT;
- static constexpr unsigned ERROR = G_IO_ERR;
- static constexpr unsigned HANGUP = G_IO_HUP;
-#endif
MultiSocketMonitor(EventLoop &_loop);
~MultiSocketMonitor();
-#ifdef USE_EPOLL
using IdleMonitor::GetEventLoop;
-#else
- EventLoop &GetEventLoop() {
- return loop;
- }
-#endif
public:
-#ifndef USE_EPOLL
- gcc_pure
- uint64_t GetTime() const {
- return g_source_get_time(&source->base);
- }
-#endif
-
+ /**
+ * Invalidate the socket list. A call to PrepareSockets() is
+ * scheduled which will then update the list.
+ */
void InvalidateSockets() {
-#ifdef USE_EPOLL
refresh = true;
IdleMonitor::Schedule();
-#else
- /* no-op because GLib always calls the GSource's
- "prepare" method before each poll() anyway */
-#endif
}
void AddSocket(int fd, unsigned events) {
fds.emplace_front(*this, fd, events);
-#ifndef USE_EPOLL
- g_source_add_poll(&source->base, &fds.front().pfd);
-#endif
}
+ /**
+ * Remove all sockets.
+ */
+ void ClearSocketList();
+
+ /**
+ * Update the known sockets by invoking the given function for
+ * each one; its return value is the events bit mask. A
+ * return value of 0 means the socket will be removed from the
+ * list.
+ */
template<typename E>
void UpdateSocketList(E &&e) {
for (auto prev = fds.before_begin(), end = fds.end(),
@@ -198,16 +148,19 @@ public:
i->SetEvents(events);
prev = i;
} else {
-#ifdef USE_EPOLL
- i->Steal();
-#else
- g_source_remove_poll(&source->base, &i->pfd);
-#endif
fds.erase_after(prev);
}
}
}
+#ifndef WIN32
+ /**
+ * Replace the socket list with the given file descriptors.
+ * The given pollfd array will be modified by this method.
+ */
+ void ReplaceSocketList(pollfd *pfds, unsigned n);
+#endif
+
protected:
/**
* @return timeout [ms] or -1 for no timeout
@@ -215,7 +168,6 @@ protected:
virtual int PrepareSockets() = 0;
virtual void DispatchSockets() = 0;
-#ifdef USE_EPOLL
private:
void SetReady() {
ready = true;
@@ -230,23 +182,6 @@ private:
}
virtual void OnIdle() final;
-
-#else
-public:
- /* GSource callbacks */
- static gboolean Prepare(GSource *source, gint *timeout_r);
- static gboolean Check(GSource *source);
- static gboolean Dispatch(GSource *source, GSourceFunc callback,
- gpointer user_data);
-
-private:
- bool Prepare(gint *timeout_r);
- bool Check() const;
-
- void Dispatch() {
- DispatchSockets();
- }
-#endif
};
#endif
diff --git a/src/event/PollGroup.hxx b/src/event/PollGroup.hxx
new file mode 100644
index 000000000..038ffc13e
--- /dev/null
+++ b/src/event/PollGroup.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2013 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_EVENT_POLLGROUP_HXX
+#define MPD_EVENT_POLLGROUP_HXX
+
+#ifdef USE_EPOLL
+#include "PollGroupEPoll.hxx"
+typedef PollResultEPoll PollResult;
+typedef PollGroupEPoll PollGroup;
+#endif
+
+#ifdef USE_WINSELECT
+#include "PollGroupWinSelect.hxx"
+typedef PollResultGeneric PollResult;
+typedef PollGroupWinSelect PollGroup;
+#endif
+
+#ifdef USE_POLL
+#include "PollGroupPoll.hxx"
+typedef PollResultGeneric PollResult;
+typedef PollGroupPoll PollGroup;
+#endif
+
+#endif
diff --git a/src/event/PollGroupEPoll.hxx b/src/event/PollGroupEPoll.hxx
new file mode 100644
index 000000000..21bb3a322
--- /dev/null
+++ b/src/event/PollGroupEPoll.hxx
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2003-2013 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_EVENT_POLLGROUP_EPOLL_HXX
+#define MPD_EVENT_POLLGROUP_EPOLL_HXX
+
+#include "check.h"
+
+#include "Compiler.h"
+#include "system/EPollFD.hxx"
+
+#include <array>
+#include <algorithm>
+
+class PollResultEPoll
+{
+ friend class PollGroupEPoll;
+
+ std::array<epoll_event, 16> events;
+ int n_events;
+public:
+ PollResultEPoll() : n_events(0) { }
+
+ int GetSize() const { return n_events; }
+ unsigned GetEvents(int i) const { return events[i].events; }
+ void *GetObject(int i) const { return events[i].data.ptr; }
+ void Reset() { n_events = 0; }
+
+ void Clear(void *obj) {
+ for (int i = 0; i < n_events; ++i)
+ if (events[i].data.ptr == obj)
+ events[i].events = 0;
+ }
+};
+
+class PollGroupEPoll
+{
+ EPollFD epoll;
+
+ PollGroupEPoll(PollGroupEPoll &) = delete;
+ PollGroupEPoll &operator=(PollGroupEPoll &) = delete;
+public:
+ static constexpr unsigned READ = EPOLLIN;
+ static constexpr unsigned WRITE = EPOLLOUT;
+ static constexpr unsigned ERROR = EPOLLERR;
+ static constexpr unsigned HANGUP = EPOLLHUP;
+
+ PollGroupEPoll() = default;
+
+ void ReadEvents(PollResultEPoll &result, int timeout_ms) {
+ int ret = epoll.Wait(result.events.data(), result.events.size(),
+ timeout_ms);
+ result.n_events = std::max(0, ret);
+ }
+
+ bool Add(int fd, unsigned events, void *obj) {
+ return epoll.Add(fd, events, obj);
+ }
+
+ bool Modify(int fd, unsigned events, void *obj) {
+ return epoll.Modify(fd, events, obj);
+ }
+
+ bool Remove(int fd) {
+ return epoll.Remove(fd);
+ }
+
+ bool Abandon(gcc_unused int fd) {
+ // Nothing to do in this implementation.
+ // Closed descriptors are automatically unregistered.
+ return true;
+ }
+};
+
+#endif
diff --git a/src/event/PollGroupPoll.cxx b/src/event/PollGroupPoll.cxx
new file mode 100644
index 000000000..89d09d995
--- /dev/null
+++ b/src/event/PollGroupPoll.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2013 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"
+
+#ifdef USE_POLL
+
+#include "PollGroupPoll.hxx"
+
+#include <assert.h>
+
+PollGroupPoll::PollGroupPoll() { }
+PollGroupPoll::~PollGroupPoll() { }
+
+bool PollGroupPoll::Add(int fd, unsigned events, void *obj)
+{
+ assert(items.find(fd) == items.end());
+
+ const size_t index = poll_events.size();
+ poll_events.resize(index + 1);
+ auto &e = poll_events[index];
+ e.fd = fd;
+ e.events = events;
+ e.revents = 0;
+ auto &item = items[fd];
+ item.index = index;
+ item.obj = obj;
+ return true;
+}
+
+bool PollGroupPoll::Modify(int fd, unsigned events, void *obj)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+ item.obj = obj;
+ auto &e = poll_events[item.index];
+ e.events = events;
+ e.revents &= events;
+ return true;
+}
+
+bool PollGroupPoll::Remove(int fd)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+ size_t index = item.index;
+ size_t last_index = poll_events.size() - 1;
+ if (index != last_index) {
+ std::swap(poll_events[index], poll_events[last_index]);
+ items[poll_events[index].fd].index = index;
+ }
+ poll_events.pop_back();
+ items.erase(item_iter);
+ return true;
+}
+
+void PollGroupPoll::ReadEvents(PollResultGeneric &result, int timeout_ms)
+{
+ int n = poll(poll_events.empty() ? nullptr : &poll_events[0],
+ poll_events.size(), timeout_ms);
+
+ for (size_t i = 0; n > 0 && i < poll_events.size(); ++i) {
+ const auto &e = poll_events[i];
+ if (e.revents != 0) {
+ result.Add(e.revents, items[e.fd].obj);
+ --n;
+ }
+ }
+}
+
+#endif
diff --git a/src/event/PollGroupPoll.hxx b/src/event/PollGroupPoll.hxx
new file mode 100644
index 000000000..6c69787ac
--- /dev/null
+++ b/src/event/PollGroupPoll.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2013 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_EVENT_POLLGROUP_POLL_HXX
+#define MPD_EVENT_POLLGROUP_POLL_HXX
+
+#include "check.h"
+#include "PollResultGeneric.hxx"
+
+#include <vector>
+#include <unordered_map>
+
+#include <stddef.h>
+#include <sys/poll.h>
+
+class PollGroupPoll
+{
+ struct Item
+ {
+ size_t index;
+ void *obj;
+ };
+
+ std::vector<pollfd> poll_events;
+ std::unordered_map<int, Item> items;
+
+ PollGroupPoll(PollGroupPoll &) = delete;
+ PollGroupPoll &operator=(PollGroupPoll &) = delete;
+public:
+ static constexpr unsigned READ = POLLIN;
+ static constexpr unsigned WRITE = POLLOUT;
+ static constexpr unsigned ERROR = POLLERR;
+ static constexpr unsigned HANGUP = POLLHUP;
+
+ PollGroupPoll();
+ ~PollGroupPoll();
+
+ void ReadEvents(PollResultGeneric &result, int timeout_ms);
+ bool Add(int fd, unsigned events, void *obj);
+ bool Modify(int fd, unsigned events, void *obj);
+ bool Remove(int fd);
+ bool Abandon(int fd) {
+ return Remove(fd);
+ }
+};
+
+#endif
diff --git a/src/event/PollGroupWinSelect.cxx b/src/event/PollGroupWinSelect.cxx
new file mode 100644
index 000000000..b184ff2b2
--- /dev/null
+++ b/src/event/PollGroupWinSelect.cxx
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2003-2013 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"
+
+#ifdef USE_WINSELECT
+
+#include "PollGroupWinSelect.hxx"
+
+constexpr int EVENT_READ = 0;
+constexpr int EVENT_WRITE = 1;
+
+static inline bool HasEvent(unsigned events, int event_id)
+{
+ return (events & (1 << event_id)) != 0;
+}
+
+PollGroupWinSelect::PollGroupWinSelect() { }
+PollGroupWinSelect::~PollGroupWinSelect() { }
+
+bool PollGroupWinSelect::CanModify(PollGroupWinSelect::Item &item,
+ unsigned events, int event_id)
+{
+ if (item.index[event_id] < 0 && HasEvent(events, event_id))
+ return !event_set[event_id].IsFull();
+ return true;
+}
+
+void PollGroupWinSelect::Modify(PollGroupWinSelect::Item &item, int fd,
+ unsigned events, int event_id)
+{
+ int index = item.index[event_id];
+ auto &set = event_set[event_id];
+
+ if (index < 0 && HasEvent(events, event_id))
+ item.index[event_id] = set.Add(fd);
+ else if (index >= 0 && !HasEvent(events, event_id)) {
+ if (index != set.Size() - 1) {
+ set.MoveToEnd(index);
+ items[set[index]].index[event_id] = index;
+ }
+ set.RemoveLast();
+ item.index[event_id] = -1;
+ }
+}
+
+bool PollGroupWinSelect::Add(int fd, unsigned events, void *obj)
+{
+ assert(items.find(fd) == items.end());
+ auto &item = items[fd];
+
+ item.index[EVENT_READ] = -1;
+ item.index[EVENT_WRITE] = -1;
+ item.obj = obj;
+ item.events = 0;
+
+ if (!CanModify(item, events, EVENT_READ)) {
+ items.erase(fd);
+ return false;
+ }
+ if (!CanModify(item, events, EVENT_WRITE)) {
+ items.erase(fd);
+ return false;
+ }
+
+ Modify(item, fd, events, EVENT_READ);
+ Modify(item, fd, events, EVENT_WRITE);
+ return true;
+}
+
+bool PollGroupWinSelect::Modify(int fd, unsigned events, void *obj)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+
+ if (!CanModify(item, events, EVENT_READ))
+ return false;
+ if (!CanModify(item, events, EVENT_WRITE))
+ return false;
+
+ item.obj = obj;
+ Modify(item, fd, events, EVENT_READ);
+ Modify(item, fd, events, EVENT_WRITE);
+ return true;
+}
+
+bool PollGroupWinSelect::Remove(int fd)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+
+ Modify(item, fd, 0, EVENT_READ);
+ Modify(item, fd, 0, EVENT_WRITE);
+ items.erase(item_iter);
+ return true;
+}
+
+void PollGroupWinSelect::ReadEvents(PollResultGeneric &result, int timeout_ms)
+{
+ bool use_sleep = event_set[EVENT_READ].IsEmpty() &&
+ event_set[EVENT_WRITE].IsEmpty();
+
+ if (use_sleep) {
+ Sleep(timeout_ms < 0 ? INFINITE : (DWORD) timeout_ms);
+ return;
+ }
+
+ SocketSet read_set(event_set[EVENT_READ]);
+ SocketSet write_set(event_set[EVENT_WRITE]);
+ SocketSet except_set(event_set[EVENT_WRITE]);
+
+ timeval tv;
+ if (timeout_ms >= 0) {
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+ }
+
+ int ret = select(0,
+ read_set.IsEmpty() ? nullptr : read_set.GetPtr(),
+ write_set.IsEmpty() ? nullptr : write_set.GetPtr(),
+ except_set.IsEmpty() ? nullptr : except_set.GetPtr(),
+ timeout_ms < 0 ? nullptr : &tv);
+
+ if (ret == 0 || ret == SOCKET_ERROR)
+ return;
+
+ for (int i = 0; i < read_set.Size(); ++i)
+ items[read_set[i]].events |= READ;
+
+ for (int i = 0; i < write_set.Size(); ++i)
+ items[write_set[i]].events |= WRITE;
+
+ for (int i = 0; i < except_set.Size(); ++i)
+ items[except_set[i]].events |= WRITE;
+
+ for (auto i = items.begin(); i != items.end(); ++i)
+ if (i->second.events != 0) {
+ result.Add(i->second.events, i->second.obj);
+ i->second.events = 0;
+ }
+}
+
+#endif
diff --git a/src/event/PollGroupWinSelect.hxx b/src/event/PollGroupWinSelect.hxx
new file mode 100644
index 000000000..7a6d12d8a
--- /dev/null
+++ b/src/event/PollGroupWinSelect.hxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2013 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_EVENT_POLLGROUP_WINSELECT_HXX
+#define MPD_EVENT_POLLGROUP_WINSELECT_HXX
+
+#include "check.h"
+
+#include "PollResultGeneric.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#include <unordered_map>
+
+#include <windows.h>
+#include <winsock2.h>
+
+#ifdef ERROR
+#undef ERROR
+#endif
+
+class SocketSet
+{
+ fd_set set;
+public:
+ SocketSet() { set.fd_count = 0; }
+ SocketSet(SocketSet &other) {
+ set.fd_count = other.set.fd_count;
+ memcpy(set.fd_array,
+ other.set.fd_array,
+ sizeof (SOCKET) * set.fd_count);
+ }
+
+ fd_set *GetPtr() { return &set; }
+ int Size() { return set.fd_count; }
+ bool IsEmpty() { return set.fd_count == 0; }
+ bool IsFull() { return set.fd_count == FD_SETSIZE; }
+
+ int operator[](int index) {
+ assert(index >= 0 && (u_int)index < set.fd_count);
+ return set.fd_array[index];
+ }
+
+ int Add(int fd) {
+ assert(!IsFull());
+ set.fd_array[set.fd_count] = fd;
+ return set.fd_count++;
+ }
+
+ void MoveToEnd(int index) {
+ assert(index >= 0 && (u_int)index < set.fd_count);
+ std::swap(set.fd_array[index], set.fd_array[set.fd_count - 1]);
+ }
+
+ void RemoveLast() {
+ assert(!IsEmpty());
+ --set.fd_count;
+ }
+};
+
+class PollGroupWinSelect
+{
+ struct Item
+ {
+ int index[2];
+ void *obj;
+ unsigned events;
+ };
+
+ SocketSet event_set[2];
+ std::unordered_map<int, Item> items;
+
+ bool CanModify(Item &item, unsigned events, int event_id);
+ void Modify(Item &item, int fd, unsigned events, int event_id);
+
+ PollGroupWinSelect(PollGroupWinSelect &) = delete;
+ PollGroupWinSelect &operator=(PollGroupWinSelect &) = delete;
+public:
+ static constexpr unsigned READ = 1;
+ static constexpr unsigned WRITE = 2;
+ static constexpr unsigned ERROR = 0;
+ static constexpr unsigned HANGUP = 0;
+
+ PollGroupWinSelect();
+ ~PollGroupWinSelect();
+
+ void ReadEvents(PollResultGeneric &result, int timeout_ms);
+ bool Add(int fd, unsigned events, void *obj);
+ bool Modify(int fd, unsigned events, void *obj);
+ bool Remove(int fd);
+ bool Abandon(int fd) { return Remove(fd); }
+};
+
+#endif
diff --git a/src/event/PollResultGeneric.hxx b/src/event/PollResultGeneric.hxx
new file mode 100644
index 000000000..1c2c0d00b
--- /dev/null
+++ b/src/event/PollResultGeneric.hxx
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2003-2013 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_EVENT_POLLRESULT_GENERIC_HXX
+#define MPD_EVENT_POLLRESULT_GENERIC_HXX
+
+#include "check.h"
+
+#include <vector>
+
+class PollResultGeneric
+{
+ struct Item
+ {
+ unsigned events;
+ void *obj;
+
+ Item() = default;
+ Item(unsigned _events, void *_obj)
+ : events(_events), obj(_obj) { }
+ };
+
+ std::vector<Item> items;
+public:
+ int GetSize() const { return items.size(); }
+ unsigned GetEvents(int i) const { return items[i].events; }
+ void *GetObject(int i) const { return items[i].obj; }
+ void Reset() { items.clear(); }
+
+ void Clear(void *obj) {
+ for (auto i = items.begin(); i != items.end(); ++i)
+ if (i->obj == obj)
+ i->events = 0;
+ }
+
+ void Add(unsigned events, void *obj) {
+ items.emplace_back(events, obj);
+ }
+};
+
+#endif
diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx
index 781d29181..11592b4fb 100644
--- a/src/event/ServerSocket.cxx
+++ b/src/event/ServerSocket.cxx
@@ -31,13 +31,13 @@
#include "system/fd_util.h"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
+#include "util/Alloc.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <string>
+#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
@@ -78,7 +78,7 @@ public:
parent(_parent), serial(_serial),
path(AllocatedPath::Null()),
address_length(_address_length),
- address((sockaddr *)g_memdup(_address, _address_length))
+ address((sockaddr *)xmemdup(_address, _address_length))
{
assert(_address != nullptr);
assert(_address_length > 0);
@@ -88,7 +88,10 @@ public:
OneServerSocket &operator=(const OneServerSocket &other) = delete;
~OneServerSocket() {
- g_free(address);
+ free(address);
+
+ if (IsDefined())
+ Close();
}
unsigned GetSerial() const {
@@ -106,7 +109,10 @@ public:
using SocketMonitor::IsDefined;
using SocketMonitor::Close;
- char *ToString() const;
+ gcc_pure
+ std::string ToString() const {
+ return sockaddr_to_string(address, address_length);
+ }
void SetFD(int _fd) {
SocketMonitor::Open(_fd);
@@ -121,18 +127,6 @@ private:
static constexpr Domain server_socket_domain("server_socket");
-/**
- * Wraper for sockaddr_to_string() which never fails.
- */
-char *
-OneServerSocket::ToString() const
-{
- char *p = sockaddr_to_string(address, address_length, IgnoreError());
- if (p == nullptr)
- p = g_strdup("[unknown]");
- return p;
-}
-
static int
get_remote_uid(int fd)
{
@@ -242,23 +236,21 @@ ServerSocket::Open(Error &error)
Error error2;
if (!i.Open(error2)) {
if (good != nullptr && good->GetSerial() == i.GetSerial()) {
- char *address_string = i.ToString();
- char *good_string = good->ToString();
+ const auto address_string = i.ToString();
+ const auto good_string = good->ToString();
FormatWarning(server_socket_domain,
"bind to '%s' failed: %s "
"(continuing anyway, because "
"binding to '%s' succeeded)",
- address_string, error2.GetMessage(),
- good_string);
- g_free(address_string);
- g_free(good_string);
+ address_string.c_str(),
+ error2.GetMessage(),
+ good_string.c_str());
} else if (bad == nullptr) {
bad = &i;
- char *address_string = i.ToString();
+ const auto address_string = i.ToString();
error2.FormatPrefix("Failed to bind to '%s': ",
- address_string);
- g_free(address_string);
+ address_string.c_str());
last_error = std::move(error2);
}
diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx
index facb10371..05e13ff63 100644
--- a/src/event/ServerSocket.hxx
+++ b/src/event/ServerSocket.hxx
@@ -36,6 +36,9 @@ typedef void (*server_socket_callback_t)(int fd,
class OneServerSocket;
+/**
+ * A socket that accepts incoming stream connections (e.g. TCP).
+ */
class ServerSocket {
friend class OneServerSocket;
diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx
index 8c8527a77..eac011616 100644
--- a/src/event/SignalMonitor.cxx
+++ b/src/event/SignalMonitor.cxx
@@ -39,6 +39,9 @@
#include <algorithm>
+#include <assert.h>
+#include <signal.h>
+
class SignalMonitor final : private SocketMonitor {
#ifdef USE_SIGNALFD
SignalFD fd;
@@ -55,14 +58,6 @@ public:
#endif
}
- ~SignalMonitor() {
- /* prevent the descriptor to be closed twice */
-#ifdef USE_SIGNALFD
- if (SocketMonitor::IsDefined())
-#endif
- SocketMonitor::Steal();
- }
-
using SocketMonitor::GetEventLoop;
#ifdef USE_SIGNALFD
diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx
index 2b97059f7..ef2128684 100644
--- a/src/event/SocketMonitor.cxx
+++ b/src/event/SocketMonitor.cxx
@@ -28,12 +28,9 @@
#ifdef WIN32
#include <winsock2.h>
#else
-#include <sys/types.h>
#include <sys/socket.h>
#endif
-#ifdef USE_EPOLL
-
void
SocketMonitor::Dispatch(unsigned flags)
{
@@ -43,93 +40,19 @@ SocketMonitor::Dispatch(unsigned flags)
Cancel();
}
-#else
-
-/*
- * GSource methods
- *
- */
-
-gboolean
-SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r)
-{
- return false;
-}
-
-gboolean
-SocketMonitor::Check(GSource *_source)
-{
- const Source &source = *(const Source *)_source;
- const SocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- return monitor.Check();
-}
-
-gboolean
-SocketMonitor::Dispatch(GSource *_source,
- gcc_unused GSourceFunc callback,
- gcc_unused gpointer user_data)
-{
- Source &source = *(Source *)_source;
- SocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- monitor.Dispatch();
- return true;
-}
-
-/**
- * The vtable for our GSource implementation. Unfortunately, we
- * cannot declare it "const", because g_source_new() takes a non-const
- * pointer, for whatever reason.
- */
-static GSourceFuncs socket_monitor_source_funcs = {
- SocketMonitor::Prepare,
- SocketMonitor::Check,
- SocketMonitor::Dispatch,
- nullptr,
- nullptr,
- nullptr,
-};
-
-SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop)
- :fd(-1), loop(_loop),
- source(nullptr) {
- assert(_fd >= 0);
-
- Open(_fd);
-}
-
-#endif
-
SocketMonitor::~SocketMonitor()
{
if (IsDefined())
- Close();
+ Cancel();
}
void
SocketMonitor::Open(int _fd)
{
assert(fd < 0);
-#ifndef USE_EPOLL
- assert(source == nullptr);
-#endif
assert(_fd >= 0);
fd = _fd;
-
-#ifndef USE_EPOLL
- poll = {fd, 0, 0};
-
- source = (Source *)g_source_new(&socket_monitor_source_funcs,
- sizeof(*source));
- source->monitor = this;
-
- g_source_attach(&source->base, loop.GetContext());
- g_source_add_poll(&source->base, &poll);
-#endif
}
int
@@ -142,12 +65,6 @@ SocketMonitor::Steal()
int result = fd;
fd = -1;
-#ifndef USE_EPOLL
- g_source_destroy(&source->base);
- g_source_unref(&source->base);
- source = nullptr;
-#endif
-
return result;
}
@@ -156,12 +73,9 @@ SocketMonitor::Abandon()
{
assert(IsDefined());
-#ifdef USE_EPOLL
+ int old_fd = fd;
fd = -1;
- loop.Abandon(*this);
-#else
- Steal();
-#endif
+ loop.Abandon(old_fd, *this);
}
void
@@ -178,7 +92,6 @@ SocketMonitor::Schedule(unsigned flags)
if (flags == GetScheduledFlags())
return;
-#ifdef USE_EPOLL
if (scheduled_flags == 0)
loop.AddFD(fd, flags, *this);
else if (flags == 0)
@@ -187,12 +100,6 @@ SocketMonitor::Schedule(unsigned flags)
loop.ModifyFD(fd, flags, *this);
scheduled_flags = flags;
-#else
- poll.events = flags;
- poll.revents &= flags;
-
- loop.WakeUp();
-#endif
}
SocketMonitor::ssize_t
diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx
index 5369ddb8a..d0fcf1b57 100644
--- a/src/event/SocketMonitor.hxx
+++ b/src/event/SocketMonitor.hxx
@@ -21,12 +21,7 @@
#define MPD_SOCKET_MONITOR_HXX
#include "check.h"
-
-#ifdef USE_EPOLL
-#include <sys/epoll.h>
-#else
-#include <glib.h>
-#endif
+#include "PollGroup.hxx"
#include <type_traits>
@@ -34,8 +29,8 @@
#include <stddef.h>
#ifdef WIN32
-/* ERRORis a WIN32 macro that poisons our namespace; this is a
- kludge to allow us to use it anyway */
+/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge
+ to allow us to use it anyway */
#ifdef ERROR
#undef ERROR
#endif
@@ -43,56 +38,41 @@
class EventLoop;
+/**
+ * Monitor events on a socket. Call Schedule() to announce events
+ * you're interested in, or Cancel() to cancel your subscription. The
+ * #EventLoop will invoke virtual method OnSocketReady() as soon as
+ * any of the subscribed events are ready.
+ *
+ * This class does not feel responsible for closing the socket. Call
+ * Close() to do it manually.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
+ */
class SocketMonitor {
-#ifdef USE_EPOLL
-#else
- struct Source {
- GSource base;
-
- SocketMonitor *monitor;
- };
-#endif
-
int fd;
EventLoop &loop;
-#ifdef USE_EPOLL
/**
* A bit mask of events that is currently registered in the EventLoop.
*/
unsigned scheduled_flags;
-#else
- Source *source;
- GPollFD poll;
-#endif
public:
-#ifdef USE_EPOLL
- static constexpr unsigned READ = EPOLLIN;
- static constexpr unsigned WRITE = EPOLLOUT;
- static constexpr unsigned ERROR = EPOLLERR;
- static constexpr unsigned HANGUP = EPOLLHUP;
-#else
- static constexpr unsigned READ = G_IO_IN;
- static constexpr unsigned WRITE = G_IO_OUT;
- static constexpr unsigned ERROR = G_IO_ERR;
- static constexpr unsigned HANGUP = G_IO_HUP;
-#endif
+ static constexpr unsigned READ = PollGroup::READ;
+ static constexpr unsigned WRITE = PollGroup::WRITE;
+ static constexpr unsigned ERROR = PollGroup::ERROR;
+ static constexpr unsigned HANGUP = PollGroup::HANGUP;
typedef std::make_signed<size_t>::type ssize_t;
-#ifdef USE_EPOLL
SocketMonitor(EventLoop &_loop)
:fd(-1), loop(_loop), scheduled_flags(0) {}
SocketMonitor(int _fd, EventLoop &_loop)
:fd(_fd), loop(_loop), scheduled_flags(0) {}
-#else
- SocketMonitor(EventLoop &_loop)
- :fd(-1), loop(_loop), source(nullptr) {}
-
- SocketMonitor(int _fd, EventLoop &_loop);
-#endif
~SocketMonitor();
@@ -114,7 +94,7 @@ public:
/**
* "Steal" the socket descriptor. This abandons the socket
- * and puts the responsibility for closing it to the caller.
+ * and returns it.
*/
int Steal();
@@ -128,11 +108,7 @@ public:
unsigned GetScheduledFlags() const {
assert(IsDefined());
-#ifdef USE_EPOLL
return scheduled_flags;
-#else
- return poll.events;
-#endif
}
void Schedule(unsigned flags);
@@ -167,28 +143,7 @@ protected:
virtual bool OnSocketReady(unsigned flags) = 0;
public:
-#ifdef USE_EPOLL
void Dispatch(unsigned flags);
-#else
- /* GSource callbacks */
- static gboolean Prepare(GSource *source, gint *timeout_r);
- static gboolean Check(GSource *source);
- static gboolean Dispatch(GSource *source, GSourceFunc callback,
- gpointer user_data);
-
-private:
- bool Check() const {
- assert(IsDefined());
-
- return (poll.revents & poll.events) != 0;
- }
-
- void Dispatch() {
- assert(IsDefined());
-
- OnSocketReady(poll.revents & poll.events);
- }
-#endif
};
#endif
diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx
index cffad6b92..55260af2a 100644
--- a/src/event/TimeoutMonitor.cxx
+++ b/src/event/TimeoutMonitor.cxx
@@ -25,28 +25,19 @@ void
TimeoutMonitor::Cancel()
{
if (IsActive()) {
-#ifdef USE_EPOLL
active = false;
loop.CancelTimer(*this);
-#else
- g_source_destroy(source);
- g_source_unref(source);
- source = nullptr;
-#endif
}
}
void
+
TimeoutMonitor::Schedule(unsigned ms)
{
Cancel();
-#ifdef USE_EPOLL
active = true;
loop.AddTimer(*this, ms);
-#else
- source = loop.AddTimeout(ms, Callback, this);
-#endif
}
void
@@ -54,31 +45,11 @@ TimeoutMonitor::ScheduleSeconds(unsigned s)
{
Cancel();
-#ifdef USE_EPOLL
Schedule(s * 1000u);
-#else
- source = loop.AddTimeoutSeconds(s, Callback, this);
-#endif
}
void
TimeoutMonitor::Run()
{
-#ifndef USE_EPOLL
- Cancel();
-#endif
-
OnTimeout();
}
-
-#ifndef USE_EPOLL
-
-gboolean
-TimeoutMonitor::Callback(gpointer data)
-{
- TimeoutMonitor &monitor = *(TimeoutMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx
index 98e4e5564..0e3f4bdb7 100644
--- a/src/event/TimeoutMonitor.hxx
+++ b/src/event/TimeoutMonitor.hxx
@@ -22,34 +22,27 @@
#include "check.h"
-#ifndef USE_EPOLL
-#include <glib.h>
-#endif
-
class EventLoop;
+/**
+ * This class monitors a timeout. Use Schedule() to begin the timeout
+ * or Cancel() to cancel it.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
+ */
class TimeoutMonitor {
-#ifdef USE_EPOLL
friend class EventLoop;
-#endif
EventLoop &loop;
-#ifdef USE_EPOLL
bool active;
-#else
- GSource *source;
-#endif
public:
-#ifdef USE_EPOLL
TimeoutMonitor(EventLoop &_loop)
:loop(_loop), active(false) {
}
-#else
- TimeoutMonitor(EventLoop &_loop)
- :loop(_loop), source(nullptr) {}
-#endif
~TimeoutMonitor() {
Cancel();
@@ -60,11 +53,7 @@ public:
}
bool IsActive() const {
-#ifdef USE_EPOLL
return active;
-#else
- return source != nullptr;
-#endif
}
void Schedule(unsigned ms);
@@ -76,10 +65,6 @@ protected:
private:
void Run();
-
-#ifndef USE_EPOLL
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx
index 918a16e53..44adc8a66 100644
--- a/src/filter/AutoConvertFilterPlugin.cxx
+++ b/src/filter/AutoConvertFilterPlugin.cxx
@@ -88,7 +88,11 @@ AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
assert(audio_format2 == in_audio_format);
- convert_filter_set(convert, child_audio_format);
+ if (!convert_filter_set(convert, child_audio_format, error)) {
+ delete convert;
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
} else
/* no */
convert = nullptr;
diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx
index 040f8426f..e718844e7 100644
--- a/src/filter/ConvertFilterPlugin.cxx
+++ b/src/filter/ConvertFilterPlugin.cxx
@@ -28,7 +28,6 @@
#include "poison.h"
#include <assert.h>
-#include <string.h>
class ConvertFilter final : public Filter {
/**
@@ -39,21 +38,18 @@ class ConvertFilter final : public Filter {
/**
* The output audio format; the consumer of this plugin
- * expects PCM data in this format. This defaults to
- * #in_audio_format, and can be set with convert_filter_set().
+ * expects PCM data in this format.
+ *
+ * If this is AudioFormat::Undefined(), then the #PcmConvert
+ * attribute is not open. This can mean that Set() has failed
+ * or that no conversion is necessary.
*/
AudioFormat out_audio_format;
Manual<PcmConvert> state;
public:
- void Set(const AudioFormat &_out_audio_format) {
- assert(in_audio_format.IsValid());
- assert(out_audio_format.IsValid());
- assert(_out_audio_format.IsValid());
-
- out_audio_format = _out_audio_format;
- }
+ bool Set(const AudioFormat &_out_audio_format, Error &error);
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
virtual void Close() override;
@@ -69,12 +65,40 @@ convert_filter_init(gcc_unused const config_param &param,
return new ConvertFilter();
}
+bool
+ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error)
+{
+ assert(in_audio_format.IsValid());
+ assert(_out_audio_format.IsValid());
+
+ if (_out_audio_format == out_audio_format)
+ /* no change */
+ return true;
+
+ if (out_audio_format.IsValid()) {
+ out_audio_format.Clear();
+ state->Close();
+ }
+
+ if (_out_audio_format == in_audio_format)
+ /* optimized special case: no-op */
+ return true;
+
+ if (!state->Open(in_audio_format, _out_audio_format, error))
+ return false;
+
+ out_audio_format = _out_audio_format;
+ return true;
+}
+
AudioFormat
ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
{
assert(audio_format.IsValid());
- in_audio_format = out_audio_format = audio_format;
+ in_audio_format = audio_format;
+ out_audio_format.Clear();
+
state.Construct();
return in_audio_format;
@@ -83,6 +107,11 @@ ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
void
ConvertFilter::Close()
{
+ assert(in_audio_format.IsValid());
+
+ if (out_audio_format.IsValid())
+ state->Close();
+
state.Destruct();
poison_undefined(&in_audio_format, sizeof(in_audio_format));
@@ -93,15 +122,15 @@ const void *
ConvertFilter::FilterPCM(const void *src, size_t src_size,
size_t *dest_size_r, Error &error)
{
- if (in_audio_format == out_audio_format) {
+ assert(in_audio_format.IsValid());
+
+ if (!out_audio_format.IsValid()) {
/* optimized special case: no-op */
*dest_size_r = src_size;
return src;
}
- return state->Convert(in_audio_format,
- src, src_size,
- out_audio_format, dest_size_r,
+ return state->Convert(src, src_size, dest_size_r,
error);
}
@@ -110,10 +139,11 @@ const struct filter_plugin convert_filter_plugin = {
convert_filter_init,
};
-void
-convert_filter_set(Filter *_filter, const AudioFormat out_audio_format)
+bool
+convert_filter_set(Filter *_filter, AudioFormat out_audio_format,
+ Error &error)
{
ConvertFilter *filter = (ConvertFilter *)_filter;
- filter->Set(out_audio_format);
+ return filter->Set(out_audio_format, error);
}
diff --git a/src/filter/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx
index c814aaf49..f414e59bf 100644
--- a/src/filter/ConvertFilterPlugin.hxx
+++ b/src/filter/ConvertFilterPlugin.hxx
@@ -21,6 +21,7 @@
#define MPD_CONVERT_FILTER_PLUGIN_HXX
class Filter;
+class Error;
struct AudioFormat;
/**
@@ -29,7 +30,8 @@ struct AudioFormat;
* format switch is a violation of the filter API, this filter must be
* the last in a chain.
*/
-void
-convert_filter_set(Filter *filter, AudioFormat out_audio_format);
+bool
+convert_filter_set(Filter *filter, AudioFormat out_audio_format,
+ Error &error);
#endif
diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx
index 6c4f6b0e5..93cca9fef 100644
--- a/src/filter/NormalizeFilterPlugin.cxx
+++ b/src/filter/NormalizeFilterPlugin.cxx
@@ -25,7 +25,6 @@
#include "AudioFormat.hxx"
#include "AudioCompress/compress.h"
-#include <assert.h>
#include <string.h>
class NormalizeFilter final : public Filter {
diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx
index b2dcde4cc..998fda6f7 100644
--- a/src/filter/ReplayGainFilterPlugin.cxx
+++ b/src/filter/ReplayGainFilterPlugin.cxx
@@ -26,8 +26,9 @@
#include "ReplayGainInfo.hxx"
#include "ReplayGainConfig.hxx"
#include "MixerControl.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "pcm/PcmBuffer.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -55,8 +56,8 @@ class ReplayGainFilter final : public Filter {
ReplayGainInfo info;
/**
- * The current volume, between 0 and a value that may or may not exceed
- * #PCM_VOLUME_1.
+ * About the current volume: it is between 0 and a value that
+ * may or may not exceed #PCM_VOLUME_1.
*
* If the default value of true is used for replaygain_limit, the
* application of the volume to the signal will never cause clipping.
@@ -66,16 +67,11 @@ class ReplayGainFilter final : public Filter {
* maintain a consistent audio level. Whether clipping will actually
* occur depends on what value the user is using for replaygain_preamp.
*/
- unsigned volume;
-
- AudioFormat format;
-
- PcmBuffer buffer;
+ PcmVolume pv;
public:
ReplayGainFilter()
- :mixer(nullptr), mode(REPLAY_GAIN_OFF),
- volume(PCM_VOLUME_1) {
+ :mixer(nullptr), mode(REPLAY_GAIN_OFF) {
info.Clear();
}
@@ -125,6 +121,7 @@ public:
void
ReplayGainFilter::Update()
{
+ unsigned volume = PCM_VOLUME_1;
if (mode != REPLAY_GAIN_OFF) {
const auto &tuple = info.tuples[mode];
float scale = tuple.CalculateScale(replay_gain_preamp,
@@ -134,8 +131,9 @@ ReplayGainFilter::Update()
"scale=%f\n", (double)scale);
volume = pcm_float_to_volume(scale);
- } else
- volume = PCM_VOLUME_1;
+ }
+
+ pv.SetVolume(volume);
if (mixer != nullptr) {
/* update the hardware mixer volume */
@@ -160,48 +158,25 @@ replay_gain_filter_init(gcc_unused const config_param &param,
AudioFormat
ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
{
- format = af;
+ if (!pv.Open(af.format, error))
+ return AudioFormat::Undefined();
- return format;
+ return af;
}
void
ReplayGainFilter::Close()
{
- buffer.Clear();
+ pv.Close();
}
const void *
ReplayGainFilter::FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error)
+ size_t *dest_size_r, gcc_unused Error &error)
{
-
- *dest_size_r = src_size;
-
- if (volume == PCM_VOLUME_1)
- /* optimized special case: 100% volume = no-op */
- return src;
-
- void *dest = buffer.Get(src_size);
- if (volume <= 0) {
- /* optimized special case: 0% volume = memset(0) */
- /* XXX is this valid for all sample formats? What
- about floating point? */
- memset(dest, 0, src_size);
- return dest;
- }
-
- memcpy(dest, src, src_size);
-
- bool success = pcm_volume(dest, src_size,
- format.format,
- volume);
- if (!success) {
- error.Set(replay_gain_domain, "pcm_volume() has failed");
- return nullptr;
- }
-
- return dest;
+ const auto dest = pv.Apply({src, src_size});
+ *dest_size_r = dest.size;
+ return dest.data;
}
const struct filter_plugin replay_gain_filter_plugin = {
diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx
index d9042c21f..d2dc2d563 100644
--- a/src/filter/RouteFilterPlugin.cxx
+++ b/src/filter/RouteFilterPlugin.cxx
@@ -43,7 +43,6 @@
#include "ConfigError.hxx"
#include "ConfigData.hxx"
#include "AudioFormat.hxx"
-#include "CheckAudioFormat.hxx"
#include "FilterPlugin.hxx"
#include "FilterInternal.hxx"
#include "FilterRegistry.hxx"
diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx
index 1b663f6eb..8b9c6f8e9 100644
--- a/src/filter/VolumeFilterPlugin.cxx
+++ b/src/filter/VolumeFilterPlugin.cxx
@@ -22,9 +22,9 @@
#include "FilterPlugin.hxx"
#include "FilterInternal.hxx"
#include "FilterRegistry.hxx"
-#include "pcm/PcmVolume.hxx"
-#include "pcm/PcmBuffer.hxx"
+#include "pcm/Volume.hxx"
#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -32,29 +32,15 @@
#include <string.h>
class VolumeFilter final : public Filter {
- /**
- * The current volume, from 0 to #PCM_VOLUME_1.
- */
- unsigned volume;
-
- AudioFormat format;
-
- PcmBuffer buffer;
+ PcmVolume pv;
public:
- VolumeFilter()
- :volume(PCM_VOLUME_1) {}
-
unsigned GetVolume() const {
- assert(volume <= PCM_VOLUME_1);
-
- return volume;
+ return pv.GetVolume();
}
void SetVolume(unsigned _volume) {
- assert(_volume <= PCM_VOLUME_1);
-
- volume = _volume;
+ pv.SetVolume(_volume);
}
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
@@ -73,50 +59,27 @@ volume_filter_init(gcc_unused const config_param &param,
}
AudioFormat
-VolumeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+VolumeFilter::Open(AudioFormat &audio_format, Error &error)
{
- format = audio_format;
+ if (!pv.Open(audio_format.format, error))
+ return AudioFormat::Undefined();
- return format;
+ return audio_format;
}
void
VolumeFilter::Close()
{
- buffer.Clear();
+ pv.Close();
}
const void *
VolumeFilter::FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error)
+ size_t *dest_size_r, gcc_unused Error &error)
{
- *dest_size_r = src_size;
-
- if (volume >= PCM_VOLUME_1)
- /* optimized special case: 100% volume = no-op */
- return src;
-
- void *dest = buffer.Get(src_size);
-
- if (volume <= 0) {
- /* optimized special case: 0% volume = memset(0) */
- /* XXX is this valid for all sample formats? What
- about floating point? */
- memset(dest, 0, src_size);
- return dest;
- }
-
- memcpy(dest, src, src_size);
-
- bool success = pcm_volume(dest, src_size,
- format.format,
- volume);
- if (!success) {
- error.Set(volume_domain, "pcm_volume() has failed");
- return NULL;
- }
-
- return dest;
+ const auto dest = pv.Apply({src, src_size});
+ *dest_size_r = dest.size;
+ return dest.data;
}
const struct filter_plugin volume_filter_plugin = {
diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx
index 37b79a685..0d0aaacbe 100644
--- a/src/fs/AllocatedPath.cxx
+++ b/src/fs/AllocatedPath.cxx
@@ -26,7 +26,6 @@
#include <glib.h>
-#include <assert.h>
#include <string.h>
inline AllocatedPath::AllocatedPath(Donate, pointer _value)
@@ -38,12 +37,6 @@ inline AllocatedPath::AllocatedPath(Donate, pointer _value)
AllocatedPath::~AllocatedPath() {}
AllocatedPath
-AllocatedPath::Build(const_pointer a, const_pointer b)
-{
- return AllocatedPath(Donate(), g_build_filename(a, b, nullptr));
-}
-
-AllocatedPath
AllocatedPath::FromUTF8(const char *path_utf8)
{
return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8));
@@ -64,7 +57,7 @@ AllocatedPath::FromUTF8(const char *path_utf8, Error &error)
AllocatedPath
AllocatedPath::GetDirectoryName() const
{
- return AllocatedPath(Donate(), g_path_get_dirname(c_str()));
+ return FromFS(PathTraitsFS::GetParent(c_str()));
}
std::string
@@ -82,14 +75,14 @@ AllocatedPath::RelativeFS(const char *other_fs) const
other_fs += l;
if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
+ if (!PathTraitsFS::IsSeparator(*other_fs))
/* mismatch */
return nullptr;
/* skip remaining path separators */
do {
++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
+ } while (PathTraitsFS::IsSeparator(*other_fs));
}
return other_fs;
@@ -101,7 +94,7 @@ AllocatedPath::ChopSeparators()
size_t l = length();
const char *p = data();
- while (l >= 2 && PathTraits::IsSeparatorFS(p[l - 1])) {
+ while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) {
--l;
#if GCC_CHECK_VERSION(4,7) && !defined(__clang__)
diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx
index 36d8a1598..d44953cd6 100644
--- a/src/fs/AllocatedPath.hxx
+++ b/src/fs/AllocatedPath.hxx
@@ -28,8 +28,6 @@
#include <utility>
#include <string>
-#include <assert.h>
-
class Error;
/**
@@ -39,11 +37,10 @@ class Error;
* stored.
*/
class AllocatedPath {
- typedef std::string string;
-
- typedef PathTraits::value_type value_type;
- typedef PathTraits::pointer pointer;
- typedef PathTraits::const_pointer const_pointer;
+ typedef PathTraitsFS::string string;
+ typedef PathTraitsFS::value_type value_type;
+ typedef PathTraitsFS::pointer pointer;
+ typedef PathTraitsFS::const_pointer const_pointer;
string value;
@@ -56,6 +53,12 @@ class AllocatedPath {
AllocatedPath(const_pointer _value):value(_value) {}
+ AllocatedPath(string &&_value):value(std::move(_value)) {}
+
+ static AllocatedPath Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size) {
+ return AllocatedPath(PathTraitsFS::Build(a, a_size, b, b_size));
+ }
public:
/**
* Copy a #AllocatedPath object.
@@ -89,22 +92,28 @@ public:
* Join two path components with the path separator.
*/
gcc_pure gcc_nonnull_all
- static AllocatedPath Build(const_pointer a, const_pointer b);
+ static AllocatedPath Build(const_pointer a, const_pointer b) {
+ return Build(a, PathTraitsFS::GetLength(a),
+ b, PathTraitsFS::GetLength(b));
+ }
gcc_pure gcc_nonnull_all
static AllocatedPath Build(const_pointer a, const AllocatedPath &b) {
- return Build(a, b.c_str());
+ return Build(a, PathTraitsFS::GetLength(a),
+ b.value.c_str(), b.value.size());
}
gcc_pure gcc_nonnull_all
static AllocatedPath Build(const AllocatedPath &a, const_pointer b) {
- return Build(a.c_str(), b);
+ return Build(a.value.c_str(), a.value.size(),
+ b, PathTraitsFS::GetLength(b));
}
gcc_pure
static AllocatedPath Build(const AllocatedPath &a,
const AllocatedPath &b) {
- return Build(a.c_str(), b.c_str());
+ return Build(a.value.c_str(), a.value.size(),
+ b.value.c_str(), b.value.size());
}
/**
@@ -117,6 +126,15 @@ public:
}
/**
+ * Convert a C++ string that is already in the filesystem
+ * character set to a #Path instance.
+ */
+ gcc_pure
+ static AllocatedPath FromFS(string &&fs) {
+ return AllocatedPath(std::move(fs));
+ }
+
+ /**
* Convert a UTF-8 C string to a #AllocatedPath instance.
* Returns return a "nulled" instance on error.
*/
@@ -215,7 +233,7 @@ public:
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx
index dad5779f9..dcd291a2d 100644
--- a/src/fs/Charset.cxx
+++ b/src/fs/Charset.cxx
@@ -22,12 +22,13 @@
#include "Domain.hxx"
#include "Limits.hxx"
#include "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include "Log.hxx"
+#include "Traits.hxx"
#include <glib.h>
+#include <algorithm>
+
#include <assert.h>
#include <string.h>
@@ -76,13 +77,29 @@ GetFSCharset()
return fs_charset.empty() ? "utf-8" : fs_charset.c_str();
}
+static inline void FixSeparators(std::string &s)
+{
+#ifdef WIN32
+ // For whatever reason GCC can't convert constexpr to value reference.
+ // This leads to link errors when passing separators directly.
+ auto from = PathTraitsFS::SEPARATOR;
+ auto to = PathTraitsUTF8::SEPARATOR;
+ std::replace(s.begin(), s.end(), from, to);
+#else
+ (void)s;
+#endif
+}
+
std::string
PathToUTF8(const char *path_fs)
{
assert(path_fs != nullptr);
- if (fs_charset.empty())
- return std::string(path_fs);
+ if (fs_charset.empty()) {
+ auto result = std::string(path_fs);
+ FixSeparators(result);
+ return result;
+ }
GIConv conv = g_iconv_open("utf-8", fs_charset.c_str());
if (conv == reinterpret_cast<GIConv>(-1))
@@ -103,7 +120,9 @@ PathToUTF8(const char *path_fs)
if (ret == static_cast<size_t>(-1) || in_left > 0)
return std::string();
- return std::string(path_utf8, sizeof(path_utf8) - out_left);
+ auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left);
+ FixSeparators(result_path);
+ return result_path;
}
char *
diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx
index 63e64ef99..87e77512d 100644
--- a/src/fs/Config.cxx
+++ b/src/fs/Config.cxx
@@ -20,16 +20,10 @@
#include "config.h"
#include "Config.hxx"
#include "Charset.hxx"
-#include "Domain.hxx"
#include "ConfigGlobal.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
#include <glib.h>
-#include <assert.h>
-#include <string.h>
-
#ifdef WIN32
#include <windows.h> // for GetACP()
#include <stdio.h> // for sprintf()
diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx
index cb2f82d22..e9751c73b 100644
--- a/src/fs/FileSystem.hxx
+++ b/src/fs/FileSystem.hxx
@@ -28,7 +28,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#include <assert.h>
#include <stdio.h>
class AllocatedPath;
@@ -37,39 +36,39 @@ namespace FOpenMode {
/**
* Open mode for reading text files.
*/
- constexpr PathTraits::const_pointer ReadText = "r";
+ constexpr PathTraitsFS::const_pointer ReadText = "r";
/**
* Open mode for reading binary files.
*/
- constexpr PathTraits::const_pointer ReadBinary = "rb";
+ constexpr PathTraitsFS::const_pointer ReadBinary = "rb";
/**
* Open mode for writing text files.
*/
- constexpr PathTraits::const_pointer WriteText = "w";
+ constexpr PathTraitsFS::const_pointer WriteText = "w";
/**
* Open mode for writing binary files.
*/
- constexpr PathTraits::const_pointer WriteBinary = "wb";
+ constexpr PathTraitsFS::const_pointer WriteBinary = "wb";
/**
* Open mode for appending text files.
*/
- constexpr PathTraits::const_pointer AppendText = "a";
+ constexpr PathTraitsFS::const_pointer AppendText = "a";
/**
* Open mode for appending binary files.
*/
- constexpr PathTraits::const_pointer AppendBinary = "ab";
+ constexpr PathTraitsFS::const_pointer AppendBinary = "ab";
}
/**
* Wrapper for fopen() that uses #Path names.
*/
static inline FILE *
-FOpen(Path file, PathTraits::const_pointer mode)
+FOpen(Path file, PathTraitsFS::const_pointer mode)
{
return fopen(file.c_str(), mode);
}
@@ -132,20 +131,28 @@ MakeFifo(Path path, mode_t mode)
return mkfifo(path.c_str(), mode) == 0;
}
-#endif
-
/**
* Wrapper for access() that uses #Path names.
*/
static inline bool
CheckAccess(Path path, int mode)
{
+ return access(path.c_str(), mode) == 0;
+}
+
+#endif
+
+/**
+ * Checks is specified path exists and accessible.
+ */
+static inline bool
+CheckAccess(Path path)
+{
#ifdef WIN32
- (void)path;
- (void)mode;
- return true;
+ struct stat buf;
+ return StatFile(path, buf);
#else
- return access(path.c_str(), mode) == 0;
+ return CheckAccess(path, F_OK);
#endif
}
diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx
index 0ff0591fb..4b7fb9319 100644
--- a/src/fs/Path.cxx
+++ b/src/fs/Path.cxx
@@ -36,14 +36,14 @@ Path::RelativeFS(const char *other_fs) const
other_fs += l;
if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
+ if (!PathTraitsFS::IsSeparator(*other_fs))
/* mismatch */
return nullptr;
/* skip remaining path separators */
do {
++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
+ } while (PathTraitsFS::IsSeparator(*other_fs));
}
return other_fs;
diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx
index 6ea954577..807677f38 100644
--- a/src/fs/Path.hxx
+++ b/src/fs/Path.hxx
@@ -29,8 +29,6 @@
#include <assert.h>
#include <string.h>
-class Error;
-
/**
* A path name in the native file system character set.
*
@@ -38,9 +36,9 @@ class Error;
* instance lives, the string must not be invalidated.
*/
class Path {
- typedef PathTraits::value_type value_type;
- typedef PathTraits::pointer pointer;
- typedef PathTraits::const_pointer const_pointer;
+ typedef PathTraitsFS::value_type value_type;
+ typedef PathTraitsFS::pointer pointer;
+ typedef PathTraitsFS::const_pointer const_pointer;
const char *value;
@@ -141,7 +139,7 @@ public:
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx
new file mode 100644
index 000000000..889f6b3ea
--- /dev/null
+++ b/src/fs/StandardDirectory.cxx
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2003-2013 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"
+
+// Use X Desktop guidelines where applicable
+#if !defined(__APPLE__) && !defined(WIN32)
+#define USE_XDG
+#endif
+
+#include "StandardDirectory.hxx"
+#include "FileSystem.hxx"
+
+#include <array>
+
+#ifdef WIN32
+#include <windows.h>
+#include <shlobj.h>
+#else
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#endif
+
+#ifdef USE_XDG
+#include "util/CharUtil.hxx"
+#include "util/StringUtil.hxx"
+#include "TextFile.hxx"
+#include <string.h>
+#include <utility>
+#endif
+
+#ifndef WIN32
+class PasswdEntry
+{
+#if defined(HAVE_GETPWNAM_R) || defined(HAVE_GETPWUID_R)
+ std::array<char, 16 * 1024> buf;
+ passwd pw;
+#endif
+
+ passwd *result;
+public:
+ PasswdEntry() : result(nullptr) { }
+
+ bool ReadByName(const char *name) {
+#ifdef HAVE_GETPWNAM_R
+ getpwnam_r(name, &pw, buf.data(), buf.size(), &result);
+#else
+ result = getpwnam(name);
+#endif
+ return result != nullptr;
+ }
+
+ bool ReadByUid(uid_t uid) {
+#ifdef HAVE_GETPWUID_R
+ getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
+#else
+ result = getpwuid(uid);
+#endif
+ return result != nullptr;
+ }
+
+ const passwd *operator->() {
+ assert(result != nullptr);
+ return result;
+ }
+};
+#endif
+
+static inline bool IsValidPathString(PathTraitsFS::const_pointer path)
+{
+ return path != nullptr && *path != '\0';
+}
+
+static inline bool IsValidDir(PathTraitsFS::const_pointer dir)
+{
+ return PathTraitsFS::IsAbsolute(dir) &&
+ DirectoryExists(Path::FromFS(dir));
+}
+
+static inline AllocatedPath SafePathFromFS(PathTraitsFS::const_pointer dir)
+{
+ if (IsValidPathString(dir) && IsValidDir(dir))
+ return AllocatedPath::FromFS(dir);
+ return AllocatedPath::Null();
+}
+
+#ifdef WIN32
+static AllocatedPath GetStandardDir(int folder_id)
+{
+ std::array<char, MAX_PATH> dir;
+ auto ret = SHGetFolderPath(nullptr, folder_id | CSIDL_FLAG_DONT_VERIFY,
+ nullptr, SHGFP_TYPE_CURRENT, dir.data());
+ if (FAILED(ret))
+ return AllocatedPath::Null();
+ return SafePathFromFS(dir.data());
+}
+#endif
+
+#ifdef USE_XDG
+
+static const char home_prefix[] = "$HOME/";
+
+static bool ParseConfigLine(const char *line, const char *dir_name,
+ AllocatedPath &result_dir)
+{
+ // strip leading white space
+ line = strchug_fast(line);
+
+ // check for end-of-line or comment
+ if (*line == '\0' || *line == '#')
+ return false;
+
+ // check if current setting is for requested dir
+ if (!StringStartsWith(line, dir_name))
+ return false;
+ line += strlen(dir_name);
+
+ // strip equals sign and spaces around it
+ line = strchug_fast(line);
+ if (*line != '=')
+ return false;
+ ++line;
+ line = strchug_fast(line);
+
+ // check if path is quoted
+ bool quoted = false;
+ if (*line == '"') {
+ ++line;
+ quoted = true;
+ }
+
+ // check if path is relative to $HOME
+ bool home_relative = false;
+ if (StringStartsWith(line, home_prefix)) {
+ line += strlen(home_prefix);
+ home_relative = true;
+ }
+
+
+ const char *line_end;
+ // find end of the string
+ if (quoted) {
+ line_end = strrchr(line, '"');
+ if (line_end == nullptr)
+ return true;
+ } else {
+ line_end = line + strlen(line);
+ while (line < line_end && IsWhitespaceNotNull(line_end[-1]))
+ --line_end;
+ }
+
+ // check for empty result
+ if (line == line_end)
+ return true;
+
+ // build the result path
+ std::string path(line, line_end);
+
+ auto result = AllocatedPath::Null();
+ if (home_relative) {
+ auto home = GetHomeDir();
+ if (home.IsNull())
+ return true;
+ result = AllocatedPath::Build(home, path.c_str());
+ } else {
+ result = AllocatedPath::FromFS(std::move(path));
+ }
+
+ if (IsValidDir(result.c_str())) {
+ result_dir = std::move(result);
+ return true;
+ }
+ return true;
+}
+
+static AllocatedPath GetUserDir(const char *name)
+{
+ auto result = AllocatedPath::Null();
+ auto config_dir = GetUserConfigDir();
+ if (config_dir.IsNull())
+ return result;
+ auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs");
+ TextFile input(dirs_file);
+ if (input.HasFailed())
+ return result;
+ const char *line;
+ while ((line = input.ReadLine()) != nullptr)
+ if (ParseConfigLine(line, name, result))
+ return result;
+ return result;
+}
+
+#endif
+
+AllocatedPath GetUserConfigDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_LOCAL_APPDATA);
+#elif defined(USE_XDG)
+ // Check for $XDG_CONFIG_HOME
+ auto config_home = getenv("XDG_CONFIG_HOME");
+ if (IsValidPathString(config_home) && IsValidDir(config_home))
+ return AllocatedPath::FromFS(config_home);
+
+ // Check for $HOME/.config
+ auto home = GetHomeDir();
+ if (!home.IsNull()) {
+ AllocatedPath fallback = AllocatedPath::Build(home, ".config");
+ if (IsValidDir(fallback.c_str()))
+ return fallback;
+ }
+
+ return AllocatedPath::Null();
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath GetUserMusicDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_MYMUSIC);
+#elif defined(USE_XDG)
+ return GetUserDir("XDG_MUSIC_DIR");
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+#ifdef WIN32
+
+AllocatedPath GetSystemConfigDir()
+{
+ return GetStandardDir(CSIDL_COMMON_APPDATA);
+}
+
+AllocatedPath GetAppBaseDir()
+{
+ std::array<char, MAX_PATH> app;
+ auto ret = GetModuleFileName(nullptr, app.data(), app.size());
+
+ // Check for error
+ if (ret == 0)
+ return AllocatedPath::Null();
+
+ // Check for truncation
+ if (ret == app.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ return AllocatedPath::Null();
+
+ auto app_path = AllocatedPath::FromFS(app.data());
+ return app_path.GetDirectoryName().GetDirectoryName();
+}
+
+#else
+
+AllocatedPath GetHomeDir()
+{
+ auto home = getenv("HOME");
+ if (IsValidPathString(home) && IsValidDir(home))
+ return AllocatedPath::FromFS(home);
+ PasswdEntry pw;
+ if (pw.ReadByUid(getuid()))
+ return SafePathFromFS(pw->pw_dir);
+ return AllocatedPath::Null();
+}
+
+AllocatedPath GetHomeDir(const char *user_name)
+{
+ assert(user_name != nullptr);
+ PasswdEntry pw;
+ if (pw.ReadByName(user_name))
+ return SafePathFromFS(pw->pw_dir);
+ return AllocatedPath::Null();
+}
+
+#endif
diff --git a/src/fs/StandardDirectory.hxx b/src/fs/StandardDirectory.hxx
new file mode 100644
index 000000000..cc5c5ec2a
--- /dev/null
+++ b/src/fs/StandardDirectory.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2013 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_FS_STANDARD_DIRECTORY_HXX
+#define MPD_FS_STANDARD_DIRECTORY_HXX
+
+#include "check.h"
+#include "AllocatedPath.hxx"
+
+/**
+ * Obtains configuration directory for the current user.
+ */
+AllocatedPath GetUserConfigDir();
+
+/**
+ * Obtains music directory for the current user.
+ */
+AllocatedPath GetUserMusicDir();
+
+#ifdef WIN32
+
+/**
+ * Obtains system configuration directory.
+ */
+AllocatedPath GetSystemConfigDir();
+
+/**
+ * Obtains application application base directory.
+ * Application base directory is a directory that contains 'bin' folder
+ * for current executable.
+ */
+AllocatedPath GetAppBaseDir();
+
+#else
+
+/**
+ * Obtains home directory for the current user.
+ */
+AllocatedPath GetHomeDir();
+
+/**
+ * Obtains home directory for the specified user.
+ */
+AllocatedPath GetHomeDir(const char *user_name);
+
+#endif
+
+#endif
diff --git a/src/TextFile.cxx b/src/fs/TextFile.cxx
index 4a64ee963..4a64ee963 100644
--- a/src/TextFile.cxx
+++ b/src/fs/TextFile.cxx
diff --git a/src/TextFile.hxx b/src/fs/TextFile.hxx
index 9d8608711..9d8608711 100644
--- a/src/TextFile.hxx
+++ b/src/fs/TextFile.hxx
diff --git a/src/fs/Traits.cxx b/src/fs/Traits.cxx
index 2c3ce075b..3e874a224 100644
--- a/src/fs/Traits.cxx
+++ b/src/fs/Traits.cxx
@@ -22,24 +22,96 @@
#include <string.h>
-const char *
-PathTraits::GetBaseUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+BuildPathImpl(typename Traits::const_pointer a, size_t a_size,
+ typename Traits::const_pointer b, size_t b_size)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+ if (a_size == 0)
+ return typename Traits::string(b, b_size);
+ if (b_size == 0)
+ return typename Traits::string(a, a_size);
+
+ typename Traits::string result(a, a_size);
+
+ if (!Traits::IsSeparator(a[a_size - 1]))
+ result.push_back(Traits::SEPARATOR);
+
+ if (Traits::IsSeparator(b[0]))
+ result.append(b + 1, b_size - 1);
+ else
+ result.append(b, b_size);
+
+ return result;
+}
+
+template<typename Traits>
+typename Traits::const_pointer
+GetBasePathImpl(typename Traits::const_pointer p)
{
assert(p != nullptr);
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? slash + 1
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ return sep != nullptr
+ ? sep + 1
: p;
}
-std::string
-PathTraits::GetParentUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+GetParentPathImpl(typename Traits::const_pointer p)
{
assert(p != nullptr);
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? std::string(p, slash)
- : std::string(".");
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ if (sep == nullptr)
+ return typename Traits::string(".");
+ if (sep == p)
+ return typename Traits::string(p, p + 1);
+#ifdef WIN32
+ if (Traits::IsDrive(p) && sep == p + 2)
+ return typename Traits::string(p, p + 3);
+#endif
+ return typename Traits::string(p, sep);
+}
+
+PathTraitsFS::string
+PathTraitsFS::Build(PathTraitsFS::const_pointer a, size_t a_size,
+ PathTraitsFS::const_pointer b, size_t b_size)
+{
+ return BuildPathImpl<PathTraitsFS>(a, a_size, b, b_size);
+}
+
+PathTraitsFS::const_pointer
+PathTraitsFS::GetBase(PathTraitsFS::const_pointer p)
+{
+ return GetBasePathImpl<PathTraitsFS>(p);
+}
+
+PathTraitsFS::string
+PathTraitsFS::GetParent(PathTraitsFS::const_pointer p)
+{
+ return GetParentPathImpl<PathTraitsFS>(p);
+}
+
+PathTraitsUTF8::string
+PathTraitsUTF8::Build(PathTraitsUTF8::const_pointer a, size_t a_size,
+ PathTraitsUTF8::const_pointer b, size_t b_size)
+{
+ return BuildPathImpl<PathTraitsUTF8>(a, a_size, b, b_size);
+}
+
+PathTraitsUTF8::const_pointer
+PathTraitsUTF8::GetBase(PathTraitsUTF8::const_pointer p)
+{
+ return GetBasePathImpl<PathTraitsUTF8>(p);
+}
+
+PathTraitsUTF8::string
+PathTraitsUTF8::GetParent(PathTraitsUTF8::const_pointer p)
+{
+ return GetParentPathImpl<PathTraitsUTF8>(p);
}
diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx
index 244ab8b5c..927496e53 100644
--- a/src/fs/Traits.hxx
+++ b/src/fs/Traits.hxx
@@ -24,67 +24,144 @@
#include "Compiler.h"
#ifdef WIN32
-#include <glib.h>
+#include "util/CharUtil.hxx"
#endif
#include <string>
+#include <string.h>
#include <assert.h>
-class Error;
-
/**
- * This class describes the nature of a filesystem path.
+ * This class describes the nature of a native filesystem path.
*/
-struct PathTraits {
+struct PathTraitsFS {
+ typedef std::string string;
typedef char value_type;
typedef char *pointer;
typedef const char *const_pointer;
#ifdef WIN32
- static constexpr value_type SEPARATOR_FS = '\\';
- static constexpr char SEPARATOR_UTF8 = '/';
+ static constexpr value_type SEPARATOR = '\\';
#else
- static constexpr value_type SEPARATOR_FS = '/';
- static constexpr char SEPARATOR_UTF8 = '/';
+ static constexpr value_type SEPARATOR = '/';
#endif
- static constexpr bool IsSeparatorFS(value_type ch) {
+ static constexpr bool IsSeparator(value_type ch) {
return
#ifdef WIN32
ch == '/' ||
#endif
- ch == SEPARATOR_FS;
+ ch == SEPARATOR;
}
- static constexpr bool IsSeparatorUTF8(char ch) {
- return
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
+ assert(p != nullptr);
#ifdef WIN32
- ch == '/' ||
+ const_pointer pos = p + GetLength(p);
+ while (p != pos && !IsSeparator(*pos))
+ --pos;
+ return IsSeparator(*pos) ? pos : nullptr;
+#else
+ return strrchr(p, SEPARATOR);
#endif
- ch == SEPARATOR_UTF8;
}
- gcc_pure
- static bool IsAbsoluteFS(const_pointer p) {
- assert(p != nullptr);
+#ifdef WIN32
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
+#endif
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+ assert(p != nullptr);
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorFS(*p);
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
#endif
+ return IsSeparator(*p);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
}
- gcc_pure
- static bool IsAbsoluteUTF8(const char *p) {
+ /**
+ * Determine the "base" file name of the given native path.
+ * The return value points inside the given string.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer GetBase(const_pointer p);
+
+ /**
+ * Determine the "parent" file name of the given native path.
+ * As a special case, returns the string "." if there is no
+ * separator in the given input string.
+ */
+ gcc_pure gcc_nonnull_all
+ static string GetParent(const_pointer p);
+
+ /**
+ * Constructs the path from the given components.
+ * If either of the components is empty string,
+ * remaining component is returned unchanged.
+ * If both components are empty strings, empty string is returned.
+ */
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size);
+
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, const_pointer b) {
+ return Build(a, GetLength(a), b, GetLength(b));
+ }
+};
+
+/**
+ * This class describes the nature of a MPD internal filesystem path.
+ */
+struct PathTraitsUTF8 {
+ typedef std::string string;
+ typedef char value_type;
+ typedef char *pointer;
+ typedef const char *const_pointer;
+
+ static constexpr value_type SEPARATOR = '/';
+
+ static constexpr bool IsSeparator(value_type ch) {
+ return ch == SEPARATOR;
+ }
+
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
assert(p != nullptr);
+ return strrchr(p, SEPARATOR);
+ }
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorUTF8(*p);
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
+#endif
+
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+ assert(p != nullptr);
+#ifdef WIN32
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
#endif
+ return IsSeparator(*p);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
}
/**
@@ -92,7 +169,7 @@ struct PathTraits {
* The return value points inside the given string.
*/
gcc_pure gcc_nonnull_all
- static const char *GetBaseUTF8(const char *p);
+ static const_pointer GetBase(const_pointer p);
/**
* Determine the "parent" file name of the given UTF-8 path.
@@ -100,7 +177,22 @@ struct PathTraits {
* separator in the given input string.
*/
gcc_pure gcc_nonnull_all
- static std::string GetParentUTF8(const char *p);
+ static string GetParent(const_pointer p);
+
+ /**
+ * Constructs the path from the given components.
+ * If either of the components is empty string,
+ * remaining component is returned unchanged.
+ * If both components are empty strings, empty string is returned.
+ */
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size);
+
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, const_pointer b) {
+ return Build(a, GetLength(a), b, GetLength(b));
+ }
};
#endif
diff --git a/src/input/AlsaInputPlugin.cxx b/src/input/AlsaInputPlugin.cxx
new file mode 100644
index 000000000..9990091d3
--- /dev/null
+++ b/src/input/AlsaInputPlugin.cxx
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2003-2013 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.
+ */
+
+/*
+ * ALSA code based on an example by Paul Davis released under GPL here:
+ * http://equalarea.com/paul/alsa-audio.html
+ * and one by Matthias Nagorni, also GPL, here:
+ * http://alsamodular.sourceforge.net/alsa_programming_howto.html
+ */
+
+#include "config.h"
+#include "AlsaInputPlugin.hxx"
+#include "InputPlugin.hxx"
+#include "InputStream.hxx"
+#include "util/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/StringUtil.hxx"
+#include "util/ReusableArray.hxx"
+#include "util/Cast.hxx"
+#include "Log.hxx"
+#include "event/MultiSocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "event/Call.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "IOThread.hxx"
+
+#include <alsa/asoundlib.h>
+
+#include <assert.h>
+#include <string.h>
+
+static constexpr Domain alsa_input_domain("alsa");
+
+static constexpr const char *default_device = "hw:0,0";
+
+// the following defaults are because the PcmDecoderPlugin forces CD format
+static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
+static constexpr int default_channels = 2; // stereo
+static constexpr unsigned int default_rate = 44100; // cd quality
+
+/**
+ * This value should be the same as the read buffer size defined in
+ * PcmDecoderPlugin.cxx:pcm_stream_decode().
+ * We use it to calculate how many audio frames to buffer in the alsa driver
+ * before reading from the device. snd_pcm_readi() blocks until that many
+ * frames are ready.
+ */
+static constexpr size_t read_buffer_size = 4096;
+
+class AlsaInputStream final : MultiSocketMonitor, DeferredMonitor {
+ InputStream base;
+ snd_pcm_t *capture_handle;
+ size_t frame_size;
+ int frames_to_read;
+ bool eof;
+
+ /**
+ * Is somebody waiting for data? This is set by method
+ * Available().
+ */
+ std::atomic_bool waiting;
+
+ ReusableArray<pollfd> pfd_buffer;
+
+public:
+ AlsaInputStream(EventLoop &loop,
+ const char *uri, Mutex &mutex, Cond &cond,
+ snd_pcm_t *_handle, int _frame_size)
+ :MultiSocketMonitor(loop),
+ DeferredMonitor(loop),
+ base(input_plugin_alsa, uri, mutex, cond),
+ capture_handle(_handle),
+ frame_size(_frame_size),
+ eof(false)
+ {
+ assert(uri != nullptr);
+ assert(_handle != nullptr);
+
+ /* this mime type forces use of the PcmDecoderPlugin.
+ Needs to be generalised when/if that decoder is
+ updated to support other audio formats */
+ base.mime = strdup("audio/x-mpd-cdda-pcm");
+ base.seekable = false;
+ base.size = -1;
+ base.ready = true;
+ frames_to_read = read_buffer_size / frame_size;
+
+ snd_pcm_start(capture_handle);
+
+ DeferredMonitor::Schedule();
+ }
+
+ ~AlsaInputStream() {
+ snd_pcm_close(capture_handle);
+ }
+
+ using DeferredMonitor::GetEventLoop;
+
+ static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error);
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+#endif
+
+ static constexpr AlsaInputStream *Cast(InputStream *is) {
+ return ContainerCast(is, AlsaInputStream, base);
+ }
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ bool Available() {
+ if (snd_pcm_avail(capture_handle) > frames_to_read)
+ return true;
+
+ if (!waiting.exchange(true))
+ SafeInvalidateSockets();
+
+ return false;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error);
+
+ bool IsEOF() {
+ return eof;
+ }
+
+private:
+ static snd_pcm_t *OpenDevice(const char *device, int rate,
+ snd_pcm_format_t format, int channels,
+ Error &error);
+
+ int Recover(int err);
+
+ void SafeInvalidateSockets() {
+ DeferredMonitor::Schedule();
+ }
+
+ virtual void RunDeferred() override {
+ InvalidateSockets();
+ }
+
+ virtual int PrepareSockets() override;
+ virtual void DispatchSockets() override;
+};
+
+inline InputStream *
+AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ const char *const scheme = "alsa://";
+ if (!StringStartsWith(uri, scheme))
+ return nullptr;
+
+ const char *device = uri + strlen(scheme);
+ if (strlen(device) == 0)
+ device = default_device;
+
+ /* placeholders - eventually user-requested audio format will
+ be passed via the URI. For now we just force the
+ defaults */
+ int rate = default_rate;
+ snd_pcm_format_t format = default_format;
+ int channels = default_channels;
+
+ snd_pcm_t *handle = OpenDevice(device, rate, format, channels,
+ error);
+ if (handle == nullptr)
+ return nullptr;
+
+ int frame_size = snd_pcm_format_width(format) / 8 * channels;
+ AlsaInputStream *stream = new AlsaInputStream(io_thread_get(),
+ uri, mutex, cond,
+ handle, frame_size);
+ return &stream->base;
+}
+
+inline size_t
+AlsaInputStream::Read(void *ptr, size_t size, Error &error)
+{
+ assert(ptr != nullptr);
+
+ int num_frames = size / frame_size;
+ int ret;
+ while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) {
+ if (Recover(ret) < 0) {
+ eof = true;
+ error.Format(alsa_input_domain,
+ "PCM error - stream aborted");
+ return 0;
+ }
+ }
+
+ size_t nbytes = ret * frame_size;
+ base.offset += nbytes;
+ return nbytes;
+}
+
+int
+AlsaInputStream::PrepareSockets()
+{
+ if (!waiting) {
+ ClearSocketList();
+ return -1;
+ }
+
+ int count = snd_pcm_poll_descriptors_count(capture_handle);
+ if (count < 0) {
+ ClearSocketList();
+ return -1;
+ }
+
+ struct pollfd *pfds = pfd_buffer.Get(count);
+
+ count = snd_pcm_poll_descriptors(capture_handle, pfds, count);
+ if (count < 0)
+ count = 0;
+
+ ReplaceSocketList(pfds, count);
+ return -1;
+}
+
+void
+AlsaInputStream::DispatchSockets()
+{
+ waiting = false;
+
+ const ScopeLock protect(base.mutex);
+ /* wake up the thread that is waiting for more data */
+ base.cond.broadcast();
+}
+
+inline int
+AlsaInputStream::Recover(int err)
+{
+ switch(err) {
+ case -EPIPE:
+ LogDebug(alsa_input_domain, "Buffer Overrun");
+ // drop through
+ case -ESTRPIPE:
+ case -EINTR:
+ err = snd_pcm_recover(capture_handle, err, 1);
+ break;
+ default:
+ // something broken somewhere, give up
+ err = -1;
+ }
+ return err;
+}
+
+inline snd_pcm_t *
+AlsaInputStream::OpenDevice(const char *device,
+ int rate, snd_pcm_format_t format, int channels,
+ Error &error)
+{
+ snd_pcm_t *capture_handle;
+ int err;
+ if ((err = snd_pcm_open(&capture_handle, device,
+ SND_PCM_STREAM_CAPTURE, 0)) < 0) {
+ error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err));
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_t *hw_params;
+ if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ /* period needs to be big enough so that poll() doesn't fire too often,
+ * but small enough that buffer overruns don't occur if Read() is not
+ * invoked often enough.
+ * the calculation here is empirical; however all measurements were
+ * done using 44100:16:2. When we extend this plugin to support
+ * other audio formats then this may need to be revisited */
+ snd_pcm_uframes_t period = read_buffer_size * 2;
+ int direction = -1;
+ if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
+ &period, &direction)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set period size (%s)",
+ snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set parameters (%s)",
+ snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_free (hw_params);
+
+ snd_pcm_sw_params_t *sw_params;
+
+ snd_pcm_sw_params_malloc(&sw_params);
+ snd_pcm_sw_params_current(capture_handle, sw_params);
+
+ if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
+ period)) < 0) {
+ error.Format(alsa_input_domain,
+ "unable to set start threshold (%s)", snd_strerror(err));
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) {
+ error.Format(alsa_input_domain,
+ "unable to install sw params (%s)", snd_strerror(err));
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_sw_params_free(sw_params);
+
+ snd_pcm_prepare(capture_handle);
+
+ return capture_handle;
+}
+
+/*######################### Plugin Functions ##############################*/
+
+static InputStream *
+alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error)
+{
+ return AlsaInputStream::Create(uri, mutex, cond, error);
+}
+
+static void
+alsa_input_close(InputStream *is)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ delete ais;
+}
+
+static bool
+alsa_input_available(InputStream *is)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ return ais->Available();
+}
+
+static size_t
+alsa_input_read(InputStream *is, void *ptr, size_t size, Error &error)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ return ais->Read(ptr, size, error);
+}
+
+static bool
+alsa_input_eof(gcc_unused InputStream *is)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ return ais->IsEOF();
+}
+
+const struct InputPlugin input_plugin_alsa = {
+ "alsa",
+ nullptr,
+ nullptr,
+ alsa_input_open,
+ alsa_input_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ alsa_input_available,
+ alsa_input_read,
+ alsa_input_eof,
+ nullptr,
+};
diff --git a/src/input/AlsaInputPlugin.hxx b/src/input/AlsaInputPlugin.hxx
new file mode 100644
index 000000000..ac9519588
--- /dev/null
+++ b/src/input/AlsaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2013 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_ALSA_INPUT_PLUGIN_HXX
+#define MPD_ALSA_INPUT_PLUGIN_HXX
+
+#include "InputPlugin.hxx"
+
+extern const struct InputPlugin input_plugin_alsa;
+
+
+#endif
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx
index 5288f2b3b..597a91604 100644
--- a/src/input/ArchiveInputPlugin.cxx
+++ b/src/input/ArchiveInputPlugin.cxx
@@ -25,11 +25,11 @@
#include "ArchivePlugin.hxx"
#include "ArchiveFile.hxx"
#include "InputPlugin.hxx"
-#include "util/Error.hxx"
#include "fs/Traits.hxx"
+#include "util/Alloc.hxx"
#include "Log.hxx"
-#include <glib.h>
+#include <stdlib.h>
/**
* select correct archive plugin to handle the input stream
@@ -47,16 +47,16 @@ input_archive_open(const char *pathname,
const struct archive_plugin *arplug;
InputStream *is;
- if (!PathTraits::IsAbsoluteFS(pathname))
+ if (!PathTraitsFS::IsAbsolute(pathname))
return nullptr;
- char *pname = g_strdup(pathname);
+ char *pname = strdup(pathname);
// archive_lookup will modify pname when true is returned
const char *archive, *filename, *suffix;
if (!archive_lookup(pname, &archive, &filename, &suffix)) {
FormatDebug(archive_domain,
"not an archive, lookup %s failed", pname);
- g_free(pname);
+ free(pname);
return nullptr;
}
@@ -65,19 +65,19 @@ input_archive_open(const char *pathname,
if (!arplug) {
FormatWarning(archive_domain,
"can't handle archive %s", archive);
- g_free(pname);
+ free(pname);
return nullptr;
}
auto file = archive_file_open(arplug, archive, error);
if (file == nullptr) {
- g_free(pname);
+ free(pname);
return nullptr;
}
//setup fileops
is = file->OpenStream(filename, mutex, cond, error);
- g_free(pname);
+ free(pname);
file->Close();
return is;
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx
index b3ac57413..bf1c3c908 100644
--- a/src/input/CdioParanoiaInputPlugin.cxx
+++ b/src/input/CdioParanoiaInputPlugin.cxx
@@ -25,6 +25,7 @@
#include "CdioParanoiaInputPlugin.hxx"
#include "InputStream.hxx"
#include "InputPlugin.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
@@ -122,7 +123,7 @@ struct cdio_uri {
static bool
parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error)
{
- if (!g_str_has_prefix(src, "cdda://"))
+ if (!StringStartsWith(src, "cdda://"))
return false;
src += 7;
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
index b78545951..b74dc12a9 100644
--- a/src/input/CurlInputPlugin.cxx
+++ b/src/input/CurlInputPlugin.cxx
@@ -24,6 +24,7 @@
#include "ConfigGlobal.hxx"
#include "ConfigData.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "IcyMetaDataParser.hxx"
#include "event/SocketMonitor.hxx"
#include "event/TimeoutMonitor.hxx"
@@ -199,8 +200,6 @@ public:
Abandon() would be most appropriate, but it breaks
the second case - is that a CURL bug? is there a
better solution? */
-
- Steal();
}
/**
@@ -780,8 +779,11 @@ copy_icy_tag(struct input_curl *c)
delete c->tag;
- if (!c->meta_name.empty() && !tag->HasType(TAG_NAME))
- tag->AddItem(TAG_NAME, c->meta_name.c_str());
+ if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) {
+ TagBuilder tag_builder(std::move(*tag));
+ tag_builder.AddItem(TAG_NAME, c->meta_name.c_str());
+ *tag = tag_builder.Commit();
+ }
c->tag = tag;
}
@@ -910,8 +912,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
delete c->tag;
- c->tag = new Tag();
- c->tag->AddItem(TAG_NAME, c->meta_name.c_str());
+ TagBuilder tag_builder;
+ tag_builder.AddItem(TAG_NAME, c->meta_name.c_str());
+
+ c->tag = tag_builder.CommitNew();
} else if (StringEqualsCaseASCII(name, "icy-metaint")) {
char buffer[64];
size_t icy_metaint;
diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx
index b08299516..18704bd40 100644
--- a/src/input/DespotifyInputPlugin.cxx
+++ b/src/input/DespotifyInputPlugin.cxx
@@ -23,26 +23,25 @@
#include "InputStream.hxx"
#include "InputPlugin.hxx"
#include "tag/Tag.hxx"
+#include "util/StringUtil.hxx"
#include "Log.hxx"
extern "C" {
#include <despotify.h>
}
-#include <glib.h>
-
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
-struct DespotifyInputStream {
+class DespotifyInputStream {
InputStream base;
struct despotify_session *session;
struct ds_track *track;
- Tag *tag;
+ Tag tag;
struct ds_pcm_data pcm;
size_t len_available;
bool eof;
@@ -53,7 +52,7 @@ struct DespotifyInputStream {
ds_track *_track)
:base(input_plugin_despotify, uri, mutex, cond),
session(_session), track(_track),
- tag(mpd_despotify_tag_from_track(track)),
+ tag(mpd_despotify_tag_from_track(*track)),
len_available(0), eof(false) {
memset(&pcm, 0, sizeof(pcm));
@@ -63,30 +62,53 @@ struct DespotifyInputStream {
base.ready = true;
}
+public:
~DespotifyInputStream() {
- delete tag;
-
despotify_free_track(track);
}
+
+ static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ bool IsEOF() const {
+ return eof;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error);
+
+ Tag *ReadTag() {
+ if (tag.IsEmpty())
+ return nullptr;
+
+ Tag *result = new Tag(std::move(tag));
+ tag.Clear();
+ return result;
+ }
+
+ void Callback(int sig);
+
+private:
+ void FillBuffer();
};
-static void
-refill_buffer(DespotifyInputStream *ctx)
+inline void
+DespotifyInputStream::FillBuffer()
{
/* Wait until there is data */
while (1) {
- int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
+ int rc = despotify_get_pcm(session, &pcm);
- if (rc == 0 && ctx->pcm.len) {
- ctx->len_available = ctx->pcm.len;
+ if (rc == 0 && pcm.len) {
+ len_available = pcm.len;
break;
}
- if (ctx->eof == true)
+
+ if (eof == true)
break;
if (rc < 0) {
LogDebug(despotify_domain, "despotify_get_pcm error");
- ctx->eof = true;
+ eof = true;
break;
}
@@ -95,11 +117,9 @@ refill_buffer(DespotifyInputStream *ctx)
}
}
-static void callback(gcc_unused struct despotify_session* ds,
- int sig, gcc_unused void* data, void* callback_data)
+inline void
+DespotifyInputStream::Callback(int sig)
{
- DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
-
switch (sig) {
case DESPOTIFY_NEW_TRACK:
break;
@@ -109,35 +129,38 @@ static void callback(gcc_unused struct despotify_session* ds,
case DESPOTIFY_TRACK_PLAY_ERROR:
LogWarning(despotify_domain, "Track play error");
- ctx->eof = true;
- ctx->len_available = 0;
+ eof = true;
+ len_available = 0;
break;
case DESPOTIFY_END_OF_PLAYLIST:
- ctx->eof = true;
- FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof);
+ eof = true;
+ LogDebug(despotify_domain, "End of playlist");
break;
}
}
-
-static InputStream *
-input_despotify_open(const char *url,
- Mutex &mutex, Cond &cond,
- gcc_unused Error &error)
+static void callback(gcc_unused struct despotify_session* ds,
+ int sig, gcc_unused void* data, void* callback_data)
{
- struct despotify_session *session;
- struct ds_link *ds_link;
- struct ds_track *track;
+ DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
- if (!g_str_has_prefix(url, "spt://"))
+ ctx->Callback(sig);
+}
+
+inline InputStream *
+DespotifyInputStream::Open(const char *url,
+ Mutex &mutex, Cond &cond,
+ gcc_unused Error &error)
+{
+ if (!StringStartsWith(url, "spt://"))
return nullptr;
- session = mpd_despotify_get_session();
- if (!session)
+ despotify_session *session = mpd_despotify_get_session();
+ if (session == nullptr)
return nullptr;
- ds_link = despotify_link_from_uri(url + 6);
+ ds_link *ds_link = despotify_link_from_uri(url + 6);
if (!ds_link) {
FormatDebug(despotify_domain, "Can't find %s", url);
return nullptr;
@@ -147,7 +170,7 @@ input_despotify_open(const char *url,
return nullptr;
}
- track = despotify_link_get_track(session, ds_link);
+ ds_track *track = despotify_link_get_track(session, ds_link);
despotify_free_link(ds_link);
if (!track)
return nullptr;
@@ -170,26 +193,34 @@ input_despotify_open(const char *url,
return &ctx->base;
}
-static size_t
-input_despotify_read(InputStream *is, void *ptr, size_t size,
- gcc_unused Error &error)
+static InputStream *
+input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error)
{
- DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- size_t to_cpy = size;
+ return DespotifyInputStream::Open(url, mutex, cond, error);
+}
- if (ctx->len_available == 0)
- refill_buffer(ctx);
+inline size_t
+DespotifyInputStream::Read(void *ptr, size_t size, gcc_unused Error &error)
+{
+ if (len_available == 0)
+ FillBuffer();
- if (ctx->len_available < size)
- to_cpy = ctx->len_available;
- memcpy(ptr, ctx->pcm.buf, to_cpy);
- ctx->len_available -= to_cpy;
+ size_t to_cpy = std::min(size, len_available);
+ memcpy(ptr, pcm.buf, to_cpy);
+ len_available -= to_cpy;
- is->offset += to_cpy;
+ base.offset += to_cpy;
return to_cpy;
}
+static size_t
+input_despotify_read(InputStream *is, void *ptr, size_t size, Error &error)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+ return ctx->Read(ptr, size, error);
+}
+
static void
input_despotify_close(InputStream *is)
{
@@ -204,18 +235,15 @@ input_despotify_eof(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- return ctx->eof;
+ return ctx->IsEOF();
}
static Tag *
input_despotify_tag(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- Tag *tag = ctx->tag;
-
- ctx->tag = nullptr;
- return tag;
+ return ctx->ReadTag();
}
const InputPlugin input_plugin_despotify = {
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx
index 8f9cd0b86..7d041677b 100644
--- a/src/input/FfmpegInputPlugin.cxx
+++ b/src/input/FfmpegInputPlugin.cxx
@@ -24,17 +24,15 @@
#include "FfmpegInputPlugin.hxx"
#include "InputStream.hxx"
#include "InputPlugin.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
extern "C" {
-#include <libavutil/avutil.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
}
-#include <glib.h>
-
struct FfmpegInputStream {
InputStream base;
@@ -91,12 +89,12 @@ input_ffmpeg_open(const char *uri,
Mutex &mutex, Cond &cond,
Error &error)
{
- if (!g_str_has_prefix(uri, "gopher://") &&
- !g_str_has_prefix(uri, "rtp://") &&
- !g_str_has_prefix(uri, "rtsp://") &&
- !g_str_has_prefix(uri, "rtmp://") &&
- !g_str_has_prefix(uri, "rtmpt://") &&
- !g_str_has_prefix(uri, "rtmps://"))
+ if (!StringStartsWith(uri, "gopher://") &&
+ !StringStartsWith(uri, "rtp://") &&
+ !StringStartsWith(uri, "rtsp://") &&
+ !StringStartsWith(uri, "rtmp://") &&
+ !StringStartsWith(uri, "rtmpt://") &&
+ !StringStartsWith(uri, "rtmps://"))
return nullptr;
AVIOContext *h;
diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx
index 26e40d609..5a63a469c 100644
--- a/src/input/FileInputPlugin.cxx
+++ b/src/input/FileInputPlugin.cxx
@@ -30,8 +30,6 @@
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
-#include <string.h>
-#include <glib.h>
static constexpr Domain file_domain("file");
@@ -62,7 +60,7 @@ input_file_open(const char *filename,
int fd, ret;
struct stat st;
- if (!PathTraits::IsAbsoluteFS(filename))
+ if (!PathTraitsFS::IsAbsolute(filename))
return nullptr;
fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx
index e97c1eb3f..2c7f6d166 100644
--- a/src/input/MmsInputPlugin.cxx
+++ b/src/input/MmsInputPlugin.cxx
@@ -21,15 +21,12 @@
#include "MmsInputPlugin.hxx"
#include "InputStream.hxx"
#include "InputPlugin.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
#include <libmms/mmsx.h>
-#include <string.h>
-#include <errno.h>
-
struct MmsInputStream {
InputStream base;
@@ -61,10 +58,10 @@ input_mms_open(const char *url,
Mutex &mutex, Cond &cond,
Error &error)
{
- if (!g_str_has_prefix(url, "mms://") &&
- !g_str_has_prefix(url, "mmsh://") &&
- !g_str_has_prefix(url, "mmst://") &&
- !g_str_has_prefix(url, "mmsu://"))
+ if (!StringStartsWith(url, "mms://") &&
+ !StringStartsWith(url, "mmsh://") &&
+ !StringStartsWith(url, "mmst://") &&
+ !StringStartsWith(url, "mmsu://"))
return nullptr;
const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024);
diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx
index e11f56631..78ab75660 100644
--- a/src/input/RewindInputPlugin.cxx
+++ b/src/input/RewindInputPlugin.cxx
@@ -21,7 +21,6 @@
#include "RewindInputPlugin.hxx"
#include "InputStream.hxx"
#include "InputPlugin.hxx"
-#include "tag/Tag.hxx"
#include <assert.h>
#include <string.h>
diff --git a/src/input/SmbclientInputPlugin.cxx b/src/input/SmbclientInputPlugin.cxx
new file mode 100644
index 000000000..f97aff7d5
--- /dev/null
+++ b/src/input/SmbclientInputPlugin.cxx
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2003-2013 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 "SmbclientInputPlugin.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <libsmbclient.h>
+
+#include <string.h>
+
+class SmbclientInputStream {
+ InputStream base;
+
+ SMBCCTX *ctx;
+ int fd;
+
+public:
+ SmbclientInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ SMBCCTX *_ctx, int _fd, const struct stat &st)
+ :base(input_plugin_smbclient, uri, mutex, cond),
+ ctx(_ctx), fd(_fd) {
+ base.ready = true;
+ base.seekable = true;
+ base.size = st.st_size;
+ }
+
+ ~SmbclientInputStream() {
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ }
+
+ InputStream *GetBase() {
+ return &base;
+ }
+
+ bool IsEOF() const {
+ return base.offset >= base.size;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) {
+ ssize_t nbytes = smbc_read(fd, ptr, size);
+ if (nbytes < 0) {
+ error.SetErrno("smbc_read() failed");
+ nbytes = 0;
+ }
+
+ return nbytes;
+ }
+
+ bool Seek(InputStream::offset_type offset, int whence, Error &error) {
+ off_t result = smbc_lseek(fd, offset, whence);
+ if (result < 0) {
+ error.SetErrno("smbc_lseek() failed");
+ return false;
+ }
+
+ base.offset = result;
+ return true;
+ }
+};
+
+static void
+mpd_smbc_get_auth_data(gcc_unused const char *srv,
+ gcc_unused const char *shr,
+ char *wg, gcc_unused int wglen,
+ char *un, gcc_unused int unlen,
+ char *pw, gcc_unused int pwlen)
+{
+ // TODO: implement
+ strcpy(wg, "WORKGROUP");
+ strcpy(un, "foo");
+ strcpy(pw, "bar");
+}
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static bool
+input_smbclient_init(gcc_unused const config_param &param, Error &error)
+{
+ constexpr int debug = 0;
+ if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
+ error.SetErrno("smbc_init() failed");
+ return false;
+ }
+
+ // TODO: create one global SMBCCTX here?
+
+ // TODO: evaluate config_param, call smbc_setOption*()
+
+ return true;
+}
+
+static InputStream *
+input_smbclient_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "smb://"))
+ return nullptr;
+
+ SMBCCTX *ctx = smbc_new_context();
+ if (ctx == nullptr) {
+ error.SetErrno("smbc_new_context() failed");
+ return nullptr;
+ }
+
+ SMBCCTX *ctx2 = smbc_init_context(ctx);
+ if (ctx2 == nullptr) {
+ error.SetErrno("smbc_init_context() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ ctx = ctx2;
+
+ int fd = smbc_open(uri, O_RDONLY, 0);
+ if (fd < 0) {
+ error.SetErrno("smbc_open() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ struct stat st;
+ if (smbc_fstat(fd, &st) < 0) {
+ error.SetErrno("smbc_fstat() failed");
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ auto s = new SmbclientInputStream(uri, mutex, cond, ctx, fd, st);
+ return s->GetBase();
+}
+
+static size_t
+input_smbclient_read(InputStream *is, void *ptr, size_t size,
+ Error &error)
+{
+ SmbclientInputStream &s = *(SmbclientInputStream *)is;
+ return s.Read(ptr, size, error);
+}
+
+static void
+input_smbclient_close(InputStream *is)
+{
+ SmbclientInputStream *s = (SmbclientInputStream *)is;
+ delete s;
+}
+
+static bool
+input_smbclient_eof(InputStream *is)
+{
+ SmbclientInputStream &s = *(SmbclientInputStream *)is;
+ return s.IsEOF();
+}
+
+static bool
+input_smbclient_seek(InputStream *is,
+ InputPlugin::offset_type offset, int whence,
+ Error &error)
+{
+ SmbclientInputStream &s = *(SmbclientInputStream *)is;
+ return s.Seek(offset, whence, error);
+}
+
+const InputPlugin input_plugin_smbclient = {
+ "smbclient",
+ input_smbclient_init,
+ nullptr,
+ input_smbclient_open,
+ input_smbclient_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_smbclient_read,
+ input_smbclient_eof,
+ input_smbclient_seek,
+};
diff --git a/src/input/SmbclientInputPlugin.hxx b/src/input/SmbclientInputPlugin.hxx
new file mode 100644
index 000000000..7203a01b8
--- /dev/null
+++ b/src/input/SmbclientInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2013 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_INPUT_SMBCLIENT_H
+#define MPD_INPUT_SMBCLIENT_H
+
+extern const struct InputPlugin input_plugin_smbclient;
+
+#endif
diff --git a/src/ls.cxx b/src/ls.cxx
index b1a636416..1de515a1f 100644
--- a/src/ls.cxx
+++ b/src/ls.cxx
@@ -19,14 +19,11 @@
#include "config.h"
#include "ls.hxx"
+#include "util/StringUtil.hxx"
#include "util/UriUtil.hxx"
#include "Client.hxx"
-#include <glib.h>
-
#include <assert.h>
-#include <string.h>
-
/**
* file:// is not included in remoteUrlPrefixes, the connection method
@@ -52,12 +49,18 @@ static const char *remoteUrlPrefixes[] = {
"rtmpt://",
"rtmps://",
#endif
+#ifdef ENABLE_SMBCLIENT
+ "smb://",
+#endif
#ifdef ENABLE_CDIO_PARANOIA
"cdda://",
#endif
#ifdef ENABLE_DESPOTIFY
"spt://",
#endif
+#ifdef HAVE_ALSA
+ "alsa://",
+#endif
NULL
};
@@ -92,7 +95,7 @@ bool uri_supported_scheme(const char *uri)
assert(uri_has_scheme(uri));
while (*urlPrefixes) {
- if (g_str_has_prefix(uri, *urlPrefixes))
+ if (StringStartsWith(uri, *urlPrefixes))
return true;
urlPrefixes++;
}
diff --git a/src/ls.hxx b/src/ls.hxx
index 3879563ee..6d5f989bc 100644
--- a/src/ls.hxx
+++ b/src/ls.hxx
@@ -20,6 +20,8 @@
#ifndef MPD_LS_HXX
#define MPD_LS_HXX
+#include "Compiler.h"
+
#include <stdio.h>
class Client;
@@ -29,6 +31,7 @@ class Client;
* It is not allowed to pass an URI without a scheme, check with
* uri_has_scheme() first.
*/
+gcc_pure
bool uri_supported_scheme(const char *url);
/**
diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx
index 4a4ca433c..4f92e697d 100644
--- a/src/mixer/AlsaMixerPlugin.cxx
+++ b/src/mixer/AlsaMixerPlugin.cxx
@@ -23,10 +23,11 @@
#include "GlobalEvents.hxx"
#include "Main.hxx"
#include "event/MultiSocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
#include "event/Loop.hxx"
-#include "event/Call.hxx"
#include "util/ASCII.hxx"
#include "util/ReusableArray.hxx"
+#include "util/Clamp.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -39,29 +40,22 @@
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0;
-class AlsaMixerMonitor final : private MultiSocketMonitor {
+class AlsaMixerMonitor final : MultiSocketMonitor, DeferredMonitor {
snd_mixer_t *mixer;
ReusableArray<pollfd> pfd_buffer;
public:
AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
- :MultiSocketMonitor(_loop), mixer(_mixer) {
-#ifdef USE_EPOLL
- _loop.AddCall([this](){ InvalidateSockets(); });
-#else
- _loop.AddIdle(InitAlsaMixerMonitor, this);
-#endif
+ :MultiSocketMonitor(_loop), DeferredMonitor(_loop),
+ mixer(_mixer) {
+ DeferredMonitor::Schedule();
}
private:
-#ifndef USE_EPOLL
- static gboolean InitAlsaMixerMonitor(gpointer data) {
- AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data;
- amm.InvalidateSockets();
- return false;
+ virtual void RunDeferred() override {
+ InvalidateSockets();
}
-#endif
virtual int PrepareSockets() override;
virtual void DispatchSockets() override;
@@ -97,8 +91,10 @@ static constexpr Domain alsa_mixer_domain("alsa_mixer");
int
AlsaMixerMonitor::PrepareSockets()
{
- if (mixer == nullptr)
+ if (mixer == nullptr) {
+ ClearSocketList();
return -1;
+ }
int count = snd_mixer_poll_descriptors_count(mixer);
if (count < 0)
@@ -110,24 +106,7 @@ AlsaMixerMonitor::PrepareSockets()
if (count < 0)
count = 0;
- struct pollfd *end = pfds + count;
-
- UpdateSocketList([pfds, end](int fd) -> unsigned {
- auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){
- return pfd.fd == fd;
- });
- if (i == end)
- return 0;
-
- auto events = i->events;
- i->events = 0;
- return events;
- });
-
- for (auto i = pfds; i != end; ++i)
- if (i->events != 0)
- AddSocket(i->fd, i->events);
-
+ ReplaceSocketList(pfds, count);
return -1;
}
@@ -372,8 +351,7 @@ AlsaMixer::SetVolume(unsigned volume, Error &error)
level = (long)(((vol / 100.0) * (volume_max - volume_min) +
volume_min) + 0.5);
- level = level > volume_max ? volume_max : level;
- level = level < volume_min ? volume_min : level;
+ level = Clamp(level, volume_min, volume_max);
err = snd_mixer_selem_set_playback_volume_all(elem, level);
if (err < 0) {
diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx
index 0a459bc97..7a91cfcb1 100644
--- a/src/mixer/OssMixerPlugin.cxx
+++ b/src/mixer/OssMixerPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
+#include "ConfigData.hxx"
#include "system/fd_util.h"
#include "util/ASCII.hxx"
#include "util/Error.hxx"
@@ -28,10 +28,8 @@
#include <assert.h>
#include <string.h>
-#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
-#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx
index ff10256cb..84d42c392 100644
--- a/src/mixer/PulseMixerPlugin.cxx
+++ b/src/mixer/PulseMixerPlugin.cxx
@@ -26,7 +26,6 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <pulse/thread-mainloop.h>
#include <pulse/context.h>
#include <pulse/introspect.h>
#include <pulse/stream.h>
@@ -34,7 +33,6 @@
#include <pulse/error.h>
#include <assert.h>
-#include <string.h>
struct PulseMixer final : public Mixer {
PulseOutput *output;
diff --git a/src/mixer/PulseMixerPlugin.hxx b/src/mixer/PulseMixerPlugin.hxx
index fa73e0f5e..46bd06a01 100644
--- a/src/mixer/PulseMixerPlugin.hxx
+++ b/src/mixer/PulseMixerPlugin.hxx
@@ -20,20 +20,17 @@
#ifndef MPD_PULSE_MIXER_PLUGIN_HXX
#define MPD_PULSE_MIXER_PLUGIN_HXX
-#include <pulse/def.h>
-
struct PulseMixer;
struct pa_context;
struct pa_stream;
void
-pulse_mixer_on_connect(PulseMixer *pm, struct pa_context *context);
+pulse_mixer_on_connect(PulseMixer *pm, pa_context *context);
void
pulse_mixer_on_disconnect(PulseMixer *pm);
void
-pulse_mixer_on_change(PulseMixer *pm,
- struct pa_context *context, struct pa_stream *stream);
+pulse_mixer_on_change(PulseMixer *pm, pa_context *context, pa_stream *stream);
#endif
diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx
index 6bd700551..75147329e 100644
--- a/src/mixer/RoarMixerPlugin.cxx
+++ b/src/mixer/RoarMixerPlugin.cxx
@@ -21,8 +21,8 @@
#include "config.h"
#include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
#include "output/RoarOutputPlugin.hxx"
+#include "Compiler.h"
struct RoarMixer final : public Mixer {
/** the base mixer class */
diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx
index 193e68f23..e5f4b1659 100644
--- a/src/mixer/SoftwareMixerPlugin.cxx
+++ b/src/mixer/SoftwareMixerPlugin.cxx
@@ -24,7 +24,7 @@
#include "FilterRegistry.hxx"
#include "FilterInternal.hxx"
#include "filter/VolumeFilterPlugin.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "ConfigData.hxx"
#include "util/Error.hxx"
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
index 4877d3a46..b5ca511b2 100644
--- a/src/output/AlsaOutputPlugin.cxx
+++ b/src/output/AlsaOutputPlugin.cxx
@@ -27,7 +27,6 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <alsa/asoundlib.h>
#include <string>
@@ -118,7 +117,7 @@ struct AlsaOutput {
* It contains silence samples, enough to fill one period (see
* #period_frames).
*/
- void *silence;
+ uint8_t *silence;
AlsaOutput():mode(0), writei(snd_pcm_writei) {
}
@@ -593,8 +592,8 @@ configure_hw:
ad->period_frames = alsa_period_size;
ad->period_position = 0;
- ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm,
- alsa_period_size));
+ ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
+ alsa_period_size)];
snd_pcm_format_set_silence(format, ad->silence,
alsa_period_size * channels);
@@ -641,7 +640,7 @@ alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
error.Format(alsa_output_domain,
"Failed to configure DSD-over-USB on ALSA device \"%s\"",
alsa_device(ad));
- g_free(ad->silence);
+ delete[] ad->silence;
return false;
}
@@ -811,7 +810,7 @@ alsa_close(struct audio_output *ao)
AlsaOutput *ad = (AlsaOutput *)ao;
snd_pcm_close(ad->pcm);
- g_free(ad->silence);
+ delete[] ad->silence;
}
static size_t
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
index aeb9a6a87..7963d8c82 100644
--- a/src/output/FifoOutputPlugin.cxx
+++ b/src/output/FifoOutputPlugin.cxx
@@ -22,7 +22,6 @@
#include "ConfigError.hxx"
#include "OutputAPI.hxx"
#include "Timer.hxx"
-#include "system/fd_util.h"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
@@ -30,10 +29,8 @@
#include "Log.hxx"
#include "open.h"
-#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
-#include <string.h>
#include <unistd.h>
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
index 8e13fda38..102ce1ec3 100644
--- a/src/output/HttpdClient.cxx
+++ b/src/output/HttpdClient.cxx
@@ -37,24 +37,26 @@ HttpdClient::~HttpdClient()
if (current_page != nullptr)
current_page->Unref();
- for (auto page : pages)
- page->Unref();
+ ClearQueue();
}
if (metadata)
metadata->Unref();
+
+ if (IsDefined())
+ BufferedSocket::Close();
}
void
HttpdClient::Close()
{
- httpd->RemoveClient(*this);
+ httpd.RemoveClient(*this);
}
void
HttpdClient::LockClose()
{
- const ScopeLock protect(httpd->mutex);
+ const ScopeLock protect(httpd.mutex);
Close();
}
@@ -67,7 +69,7 @@ HttpdClient::BeginResponse()
current_page = nullptr;
if (!head_method)
- httpd->SendHeader(*this);
+ httpd.SendHeader(*this);
}
/**
@@ -155,13 +157,13 @@ HttpdClient::SendResponse()
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
"\r\n",
- httpd->content_type);
+ httpd.content_type);
} else if (metadata_requested) {
char *metadata_header =
- icy_server_metadata_header(httpd->name, httpd->genre,
- httpd->website,
- httpd->content_type,
+ icy_server_metadata_header(httpd.name, httpd.genre,
+ httpd.website,
+ httpd.content_type,
metaint);
g_strlcpy(buffer, metadata_header, sizeof(buffer));
@@ -176,7 +178,7 @@ HttpdClient::SendResponse()
"Pragma: no-cache\r\n"
"Cache-Control: no-cache, no-store\r\n"
"\r\n",
- httpd->content_type);
+ httpd.content_type);
}
ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
@@ -192,11 +194,12 @@ HttpdClient::SendResponse()
return true;
}
-HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
+HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
bool _metadata_supported)
:BufferedSocket(_fd, _loop),
httpd(_httpd),
state(REQUEST),
+ queue_size(0),
head_method(false),
dlna_streaming_requested(false),
metadata_supported(_metadata_supported),
@@ -207,16 +210,24 @@ HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
{
}
-size_t
-HttpdClient::GetQueueSize() const
+void
+HttpdClient::ClearQueue()
{
- if (state != RESPONSE)
- return 0;
+ assert(state == RESPONSE);
- size_t size = 0;
- for (auto page : pages)
- size += page->size;
- return size;
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+#ifndef NDEBUG
+ assert(queue_size >= page->size);
+ queue_size -= page->size;
+#endif
+
+ page->Unref();
+ }
+
+ assert(queue_size == 0);
}
void
@@ -225,9 +236,7 @@ HttpdClient::CancelQueue()
if (state != RESPONSE)
return;
- for (auto page : pages)
- page->Unref();
- pages.clear();
+ ClearQueue();
if (current_page == nullptr)
CancelWrite();
@@ -262,7 +271,7 @@ HttpdClient::GetBytesTillMetaData() const
inline bool
HttpdClient::TryWrite()
{
- const ScopeLock protect(httpd->mutex);
+ const ScopeLock protect(httpd.mutex);
assert(state == RESPONSE);
@@ -270,14 +279,17 @@ HttpdClient::TryWrite()
if (pages.empty()) {
/* another thread has removed the event source
while this thread was waiting for
- httpd->mutex */
+ httpd.mutex */
CancelWrite();
return true;
}
current_page = pages.front();
- pages.pop_front();
+ pages.pop();
current_position = 0;
+
+ assert(queue_size >= current_page->size);
+ queue_size -= current_page->size;
}
const ssize_t bytes_to_write = GetBytesTillMetaData();
@@ -378,8 +390,15 @@ HttpdClient::PushPage(Page *page)
/* the client is still writing the HTTP request */
return;
+ if (queue_size > 256 * 1024) {
+ FormatDebug(httpd_output_domain,
+ "client is too slow, flushing its queue");
+ ClearQueue();
+ }
+
page->Ref();
- pages.push_back(page);
+ pages.push(page);
+ queue_size += page->size;
ScheduleWrite();
}
diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx
index 66a819232..94fe2ae62 100644
--- a/src/output/HttpdClient.hxx
+++ b/src/output/HttpdClient.hxx
@@ -23,18 +23,19 @@
#include "event/BufferedSocket.hxx"
#include "Compiler.h"
+#include <queue>
#include <list>
#include <stddef.h>
-struct HttpdOutput;
+class HttpdOutput;
class Page;
-class HttpdClient final : public BufferedSocket {
+class HttpdClient final : BufferedSocket {
/**
* The httpd output object this client is connected to.
*/
- HttpdOutput *const httpd;
+ HttpdOutput &httpd;
/**
* The current state of the client.
@@ -53,7 +54,12 @@ class HttpdClient final : public BufferedSocket {
/**
* A queue of #Page objects to be sent to the client.
*/
- std::list<Page *> pages;
+ std::queue<Page *, std::list<Page *>> pages;
+
+ /**
+ * The sum of all page sizes in #pages.
+ */
+ size_t queue_size;
/**
* The #page which is currently being sent to the client.
@@ -120,7 +126,7 @@ public:
* @param httpd the HTTP output device
* @param fd the socket file descriptor
*/
- HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop,
+ HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop,
bool _metadata_supported);
/**
@@ -137,12 +143,6 @@ public:
void LockClose();
/**
- * Returns the total size of this client's page queue.
- */
- gcc_pure
- size_t GetQueueSize() const;
-
- /**
* Clears the page queue.
*/
void CancelQueue();
@@ -180,6 +180,9 @@ public:
*/
void PushMetaData(Page *page);
+private:
+ void ClearQueue();
+
protected:
virtual bool OnSocketReady(unsigned flags) override;
virtual InputResult OnSocketInput(void *data, size_t length) override;
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
index b76493a44..8d35d35e9 100644
--- a/src/output/HttpdInternal.hxx
+++ b/src/output/HttpdInternal.hxx
@@ -29,6 +29,8 @@
#include "Timer.hxx"
#include "thread/Mutex.hxx"
#include "event/ServerSocket.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Cast.hxx"
#ifdef _LIBCPP_VERSION
/* can't use incomplete template arguments with libc++ */
@@ -36,6 +38,8 @@
#endif
#include <forward_list>
+#include <queue>
+#include <list>
struct config_param;
class Error;
@@ -46,7 +50,7 @@ class Page;
struct Encoder;
struct Tag;
-struct HttpdOutput final : private ServerSocket {
+class HttpdOutput final : ServerSocket, DeferredMonitor {
struct audio_output base;
/**
@@ -68,6 +72,7 @@ struct HttpdOutput final : private ServerSocket {
*/
size_t unflushed_input;
+public:
/**
* The MIME type produced by the #encoder.
*/
@@ -80,6 +85,13 @@ struct HttpdOutput final : private ServerSocket {
mutable Mutex mutex;
/**
+ * This condition gets signalled when an item is removed from
+ * #pages.
+ */
+ Cond cond;
+
+private:
+ /**
* A #Timer object to synchronize this output with the
* wallclock.
*/
@@ -96,6 +108,15 @@ struct HttpdOutput final : private ServerSocket {
Page *metadata;
/**
+ * The page queue, i.e. pages from the encoder to be
+ * broadcasted to all clients. This container is necessary to
+ * pass pages from the OutputThread to the IOThread. It is
+ * protected by #mutex, and removing signals #cond.
+ */
+ std::queue<Page *, std::list<Page *>> pages;
+
+ public:
+ /**
* The configured name.
*/
char const *name;
@@ -108,6 +129,7 @@ struct HttpdOutput final : private ServerSocket {
*/
char const *website;
+private:
/**
* A linked list containing all clients which are currently
* connected.
@@ -126,11 +148,46 @@ struct HttpdOutput final : private ServerSocket {
*/
unsigned clients_max, clients_cnt;
+public:
HttpdOutput(EventLoop &_loop);
~HttpdOutput();
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+#endif
+
+ static constexpr HttpdOutput *Cast(audio_output *ao) {
+ return ContainerCast(ao, HttpdOutput, base);
+ }
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ using DeferredMonitor::GetEventLoop;
+
+ bool Init(const config_param &param, Error &error);
+
+ void Finish() {
+ ao_base_finish(&base);
+ }
+
bool Configure(const config_param &param, Error &error);
+ audio_output *InitAndConfigure(const config_param &param,
+ Error &error) {
+ if (!Init(param, error))
+ return nullptr;
+
+ if (!Configure(param, error)) {
+ Finish();
+ return nullptr;
+ }
+
+ return &base;
+ }
+
bool Bind(Error &error);
void Unbind();
@@ -181,6 +238,9 @@ struct HttpdOutput final : private ServerSocket {
*/
void SendHeader(HttpdClient &client) const;
+ gcc_pure
+ unsigned Delay() const;
+
/**
* Reads data from the encoder (as much as available) and
* returns it as a new #page object.
@@ -203,7 +263,13 @@ struct HttpdOutput final : private ServerSocket {
void SendTag(const Tag *tag);
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+ void CancelAllClients();
+
private:
+ virtual void RunDeferred() override;
+
virtual void OnAccept(int fd, const sockaddr &address,
size_t address_length, int uid) override;
};
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
index 369c06937..4cd2b4ae8 100644
--- a/src/output/HttpdOutputPlugin.cxx
+++ b/src/output/HttpdOutputPlugin.cxx
@@ -28,13 +28,12 @@
#include "Page.hxx"
#include "IcyMetaDataServer.hxx"
#include "system/fd_util.h"
-#include "Main.hxx"
+#include "IOThread.hxx"
+#include "event/Call.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <sys/types.h>
@@ -51,7 +50,7 @@ const Domain httpd_output_domain("httpd_output");
inline
HttpdOutput::HttpdOutput(EventLoop &_loop)
- :ServerSocket(_loop),
+ :ServerSocket(_loop), DeferredMonitor(_loop),
encoder(nullptr), unflushed_input(0),
metadata(nullptr)
{
@@ -72,8 +71,11 @@ HttpdOutput::Bind(Error &error)
{
open = false;
- const ScopeLock protect(mutex);
- return ServerSocket::Open(error);
+ bool result = false;
+ BlockingCall(GetEventLoop(), [this, &error, &result](){
+ result = ServerSocket::Open(error);
+ });
+ return result;
}
inline void
@@ -81,8 +83,9 @@ HttpdOutput::Unbind()
{
assert(!open);
- const ScopeLock protect(mutex);
- ServerSocket::Close();
+ BlockingCall(GetEventLoop(), [this](){
+ ServerSocket::Close();
+ });
}
inline bool
@@ -130,47 +133,30 @@ HttpdOutput::Configure(const config_param &param, Error &error)
return true;
}
+inline bool
+HttpdOutput::Init(const config_param &param, Error &error)
+{
+ return ao_base_init(&base, &httpd_output_plugin, param, error);
+}
+
static struct audio_output *
httpd_output_init(const config_param &param, Error &error)
{
- HttpdOutput *httpd = new HttpdOutput(*main_loop);
+ HttpdOutput *httpd = new HttpdOutput(io_thread_get());
- if (!ao_base_init(&httpd->base, &httpd_output_plugin, param,
- error)) {
+ audio_output *result = httpd->InitAndConfigure(param, error);
+ if (result == nullptr)
delete httpd;
- return nullptr;
- }
- if (!httpd->Configure(param, error)) {
- ao_base_finish(&httpd->base);
- delete httpd;
- return nullptr;
- }
-
- return &httpd->base;
-}
-
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Winvalid-offsetof"
-#endif
-
-static inline constexpr HttpdOutput *
-Cast(audio_output *ao)
-{
- return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base));
+ return result;
}
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-
static void
httpd_output_finish(struct audio_output *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
- ao_base_finish(&httpd->base);
+ httpd->Finish();
delete httpd;
}
@@ -181,7 +167,7 @@ httpd_output_finish(struct audio_output *ao)
inline void
HttpdOutput::AddClient(int fd)
{
- clients.emplace_front(this, fd, GetEventLoop(),
+ clients.emplace_front(*this, fd, GetEventLoop(),
encoder->plugin.tag == nullptr);
++clients_cnt;
@@ -191,6 +177,29 @@ HttpdOutput::AddClient(int fd)
}
void
+HttpdOutput::RunDeferred()
+{
+ /* this method runs in the IOThread; it broadcasts pages from
+ our own queue to all clients */
+
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+ for (auto &client : clients)
+ client.PushPage(page);
+
+ page->Unref();
+ }
+
+ /* wake up the client that may be waiting for the queue to be
+ flushed */
+ cond.broadcast();
+}
+
+void
HttpdOutput::OnAccept(int fd, const sockaddr &address,
size_t address_length, gcc_unused int uid)
{
@@ -199,9 +208,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
#ifdef HAVE_LIBWRAP
if (address.sa_family != AF_UNIX) {
- char *hostaddr = sockaddr_to_string(&address, address_length,
- IgnoreError());
- const char *progname = g_get_prgname();
+ const auto hostaddr = sockaddr_to_string(&address,
+ address_length);
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
struct request_info req;
request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
@@ -212,13 +222,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
/* tcp wrappers says no */
FormatWarning(httpd_output_domain,
"libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
- g_free(hostaddr);
+ progname, hostaddr.c_str());
close_socket(fd);
return;
}
-
- g_free(hostaddr);
}
#else
(void)address;
@@ -271,7 +278,7 @@ HttpdOutput::ReadPage()
static bool
httpd_output_enable(struct audio_output *ao, Error &error)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
return httpd->Bind(error);
}
@@ -279,7 +286,7 @@ httpd_output_enable(struct audio_output *ao, Error &error)
static void
httpd_output_disable(struct audio_output *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
httpd->Unbind();
}
@@ -325,9 +332,7 @@ static bool
httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
Error &error)
{
- HttpdOutput *httpd = Cast(ao);
-
- assert(httpd->clients.empty());
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
const ScopeLock protect(httpd->mutex);
return httpd->Open(audio_format, error);
@@ -342,7 +347,9 @@ HttpdOutput::Close()
delete timer;
- clients.clear();
+ BlockingCall(GetEventLoop(), [this](){
+ clients.clear();
+ });
if (header != nullptr)
header->Unref();
@@ -353,7 +360,7 @@ HttpdOutput::Close()
static void
httpd_output_close(struct audio_output *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
const ScopeLock protect(httpd->mutex);
httpd->Close();
@@ -382,17 +389,15 @@ HttpdOutput::SendHeader(HttpdClient &client) const
client.PushPage(header);
}
-static unsigned
-httpd_output_delay(struct audio_output *ao)
+inline unsigned
+HttpdOutput::Delay() const
{
- HttpdOutput *httpd = Cast(ao);
-
- if (!httpd->LockHasClients() && httpd->base.pause) {
+ if (!LockHasClients() && base.pause) {
/* if there's no client and this output is paused,
then httpd_output_pause() will not do anything, it
will not fill the buffer and it will not update the
timer; therefore, we reset the timer here */
- httpd->timer->Reset();
+ timer->Reset();
/* some arbitrary delay that is long enough to avoid
consuming too much CPU, and short enough to notice
@@ -400,39 +405,47 @@ httpd_output_delay(struct audio_output *ao)
return 1000;
}
- return httpd->timer->IsStarted()
- ? httpd->timer->GetDelay()
+ return timer->IsStarted()
+ ? timer->GetDelay()
: 0;
}
+static unsigned
+httpd_output_delay(struct audio_output *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Delay();
+}
+
void
HttpdOutput::BroadcastPage(Page *page)
{
assert(page != nullptr);
- const ScopeLock protect(mutex);
- for (auto &client : clients)
- client.PushPage(page);
+ mutex.lock();
+ pages.push(page);
+ page->Ref();
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
}
void
HttpdOutput::BroadcastFromEncoder()
{
+ /* synchronize with the IOThread */
mutex.lock();
- for (auto &client : clients) {
- if (client.GetQueueSize() > 256 * 1024) {
- FormatDebug(httpd_output_domain,
- "client is too slow, flushing its queue");
- client.CancelQueue();
- }
- }
- mutex.unlock();
+ while (!pages.empty())
+ cond.wait(mutex);
Page *page;
- while ((page = ReadPage()) != nullptr) {
- BroadcastPage(page);
- page->Unref();
- }
+ while ((page = ReadPage()) != nullptr)
+ pages.push(page);
+
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
}
inline bool
@@ -447,28 +460,34 @@ HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
return true;
}
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+HttpdOutput::Play(const void *chunk, size_t size, Error &error)
{
- HttpdOutput *httpd = Cast(ao);
-
- if (httpd->LockHasClients()) {
- if (!httpd->EncodeAndPlay(chunk, size, error))
+ if (LockHasClients()) {
+ if (!EncodeAndPlay(chunk, size, error))
return 0;
}
- if (!httpd->timer->IsStarted())
- httpd->timer->Start();
- httpd->timer->Add(size);
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
return size;
}
+static size_t
+httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Play(chunk, size, error);
+}
+
static bool
httpd_output_pause(struct audio_output *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
if (httpd->LockHasClients()) {
static const char silence[1020] = { 0 };
@@ -531,19 +550,36 @@ HttpdOutput::SendTag(const Tag *tag)
static void
httpd_output_tag(struct audio_output *ao, const Tag *tag)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
httpd->SendTag(tag);
}
+inline void
+HttpdOutput::CancelAllClients()
+{
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+ page->Unref();
+ }
+
+ for (auto &client : clients)
+ client.CancelQueue();
+
+ cond.broadcast();
+}
+
static void
httpd_output_cancel(struct audio_output *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
- const ScopeLock protect(httpd->mutex);
- for (auto &client : httpd->clients)
- client.CancelQueue();
+ BlockingCall(io_thread_get(), [httpd](){
+ httpd->CancelAllClients();
+ });
}
const struct audio_output_plugin httpd_output_plugin = {
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
index 7ed672f95..ef06b5abf 100644
--- a/src/output/JackOutputPlugin.cxx
+++ b/src/output/JackOutputPlugin.cxx
@@ -34,10 +34,6 @@
#include <stdlib.h>
#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
enum {
MAX_PORTS = 16,
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
index e2eec9dbc..a901b7d2a 100644
--- a/src/output/NullOutputPlugin.cxx
+++ b/src/output/NullOutputPlugin.cxx
@@ -22,8 +22,6 @@
#include "OutputAPI.hxx"
#include "Timer.hxx"
-#include <assert.h>
-
struct NullOutput {
struct audio_output base;
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx
index 97ebae056..586210b21 100644
--- a/src/output/OSXOutputPlugin.cxx
+++ b/src/output/OSXOutputPlugin.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "OSXOutputPlugin.hxx"
#include "OutputAPI.hxx"
-#include "util/fifo_buffer.h"
+#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "thread/Mutex.hxx"
@@ -44,7 +44,7 @@ struct OSXOutput {
Mutex mutex;
Cond condition;
- struct fifo_buffer *buffer;
+ DynamicFifoBuffer<uint8_t> *buffer;
};
static constexpr Domain osx_output_domain("osx_output");
@@ -72,7 +72,7 @@ osx_output_configure(OSXOutput *oo, const config_param &param)
}
else {
oo->component_subtype = kAudioUnitSubType_HALOutput;
- /* XXX am I supposed to g_strdup() this? */
+ /* XXX am I supposed to strdup() this? */
oo->device_name = device;
}
}
@@ -207,22 +207,19 @@ osx_render(void *vdata,
od->mutex.lock();
- size_t nbytes;
- const void *src = fifo_buffer_read(od->buffer, &nbytes);
+ auto src = od->buffer->Read();
+ if (!src.IsEmpty()) {
+ if (src.size > buffer_size)
+ src.size = buffer_size;
- if (src != NULL) {
- if (nbytes > buffer_size)
- nbytes = buffer_size;
-
- memcpy(buffer->mData, src, nbytes);
- fifo_buffer_consume(od->buffer, nbytes);
- } else
- nbytes = 0;
+ memcpy(buffer->mData, src.data, src.size);
+ od->buffer->Consume(src.size);
+ }
od->condition.signal();
od->mutex.unlock();
- buffer->mDataByteSize = nbytes;
+ buffer->mDataByteSize = src.size;
unsigned i;
for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
@@ -298,7 +295,7 @@ osx_output_cancel(struct audio_output *ao)
OSXOutput *od = (OSXOutput *)ao;
const ScopeLock protect(od->mutex);
- fifo_buffer_clear(od->buffer);
+ od->buffer->Clear();
}
static void
@@ -309,7 +306,7 @@ osx_output_close(struct audio_output *ao)
AudioOutputUnitStop(od->au);
AudioUnitUninitialize(od->au);
- fifo_buffer_free(od->buffer);
+ delete od->buffer;
}
static bool
@@ -370,8 +367,8 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
/* create a buffer of 1s */
- od->buffer = fifo_buffer_new(audio_format.sample_rate *
- audio_format.GetFrameSize());
+ od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate *
+ audio_format.GetFrameSize());
status = AudioOutputUnitStart(od->au);
if (status != 0) {
@@ -393,23 +390,21 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
const ScopeLock protect(od->mutex);
- void *dest;
- size_t max_length;
-
+ DynamicFifoBuffer<uint8_t>::Range dest;
while (true) {
- dest = fifo_buffer_write(od->buffer, &max_length);
- if (dest != NULL)
+ dest = od->buffer->Write();
+ if (!dest.IsEmpty())
break;
/* wait for some free space in the buffer */
od->condition.wait(od->mutex);
}
- if (size > max_length)
- size = max_length;
+ if (size > dest.size)
+ size = dest.size;
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
+ memcpy(dest.data, chunk, size);
+ od->buffer->Append(size);
return size;
}
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx
index 0ed8404bc..69d6c5f99 100644
--- a/src/output/PulseOutputPlugin.hxx
+++ b/src/output/PulseOutputPlugin.hxx
@@ -41,6 +41,6 @@ pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm);
bool
pulse_output_set_volume(PulseOutput *po,
- const struct pa_cvolume *volume, Error &error);
+ const pa_cvolume *volume, Error &error);
#endif
diff --git a/src/pcm/ChannelsConverter.cxx b/src/pcm/ChannelsConverter.cxx
new file mode 100644
index 000000000..8ffcbfe41
--- /dev/null
+++ b/src/pcm/ChannelsConverter.cxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2013 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 "ChannelsConverter.hxx"
+#include "PcmChannels.hxx"
+#include "Domain.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+bool
+PcmChannelsConverter::Open(SampleFormat _format,
+ unsigned _src_channels, unsigned _dest_channels,
+ gcc_unused Error &error)
+{
+ assert(_format != SampleFormat::UNDEFINED);
+
+ switch (_format) {
+ case SampleFormat::S16:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ break;
+
+ default:
+ error.Format(pcm_domain,
+ "PCM channel conversion for %s is not implemented",
+ sample_format_to_string(format));
+ return false;
+ }
+
+ format = _format;
+ src_channels = _src_channels;
+ dest_channels = _dest_channels;
+ return true;
+}
+
+void
+PcmChannelsConverter::Close()
+{
+#ifndef NDEBUG
+ format = SampleFormat::UNDEFINED;
+#endif
+}
+
+ConstBuffer<void>
+PcmChannelsConverter::Convert(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S16:
+ return pcm_convert_channels_16(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<int16_t>::FromVoid(src)).ToVoid();
+
+ case SampleFormat::S24_P32:
+ return pcm_convert_channels_24(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<int32_t>::FromVoid(src)).ToVoid();
+
+ case SampleFormat::S32:
+ return pcm_convert_channels_32(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<int32_t>::FromVoid(src)).ToVoid();
+
+ case SampleFormat::FLOAT:
+ return pcm_convert_channels_float(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<float>::FromVoid(src)).ToVoid();
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/ChannelsConverter.hxx b/src/pcm/ChannelsConverter.hxx
new file mode 100644
index 000000000..4311b9671
--- /dev/null
+++ b/src/pcm/ChannelsConverter.hxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_CHANNELS_CONVERTER_HXX
+#define MPD_PCM_CHANNELS_CONVERTER_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+#include "PcmBuffer.hxx"
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+class Error;
+template<typename T> struct ConstBuffer;
+
+/**
+ * A class that converts samples from one format to another.
+ */
+class PcmChannelsConverter {
+ SampleFormat format;
+ unsigned src_channels, dest_channels;
+
+ PcmBuffer buffer;
+
+public:
+#ifndef NDEBUG
+ PcmChannelsConverter()
+ :format(SampleFormat::UNDEFINED) {}
+
+ ~PcmChannelsConverter() {
+ assert(format == SampleFormat::UNDEFINED);
+ }
+#endif
+
+ /**
+ * Opens the object, prepare for Convert().
+ *
+ * @param format the sample format
+ * @param src_channels the number of source channels
+ * @param dest_channels the number of destination channels
+ * @param error location to store the error
+ * @return true on success
+ */
+ bool Open(SampleFormat format,
+ unsigned src_channels, unsigned dest_channels,
+ Error &error);
+
+ /**
+ * Closes the object. After that, you may call Open() again.
+ */
+ void Close();
+
+ /**
+ * Convert a block of PCM data.
+ *
+ * @param src the input buffer
+ * @param error location to store the error
+ * @return the destination buffer on success,
+ * ConstBuffer::Null() on error
+ */
+ gcc_pure
+ ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error);
+};
+
+#endif
diff --git a/src/pcm/ConfiguredResampler.cxx b/src/pcm/ConfiguredResampler.cxx
new file mode 100644
index 000000000..845fa2332
--- /dev/null
+++ b/src/pcm/ConfiguredResampler.cxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2013 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 "ConfiguredResampler.hxx"
+#include "FallbackResampler.hxx"
+#include "ConfigGlobal.hxx"
+#include "ConfigOption.hxx"
+#include "ConfigError.hxx"
+#include "util/Error.hxx"
+
+#ifdef HAVE_LIBSAMPLERATE
+#include "LibsamplerateResampler.hxx"
+#endif
+
+#ifdef HAVE_SOXR
+#include "SoxrResampler.hxx"
+#endif
+
+#include <string.h>
+
+enum class SelectedResampler {
+ FALLBACK,
+
+#ifdef HAVE_LIBSAMPLERATE
+ LIBSAMPLERATE,
+#endif
+
+#ifdef HAVE_SOXR
+ SOXR,
+#endif
+};
+
+static SelectedResampler selected_resampler = SelectedResampler::FALLBACK;
+
+bool
+pcm_resampler_global_init(Error &error)
+{
+ const char *converter =
+ config_get_string(CONF_SAMPLERATE_CONVERTER, "");
+
+ if (strcmp(converter, "internal") == 0)
+ return true;
+
+#ifdef HAVE_SOXR
+ if (strcmp(converter, "soxr") == 0) {
+ selected_resampler = SelectedResampler::SOXR;
+ return true;
+ }
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+ selected_resampler = SelectedResampler::LIBSAMPLERATE;
+ return pcm_resample_lsr_global_init(converter, error);
+#endif
+
+ if (*converter == 0)
+ return true;
+
+ error.Format(config_domain,
+ "The samplerate_converter '%s' is not available",
+ converter);
+ return false;
+}
+
+PcmResampler *
+pcm_resampler_create()
+{
+ switch (selected_resampler) {
+ case SelectedResampler::FALLBACK:
+ return new FallbackPcmResampler();
+
+#ifdef HAVE_LIBSAMPLERATE
+ case SelectedResampler::LIBSAMPLERATE:
+ return new LibsampleratePcmResampler();
+#endif
+
+#ifdef HAVE_SOXR
+ case SelectedResampler::SOXR:
+ return new SoxrPcmResampler();
+#endif
+ }
+
+ gcc_unreachable();
+}
diff --git a/src/pcm/ConfiguredResampler.hxx b/src/pcm/ConfiguredResampler.hxx
new file mode 100644
index 000000000..6d12ee9c6
--- /dev/null
+++ b/src/pcm/ConfiguredResampler.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2013 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_CONFIGURED_RESAMPLER_HXX
+#define MPD_CONFIGURED_RESAMPLER_HXX
+
+#include "check.h"
+
+class Error;
+class PcmResampler;
+
+bool
+pcm_resampler_global_init(Error &error);
+
+/**
+ * Create a #PcmResampler instance from the implementation class
+ * configured in mpd.conf.
+ */
+PcmResampler *
+pcm_resampler_create();
+
+#endif
diff --git a/src/pcm/Domain.cxx b/src/pcm/Domain.cxx
new file mode 100644
index 000000000..9f07d80dd
--- /dev/null
+++ b/src/pcm/Domain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2013 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 "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain pcm_domain("pcm");
diff --git a/src/pcm/Domain.hxx b/src/pcm/Domain.hxx
new file mode 100644
index 000000000..170d7406d
--- /dev/null
+++ b/src/pcm/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2013 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 PCM_DOMAIN_HXX
+#define PCM_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain pcm_domain;
+
+#endif
diff --git a/src/pcm/FallbackResampler.cxx b/src/pcm/FallbackResampler.cxx
new file mode 100644
index 000000000..a3b6b78ee
--- /dev/null
+++ b/src/pcm/FallbackResampler.cxx
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2003-2013 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 "FallbackResampler.hxx"
+
+#include <assert.h>
+
+AudioFormat
+FallbackPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate,
+ gcc_unused Error &error)
+{
+ assert(af.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ switch (af.format) {
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ af.format = SampleFormat::S16;
+ break;
+
+ case SampleFormat::S16:
+ case SampleFormat::FLOAT:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ break;
+
+ case SampleFormat::DSD:
+ af.format = SampleFormat::FLOAT;
+ break;
+ }
+
+ format = af;
+ out_rate = new_sample_rate;
+
+ AudioFormat result = af;
+ result.sample_rate = new_sample_rate;
+ return result;
+}
+
+void
+FallbackPcmResampler::Close()
+{
+}
+
+template<typename T>
+static ConstBuffer<T>
+pcm_resample_fallback(PcmBuffer &buffer,
+ unsigned channels,
+ unsigned src_rate,
+ ConstBuffer<T> src,
+ unsigned dest_rate)
+{
+ unsigned dest_pos = 0;
+ unsigned src_frames = src.size / channels;
+ unsigned dest_frames =
+ (src_frames * dest_rate + src_rate - 1) / src_rate;
+ unsigned dest_samples = dest_frames * channels;
+ size_t dest_size = dest_samples * sizeof(*src.data);
+ T *dest_buffer = (T *)buffer.Get(dest_size);
+
+ assert((src.size % channels) == 0);
+
+ switch (channels) {
+ case 1:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+
+ dest_buffer[dest_pos++] = src.data[src_pos];
+ }
+ break;
+ case 2:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+ src_pos &= ~1;
+
+ dest_buffer[dest_pos++] = src.data[src_pos];
+ dest_buffer[dest_pos++] = src.data[src_pos + 1];
+ }
+ break;
+ }
+
+ return { dest_buffer, dest_samples };
+}
+
+template<typename T>
+static ConstBuffer<void>
+pcm_resample_fallback_void(PcmBuffer &buffer,
+ unsigned channels,
+ unsigned src_rate,
+ ConstBuffer<void> src,
+ unsigned dest_rate)
+{
+ const auto typed_src = ConstBuffer<T>::FromVoid(src);
+ return pcm_resample_fallback(buffer, channels, src_rate, typed_src,
+ dest_rate);
+}
+
+ConstBuffer<void>
+FallbackPcmResampler::Resample(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ switch (format.format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S16:
+ return pcm_resample_fallback_void<int16_t>(buffer,
+ format.channels,
+ format.sample_rate,
+ src,
+ out_rate);
+
+ case SampleFormat::FLOAT:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ return pcm_resample_fallback_void<int32_t>(buffer,
+ format.channels,
+ format.sample_rate,
+ src,
+ out_rate);
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/FallbackResampler.hxx b/src/pcm/FallbackResampler.hxx
new file mode 100644
index 000000000..1b8d0377d
--- /dev/null
+++ b/src/pcm/FallbackResampler.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_FALLBACK_RESAMPLER_HXX
+#define MPD_PCM_FALLBACK_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+/**
+ * A naive resampler that is used when no external library was found
+ * (or when the user explicitly asks for bad quality).
+ */
+class FallbackPcmResampler final : public PcmResampler {
+ AudioFormat format;
+ unsigned out_rate;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+#endif
diff --git a/src/pcm/FormatConverter.cxx b/src/pcm/FormatConverter.cxx
new file mode 100644
index 000000000..8ff8b8940
--- /dev/null
+++ b/src/pcm/FormatConverter.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2013 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 "FormatConverter.hxx"
+#include "PcmFormat.hxx"
+#include "Domain.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+bool
+PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format,
+ gcc_unused Error &error)
+{
+ assert(_src_format != SampleFormat::UNDEFINED);
+ assert(_dest_format != SampleFormat::UNDEFINED);
+
+ src_format = _src_format;
+ dest_format = _dest_format;
+ return true;
+}
+
+void
+PcmFormatConverter::Close()
+{
+#ifndef NDEBUG
+ src_format = SampleFormat::UNDEFINED;
+ dest_format = SampleFormat::UNDEFINED;
+#endif
+}
+
+ConstBuffer<void>
+PcmFormatConverter::Convert(ConstBuffer<void> src, Error &error)
+{
+ switch (dest_format) {
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ error.Format(pcm_domain,
+ "PCM conversion from %s to %s is not implemented",
+ sample_format_to_string(src_format),
+ sample_format_to_string(dest_format));
+ return nullptr;
+
+ case SampleFormat::S16:
+ return pcm_convert_to_16(buffer, dither,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::S24_P32:
+ return pcm_convert_to_24(buffer,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::S32:
+ return pcm_convert_to_32(buffer,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::FLOAT:
+ return pcm_convert_to_float(buffer,
+ src_format,
+ src).ToVoid();
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/FormatConverter.hxx b/src/pcm/FormatConverter.hxx
new file mode 100644
index 000000000..f5b13a0b0
--- /dev/null
+++ b/src/pcm/FormatConverter.hxx
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_FORMAT_CONVERTER_HXX
+#define MPD_PCM_FORMAT_CONVERTER_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+#include "PcmBuffer.hxx"
+#include "PcmDither.hxx"
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+class Error;
+template<typename T> struct ConstBuffer;
+
+/**
+ * A class that converts samples from one format to another.
+ */
+class PcmFormatConverter {
+ SampleFormat src_format, dest_format;
+
+ PcmBuffer buffer;
+ PcmDither dither;
+
+public:
+#ifndef NDEBUG
+ PcmFormatConverter()
+ :src_format(SampleFormat::UNDEFINED),
+ dest_format(SampleFormat::UNDEFINED) {}
+
+ ~PcmFormatConverter() {
+ assert(src_format == SampleFormat::UNDEFINED);
+ assert(dest_format == SampleFormat::UNDEFINED);
+ }
+#endif
+
+ /**
+ * Opens the object, prepare for Convert().
+ *
+ * @param src_format the sample format of incoming data
+ * @param dest_format the sample format of outgoing data
+ * @param error location to store the error
+ * @return true on success
+ */
+ bool Open(SampleFormat src_format, SampleFormat dest_format,
+ Error &error);
+
+ /**
+ * Closes the object. After that, you may call Open() again.
+ */
+ void Close();
+
+ /**
+ * Convert a block of PCM data.
+ *
+ * @param src the input buffer
+ * @param error location to store the error
+ * @return the destination buffer on success,
+ * ConstBuffer::Null() on error
+ */
+ gcc_pure
+ ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error);
+};
+
+#endif
diff --git a/src/pcm/GlueResampler.cxx b/src/pcm/GlueResampler.cxx
new file mode 100644
index 000000000..ef80e08a5
--- /dev/null
+++ b/src/pcm/GlueResampler.cxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2013 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 "GlueResampler.hxx"
+#include "ConfiguredResampler.hxx"
+#include "Resampler.hxx"
+
+#include <assert.h>
+
+GluePcmResampler::GluePcmResampler()
+ :resampler(pcm_resampler_create()) {}
+
+GluePcmResampler::~GluePcmResampler()
+{
+ delete resampler;
+}
+
+bool
+GluePcmResampler::Open(AudioFormat src_format, unsigned new_sample_rate,
+ Error &error)
+{
+ assert(src_format.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ AudioFormat requested_format = src_format;
+ AudioFormat dest_format = resampler->Open(requested_format,
+ new_sample_rate,
+ error);
+ if (!dest_format.IsValid())
+ return false;
+
+ assert(requested_format.channels == src_format.channels);
+ assert(dest_format.channels == src_format.channels);
+ assert(dest_format.sample_rate == new_sample_rate);
+
+ if (requested_format.format != src_format.format &&
+ !format_converter.Open(src_format.format, requested_format.format,
+ error))
+ return false;
+
+ src_sample_format = src_format.format;
+ requested_sample_format = requested_format.format;
+ output_sample_format = dest_format.format;
+ return true;
+}
+
+void
+GluePcmResampler::Close()
+{
+ if (requested_sample_format != src_sample_format)
+ format_converter.Close();
+
+ resampler->Close();
+}
+
+ConstBuffer<void>
+GluePcmResampler::Resample(ConstBuffer<void> src, Error &error)
+{
+ assert(!src.IsNull());
+
+ if (requested_sample_format != src_sample_format) {
+ src = format_converter.Convert(src, error);
+ if (src.IsNull())
+ return nullptr;
+ }
+
+ return resampler->Resample(src, error);
+}
diff --git a/src/pcm/GlueResampler.hxx b/src/pcm/GlueResampler.hxx
new file mode 100644
index 000000000..7bd923bab
--- /dev/null
+++ b/src/pcm/GlueResampler.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2013 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_GLUE_RESAMPLER_HXX
+#define MPD_GLUE_RESAMPLER_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+#include "FormatConverter.hxx"
+
+class Error;
+class PcmResampler;
+template<typename T> struct ConstBuffer;
+
+/**
+ * A glue class that integrates a #PcmResampler and automatically
+ * converts source data to the sample format required by the
+ * #PcmResampler instance.
+ */
+class GluePcmResampler {
+ PcmResampler *const resampler;
+
+ SampleFormat src_sample_format, requested_sample_format;
+ SampleFormat output_sample_format;
+
+ /**
+ * This object converts input data to the sample format
+ * requested by the #PcmResampler.
+ */
+ PcmFormatConverter format_converter;
+
+public:
+ GluePcmResampler();
+ ~GluePcmResampler();
+
+ bool Open(AudioFormat src_format, unsigned new_sample_rate,
+ Error &error);
+ void Close();
+
+ SampleFormat GetOutputSampleFormat() const {
+ return output_sample_format;
+ }
+
+ ConstBuffer<void> Resample(ConstBuffer<void> src, Error &error);
+};
+
+#endif
diff --git a/src/pcm/LibsamplerateResampler.cxx b/src/pcm/LibsamplerateResampler.cxx
new file mode 100644
index 000000000..586391e67
--- /dev/null
+++ b/src/pcm/LibsamplerateResampler.cxx
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2003-2013 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 "LibsamplerateResampler.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+static constexpr Domain libsamplerate_domain("libsamplerate");
+
+static int lsr_converter = SRC_SINC_FASTEST;
+
+static bool
+lsr_parse_converter(const char *s)
+{
+ assert(s != nullptr);
+
+ if (*s == 0)
+ return true;
+
+ char *endptr;
+ long l = strtol(s, &endptr, 10);
+ if (*endptr == 0 && src_get_name(l) != nullptr) {
+ lsr_converter = l;
+ return true;
+ }
+
+ size_t length = strlen(s);
+ for (int i = 0;; ++i) {
+ const char *name = src_get_name(i);
+ if (name == nullptr)
+ break;
+
+ if (StringEqualsCaseASCII(s, name, length)) {
+ lsr_converter = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error)
+{
+ if (!lsr_parse_converter(converter)) {
+ error.Format(libsamplerate_domain,
+ "unknown samplerate converter '%s'", converter);
+ return false;
+ }
+
+ FormatDebug(libsamplerate_domain,
+ "libsamplerate converter '%s'",
+ src_get_name(lsr_converter));
+
+ return true;
+}
+
+AudioFormat
+LibsampleratePcmResampler::Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error)
+{
+ assert(af.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ src_rate = af.sample_rate;
+ dest_rate = new_sample_rate;
+ channels = af.channels;
+
+ /* libsamplerate works with floating point samples */
+ af.format = SampleFormat::FLOAT;
+
+ int src_error;
+ state = src_new(lsr_converter, channels, &src_error);
+ if (!state) {
+ error.Format(libsamplerate_domain, src_error,
+ "libsamplerate initialization has failed: %s",
+ src_strerror(src_error));
+ return AudioFormat::Undefined();
+ }
+
+ memset(&data, 0, sizeof(data));
+
+ data.src_ratio = double(new_sample_rate) / double(af.sample_rate);
+ FormatDebug(libsamplerate_domain,
+ "setting samplerate conversion ratio to %.2lf",
+ data.src_ratio);
+ src_set_ratio(state, data.src_ratio);
+
+ AudioFormat result = af;
+ result.sample_rate = new_sample_rate;
+ return result;
+}
+
+void
+LibsampleratePcmResampler::Close()
+{
+ state = src_delete(state);
+}
+
+static bool
+src_process(SRC_STATE *state, SRC_DATA *data, Error &error)
+{
+ int result = src_process(state, data);
+ if (result != 0) {
+ error.Format(libsamplerate_domain, result,
+ "libsamplerate has failed: %s",
+ src_strerror(result));
+ return false;
+ }
+
+ return true;
+}
+
+inline ConstBuffer<float>
+LibsampleratePcmResampler::Resample2(ConstBuffer<float> src, Error &error)
+{
+ assert(src.size % channels == 0);
+
+ const unsigned src_frames = src.size / channels;
+ const unsigned dest_frames =
+ (src_frames * dest_rate + src_rate - 1) / src_rate;
+ size_t data_out_size = dest_frames * sizeof(float) * channels;
+
+ data.data_in = const_cast<float *>(src.data);
+ data.data_out = (float *)buffer.Get(data_out_size);
+ data.input_frames = src_frames;
+ data.output_frames = dest_frames;
+
+ if (!src_process(state, &data, error))
+ return nullptr;
+
+ return ConstBuffer<float>(data.data_out,
+ data.output_frames_gen * channels);
+}
+
+ConstBuffer<void>
+LibsampleratePcmResampler::Resample(ConstBuffer<void> src, Error &error)
+{
+ return Resample2(ConstBuffer<float>::FromVoid(src), error).ToVoid();
+}
diff --git a/src/pcm/LibsamplerateResampler.hxx b/src/pcm/LibsamplerateResampler.hxx
new file mode 100644
index 000000000..0c1f613c8
--- /dev/null
+++ b/src/pcm/LibsamplerateResampler.hxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_LIBSAMPLERATE_RESAMPLER_HXX
+#define MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+#include <samplerate.h>
+
+/**
+ * A resampler using libsamplerate.
+ */
+class LibsampleratePcmResampler final : public PcmResampler {
+ unsigned src_rate, dest_rate;
+ unsigned channels;
+
+ SRC_STATE *state;
+ SRC_DATA data;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+
+private:
+ ConstBuffer<float> Resample2(ConstBuffer<float> src, Error &error);
+};
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error);
+
+#endif
diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx
index 578c579be..5af9a978d 100644
--- a/src/pcm/PcmBuffer.cxx
+++ b/src/pcm/PcmBuffer.cxx
@@ -19,7 +19,6 @@
#include "config.h"
#include "PcmBuffer.hxx"
-#include "poison.h"
void *
PcmBuffer::Get(size_t new_size)
diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx
index 717e24938..44a3ebf99 100644
--- a/src/pcm/PcmBuffer.hxx
+++ b/src/pcm/PcmBuffer.hxx
@@ -49,6 +49,12 @@ public:
*/
gcc_malloc
void *Get(size_t size);
+
+ template<typename T>
+ gcc_malloc
+ T *GetT(size_t n) {
+ return (T *)Get(n * sizeof(T));
+ }
};
#endif
diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx
index eb69985c1..2e433e611 100644
--- a/src/pcm/PcmChannels.cxx
+++ b/src/pcm/PcmChannels.cxx
@@ -20,7 +20,9 @@
#include "config.h"
#include "PcmChannels.hxx"
#include "PcmBuffer.hxx"
-#include "PcmUtils.hxx"
+#include "Traits.hxx"
+#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include <assert.h>
@@ -37,254 +39,143 @@ MonoToStereo(D dest, S src, S end)
}
-static void
-pcm_convert_channels_16_2_to_1(int16_t *gcc_restrict dest,
- const int16_t *gcc_restrict src,
- const int16_t *gcc_restrict src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::value_type
+StereoToMono(typename Traits::value_type _a,
+ typename Traits::value_type _b)
{
- while (src < src_end) {
- int32_t a = *src++, b = *src++;
+ typename Traits::sum_type a(_a);
+ typename Traits::sum_type b(_b);
- *dest++ = (a + b) / 2;
- }
+ return typename Traits::value_type((a + b) / 2);
}
-static void
-pcm_convert_channels_16_n_to_2(int16_t *gcc_restrict dest,
- unsigned src_channels,
- const int16_t *gcc_restrict src,
- const int16_t *gcc_restrict src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+StereoToMono(typename Traits::pointer_type dest,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
{
- unsigned c;
-
- assert(src_channels > 0);
-
- while (src < src_end) {
- int32_t sum = 0;
- int16_t value;
-
- for (c = 0; c < src_channels; ++c)
- sum += *src++;
- value = sum / (int)src_channels;
+ while (src != end) {
+ const auto a = *src++;
+ const auto b = *src++;
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
+ *dest++ = StereoToMono<F, Traits>(a, b);
}
-}
-
-const int16_t *
-pcm_convert_channels_16(PcmBuffer &buffer,
- unsigned dest_channels,
- unsigned src_channels, const int16_t *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
-
- int16_t *dest = (int16_t *)buffer.Get(dest_size);
- const int16_t *src_end = pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_16_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_16_n_to_2(dest, src_channels, src,
- src_end);
- else
- return nullptr;
return dest;
}
-static void
-pcm_convert_channels_24_2_to_1(int32_t *gcc_restrict dest,
- const int32_t *gcc_restrict src,
- const int32_t *gcc_restrict src_end)
-{
- while (src < src_end) {
- int32_t a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
-}
-
-static void
-pcm_convert_channels_24_n_to_2(int32_t *gcc_restrict dest,
- unsigned src_channels,
- const int32_t *gcc_restrict src,
- const int32_t *gcc_restrict src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+NToStereo(typename Traits::pointer_type dest,
+ unsigned src_channels,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
{
- unsigned c;
+ assert((end - src) % src_channels == 0);
- assert(src_channels > 0);
-
- while (src < src_end) {
- int32_t sum = 0;
- int32_t value;
-
- for (c = 0; c < src_channels; ++c)
+ while (src != end) {
+ typename Traits::sum_type sum = *src++;
+ for (unsigned c = 1; c < src_channels; ++c)
sum += *src++;
- value = sum / (int)src_channels;
- /* XXX this is actually only mono ... */
+ typename Traits::value_type value(sum / int(src_channels));
+
+ /* TODO: this is actually only mono ... */
*dest++ = value;
*dest++ = value;
}
-}
-
-const int32_t *
-pcm_convert_channels_24(PcmBuffer &buffer,
- unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
-
- int32_t *dest = (int32_t *)buffer.Get(dest_size);
- const int32_t *src_end = (const int32_t *)
- pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_24_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_24_n_to_2(dest, src_channels, src,
- src_end);
- else
- return nullptr;
return dest;
}
-static void
-pcm_convert_channels_32_2_to_1(int32_t *gcc_restrict dest,
- const int32_t *gcc_restrict src,
- const int32_t *gcc_restrict src_end)
-{
- while (src < src_end) {
- int64_t a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
-}
-
-static void
-pcm_convert_channels_32_n_to_2(int32_t *dest,
- unsigned src_channels, const int32_t *src,
- const int32_t *src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+NToM(typename Traits::pointer_type dest,
+ unsigned dest_channels,
+ unsigned src_channels,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
{
- unsigned c;
+ assert((end - src) % src_channels == 0);
- assert(src_channels > 0);
-
- while (src < src_end) {
- int64_t sum = 0;
- int32_t value;
-
- for (c = 0; c < src_channels; ++c)
+ while (src != end) {
+ typename Traits::sum_type sum = *src++;
+ for (unsigned c = 1; c < src_channels; ++c)
sum += *src++;
- value = sum / (int64_t)src_channels;
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
+ typename Traits::value_type value(sum / int(src_channels));
+
+ /* TODO: this is actually only mono ... */
+ for (unsigned c = 0; c < dest_channels; ++c)
+ *dest++ = value;
}
+
+ return dest;
}
-const int32_t *
-pcm_convert_channels_32(PcmBuffer &buffer,
- unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static ConstBuffer<typename Traits::value_type>
+ConvertChannels(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<typename Traits::value_type> src)
{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
+ assert(src.size % src_channels == 0);
- int32_t *dest = (int32_t *)buffer.Get(dest_size);
- const int32_t *src_end = (const int32_t *)
- pcm_end_pointer(src, src_size);
+ const size_t dest_size = src.size / src_channels * dest_channels;
+ auto dest = buffer.GetT<typename Traits::value_type>(dest_size);
if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
+ MonoToStereo(dest, src.begin(), src.end());
else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_32_2_to_1(dest, src, src_end);
+ StereoToMono<F>(dest, src.begin(), src.end());
else if (dest_channels == 2)
- pcm_convert_channels_32_n_to_2(dest, src_channels, src,
- src_end);
+ NToStereo<F>(dest, src_channels, src.begin(), src.end());
else
- return nullptr;
+ NToM<F>(dest, dest_channels,
+ src_channels, src.begin(), src.end());
- return dest;
+ return { dest, dest_size };
}
-static void
-pcm_convert_channels_float_2_to_1(float *gcc_restrict dest,
- const float *gcc_restrict src,
- const float *gcc_restrict src_end)
+ConstBuffer<int16_t>
+pcm_convert_channels_16(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<int16_t> src)
{
- while (src < src_end) {
- double a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
+ return ConvertChannels<SampleFormat::S16>(buffer, dest_channels,
+ src_channels, src);
}
-static void
-pcm_convert_channels_float_n_to_2(float *dest,
- unsigned src_channels, const float *src,
- const float *src_end)
+ConstBuffer<int32_t>
+pcm_convert_channels_24(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<int32_t> src)
{
- unsigned c;
-
- assert(src_channels > 0);
-
- while (src < src_end) {
- double sum = 0;
- float value;
-
- for (c = 0; c < src_channels; ++c)
- sum += *src++;
- value = sum / (double)src_channels;
+ return ConvertChannels<SampleFormat::S24_P32>(buffer, dest_channels,
+ src_channels, src);
+}
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
- }
+ConstBuffer<int32_t>
+pcm_convert_channels_32(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<int32_t> src)
+{
+ return ConvertChannels<SampleFormat::S32>(buffer, dest_channels,
+ src_channels, src);
}
-const float *
+ConstBuffer<float>
pcm_convert_channels_float(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const float *src,
- size_t src_size, size_t *dest_size_r)
+ unsigned src_channels,
+ ConstBuffer<float> src)
{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
-
- float *dest = (float *)buffer.Get(dest_size);
- const float *src_end = (const float *)pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_float_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_float_n_to_2(dest, src_channels, src,
- src_end);
- else
- return nullptr;
-
- return dest;
+ return ConvertChannels<SampleFormat::FLOAT>(buffer, dest_channels,
+ src_channels, src);
}
diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx
index c67822825..ca4e06a8c 100644
--- a/src/pcm/PcmChannels.hxx
+++ b/src/pcm/PcmChannels.hxx
@@ -24,6 +24,7 @@
#include <stddef.h>
class PcmBuffer;
+template<typename T> struct ConstBuffer;
/**
* Changes the number of channels in 16 bit PCM data.
@@ -32,15 +33,13 @@ class PcmBuffer;
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int16_t *
+ConstBuffer<int16_t>
pcm_convert_channels_16(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const int16_t *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<int16_t> src);
/**
* Changes the number of channels in 24 bit PCM data (aligned at 32
@@ -50,15 +49,13 @@ pcm_convert_channels_16(PcmBuffer &buffer,
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_channels_24(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<int32_t> src);
/**
* Changes the number of channels in 32 bit PCM data.
@@ -67,15 +64,13 @@ pcm_convert_channels_24(PcmBuffer &buffer,
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_channels_32(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<int32_t> src);
/**
* Changes the number of channels in 32 bit float PCM data.
@@ -84,14 +79,12 @@ pcm_convert_channels_32(PcmBuffer &buffer,
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const float *
+ConstBuffer<float>
pcm_convert_channels_float(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const float *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<float> src);
#endif
diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx
index 8eafe527c..6eecd3b01 100644
--- a/src/pcm/PcmConvert.cxx
+++ b/src/pcm/PcmConvert.cxx
@@ -19,289 +19,151 @@
#include "config.h"
#include "PcmConvert.hxx"
-#include "PcmChannels.hxx"
-#include "PcmFormat.hxx"
+#include "Domain.hxx"
+#include "ConfiguredResampler.hxx"
#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "util/ConstBuffer.hxx"
#include <assert.h>
#include <math.h>
-const Domain pcm_convert_domain("pcm_convert");
+bool
+pcm_convert_global_init(Error &error)
+{
+ return pcm_resampler_global_init(error);
+}
PcmConvert::PcmConvert()
{
+#ifndef NDEBUG
+ src_format.Clear();
+ dest_format.Clear();
+#endif
}
PcmConvert::~PcmConvert()
{
+ assert(!src_format.IsValid());
+ assert(!dest_format.IsValid());
}
-void
-PcmConvert::Reset()
+bool
+PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format,
+ Error &error)
{
- dsd.Reset();
- resampler.Reset();
-}
+ assert(!src_format.IsValid());
+ assert(!dest_format.IsValid());
+ assert(_src_format.IsValid());
+ assert(_dest_format.IsValid());
-inline const int16_t *
-PcmConvert::Convert16(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
-{
- const int16_t *buf;
- size_t len;
+ src_format = _src_format;
+ dest_format = _dest_format;
+
+ AudioFormat format = src_format;
+ if (format.format == SampleFormat::DSD)
+ format.format = SampleFormat::FLOAT;
- assert(dest_format.format == SampleFormat::S16);
+ enable_resampler = format.sample_rate != dest_format.sample_rate;
+ if (enable_resampler) {
+ if (!resampler.Open(format, dest_format.sample_rate, error))
+ return false;
- buf = pcm_convert_to_16(format_buffer, dither,
- src_format.format,
- src_buffer, src_size,
- &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 16 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
+ format.format = resampler.GetOutputSampleFormat();
+ format.sample_rate = dest_format.sample_rate;
}
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_16(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
- return nullptr;
- }
+ enable_format = format.format != dest_format.format;
+ if (enable_format &&
+ !format_converter.Open(format.format, dest_format.format, error)) {
+ if (enable_resampler)
+ resampler.Close();
+ return false;
}
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample16(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return nullptr;
+ format.format = dest_format.format;
+
+ enable_channels = format.channels != dest_format.channels;
+ if (enable_channels &&
+ !channels_converter.Open(format.format, format.channels,
+ dest_format.channels, error)) {
+ if (enable_format)
+ format_converter.Close();
+ if (enable_resampler)
+ resampler.Close();
+ return false;
}
- *dest_size_r = len;
- return buf;
+ return true;
}
-inline const int32_t *
-PcmConvert::Convert24(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+void
+PcmConvert::Close()
{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format.format == SampleFormat::S24_P32);
-
- buf = pcm_convert_to_24(format_buffer,
- src_format.format,
- src_buffer, src_size, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 24 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
+ if (enable_channels)
+ channels_converter.Close();
+ if (enable_format)
+ format_converter.Close();
+ if (enable_resampler)
+ resampler.Close();
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_24(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
- return nullptr;
- }
- }
-
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample24(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return nullptr;
- }
+ dsd.Reset();
- *dest_size_r = len;
- return buf;
+#ifndef NDEBUG
+ src_format.Clear();
+ dest_format.Clear();
+#endif
}
-inline const int32_t *
-PcmConvert::Convert32(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+const void *
+PcmConvert::Convert(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ Error &error)
{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format.format == SampleFormat::S32);
-
- buf = pcm_convert_to_32(format_buffer,
- src_format.format,
- src_buffer, src_size, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 32 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
-
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_32(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
+ ConstBuffer<void> buffer(src, src_size);
+ AudioFormat format = src_format;
+
+ if (format.format == SampleFormat::DSD) {
+ auto s = ConstBuffer<uint8_t>::FromVoid(buffer);
+ auto d = dsd.ToFloat(format.channels,
+ false, s);
+ if (d.IsNull()) {
+ error.Set(pcm_domain,
+ "DSD to PCM conversion failed");
return nullptr;
}
- }
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample32(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return buf;
+ buffer = d.ToVoid();
+ format.format = SampleFormat::FLOAT;
}
- *dest_size_r = len;
- return buf;
-}
-
-inline const float *
-PcmConvert::ConvertFloat(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
-{
- const float *buffer = (const float *)src_buffer;
- size_t size = src_size;
-
- assert(dest_format.format == SampleFormat::FLOAT);
-
- /* convert to float now */
+ if (enable_resampler) {
+ buffer = resampler.Resample(buffer, error);
+ if (buffer.IsNull())
+ return nullptr;
- buffer = pcm_convert_to_float(format_buffer,
- src_format.format,
- buffer, size, &size);
- if (buffer == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to float is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
+ format.format = resampler.GetOutputSampleFormat();
+ format.sample_rate = dest_format.sample_rate;
}
- /* convert channels */
-
- if (src_format.channels != dest_format.channels) {
- buffer = pcm_convert_channels_float(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buffer, size, &size);
- if (buffer == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
+ if (enable_format) {
+ buffer = format_converter.Convert(buffer, error);
+ if (buffer.IsNull())
return nullptr;
- }
- }
- /* resample with float, because this is the best format for
- libsamplerate */
-
- if (src_format.sample_rate != dest_format.sample_rate) {
- buffer = resampler.ResampleFloat(dest_format.channels,
- src_format.sample_rate,
- buffer, size,
- dest_format.sample_rate,
- &size, error);
- if (buffer == nullptr)
- return nullptr;
+ format.format = dest_format.format;
}
- *dest_size_r = size;
- return buffer;
-}
-
-const void *
-PcmConvert::Convert(AudioFormat src_format,
- const void *src, size_t src_size,
- const AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error)
-{
- AudioFormat float_format;
- if (src_format.format == SampleFormat::DSD) {
- size_t f_size;
- const float *f = dsd.ToFloat(src_format.channels,
- false, (const uint8_t *)src,
- src_size, &f_size);
- if (f == nullptr) {
- error.Set(pcm_convert_domain,
- "DSD to PCM conversion failed");
+ if (enable_channels) {
+ buffer = channels_converter.Convert(buffer, error);
+ if (buffer.IsNull())
return nullptr;
- }
- float_format = src_format;
- float_format.format = SampleFormat::FLOAT;
-
- src_format = float_format;
- src = f;
- src_size = f_size;
+ format.channels = dest_format.channels;
}
- switch (dest_format.format) {
- case SampleFormat::S16:
- return Convert16(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::S24_P32:
- return Convert24(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::S32:
- return Convert32(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::FLOAT:
- return ConvertFloat(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- default:
- error.Format(pcm_convert_domain,
- "PCM conversion to %s is not implemented",
- sample_format_to_string(dest_format.format));
- return nullptr;
- }
+ *dest_size_r = buffer.size;
+ return buffer.data;
}
diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx
index 40f785179..586c303f2 100644
--- a/src/pcm/PcmConvert.hxx
+++ b/src/pcm/PcmConvert.hxx
@@ -20,15 +20,18 @@
#ifndef PCM_CONVERT_HXX
#define PCM_CONVERT_HXX
-#include "PcmDither.hxx"
#include "PcmDsd.hxx"
-#include "PcmResample.hxx"
#include "PcmBuffer.hxx"
+#include "FormatConverter.hxx"
+#include "ChannelsConverter.hxx"
+#include "GlueResampler.hxx"
+#include "AudioFormat.hxx"
#include <stddef.h>
-struct AudioFormat;
+template<typename T> struct ConstBuffer;
class Error;
+class Domain;
/**
* This object is statically allocated (within another struct), and
@@ -38,27 +41,29 @@ class Error;
class PcmConvert {
PcmDsd dsd;
- PcmResampler resampler;
+ GluePcmResampler resampler;
+ PcmFormatConverter format_converter;
+ PcmChannelsConverter channels_converter;
- PcmDither dither;
+ AudioFormat src_format, dest_format;
- /** the buffer for converting the sample format */
- PcmBuffer format_buffer;
-
- /** the buffer for converting the channel count */
- PcmBuffer channels_buffer;
+ bool enable_resampler, enable_format, enable_channels;
public:
PcmConvert();
~PcmConvert();
+ /**
+ * Prepare the object. Call Close() when done.
+ */
+ bool Open(AudioFormat _src_format, AudioFormat _dest_format,
+ Error &error);
/**
- * Reset the pcm_convert_state object. Use this at the
- * boundary between two distinct songs and each time the
- * format changes.
+ * Close the object after it was prepared with Open(). After
+ * that, it may be reused by calling Open() again.
*/
- void Reset();
+ void Close();
/**
* Converts PCM data between two audio formats.
@@ -72,38 +77,12 @@ public:
* ignore errors
* @return the destination buffer, or NULL on error
*/
- const void *Convert(AudioFormat src_format,
- const void *src, size_t src_size,
- AudioFormat dest_format,
+ const void *Convert(const void *src, size_t src_size,
size_t *dest_size_r,
Error &error);
-
-private:
- const int16_t *Convert16(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const int32_t *Convert24(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const int32_t *Convert32(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const float *ConvertFloat(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
};
-extern const class Domain pcm_convert_domain;
+bool
+pcm_convert_global_init(Error &error);
#endif
diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx
index 98d0d443e..d9d4d28ca 100644
--- a/src/pcm/PcmDither.cxx
+++ b/src/pcm/PcmDither.cxx
@@ -20,18 +20,14 @@
#include "config.h"
#include "PcmDither.hxx"
#include "PcmPrng.hxx"
+#include "Traits.hxx"
-inline int16_t
-PcmDither::Dither24To16(int_fast32_t sample)
+template<typename T, T MIN, T MAX, unsigned scale_bits>
+inline T
+PcmDither::Dither(T sample)
{
- constexpr unsigned from_bits = 24;
- constexpr unsigned to_bits = 16;
- constexpr unsigned scale_bits = from_bits - to_bits;
- constexpr int_fast32_t round = 1 << (scale_bits - 1);
- constexpr int_fast32_t mask = (1 << scale_bits) - 1;
- constexpr int_fast32_t ONE = 1 << (from_bits - 1);
- constexpr int_fast32_t MIN = -ONE;
- constexpr int_fast32_t MAX = ONE - 1;
+ constexpr T round = 1 << (scale_bits - 1);
+ constexpr T mask = (1 << scale_bits) - 1;
sample += error[0] - error[1] + error[2];
@@ -39,9 +35,9 @@ PcmDither::Dither24To16(int_fast32_t sample)
error[1] = error[0] / 2;
/* round */
- int_fast32_t output = sample + round;
+ T output = sample + round;
- int_fast32_t rnd = pcm_prng(random);
+ const T rnd = pcm_prng(random);
output += (rnd & mask) - (random & mask);
random = rnd;
@@ -63,27 +59,59 @@ PcmDither::Dither24To16(int_fast32_t sample)
error[0] = sample - output;
- return (int16_t)(output >> scale_bits);
+ return output >> scale_bits;
}
-void
-PcmDither::Dither24To16(int16_t *dest, const int32_t *src,
- const int32_t *src_end)
+template<typename ST, unsigned SBITS, unsigned DBITS>
+inline ST
+PcmDither::DitherShift(ST sample)
+{
+ static_assert(sizeof(ST) * 8 > SBITS, "Source type too small");
+ static_assert(SBITS > DBITS, "Non-positive scale_bits");
+
+ static constexpr ST MIN = -(ST(1) << (SBITS - 1));
+ static constexpr ST MAX = (ST(1) << (SBITS - 1)) - 1;
+
+ return Dither<ST, MIN, MAX, SBITS - DBITS>(sample);
+}
+
+template<typename ST, typename DT>
+inline typename DT::value_type
+PcmDither::DitherConvert(typename ST::value_type sample)
+{
+ static_assert(ST::BITS > DT::BITS,
+ "Sample formats cannot be dithered");
+
+ constexpr unsigned scale_bits = ST::BITS - DT::BITS;
+
+ return Dither<typename ST::sum_type, ST::MIN, ST::MAX,
+ scale_bits>(sample);
+}
+
+template<typename ST, typename DT>
+inline void
+PcmDither::DitherConvert(typename DT::pointer_type dest,
+ typename ST::const_pointer_type src,
+ typename ST::const_pointer_type src_end)
{
while (src < src_end)
- *dest++ = Dither24To16(*src++);
+ *dest++ = DitherConvert<ST, DT>(*src++);
}
-inline int16_t
-PcmDither::Dither32To16(int_fast32_t sample)
+inline void
+PcmDither::Dither24To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end)
{
- return Dither24To16(sample >> 8);
+ typedef SampleTraits<SampleFormat::S24_P32> ST;
+ typedef SampleTraits<SampleFormat::S16> DT;
+ DitherConvert<ST, DT>(dest, src, src_end);
}
-void
+inline void
PcmDither::Dither32To16(int16_t *dest, const int32_t *src,
const int32_t *src_end)
{
- while (src < src_end)
- *dest++ = Dither32To16(*src++);
+ typedef SampleTraits<SampleFormat::S32> ST;
+ typedef SampleTraits<SampleFormat::S16> DT;
+ DitherConvert<ST, DT>(dest, src, src_end);
}
diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx
index 106382307..473a45ac3 100644
--- a/src/pcm/PcmDither.hxx
+++ b/src/pcm/PcmDither.hxx
@@ -22,6 +22,8 @@
#include <stdint.h>
+enum class SampleFormat : uint8_t;
+
class PcmDither {
int32_t error[3];
int32_t random;
@@ -30,6 +32,18 @@ public:
constexpr PcmDither()
:error{0, 0, 0}, random(0) {}
+ /**
+ * Shift the given sample by #SBITS-#DBITS to the right, and
+ * apply dithering.
+ *
+ * @param ST the input sample type
+ * @param SBITS the input bit width
+ * @param DBITS the output bit width
+ * @param sample the input sample value
+ */
+ template<typename ST, unsigned SBITS, unsigned DBITS>
+ ST DitherShift(ST sample);
+
void Dither24To16(int16_t *dest, const int32_t *src,
const int32_t *src_end);
@@ -37,8 +51,34 @@ public:
const int32_t *src_end);
private:
- int16_t Dither24To16(int_fast32_t sample);
- int16_t Dither32To16(int_fast32_t sample);
+ /**
+ * Shift the given sample by #scale_bits to the right, and
+ * apply dithering.
+ *
+ * @param T the input sample type
+ * @param MIN the minimum input sample value
+ * @param MAX the maximum input sample value
+ * @param scale_bits the number of bits to be discarded
+ * @param sample the input sample value
+ */
+ template<typename T, T MIN, T MAX, unsigned scale_bits>
+ T Dither(T sample);
+
+ /**
+ * Convert the given sample from one sample format to another,
+ * discarding bits.
+ *
+ * @param ST the input #SampleTraits class
+ * @param ST the output #SampleTraits class
+ * @param sample the input sample value
+ */
+ template<typename ST, typename DT>
+ typename DT::value_type DitherConvert(typename ST::value_type sample);
+
+ template<typename ST, typename DT>
+ void DitherConvert(typename DT::pointer_type dest,
+ typename ST::const_pointer_type src,
+ typename ST::const_pointer_type src_end);
};
#endif
diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx
index 4db274635..5952cad7c 100644
--- a/src/pcm/PcmDsd.cxx
+++ b/src/pcm/PcmDsd.cxx
@@ -21,11 +21,11 @@
#include "PcmDsd.hxx"
#include "dsd2pcm/dsd2pcm.h"
#include "util/Macros.hxx"
+#include "util/ConstBuffer.hxx"
#include <algorithm>
#include <assert.h>
-#include <string.h>
PcmDsd::PcmDsd()
{
@@ -47,22 +47,20 @@ PcmDsd::Reset()
dsd2pcm_reset(dsd2pcm[i]);
}
-const float *
+ConstBuffer<float>
PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r)
+ ConstBuffer<uint8_t> src)
{
- assert(src != nullptr);
- assert(src_size > 0);
- assert(src_size % channels == 0);
+ assert(!src.IsNull());
+ assert(!src.IsEmpty());
+ assert(src.size % channels == 0);
assert(channels <= ARRAY_SIZE(dsd2pcm));
- const unsigned num_samples = src_size;
- const unsigned num_frames = src_size / channels;
+ const unsigned num_samples = src.size;
+ const unsigned num_frames = src.size / channels;
float *dest;
const size_t dest_size = num_samples * sizeof(*dest);
- *dest_size_r = dest_size;
dest = (float *)buffer.Get(dest_size);
for (unsigned c = 0; c < channels; ++c) {
@@ -73,9 +71,9 @@ PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
}
dsd2pcm_translate(dsd2pcm[c], num_frames,
- src + c, channels,
+ src.data + c, channels,
lsbfirst, dest + c, channels);
}
- return dest;
+ return { dest, num_samples };
}
diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx
index 26ee11b13..b9b6d51ee 100644
--- a/src/pcm/PcmDsd.hxx
+++ b/src/pcm/PcmDsd.hxx
@@ -25,22 +25,24 @@
#include <stdint.h>
+template<typename T> struct ConstBuffer;
+
/**
* Wrapper for the dsd2pcm library.
*/
-struct PcmDsd {
+class PcmDsd {
PcmBuffer buffer;
struct dsd2pcm_ctx_s *dsd2pcm[32];
+public:
PcmDsd();
~PcmDsd();
void Reset();
- const float *ToFloat(unsigned channels, bool lsbfirst,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r);
+ ConstBuffer<float> ToFloat(unsigned channels, bool lsbfirst,
+ ConstBuffer<uint8_t> src);
};
#endif
diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx
index 2d0f33a15..833d84653 100644
--- a/src/pcm/PcmDsdUsb.cxx
+++ b/src/pcm/PcmDsdUsb.cxx
@@ -22,6 +22,8 @@
#include "PcmBuffer.hxx"
#include "AudioFormat.hxx"
+#include <assert.h>
+
constexpr
static inline uint32_t
pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx
index 4565c71c6..ff8f2a80c 100644
--- a/src/pcm/PcmFormat.cxx
+++ b/src/pcm/PcmFormat.cxx
@@ -19,61 +19,26 @@
#include "config.h"
#include "PcmFormat.hxx"
-#include "PcmDither.hxx"
#include "PcmBuffer.hxx"
#include "PcmUtils.hxx"
+#include "Traits.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/WritableBuffer.hxx"
-#include <type_traits>
+#include "PcmDither.cxx" // including the .cxx file to get inlined templates
-template<SampleFormat F>
-struct SampleTraits {};
-
-template<>
-struct SampleTraits<SampleFormat::S8> {
- typedef int8_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
-
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = sizeof(value_type) * 8;
-};
-
-template<>
-struct SampleTraits<SampleFormat::S16> {
- typedef int16_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
-
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = sizeof(value_type) * 8;
-};
-
-template<>
-struct SampleTraits<SampleFormat::S32> {
- typedef int32_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
-
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = sizeof(value_type) * 8;
-};
-
-template<>
-struct SampleTraits<SampleFormat::S24_P32> {
- typedef int32_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
-
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = 24;
-};
+template<typename T>
+static inline ConstBuffer<T>
+ToConst(WritableBuffer<T> b)
+{
+ return { b.data, b.size };
+}
static void
-pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end)
+pcm_convert_8_to_16(int16_t *out, const int8_t *in, size_t n)
{
- while (in < in_end) {
- *out++ = *in++ << 8;
- }
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] << 8;
}
static void
@@ -93,28 +58,19 @@ pcm_convert_32_to_16(PcmDither &dither,
template<SampleFormat F, class Traits=SampleTraits<F>>
static void
ConvertFromFloat(typename Traits::pointer_type dest,
- const float *src, const float *end)
+ const float *src, size_t n)
{
constexpr auto bits = Traits::BITS;
const float factor = 1 << (bits - 1);
- while (src != end) {
- int sample(*src++ * factor);
- *dest++ = PcmClamp<typename Traits::value_type, int, bits>(sample);
+ for (size_t i = 0; i != n; ++i) {
+ typename Traits::long_type sample(src[i] * factor);
+ dest[i] = PcmClamp<F, Traits>(sample);
}
}
template<SampleFormat F, class Traits=SampleTraits<F>>
-static void
-ConvertFromFloat(typename Traits::pointer_type dest,
- const float *src, size_t size)
-{
- ConvertFromFloat<F, Traits>(dest, src,
- pcm_end_pointer(src, size));
-}
-
-template<SampleFormat F, class Traits=SampleTraits<F>>
static typename Traits::pointer_type
AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size,
size_t *dest_size_r)
@@ -125,65 +81,55 @@ AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size,
const size_t num_samples = src_size / src_sample_size;
*dest_size_r = num_samples * sizeof(typename Traits::value_type);
auto dest = (typename Traits::pointer_type)buffer.Get(*dest_size_r);
- ConvertFromFloat<F, Traits>(dest, src, src_size);
+ ConvertFromFloat<F, Traits>(dest, src, src_size / sizeof(*src));
return dest;
}
-static int16_t *
-pcm_allocate_8_to_16(PcmBuffer &buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static WritableBuffer<typename Traits::value_type>
+AllocateFromFloat(PcmBuffer &buffer, ConstBuffer<float> src)
{
- int16_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = (int16_t *)buffer.Get(*dest_size_r);
- pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<typename Traits::value_type>(src.size);
+ ConvertFromFloat<F, Traits>(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int16_t *
+static ConstBuffer<int16_t>
+pcm_allocate_8_to_16(PcmBuffer &buffer, ConstBuffer<int8_t> src)
+{
+ auto dest = buffer.GetT<int16_t>(src.size);
+ pcm_convert_8_to_16(dest, src.data, src.size);
+ return { dest, src.size };
+}
+
+static ConstBuffer<int16_t>
pcm_allocate_24p32_to_16(PcmBuffer &buffer, PcmDither &dither,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- int16_t *dest;
- *dest_size_r = src_size / 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = (int16_t *)buffer.Get(*dest_size_r);
- pcm_convert_24_to_16(dither, dest, src,
- pcm_end_pointer(src, src_size));
- return dest;
+ ConstBuffer<int32_t> src)
+{
+ auto dest = buffer.GetT<int16_t>(src.size);
+ pcm_convert_24_to_16(dither, dest, src.data, src.end());
+ return { dest, src.size };
}
-static int16_t *
+static ConstBuffer<int16_t>
pcm_allocate_32_to_16(PcmBuffer &buffer, PcmDither &dither,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
-{
- int16_t *dest;
- *dest_size_r = src_size / 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = (int16_t *)buffer.Get(*dest_size_r);
- pcm_convert_32_to_16(dither, dest, src,
- pcm_end_pointer(src, src_size));
- return dest;
+ ConstBuffer<int32_t> src)
+{
+ auto dest = buffer.GetT<int16_t>(src.size);
+ pcm_convert_32_to_16(dither, dest, src.data, src.end());
+ return { dest, src.size };
}
-static int16_t *
-pcm_allocate_float_to_16(PcmBuffer &buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int16_t>
+pcm_allocate_float_to_16(PcmBuffer &buffer, ConstBuffer<float> src)
{
- return AllocateFromFloat<SampleFormat::S16>(buffer, src, src_size,
- dest_size_r);
+ return ToConst(AllocateFromFloat<SampleFormat::S16>(buffer, src));
}
-const int16_t *
+ConstBuffer<int16_t>
pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
- assert(src_size % sample_format_size(src_format) == 0);
-
switch (src_format) {
case SampleFormat::UNDEFINED:
case SampleFormat::DSD:
@@ -191,104 +137,84 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
case SampleFormat::S8:
return pcm_allocate_8_to_16(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
- *dest_size_r = src_size;
- return (const int16_t *)src;
+ return ConstBuffer<int16_t>::FromVoid(src);
case SampleFormat::S24_P32:
return pcm_allocate_24p32_to_16(buffer, dither,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::S32:
return pcm_allocate_32_to_16(buffer, dither,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::FLOAT:
return pcm_allocate_float_to_16(buffer,
- (const float *)src, src_size,
- dest_size_r);
+ ConstBuffer<float>::FromVoid(src));
}
return nullptr;
}
static void
-pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
+pcm_convert_8_to_24(int32_t *out, const int8_t *in, size_t n)
{
- while (in < in_end)
- *out++ = *in++ << 16;
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] << 16;
}
static void
-pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end)
+pcm_convert_16_to_24(int32_t *out, const int16_t *in, size_t n)
{
- while (in < in_end)
- *out++ = *in++ << 8;
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] << 8;
}
static void
pcm_convert_32_to_24(int32_t *gcc_restrict out,
const int32_t *gcc_restrict in,
- const int32_t *gcc_restrict in_end)
+ size_t n)
{
- while (in < in_end)
- *out++ = *in++ >> 8;
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] >> 8;
}
-static int32_t *
-pcm_allocate_8_to_24(PcmBuffer &buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_8_to_24(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<int32_t>(src.size);
+ pcm_convert_8_to_24(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int32_t *
-pcm_allocate_16_to_24(PcmBuffer &buffer,
- const int16_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_16_to_24(PcmBuffer &buffer, ConstBuffer<int16_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size * 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<int32_t>(src.size);
+ pcm_convert_16_to_24(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int32_t *
-pcm_allocate_32_to_24(PcmBuffer &buffer,
- const int32_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_32_to_24(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- *dest_size_r = src_size;
- int32_t *dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<int32_t>(src.size);
+ pcm_convert_32_to_24(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int32_t *
-pcm_allocate_float_to_24(PcmBuffer &buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
+static WritableBuffer<int32_t>
+pcm_allocate_float_to_24(PcmBuffer &buffer, ConstBuffer<float> src)
{
- return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src, src_size,
- dest_size_r);
+ return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src);
}
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_to_24(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
- assert(src_size % sample_format_size(src_format) == 0);
-
switch (src_format) {
case SampleFormat::UNDEFINED:
case SampleFormat::DSD:
@@ -296,110 +222,89 @@ pcm_convert_to_24(PcmBuffer &buffer,
case SampleFormat::S8:
return pcm_allocate_8_to_24(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
return pcm_allocate_16_to_24(buffer,
- (const int16_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int16_t>::FromVoid(src));
case SampleFormat::S24_P32:
- *dest_size_r = src_size;
- return (const int32_t *)src;
+ return ConstBuffer<int32_t>::FromVoid(src);
case SampleFormat::S32:
return pcm_allocate_32_to_24(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::FLOAT:
- return pcm_allocate_float_to_24(buffer,
- (const float *)src, src_size,
- dest_size_r);
+ return ToConst(pcm_allocate_float_to_24(buffer,
+ ConstBuffer<float>::FromVoid(src)));
}
return nullptr;
}
static void
-pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
+pcm_convert_8_to_32(int32_t *out, const int8_t *in, size_t n)
{
- while (in < in_end)
- *out++ = *in++ << 24;
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] << 24;
}
static void
-pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end)
+pcm_convert_16_to_32(int32_t *out, const int16_t *in, size_t n)
{
- while (in < in_end)
- *out++ = *in++ << 16;
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] << 16;
}
static void
pcm_convert_24_to_32(int32_t *gcc_restrict out,
const int32_t *gcc_restrict in,
- const int32_t *gcc_restrict in_end)
+ size_t n)
{
- while (in < in_end)
- *out++ = *in++ << 8;
+ for (size_t i = 0; i != n; ++i)
+ out[i] = in[i] << 8;
}
-static int32_t *
-pcm_allocate_8_to_32(PcmBuffer &buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_8_to_32(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<int32_t>(src.size);
+ pcm_convert_8_to_32(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int32_t *
-pcm_allocate_16_to_32(PcmBuffer &buffer,
- const int16_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_16_to_32(PcmBuffer &buffer, ConstBuffer<int16_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size * 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<int32_t>(src.size);
+ pcm_convert_16_to_32(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int32_t *
-pcm_allocate_24p32_to_32(PcmBuffer &buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_24p32_to_32(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- *dest_size_r = src_size;
- int32_t *dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ auto dest = buffer.GetT<int32_t>(src.size);
+ pcm_convert_24_to_32(dest, src.data, src.size);
+ return { dest, src.size };
}
-static int32_t *
-pcm_allocate_float_to_32(PcmBuffer &buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_float_to_32(PcmBuffer &buffer, ConstBuffer<float> src)
{
/* convert to S24_P32 first */
- int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
- dest_size_r);
+ auto dest = pcm_allocate_float_to_24(buffer, src);
/* convert to 32 bit in-place */
- pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
- return dest;
+ pcm_convert_24_to_32(dest.data, dest.data, src.size);
+ return ToConst(dest);
}
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_to_32(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
- assert(src_size % sample_format_size(src_format) == 0);
-
switch (src_format) {
case SampleFormat::UNDEFINED:
case SampleFormat::DSD:
@@ -407,27 +312,22 @@ pcm_convert_to_32(PcmBuffer &buffer,
case SampleFormat::S8:
return pcm_allocate_8_to_32(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
return pcm_allocate_16_to_32(buffer,
- (const int16_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int16_t>::FromVoid(src));
case SampleFormat::S24_P32:
return pcm_allocate_24p32_to_32(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::S32:
- *dest_size_r = src_size;
- return (const int32_t *)src;
+ return ConstBuffer<int32_t>::FromVoid(src);
case SampleFormat::FLOAT:
return pcm_allocate_float_to_32(buffer,
- (const float *)src, src_size,
- dest_size_r);
+ ConstBuffer<float>::FromVoid(src));
}
return nullptr;
@@ -437,78 +337,50 @@ template<SampleFormat F, class Traits=SampleTraits<F>>
static void
ConvertToFloat(float *dest,
typename Traits::const_pointer_type src,
- typename Traits::const_pointer_type end)
+ size_t n)
{
constexpr float factor = 0.5 / (1 << (Traits::BITS - 2));
- while (src != end)
- *dest++ = float(*src++) * factor;
-
-}
-
-template<SampleFormat F, class Traits=SampleTraits<F>>
-static void
-ConvertToFloat(float *dest,
- typename Traits::const_pointer_type src, size_t size)
-{
- ConvertToFloat<F, Traits>(dest, src, pcm_end_pointer(src, size));
+ for (size_t i = 0; i != n; ++i)
+ dest[i] = float(src[i]) * factor;
}
template<SampleFormat F, class Traits=SampleTraits<F>>
-static float *
+static ConstBuffer<float>
AllocateToFloat(PcmBuffer &buffer,
- typename Traits::const_pointer_type src, size_t src_size,
- size_t *dest_size_r)
+ ConstBuffer<typename Traits::value_type> src)
{
- constexpr size_t src_sample_size = Traits::SAMPLE_SIZE;
- assert(src_size % src_sample_size == 0);
-
- const size_t num_samples = src_size / src_sample_size;
- *dest_size_r = num_samples * sizeof(float);
- float *dest = (float *)buffer.Get(*dest_size_r);
- ConvertToFloat<F, Traits>(dest, src, src_size);
- return dest;
+ float *dest = buffer.GetT<float>(src.size);
+ ConvertToFloat<F, Traits>(dest, src.data, src.size);
+ return { dest, src.size };
}
-static float *
-pcm_allocate_8_to_float(PcmBuffer &buffer,
- const int8_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_8_to_float(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- return AllocateToFloat<SampleFormat::S8>(buffer, src, src_size,
- dest_size_r);
+ return AllocateToFloat<SampleFormat::S8>(buffer, src);
}
-static float *
-pcm_allocate_16_to_float(PcmBuffer &buffer,
- const int16_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_16_to_float(PcmBuffer &buffer, ConstBuffer<int16_t> src)
{
- return AllocateToFloat<SampleFormat::S16>(buffer, src, src_size,
- dest_size_r);
+ return AllocateToFloat<SampleFormat::S16>(buffer, src);
}
-static float *
-pcm_allocate_24p32_to_float(PcmBuffer &buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_24p32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- return AllocateToFloat<SampleFormat::S24_P32>(buffer, src, src_size,
- dest_size_r);
+ return AllocateToFloat<SampleFormat::S24_P32>(buffer, src);
}
-static float *
-pcm_allocate_32_to_float(PcmBuffer &buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- return AllocateToFloat<SampleFormat::S32>(buffer, src, src_size,
- dest_size_r);
+ return AllocateToFloat<SampleFormat::S32>(buffer, src);
}
-const float *
+ConstBuffer<float>
pcm_convert_to_float(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
switch (src_format) {
case SampleFormat::UNDEFINED:
@@ -517,27 +389,22 @@ pcm_convert_to_float(PcmBuffer &buffer,
case SampleFormat::S8:
return pcm_allocate_8_to_float(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
return pcm_allocate_16_to_float(buffer,
- (const int16_t *)src, src_size,
- dest_size_r);
-
- case SampleFormat::S24_P32:
- return pcm_allocate_24p32_to_float(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int16_t>::FromVoid(src));
case SampleFormat::S32:
return pcm_allocate_32_to_float(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_float(buffer,
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::FLOAT:
- *dest_size_r = src_size;
- return (const float *)src;
+ return ConstBuffer<float>::FromVoid(src);
}
return nullptr;
diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx
index cc44d6dd5..378ac8e2a 100644
--- a/src/pcm/PcmFormat.hxx
+++ b/src/pcm/PcmFormat.hxx
@@ -25,6 +25,7 @@
#include <stdint.h>
#include <stddef.h>
+template<typename T> struct ConstBuffer;
class PcmBuffer;
class PcmDither;
@@ -36,14 +37,12 @@ class PcmDither;
* @param dither a pcm_dither object for 24-to-16 conversion
* @param bits the number of in the source buffer
* @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int16_t *
+gcc_pure
+ConstBuffer<int16_t>
pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
/**
* Converts PCM samples to 24 bit (32 bit alignment).
@@ -51,14 +50,12 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
* @param buffer a PcmBuffer object
* @param bits the number of in the source buffer
* @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+gcc_pure
+ConstBuffer<int32_t>
pcm_convert_to_24(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
/**
* Converts PCM samples to 32 bit.
@@ -66,14 +63,12 @@ pcm_convert_to_24(PcmBuffer &buffer,
* @param buffer a PcmBuffer object
* @param bits the number of in the source buffer
* @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+gcc_pure
+ConstBuffer<int32_t>
pcm_convert_to_32(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
/**
* Converts PCM samples to 32 bit floating point.
@@ -85,9 +80,9 @@ pcm_convert_to_32(PcmBuffer &buffer,
* @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const float *
+gcc_pure
+ConstBuffer<float>
pcm_convert_to_float(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
#endif
diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx
index 001794061..c1ffd8633 100644
--- a/src/pcm/PcmMix.cxx
+++ b/src/pcm/PcmMix.cxx
@@ -19,42 +19,56 @@
#include "config.h"
#include "PcmMix.hxx"
-#include "PcmVolume.hxx"
+#include "Volume.hxx"
#include "PcmUtils.hxx"
#include "AudioFormat.hxx"
+#include "Traits.hxx"
+#include "util/Clamp.hxx"
+#include "PcmDither.cxx" // including the .cxx file to get inlined templates
+
+#include <assert.h>
#include <math.h>
-template<typename T, typename U, unsigned bits>
-static T
-PcmAddVolume(T _a, T _b, int volume1, int volume2)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::value_type
+PcmAddVolume(PcmDither &dither,
+ typename Traits::value_type _a, typename Traits::value_type _b,
+ int volume1, int volume2)
{
- U a(_a), b(_b);
-
- U c = ((a * volume1 + b * volume2) +
- pcm_volume_dither() + PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
+ typename Traits::long_type a(_a), b(_b);
+ typename Traits::long_type c(a * volume1 + b * volume2);
- return PcmClamp<T, U, bits>(c);
+ return dither.DitherShift<typename Traits::long_type,
+ Traits::BITS + PCM_VOLUME_BITS,
+ Traits::BITS>(c);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
-PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2)
+PcmAddVolume(PcmDither &dither,
+ typename Traits::pointer_type a,
+ typename Traits::const_pointer_type b,
+ size_t n, int volume1, int volume2)
{
for (size_t i = 0; i != n; ++i)
- a[i] = PcmAddVolume<T, U, bits>(a[i], b[i], volume1, volume2);
+ a[i] = PcmAddVolume<F, Traits>(dither, a[i], b[i],
+ volume1, volume2);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
-PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2)
+PcmAddVolumeVoid(PcmDither &dither,
+ void *a, const void *b, size_t size, int volume1, int volume2)
{
- constexpr size_t sample_size = sizeof(T);
+ constexpr size_t sample_size = Traits::SAMPLE_SIZE;
assert(size % sample_size == 0);
- PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size,
- volume1, volume2);
+ PcmAddVolume<F, Traits>(dither,
+ typename Traits::pointer_type(a),
+ typename Traits::const_pointer_type(b),
+ size / sample_size,
+ volume1, volume2);
}
static void
@@ -72,7 +86,7 @@ pcm_add_vol_float(float *buffer1, const float *buffer2,
}
static bool
-pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
+pcm_add_vol(PcmDither &dither, void *buffer1, const void *buffer2, size_t size,
int vol1, int vol2,
SampleFormat format)
{
@@ -83,23 +97,27 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
return false;
case SampleFormat::S8:
- PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S8>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::S16:
- PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S16>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::S24_P32:
- PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S24_P32>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::S32:
- PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S32>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::FLOAT:
@@ -114,30 +132,35 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
gcc_unreachable();
}
-template<typename T, typename U, unsigned bits>
-static T
-PcmAdd(T _a, T _b)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::value_type
+PcmAdd(typename Traits::value_type _a, typename Traits::value_type _b)
{
- U a(_a), b(_b);
- return PcmClamp<T, U, bits>(a + b);
+ typename Traits::sum_type a(_a), b(_b);
+
+ return PcmClamp<F, Traits>(a + b);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
-PcmAdd(T *a, const T *b, unsigned n)
+PcmAdd(typename Traits::pointer_type a,
+ typename Traits::const_pointer_type b,
+ size_t n)
{
for (size_t i = 0; i != n; ++i)
- a[i] = PcmAdd<T, U, bits>(a[i], b[i]);
+ a[i] = PcmAdd<F, Traits>(a[i], b[i]);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
PcmAddVoid(void *a, const void *b, size_t size)
{
- constexpr size_t sample_size = sizeof(T);
+ constexpr size_t sample_size = Traits::SAMPLE_SIZE;
assert(size % sample_size == 0);
- PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size);
+ PcmAdd<F, Traits>(typename Traits::pointer_type(a),
+ typename Traits::const_pointer_type(b),
+ size / sample_size);
}
static void
@@ -162,19 +185,19 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
return false;
case SampleFormat::S8:
- PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S8>(buffer1, buffer2, size);
return true;
case SampleFormat::S16:
- PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S16>(buffer1, buffer2, size);
return true;
case SampleFormat::S24_P32:
- PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S24_P32>(buffer1, buffer2, size);
return true;
case SampleFormat::S32:
- PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S32>(buffer1, buffer2, size);
return true;
case SampleFormat::FLOAT:
@@ -188,10 +211,9 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
}
bool
-pcm_mix(void *buffer1, const void *buffer2, size_t size,
+pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size,
SampleFormat format, float portion1)
{
- int vol1;
float s;
/* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses -1
@@ -202,8 +224,9 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size,
s = sin(M_PI_2 * portion1);
s *= s;
- vol1 = s * PCM_VOLUME_1 + 0.5;
- vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1);
+ int vol1 = s * PCM_VOLUME_1S + 0.5;
+ vol1 = Clamp<int>(vol1, 0, PCM_VOLUME_1S);
- return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
+ return pcm_add_vol(dither, buffer1, buffer2, size,
+ vol1, PCM_VOLUME_1S - vol1, format);
}
diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx
index 637c88f8a..a6657a979 100644
--- a/src/pcm/PcmMix.hxx
+++ b/src/pcm/PcmMix.hxx
@@ -25,6 +25,8 @@
#include <stddef.h>
+class PcmDither;
+
/*
* Linearly mixes two PCM buffers. Both must have the same length and
* the same audio format. The formula is:
@@ -44,7 +46,7 @@
*/
gcc_warn_unused_result
bool
-pcm_mix(void *buffer1, const void *buffer2, size_t size,
+pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size,
SampleFormat format, float portion1);
#endif
diff --git a/src/pcm/PcmPrng.hxx b/src/pcm/PcmPrng.hxx
index 0c823250d..73b1456a8 100644
--- a/src/pcm/PcmPrng.hxx
+++ b/src/pcm/PcmPrng.hxx
@@ -24,7 +24,7 @@
* A very simple linear congruential PRNG. It's good enough for PCM
* dithering.
*/
-static unsigned long
+constexpr static inline unsigned long
pcm_prng(unsigned long state)
{
return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx
deleted file mode 100644
index 01f269ea9..000000000
--- a/src/pcm/PcmResample.cxx
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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 "PcmResampleInternal.hxx"
-
-#ifdef HAVE_LIBSAMPLERATE
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#endif
-
-#include <string.h>
-
-#ifdef HAVE_LIBSAMPLERATE
-static bool lsr_enabled;
-#endif
-
-#ifdef HAVE_LIBSAMPLERATE
-static bool
-pcm_resample_lsr_enabled(void)
-{
- return lsr_enabled;
-}
-#endif
-
-bool
-pcm_resample_global_init(Error &error)
-{
-#ifdef HAVE_LIBSAMPLERATE
- const char *converter =
- config_get_string(CONF_SAMPLERATE_CONVERTER, "");
-
- lsr_enabled = strcmp(converter, "internal") != 0;
- if (lsr_enabled)
- return pcm_resample_lsr_global_init(converter, error);
- else
- return true;
-#else
- (void)error;
- return true;
-#endif
-}
-
-PcmResampler::PcmResampler()
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- pcm_resample_lsr_init(this);
-#endif
-}
-
-PcmResampler::~PcmResampler()
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- pcm_resample_lsr_deinit(this);
-#endif
-}
-
-void
-PcmResampler::Reset()
-{
-#ifdef HAVE_LIBSAMPLERATE
- pcm_resample_lsr_reset(this);
-#endif
-}
-
-const float *
-PcmResampler::ResampleFloat(unsigned channels, unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_float(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- /* sizeof(float)==sizeof(int32_t); the fallback resampler does
- not do any math on the sample values, so this hack is
- possible: */
- return (const float *)
- pcm_resample_fallback_32(this, channels,
- src_rate, (const int32_t *)src_buffer,
- src_size,
- dest_rate, dest_size_r);
-}
-
-const int16_t *
-PcmResampler::Resample16(unsigned channels,
- unsigned src_rate, const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_16(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- return pcm_resample_fallback_16(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
-
-const int32_t *
-PcmResampler::Resample32(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_32(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- return pcm_resample_fallback_32(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
-
-const int32_t *
-PcmResampler::Resample24(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_24(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- /* reuse the 32 bit code - the resampler code doesn't care if
- the upper 8 bits are actually used */
- return pcm_resample_fallback_32(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
diff --git a/src/pcm/PcmResample.hxx b/src/pcm/PcmResample.hxx
deleted file mode 100644
index e839d6ecd..000000000
--- a/src/pcm/PcmResample.hxx
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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_PCM_RESAMPLE_HXX
-#define MPD_PCM_RESAMPLE_HXX
-
-#include "check.h"
-#include "PcmBuffer.hxx"
-
-#include <stdint.h>
-#include <stddef.h>
-
-#ifdef HAVE_LIBSAMPLERATE
-#include <samplerate.h>
-#endif
-
-class Error;
-
-/**
- * This object is statically allocated (within another struct), and
- * holds buffer allocations and the state for the resampler.
- */
-struct PcmResampler {
-#ifdef HAVE_LIBSAMPLERATE
- SRC_STATE *state;
- SRC_DATA data;
-
- PcmBuffer in, out;
-
- struct {
- unsigned src_rate;
- unsigned dest_rate;
- unsigned channels;
- } prev;
-
- int error;
-#endif
-
- PcmBuffer buffer;
-
- PcmResampler();
- ~PcmResampler();
-
- /**
- * @see pcm_convert_reset()
- */
- void Reset();
-
- /**
- * Resamples 32 bit float data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const float *ResampleFloat(unsigned channels, unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-
- /**
- * Resamples 16 bit PCM data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const int16_t *Resample16(unsigned channels, unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-
- /**
- * Resamples 32 bit PCM data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const int32_t *Resample32(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-
- /**
- * Resamples 24 bit PCM data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const int32_t *Resample24(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-};
-
-bool
-pcm_resample_global_init(Error &error);
-
-#endif
diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx
deleted file mode 100644
index a62cd64f7..000000000
--- a/src/pcm/PcmResampleFallback.cxx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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 "PcmResampleInternal.hxx"
-
-#include <assert.h>
-
-/* resampling code blatantly ripped from ESD */
-const int16_t *
-pcm_resample_fallback_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
-{
- unsigned dest_pos = 0;
- unsigned src_frames = src_size / channels / sizeof(*src_buffer);
- unsigned dest_frames =
- (src_frames * dest_rate + src_rate - 1) / src_rate;
- unsigned dest_samples = dest_frames * channels;
- size_t dest_size = dest_samples * sizeof(*src_buffer);
- int16_t *dest_buffer = (int16_t *)state->buffer.Get(dest_size);
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- switch (channels) {
- case 1:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- }
- break;
- case 2:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
- src_pos &= ~1;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- dest_buffer[dest_pos++] = src_buffer[src_pos + 1];
- }
- break;
- }
-
- *dest_size_r = dest_size;
- return dest_buffer;
-}
-
-const int32_t *
-pcm_resample_fallback_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
-{
- unsigned dest_pos = 0;
- unsigned src_frames = src_size / channels / sizeof(*src_buffer);
- unsigned dest_frames =
- (src_frames * dest_rate + src_rate - 1) / src_rate;
- unsigned dest_samples = dest_frames * channels;
- size_t dest_size = dest_samples * sizeof(*src_buffer);
- int32_t *dest_buffer = (int32_t *)state->buffer.Get(dest_size);
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- switch (channels) {
- case 1:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- }
- break;
- case 2:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
- src_pos &= ~1;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- dest_buffer[dest_pos++] = src_buffer[src_pos + 1];
- }
- break;
- }
-
- *dest_size_r = dest_size;
- return dest_buffer;
-}
diff --git a/src/pcm/PcmResampleInternal.hxx b/src/pcm/PcmResampleInternal.hxx
deleted file mode 100644
index 5090c13d1..000000000
--- a/src/pcm/PcmResampleInternal.hxx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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.
- */
-
-/** \file
- *
- * Internal declarations for the pcm_resample library. The "internal"
- * resampler is called "fallback" in the MPD source, so the file name
- * of this header is somewhat unrelated to it.
- */
-
-#ifndef MPD_PCM_RESAMPLE_INTERNAL_HXX
-#define MPD_PCM_RESAMPLE_INTERNAL_HXX
-
-#include "check.h"
-#include "PcmResample.hxx"
-
-#ifdef HAVE_LIBSAMPLERATE
-
-bool
-pcm_resample_lsr_global_init(const char *converter, Error &error);
-
-void
-pcm_resample_lsr_init(PcmResampler *state);
-
-void
-pcm_resample_lsr_deinit(PcmResampler *state);
-
-void
-pcm_resample_lsr_reset(PcmResampler *state);
-
-const float *
-pcm_resample_lsr_float(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-const int16_t *
-pcm_resample_lsr_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-const int32_t *
-pcm_resample_lsr_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-const int32_t *
-pcm_resample_lsr_24(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-#endif
-
-const int16_t *
-pcm_resample_fallback_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
-
-const int32_t *
-pcm_resample_fallback_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm/PcmResampleLibsamplerate.cxx b/src/pcm/PcmResampleLibsamplerate.cxx
deleted file mode 100644
index 9eac2d545..000000000
--- a/src/pcm/PcmResampleLibsamplerate.cxx
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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 "PcmResampleInternal.hxx"
-#include "PcmUtils.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-static int lsr_converter = SRC_SINC_FASTEST;
-
-static constexpr Domain libsamplerate_domain("libsamplerate");
-
-static bool
-lsr_parse_converter(const char *s)
-{
- assert(s != nullptr);
-
- if (*s == 0)
- return true;
-
- char *endptr;
- long l = strtol(s, &endptr, 10);
- if (*endptr == 0 && src_get_name(l) != nullptr) {
- lsr_converter = l;
- return true;
- }
-
- size_t length = strlen(s);
- for (int i = 0;; ++i) {
- const char *name = src_get_name(i);
- if (name == nullptr)
- break;
-
- if (StringEqualsCaseASCII(s, name, length)) {
- lsr_converter = i;
- return true;
- }
- }
-
- return false;
-}
-
-bool
-pcm_resample_lsr_global_init(const char *converter, Error &error)
-{
- if (!lsr_parse_converter(converter)) {
- error.Format(libsamplerate_domain,
- "unknown samplerate converter '%s'", converter);
- return false;
- }
-
- FormatDebug(libsamplerate_domain,
- "libsamplerate converter '%s'",
- src_get_name(lsr_converter));
-
- return true;
-}
-
-void
-pcm_resample_lsr_init(PcmResampler *state)
-{
- state->state = nullptr;
- memset(&state->data, 0, sizeof(state->data));
- memset(&state->prev, 0, sizeof(state->prev));
- state->error = 0;
-}
-
-void
-pcm_resample_lsr_deinit(PcmResampler *state)
-{
- if (state->state != nullptr)
- state->state = src_delete(state->state);
-}
-
-void
-pcm_resample_lsr_reset(PcmResampler *state)
-{
- if (state->state != nullptr)
- src_reset(state->state);
-}
-
-static bool
-pcm_resample_set(PcmResampler *state,
- unsigned channels, unsigned src_rate, unsigned dest_rate,
- Error &error_r)
-{
- /* (re)set the state/ratio if the in or out format changed */
- if (channels == state->prev.channels &&
- src_rate == state->prev.src_rate &&
- dest_rate == state->prev.dest_rate)
- return true;
-
- state->error = 0;
- state->prev.channels = channels;
- state->prev.src_rate = src_rate;
- state->prev.dest_rate = dest_rate;
-
- if (state->state)
- state->state = src_delete(state->state);
-
- int error;
- state->state = src_new(lsr_converter, channels, &error);
- if (!state->state) {
- error_r.Format(libsamplerate_domain, error,
- "libsamplerate initialization has failed: %s",
- src_strerror(error));
- return false;
- }
-
- SRC_DATA *data = &state->data;
- data->src_ratio = (double)dest_rate / (double)src_rate;
- FormatDebug(libsamplerate_domain,
- "setting samplerate conversion ratio to %.2lf",
- data->src_ratio);
- src_set_ratio(state->state, data->src_ratio);
-
- return true;
-}
-
-static bool
-lsr_process(PcmResampler *state, Error &error)
-{
- if (state->error == 0)
- state->error = src_process(state->state, &state->data);
- if (state->error) {
- error.Format(libsamplerate_domain, state->error,
- "libsamplerate has failed: %s",
- src_strerror(state->error));
- return false;
- }
-
- return true;
-}
-
-const float *
-pcm_resample_lsr_float(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- SRC_DATA *data = &state->data;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate, error))
- return nullptr;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- data->data_in = const_cast<float *>(src_buffer);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- size_t data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = (float *)state->out.Get(data_out_size);
-
- if (!lsr_process(state, error))
- return nullptr;
-
- *dest_size_r = data->output_frames_gen *
- sizeof(*data->data_out) * channels;
- return data->data_out;
-}
-
-const int16_t *
-pcm_resample_lsr_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- SRC_DATA *data = &state->data;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate,
- error))
- return nullptr;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- size_t data_in_size = data->input_frames * sizeof(float) * channels;
- data->data_in = (float *)state->in.Get(data_in_size);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- size_t data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = (float *)state->out.Get(data_out_size);
-
- src_short_to_float_array(src_buffer, data->data_in,
- data->input_frames * channels);
-
- if (!lsr_process(state, error))
- return nullptr;
-
- int16_t *dest_buffer;
- *dest_size_r = data->output_frames_gen *
- sizeof(*dest_buffer) * channels;
- dest_buffer = (int16_t *)state->buffer.Get(*dest_size_r);
- src_float_to_short_array(data->data_out, dest_buffer,
- data->output_frames_gen * channels);
-
- return dest_buffer;
-}
-
-#ifdef HAVE_LIBSAMPLERATE_NOINT
-
-/* libsamplerate introduced these functions in v0.1.3 */
-
-static void
-src_int_to_float_array(const int *in, float *out, int len)
-{
- while (len-- > 0)
- *out++ = *in++ / (float)(1 << (24 - 1));
-}
-
-static void
-src_float_to_int_array (const float *in, int *out, int len)
-{
- while (len-- > 0)
- *out++ = *in++ * (float)(1 << (24 - 1));
-}
-
-#endif
-
-const int32_t *
-pcm_resample_lsr_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- SRC_DATA *data = &state->data;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate,
- error))
- return nullptr;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- size_t data_in_size = data->input_frames * sizeof(float) * channels;
- data->data_in = (float *)state->in.Get(data_in_size);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- size_t data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = (float *)state->out.Get(data_out_size);
-
- src_int_to_float_array(src_buffer, data->data_in,
- data->input_frames * channels);
-
- if (!lsr_process(state, error))
- return nullptr;
-
- int32_t *dest_buffer;
- *dest_size_r = data->output_frames_gen *
- sizeof(*dest_buffer) * channels;
- dest_buffer = (int32_t *)state->buffer.Get(*dest_size_r);
- src_float_to_int_array(data->data_out, dest_buffer,
- data->output_frames_gen * channels);
-
- return dest_buffer;
-}
-
-const int32_t *
-pcm_resample_lsr_24(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- const auto result = pcm_resample_lsr_32(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error);
- if (result != nullptr)
- /* src_float_to_int_array() clamps for 32 bit
- integers; now make sure everything's fine for 24
- bit */
- /* TODO: eliminate the 32 bit clamp to reduce overhead */
- PcmClampN<int32_t, int32_t, 24>(const_cast<int32_t *>(result),
- result,
- *dest_size_r / sizeof(*result));
-
- return result;
-}
diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx
index febe12d7b..9c741e55b 100644
--- a/src/pcm/PcmUtils.hxx
+++ b/src/pcm/PcmUtils.hxx
@@ -26,53 +26,31 @@
#include <stdint.h>
-/**
- * Add a byte count to the specified pointer. This is a utility
- * function to convert a source pointer and a byte count to an "end"
- * pointer for use in loops.
- */
-template<typename T>
-static inline const T *
-pcm_end_pointer(const T *p, size_t size)
-{
- return (const T *)((const uint8_t *)p + size);
-}
+enum class SampleFormat : uint8_t;
+template<SampleFormat F> struct SampleTraits;
/**
* Check if the value is within the range of the provided bit size,
* and caps it if necessary.
*/
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
gcc_const
-static inline T
-PcmClamp(U x)
+static inline typename Traits::value_type
+PcmClamp(typename Traits::long_type x)
{
- constexpr U MIN_VALUE = -(U(1) << (bits - 1));
- constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1;
+ typedef typename Traits::value_type T;
typedef std::numeric_limits<T> limits;
- static_assert(MIN_VALUE >= limits::min(), "out of range");
- static_assert(MAX_VALUE <= limits::max(), "out of range");
+ static_assert(Traits::MIN >= limits::min(), "out of range");
+ static_assert(Traits::MAX <= limits::max(), "out of range");
- if (gcc_unlikely(x < MIN_VALUE))
- return T(MIN_VALUE);
+ if (gcc_unlikely(x < Traits::MIN))
+ return T(Traits::MIN);
- if (gcc_unlikely(x > MAX_VALUE))
- return T(MAX_VALUE);
+ if (gcc_unlikely(x > Traits::MAX))
+ return T(Traits::MAX);
return T(x);
}
-/**
- * Check if the values in this buffer are within the range of the
- * provided bit size, and clamps them whenever necessary.
- */
-template<typename T, typename U, unsigned bits>
-static inline void
-PcmClampN(T *dest, const U *src, unsigned n)
-{
- while (n-- > 0)
- *dest++ = PcmClamp<T, U, bits>(*src++);
-}
-
#endif
diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx
deleted file mode 100644
index 564880633..000000000
--- a/src/pcm/PcmVolume.cxx
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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 "PcmVolume.hxx"
-#include "PcmUtils.hxx"
-#include "AudioFormat.hxx"
-
-#include <stdint.h>
-#include <string.h>
-
-static void
-pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume)
-{
- while (buffer < end) {
- int32_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample);
- }
-}
-
-static void
-pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume)
-{
- while (buffer < end) {
- int32_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample);
- }
-}
-
-#ifdef __i386__
-/**
- * Optimized volume function for i386. Use the EDX:EAX 2*32 bit
- * multiplication result instead of emulating 64 bit multiplication.
- */
-static inline int32_t
-pcm_volume_sample_24(int32_t sample, int32_t volume, gcc_unused int32_t dither)
-{
- int32_t result;
-
- asm(/* edx:eax = sample * volume */
- "imul %2\n"
-
- /* "add %3, %1\n" dithering disabled for now, because we
- have no overflow check - is dithering really important
- here? */
-
- /* eax = edx:eax / PCM_VOLUME_1 */
- "sal $22, %%edx\n"
- "shr $10, %1\n"
- "or %%edx, %1\n"
-
- : "=a"(result)
- : "0"(sample), "r"(volume) /* , "r"(dither) */
- : "edx"
- );
-
- return result;
-}
-#endif
-
-static void
-pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume)
-{
- while (buffer < end) {
-#ifdef __i386__
- /* assembly version for i386 */
- int32_t sample = *buffer;
-
- sample = pcm_volume_sample_24(sample, volume,
- pcm_volume_dither());
-#else
- /* portable version */
- int64_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-#endif
- *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample);
- }
-}
-
-static void
-pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume)
-{
- while (buffer < end) {
-#ifdef __i386__
- /* assembly version for i386 */
- int32_t sample = *buffer;
-
- *buffer++ = pcm_volume_sample_24(sample, volume, 0);
-#else
- /* portable version */
- int64_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
- *buffer++ = PcmClamp<int32_t, int64_t, 32>(sample);
-#endif
- }
-}
-
-static void
-pcm_volume_change_float(float *buffer, const float *end, float volume)
-{
- while (buffer < end) {
- float sample = *buffer;
- sample *= volume;
- *buffer++ = sample;
- }
-}
-
-bool
-pcm_volume(void *buffer, size_t length,
- SampleFormat format,
- int volume)
-{
- if (volume == PCM_VOLUME_1)
- return true;
-
- if (volume <= 0) {
- memset(buffer, 0, length);
- return true;
- }
-
- const void *end = pcm_end_pointer(buffer, length);
- switch (format) {
- case SampleFormat::UNDEFINED:
- case SampleFormat::DSD:
- /* not implemented */
- return false;
-
- case SampleFormat::S8:
- pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end,
- volume);
- return true;
-
- case SampleFormat::S16:
- pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end,
- volume);
- return true;
-
- case SampleFormat::S24_P32:
- pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end,
- volume);
- return true;
-
- case SampleFormat::S32:
- pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end,
- volume);
- return true;
-
- case SampleFormat::FLOAT:
- pcm_volume_change_float((float *)buffer, (const float *)end,
- pcm_volume_to_float(volume));
- return true;
- }
-
- assert(false);
- gcc_unreachable();
-}
diff --git a/src/pcm/PcmVolume.hxx b/src/pcm/PcmVolume.hxx
deleted file mode 100644
index 8cd82acf7..000000000
--- a/src/pcm/PcmVolume.hxx
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2003-2013 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_PCM_VOLUME_HXX
-#define MPD_PCM_VOLUME_HXX
-
-#include "PcmPrng.hxx"
-#include "AudioFormat.hxx"
-
-#include <stdint.h>
-#include <stddef.h>
-
-enum {
- /** this value means "100% volume" */
- PCM_VOLUME_1 = 1024,
-};
-
-struct AudioFormat;
-
-/**
- * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an
- * integer volume value (1000 = 100%).
- */
-static inline int
-pcm_float_to_volume(float volume)
-{
- return volume * PCM_VOLUME_1 + 0.5;
-}
-
-static inline float
-pcm_volume_to_float(int volume)
-{
- return (float)volume / (float)PCM_VOLUME_1;
-}
-
-/**
- * Returns the next volume dithering number, between -511 and +511.
- * This number is taken from a global PRNG, see pcm_prng().
- */
-static inline int
-pcm_volume_dither(void)
-{
- static unsigned long state;
- uint32_t r;
-
- r = state = pcm_prng(state);
-
- return (r & 511) - ((r >> 9) & 511);
-}
-
-/**
- * Adjust the volume of the specified PCM buffer.
- *
- * @param buffer the PCM buffer
- * @param length the length of the PCM buffer
- * @param format the sample format of the PCM buffer
- * @param volume the volume between 0 and #PCM_VOLUME_1
- * @return true on success, false if the audio format is not supported
- */
-bool
-pcm_volume(void *buffer, size_t length,
- SampleFormat format,
- int volume);
-
-#endif
diff --git a/src/pcm/Resampler.hxx b/src/pcm/Resampler.hxx
new file mode 100644
index 000000000..a74ef4e77
--- /dev/null
+++ b/src/pcm/Resampler.hxx
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_RESAMPLER_HXX
+#define MPD_PCM_RESAMPLER_HXX
+
+#include "util/ConstBuffer.hxx"
+#include "Compiler.h"
+
+struct AudioFormat;
+class Error;
+
+/**
+ * This is an interface for plugins that convert PCM data to a
+ * specific sample rate.
+ */
+class PcmResampler {
+public:
+ virtual ~PcmResampler() {}
+
+ /**
+ * Opens the resampler, preparing it for Resample().
+ *
+ * @param af the audio format of incoming data; the plugin may
+ * modify the object to enforce another input format (however,
+ * it may not request a different input sample rate)
+ * @param new_sample_rate the requested output sample rate
+ * @param error location to store the error
+ * @return the format of outgoing data or
+ * AudioFormat::Undefined() on error
+ */
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) = 0;
+
+ /**
+ * Closes the resampler. After that, you may call Open()
+ * again.
+ */
+ virtual void Close() = 0;
+
+ /**
+ * Resamples a block of PCM data.
+ *
+ * @param src the input buffer
+ * @param src_size the size of #src_buffer in bytes
+ * @param dest_size_r the size of the returned buffer
+ * @param error location to store the error occurring, or nullptr
+ * to ignore errors.
+ * @return the destination buffer on success (will be
+ * invalidated by filter_close() or filter_filter()), nullptr on
+ * error
+ */
+ gcc_pure
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) = 0;
+};
+
+#endif
diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx
new file mode 100644
index 000000000..e82ae1481
--- /dev/null
+++ b/src/pcm/SoxrResampler.cxx
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2003-2013 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 "SoxrResampler.hxx"
+#include "AudioFormat.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <soxr.h>
+
+#include <assert.h>
+
+static constexpr Domain soxr_domain("soxr");
+
+AudioFormat
+SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error)
+{
+ assert(af.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ soxr_error_t e;
+ soxr = soxr_create(af.sample_rate, new_sample_rate,
+ af.channels, &e,
+ nullptr, nullptr, nullptr);
+ if (soxr == nullptr) {
+ error.Format(soxr_domain,
+ "soxr initialization has failed: %s", e);
+ return AudioFormat::Undefined();
+ }
+
+ FormatDebug(soxr_domain, "soxr engine '%s'", soxr_engine(soxr));
+
+ channels = af.channels;
+
+ ratio = float(new_sample_rate) / float(af.sample_rate);
+ FormatDebug(soxr_domain,
+ "samplerate conversion ratio to %.2lf",
+ ratio);
+
+ /* libsoxr works with floating point samples */
+ af.format = SampleFormat::FLOAT;
+
+ AudioFormat result = af;
+ result.sample_rate = new_sample_rate;
+ return result;
+}
+
+void
+SoxrPcmResampler::Close()
+{
+ soxr_delete(soxr);
+}
+
+ConstBuffer<void>
+SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error)
+{
+ const size_t frame_size = channels * sizeof(float);
+ assert(src.size % frame_size == 0);
+
+ const size_t n_frames = src.size / frame_size;
+
+ const size_t o_frames = size_t(n_frames * ratio + 0.5);
+
+ float *output_buffer = (float *)buffer.Get(o_frames * frame_size);
+
+ size_t i_done, o_done;
+ soxr_error_t e = soxr_process(soxr, src.data, n_frames, &i_done,
+ output_buffer, o_frames, &o_done);
+ if (e != nullptr) {
+ error.Format(soxr_domain, "soxr error: %s", e);
+ return nullptr;
+ }
+
+ return { output_buffer, o_done * frame_size };
+}
diff --git a/src/SongPointer.hxx b/src/pcm/SoxrResampler.hxx
index ded3b3e1d..69c173741 100644
--- a/src/SongPointer.hxx
+++ b/src/pcm/SoxrResampler.hxx
@@ -17,47 +17,31 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_SONG_POINTER_HXX
-#define MPD_SONG_POINTER_HXX
+#ifndef MPD_PCM_SOXR_RESAMPLER_HXX
+#define MPD_PCM_SOXR_RESAMPLER_HXX
-#include "Song.hxx"
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
-#include <utility>
+struct AudioFormat;
-class SongPointer {
- Song *song;
-
-public:
- explicit SongPointer(Song *_song)
- :song(_song) {}
-
- SongPointer(const SongPointer &) = delete;
-
- SongPointer(SongPointer &&other):song(other.song) {
- other.song = nullptr;
- }
-
- ~SongPointer() {
- if (song != nullptr)
- song->Free();
- }
-
- SongPointer &operator=(const SongPointer &) = delete;
+/**
+ * A resampler using soxr.
+ */
+class SoxrPcmResampler final : public PcmResampler {
+ struct soxr *soxr;
- SongPointer &operator=(SongPointer &&other) {
- std::swap(song, other.song);
- return *this;
- }
+ unsigned channels;
+ float ratio;
- operator const Song *() const {
- return song;
- }
+ PcmBuffer buffer;
- Song *Steal() {
- auto result = song;
- song = nullptr;
- return result;
- }
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
};
#endif
diff --git a/src/pcm/Traits.hxx b/src/pcm/Traits.hxx
new file mode 100644
index 000000000..ac1ac532d
--- /dev/null
+++ b/src/pcm/Traits.hxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_TRAITS_HXX
+#define MPD_PCM_TRAITS_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+/**
+ * This template describes the specified #SampleFormat. This is an
+ * empty prototype; the specializations contain the real definitions.
+ * See SampleTraits<uint8_t> for more documentation.
+ */
+template<SampleFormat F>
+struct SampleTraits {};
+
+template<>
+struct SampleTraits<SampleFormat::S8> {
+ /**
+ * The type used for one sample value.
+ */
+ typedef int8_t value_type;
+
+ /**
+ * A writable pointer.
+ */
+ typedef value_type *pointer_type;
+
+ /**
+ * A read-only pointer.
+ */
+ typedef const value_type *const_pointer_type;
+
+ /**
+ * A "long" type that is large and accurate enough for adding
+ * two values without risking an (integer) overflow or
+ * (floating point) precision loss.
+ */
+ typedef int sum_type;
+
+ /**
+ * A "long" type that is large and accurate enough for
+ * arithmetic without risking an (integer) overflow or
+ * (floating point) precision loss.
+ */
+ typedef int_least32_t long_type;
+
+ /**
+ * The size of one sample in bytes.
+ */
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+
+ /**
+ * The integer bit depth of one sample. This attribute may
+ * not exist if this is not an integer sample format.
+ */
+ static constexpr unsigned BITS = sizeof(value_type) * 8;
+
+ /**
+ * The minimum sample value.
+ */
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+
+ /**
+ * The maximum sample value.
+ */
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::S16> {
+ typedef int16_t value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef int_least32_t sum_type;
+ typedef int_least32_t long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+ static constexpr unsigned BITS = sizeof(value_type) * 8;
+
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::S32> {
+ typedef int32_t value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef int_least64_t sum_type;
+ typedef int_least64_t long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+ static constexpr unsigned BITS = sizeof(value_type) * 8;
+
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::S24_P32> {
+ typedef int32_t value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef int_least32_t sum_type;
+ typedef int_least64_t long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+ static constexpr unsigned BITS = 24;
+
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::FLOAT> {
+ typedef float value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef float sum_type;
+ typedef float long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+
+ static constexpr value_type MIN = -1;
+ static constexpr value_type MAX = 1;
+};
+
+#endif
diff --git a/src/pcm/Volume.cxx b/src/pcm/Volume.cxx
new file mode 100644
index 000000000..988d3fbbf
--- /dev/null
+++ b/src/pcm/Volume.cxx
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2003-2013 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 "Volume.hxx"
+#include "Domain.hxx"
+#include "PcmUtils.hxx"
+#include "Traits.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include "PcmDither.cxx" // including the .cxx file to get inlined templates
+
+#include <stdint.h>
+#include <string.h>
+
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static inline typename Traits::value_type
+pcm_volume_sample(PcmDither &dither,
+ typename Traits::value_type _sample,
+ int volume)
+{
+ typename Traits::long_type sample(_sample);
+
+ return dither.DitherShift<typename Traits::long_type,
+ Traits::BITS + PCM_VOLUME_BITS,
+ Traits::BITS>(sample * volume);
+}
+
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static void
+pcm_volume_change(PcmDither &dither,
+ typename Traits::pointer_type dest,
+ typename Traits::const_pointer_type src,
+ size_t n,
+ int volume)
+{
+ for (size_t i = 0; i != n; ++i)
+ dest[i] = pcm_volume_sample<F, Traits>(dither, src[i], volume);
+}
+
+static void
+pcm_volume_change_8(PcmDither &dither,
+ int8_t *dest, const int8_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S8>(dither, dest, src, n, volume);
+}
+
+static void
+pcm_volume_change_16(PcmDither &dither,
+ int16_t *dest, const int16_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S16>(dither, dest, src, n, volume);
+}
+
+static void
+pcm_volume_change_24(PcmDither &dither,
+ int32_t *dest, const int32_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S24_P32>(dither, dest, src, n,
+ volume);
+}
+
+static void
+pcm_volume_change_32(PcmDither &dither,
+ int32_t *dest, const int32_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S32>(dither, dest, src, n, volume);
+}
+
+static void
+pcm_volume_change_float(float *dest, const float *src, size_t n,
+ float volume)
+{
+ for (size_t i = 0; i != n; ++i)
+ dest[i] = src[i] * volume;
+}
+
+bool
+PcmVolume::Open(SampleFormat _format, Error &error)
+{
+ assert(format == SampleFormat::UNDEFINED);
+
+ switch (_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ error.Format(pcm_domain,
+ "Software volume for %s is not implemented",
+ sample_format_to_string(_format));
+ return false;
+
+ case SampleFormat::S8:
+ case SampleFormat::S16:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ break;
+ }
+
+ format = _format;
+ return true;
+}
+
+ConstBuffer<void>
+PcmVolume::Apply(ConstBuffer<void> src)
+{
+ if (volume == PCM_VOLUME_1)
+ return src;
+
+ void *data = buffer.Get(src.size);
+
+ if (volume == 0) {
+ /* optimized special case: 0% volume = memset(0) */
+ /* TODO: is this valid for all sample formats? What
+ about floating point? */
+ memset(data, 0, src.size);
+ return { data, src.size };
+ }
+
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::DSD:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ pcm_volume_change_8(dither, (int8_t *)data,
+ (const int8_t *)src.data,
+ src.size / sizeof(int8_t),
+ volume);
+ break;
+
+ case SampleFormat::S16:
+ pcm_volume_change_16(dither, (int16_t *)data,
+ (const int16_t *)src.data,
+ src.size / sizeof(int16_t),
+ volume);
+ break;
+
+ case SampleFormat::S24_P32:
+ pcm_volume_change_24(dither, (int32_t *)data,
+ (const int32_t *)src.data,
+ src.size / sizeof(int32_t),
+ volume);
+ break;
+
+ case SampleFormat::S32:
+ pcm_volume_change_32(dither, (int32_t *)data,
+ (const int32_t *)src.data,
+ src.size / sizeof(int32_t),
+ volume);
+ break;
+
+ case SampleFormat::FLOAT:
+ pcm_volume_change_float((float *)data,
+ (const float *)src.data,
+ src.size / sizeof(float),
+ pcm_volume_to_float(volume));
+ break;
+ }
+
+ return { data, src.size };
+}
diff --git a/src/pcm/Volume.hxx b/src/pcm/Volume.hxx
new file mode 100644
index 000000000..6a6f6196d
--- /dev/null
+++ b/src/pcm/Volume.hxx
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2003-2013 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_PCM_VOLUME_HXX
+#define MPD_PCM_VOLUME_HXX
+
+#include "AudioFormat.hxx"
+#include "PcmBuffer.hxx"
+#include "PcmDither.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+class Error;
+template<typename T> struct ConstBuffer;
+
+/**
+ * Number of fractional bits for a fixed-point volume value.
+ */
+static constexpr unsigned PCM_VOLUME_BITS = 10;
+
+/**
+ * This value means "100% volume".
+ */
+static constexpr unsigned PCM_VOLUME_1 = 1024;
+static constexpr int PCM_VOLUME_1S = PCM_VOLUME_1;
+
+struct AudioFormat;
+
+/**
+ * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an
+ * integer volume value (1000 = 100%).
+ */
+static inline int
+pcm_float_to_volume(float volume)
+{
+ return volume * PCM_VOLUME_1 + 0.5;
+}
+
+static inline float
+pcm_volume_to_float(int volume)
+{
+ return (float)volume / (float)PCM_VOLUME_1;
+}
+
+/**
+ * A class that converts samples from one format to another.
+ */
+class PcmVolume {
+ SampleFormat format;
+
+ unsigned volume;
+
+ PcmBuffer buffer;
+ PcmDither dither;
+
+public:
+ PcmVolume()
+ :volume(PCM_VOLUME_1) {
+#ifndef NDEBUG
+ format = SampleFormat::UNDEFINED;
+#endif
+ }
+
+#ifndef NDEBUG
+ ~PcmVolume() {
+ assert(format == SampleFormat::UNDEFINED);
+ }
+#endif
+
+ unsigned GetVolume() const {
+ return volume;
+ }
+
+ /**
+ * @param _volume the volume level in the range
+ * [0..#PCM_VOLUME_1]; may be bigger than #PCM_VOLUME_1, but
+ * then it will most likely clip a lot
+ */
+ void SetVolume(unsigned _volume) {
+ volume = _volume;
+ }
+
+ /**
+ * Opens the object, prepare for Apply().
+ *
+ * @param format the sample format
+ * @param error location to store the error
+ * @return true on success
+ */
+ bool Open(SampleFormat format, Error &error);
+
+ /**
+ * Closes the object. After that, you may call Open() again.
+ */
+ void Close() {
+#ifndef NDEBUG
+ assert(format != SampleFormat::UNDEFINED);
+ format = SampleFormat::UNDEFINED;
+#endif
+ }
+
+ /**
+ * Apply the volume level.
+ */
+ gcc_pure
+ ConstBuffer<void> Apply(ConstBuffer<void> src);
+};
+
+#endif
diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx
index 94198b8c3..4e48dbf8d 100644
--- a/src/playlist/AsxPlaylistPlugin.cxx
+++ b/src/playlist/AsxPlaylistPlugin.cxx
@@ -21,21 +21,13 @@
#include "AsxPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/ASCII.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
+#include "Expat.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain asx_domain("asx");
-
/**
* This is the state object for the GLib XML parser.
*/
@@ -44,7 +36,7 @@ struct AsxParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
@@ -58,36 +50,23 @@ struct AsxParser {
* valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
* is no (known) tag.
*/
- TagType tag;
+ TagType tag_type;
/**
- * The current song. It is allocated after the "location"
- * element.
+ * The current song URI. It is set by the "ref" element.
*/
- Song *song;
+ std::string location;
+
+ TagBuilder tag_builder;
AsxParser()
:state(ROOT) {}
};
-static const gchar *
-get_attribute(const gchar **attribute_names, const gchar **attribute_values,
- const gchar *name)
-{
- for (unsigned i = 0; attribute_names[i] != nullptr; ++i)
- if (StringEqualsCaseASCII(attribute_names[i], name))
- return attribute_values[i];
-
- return nullptr;
-}
-
-static void
-asx_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+asx_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
{
AsxParser *parser = (AsxParser *)user_data;
@@ -95,48 +74,31 @@ asx_start_element(gcc_unused GMarkupParseContext *context,
case AsxParser::ROOT:
if (StringEqualsCaseASCII(element_name, "entry")) {
parser->state = AsxParser::ENTRY;
- parser->song = Song::NewRemote("asx:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
case AsxParser::ENTRY:
if (StringEqualsCaseASCII(element_name, "ref")) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "href");
- if (href != nullptr) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- Song *song = Song::NewRemote(href);
-
- if (parser->song != nullptr) {
- song->tag = parser->song->tag;
- parser->song->tag = nullptr;
- parser->song->Free();
- }
-
- parser->song = song;
- }
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "href");
+ if (href != nullptr)
+ parser->location = href;
} else if (StringEqualsCaseASCII(element_name, "author"))
/* is that correct? or should it be COMPOSER
or PERFORMER? */
- parser->tag = TAG_ARTIST;
+ parser->tag_type = TAG_ARTIST;
else if (StringEqualsCaseASCII(element_name, "title"))
- parser->tag = TAG_TITLE;
+ parser->tag_type = TAG_TITLE;
break;
}
}
-static void
-asx_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+asx_end_element(void *user_data, const XML_Char *element_name)
{
AsxParser *parser = (AsxParser *)user_data;
@@ -146,23 +108,20 @@ asx_end_element(gcc_unused GMarkupParseContext *context,
case AsxParser::ENTRY:
if (StringEqualsCaseASCII(element_name, "entry")) {
- if (strcmp(parser->song->uri, "asx:") != 0)
- parser->songs.emplace_front(parser->song);
- else
- parser->song->Free();
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
parser->state = AsxParser::ROOT;
} else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
break;
}
}
-static void
-asx_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+asx_char_data(void *user_data, const XML_Char *s, int len)
{
AsxParser *parser = (AsxParser *)user_data;
@@ -171,34 +130,13 @@ asx_text(gcc_unused GMarkupParseContext *context,
break;
case AsxParser::ENTRY:
- if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag,
- text, text_len);
- }
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
break;
}
}
-static const GMarkupParser asx_parser = {
- asx_start_element,
- asx_end_element,
- asx_text,
- nullptr,
- nullptr,
-};
-
-static void
-asx_parser_destroy(gpointer data)
-{
- AsxParser *parser = (AsxParser *)data;
-
- if (parser->state >= AsxParser::ENTRY)
- parser->song->Free();
-}
-
/*
* The playlist object
*
@@ -208,58 +146,21 @@ static SongEnumerator *
asx_open_stream(InputStream &is)
{
AsxParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the ASX XML file */
-
- context = g_markup_parse_context_new(&asx_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, asx_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatErrno(asx_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(asx_start_element, asx_end_element);
+ expat.SetCharacterDataHandler(asx_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
return nullptr;
}
}
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatErrno(asx_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
+ return new MemorySongEnumerator(std::move(parser.songs));
}
static const char *const asx_suffixes[] = {
diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx
index 42a43bbad..1da76d372 100644
--- a/src/playlist/CuePlaylistPlugin.cxx
+++ b/src/playlist/CuePlaylistPlugin.cxx
@@ -21,13 +21,10 @@
#include "CuePlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "Song.hxx"
#include "cue/CueParser.hxx"
#include "TextInputStream.hxx"
-#include <assert.h>
-#include <string.h>
+#include <string>
class CuePlaylist final : public SongEnumerator {
InputStream &is;
@@ -39,7 +36,7 @@ class CuePlaylist final : public SongEnumerator {
:is(_is), tis(is) {
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
@@ -48,10 +45,10 @@ cue_playlist_open_stream(InputStream &is)
return new CuePlaylist(is);
}
-Song *
+DetachedSong *
CuePlaylist::NextSong()
{
- Song *song = parser.Get();
+ DetachedSong *song = parser.Get();
if (song != nullptr)
return song;
diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx
index a1a865c08..d73c7fe72 100644
--- a/src/playlist/DespotifyPlaylistPlugin.cxx
+++ b/src/playlist/DespotifyPlaylistPlugin.cxx
@@ -23,7 +23,7 @@
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
#include "tag/Tag.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Log.hxx"
extern "C" {
@@ -34,10 +34,9 @@ extern "C" {
#include <stdlib.h>
static void
-add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
+add_song(std::forward_list<DetachedSong> &songs, ds_track &track)
{
const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
- Song *song;
char uri[128];
char *ds_uri;
@@ -45,35 +44,32 @@ add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
ds_uri = uri + strlen(dsp_scheme) + 3;
- if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
+ if (despotify_track_to_uri(&track, ds_uri) != ds_uri) {
/* Should never really fail, but let's be sure */
FormatDebug(despotify_domain,
- "Can't add track %s", track->title);
+ "Can't add track %s", track.title);
return;
}
- song = Song::NewRemote(uri);
- song->tag = mpd_despotify_tag_from_track(track);
-
- songs.emplace_front(song);
+ songs.emplace_front(uri, mpd_despotify_tag_from_track(track));
}
static bool
parse_track(struct despotify_session *session,
- std::forward_list<SongPointer> &songs,
+ std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
struct ds_track *track = despotify_link_get_track(session, link);
if (track == nullptr)
return false;
- add_song(songs, track);
+ add_song(songs, *track);
return true;
}
static bool
parse_playlist(struct despotify_session *session,
- std::forward_list<SongPointer> &songs,
+ std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
ds_playlist *playlist = despotify_link_get_playlist(session, link);
@@ -82,7 +78,7 @@ parse_playlist(struct despotify_session *session,
for (ds_track *track = playlist->tracks; track != nullptr;
track = track->next)
- add_song(songs, track);
+ add_song(songs, *track);
return true;
}
@@ -103,7 +99,7 @@ despotify_playlist_open_uri(const char *url,
return nullptr;
}
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
bool parse_result;
switch (link->type) {
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
index d758650eb..77df51778 100644
--- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx
+++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
@@ -27,18 +27,16 @@
#include "EmbeddedCuePlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagId3.hxx"
#include "tag/ApeTag.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "TagFile.hxx"
#include "cue/CueParser.hxx"
#include "fs/Traits.hxx"
#include "fs/AllocatedPath.hxx"
#include "util/ASCII.hxx"
-#include <assert.h>
#include <string.h>
class EmbeddedCuePlaylist final : public SongEnumerator {
@@ -71,7 +69,7 @@ public:
delete parser;
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static void
@@ -95,7 +93,7 @@ embcue_playlist_open_uri(const char *uri,
gcc_unused Mutex &mutex,
gcc_unused Cond &cond)
{
- if (!PathTraits::IsAbsoluteUTF8(uri))
+ if (!PathTraitsUTF8::IsAbsolute(uri))
/* only local files supported */
return nullptr;
@@ -105,7 +103,7 @@ embcue_playlist_open_uri(const char *uri,
const auto playlist = new EmbeddedCuePlaylist();
- tag_file_scan(path_fs, &embcue_tag_handler, playlist);
+ tag_file_scan(path_fs, embcue_tag_handler, playlist);
if (playlist->cuesheet.empty()) {
tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
if (playlist->cuesheet.empty())
@@ -118,7 +116,7 @@ embcue_playlist_open_uri(const char *uri,
return nullptr;
}
- playlist->filename = PathTraits::GetBaseUTF8(uri);
+ playlist->filename = PathTraitsUTF8::GetBase(uri);
playlist->next = &playlist->cuesheet[0];
playlist->parser = new CueParser();
@@ -126,10 +124,10 @@ embcue_playlist_open_uri(const char *uri,
return playlist;
}
-Song *
+DetachedSong *
EmbeddedCuePlaylist::NextSong()
{
- Song *song = parser->Get();
+ DetachedSong *song = parser->Get();
if (song != nullptr)
return song;
@@ -147,14 +145,16 @@ EmbeddedCuePlaylist::NextSong()
parser->Feed(line);
song = parser->Get();
- if (song != nullptr)
- return song->ReplaceURI(filename.c_str());
+ if (song != nullptr) {
+ song->SetURI(filename);
+ return song;
+ }
}
parser->Finish();
song = parser->Get();
if (song != nullptr)
- song = song->ReplaceURI(filename.c_str());
+ song->SetURI(filename);
return song;
}
diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx
index 8d260fec7..51211988c 100644
--- a/src/playlist/ExtM3uPlaylistPlugin.cxx
+++ b/src/playlist/ExtM3uPlaylistPlugin.cxx
@@ -21,13 +21,12 @@
#include "ExtM3uPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "SongEnumerator.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
#include "TextInputStream.hxx"
-#include <glib.h>
-
#include <string.h>
#include <stdlib.h>
@@ -45,7 +44,7 @@ public:
strcmp(line.c_str(), "#EXTM3U") == 0;
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
@@ -74,7 +73,6 @@ extm3u_parse_tag(const char *line)
long duration;
char *endptr;
const char *name;
- Tag *tag;
duration = strtol(line, &endptr, 10);
if (endptr[0] != ',')
@@ -91,35 +89,34 @@ extm3u_parse_tag(const char *line)
object */
return NULL;
- tag = new Tag();
- tag->time = duration;
+ TagBuilder tag;
+ tag.SetTime(duration);
/* unfortunately, there is no real specification for the
EXTM3U format, so we must assume that the string after the
comma is opaque, and is just the song name*/
if (*name != 0)
- tag->AddItem(TAG_NAME, name);
+ tag.AddItem(TAG_NAME, name);
- return tag;
+ return tag.CommitNew();
}
-Song *
+DetachedSong *
ExtM3uPlaylist::NextSong()
{
Tag *tag = NULL;
std::string line;
const char *line_s;
- Song *song;
do {
if (!tis.ReadLine(line)) {
delete tag;
return NULL;
}
-
+
line_s = line.c_str();
- if (g_str_has_prefix(line_s, "#EXTINF:")) {
+ if (StringStartsWith(line_s, "#EXTINF:")) {
delete tag;
tag = extm3u_parse_tag(line_s + 8);
continue;
@@ -128,8 +125,8 @@ ExtM3uPlaylist::NextSong()
line_s = strchug_fast(line_s);
} while (line_s[0] == '#' || *line_s == 0);
- song = Song::NewRemote(line_s);
- song->tag = tag;
+ DetachedSong *song = new DetachedSong(line_s, std::move(*tag));
+ delete tag;
return song;
}
diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx
index 3f99bdfdf..09f2c91cb 100644
--- a/src/playlist/M3uPlaylistPlugin.cxx
+++ b/src/playlist/M3uPlaylistPlugin.cxx
@@ -21,7 +21,7 @@
#include "M3uPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "SongEnumerator.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "util/StringUtil.hxx"
#include "TextInputStream.hxx"
@@ -33,7 +33,7 @@ public:
:tis(is) {
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
@@ -42,7 +42,7 @@ m3u_open_stream(InputStream &is)
return new M3uPlaylist(is);
}
-Song *
+DetachedSong *
M3uPlaylist::NextSong()
{
std::string line;
@@ -56,7 +56,7 @@ M3uPlaylist::NextSong()
line_s = strchug_fast(line_s);
} while (line_s[0] == '#' || *line_s == 0);
- return Song::NewRemote(line_s);
+ return new DetachedSong(line_s);
}
static const char *const m3u_suffixes[] = {
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx
index 99be3ad35..103451dfe 100644
--- a/src/playlist/PlsPlaylistPlugin.cxx
+++ b/src/playlist/PlsPlaylistPlugin.cxx
@@ -22,8 +22,8 @@
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "DetachedSong.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -32,13 +32,14 @@
#include <string>
+#include <stdio.h>
+
static constexpr Domain pls_domain("pls");
static void
-pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
+pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
{
gchar *value;
- int length;
GError *error = nullptr;
int num_entries = g_key_file_get_integer(keyfile, "playlist",
"NumberOfEntries", &error);
@@ -57,13 +58,11 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
}
}
- while (num_entries > 0) {
- Song *song;
-
+ for (; num_entries > 0; --num_entries) {
char key[64];
sprintf(key, "File%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
+ char *uri = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
if(error) {
FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
key, error->message);
@@ -71,36 +70,24 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
return;
}
- song = Song::NewRemote(value);
- g_free(value);
+ TagBuilder tag;
sprintf(key, "Title%u", num_entries);
value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error == nullptr && value){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->AddItem(TAG_TITLE, value);
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
+ nullptr);
+ if (value != nullptr)
+ tag.AddItem(TAG_TITLE, value);
+
g_free(value);
sprintf(key, "Length%u", num_entries);
- length = g_key_file_get_integer(keyfile, "playlist", key,
- &error);
- if(error == nullptr && length > 0){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->time = length;
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
+ int length = g_key_file_get_integer(keyfile, "playlist", key,
+ nullptr);
+ if (length > 0)
+ tag.SetTime(length);
- songs.emplace_front(song);
- num_entries--;
+ songs.emplace_front(uri, tag.Commit());
+ g_free(uri);
}
}
@@ -110,15 +97,12 @@ pls_open_stream(InputStream &is)
{
GError *error = nullptr;
Error error2;
- size_t nbytes;
- char buffer[1024];
- bool success;
- GKeyFile *keyfile;
std::string kf_data;
do {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
+ char buffer[1024];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
if (nbytes == 0) {
if (error2.IsDefined()) {
LogError(error2);
@@ -137,12 +121,10 @@ pls_open_stream(InputStream &is)
return nullptr;
}
- keyfile = g_key_file_new();
- success = g_key_file_load_from_data(keyfile,
- kf_data.data(), kf_data.length(),
- G_KEY_FILE_NONE, &error);
-
- if (!success) {
+ 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);
@@ -150,7 +132,7 @@ pls_open_stream(InputStream &is)
return nullptr;
}
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
pls_parser(keyfile, songs);
g_key_file_free(keyfile);
diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
index e2a44bfd3..604f99baf 100644
--- a/src/playlist/RssPlaylistPlugin.cxx
+++ b/src/playlist/RssPlaylistPlugin.cxx
@@ -21,21 +21,13 @@
#include "RssPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/ASCII.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
+#include "Expat.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain rss_domain("rss");
-
/**
* This is the state object for the GLib XML parser.
*/
@@ -44,7 +36,7 @@ struct RssParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
@@ -58,35 +50,23 @@ struct RssParser {
* valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
* is no (known) tag.
*/
- TagType tag;
+ TagType tag_type;
/**
- * The current song. It is allocated after the "location"
+ * The current song URI. It is set by the "enclosure"
* element.
*/
- Song *song;
+ std::string location;
+
+ TagBuilder tag_builder;
RssParser()
:state(ROOT) {}
};
-static const gchar *
-get_attribute(const gchar **attribute_names, const gchar **attribute_values,
- const gchar *name)
-{
- for (unsigned i = 0; attribute_names[i] != nullptr; ++i)
- if (StringEqualsCaseASCII(attribute_names[i], name))
- return attribute_values[i];
-
- return nullptr;
-}
-
-static void
-rss_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+rss_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
{
RssParser *parser = (RssParser *)user_data;
@@ -94,46 +74,29 @@ rss_start_element(gcc_unused GMarkupParseContext *context,
case RssParser::ROOT:
if (StringEqualsCaseASCII(element_name, "item")) {
parser->state = RssParser::ITEM;
- parser->song = Song::NewRemote("rss:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
case RssParser::ITEM:
if (StringEqualsCaseASCII(element_name, "enclosure")) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "url");
- if (href != nullptr) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- Song *song = Song::NewRemote(href);
-
- if (parser->song != nullptr) {
- song->tag = parser->song->tag;
- parser->song->tag = nullptr;
- parser->song->Free();
- }
-
- parser->song = song;
- }
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "url");
+ if (href != nullptr)
+ parser->location = href;
} else if (StringEqualsCaseASCII(element_name, "title"))
- parser->tag = TAG_TITLE;
+ parser->tag_type = TAG_TITLE;
else if (StringEqualsCaseASCII(element_name, "itunes:author"))
- parser->tag = TAG_ARTIST;
+ parser->tag_type = TAG_ARTIST;
break;
}
}
-static void
-rss_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+rss_end_element(void *user_data, const XML_Char *element_name)
{
RssParser *parser = (RssParser *)user_data;
@@ -143,23 +106,20 @@ rss_end_element(gcc_unused GMarkupParseContext *context,
case RssParser::ITEM:
if (StringEqualsCaseASCII(element_name, "item")) {
- if (strcmp(parser->song->uri, "rss:") != 0)
- parser->songs.emplace_front(parser->song);
- else
- parser->song->Free();
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
parser->state = RssParser::ROOT;
} else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
break;
}
}
-static void
-rss_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+rss_char_data(void *user_data, const XML_Char *s, int len)
{
RssParser *parser = (RssParser *)user_data;
@@ -168,34 +128,13 @@ rss_text(gcc_unused GMarkupParseContext *context,
break;
case RssParser::ITEM:
- if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag,
- text, text_len);
- }
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
break;
}
}
-static const GMarkupParser rss_parser = {
- rss_start_element,
- rss_end_element,
- rss_text,
- nullptr,
- nullptr,
-};
-
-static void
-rss_parser_destroy(gpointer data)
-{
- RssParser *parser = (RssParser *)data;
-
- if (parser->state >= RssParser::ITEM)
- parser->song->Free();
-}
-
/*
* The playlist object
*
@@ -205,58 +144,21 @@ static SongEnumerator *
rss_open_stream(InputStream &is)
{
RssParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the RSS XML file */
-
- context = g_markup_parse_context_new(&rss_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, rss_parser_destroy);
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(rss_start_element, rss_end_element);
+ expat.SetCharacterDataHandler(rss_char_data);
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
return nullptr;
}
}
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
+ return new MemorySongEnumerator(std::move(parser.songs));
}
static const char *const rss_suffixes[] = {
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx
index f6797b14d..50a9cb214 100644
--- a/src/playlist/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/SoundCloudPlaylistPlugin.cxx
@@ -24,7 +24,8 @@
#include "ConfigData.hxx"
#include "InputStream.hxx"
#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -45,7 +46,8 @@ static constexpr Domain soundcloud_domain("soundcloud");
static bool
soundcloud_init(const config_param &param)
{
- soundcloud_config.apikey = param.GetBlockValue("apikey", "");
+ // APIKEY for MPD application, registered under DarkFox' account.
+ soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8");
if (soundcloud_config.apikey.empty()) {
LogDebug(soundcloud_domain,
"disabling the soundcloud playlist plugin "
@@ -62,19 +64,20 @@ soundcloud_init(const config_param &param)
* @return Constructed URL. Must be freed with g_free.
*/
static char *
-soundcloud_resolve(const char* uri) {
+soundcloud_resolve(const char* uri)
+{
char *u, *ru;
- if (g_str_has_prefix(uri, "http://")) {
+ if (StringStartsWith(uri, "https://")) {
u = g_strdup(uri);
- } else if (g_str_has_prefix(uri, "soundcloud.com")) {
- u = g_strconcat("http://", uri, nullptr);
+ } else if (StringStartsWith(uri, "soundcloud.com")) {
+ u = g_strconcat("https://", uri, nullptr);
} else {
/* assume it's just a path on soundcloud.com */
- u = g_strconcat("http://soundcloud.com/", uri, nullptr);
+ u = g_strconcat("https://soundcloud.com/", uri, nullptr);
}
- ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
+ ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
u, "&client_id=",
soundcloud_config.apikey.c_str(), nullptr);
g_free(u);
@@ -105,15 +108,16 @@ struct parse_data {
char* title;
int got_url; /* nesting level of last stream_url */
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
};
-static int handle_integer(void *ctx,
- long
+static int
+handle_integer(void *ctx,
+ long
#ifndef HAVE_YAJL1
- long
+ long
#endif
- intval)
+ intval)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -128,26 +132,25 @@ static int handle_integer(void *ctx,
return 1;
}
-static int handle_string(void *ctx, const unsigned char* stringval,
+static int
+handle_string(void *ctx, const unsigned char* stringval,
#ifdef HAVE_YAJL1
- unsigned int
+ unsigned int
#else
- size_t
+ size_t
#endif
- stringlen)
+ stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
const char *s = (const char *) stringval;
switch (data->key) {
case Title:
- if (data->title != nullptr)
- g_free(data->title);
+ g_free(data->title);
data->title = g_strndup(s, stringlen);
break;
case Stream_URL:
- if (data->stream_url != nullptr)
- g_free(data->stream_url);
+ g_free(data->stream_url);
data->stream_url = g_strndup(s, stringlen);
data->got_url = 1;
break;
@@ -158,13 +161,14 @@ static int handle_string(void *ctx, const unsigned char* stringval,
return 1;
}
-static int handle_mapkey(void *ctx, const unsigned char* stringval,
+static int
+handle_mapkey(void *ctx, const unsigned char* stringval,
#ifdef HAVE_YAJL1
- unsigned int
+ unsigned int
#else
- size_t
+ size_t
#endif
- stringlen)
+ stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -181,7 +185,8 @@ static int handle_mapkey(void *ctx, const unsigned char* stringval,
return 1;
}
-static int handle_start_map(void *ctx)
+static int
+handle_start_map(void *ctx)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -191,7 +196,8 @@ static int handle_start_map(void *ctx)
return 1;
}
-static int handle_end_map(void *ctx)
+static int
+handle_end_map(void *ctx)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -206,21 +212,16 @@ static int handle_end_map(void *ctx)
/* got_url == 1, track finished, make it into a song */
data->got_url = 0;
- Song *s;
- char *u;
+ char *u = g_strconcat(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
- u = g_strconcat(data->stream_url, "?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- s = Song::NewRemote(u);
- g_free(u);
-
- Tag *t = new Tag();
- t->time = data->duration / 1000;
+ TagBuilder tag;
+ tag.SetTime(data->duration / 1000);
if (data->title != nullptr)
- t->AddItem(TAG_NAME, data->title);
- s->tag = t;
+ tag.AddItem(TAG_NAME, data->title);
- data->songs.emplace_front(s);
+ data->songs.emplace_front(u, tag.Commit());
+ g_free(u);
return 1;
}
@@ -249,12 +250,9 @@ static int
soundcloud_parse_json(const char *url, yajl_handle hand,
Mutex &mutex, Cond &cond)
{
- char buffer[4096];
- unsigned char *ubuffer = (unsigned char *)buffer;
-
Error error;
- InputStream *input_stream = InputStream::Open(url, mutex, cond,
- error);
+ InputStream *input_stream = InputStream::OpenReady(url, mutex, cond,
+ error);
if (input_stream == nullptr) {
if (error.IsDefined())
LogError(error);
@@ -262,12 +260,13 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
}
mutex.lock();
- input_stream->WaitReady();
yajl_status stat;
int done = 0;
while (!done) {
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
const size_t nbytes =
input_stream->Read(buffer, sizeof(buffer), error);
if (nbytes == 0) {
@@ -318,80 +317,62 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
* soundcloud://playlist/<playlist-id>
* soundcloud://url/<url or path of soundcloud page>
*/
-
static SongEnumerator *
soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
{
- char *s, *p;
- char *scheme, *arg, *rest;
- s = g_strdup(uri);
- scheme = s;
- for (p = s; *p; p++) {
- if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') {
- *p = 0;
- p += 3;
- break;
- }
- }
- arg = p;
- for (; *p; p++) {
- if (*p == '/') {
- *p = 0;
- p++;
- break;
- }
- }
- rest = p;
-
- if (strcmp(scheme, "soundcloud") != 0) {
- FormatWarning(soundcloud_domain,
- "incompatible scheme for soundcloud plugin: %s",
- scheme);
- g_free(s);
- return nullptr;
- }
+ assert(memcmp(uri, "soundcloud://", 13) == 0);
+ uri += 13;
char *u = nullptr;
- if (strcmp(arg, "track") == 0) {
- u = g_strconcat("http://api.soundcloud.com/tracks/",
+ 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);
- } else if (strcmp(arg, "playlist") == 0) {
- u = g_strconcat("http://api.soundcloud.com/playlists/",
+ } 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);
- } else if (strcmp(arg, "url") == 0) {
+ } 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);
+ } 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);
+ } else if (memcmp(uri, "url/", 4) == 0) {
+ const char *rest = uri + 4;
/* Translate to soundcloud resolver call. libcurl will automatically
follow the redirect to the right resource. */
u = soundcloud_resolve(rest);
}
- g_free(s);
if (u == nullptr) {
LogWarning(soundcloud_domain, "unknown soundcloud URI");
return nullptr;
}
- yajl_handle hand;
struct parse_data data;
-
data.got_url = 0;
data.title = nullptr;
data.stream_url = nullptr;
#ifdef HAVE_YAJL1
- hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, (void *) &data);
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
+ &data);
#else
- hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data);
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
#endif
int ret = soundcloud_parse_json(u, hand, mutex, cond);
g_free(u);
yajl_free(hand);
- if (data.title != nullptr)
- g_free(data.title);
- if (data.stream_url != nullptr)
- g_free(data.stream_url);
+ g_free(data.title);
+ g_free(data.stream_url);
if (ret == -1)
return nullptr;
diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx
index dcfab5a80..2157dd678 100644
--- a/src/playlist/XspfPlaylistPlugin.cxx
+++ b/src/playlist/XspfPlaylistPlugin.cxx
@@ -21,15 +21,14 @@
#include "XspfPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
+#include "DetachedSong.hxx"
#include "InputStream.hxx"
-#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "Expat.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
#include <string.h>
static constexpr Domain xspf_domain("xspf");
@@ -42,7 +41,7 @@ struct XspfParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
@@ -57,24 +56,22 @@ struct XspfParser {
* valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
* is no (known) tag.
*/
- TagType tag;
+ TagType tag_type;
/**
- * The current song. It is allocated after the "location"
- * element.
+ * The current song URI. It is set by the "location" element.
*/
- Song *song;
+ std::string location;
+
+ TagBuilder tag_builder;
XspfParser()
:state(ROOT) {}
};
-static void
-xspf_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gcc_unused const gchar **attribute_names,
- gcc_unused const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+xspf_start_element(void *user_data, const XML_Char *element_name,
+ gcc_unused const XML_Char **atts)
{
XspfParser *parser = (XspfParser *)user_data;
@@ -94,8 +91,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
case XspfParser::TRACKLIST:
if (strcmp(element_name, "track") == 0) {
parser->state = XspfParser::TRACK;
- parser->song = nullptr;
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
@@ -104,17 +101,17 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
if (strcmp(element_name, "location") == 0)
parser->state = XspfParser::LOCATION;
else if (strcmp(element_name, "title") == 0)
- parser->tag = TAG_TITLE;
+ parser->tag_type = TAG_TITLE;
else if (strcmp(element_name, "creator") == 0)
/* TAG_COMPOSER would be more correct
according to the XSPF spec */
- parser->tag = TAG_ARTIST;
+ parser->tag_type = TAG_ARTIST;
else if (strcmp(element_name, "annotation") == 0)
- parser->tag = TAG_COMMENT;
+ parser->tag_type = TAG_COMMENT;
else if (strcmp(element_name, "album") == 0)
- parser->tag = TAG_ALBUM;
+ parser->tag_type = TAG_ALBUM;
else if (strcmp(element_name, "trackNum") == 0)
- parser->tag = TAG_TRACK;
+ parser->tag_type = TAG_TRACK;
break;
@@ -123,10 +120,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
}
}
-static void
-xspf_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+xspf_end_element(void *user_data, const XML_Char *element_name)
{
XspfParser *parser = (XspfParser *)user_data;
@@ -148,12 +143,13 @@ xspf_end_element(gcc_unused GMarkupParseContext *context,
case XspfParser::TRACK:
if (strcmp(element_name, "track") == 0) {
- if (parser->song != nullptr)
- parser->songs.emplace_front(parser->song);
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
parser->state = XspfParser::TRACKLIST;
} else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
break;
@@ -163,10 +159,8 @@ xspf_end_element(gcc_unused GMarkupParseContext *context,
}
}
-static void
-xspf_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+xspf_char_data(void *user_data, const XML_Char *s, int len)
{
XspfParser *parser = (XspfParser *)user_data;
@@ -177,43 +171,19 @@ xspf_text(gcc_unused GMarkupParseContext *context,
break;
case XspfParser::TRACK:
- if (parser->song != nullptr &&
- parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag, text, text_len);
- }
+ if (!parser->location.empty() &&
+ parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
break;
case XspfParser::LOCATION:
- if (parser->song == nullptr) {
- char *uri = g_strndup(text, text_len);
- parser->song = Song::NewRemote(uri);
- g_free(uri);
- }
+ parser->location.assign(s, len);
break;
}
}
-static const GMarkupParser xspf_parser = {
- xspf_start_element,
- xspf_end_element,
- xspf_text,
- nullptr,
- nullptr,
-};
-
-static void
-xspf_parser_destroy(gpointer data)
-{
- XspfParser *parser = (XspfParser *)data;
-
- if (parser->state >= XspfParser::TRACK && parser->song != nullptr)
- parser->song->Free();
-}
-
/*
* The playlist object
*
@@ -223,58 +193,21 @@ static SongEnumerator *
xspf_open_stream(InputStream &is)
{
XspfParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the XSPF XML file */
-
- context = g_markup_parse_context_new(&xspf_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, xspf_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(xspf_start_element, xspf_end_element);
+ expat.SetCharacterDataHandler(xspf_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
return nullptr;
}
}
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
+ return new MemorySongEnumerator(std::move(parser.songs));
}
static const char *const xspf_suffixes[] = {
diff --git a/src/system/Clock.cxx b/src/system/Clock.cxx
index 347997a44..4d89b9b89 100644
--- a/src/system/Clock.cxx
+++ b/src/system/Clock.cxx
@@ -31,6 +31,28 @@
#endif
unsigned
+MonotonicClockS(void)
+{
+#ifdef WIN32
+ return GetTickCount() / 1000;
+#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
+ static mach_timebase_info_data_t base;
+ if (base.denom == 0)
+ (void)mach_timebase_info(&base);
+
+ return (unsigned)((mach_absolute_time() * base.numer / 1000)
+ / (1000000 * base.denom));
+#elif defined(CLOCK_MONOTONIC)
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec;
+#else
+ /* we have no monotonic clock, fall back to time() */
+ return time(nullptr);
+#endif
+}
+
+unsigned
MonotonicClockMS(void)
{
#ifdef WIN32
@@ -96,3 +118,29 @@ MonotonicClockUS(void)
#endif
}
+#ifdef WIN32
+
+gcc_const
+static unsigned
+DeltaFileTimeS(FILETIME a, FILETIME b)
+{
+ ULARGE_INTEGER a2, b2;
+ b2.LowPart = b.dwLowDateTime;
+ b2.HighPart = b.dwHighDateTime;
+ a2.LowPart = a.dwLowDateTime;
+ a2.HighPart = a.dwHighDateTime;
+ return (a2.QuadPart - b2.QuadPart) / 10000000;
+}
+
+unsigned
+GetProcessUptimeS()
+{
+ FILETIME creation_time, exit_time, kernel_time, user_time, now;
+ GetProcessTimes(GetCurrentProcess(), &creation_time,
+ &exit_time, &kernel_time, &user_time);
+ GetSystemTimeAsFileTime(&now);
+
+ return DeltaFileTimeS(now, creation_time);
+}
+
+#endif
diff --git a/src/system/Clock.hxx b/src/system/Clock.hxx
index 7be1127bf..1c3651a99 100644
--- a/src/system/Clock.hxx
+++ b/src/system/Clock.hxx
@@ -25,6 +25,13 @@
#include <stdint.h>
/**
+ * Returns the value of a monotonic clock in seconds.
+ */
+gcc_pure
+unsigned
+MonotonicClockS();
+
+/**
* Returns the value of a monotonic clock in milliseconds.
*/
gcc_pure
@@ -38,4 +45,15 @@ gcc_pure
uint64_t
MonotonicClockUS();
+#ifdef WIN32
+
+/**
+ * Returns the uptime of the current process in seconds.
+ */
+gcc_pure
+unsigned
+GetProcessUptimeS();
+
+#endif
+
#endif
diff --git a/src/system/EPollFD.hxx b/src/system/EPollFD.hxx
index 41f7ec377..a6ea51d18 100644
--- a/src/system/EPollFD.hxx
+++ b/src/system/EPollFD.hxx
@@ -23,6 +23,7 @@
#include <assert.h>
#include <sys/epoll.h>
#include <unistd.h>
+#include <stdint.h>
#include "check.h"
diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx
index f02b4b581..b54a677f2 100644
--- a/src/system/FatalError.cxx
+++ b/src/system/FatalError.cxx
@@ -23,12 +23,15 @@
#include "util/Domain.hxx"
#include "LogV.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#ifdef WIN32
#include <windows.h>
@@ -75,12 +78,16 @@ FatalError(const char *msg, const Error &error)
FormatFatalError("%s: %s", msg, error.GetMessage());
}
+#ifdef HAVE_GLIB
+
void
FatalError(const char *msg, GError *error)
{
FormatFatalError("%s: %s", msg, error->message);
}
+#endif
+
void
FatalSystemError(const char *msg)
{
@@ -88,7 +95,7 @@ FatalSystemError(const char *msg)
#ifdef WIN32
system_error = g_win32_error_message(GetLastError());
#else
- system_error = g_strerror(errno);
+ system_error = strerror(errno);
#endif
FormatError(fatal_error_domain, "%s: %s", msg, system_error);
diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx
index 2845359ef..4ef290714 100644
--- a/src/system/FatalError.hxx
+++ b/src/system/FatalError.hxx
@@ -45,9 +45,11 @@ gcc_noreturn
void
FatalError(const char *msg, const Error &error);
+#ifdef HAVE_GLIB
gcc_noreturn
void
FatalError(const char *msg, GError *error);
+#endif
/**
* Call this after a system call has failed that is not supposed to
diff --git a/src/system/PeriodClock.hxx b/src/system/PeriodClock.hxx
new file mode 100644
index 000000000..61f52933f
--- /dev/null
+++ b/src/system/PeriodClock.hxx
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2003-2013 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_PERIOD_CLOCK_HXX
+#define MPD_PERIOD_CLOCK_HXX
+
+#include "Clock.hxx"
+
+/**
+ * This is a stopwatch which saves the timestamp of an event, and can
+ * check whether a specified time span has passed since then.
+ */
+class PeriodClock {
+protected:
+ typedef unsigned Stamp;
+
+private:
+ Stamp last;
+
+public:
+ /**
+ * Initializes the object, setting the last time stamp to "0",
+ * i.e. a Check() will always succeed. If you do not want this
+ * default behaviour, call Update() immediately after creating the
+ * object.
+ */
+ constexpr
+ PeriodClock():last(0) {}
+
+protected:
+ static Stamp GetNow() {
+ return MonotonicClockMS();
+ }
+
+ constexpr int Elapsed(Stamp now) const {
+ return last == 0
+ ? -1
+ : now - last;
+ }
+
+ constexpr bool Check(Stamp now, unsigned duration) const {
+ return now >= last + duration;
+ }
+
+ void Update(Stamp now) {
+ last = now;
+ }
+
+public:
+ bool IsDefined() const {
+ return last != 0;
+ }
+
+ /**
+ * Resets the clock.
+ */
+ void Reset() {
+ last = 0;
+ }
+
+ /**
+ * Returns the number of milliseconds elapsed since the last
+ * update(). Returns -1 if update() was never called.
+ */
+ int Elapsed() const {
+ return Elapsed(GetNow());
+ }
+
+ /**
+ * Combines a call to Elapsed() and Update().
+ */
+ int ElapsedUpdate() {
+ const auto now = GetNow();
+ int result = Elapsed(now);
+ Update(now);
+ return result;
+ }
+
+ /**
+ * Checks whether the specified duration has passed since the last
+ * update.
+ *
+ * @param duration the duration in milliseconds
+ */
+ bool Check(unsigned duration) const {
+ return Check(GetNow(), duration);
+ }
+
+ /**
+ * Updates the time stamp, setting it to the current clock.
+ */
+ void Update() {
+ Update(GetNow());
+ }
+
+ /**
+ * Updates the time stamp, setting it to the current clock plus the
+ * specified offset.
+ */
+ void UpdateWithOffset(int offset) {
+ Update(GetNow() + offset);
+ }
+
+ /**
+ * Checks whether the specified duration has passed since the last
+ * update. If yes, it updates the time stamp.
+ *
+ * @param duration the duration in milliseconds
+ */
+ bool CheckUpdate(unsigned duration) {
+ Stamp now = GetNow();
+ if (Check(now, duration)) {
+ Update(now);
+ return true;
+ } else
+ return false;
+ }
+
+ /**
+ * Checks whether the specified duration has passed since the last
+ * update. After that, it updates the time stamp.
+ *
+ * @param duration the duration in milliseconds
+ */
+ bool CheckAlwaysUpdate(unsigned duration) {
+ Stamp now = GetNow();
+ bool ret = Check(now, duration);
+ Update(now);
+ return ret;
+ }
+};
+
+#endif
diff --git a/src/system/Resolver.cxx b/src/system/Resolver.cxx
index 5e6ea590b..0cfb754ec 100644
--- a/src/system/Resolver.cxx
+++ b/src/system/Resolver.cxx
@@ -22,11 +22,12 @@
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
-
#ifndef WIN32
#include <sys/socket.h>
#include <netdb.h>
+#ifdef HAVE_TCP
+#include <netinet/in.h>
+#endif
#else
#include <ws2tcpip.h>
#include <winsock.h>
@@ -41,17 +42,17 @@
const Domain resolver_domain("resolver");
-char *
-sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error)
+std::string
+sockaddr_to_string(const struct sockaddr *sa, size_t length)
{
#ifdef HAVE_UN
if (sa->sa_family == AF_UNIX) {
/* return path of UNIX domain sockets */
const sockaddr_un &s_un = *(const sockaddr_un *)sa;
if (length < sizeof(s_un) || s_un.sun_path[0] == 0)
- return g_strdup("local");
+ return "local";
- return g_strdup(s_un.sun_path);
+ return s_un.sun_path;
}
#endif
@@ -80,17 +81,23 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error)
ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv),
NI_NUMERICHOST|NI_NUMERICSERV);
- if (ret != 0) {
- error.Set(resolver_domain, ret, gai_strerror(ret));
- return NULL;
- }
+ if (ret != 0)
+ return "unknown";
#ifdef HAVE_IPV6
- if (strchr(host, ':') != NULL)
- return g_strconcat("[", host, "]:", serv, NULL);
+ if (strchr(host, ':') != NULL) {
+ std::string result("[");
+ result.append(host);
+ result.append("]:");
+ result.append(serv);
+ return result;
+ }
#endif
- return g_strconcat(host, ":", serv, NULL);
+ std::string result(host);
+ result.push_back(':');
+ result.append(serv);
+ return result;
}
struct addrinfo *
@@ -98,19 +105,19 @@ resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
Error &error)
{
- char *p = g_strdup(host_port);
- const char *host = p, *port = NULL;
+ std::string p(host_port);
+ const char *host = p.c_str(), *port = nullptr;
if (host_port[0] == '[') {
/* IPv6 needs enclosing square braces, to
differentiate between IP colons and the port
separator */
- char *q = strchr(p + 1, ']');
- if (q != NULL && q[1] == ':' && q[2] != 0) {
- *q = 0;
+ size_t q = p.find(']', 1);
+ if (q != p.npos && p[q + 1] == ':' && p[q + 2] != 0) {
+ p[q] = 0;
+ port = host + q + 2;
++host;
- port = q + 2;
}
}
@@ -118,10 +125,11 @@ resolve_host_port(const char *host_port, unsigned default_port,
/* port is after the colon, but only if it's the only
colon (don't split IPv6 addresses) */
- char *q = strchr(p, ':');
- if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) {
- *q = 0;
- port = q + 1;
+ auto q = p.find(':');
+ if (q != p.npos && p[q + 1] != 0 &&
+ p.find(':', q + 1) == p.npos) {
+ p[q] = 0;
+ port = host + q + 1;
}
}
@@ -142,7 +150,6 @@ resolve_host_port(const char *host_port, unsigned default_port,
struct addrinfo *ai;
int ret = getaddrinfo(host, port, &hints, &ai);
- g_free(p);
if (ret != 0) {
error.Format(resolver_domain, ret,
"Failed to look up '%s': %s",
diff --git a/src/system/Resolver.hxx b/src/system/Resolver.hxx
index 62ef455a1..044d3f96a 100644
--- a/src/system/Resolver.hxx
+++ b/src/system/Resolver.hxx
@@ -22,27 +22,27 @@
#include "Compiler.h"
+#include <string>
+
#include <stddef.h>
struct sockaddr;
struct addrinfo;
class Error;
+class Domain;
-extern const class Domain resolver_domain;
+extern const Domain resolver_domain;
/**
* Converts the specified socket address into a string in the form
- * "IP:PORT". The return value must be freed with g_free() when you
- * don't need it anymore.
+ * "IP:PORT".
*
* @param sa the sockaddr struct
* @param length the length of #sa in bytes
- * @param error location to store the error occurring, or NULL to
- * ignore errors
*/
-gcc_malloc
-char *
-sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error);
+gcc_pure
+std::string
+sockaddr_to_string(const sockaddr *sa, size_t length);
/**
* Resolve a specification in the form "host", "host:port",
@@ -54,7 +54,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error);
* @return an #addrinfo linked list that must be freed with
* freeaddrinfo(), or NULL on error
*/
-struct addrinfo *
+addrinfo *
resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
Error &error);
diff --git a/src/system/SignalFD.cxx b/src/system/SignalFD.cxx
index b89775dcd..d5953056d 100644
--- a/src/system/SignalFD.cxx
+++ b/src/system/SignalFD.cxx
@@ -20,7 +20,6 @@
#include "config.h"
#ifdef USE_SIGNALFD
#include "SignalFD.hxx"
-#include "fd_util.h"
#include "FatalError.hxx"
#include <assert.h>
diff --git a/src/system/SocketError.cxx b/src/system/SocketError.cxx
index 315a86e1f..bb1fa5abf 100644
--- a/src/system/SocketError.cxx
+++ b/src/system/SocketError.cxx
@@ -21,7 +21,7 @@
#include "SocketError.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
+#include <string.h>
const Domain socket_domain("socket");
@@ -41,6 +41,6 @@ SocketErrorMessage::SocketErrorMessage(socket_error_t code)
#else
SocketErrorMessage::SocketErrorMessage(socket_error_t code)
- :msg(g_strerror(code)) {}
+ :msg(strerror(code)) {}
#endif
diff --git a/src/system/SocketError.hxx b/src/system/SocketError.hxx
index 22fbd2441..28e1bace0 100644
--- a/src/system/SocketError.hxx
+++ b/src/system/SocketError.hxx
@@ -21,7 +21,7 @@
#define MPD_SOCKET_ERROR_HXX
#include "Compiler.h"
-#include "util/Error.hxx"
+#include "util/Error.hxx" // IWYU pragma: export
#ifdef WIN32
#include <winsock2.h>
@@ -31,11 +31,13 @@ typedef DWORD socket_error_t;
typedef int socket_error_t;
#endif
+class Domain;
+
/**
* A #Domain for #Error for socket I/O errors. The code is an errno
* value (or WSAGetLastError() on Windows).
*/
-extern const class Domain socket_domain;
+extern const Domain socket_domain;
gcc_pure
static inline socket_error_t
diff --git a/src/system/SocketUtil.cxx b/src/system/SocketUtil.cxx
index 9c4002386..ae98f33ff 100644
--- a/src/system/SocketUtil.cxx
+++ b/src/system/SocketUtil.cxx
@@ -22,8 +22,6 @@
#include "SocketError.hxx"
#include "fd_util.h"
-#include <glib.h>
-
#include <unistd.h>
#ifndef WIN32
diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx
index 73e46e49f..84c386678 100644
--- a/src/tag/Aiff.cxx
+++ b/src/tag/Aiff.cxx
@@ -26,9 +26,7 @@
#include <limits>
#include <stdint.h>
-#include <sys/types.h>
#include <sys/stat.h>
-#include <unistd.h>
#include <string.h>
static constexpr Domain aiff_domain("aiff");
diff --git a/src/tag/ApeLoader.cxx b/src/tag/ApeLoader.cxx
index 8251efe10..febe4467d 100644
--- a/src/tag/ApeLoader.cxx
+++ b/src/tag/ApeLoader.cxx
@@ -22,8 +22,6 @@
#include "system/ByteOrder.hxx"
#include "fs/FileSystem.hxx"
-#include <glib.h>
-
#include <stdint.h>
#include <assert.h>
#include <stdio.h>
@@ -61,9 +59,9 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
remaining -= sizeof(footer);
assert(remaining > 10);
- char *buffer = (char *)g_malloc(remaining);
+ char *buffer = new char[remaining];
if (fread(buffer, 1, remaining, fp) != remaining) {
- g_free(buffer);
+ delete[] buffer;
return false;
}
@@ -98,7 +96,7 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
remaining -= size;
}
- g_free(buffer);
+ delete[] buffer;
return true;
}
diff --git a/src/tag/ApeTag.hxx b/src/tag/ApeTag.hxx
index e35edc381..d2c0365c7 100644
--- a/src/tag/ApeTag.hxx
+++ b/src/tag/ApeTag.hxx
@@ -34,6 +34,6 @@ extern const struct tag_table ape_tags[];
*/
bool
tag_ape_scan2(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/tag/Riff.cxx b/src/tag/Riff.cxx
index ac162bc24..0f8f265fc 100644
--- a/src/tag/Riff.cxx
+++ b/src/tag/Riff.cxx
@@ -26,7 +26,6 @@
#include <limits>
#include <stdint.h>
-#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
diff --git a/src/tag/Tag.cxx b/src/tag/Tag.cxx
index 6bf070429..d6b3abe55 100644
--- a/src/tag/Tag.cxx
+++ b/src/tag/Tag.cxx
@@ -22,9 +22,9 @@
#include "TagPool.hxx"
#include "TagString.hxx"
#include "TagSettings.h"
+#include "TagBuilder.hxx"
#include "util/ASCII.hxx"
-#include <glib.h>
#include <assert.h>
#include <string.h>
@@ -58,12 +58,6 @@ tag_name_parse_i(const char *name)
return TAG_NUM_OF_ITEM_TYPES;
}
-static size_t
-items_size(const Tag &tag)
-{
- return tag.num_items * sizeof(TagItem *);
-}
-
void
Tag::Clear()
{
@@ -75,28 +69,18 @@ Tag::Clear()
tag_pool_put_item(items[i]);
tag_pool_lock.unlock();
- g_free(items);
+ delete[] items;
items = nullptr;
num_items = 0;
}
-Tag::~Tag()
-{
- tag_pool_lock.lock();
- for (int i = num_items; --i >= 0; )
- tag_pool_put_item(items[i]);
- tag_pool_lock.unlock();
-
- g_free(items);
-}
-
Tag::Tag(const Tag &other)
:time(other.time), has_playlist(other.has_playlist),
items(nullptr),
num_items(other.num_items)
{
if (num_items > 0) {
- items = (TagItem **)g_malloc(items_size(other));
+ items = new TagItem *[num_items];
tag_pool_lock.lock();
for (unsigned i = 0; i < num_items; i++)
@@ -108,46 +92,9 @@ Tag::Tag(const Tag &other)
Tag *
Tag::Merge(const Tag &base, const Tag &add)
{
- unsigned n;
-
- /* allocate new tag object */
-
- Tag *ret = new Tag();
- ret->time = add.time > 0 ? add.time : base.time;
- ret->num_items = base.num_items + add.num_items;
- ret->items = ret->num_items > 0
- ? (TagItem **)g_malloc(items_size(*ret))
- : nullptr;
-
- tag_pool_lock.lock();
-
- /* copy all items from "add" */
-
- for (unsigned i = 0; i < add.num_items; ++i)
- ret->items[i] = tag_pool_dup_item(add.items[i]);
-
- n = add.num_items;
-
- /* copy additional items from "base" */
-
- for (unsigned i = 0; i < base.num_items; ++i)
- if (!add.HasType(base.items[i]->type))
- ret->items[n++] = tag_pool_dup_item(base.items[i]);
-
- tag_pool_lock.unlock();
-
- assert(n <= ret->num_items);
-
- if (n < ret->num_items) {
- /* some tags were not copied - shrink ret->items */
- assert(n > 0);
-
- ret->num_items = n;
- ret->items = (TagItem **)
- g_realloc(ret->items, items_size(*ret));
- }
-
- return ret;
+ TagBuilder builder(add);
+ builder.Complement(base);
+ return builder.CommitNew();
}
Tag *
@@ -183,43 +130,3 @@ Tag::HasType(TagType type) const
{
return GetValue(type) != nullptr;
}
-
-void
-Tag::AddItemInternal(TagType type, const char *value, size_t len)
-{
- unsigned int i = num_items;
-
- char *p = FixTagString(value, len);
- if (p != nullptr) {
- value = p;
- len = strlen(value);
- }
-
- num_items++;
-
- items = (TagItem **)g_realloc(items, items_size(*this));
-
- tag_pool_lock.lock();
- items[i] = tag_pool_get_item(type, value, len);
- tag_pool_lock.unlock();
-
- g_free(p);
-}
-
-void
-Tag::AddItem(TagType type, const char *value, size_t len)
-{
- if (ignore_tag_items[type])
- return;
-
- if (value == nullptr || len == 0)
- return;
-
- AddItemInternal(type, value, len);
-}
-
-void
-Tag::AddItem(TagType type, const char *value)
-{
- AddItem(type, value, strlen(value));
-}
diff --git a/src/tag/Tag.hxx b/src/tag/Tag.hxx
index 5846e7a9d..480115c5b 100644
--- a/src/tag/Tag.hxx
+++ b/src/tag/Tag.hxx
@@ -20,8 +20,8 @@
#ifndef MPD_TAG_HXX
#define MPD_TAG_HXX
-#include "TagType.h"
-#include "TagItem.hxx"
+#include "TagType.h" // IWYU pragma: export
+#include "TagItem.hxx" // IWYU pragma: export
#include "Compiler.h"
#include <algorithm>
@@ -71,7 +71,9 @@ struct Tag {
/**
* Free the tag object and all its items.
*/
- ~Tag();
+ ~Tag() {
+ Clear();
+ }
Tag &operator=(const Tag &other) = delete;
@@ -104,24 +106,6 @@ struct Tag {
void Clear();
/**
- * Appends a new tag item.
- *
- * @param type the type of the new tag item
- * @param value the value of the tag item (not null-terminated)
- * @param len the length of #value
- */
- void AddItem(TagType type, const char *value, size_t len);
-
- /**
- * Appends a new tag item.
- *
- * @param tag the #tag object
- * @param type the type of the new tag item
- * @param value the value of the tag item (null-terminated)
- */
- void AddItem(TagType type, const char *value);
-
- /**
* Merges the data from two tags. If both tags share data for the
* same TagType, only data from "add" is used.
*
@@ -152,9 +136,6 @@ struct Tag {
*/
gcc_pure
bool HasType(TagType type) const;
-
-private:
- void AddItemInternal(TagType type, const char *value, size_t len);
};
/**
diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx
index 25e5cc24b..462f5c49a 100644
--- a/src/tag/TagBuilder.cxx
+++ b/src/tag/TagBuilder.cxx
@@ -24,23 +24,90 @@
#include "TagString.hxx"
#include "Tag.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
-void
-TagBuilder::Clear()
+TagBuilder::TagBuilder(const Tag &other)
+ :time(other.time), has_playlist(other.has_playlist)
{
- time = -1;
- has_playlist = false;
+ items.reserve(other.num_items);
tag_pool_lock.lock();
+ for (unsigned i = 0, n = other.num_items; i != n; ++i)
+ items.push_back(tag_pool_dup_item(other.items[i]));
+ tag_pool_lock.unlock();
+}
+
+TagBuilder::TagBuilder(Tag &&other)
+ :time(other.time), has_playlist(other.has_playlist)
+{
+ /* move all TagItem pointers from the Tag object; we don't
+ need to contact the tag pool, because all we do is move
+ references */
+ items.reserve(other.num_items);
+ std::copy_n(other.items, other.num_items, std::back_inserter(items));
+
+ /* discard the pointers from the Tag object */
+ other.num_items = 0;
+ delete[] other.items;
+ other.items = nullptr;
+}
+
+TagBuilder &
+TagBuilder::operator=(const TagBuilder &other)
+{
+ /* copy all attributes */
+ time = other.time;
+ has_playlist = other.has_playlist;
+ items = other.items;
+
+ /* increment the tag pool refcounters */
+ tag_pool_lock.lock();
for (auto i : items)
- tag_pool_put_item(i);
+ tag_pool_dup_item(i);
tag_pool_lock.unlock();
+ return *this;
+}
+
+TagBuilder &
+TagBuilder::operator=(TagBuilder &&other)
+{
+ time = other.time;
+ has_playlist = other.has_playlist;
+ items = std::move(other.items);
+
+ return *this;
+}
+
+TagBuilder &
+TagBuilder::operator=(Tag &&other)
+{
+ time = other.time;
+ has_playlist = other.has_playlist;
+
+ /* move all TagItem pointers from the Tag object; we don't
+ need to contact the tag pool, because all we do is move
+ references */
items.clear();
+ items.reserve(other.num_items);
+ std::copy_n(other.items, other.num_items, std::back_inserter(items));
+
+ /* discard the pointers from the Tag object */
+ other.num_items = 0;
+ delete[] other.items;
+ other.items = nullptr;
+
+ return *this;
+}
+
+void
+TagBuilder::Clear()
+{
+ time = -1;
+ has_playlist = false;
+ RemoveAll();
}
void
@@ -57,7 +124,7 @@ TagBuilder::Commit(Tag &tag)
object */
const unsigned n_items = items.size();
tag.num_items = n_items;
- tag.items = g_new(TagItem *, n_items);
+ tag.items = new TagItem *[n_items];
std::copy_n(items.begin(), n_items, tag.items);
items.clear();
@@ -66,14 +133,51 @@ TagBuilder::Commit(Tag &tag)
Clear();
}
-Tag *
+Tag
TagBuilder::Commit()
{
+ Tag tag;
+ Commit(tag);
+ return tag;
+}
+
+Tag *
+TagBuilder::CommitNew()
+{
Tag *tag = new Tag();
Commit(*tag);
return tag;
}
+bool
+TagBuilder::HasType(TagType type) const
+{
+ for (auto i : items)
+ if (i->type == type)
+ return true;
+
+ return false;
+}
+
+void
+TagBuilder::Complement(const Tag &other)
+{
+ if (time <= 0)
+ time = other.time;
+
+ has_playlist |= other.has_playlist;
+
+ items.reserve(items.size() + other.num_items);
+
+ tag_pool_lock.lock();
+ for (unsigned i = 0, n = other.num_items; i != n; ++i) {
+ TagItem *item = other.items[i];
+ if (!HasType(item->type))
+ items.push_back(tag_pool_dup_item(item));
+ }
+ tag_pool_lock.unlock();
+}
+
inline void
TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
{
@@ -90,7 +194,7 @@ TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
auto i = tag_pool_get_item(type, value, length);
tag_pool_lock.unlock();
- g_free(p);
+ free(p);
items.push_back(i);
}
@@ -113,3 +217,29 @@ TagBuilder::AddItem(TagType type, const char *value)
AddItem(type, value, strlen(value));
}
+
+void
+TagBuilder::RemoveAll()
+{
+ tag_pool_lock.lock();
+ for (auto i : items)
+ tag_pool_put_item(i);
+ tag_pool_lock.unlock();
+
+ items.clear();
+}
+
+void
+TagBuilder::RemoveType(TagType type)
+{
+ const auto begin = items.begin(), end = items.end();
+
+ items.erase(std::remove_if(begin, end,
+ [type](TagItem *item) {
+ if (item->type != type)
+ return false;
+ tag_pool_put_item(item);
+ return true;
+ }),
+ end);
+}
diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx
index ffc60a1b2..984261ded 100644
--- a/src/tag/TagBuilder.hxx
+++ b/src/tag/TagBuilder.hxx
@@ -63,7 +63,14 @@ public:
}
TagBuilder(const TagBuilder &other) = delete;
- TagBuilder &operator=(const TagBuilder &other) = delete;
+
+ explicit TagBuilder(const Tag &other);
+ explicit TagBuilder(Tag &&other);
+
+ TagBuilder &operator=(const TagBuilder &other);
+ TagBuilder &operator=(TagBuilder &&other);
+
+ TagBuilder &operator=(Tag &&other);
/**
* Returns true if the tag contains no items. This ignores the "time"
@@ -90,11 +97,17 @@ public:
void Commit(Tag &tag);
/**
+ * Create a new #Tag instance from data in this object. This
+ * object is empty afterwards.
+ */
+ Tag Commit();
+
+ /**
* Create a new #Tag instance from data in this object. The
* returned object is owned by the caller. This object is
* empty afterwards.
*/
- Tag *Commit();
+ Tag *CommitNew();
void SetTime(int _time) {
time = _time;
@@ -109,6 +122,19 @@ public:
}
/**
+ * Checks whether the tag contains one or more items with
+ * the specified type.
+ */
+ gcc_pure
+ bool HasType(TagType type) const;
+
+ /**
+ * Copy attributes and items from the other object that do not
+ * exist in this object.
+ */
+ void Complement(const Tag &other);
+
+ /**
* Appends a new tag item.
*
* @param type the type of the new tag item
@@ -127,6 +153,16 @@ public:
gcc_nonnull_all
void AddItem(TagType type, const char *value);
+ /**
+ * Removes all tag items.
+ */
+ void RemoveAll();
+
+ /**
+ * Removes all tag items of the specified type.
+ */
+ void RemoveType(TagType type);
+
private:
gcc_nonnull_all
void AddItemInternal(TagType type, const char *value, size_t length);
diff --git a/src/tag/TagConfig.cxx b/src/tag/TagConfig.cxx
index 96fd1847f..5c81fa603 100644
--- a/src/tag/TagConfig.cxx
+++ b/src/tag/TagConfig.cxx
@@ -24,13 +24,14 @@
#include "ConfigGlobal.hxx"
#include "ConfigOption.hxx"
#include "system/FatalError.hxx"
+#include "util/Alloc.hxx"
#include "util/ASCII.hxx"
#include <glib.h>
#include <algorithm>
-#include <string.h>
+#include <stdlib.h>
void
TagLoadConfig()
@@ -46,7 +47,7 @@ TagLoadConfig()
bool quit = false;
char *temp, *c, *s;
- temp = c = s = g_strdup(value);
+ temp = c = s = xstrdup(value);
do {
if (*s == ',' || *s == '\0') {
if (*s == '\0')
@@ -70,5 +71,5 @@ TagLoadConfig()
s++;
} while (!quit);
- g_free(temp);
+ free(temp);
}
diff --git a/src/tag/TagConfig.hxx b/src/tag/TagConfig.hxx
index 5ec6766d4..3bc86cc40 100644
--- a/src/tag/TagConfig.hxx
+++ b/src/tag/TagConfig.hxx
@@ -20,8 +20,6 @@
#ifndef MPD_TAG_CONFIG_HXX
#define MPD_TAG_CONFIG_HXX
-#include "TagType.h"
-
void
TagLoadConfig();
diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx
index df70a95e5..0397c0039 100644
--- a/src/tag/TagId3.cxx
+++ b/src/tag/TagId3.cxx
@@ -21,7 +21,6 @@
#include "TagId3.hxx"
#include "TagHandler.hxx"
#include "TagTable.hxx"
-#include "Tag.hxx"
#include "TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -35,9 +34,10 @@
#include <glib.h>
#include <id3tag.h>
+#include <string>
+
#include <stdio.h>
#include <stdlib.h>
-#include <errno.h>
#include <string.h>
# ifndef ID3_FRAME_COMPOSER
@@ -70,14 +70,11 @@ tag_is_id3v1(struct id3_tag *tag)
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
- union id3_field *field;
- const id3_ucs4_t *ucs4;
-
- field = id3_frame_field(frame, i);
+ id3_field *field = id3_frame_field(frame, i);
if (field == nullptr)
return nullptr;
- ucs4 = id3_field_getstring(field);
+ const id3_ucs4_t *ucs4 = id3_field_getstring(field);
if (ucs4 == nullptr)
return nullptr;
@@ -89,17 +86,15 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i)
static id3_utf8_t *
import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
{
- id3_utf8_t *utf8, *utf8_stripped;
- id3_latin1_t *isostr;
- const char *encoding;
+ id3_utf8_t *utf8;
/* use encoding field here? */
+ const char *encoding;
if (is_id3v1 &&
(encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) {
- isostr = id3_ucs4_latin1duplicate(ucs4);
- if (G_UNLIKELY(!isostr)) {
+ id3_latin1_t *isostr = id3_ucs4_latin1duplicate(ucs4);
+ if (gcc_unlikely(isostr == nullptr))
return nullptr;
- }
utf8 = (id3_utf8_t *)
g_convert_with_fallback((const char*)isostr, -1,
@@ -110,19 +105,19 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
FormatWarning(id3_domain,
"Unable to convert %s string to UTF-8: '%s'",
encoding, isostr);
- g_free(isostr);
+ free(isostr);
return nullptr;
}
- g_free(isostr);
+ free(isostr);
} else {
utf8 = id3_ucs4_utf8duplicate(ucs4);
- if (G_UNLIKELY(!utf8)) {
+ if (gcc_unlikely(utf8 == nullptr))
return nullptr;
- }
}
- utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8));
- g_free(utf8);
+ id3_utf8_t *utf8_stripped = (id3_utf8_t *)
+ g_strdup(g_strstrip((gchar *)utf8));
+ free(utf8);
return utf8_stripped;
}
@@ -139,17 +134,12 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
TagType type,
const struct tag_handler *handler, void *handler_ctx)
{
- id3_ucs4_t const *ucs4;
- id3_utf8_t *utf8;
- union id3_field const *field;
- unsigned int nstrings, i;
-
if (frame->nfields != 2)
return;
/* check the encoding field */
- field = id3_frame_field(frame, 0);
+ const id3_field *field = id3_frame_field(frame, 0);
if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
return;
@@ -160,16 +150,16 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
return;
/* Get the number of strings available */
- nstrings = id3_field_getnstrings(field);
- for (i = 0; i < nstrings; i++) {
- ucs4 = id3_field_getstrings(field, i);
+ const unsigned nstrings = id3_field_getnstrings(field);
+ for (unsigned i = 0; i < nstrings; i++) {
+ const id3_ucs4_t *ucs4 = id3_field_getstrings(field, i);
if (ucs4 == nullptr)
continue;
if (type == TAG_GENRE)
ucs4 = id3_genre_name(ucs4);
- utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
if (utf8 == nullptr)
continue;
@@ -209,23 +199,19 @@ tag_id3_import_comment_frame(struct id3_tag *tag,
const struct tag_handler *handler,
void *handler_ctx)
{
- id3_ucs4_t const *ucs4;
- id3_utf8_t *utf8;
- union id3_field const *field;
-
if (frame->nfields != 4)
return;
/* for now I only read the 4th field, with the fullstring */
- field = id3_frame_field(frame, 3);
+ const id3_field *field = id3_frame_field(frame, 3);
if (field == nullptr)
return;
- ucs4 = id3_field_getfullstring(field);
+ const id3_ucs4_t *ucs4 = id3_field_getfullstring(field);
if (ucs4 == nullptr)
return;
- utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
if (utf8 == nullptr)
return;
@@ -277,19 +263,15 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
void *handler_ctx)
{
for (unsigned i = 0;; ++i) {
- const struct id3_frame *frame;
- id3_utf8_t *name, *value;
- TagType type;
-
- frame = id3_tag_findframe(id3_tag, "TXXX", i);
+ const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i);
if (frame == nullptr)
break;
- name = tag_id3_getstring(frame, 1);
+ id3_utf8_t *name = tag_id3_getstring(frame, 1);
if (name == nullptr)
continue;
- value = tag_id3_getstring(frame, 2);
+ id3_utf8_t *value = tag_id3_getstring(frame, 2);
if (value == nullptr)
continue;
@@ -297,7 +279,7 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
(const char *)name,
(const char *)value);
- type = tag_id3_parse_txxx_name((const char*)name);
+ TagType type = tag_id3_parse_txxx_name((const char*)name);
free(name);
if (type != TAG_NUM_OF_ITEM_TYPES)
@@ -316,21 +298,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag,
const struct tag_handler *handler, void *handler_ctx)
{
for (unsigned i = 0;; ++i) {
- const struct id3_frame *frame;
- union id3_field *field;
- const id3_latin1_t *name;
- const id3_byte_t *value;
- id3_length_t length;
-
- frame = id3_tag_findframe(id3_tag, "UFID", i);
+ const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i);
if (frame == nullptr)
break;
- field = id3_frame_field(frame, 0);
+ id3_field *field = id3_frame_field(frame, 0);
if (field == nullptr)
continue;
- name = id3_field_getlatin1(field);
+ const id3_latin1_t *name = id3_field_getlatin1(field);
if (name == nullptr ||
strcmp((const char *)name, "http://musicbrainz.org") != 0)
continue;
@@ -339,14 +315,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag,
if (field == nullptr)
continue;
- value = id3_field_getbinarydata(field, &length);
+ id3_length_t length;
+ const id3_byte_t *value =
+ id3_field_getbinarydata(field, &length);
if (value == nullptr || length == 0)
continue;
- char *p = g_strndup((const char *)value, length);
+ std::string p((const char *)value, length);
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_MUSICBRAINZ_TRACKID, p);
- g_free(p);
+ TAG_MUSICBRAINZ_TRACKID, p.c_str());
}
}
@@ -393,73 +370,57 @@ tag_id3_import(struct id3_tag *tag)
scan_id3_tag(tag, &add_tag_handler, &tag_builder);
return tag_builder.IsEmpty()
? nullptr
- : tag_builder.Commit();
+ : tag_builder.CommitNew();
}
-static int
+static size_t
fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence)
{
if (fseek(stream, offset, whence) != 0) return 0;
return fread(buf, 1, size, stream);
}
-static int
+static long
get_id3v2_footer_size(FILE *stream, long offset, int whence)
{
id3_byte_t buf[ID3_TAG_QUERYSIZE];
- int bufsize;
-
- bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
- if (bufsize <= 0) return 0;
+ size_t bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
+ if (bufsize == 0) return 0;
return id3_tag_query(buf, bufsize);
}
static struct id3_tag *
tag_id3_read(FILE *stream, long offset, int whence)
{
- struct id3_tag *tag;
- id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
- int tag_size;
- int query_buffer_size;
-
/* It's ok if we get less than we asked for */
- query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
- stream, offset, whence);
+ id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
+ size_t query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
+ stream, offset, whence);
if (query_buffer_size <= 0)
return nullptr;
/* Look for a tag header */
- tag_size = id3_tag_query(query_buffer, query_buffer_size);
+ long tag_size = id3_tag_query(query_buffer, query_buffer_size);
if (tag_size <= 0) return nullptr;
/* Found a tag. Allocate a buffer and read it in. */
- id3_byte_t *tag_buffer = (id3_byte_t *)g_malloc(tag_size);
- if (!tag_buffer)
- return nullptr;
-
+ id3_byte_t *tag_buffer = new id3_byte_t[tag_size];
int tag_buffer_size = fill_buffer(tag_buffer, tag_size,
stream, offset, whence);
if (tag_buffer_size < tag_size) {
- g_free(tag_buffer);
+ delete[] tag_buffer;
return nullptr;
}
- tag = id3_tag_parse(tag_buffer, tag_buffer_size);
-
- g_free(tag_buffer);
-
+ id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size);
+ delete[] tag_buffer;
return tag;
}
static struct id3_tag *
tag_id3_find_from_beginning(FILE *stream)
{
- struct id3_tag *tag;
- struct id3_tag *seektag;
- struct id3_frame *frame;
- int seek;
-
- tag = tag_id3_read(stream, 0, SEEK_SET);
+ id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET);
if (!tag) {
return nullptr;
} else if (tag_is_id3v1(tag)) {
@@ -469,14 +430,15 @@ tag_id3_find_from_beginning(FILE *stream)
}
/* We have an id3v2 tag, so let's look for SEEK frames */
+ id3_frame *frame;
while ((frame = id3_tag_findframe(tag, "SEEK", 0))) {
/* Found a SEEK frame, get it's value */
- seek = id3_field_getint(id3_frame_field(frame, 0));
+ int seek = id3_field_getint(id3_frame_field(frame, 0));
if (seek < 0)
break;
/* Get the tag specified by the SEEK frame */
- seektag = tag_id3_read(stream, seek, SEEK_CUR);
+ id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR);
if (!seektag || tag_is_id3v1(seektag))
break;
@@ -491,20 +453,16 @@ tag_id3_find_from_beginning(FILE *stream)
static struct id3_tag *
tag_id3_find_from_end(FILE *stream)
{
- struct id3_tag *tag;
- struct id3_tag *v1tag;
- int tagsize;
-
/* Get an id3v1 tag from the end of file for later use */
- v1tag = tag_id3_read(stream, -128, SEEK_END);
+ id3_tag *v1tag = tag_id3_read(stream, -128, SEEK_END);
/* Get the id3v2 tag size from the footer (located before v1tag) */
- tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
+ int tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
if (tagsize >= 0)
return v1tag;
/* Get the tag which the footer belongs to */
- tag = tag_id3_read(stream, tagsize, SEEK_CUR);
+ id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR);
if (!tag)
return v1tag;
@@ -527,16 +485,16 @@ tag_id3_riff_aiff_load(FILE *file)
/* too large, don't allocate so much memory */
return nullptr;
- id3_byte_t *buffer = (id3_byte_t *)g_malloc(size);
+ id3_byte_t *buffer = new id3_byte_t[size];
size_t ret = fread(buffer, size, 1, file);
if (ret != 1) {
LogWarning(id3_domain, "Failed to read RIFF chunk");
- g_free(buffer);
+ delete[] buffer;
return nullptr;
}
struct id3_tag *tag = id3_tag_parse(buffer, size);
- g_free(buffer);
+ delete[] buffer;
return tag;
}
diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx
index 749166116..aa564e946 100644
--- a/src/tag/TagId3.hxx
+++ b/src/tag/TagId3.hxx
@@ -33,10 +33,10 @@ class Error;
bool
tag_id3_scan(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
Tag *
-tag_id3_import(struct id3_tag *);
+tag_id3_import(id3_tag *);
/**
* Loads the ID3 tags from the file into a libid3tag object. The
@@ -53,8 +53,8 @@ tag_id3_load(Path path_fs, Error &error);
*
*/
void
-scan_id3_tag(struct id3_tag *tag,
- const struct tag_handler *handler, void *handler_ctx);
+scan_id3_tag(id3_tag *tag,
+ const tag_handler *handler, void *handler_ctx);
#else
@@ -62,7 +62,7 @@ scan_id3_tag(struct id3_tag *tag,
static inline bool
tag_id3_scan(gcc_unused Path path_fs,
- gcc_unused const struct tag_handler *handler,
+ gcc_unused const tag_handler *handler,
gcc_unused void *handler_ctx)
{
return false;
diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx
index cc28ea9a6..409edb662 100644
--- a/src/tag/TagPool.cxx
+++ b/src/tag/TagPool.cxx
@@ -20,23 +20,46 @@
#include "config.h"
#include "TagPool.hxx"
#include "TagItem.hxx"
-
-#include <glib.h>
+#include "util/Cast.hxx"
+#include "util/VarSize.hxx"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
Mutex tag_pool_lock;
#define NUM_SLOTS 4096
-struct slot {
- struct slot *next;
+struct TagPoolSlot {
+ TagPoolSlot *next;
unsigned char ref;
TagItem item;
-} mpd_packed;
-static struct slot *slots[NUM_SLOTS];
+ TagPoolSlot(TagPoolSlot *_next, TagType type,
+ const char *value, size_t length)
+ :next(_next), ref(1) {
+ item.type = type;
+ memcpy(item.value, value, length);
+ item.value[length] = 0;
+ }
+
+ static TagPoolSlot *Create(TagPoolSlot *_next, TagType type,
+ const char *value, size_t length);
+} gcc_packed;
+
+TagPoolSlot *
+TagPoolSlot::Create(TagPoolSlot *_next, TagType type,
+ const char *value, size_t length)
+{
+ TagPoolSlot *dummy;
+ return NewVarSize<TagPoolSlot>(sizeof(dummy->item.value),
+ length + 1,
+ _next, type,
+ value, length);
+}
+
+static TagPoolSlot *slots[NUM_SLOTS];
static inline unsigned
calc_hash_n(TagType type, const char *p, size_t length)
@@ -64,32 +87,16 @@ calc_hash(TagType type, const char *p)
return hash ^ type;
}
-static inline struct slot *
+static inline constexpr TagPoolSlot *
tag_item_to_slot(TagItem *item)
{
- return (struct slot*)(((char*)item) - offsetof(struct slot, item));
-}
-
-static struct slot *slot_alloc(struct slot *next,
- TagType type,
- const char *value, int length)
-{
- struct slot *slot;
-
- slot = (struct slot *)
- g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1);
- slot->next = next;
- slot->ref = 1;
- slot->item.type = type;
- memcpy(slot->item.value, value, length);
- slot->item.value[length] = 0;
- return slot;
+ return ContainerCast(item, TagPoolSlot, item);
}
TagItem *
tag_pool_get_item(TagType type, const char *value, size_t length)
{
- struct slot **slot_p, *slot;
+ TagPoolSlot **slot_p, *slot;
slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS];
for (slot = *slot_p; slot != nullptr; slot = slot->next) {
@@ -103,7 +110,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
}
}
- slot = slot_alloc(*slot_p, type, value, length);
+ slot = TagPoolSlot::Create(*slot_p, type, value, length);
*slot_p = slot;
return &slot->item;
}
@@ -111,7 +118,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
TagItem *
tag_pool_dup_item(TagItem *item)
{
- struct slot *slot = tag_item_to_slot(item);
+ TagPoolSlot *slot = tag_item_to_slot(item);
assert(slot->ref > 0);
@@ -122,11 +129,11 @@ tag_pool_dup_item(TagItem *item)
/* the reference counter overflows above 0xff;
duplicate the item, and start with 1 */
size_t length = strlen(item->value);
- struct slot **slot_p =
+ TagPoolSlot **slot_p =
&slots[calc_hash_n(item->type, item->value,
length) % NUM_SLOTS];
- slot = slot_alloc(*slot_p, item->type,
- item->value, strlen(item->value));
+ slot = TagPoolSlot::Create(*slot_p, item->type,
+ item->value, strlen(item->value));
*slot_p = slot;
return &slot->item;
}
@@ -135,7 +142,7 @@ tag_pool_dup_item(TagItem *item)
void
tag_pool_put_item(TagItem *item)
{
- struct slot **slot_p, *slot;
+ TagPoolSlot **slot_p, *slot;
slot = tag_item_to_slot(item);
assert(slot->ref > 0);
@@ -151,5 +158,5 @@ tag_pool_put_item(TagItem *item)
}
*slot_p = slot->next;
- g_free(slot);
+ DeleteVarSize(slot);
}
diff --git a/src/tag/TagRva2.cxx b/src/tag/TagRva2.cxx
index 204001aa7..442695581 100644
--- a/src/tag/TagRva2.cxx
+++ b/src/tag/TagRva2.cxx
@@ -21,10 +21,10 @@
#include "TagRva2.hxx"
#include "ReplayGainInfo.hxx"
+#include <id3tag.h>
+
#include <stdint.h>
#include <string.h>
-#include <glib.h>
-#include <id3tag.h>
enum rva2_channel {
CHANNEL_OTHER = 0x00,
diff --git a/src/tag/TagRva2.hxx b/src/tag/TagRva2.hxx
index 98154041a..a1d67a777 100644
--- a/src/tag/TagRva2.hxx
+++ b/src/tag/TagRva2.hxx
@@ -32,6 +32,6 @@ struct ReplayGainInfo;
* @return true on success
*/
bool
-tag_rva2_parse(struct id3_tag *tag, ReplayGainInfo &replay_gain_info);
+tag_rva2_parse(id3_tag *tag, ReplayGainInfo &replay_gain_info);
#endif
diff --git a/src/tag/TagString.cxx b/src/tag/TagString.cxx
index 3e8d8c1b0..9d2bd68ec 100644
--- a/src/tag/TagString.cxx
+++ b/src/tag/TagString.cxx
@@ -19,11 +19,13 @@
#include "config.h"
#include "TagString.hxx"
+#include "util/Alloc.hxx"
#include <glib.h>
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
/**
* Replace invalid sequences with the question mark.
@@ -33,7 +35,7 @@ patch_utf8(const char *src, size_t length, const gchar *end)
{
/* duplicate the string, and replace invalid bytes in that
buffer */
- char *dest = g_strdup(src);
+ char *dest = xstrdup(src);
do {
dest[end - src] = '?';
@@ -58,9 +60,12 @@ fix_utf8(const char *str, size_t length)
/* no, it's not - try to import it from ISO-Latin-1 */
temp = g_convert(str, length, "utf-8", "iso-8859-1",
nullptr, &written, nullptr);
- if (temp != nullptr)
+ if (temp != nullptr) {
/* success! */
- return temp;
+ char *p = xstrdup(temp);
+ g_free(temp);
+ return p;
+ }
/* no, still broken - there's no medication, just patch
invalid sequences */
@@ -96,7 +101,7 @@ clear_non_printable(const char *p, size_t length)
if (first == nullptr)
return nullptr;
- dest = g_strndup(p, length);
+ dest = xstrndup(p, length);
for (size_t i = first - p; i < length; ++i)
if (char_is_non_printable(dest[i]))
@@ -120,7 +125,7 @@ FixTagString(const char *p, size_t length)
if (cleared == nullptr)
cleared = utf8;
else
- g_free(utf8);
+ free(utf8);
return cleared;
}
diff --git a/src/tag/TagTable.cxx b/src/tag/TagTable.cxx
index b51bc35ba..5224404a9 100644
--- a/src/tag/TagTable.cxx
+++ b/src/tag/TagTable.cxx
@@ -51,3 +51,13 @@ tag_table_lookup_i(const struct tag_table *table, const char *name)
return TAG_NUM_OF_ITEM_TYPES;
}
+
+const char *
+tag_table_lookup(const tag_table *table, TagType type)
+{
+ for (; table->name != nullptr; ++table)
+ if (table->type == type)
+ return table->name;
+
+ return nullptr;
+}
diff --git a/src/tag/TagTable.hxx b/src/tag/TagTable.hxx
index c050f61ac..423a1e6ad 100644
--- a/src/tag/TagTable.hxx
+++ b/src/tag/TagTable.hxx
@@ -47,4 +47,13 @@ gcc_pure
TagType
tag_table_lookup_i(const tag_table *table, const char *name);
+/**
+ * Looks up a #TagType in a tag translation table and returns its
+ * string representation. Returns nullptr if the specified type was
+ * not found in the table.
+ */
+gcc_pure
+const char *
+tag_table_lookup(const tag_table *table, TagType type);
+
#endif
diff --git a/src/util/Alloc.cxx b/src/util/Alloc.cxx
new file mode 100644
index 000000000..ec3579470
--- /dev/null
+++ b/src/util/Alloc.cxx
@@ -0,0 +1,76 @@
+/*
+ * 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 "Alloc.hxx"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+gcc_noreturn
+static void
+oom()
+{
+ (void)write(STDERR_FILENO, "Out of memory\n", 14);
+ _exit(1);
+}
+
+void *
+xalloc(size_t size)
+{
+ void *p = malloc(size);
+ if (gcc_unlikely(p == nullptr))
+ oom();
+
+ return p;
+}
+
+void *
+xmemdup(const void *s, size_t size)
+{
+ void *p = xalloc(size);
+ memcpy(p, s, size);
+ return p;
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *p = strdup(s);
+ if (gcc_unlikely(p == nullptr))
+ oom();
+
+ return p;
+}
+
+char *
+xstrndup(const char *s, size_t n)
+{
+#ifdef WIN32
+ char *p = (char *)xalloc(n + 1);
+ memcpy(p, s, n);
+ p[n] = 0;
+#else
+ char *p = strndup(s, n);
+ if (gcc_unlikely(p == nullptr))
+ oom();
+#endif
+
+ return p;
+}
diff --git a/src/util/Alloc.hxx b/src/util/Alloc.hxx
new file mode 100644
index 000000000..15c123b7a
--- /dev/null
+++ b/src/util/Alloc.hxx
@@ -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.
+ */
+
+#ifndef MPD_ALLOC_HXX
+#define MPD_ALLOC_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+/**
+ * Allocate memory. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc
+void *
+xalloc(size_t size);
+
+/**
+ * Duplicate memory. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+void *
+xmemdup(const void *s, size_t size);
+
+/**
+ * Duplicate a string. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+char *
+xstrdup(const char *s);
+
+/**
+ * Duplicate a string. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+char *
+xstrndup(const char *s, size_t n);
+
+#endif
diff --git a/src/util/growing_fifo.h b/src/util/Cast.hxx
index 723c3b3ff..69172e6de 100644
--- a/src/util/growing_fifo.h
+++ b/src/util/Cast.hxx
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -28,46 +27,32 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/** \file
- *
- * Helper functions for our FIFO buffer library (fifo_buffer.h) that
- * allows growing the buffer on demand.
- *
- * This library is not thread safe.
- */
-
-#ifndef MPD_GROWING_FIFO_H
-#define MPD_GROWING_FIFO_H
+#ifndef CAST_HXX
+#define CAST_HXX
#include <stddef.h>
-struct fifo_buffer;
-
/**
- * Allocate a new #fifo_buffer with the default size.
- */
-struct fifo_buffer *
-growing_fifo_new(void);
-
-/**
- * Prepares writing to the buffer, see fifo_buffer_write() for
- * details. The difference is that this function will automatically
- * grow the buffer if it is too small.
- *
- * The caller is responsible for limiting the capacity of the buffer.
- *
- * @param length the number of bytes that will be written
- * @return a pointer to the end of the buffer (will not be NULL)
+ * Offset the given pointer by the specified number of bytes.
*/
-void *
-growing_fifo_write(struct fifo_buffer **buffer_p, size_t length);
+static constexpr void *
+OffsetPointer(void *p, ptrdiff_t offset)
+{
+ return (char *)p + offset;
+}
+
+template<typename T, typename U>
+static constexpr T *
+OffsetCast(U *p, ptrdiff_t offset)
+{
+ return reinterpret_cast<T *>(OffsetPointer(p, offset));
+}
/**
- * A helper function that combines growing_fifo_write(), memcpy(),
- * fifo_buffer_append().
+ * Cast the given pointer to a struct member to its parent structure.
*/
-void
-growing_fifo_append(struct fifo_buffer **buffer_p,
- const void *data, size_t length);
+#define ContainerCast(p, container, attribute) \
+ OffsetCast<container, decltype(((container*)nullptr)->attribute)>\
+ ((p), -ptrdiff_t(offsetof(container, attribute)))
#endif
diff --git a/src/util/Clamp.hxx b/src/util/Clamp.hxx
new file mode 100644
index 000000000..3217ef9f7
--- /dev/null
+++ b/src/util/Clamp.hxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CLAMP_HPP
+#define CLAMP_HPP
+
+#include "Compiler.h"
+
+/**
+ * Clamps the specified value in a range. Returns #min or #max if the
+ * value is outside.
+ */
+template<typename T>
+static inline constexpr const T &
+Clamp(const T &value, const T &min, const T &max)
+{
+ return gcc_unlikely(value < min)
+ ? min
+ : (gcc_unlikely(value > max)
+ ? max : value);
+}
+
+#endif
diff --git a/src/util/ConstBuffer.hxx b/src/util/ConstBuffer.hxx
new file mode 100644
index 000000000..bd3c405e5
--- /dev/null
+++ b/src/util/ConstBuffer.hxx
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONST_BUFFER_HPP
+#define CONST_BUFFER_HPP
+
+#include "Compiler.h"
+
+#include <cstddef>
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+/**
+ * A reference to a memory area that is read-only.
+ */
+template<typename T>
+struct ConstBuffer {
+ typedef size_t size_type;
+ typedef const T *pointer_type;
+ typedef pointer_type const_pointer_type;
+ typedef pointer_type iterator;
+ typedef pointer_type const_iterator;
+
+ pointer_type data;
+ size_type size;
+
+ ConstBuffer() = default;
+
+ constexpr ConstBuffer(std::nullptr_t):data(nullptr), size(0) {}
+
+ constexpr ConstBuffer(pointer_type _data, size_type _size)
+ :data(_data), size(_size) {}
+
+ constexpr static ConstBuffer Null() {
+ return ConstBuffer(nullptr, 0);
+ }
+
+ /**
+ * Cast a ConstBuffer<void> to a ConstBuffer<T>. A "void"
+ * buffer records its size in bytes, and when casting to "T",
+ * the assertion below ensures that the size is a multiple of
+ * sizeof(T).
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ static ConstBuffer<T> FromVoid(ConstBuffer<void> other) {
+ static_assert(sizeof(T) > 0, "Empty base type");
+#ifndef NDEBUG
+ assert(other.size % sizeof(T) == 0);
+#endif
+ return ConstBuffer<T>(pointer_type(other.data),
+ other.size / sizeof(T));
+ }
+
+ constexpr ConstBuffer<void> ToVoid() const {
+ static_assert(sizeof(T) > 0, "Empty base type");
+ return ConstBuffer<void>(data, size * sizeof(T));
+ }
+
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
+
+ constexpr bool IsEmpty() const {
+ return size == 0;
+ }
+
+ constexpr iterator begin() const {
+ return data;
+ }
+
+ constexpr iterator end() const {
+ return data + size;
+ }
+
+ constexpr const_iterator cbegin() const {
+ return data;
+ }
+
+ constexpr const_iterator cend() const {
+ return data + size;
+ }
+
+ constexpr operator ConstBuffer<void>() const {
+ return { data, size };
+ }
+};
+
+#endif
diff --git a/src/util/Domain.hxx b/src/util/Domain.hxx
index bbdbf8371..6dce7b731 100644
--- a/src/util/Domain.hxx
+++ b/src/util/Domain.hxx
@@ -1,24 +1,34 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.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.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * 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.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * 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.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_DOMAIN_HXX
-#define MPD_DOMAIN_HXX
+#ifndef DOMAIN_HXX
+#define DOMAIN_HXX
class Domain {
const char *const name;
diff --git a/src/util/DynamicFifoBuffer.hxx b/src/util/DynamicFifoBuffer.hxx
new file mode 100644
index 000000000..df50328b4
--- /dev/null
+++ b/src/util/DynamicFifoBuffer.hxx
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2003-2013 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FIFO_BUFFER_HPP
+#define FIFO_BUFFER_HPP
+
+#include "WritableBuffer.hxx"
+
+#include <utility>
+#include <algorithm>
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * A first-in-first-out buffer: you can append data at the end, and
+ * read data from the beginning. This class automatically shifts the
+ * buffer as needed. It is not thread safe.
+ */
+template<typename T>
+class DynamicFifoBuffer {
+public:
+ typedef size_t size_type;
+ typedef WritableBuffer<T> Range;
+ typedef typename Range::pointer_type pointer_type;
+ typedef typename Range::const_pointer_type const_pointer_type;
+
+protected:
+ size_type head, tail, capacity;
+ T *data;
+
+public:
+ explicit DynamicFifoBuffer(size_type _capacity)
+ :head(0), tail(0), capacity(_capacity),
+ data(new T[capacity]) {}
+ ~DynamicFifoBuffer() {
+ delete[] data;
+ }
+
+ DynamicFifoBuffer(const DynamicFifoBuffer &) = delete;
+
+ size_type GetCapacity() {
+ return capacity;
+ }
+
+ void Grow(size_type new_capacity) {
+ assert(new_capacity > capacity);
+
+ T *new_data = new T[new_capacity];
+ std::move(data + head, data + tail, new_data);
+ delete[] data;
+ data = new_data;
+ capacity = new_capacity;
+ tail -= head;
+ head = 0;
+ }
+
+ void Clear() {
+ head = tail = 0;
+ }
+
+ bool IsEmpty() const {
+ return head == tail;
+ }
+
+ bool IsFull() const {
+ return head == 0 && tail == capacity;
+ }
+
+ /**
+ * Prepares writing. Returns a buffer range which may be written.
+ * When you are finished, call append().
+ */
+ Range Write() {
+ Shift();
+ return Range(data + tail, capacity - tail);
+ }
+
+ /**
+ * Expands the tail of the buffer, after data has been written to
+ * the buffer returned by write().
+ */
+ void Append(size_type n) {
+ assert(tail <= capacity);
+ assert(n <= capacity);
+ assert(tail + n <= capacity);
+
+ tail += n;
+ }
+
+ void WantWrite(size_type n) {
+ if (tail + n <= capacity)
+ /* enough space after the tail */
+ return;
+
+ const size_type in_use = tail - head;
+ const size_type required_capacity = in_use + n;
+ if (capacity >= required_capacity) {
+ Shift();
+ } else {
+ size_type new_capacity = capacity;
+ do {
+ new_capacity <<= 1;
+ } while (new_capacity < required_capacity);
+
+ Grow(new_capacity);
+ }
+ }
+
+ /**
+ * Write data to the bfufer, growing it as needed. Returns a
+ * writable pointer.
+ */
+ pointer_type Write(size_type n) {
+ WantWrite(n);
+ return data + tail;
+ }
+
+ /**
+ * Append data to the buffer, growing it as needed.
+ */
+ void Append(const_pointer_type p, size_type n) {
+ std::copy_n(p, n, Write(n));
+ Append(n);
+ }
+
+ /**
+ * Return a buffer range which may be read. The buffer pointer is
+ * writable, to allow modifications while parsing.
+ */
+ Range Read() {
+ return Range(data + head, tail - head);
+ }
+
+ /**
+ * Marks a chunk as consumed.
+ */
+ void Consume(size_type n) {
+ assert(tail <= capacity);
+ assert(head <= tail);
+ assert(n <= tail);
+ assert(head + n <= tail);
+
+ head += n;
+ }
+
+ size_type Read(pointer_type p, size_type n) {
+ auto range = Read();
+ if (n > range.size)
+ n = range.size;
+ std::copy_n(range.data, n, p);
+ Consume(n);
+ return n;
+ }
+
+protected:
+ void Shift() {
+ if (head == 0)
+ return;
+
+ assert(head <= capacity);
+ assert(tail <= capacity);
+ assert(tail >= head);
+
+ std::move(data + head, data + tail, data);
+
+ tail -= head;
+ head = 0;
+ }
+};
+
+#endif
diff --git a/src/util/Error.cxx b/src/util/Error.cxx
index 5675f4d81..649276b20 100644
--- a/src/util/Error.cxx
+++ b/src/util/Error.cxx
@@ -1,31 +1,44 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.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.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * 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.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * 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.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Error.hxx"
#include "Domain.hxx"
+#ifdef WIN32
#include <glib.h>
+#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
+#include <string.h>
const Domain errno_domain("errno");
@@ -70,7 +83,7 @@ Error::FormatPrefix(const char *fmt, ...)
void
Error::SetErrno(int e)
{
- Set(errno_domain, e, g_strerror(e));
+ Set(errno_domain, e, strerror(e));
}
void
@@ -82,7 +95,7 @@ Error::SetErrno()
void
Error::SetErrno(int e, const char *prefix)
{
- Format(errno_domain, e, "%s: %s", prefix, g_strerror(e));
+ Format(errno_domain, e, "%s: %s", prefix, strerror(e));
}
void
diff --git a/src/util/Error.hxx b/src/util/Error.hxx
index ec8867c6c..898a8f1c1 100644
--- a/src/util/Error.hxx
+++ b/src/util/Error.hxx
@@ -1,30 +1,40 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.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.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * 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.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * 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.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_ERROR_HXX
-#define MPD_ERROR_HXX
+#ifndef ERROR_HXX
+#define ERROR_HXX
#include "check.h"
#include "Compiler.h"
#include <string>
-#include <algorithm>
+#include <utility>
#include <assert.h>
diff --git a/src/util/FormatString.cxx b/src/util/FormatString.cxx
index c13d0fb52..ea6d41860 100644
--- a/src/util/FormatString.cxx
+++ b/src/util/FormatString.cxx
@@ -19,10 +19,13 @@
#include "FormatString.hxx"
-#include <string.h>
#include <stdio.h>
#include <stdlib.h>
+#ifdef WIN32
+#include <string.h>
+#endif
+
char *
FormatNewV(const char *fmt, va_list args)
{
diff --git a/src/util/OptionDef.hxx b/src/util/OptionDef.hxx
new file mode 100644
index 000000000..ca9e6083f
--- /dev/null
+++ b/src/util/OptionDef.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2013 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_UTIL_OPTIONDEF_HXX
+#define MPD_UTIL_OPTIONDEF_HXX
+
+/**
+ * Command line option definition.
+ */
+class OptionDef
+{
+ const char *long_option;
+ char short_option;
+ const char *desc;
+public:
+ constexpr OptionDef(const char *_long_option, const char *_desc)
+ : long_option(_long_option),
+ short_option(0),
+ desc(_desc) { }
+
+ constexpr OptionDef(const char *_long_option,
+ char _short_option, const char *_desc)
+ : long_option(_long_option),
+ short_option(_short_option),
+ desc(_desc) { }
+
+ bool HasLongOption() const { return long_option != nullptr; }
+ bool HasShortOption() const { return short_option != 0; }
+ bool HasDescription() const { return desc != nullptr; }
+
+ const char *GetLongOption() const {
+ assert(HasLongOption());
+ return long_option;
+ }
+
+ char GetShortOption() const {
+ assert(HasShortOption());
+ return short_option;
+ }
+
+ const char *GetDescription() const {
+ assert(HasDescription());
+ return desc;
+ }
+};
+
+#endif
diff --git a/src/util/OptionParser.cxx b/src/util/OptionParser.cxx
new file mode 100644
index 000000000..b8addb9a2
--- /dev/null
+++ b/src/util/OptionParser.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2013 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 "OptionParser.hxx"
+#include "OptionDef.hxx"
+
+#include <string.h>
+
+bool OptionParser::CheckOption(const OptionDef &opt)
+{
+ assert(option != nullptr);
+
+ if (is_long)
+ return opt.HasLongOption() &&
+ strcmp(option, opt.GetLongOption()) == 0;
+
+ return opt.HasShortOption() &&
+ option[0] == opt.GetShortOption() &&
+ option[1] == '\0';
+}
+
+bool OptionParser::ParseNext()
+{
+ assert(HasEntries());
+ char *arg = *argv;
+ ++argv;
+ --argc;
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ option = arg + 2;
+ is_long = true;
+ }
+ else {
+ option = arg + 1;
+ is_long = false;
+ }
+ option_raw = arg;
+ return true;
+ }
+ option = nullptr;
+ option_raw = nullptr;
+ return false;
+}
diff --git a/src/util/OptionParser.hxx b/src/util/OptionParser.hxx
new file mode 100644
index 000000000..74091bc29
--- /dev/null
+++ b/src/util/OptionParser.hxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2013 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_UTIL_OPTIONPARSER_HXX
+#define MPD_UTIL_OPTIONPARSER_HXX
+
+#include <assert.h>
+
+class OptionDef;
+
+/**
+ * Command line option parser.
+ */
+class OptionParser
+{
+ int argc;
+ char **argv;
+ char *option;
+ char *option_raw;
+ bool is_long;
+public:
+ /**
+ * Constructs #OptionParser.
+ */
+ OptionParser(int _argc, char **_argv)
+ : argc(_argc - 1), argv(_argv + 1),
+ option(nullptr), option_raw(nullptr), is_long(false) { }
+
+ /**
+ * Checks if there are command line entries to process.
+ */
+ bool HasEntries() const { return argc > 0; }
+
+ /**
+ * Gets the last parsed option.
+ */
+ char *GetOption() {
+ assert(option_raw != nullptr);
+ return option_raw;
+ }
+
+ /**
+ * Checks if current option is a specified option.
+ */
+ bool CheckOption(const OptionDef& opt);
+
+ /**
+ * Checks if current option is a specified option
+ * or specified alternative option.
+ */
+ bool CheckOption(const OptionDef& opt, const OptionDef &alt_opt) {
+ return CheckOption(opt) || CheckOption(alt_opt);
+ }
+
+ /**
+ * Parses current command line entry.
+ * Returns true on success, false otherwise.
+ * Regardless of result, advances current position to the next
+ * command line entry.
+ */
+ bool ParseNext();
+
+ /**
+ * Checks if specified string is a command line option.
+ */
+ static bool IsOption(const char *s) {
+ assert(s != nullptr);
+ return s[0] == '-';
+ }
+};
+
+#endif
diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx
index a3659b8f4..5e86503e8 100644
--- a/src/util/PeakBuffer.cxx
+++ b/src/util/PeakBuffer.cxx
@@ -18,46 +18,39 @@
*/
#include "PeakBuffer.hxx"
-#include "HugeAllocator.hxx"
-#include "fifo_buffer.h"
+#include "DynamicFifoBuffer.hxx"
#include <algorithm>
#include <assert.h>
-#include <stdint.h>
#include <string.h>
PeakBuffer::~PeakBuffer()
{
- if (normal_buffer != nullptr)
- fifo_buffer_free(normal_buffer);
-
- if (peak_buffer != nullptr)
- HugeFree(peak_buffer, peak_size);
+ delete normal_buffer;
+ delete peak_buffer;
}
bool
PeakBuffer::IsEmpty() const
{
- return (normal_buffer == nullptr ||
- fifo_buffer_is_empty(normal_buffer)) &&
- (peak_buffer == nullptr ||
- fifo_buffer_is_empty(peak_buffer));
+ return (normal_buffer == nullptr || normal_buffer->IsEmpty()) &&
+ (peak_buffer == nullptr || peak_buffer->IsEmpty());
}
-const void *
-PeakBuffer::Read(size_t *length_r) const
+WritableBuffer<void>
+PeakBuffer::Read() const
{
if (normal_buffer != nullptr) {
- const void *p = fifo_buffer_read(normal_buffer, length_r);
- if (p != nullptr)
- return p;
+ const auto p = normal_buffer->Read();
+ if (!p.IsEmpty())
+ return p.ToVoid();
}
if (peak_buffer != nullptr) {
- const void *p = fifo_buffer_read(peak_buffer, length_r);
- if (p != nullptr)
- return p;
+ const auto p = peak_buffer->Read();
+ if (!p.IsEmpty())
+ return p.ToVoid();
}
return nullptr;
@@ -66,15 +59,15 @@ PeakBuffer::Read(size_t *length_r) const
void
PeakBuffer::Consume(size_t length)
{
- if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) {
- fifo_buffer_consume(normal_buffer, length);
+ if (normal_buffer != nullptr && !normal_buffer->IsEmpty()) {
+ normal_buffer->Consume(length);
return;
}
- if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) {
- fifo_buffer_consume(peak_buffer, length);
- if (fifo_buffer_is_empty(peak_buffer)) {
- HugeFree(peak_buffer, peak_size);
+ if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) {
+ peak_buffer->Consume(length);
+ if (peak_buffer->IsEmpty()) {
+ delete peak_buffer;
peak_buffer = nullptr;
}
@@ -83,7 +76,7 @@ PeakBuffer::Consume(size_t length)
}
static size_t
-AppendTo(fifo_buffer *buffer, const void *data, size_t length)
+AppendTo(DynamicFifoBuffer<uint8_t> &buffer, const void *data, size_t length)
{
assert(data != nullptr);
assert(length > 0);
@@ -91,14 +84,13 @@ AppendTo(fifo_buffer *buffer, const void *data, size_t length)
size_t total = 0;
do {
- size_t max_length;
- void *p = fifo_buffer_write(buffer, &max_length);
- if (p == nullptr)
+ const auto p = buffer.Write();
+ if (p.IsEmpty())
break;
- const size_t nbytes = std::min(length, max_length);
- memcpy(p, data, nbytes);
- fifo_buffer_append(buffer, nbytes);
+ const size_t nbytes = std::min(length, p.size);
+ memcpy(p.data, data, nbytes);
+ buffer.Append(nbytes);
data = (const uint8_t *)data + nbytes;
length -= nbytes;
@@ -114,15 +106,15 @@ PeakBuffer::Append(const void *data, size_t length)
if (length == 0)
return true;
- if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) {
- size_t nbytes = AppendTo(peak_buffer, data, length);
+ if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) {
+ size_t nbytes = AppendTo(*peak_buffer, data, length);
return nbytes == length;
}
if (normal_buffer == nullptr)
- normal_buffer = fifo_buffer_new(normal_size);
+ normal_buffer = new DynamicFifoBuffer<uint8_t>(normal_size);
- size_t nbytes = AppendTo(normal_buffer, data, length);
+ size_t nbytes = AppendTo(*normal_buffer, data, length);
if (nbytes > 0) {
data = (const uint8_t *)data + nbytes;
length -= nbytes;
@@ -131,13 +123,11 @@ PeakBuffer::Append(const void *data, size_t length)
}
if (peak_buffer == nullptr && peak_size > 0) {
- peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
+ peak_buffer = new DynamicFifoBuffer<uint8_t>(peak_size);
if (peak_buffer == nullptr)
return false;
-
- fifo_buffer_init(peak_buffer, peak_size);
}
- nbytes = AppendTo(peak_buffer, data, length);
+ nbytes = AppendTo(*peak_buffer, data, length);
return nbytes == length;
}
diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx
index a3f385e3e..66f4c1a9f 100644
--- a/src/util/PeakBuffer.hxx
+++ b/src/util/PeakBuffer.hxx
@@ -20,11 +20,14 @@
#ifndef MPD_PEAK_BUFFER_HXX
#define MPD_PEAK_BUFFER_HXX
+#include "WritableBuffer.hxx"
#include "Compiler.h"
#include <stddef.h>
+#include <stdint.h>
-struct fifo_buffer;
+template<typename T> struct WritableBuffer;
+template<typename T> class DynamicFifoBuffer;
/**
* A FIFO-like buffer that will allocate more memory on demand to
@@ -34,7 +37,7 @@ struct fifo_buffer;
class PeakBuffer {
size_t normal_size, peak_size;
- fifo_buffer *normal_buffer, *peak_buffer;
+ DynamicFifoBuffer<uint8_t> *normal_buffer, *peak_buffer;
public:
PeakBuffer(size_t _normal_size, size_t _peak_size)
@@ -57,7 +60,9 @@ public:
gcc_pure
bool IsEmpty() const;
- const void *Read(size_t *length_r) const;
+ gcc_pure
+ WritableBuffer<void> Read() const;
+
void Consume(size_t length);
bool Append(const void *data, size_t length);
diff --git a/src/util/SplitString.cxx b/src/util/SplitString.cxx
new file mode 100644
index 000000000..c8314f28f
--- /dev/null
+++ b/src/util/SplitString.cxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2013 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 "SplitString.hxx"
+
+#include <string.h>
+
+SplitString::SplitString(const char *s, char separator)
+ :first(nullptr)
+{
+ const char *x = strchr(s, separator);
+ if (x == nullptr)
+ return;
+
+ size_t length = x - s;
+ second = x + 1;
+
+ first = new char[length + 1];
+ memcpy(first, s, length);
+ first[length] = 0;
+}
diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx
new file mode 100644
index 000000000..a8512b13c
--- /dev/null
+++ b/src/util/SplitString.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2013 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_SPLIT_STRING_HXX
+#define MPD_SPLIT_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 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;
+ }
+};
+
+#endif
diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx
index 7e295bf90..512a75f5c 100644
--- a/src/util/StringUtil.cxx
+++ b/src/util/StringUtil.cxx
@@ -22,6 +22,7 @@
#include "ASCII.hxx"
#include <assert.h>
+#include <string.h>
const char *
strchug_fast(const char *p)
@@ -33,6 +34,13 @@ strchug_fast(const char *p)
}
bool
+StringStartsWith(const char *haystack, const char *needle)
+{
+ const size_t length = strlen(needle);
+ return memcmp(haystack, needle, length) == 0;
+}
+
+bool
string_array_contains(const char *const* haystack, const char *needle)
{
assert(haystack != nullptr);
diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx
index 1c67910a9..f25160107 100644
--- a/src/util/StringUtil.hxx
+++ b/src/util/StringUtil.hxx
@@ -40,6 +40,10 @@ strchug_fast(char *p)
return const_cast<char *>(strchug_fast((const char *)p));
}
+gcc_pure
+bool
+StringStartsWith(const char *haystack, const char *needle);
+
/**
* Checks whether a string array contains the specified string.
*
diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx
index 1c8af23fd..a57f5b20c 100644
--- a/src/util/Tokenizer.cxx
+++ b/src/util/Tokenizer.cxx
@@ -24,11 +24,6 @@
#include "Error.hxx"
#include "Domain.hxx"
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
static constexpr Domain tokenizer_domain("tokenizer");
static inline bool
diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx
index 2609db2cf..174c977e1 100644
--- a/src/util/UriUtil.cxx
+++ b/src/util/UriUtil.cxx
@@ -27,6 +27,16 @@ bool uri_has_scheme(const char *uri)
return strstr(uri, "://") != nullptr;
}
+std::string
+uri_get_scheme(const char *uri)
+{
+ const char *end = strstr(uri, "://");
+ if (end == nullptr)
+ end = uri;
+
+ return std::string(uri, end);
+}
+
/* suffixes should be ascii only characters */
const char *
uri_get_suffix(const char *uri)
diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx
index 78d0a6bff..20e468103 100644
--- a/src/util/UriUtil.hxx
+++ b/src/util/UriUtil.hxx
@@ -31,6 +31,13 @@
gcc_pure
bool uri_has_scheme(const char *uri);
+/**
+ * Returns the scheme name of the specified URI, or an empty string.
+ */
+gcc_pure
+std::string
+uri_get_scheme(const char *uri);
+
gcc_pure
const char *
uri_get_suffix(const char *uri);
diff --git a/src/util/VarSize.hxx b/src/util/VarSize.hxx
new file mode 100644
index 000000000..04f1bf580
--- /dev/null
+++ b/src/util/VarSize.hxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MPD_VAR_SIZE_HXX
+#define MPD_VAR_SIZE_HXX
+
+#include "Alloc.hxx"
+#include "Compiler.h"
+
+#include <type_traits>
+#include <utility>
+#include <new>
+
+/**
+ * Allocate and construct a variable-size object. That is useful for
+ * example when you want to store a variable-length string as the last
+ * attribute without the overhead of a second allocation.
+ *
+ * @param T a struct/class with a variable-size last attribute
+ * @param declared_tail_size the declared size of the last element in
+ * #T
+ * @param real_tail_size the real required size of the last element in
+ * #T
+ */
+template<class T, typename... Args>
+gcc_malloc
+T *
+NewVarSize(size_t declared_tail_size, size_t real_tail_size, Args&&... args)
+{
+ static_assert(std::is_standard_layout<T>::value,
+ "Not standard-layout");
+
+ /* determine the total size of this instance */
+ size_t size = sizeof(T) - declared_tail_size + real_tail_size;
+
+ /* allocate memory */
+ T *instance = (T *)xalloc(size);
+
+ /* call the constructor */
+ new(instance) T(std::forward<Args>(args)...);
+
+ return instance;
+}
+
+template<typename T>
+gcc_nonnull_all
+void
+DeleteVarSize(T *instance)
+{
+ /* call the destructor */
+ instance->T::~T();
+
+ /* free memory */
+ free(instance);
+}
+
+#endif
diff --git a/src/util/WritableBuffer.hxx b/src/util/WritableBuffer.hxx
index 4e529cfad..7267813f5 100644
--- a/src/util/WritableBuffer.hxx
+++ b/src/util/WritableBuffer.hxx
@@ -32,7 +32,11 @@
#include "Compiler.h"
-#include <stddef.h>
+#include <cstddef>
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
/**
* A reference to a memory area that is writable.
@@ -41,47 +45,72 @@
*/
template<typename T>
struct WritableBuffer {
- typedef size_t size_type;
- typedef T *pointer_type;
- typedef const T *const_pointer_type;
- typedef pointer_type iterator;
- typedef const_pointer_type const_iterator;
+ typedef size_t size_type;
+ typedef T *pointer_type;
+ typedef const T *const_pointer_type;
+ typedef pointer_type iterator;
+ typedef const_pointer_type const_iterator;
+
+ pointer_type data;
+ size_type size;
+
+ WritableBuffer() = default;
- pointer_type data;
- size_type size;
+ constexpr WritableBuffer(std::nullptr_t):data(nullptr), size(0) {}
- WritableBuffer() = default;
+ constexpr WritableBuffer(pointer_type _data, size_type _size)
+ :data(_data), size(_size) {}
- constexpr WritableBuffer(pointer_type _data, size_type _size)
- :data(_data), size(_size) {}
+ constexpr static WritableBuffer Null() {
+ return { nullptr, 0 };
+ }
+
+ /**
+ * Cast a WritableBuffer<void> to a WritableBuffer<T>. A "void"
+ * buffer records its size in bytes, and when casting to "T",
+ * the assertion below ensures that the size is a multiple of
+ * sizeof(T).
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ static WritableBuffer<T> FromVoid(WritableBuffer<void> other) {
+ static_assert(sizeof(T) > 0, "Empty base type");
+#ifndef NDEBUG
+ assert(other.size % sizeof(T) == 0);
+#endif
+ return WritableBuffer<T>(pointer_type(other.data),
+ other.size / sizeof(T));
+ }
- constexpr static WritableBuffer Null() {
- return { nullptr, 0 };
- }
+ constexpr WritableBuffer<void> ToVoid() const {
+ static_assert(sizeof(T) > 0, "Empty base type");
+ return WritableBuffer<void>(data, size * sizeof(T));
+ }
- constexpr bool IsNull() const {
- return data == nullptr;
- }
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
- constexpr bool IsEmpty() const {
- return size == 0;
- }
+ constexpr bool IsEmpty() const {
+ return size == 0;
+ }
- constexpr iterator begin() const {
- return data;
- }
+ constexpr iterator begin() const {
+ return data;
+ }
- constexpr iterator end() const {
- return data + size;
- }
+ constexpr iterator end() const {
+ return data + size;
+ }
- constexpr const_iterator cbegin() const {
- return data;
- }
+ constexpr const_iterator cbegin() const {
+ return data;
+ }
- constexpr const_iterator cend() const {
- return data + size;
- }
+ constexpr const_iterator cend() const {
+ return data + size;
+ }
};
#endif
diff --git a/src/util/fifo_buffer.c b/src/util/fifo_buffer.c
deleted file mode 100644
index 162ddf946..000000000
--- a/src/util/fifo_buffer.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "config.h"
-#include "fifo_buffer.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct fifo_buffer {
- size_t size, start, end;
- unsigned char buffer[sizeof(size_t)];
-};
-
-struct fifo_buffer *
-fifo_buffer_new(size_t size)
-{
- struct fifo_buffer *buffer;
-
- assert(size > 0);
-
- buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) -
- sizeof(buffer->buffer) + size);
-
- buffer->size = size;
- buffer->start = 0;
- buffer->end = 0;
-
- return buffer;
-}
-
-void
-fifo_buffer_init(struct fifo_buffer *buffer, size_t size)
-{
- buffer->size = size - (sizeof(*buffer) - sizeof(buffer->buffer));
- buffer->start = 0;
- buffer->end = 0;
-}
-
-static void
-fifo_buffer_move(struct fifo_buffer *buffer);
-
-struct fifo_buffer *
-fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size)
-{
- if (buffer == NULL)
- return new_size > 0
- ? fifo_buffer_new(new_size)
- : NULL;
-
- /* existing data must fit in new size */
- assert(new_size >= buffer->end - buffer->start);
-
- if (new_size == 0) {
- fifo_buffer_free(buffer);
- return NULL;
- }
-
- /* compress the buffer when we're shrinking and the tail of
- the buffer would exceed the new size */
- if (buffer->end > new_size)
- fifo_buffer_move(buffer);
-
- /* existing data must fit in new size: second check */
- assert(buffer->end <= new_size);
-
- buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) +
- new_size);
- buffer->size = new_size;
- return buffer;
-}
-
-void
-fifo_buffer_free(struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- g_free(buffer);
-}
-
-size_t
-fifo_buffer_capacity(const struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- return buffer->size;
-}
-
-size_t
-fifo_buffer_available(const struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- return buffer->end - buffer->start;
-}
-
-void
-fifo_buffer_clear(struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- buffer->start = 0;
- buffer->end = 0;
-}
-
-const void *
-fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r)
-{
- assert(buffer != NULL);
- assert(buffer->end >= buffer->start);
- assert(length_r != NULL);
-
- if (buffer->start == buffer->end)
- /* the buffer is empty */
- return NULL;
-
- *length_r = buffer->end - buffer->start;
- return buffer->buffer + buffer->start;
-}
-
-void
-fifo_buffer_consume(struct fifo_buffer *buffer, size_t length)
-{
- assert(buffer != NULL);
- assert(buffer->end >= buffer->start);
- assert(buffer->start + length <= buffer->end);
-
- buffer->start += length;
-}
-
-/**
- * Move data to the beginning of the buffer, to make room at the end.
- */
-static void
-fifo_buffer_move(struct fifo_buffer *buffer)
-{
- if (buffer->start == 0)
- return;
-
- if (buffer->end > buffer->start)
- memmove(buffer->buffer,
- buffer->buffer + buffer->start,
- buffer->end - buffer->start);
-
- buffer->end -= buffer->start;
- buffer->start = 0;
-}
-
-void *
-fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r)
-{
- assert(buffer != NULL);
- assert(buffer->end <= buffer->size);
- assert(max_length_r != NULL);
-
- if (buffer->end == buffer->size) {
- fifo_buffer_move(buffer);
- if (buffer->end == buffer->size)
- return NULL;
- } else if (buffer->start > 0 && buffer->start == buffer->end) {
- buffer->start = 0;
- buffer->end = 0;
- }
-
- *max_length_r = buffer->size - buffer->end;
- return buffer->buffer + buffer->end;
-}
-
-void
-fifo_buffer_append(struct fifo_buffer *buffer, size_t length)
-{
- assert(buffer != NULL);
- assert(buffer->end >= buffer->start);
- assert(buffer->end + length <= buffer->size);
-
- buffer->end += length;
-}
-
-bool
-fifo_buffer_is_empty(struct fifo_buffer *buffer)
-{
- return buffer->start == buffer->end;
-}
-
-bool
-fifo_buffer_is_full(struct fifo_buffer *buffer)
-{
- return buffer->start == 0 && buffer->end == buffer->size;
-}
diff --git a/src/util/fifo_buffer.h b/src/util/fifo_buffer.h
deleted file mode 100644
index ccea97d86..000000000
--- a/src/util/fifo_buffer.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** \file
- *
- * This is a general purpose FIFO buffer library. You may append data
- * at the end, while another instance reads data from the beginning.
- * It is optimized for zero-copy usage: you get pointers to the real
- * buffer, where you may operate on.
- *
- * This library is not thread safe.
- */
-
-#ifndef MPD_FIFO_BUFFER_H
-#define MPD_FIFO_BUFFER_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct fifo_buffer;
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Creates a new #fifo_buffer object. Free this object with
- * fifo_buffer_free().
- *
- * @param size the size of the buffer in bytes
- * @return the new #fifo_buffer object
- */
-struct fifo_buffer *
-fifo_buffer_new(size_t size);
-
-void
-fifo_buffer_init(struct fifo_buffer *buffer, size_t size);
-
-/**
- * Change the capacity of the #fifo_buffer, while preserving existing
- * data.
- *
- * @param buffer the old buffer, may be NULL
- * @param new_size the requested new size of the #fifo_buffer; must
- * not be smaller than the data which is stored in the old buffer
- * @return the new buffer, may be NULL if the requested new size is 0
- */
-struct fifo_buffer *
-fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size);
-
-/**
- * Frees the resources consumed by this #fifo_buffer object.
- */
-void
-fifo_buffer_free(struct fifo_buffer *buffer);
-
-/**
- * Return the capacity of the buffer, i.e. the size that was passed to
- * fifo_buffer_new().
- */
-size_t
-fifo_buffer_capacity(const struct fifo_buffer *buffer);
-
-/**
- * Return the number of bytes currently stored in the buffer.
- */
-size_t
-fifo_buffer_available(const struct fifo_buffer *buffer);
-
-/**
- * Clears all data currently in this #fifo_buffer object. This does
- * not overwrite the actuall buffer; it just resets the internal
- * pointers.
- */
-void
-fifo_buffer_clear(struct fifo_buffer *buffer);
-
-/**
- * Reads from the beginning of the buffer. To remove consumed data
- * from the buffer, call fifo_buffer_consume().
- *
- * @param buffer the #fifo_buffer object
- * @param length_r the maximum amount to read is returned here
- * @return a pointer to the beginning of the buffer, or NULL if the
- * buffer is empty
- */
-const void *
-fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r);
-
-/**
- * Marks data at the beginning of the buffer as "consumed".
- *
- * @param buffer the #fifo_buffer object
- * @param length the number of bytes which were consumed
- */
-void
-fifo_buffer_consume(struct fifo_buffer *buffer, size_t length);
-
-/**
- * Prepares writing to the buffer. This returns a buffer which you
- * can write to. To commit the write operation, call
- * fifo_buffer_append().
- *
- * @param buffer the #fifo_buffer object
- * @param max_length_r the maximum amount to write is returned here
- * @return a pointer to the end of the buffer, or NULL if the buffer
- * is already full
- */
-void *
-fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r);
-
-/**
- * Commits the write operation initiated by fifo_buffer_write().
- *
- * @param buffer the #fifo_buffer object
- * @param length the number of bytes which were written
- */
-void
-fifo_buffer_append(struct fifo_buffer *buffer, size_t length);
-
-/**
- * Checks if the buffer is empty.
- */
-bool
-fifo_buffer_is_empty(struct fifo_buffer *buffer);
-
-/**
- * Checks if the buffer is full.
- */
-bool
-fifo_buffer_is_full(struct fifo_buffer *buffer);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/src/util/growing_fifo.c b/src/util/growing_fifo.c
deleted file mode 100644
index 88431f60e..000000000
--- a/src/util/growing_fifo.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "growing_fifo.h"
-#include "fifo_buffer.h"
-
-#include <assert.h>
-#include <string.h>
-
-/**
- * Align buffer sizes at 8 kB boundaries. Must be a power of two.
- */
-static const size_t GROWING_FIFO_ALIGN = 8192;
-
-/**
- * Align the specified size to the next #GROWING_FIFO_ALIGN boundary.
- */
-static size_t
-align(size_t size)
-{
- return ((size - 1) | (GROWING_FIFO_ALIGN - 1)) + 1;
-}
-
-struct fifo_buffer *
-growing_fifo_new(void)
-{
- return fifo_buffer_new(GROWING_FIFO_ALIGN);
-}
-
-void *
-growing_fifo_write(struct fifo_buffer **buffer_p, size_t length)
-{
- assert(buffer_p != NULL);
-
- struct fifo_buffer *buffer = *buffer_p;
- assert(buffer != NULL);
-
- size_t max_length;
- void *p = fifo_buffer_write(buffer, &max_length);
- if (p != NULL && max_length >= length)
- return p;
-
- /* grow */
- size_t new_size = fifo_buffer_available(buffer) + length;
- assert(new_size > fifo_buffer_capacity(buffer));
- *buffer_p = buffer = fifo_buffer_realloc(buffer, align(new_size));
-
- /* try again */
- p = fifo_buffer_write(buffer, &max_length);
- assert(p != NULL);
- assert(max_length >= length);
-
- return p;
-}
-
-void
-growing_fifo_append(struct fifo_buffer **buffer_p,
- const void *data, size_t length)
-{
- void *p = growing_fifo_write(buffer_p, length);
- memcpy(p, data, length);
- fifo_buffer_append(*buffer_p, length);
-}
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index 21a12d294..ff3464099 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -21,6 +21,7 @@
#include "DatabaseRegistry.hxx"
#include "DatabasePlugin.hxx"
#include "DatabaseSelection.hxx"
+#include "DatabaseListener.hxx"
#include "Directory.hxx"
#include "Song.hxx"
#include "PlaylistVector.hxx"
@@ -28,6 +29,7 @@
#include "ConfigData.hxx"
#include "tag/TagConfig.hxx"
#include "fs/Path.hxx"
+#include "event/Loop.hxx"
#include "util/Error.hxx"
#include <glib.h>
@@ -39,15 +41,21 @@ using std::endl;
#include <stdlib.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
+#ifdef HAVE_LIBUPNP
+#include "InputStream.hxx"
+size_t
+InputStream::LockRead(void *, size_t, Error &)
{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
+ return 0;
}
+#endif
+
+class MyDatabaseListener final : public DatabaseListener {
+public:
+ virtual void OnDatabaseModified() override {
+ cout << "DatabaseModified" << endl;
+ }
+};
static bool
DumpDirectory(const Directory &directory, Error &)
@@ -59,7 +67,10 @@ DumpDirectory(const Directory &directory, Error &)
static bool
DumpSong(Song &song, Error &)
{
- cout << "S " << song.parent->path << "/" << song.uri << endl;
+ cout << "S ";
+ if (song.parent != nullptr && !song.parent->IsRoot())
+ cout << song.parent->path << "/";
+ cout << song.uri << endl;
return true;
}
@@ -94,8 +105,6 @@ main(int argc, char **argv)
g_thread_init(nullptr);
#endif
- g_log_set_default_handler(my_log_func, nullptr);
-
/* initialize MPD */
config_global_init();
@@ -108,14 +117,18 @@ main(int argc, char **argv)
TagLoadConfig();
+ EventLoop event_loop;
+ MyDatabaseListener database_listener;
+
/* do it */
const struct config_param *path = config_get_param(CONF_DB_FILE);
- config_param param("database", path->line);
+ config_param param("database", path != nullptr ? path->line : -1);
if (path != nullptr)
param.AddBlockParam("path", path->value.c_str(), path->line);
- Database *db = plugin->create(param, error);
+ Database *db = plugin->create(event_loop, database_listener,
+ param, error);
if (db == nullptr) {
cerr << error.GetMessage() << endl;
diff --git a/test/FakeDecoderAPI.cxx b/test/FakeDecoderAPI.cxx
new file mode 100644
index 000000000..3045722cf
--- /dev/null
+++ b/test/FakeDecoderAPI.cxx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2003-2012 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 "DecoderAPI.hxx"
+#include "InputStream.hxx"
+#include "util/Error.hxx"
+#include "Compiler.h"
+
+#include <glib.h>
+
+#include <unistd.h>
+
+void
+decoder_initialized(gcc_unused Decoder &decoder,
+ gcc_unused const AudioFormat audio_format,
+ gcc_unused bool seekable,
+ gcc_unused float total_time)
+{
+}
+
+DecoderCommand
+decoder_get_command(gcc_unused Decoder &decoder)
+{
+ return DecoderCommand::NONE;
+}
+
+void
+decoder_command_finished(gcc_unused Decoder &decoder)
+{
+}
+
+double
+decoder_seek_where(gcc_unused Decoder &decoder)
+{
+ return 1.0;
+}
+
+void
+decoder_seek_error(gcc_unused Decoder &decoder)
+{
+}
+
+size_t
+decoder_read(gcc_unused Decoder *decoder,
+ InputStream &is,
+ void *buffer, size_t length)
+{
+ return is.LockRead(buffer, length, IgnoreError());
+}
+
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *_buffer, size_t size)
+{
+ uint8_t *buffer = (uint8_t *)_buffer;
+
+ while (size > 0) {
+ size_t nbytes = decoder_read(decoder, is, buffer, size);
+ if (nbytes == 0)
+ return false;
+
+ buffer += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+ while (size > 0) {
+ char buffer[1024];
+ size_t nbytes = decoder_read(decoder, is, buffer,
+ std::min(sizeof(buffer), size));
+ if (nbytes == 0)
+ return false;
+
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+void
+decoder_timestamp(gcc_unused Decoder &decoder,
+ gcc_unused double t)
+{
+}
+
+DecoderCommand
+decoder_data(gcc_unused Decoder &decoder,
+ gcc_unused InputStream *is,
+ const void *data, size_t datalen,
+ gcc_unused uint16_t kbit_rate)
+{
+ gcc_unused ssize_t nbytes = write(1, data, datalen);
+ return DecoderCommand::NONE;
+}
+
+DecoderCommand
+decoder_tag(gcc_unused Decoder &decoder,
+ gcc_unused InputStream *is,
+ gcc_unused Tag &&tag)
+{
+ return DecoderCommand::NONE;
+}
+
+void
+decoder_replay_gain(gcc_unused Decoder &decoder,
+ const ReplayGainInfo *rgi)
+{
+ const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
+ if (tuple->IsDefined())
+ fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
+ if (tuple->IsDefined())
+ fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+}
+
+void
+decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
+{
+}
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index d11562930..b0702d3be 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -19,12 +19,10 @@
#include "config.h"
#include "TagSave.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "SongEnumerator.hxx"
-#include "Directory.hxx"
#include "InputStream.hxx"
#include "ConfigGlobal.hxx"
-#include "DecoderAPI.hxx"
#include "DecoderList.hxx"
#include "InputInit.hxx"
#include "IOThread.hxx"
@@ -40,110 +38,14 @@
#include <unistd.h>
#include <stdlib.h>
-Directory::Directory() {}
-Directory::~Directory() {}
-
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
-void
-decoder_initialized(gcc_unused Decoder &decoder,
- gcc_unused const AudioFormat audio_format,
- gcc_unused bool seekable,
- gcc_unused float total_time)
-{
-}
-
-DecoderCommand
-decoder_get_command(gcc_unused Decoder &decoder)
-{
- return DecoderCommand::NONE;
-}
-
-void
-decoder_command_finished(gcc_unused Decoder &decoder)
-{
-}
-
-double
-decoder_seek_where(gcc_unused Decoder &decoder)
-{
- return 1.0;
-}
-
-void
-decoder_seek_error(gcc_unused Decoder &decoder)
-{
-}
-
-size_t
-decoder_read(gcc_unused Decoder *decoder,
- InputStream &is,
- void *buffer, size_t length)
-{
- return is.LockRead(buffer, length, IgnoreError());
-}
-
-void
-decoder_timestamp(gcc_unused Decoder &decoder,
- gcc_unused double t)
-{
-}
-
-DecoderCommand
-decoder_data(gcc_unused Decoder &decoder,
- gcc_unused InputStream *is,
- const void *data, size_t datalen,
- gcc_unused uint16_t kbit_rate)
-{
- gcc_unused ssize_t nbytes = write(1, data, datalen);
- return DecoderCommand::NONE;
-}
-
-DecoderCommand
-decoder_tag(gcc_unused Decoder &decoder,
- gcc_unused InputStream *is,
- gcc_unused Tag &&tag)
-{
- return DecoderCommand::NONE;
-}
-
-void
-decoder_replay_gain(gcc_unused Decoder &decoder,
- const ReplayGainInfo *rgi)
-{
- const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
- if (tuple->IsDefined())
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
- if (tuple->IsDefined())
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-}
-
-void
-decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
-{
-}
-
int main(int argc, char **argv)
{
const char *uri;
InputStream *is = NULL;
- Song *song;
if (argc != 3) {
- g_printerr("Usage: dump_playlist CONFIG URI\n");
- return 1;
+ fprintf(stderr, "Usage: dump_playlist CONFIG URI\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
@@ -155,16 +57,14 @@ int main(int argc, char **argv)
g_thread_init(NULL);
#endif
- g_log_set_default_handler(my_log_func, NULL);
-
/* initialize MPD */
config_global_init();
Error error;
if (!ReadConfigFile(config_path, error)) {
- g_printerr("%s\n", error.GetMessage());
- return 1;
+ LogError(error);
+ return EXIT_FAILURE;
}
io_thread_init();
@@ -172,7 +72,7 @@ int main(int argc, char **argv)
if (!input_stream_global_init(error)) {
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
playlist_list_global_init();
@@ -187,47 +87,49 @@ int main(int argc, char **argv)
if (playlist == NULL) {
/* open the stream and wait until it becomes ready */
- is = InputStream::Open(uri, mutex, cond, error);
+ is = InputStream::OpenReady(uri, mutex, cond, error);
if (is == NULL) {
if (error.IsDefined())
LogError(error);
else
- g_printerr("InputStream::Open() failed\n");
+ fprintf(stderr,
+ "InputStream::Open() failed\n");
return 2;
}
- is->LockWaitReady();
-
/* open the playlist */
playlist = playlist_list_open_stream(*is, uri);
if (playlist == NULL) {
is->Close();
- g_printerr("Failed to open playlist\n");
+ fprintf(stderr, "Failed to open playlist\n");
return 2;
}
}
/* dump the playlist */
+ DetachedSong *song;
while ((song = playlist->NextSong()) != NULL) {
- g_print("%s\n", song->uri);
-
- if (song->end_ms > 0)
- g_print("range: %u:%02u..%u:%02u\n",
- song->start_ms / 60000,
- (song->start_ms / 1000) % 60,
- song->end_ms / 60000,
- (song->end_ms / 1000) % 60);
- else if (song->start_ms > 0)
- g_print("range: %u:%02u..\n",
- song->start_ms / 60000,
- (song->start_ms / 1000) % 60);
-
- if (song->tag != NULL)
- tag_save(stdout, *song->tag);
-
- song->Free();
+ printf("%s\n", song->GetURI());
+
+ const unsigned start_ms = song->GetStartMS();
+ const unsigned end_ms = song->GetEndMS();
+
+ if (end_ms > 0)
+ printf("range: %u:%02u..%u:%02u\n",
+ start_ms / 60000,
+ (start_ms / 1000) % 60,
+ end_ms / 60000,
+ (end_ms / 1000) % 60);
+ else if (start_ms > 0)
+ printf("range: %u:%02u..\n",
+ start_ms / 60000,
+ (start_ms / 1000) % 60);
+
+ tag_save(stdout, song->GetTag());
+
+ delete song;
}
/* deinitialize everything */
diff --git a/test/dump_rva2.cxx b/test/dump_rva2.cxx
index e1ba5336a..e79703e50 100644
--- a/test/dump_rva2.cxx
+++ b/test/dump_rva2.cxx
@@ -24,16 +24,16 @@
#include "ConfigGlobal.hxx"
#include "util/Error.hxx"
#include "fs/Path.hxx"
+#include "Log.hxx"
#include <id3tag.h>
-#include <glib.h>
-
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include <stdlib.h>
+#include <stdio.h>
const char *
config_get_string(gcc_unused enum ConfigOption option,
@@ -50,8 +50,8 @@ int main(int argc, char **argv)
#endif
if (argc != 2) {
- g_printerr("Usage: read_rva2 FILE\n");
- return 1;
+ fprintf(stderr, "Usage: read_rva2 FILE\n");
+ return EXIT_FAILURE;
}
const char *path = argv[1];
@@ -60,9 +60,9 @@ int main(int argc, char **argv)
struct id3_tag *tag = tag_id3_load(Path::FromFS(path), error);
if (tag == NULL) {
if (error.IsDefined())
- g_printerr("%s\n", error.GetMessage());
+ LogError(error);
else
- g_printerr("No ID3 tag found\n");
+ fprintf(stderr, "No ID3 tag found\n");
return EXIT_FAILURE;
}
@@ -74,19 +74,19 @@ int main(int argc, char **argv)
id3_tag_delete(tag);
if (!success) {
- g_printerr("No RVA2 tag found\n");
+ fprintf(stderr, "No RVA2 tag found\n");
return EXIT_FAILURE;
}
const ReplayGainTuple *tuple = &replay_gain.tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
return EXIT_SUCCESS;
}
diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx
index bb84f5cce..764d3f24b 100644
--- a/test/dump_text_file.cxx
+++ b/test/dump_text_file.cxx
@@ -39,16 +39,6 @@
#include <stdlib.h>
static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
-static void
dump_text_file(TextInputStream &is)
{
std::string line;
@@ -59,23 +49,6 @@ dump_text_file(TextInputStream &is)
static int
dump_input_stream(InputStream &is)
{
- Error error;
-
- is.Lock();
-
- /* wait until the stream becomes ready */
-
- is.WaitReady();
-
- if (!is.Check(error)) {
- LogError(error);
- is.Unlock();
- return EXIT_FAILURE;
- }
-
- /* read data and tags from the stream */
-
- is.Unlock();
{
TextInputStream tis(is);
dump_text_file(tis);
@@ -83,6 +56,7 @@ dump_input_stream(InputStream &is)
is.Lock();
+ Error error;
if (!is.Check(error)) {
LogError(error);
is.Unlock();
@@ -99,8 +73,8 @@ int main(int argc, char **argv)
int ret;
if (argc != 2) {
- g_printerr("Usage: run_input URI\n");
- return 1;
+ fprintf(stderr, "Usage: run_input URI\n");
+ return EXIT_FAILURE;
}
/* initialize GLib */
@@ -109,8 +83,6 @@ int main(int argc, char **argv)
g_thread_init(NULL);
#endif
- g_log_set_default_handler(my_log_func, NULL);
-
/* initialize MPD */
config_global_init();
@@ -133,7 +105,7 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
- InputStream *is = InputStream::Open(argv[1], mutex, cond, error);
+ InputStream *is = InputStream::OpenReady(argv[1], mutex, cond, error);
if (is != NULL) {
ret = dump_input_stream(*is);
is->Close();
@@ -141,8 +113,8 @@ int main(int argc, char **argv)
if (error.IsDefined())
LogError(error);
else
- g_printerr("input_stream::Open() failed\n");
- ret = 2;
+ fprintf(stderr, "input_stream::Open() failed\n");
+ ret = EXIT_FAILURE;
}
/* deinitialize everything */
diff --git a/test/read_conf.cxx b/test/read_conf.cxx
index d5eacec67..b27c70497 100644
--- a/test/read_conf.cxx
+++ b/test/read_conf.cxx
@@ -21,40 +21,28 @@
#include "ConfigGlobal.hxx"
#include "fs/Path.hxx"
#include "util/Error.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
#include <assert.h>
-
-static void
-my_log_func(gcc_unused const gchar *log_domain,
- GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_level > G_LOG_LEVEL_WARNING)
- return;
-
- g_printerr("%s\n", message);
-}
+#include <stdio.h>
+#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc != 3) {
- g_printerr("Usage: read_conf FILE SETTING\n");
- return 1;
+ fprintf(stderr, "Usage: read_conf FILE SETTING\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
const char *name = argv[2];
- g_log_set_default_handler(my_log_func, NULL);
-
config_global_init();
Error error;
if (!ReadConfigFile(config_path, error)) {
- g_printerr("%s:", error.GetMessage());
- return 1;
+ LogError(error);
+ return EXIT_FAILURE;
}
ConfigOption option = ParseConfigOptionName(name);
@@ -63,11 +51,11 @@ int main(int argc, char **argv)
: nullptr;
int ret;
if (value != NULL) {
- g_print("%s\n", value);
- ret = 0;
+ printf("%s\n", value);
+ ret = EXIT_SUCCESS;
} else {
- g_printerr("No such setting: %s\n", name);
- ret = 2;
+ fprintf(stderr, "No such setting: %s\n", name);
+ ret = EXIT_FAILURE;
}
config_global_finish();
diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx
index 8426443ae..95aacf6db 100644
--- a/test/read_mixer.cxx
+++ b/test/read_mixer.cxx
@@ -21,12 +21,13 @@
#include "MixerControl.hxx"
#include "MixerList.hxx"
#include "FilterRegistry.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "GlobalEvents.hxx"
#include "Main.hxx"
#include "event/Loop.hxx"
#include "ConfigData.hxx"
#include "util/Error.hxx"
+#include "Log.hxx"
#include <glib.h>
@@ -101,22 +102,13 @@ filter_plugin_by_name(gcc_unused const char *name)
return NULL;
}
-bool
-pcm_volume(gcc_unused void *buffer, gcc_unused size_t length,
- gcc_unused SampleFormat format,
- gcc_unused int volume)
-{
- assert(false);
- return false;
-}
-
int main(int argc, gcc_unused char **argv)
{
int volume;
if (argc != 2) {
- g_printerr("Usage: read_mixer PLUGIN\n");
- return 1;
+ fprintf(stderr, "Usage: read_mixer PLUGIN\n");
+ return EXIT_FAILURE;
}
#if !GLIB_CHECK_VERSION(2,32,0)
@@ -129,14 +121,14 @@ int main(int argc, gcc_unused char **argv)
Mixer *mixer = mixer_new(&alsa_mixer_plugin, nullptr,
config_param(), error);
if (mixer == NULL) {
- g_printerr("mixer_new() failed: %s\n", error.GetMessage());
- return 2;
+ LogError(error, "mixer_new() failed");
+ return EXIT_FAILURE;
}
if (!mixer_open(mixer, error)) {
mixer_free(mixer);
- g_printerr("failed to open the mixer: %s\n", error.GetMessage());
- return 2;
+ LogError(error, "failed to open the mixer");
+ return EXIT_FAILURE;
}
volume = mixer_get_volume(mixer, error);
@@ -149,13 +141,12 @@ int main(int argc, gcc_unused char **argv)
if (volume < 0) {
if (error.IsDefined()) {
- g_printerr("failed to read volume: %s\n",
- error.GetMessage());
+ LogError(error, "failed to read volume");
} else
- g_printerr("failed to read volume\n");
- return 2;
+ fprintf(stderr, "failed to read volume\n");
+ return EXIT_FAILURE;
}
- g_print("%d\n", volume);
+ printf("%d\n", volume);
return 0;
}
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index 90f1424d9..49d40befb 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "IOThread.hxx"
#include "DecoderList.hxx"
-#include "DecoderAPI.hxx"
+#include "DecoderPlugin.hxx"
#include "InputInit.hxx"
#include "InputStream.hxx"
#include "AudioFormat.hxx"
@@ -37,103 +37,31 @@
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
-void
-decoder_initialized(gcc_unused Decoder &decoder,
- gcc_unused const AudioFormat audio_format,
- gcc_unused bool seekable,
- gcc_unused float total_time)
-{
-}
-
-DecoderCommand
-decoder_get_command(gcc_unused Decoder &decoder)
-{
- return DecoderCommand::NONE;
-}
-
-void
-decoder_command_finished(gcc_unused Decoder &decoder)
-{
-}
-
-double
-decoder_seek_where(gcc_unused Decoder &decoder)
-{
- return 1.0;
-}
-
-void
-decoder_seek_error(gcc_unused Decoder &decoder)
-{
-}
-
-size_t
-decoder_read(gcc_unused Decoder *decoder,
- InputStream &is,
- void *buffer, size_t length)
-{
- return is.LockRead(buffer, length, IgnoreError());
-}
-
-void
-decoder_timestamp(gcc_unused Decoder &decoder,
- gcc_unused double t)
-{
-}
-
-DecoderCommand
-decoder_data(gcc_unused Decoder &decoder,
- gcc_unused InputStream *is,
- const void *data, size_t datalen,
- gcc_unused uint16_t kbit_rate)
-{
- gcc_unused ssize_t nbytes = write(1, data, datalen);
- return DecoderCommand::NONE;
-}
-
-DecoderCommand
-decoder_tag(gcc_unused Decoder &decoder,
- gcc_unused InputStream *is,
- gcc_unused Tag &&tag)
-{
- return DecoderCommand::NONE;
-}
-
-void
-decoder_replay_gain(gcc_unused Decoder &decoder,
- gcc_unused const ReplayGainInfo *replay_gain_info)
-{
-}
-
-void
-decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
-{
-}
-
static bool empty = true;
static void
print_duration(unsigned seconds, gcc_unused void *ctx)
{
- g_print("duration=%d\n", seconds);
+ printf("duration=%d\n", seconds);
}
static void
print_tag(TagType type, const char *value, gcc_unused void *ctx)
{
- g_print("[%s]=%s\n", tag_item_names[type], value);
+ printf("[%s]=%s\n", tag_item_names[type], value);
empty = false;
}
static void
print_pair(const char *name, const char *value, gcc_unused void *ctx)
{
- g_print("\"%s\"=%s\n", name, value);
+ printf("\"%s\"=%s\n", name, value);
}
static const struct tag_handler print_handler = {
@@ -153,8 +81,8 @@ int main(int argc, char **argv)
#endif
if (argc != 3) {
- g_printerr("Usage: read_tags DECODER FILE\n");
- return 1;
+ fprintf(stderr, "Usage: read_tags DECODER FILE\n");
+ return EXIT_FAILURE;
}
decoder_name = argv[1];
@@ -177,8 +105,8 @@ int main(int argc, char **argv)
plugin = decoder_plugin_from_name(decoder_name);
if (plugin == NULL) {
- g_printerr("No such decoder: %s\n", decoder_name);
- return 1;
+ fprintf(stderr, "No such decoder: %s\n", decoder_name);
+ return EXIT_FAILURE;
}
bool success = plugin->ScanFile(path, print_handler, nullptr);
@@ -186,28 +114,13 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
- InputStream *is = InputStream::Open(path, mutex, cond,
- error);
+ InputStream *is = InputStream::OpenReady(path, mutex, cond,
+ error);
if (is == NULL) {
- g_printerr("Failed to open %s: %s\n",
- path, error.GetMessage());
- return 1;
- }
-
- mutex.lock();
-
- is->WaitReady();
-
- if (!is->Check(error)) {
- mutex.unlock();
-
- g_printerr("Failed to read %s: %s\n",
- path, error.GetMessage());
+ FormatError(error, "Failed to open %s", path);
return EXIT_FAILURE;
}
- mutex.unlock();
-
success = plugin->ScanStream(*is, print_handler, nullptr);
is->Close();
}
@@ -217,8 +130,8 @@ int main(int argc, char **argv)
io_thread_deinit();
if (!success) {
- g_printerr("Failed to read tags\n");
- return 1;
+ fprintf(stderr, "Failed to read tags\n");
+ return EXIT_FAILURE;
}
if (empty) {
diff --git a/test/run_convert.cxx b/test/run_convert.cxx
index 0e873a3b3..67783592c 100644
--- a/test/run_convert.cxx
+++ b/test/run_convert.cxx
@@ -30,25 +30,14 @@
#include "ConfigGlobal.hxx"
#include "util/FifoBuffer.hxx"
#include "util/Error.hxx"
+#include "Log.hxx"
#include "stdbin.h"
-#include <glib.h>
-
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
const char *
config_get_string(gcc_unused enum ConfigOption option,
const char *default_value)
@@ -62,26 +51,23 @@ int main(int argc, char **argv)
const void *output;
if (argc != 3) {
- g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n");
+ fprintf(stderr,
+ "Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n");
return 1;
}
- g_log_set_default_handler(my_log_func, NULL);
-
Error error;
if (!audio_format_parse(in_audio_format, argv[1],
false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
AudioFormat out_audio_format_mask;
if (!audio_format_parse(out_audio_format_mask, argv[2],
true, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
out_audio_format = in_audio_format;
@@ -90,6 +76,10 @@ int main(int argc, char **argv)
const size_t in_frame_size = in_audio_format.GetFrameSize();
PcmConvert state;
+ if (!state.Open(in_audio_format, out_audio_format_mask, error)) {
+ LogError(error, "Failed to open PcmConvert");
+ return EXIT_FAILURE;
+ }
FifoBuffer<uint8_t, 4096> buffer;
@@ -115,15 +105,18 @@ int main(int argc, char **argv)
buffer.Consume(src.size);
size_t length;
- output = state.Convert(in_audio_format, src.data, src.size,
- out_audio_format, &length, error);
+ output = state.Convert(src.data, src.size,
+ &length, error);
if (output == NULL) {
- g_printerr("Failed to convert: %s\n", error.GetMessage());
- return 2;
+ state.Close();
+ LogError(error, "Failed to convert");
+ return EXIT_FAILURE;
}
gcc_unused ssize_t ignored = write(1, output, length);
}
+ state.Close();
+
return EXIT_SUCCESS;
}
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index 2f7330a1d..fb29bd3b3 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -29,21 +29,14 @@
#include "Log.hxx"
#include "stdbin.h"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
+#include <stdio.h>
struct Decoder {
const char *uri;
@@ -64,9 +57,9 @@ decoder_initialized(Decoder &decoder,
assert(!decoder.initialized);
assert(audio_format.IsValid());
- g_printerr("audio_format=%s duration=%f\n",
- audio_format_to_string(audio_format, &af_string),
- duration);
+ fprintf(stderr, "audio_format=%s duration=%f\n",
+ audio_format_to_string(audio_format, &af_string),
+ duration);
decoder.initialized = true;
}
@@ -101,6 +94,40 @@ decoder_read(gcc_unused Decoder *decoder,
return is.LockRead(buffer, length, IgnoreError());
}
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *_buffer, size_t size)
+{
+ uint8_t *buffer = (uint8_t *)_buffer;
+
+ while (size > 0) {
+ size_t nbytes = decoder_read(decoder, is, buffer, size);
+ if (nbytes == 0)
+ return false;
+
+ buffer += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+ while (size > 0) {
+ char buffer[1024];
+ size_t nbytes = decoder_read(decoder, is, buffer,
+ std::min(sizeof(buffer), size));
+ if (nbytes == 0)
+ return false;
+
+ size -= nbytes;
+ }
+
+ return true;
+}
+
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
@@ -131,13 +158,13 @@ decoder_replay_gain(gcc_unused Decoder &decoder,
{
const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
}
void
@@ -150,19 +177,19 @@ int main(int argc, char **argv)
const char *decoder_name;
if (argc != 3) {
- g_printerr("Usage: run_decoder DECODER URI >OUT\n");
- return 1;
+ fprintf(stderr, "Usage: run_decoder DECODER URI >OUT\n");
+ return EXIT_FAILURE;
}
Decoder decoder;
decoder_name = argv[1];
decoder.uri = argv[2];
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
io_thread_init();
io_thread_start();
@@ -170,15 +197,15 @@ int main(int argc, char **argv)
Error error;
if (!input_stream_global_init(error)) {
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
decoder_plugin_init_all();
decoder.plugin = decoder_plugin_from_name(decoder_name);
if (decoder.plugin == NULL) {
- g_printerr("No such decoder: %s\n", decoder_name);
- return 1;
+ fprintf(stderr, "No such decoder: %s\n", decoder_name);
+ return EXIT_FAILURE;
}
decoder.initialized = false;
@@ -195,17 +222,17 @@ int main(int argc, char **argv)
if (error.IsDefined())
LogError(error);
else
- g_printerr("InputStream::Open() failed\n");
+ fprintf(stderr, "InputStream::Open() failed\n");
- return 1;
+ return EXIT_FAILURE;
}
decoder.plugin->StreamDecode(decoder, *is);
is->Close();
} else {
- g_printerr("Decoder plugin is not usable\n");
- return 1;
+ fprintf(stderr, "Decoder plugin is not usable\n");
+ return EXIT_FAILURE;
}
decoder_plugin_deinit_all();
@@ -213,8 +240,8 @@ int main(int argc, char **argv)
io_thread_deinit();
if (!decoder.initialized) {
- g_printerr("Decoding failed\n");
- return 1;
+ fprintf(stderr, "Decoding failed\n");
+ return EXIT_FAILURE;
}
return 0;
diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx
index 838ee708e..a71b815f5 100644
--- a/test/run_encoder.cxx
+++ b/test/run_encoder.cxx
@@ -24,10 +24,11 @@
#include "AudioParser.hxx"
#include "ConfigData.hxx"
#include "util/Error.hxx"
+#include "Log.hxx"
#include "stdbin.h"
-#include <glib.h>
-
+#include <stdio.h>
+#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
@@ -50,8 +51,9 @@ int main(int argc, char **argv)
/* parse command line */
if (argc > 3) {
- g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n");
- return 1;
+ fprintf(stderr,
+ "Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n");
+ return EXIT_FAILURE;
}
if (argc > 1)
@@ -63,8 +65,8 @@ int main(int argc, char **argv)
const auto plugin = encoder_plugin_get(encoder_name);
if (plugin == NULL) {
- g_printerr("No such encoder: %s\n", encoder_name);
- return 1;
+ fprintf(stderr, "No such encoder: %s\n", encoder_name);
+ return EXIT_FAILURE;
}
config_param param;
@@ -73,9 +75,8 @@ int main(int argc, char **argv)
Error error;
const auto encoder = encoder_init(*plugin, param, error);
if (encoder == NULL) {
- g_printerr("Failed to initialize encoder: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to initialize encoder");
+ return EXIT_FAILURE;
}
/* open the encoder */
@@ -83,16 +84,14 @@ int main(int argc, char **argv)
AudioFormat audio_format(44100, SampleFormat::S16, 2);
if (argc > 2) {
if (!audio_format_parse(audio_format, argv[2], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
if (!encoder_open(encoder, audio_format, error)) {
- g_printerr("Failed to open encoder: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to open encoder");
+ return EXIT_FAILURE;
}
encoder_to_stdout(*encoder);
@@ -102,19 +101,20 @@ int main(int argc, char **argv)
ssize_t nbytes;
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
if (!encoder_write(encoder, buffer, nbytes, error)) {
- g_printerr("encoder_write() failed: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "encoder_write() failed");
+ return EXIT_FAILURE;
}
encoder_to_stdout(*encoder);
}
if (!encoder_end(encoder, error)) {
- g_printerr("encoder_flush() failed: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "encoder_flush() failed");
+ return EXIT_FAILURE;
}
encoder_to_stdout(*encoder);
+
+ encoder_close(encoder);
+ encoder_finish(encoder);
}
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index 085fc256b..3b5e803d7 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -25,16 +25,19 @@
#include "AudioFormat.hxx"
#include "FilterPlugin.hxx"
#include "FilterInternal.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "MixerControl.hxx"
#include "stdbin.h"
#include "util/Error.hxx"
#include "system/FatalError.hxx"
+#include "Log.hxx"
#include <glib.h>
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
#include <errno.h>
#include <unistd.h>
@@ -45,16 +48,6 @@ mixer_set_volume(gcc_unused Mixer *mixer,
return true;
}
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
static const struct config_param *
find_named_config_block(ConfigOption option, const char *name)
{
@@ -76,14 +69,14 @@ load_filter(const char *name)
param = find_named_config_block(CONF_AUDIO_FILTER, name);
if (param == NULL) {
- g_printerr("No such configured filter: %s\n", name);
+ fprintf(stderr, "No such configured filter: %s\n", name);
return nullptr;
}
Error error;
Filter *filter = filter_configured_new(*param, error);
if (filter == NULL) {
- g_printerr("Failed to load filter: %s\n", error.GetMessage());
+ LogError(error, "Failed to load filter");
return NULL;
}
@@ -97,8 +90,8 @@ int main(int argc, char **argv)
char buffer[4096];
if (argc < 3 || argc > 4) {
- g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
- return 1;
+ fprintf(stderr, "Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
@@ -111,8 +104,6 @@ int main(int argc, char **argv)
g_thread_init(NULL);
#endif
- g_log_set_default_handler(my_log_func, NULL);
-
/* read configuration file (mpd.conf) */
config_global_init();
@@ -124,9 +115,8 @@ int main(int argc, char **argv)
if (argc > 3) {
Error error;
if (!audio_format_parse(audio_format, argv[3], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
@@ -134,20 +124,20 @@ int main(int argc, char **argv)
Filter *filter = load_filter(argv[2]);
if (filter == NULL)
- return 1;
+ return EXIT_FAILURE;
/* open the filter */
Error error;
const AudioFormat out_audio_format = filter->Open(audio_format, error);
if (!out_audio_format.IsDefined()) {
- g_printerr("Failed to open filter: %s\n", error.GetMessage());
+ LogError(error, "Failed to open filter");
delete filter;
- return 1;
+ return EXIT_FAILURE;
}
- g_printerr("audio_format=%s\n",
- audio_format_to_string(out_audio_format, &af_string));
+ fprintf(stderr, "audio_format=%s\n",
+ audio_format_to_string(out_audio_format, &af_string));
/* play */
@@ -163,15 +153,16 @@ int main(int argc, char **argv)
dest = filter->FilterPCM(buffer, (size_t)nbytes,
&length, error);
if (dest == NULL) {
- g_printerr("Filter failed: %s\n", error.GetMessage());
+ LogError(error, "Filter failed");
filter->Close();
delete filter;
- return 1;
+ return EXIT_FAILURE;
}
nbytes = write(1, dest, length);
if (nbytes < 0) {
- g_printerr("Failed to write: %s\n", g_strerror(errno));
+ fprintf(stderr, "Failed to write: %s\n",
+ strerror(errno));
filter->Close();
delete filter;
return 1;
diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx
index c57e6e9ef..8dcc371cc 100644
--- a/test/run_inotify.cxx
+++ b/test/run_inotify.cxx
@@ -24,8 +24,6 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <sys/inotify.h>
static constexpr unsigned IN_MASK =
@@ -39,7 +37,7 @@ static void
my_inotify_callback(gcc_unused int wd, unsigned mask,
const char *name, gcc_unused void *ctx)
{
- g_print("mask=0x%x name='%s'\n", mask, name);
+ printf("mask=0x%x name='%s'\n", mask, name);
}
int main(int argc, char **argv)
@@ -47,8 +45,8 @@ int main(int argc, char **argv)
const char *path;
if (argc != 2) {
- g_printerr("Usage: run_inotify PATH\n");
- return 1;
+ fprintf(stderr, "Usage: run_inotify PATH\n");
+ return EXIT_FAILURE;
}
path = argv[1];
@@ -62,17 +60,18 @@ int main(int argc, char **argv)
nullptr, error);
if (source == NULL) {
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
int descriptor = source->Add(path, IN_MASK, error);
if (descriptor < 0) {
delete source;
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
event_loop.Run();
delete source;
+ return EXIT_SUCCESS;
}
diff --git a/test/run_input.cxx b/test/run_input.cxx
index 3817ed418..0c1ba1d36 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -33,21 +33,13 @@
#include "ArchiveList.hxx"
#endif
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdlib.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
static int
dump_input_stream(InputStream *is)
{
@@ -58,27 +50,17 @@ dump_input_stream(InputStream *is)
is->Lock();
- /* wait until the stream becomes ready */
-
- is->WaitReady();
-
- if (!is->Check(error)) {
- LogError(error);
- is->Unlock();
- return EXIT_FAILURE;
- }
-
/* print meta data */
if (!is->mime.empty())
- g_printerr("MIME type: %s\n", is->mime.c_str());
+ fprintf(stderr, "MIME type: %s\n", is->mime.c_str());
/* read data and tags from the stream */
while (!is->IsEOF()) {
Tag *tag = is->ReadTag();
if (tag != NULL) {
- g_printerr("Received a tag:\n");
+ fprintf(stderr, "Received a tag:\n");
tag_save(stderr, *tag);
delete tag;
}
@@ -114,17 +96,17 @@ int main(int argc, char **argv)
int ret;
if (argc != 2) {
- g_printerr("Usage: run_input URI\n");
- return 1;
+ fprintf(stderr, "Usage: run_input URI\n");
+ return EXIT_FAILURE;
}
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
/* initialize MPD */
@@ -147,7 +129,7 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
- is = InputStream::Open(argv[1], mutex, cond, error);
+ is = InputStream::OpenReady(argv[1], mutex, cond, error);
if (is != NULL) {
ret = dump_input_stream(is);
is->Close();
@@ -155,8 +137,8 @@ int main(int argc, char **argv)
if (error.IsDefined())
LogError(error);
else
- g_printerr("input_stream::Open() failed\n");
- ret = 2;
+ fprintf(stderr, "input_stream::Open() failed\n");
+ ret = EXIT_FAILURE;
}
/* deinitialize everything */
diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx
index 3193fefd2..091c3d61a 100644
--- a/test/run_normalize.cxx
+++ b/test/run_normalize.cxx
@@ -33,6 +33,7 @@
#include <glib.h>
#include <stddef.h>
+#include <stdio.h>
#include <unistd.h>
#include <string.h>
@@ -43,7 +44,7 @@ int main(int argc, char **argv)
ssize_t nbytes;
if (argc > 2) {
- g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n");
+ fprintf(stderr, "Usage: run_normalize [FORMAT] <IN >OUT\n");
return 1;
}
@@ -51,7 +52,7 @@ int main(int argc, char **argv)
if (argc > 1) {
Error error;
if (!audio_format_parse(audio_format, argv[1], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
+ fprintf(stderr, "Failed to parse audio format: %s\n",
error.GetMessage());
return 1;
}
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 7982bd7de..86c2452b9 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -36,6 +36,7 @@
#include "PlayerControl.hxx"
#include "stdbin.h"
#include "util/Error.hxx"
+#include "Log.hxx"
#include <glib.h>
@@ -43,6 +44,7 @@
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
EventLoop *main_loop;
@@ -83,7 +85,7 @@ load_audio_output(const char *name)
param = find_named_config_block(CONF_AUDIO_OUTPUT, name);
if (param == NULL) {
- g_printerr("No such configured audio output: %s\n", name);
+ fprintf(stderr, "No such configured audio output: %s\n", name);
return nullptr;
}
@@ -93,7 +95,7 @@ load_audio_output(const char *name)
struct audio_output *ao =
audio_output_new(*param, dummy_player_control, error);
if (ao == nullptr)
- g_printerr("%s\n", error.GetMessage());
+ LogError(error);
return ao;
}
@@ -105,21 +107,19 @@ run_output(struct audio_output *ao, AudioFormat audio_format)
Error error;
if (!ao_plugin_enable(ao, error)) {
- g_printerr("Failed to enable audio output: %s\n",
- error.GetMessage());
+ LogError(error, "Failed to enable audio output");
return false;
}
if (!ao_plugin_open(ao, audio_format, error)) {
ao_plugin_disable(ao);
- g_printerr("Failed to open audio output: %s\n",
- error.GetMessage());
+ LogError(error, "Failed to open audio output");
return false;
}
struct audio_format_string af_string;
- g_printerr("audio_format=%s\n",
- audio_format_to_string(audio_format, &af_string));
+ fprintf(stderr, "audio_format=%s\n",
+ audio_format_to_string(audio_format, &af_string));
size_t frame_size = audio_format.GetFrameSize();
@@ -145,8 +145,7 @@ run_output(struct audio_output *ao, AudioFormat audio_format)
if (consumed == 0) {
ao_plugin_close(ao);
ao_plugin_disable(ao);
- g_printerr("Failed to play: %s\n",
- error.GetMessage());
+ LogError(error, "Failed to play");
return false;
}
@@ -168,8 +167,8 @@ int main(int argc, char **argv)
Error error;
if (argc < 3 || argc > 4) {
- g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n");
- return 1;
+ fprintf(stderr, "Usage: run_output CONFIG NAME [FORMAT] <IN\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
@@ -184,8 +183,8 @@ int main(int argc, char **argv)
config_global_init();
if (!ReadConfigFile(config_path, error)) {
- g_printerr("%s\n", error.GetMessage());
- return 1;
+ LogError(error);
+ return EXIT_FAILURE;
}
main_loop = new EventLoop(EventLoop::Default());
@@ -203,9 +202,8 @@ int main(int argc, char **argv)
if (argc > 3) {
if (!audio_format_parse(audio_format, argv[3], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
diff --git a/test/run_resolver.cxx b/test/run_resolver.cxx
index 7da2fd5b2..7a0ea3b64 100644
--- a/test/run_resolver.cxx
+++ b/test/run_resolver.cxx
@@ -22,8 +22,6 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#ifdef WIN32
#include <ws2tcpip.h>
#include <winsock.h>
@@ -32,12 +30,13 @@
#include <netdb.h>
#endif
+#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc != 2) {
- g_printerr("Usage: run_resolver HOST\n");
+ fprintf(stderr, "Usage: run_resolver HOST\n");
return EXIT_FAILURE;
}
@@ -51,16 +50,8 @@ int main(int argc, char **argv)
}
for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) {
- char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen,
- error);
- if (p == NULL) {
- freeaddrinfo(ai);
- LogError(error);
- return EXIT_FAILURE;
- }
-
- g_print("%s\n", p);
- g_free(p);
+ const auto s = sockaddr_to_string(i->ai_addr, i->ai_addrlen);
+ printf("%s\n", s.c_str());
}
freeaddrinfo(ai);
diff --git a/test/software_volume.cxx b/test/software_volume.cxx
index 19a0be88c..b10eabeb3 100644
--- a/test/software_volume.cxx
+++ b/test/software_volume.cxx
@@ -24,15 +24,17 @@
*/
#include "config.h"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "AudioParser.hxx"
#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "stdbin.h"
+#include "Log.hxx"
-#include <glib.h>
-
+#include <stdio.h>
#include <stddef.h>
+#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
@@ -41,28 +43,29 @@ int main(int argc, char **argv)
ssize_t nbytes;
if (argc > 2) {
- g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n");
- return 1;
+ fprintf(stderr, "Usage: software_volume [FORMAT] <IN >OUT\n");
+ return EXIT_FAILURE;
}
Error error;
AudioFormat audio_format(48000, SampleFormat::S16, 2);
if (argc > 1) {
if (!audio_format_parse(audio_format, argv[1], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
- while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
- if (!pcm_volume(buffer, nbytes,
- audio_format.format,
- PCM_VOLUME_1 / 2)) {
- g_printerr("pcm_volume() has failed\n");
- return 2;
- }
+ PcmVolume pv;
+ if (!pv.Open(audio_format.format, error)) {
+ fprintf(stderr, "%s\n", error.GetMessage());
+ return EXIT_FAILURE;
+ }
- gcc_unused ssize_t ignored = write(1, buffer, nbytes);
+ while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
+ auto dest = pv.Apply({buffer, size_t(nbytes)});
+ gcc_unused ssize_t ignored = write(1, dest.data, dest.size);
}
+
+ pv.Close();
}
diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx
index 85c872674..355553687 100644
--- a/test/test_pcm_channels.cxx
+++ b/test/test_pcm_channels.cxx
@@ -22,67 +22,60 @@
#include "test_pcm_util.hxx"
#include "pcm/PcmChannels.hxx"
#include "pcm/PcmBuffer.hxx"
+#include "util/ConstBuffer.hxx"
void
PcmChannelsTest::TestChannels16()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 256;
const auto src = TestDataBuffer<int16_t, N * 2>();
PcmBuffer buffer;
/* stereo to mono */
- size_t dest_size;
- const int16_t *dest =
- pcm_convert_channels_16(buffer, 1, 2, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) / 2, dest_size);
+ auto dest = pcm_convert_channels_16(buffer, 1, 2, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N, dest.size);
for (unsigned i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int16_t((src[i * 2] + src[i * 2 + 1]) / 2),
- dest[i]);
+ dest.data[i]);
/* mono to stereo */
- dest = pcm_convert_channels_16(buffer, 2, 1, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) * 2, dest_size);
+ dest = pcm_convert_channels_16(buffer, 2, 1, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N * 4, dest.size);
for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
- CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
+ CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2]);
+ CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2 + 1]);
}
}
void
PcmChannelsTest::TestChannels32()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 256;
const auto src = TestDataBuffer<int32_t, N * 2>();
PcmBuffer buffer;
/* stereo to mono */
- size_t dest_size;
- const int32_t *dest =
- pcm_convert_channels_32(buffer, 1, 2, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) / 2, dest_size);
+ auto dest = pcm_convert_channels_32(buffer, 1, 2, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N, dest.size);
for (unsigned i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int32_t(((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2),
- dest[i]);
+ dest.data[i]);
/* mono to stereo */
- dest = pcm_convert_channels_32(buffer, 2, 1, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) * 2, dest_size);
+ dest = pcm_convert_channels_32(buffer, 2, 1, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N * 4, dest.size);
for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
- CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
+ CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2]);
+ CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2 + 1]);
}
}
diff --git a/test/test_pcm_dither.cxx b/test/test_pcm_dither.cxx
index 710deffcc..bf7484885 100644
--- a/test/test_pcm_dither.cxx
+++ b/test/test_pcm_dither.cxx
@@ -19,7 +19,7 @@
#include "test_pcm_all.hxx"
#include "test_pcm_util.hxx"
-#include "pcm/PcmDither.hxx"
+#include "pcm/PcmDither.cxx"
void
PcmDitherTest::TestDither24()
diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx
index 49f4ccd4b..3b0dbb8fe 100644
--- a/test/test_pcm_format.cxx
+++ b/test/test_pcm_format.cxx
@@ -29,86 +29,72 @@
void
PcmFormatTest::TestFormat8to16()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 256;
const auto src = TestDataBuffer<int8_t, N>();
PcmBuffer buffer;
- size_t d_size;
PcmDither dither;
- auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8,
- src, sizeof(src), &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8, src);
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
- CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 8);
+ CPPUNIT_ASSERT_EQUAL(int(src[i]), d.data[i] >> 8);
}
void
PcmFormatTest::TestFormat16to24()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 256;
const auto src = TestDataBuffer<int16_t, N>();
PcmBuffer buffer;
- size_t d_size;
- auto d = pcm_convert_to_24(buffer, SampleFormat::S16,
- src, sizeof(src), &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ auto d = pcm_convert_to_24(buffer, SampleFormat::S16, src);
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
- CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 8);
+ CPPUNIT_ASSERT_EQUAL(int(src[i]), d.data[i] >> 8);
}
void
PcmFormatTest::TestFormat16to32()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 256;
const auto src = TestDataBuffer<int16_t, N>();
PcmBuffer buffer;
- size_t d_size;
- auto d = pcm_convert_to_32(buffer, SampleFormat::S16,
- src, sizeof(src), &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ auto d = pcm_convert_to_32(buffer, SampleFormat::S16, src);
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
- CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 16);
+ CPPUNIT_ASSERT_EQUAL(int(src[i]), d.data[i] >> 16);
}
void
PcmFormatTest::TestFormatFloat()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 256;
const auto src = TestDataBuffer<int16_t, N>();
PcmBuffer buffer1, buffer2;
- size_t f_size;
- auto f = pcm_convert_to_float(buffer1, SampleFormat::S16,
- src, sizeof(src), &f_size);
- auto f_end = pcm_end_pointer(f, f_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(f_end - f));
+ auto f = pcm_convert_to_float(buffer1, SampleFormat::S16, src);
+ CPPUNIT_ASSERT_EQUAL(N, f.size);
- for (auto i = f; i != f_end; ++i) {
- CPPUNIT_ASSERT(*i >= -1.);
- CPPUNIT_ASSERT(*i <= 1.);
+ for (size_t i = 0; i != f.size; ++i) {
+ CPPUNIT_ASSERT(f.data[i] >= -1.);
+ CPPUNIT_ASSERT(f.data[i] <= 1.);
}
PcmDither dither;
- size_t d_size;
auto d = pcm_convert_to_16(buffer2, dither,
SampleFormat::FLOAT,
- f, f_size, &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ f.ToVoid());
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
- CPPUNIT_ASSERT_EQUAL(src[i], d[i]);
+ CPPUNIT_ASSERT_EQUAL(src[i], d.data[i]);
}
diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx
index 2a8a11388..542c4de80 100644
--- a/test/test_pcm_mix.cxx
+++ b/test/test_pcm_mix.cxx
@@ -21,6 +21,7 @@
#include "test_pcm_all.hxx"
#include "test_pcm_util.hxx"
#include "pcm/PcmMix.hxx"
+#include "pcm/PcmDither.hxx"
template<typename T, SampleFormat format, typename G=RandomInt<T>>
static void
@@ -30,23 +31,26 @@ TestPcmMix(G g=G())
const auto src1 = TestDataBuffer<T, N>(g);
const auto src2 = TestDataBuffer<T, N>(g);
+ PcmDither dither;
+
/* portion1=1.0: result must be equal to src1 */
auto result = src1;
- bool success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ bool success = pcm_mix(dither,
+ result.begin(), src2.begin(), sizeof(result),
format, 1.0);
CPPUNIT_ASSERT(success);
- AssertEqualWithTolerance(result, src1, 1);
+ AssertEqualWithTolerance(result, src1, 3);
/* portion1=0.0: result must be equal to src2 */
result = src1;
- success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ success = pcm_mix(dither, result.begin(), src2.begin(), sizeof(result),
format, 0.0);
CPPUNIT_ASSERT(success);
- AssertEqualWithTolerance(result, src2, 1);
+ AssertEqualWithTolerance(result, src2, 3);
/* portion1=0.5 */
result = src1;
- success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ success = pcm_mix(dither, result.begin(), src2.begin(), sizeof(result),
format, 0.5);
CPPUNIT_ASSERT(success);
@@ -54,7 +58,7 @@ TestPcmMix(G g=G())
for (unsigned i = 0; i < N; ++i)
expected[i] = (int64_t(src1[i]) + int64_t(src2[i])) / 2;
- AssertEqualWithTolerance(result, expected, 1);
+ AssertEqualWithTolerance(result, expected, 3);
}
void
diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx
index b378c75a7..216e360ce 100644
--- a/test/test_pcm_util.hxx
+++ b/test/test_pcm_util.hxx
@@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "util/ConstBuffer.hxx"
+
#include <array>
#include <random>
@@ -76,6 +78,14 @@ public:
operator typename std::array<T, N>::const_pointer() const {
return begin();
}
+
+ operator ConstBuffer<T>() const {
+ return { begin(), size() };
+ }
+
+ operator ConstBuffer<void>() const {
+ return { begin(), size() * sizeof(T) };
+ }
};
template<typename T>
diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx
index 764d8b127..c3a261f7a 100644
--- a/test/test_pcm_volume.cxx
+++ b/test/test_pcm_volume.cxx
@@ -17,169 +17,109 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "test_pcm_all.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
+#include "pcm/Traits.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
#include "test_pcm_util.hxx"
#include <algorithm>
#include <string.h>
-void
-PcmVolumeTest::TestVolume8()
+template<SampleFormat F, class Traits=SampleTraits<F>,
+ typename G=RandomInt<typename Traits::value_type>>
+static void
+TestVolume(G g=G())
{
- constexpr unsigned N = 256;
- static int8_t zero[N];
- const auto src = TestDataBuffer<int8_t, N>();
+ typedef typename Traits::value_type value_type;
+
+ PcmVolume pv;
+ CPPUNIT_ASSERT(pv.Open(F, IgnoreError()));
- int8_t dest[N];
+ constexpr size_t N = 256;
+ static value_type zero[N];
+ const auto _src = TestDataBuffer<value_type, N>(g);
+ const ConstBuffer<void> src(_src, sizeof(_src));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S8, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
+ pv.SetVolume(0);
+ auto dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero)));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S8, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
+ pv.SetVolume(PCM_VOLUME_1);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S8, PCM_VOLUME_1 / 2));
+ pv.SetVolume(PCM_VOLUME_1 / 2);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ const auto _dest = ConstBuffer<value_type>::FromVoid(dest);
for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
+ const auto expected = (_src[i] + 1) / 2;
+ CPPUNIT_ASSERT(_dest.data[i] >= expected - 4);
+ CPPUNIT_ASSERT(_dest.data[i] <= expected + 4);
}
+
+ pv.Close();
}
void
-PcmVolumeTest::TestVolume16()
+PcmVolumeTest::TestVolume8()
{
- constexpr unsigned N = 256;
- static int16_t zero[N];
- const auto src = TestDataBuffer<int16_t, N>();
-
- int16_t dest[N];
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S16, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S16, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S16, PCM_VOLUME_1 / 2));
+ TestVolume<SampleFormat::S8>();
+}
- for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
- }
+void
+PcmVolumeTest::TestVolume16()
+{
+ TestVolume<SampleFormat::S16>();
}
void
PcmVolumeTest::TestVolume24()
{
- constexpr unsigned N = 256;
- static int32_t zero[N];
- const auto src = TestDataBuffer<int32_t, N>(RandomInt24());
-
- int32_t dest[N];
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S24_P32, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S24_P32, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S24_P32, PCM_VOLUME_1 / 2));
-
- for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
- }
+ TestVolume<SampleFormat::S24_P32>(RandomInt24());
}
void
PcmVolumeTest::TestVolume32()
{
- constexpr unsigned N = 256;
- static int32_t zero[N];
- const auto src = TestDataBuffer<int32_t, N>();
-
- int32_t dest[N];
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S32, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S32, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S32, PCM_VOLUME_1 / 2));
-
- for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
- }
+ TestVolume<SampleFormat::S32>();
}
void
PcmVolumeTest::TestVolumeFloat()
{
- constexpr unsigned N = 256;
- static float zero[N];
- const auto src = TestDataBuffer<float, N>(RandomFloat());
+ PcmVolume pv;
+ CPPUNIT_ASSERT(pv.Open(SampleFormat::FLOAT, IgnoreError()));
- float dest[N];
+ constexpr size_t N = 256;
+ static float zero[N];
+ const auto _src = TestDataBuffer<float, N>(RandomFloat());
+ const ConstBuffer<void> src(_src, sizeof(_src));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::FLOAT, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
+ pv.SetVolume(0);
+ auto dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero)));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::FLOAT, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
+ pv.SetVolume(PCM_VOLUME_1);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::FLOAT,
- PCM_VOLUME_1 / 2));
+ pv.SetVolume(PCM_VOLUME_1 / 2);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ const auto _dest = ConstBuffer<float>::FromVoid(dest);
for (unsigned i = 0; i < N; ++i)
- CPPUNIT_ASSERT_DOUBLES_EQUAL(src[i] / 2, dest[i], 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(_src[i] / 2, _dest.data[i], 1);
+
+ pv.Close();
}
diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx
index a1037798c..953103f9a 100644
--- a/test/test_queue_priority.cxx
+++ b/test/test_queue_priority.cxx
@@ -1,7 +1,6 @@
#include "config.h"
#include "Queue.hxx"
-#include "Song.hxx"
-#include "Directory.hxx"
+#include "DetachedSong.hxx"
#include "util/Macros.hxx"
#include <cppunit/TestFixture.h>
@@ -9,21 +8,8 @@
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
-Directory detached_root;
-
-Directory::Directory() {}
-Directory::~Directory() {}
-
-Song *
-Song::DupDetached() const
-{
- return const_cast<Song *>(this);
-}
-
-void
-Song::Free()
-{
-}
+Tag::Tag(const Tag &) {}
+void Tag::Clear() {}
static void
check_descending_priority(const struct queue *queue,
@@ -53,12 +39,29 @@ public:
void
QueuePriorityTest::TestPriority()
{
- static Song songs[16];
+ DetachedSong songs[16] = {
+ DetachedSong("0.ogg"),
+ DetachedSong("1.ogg"),
+ DetachedSong("2.ogg"),
+ DetachedSong("3.ogg"),
+ DetachedSong("4.ogg"),
+ DetachedSong("5.ogg"),
+ DetachedSong("6.ogg"),
+ DetachedSong("7.ogg"),
+ DetachedSong("8.ogg"),
+ DetachedSong("9.ogg"),
+ DetachedSong("a.ogg"),
+ DetachedSong("b.ogg"),
+ DetachedSong("c.ogg"),
+ DetachedSong("d.ogg"),
+ DetachedSong("e.ogg"),
+ DetachedSong("f.ogg"),
+ };
struct queue queue(32);
for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i)
- queue.Append(&songs[i], 0);
+ queue.Append(DetachedSong(songs[i]), 0);
CPPUNIT_ASSERT_EQUAL(unsigned(ARRAY_SIZE(songs)), queue.GetLength());
diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx
index 1d95f6deb..bb8731c2f 100644
--- a/test/test_vorbis_encoder.cxx
+++ b/test/test_vorbis_encoder.cxx
@@ -24,6 +24,7 @@
#include "ConfigData.hxx"
#include "stdbin.h"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include <stddef.h>
@@ -81,8 +82,13 @@ main(gcc_unused int argc, gcc_unused char **argv)
encoder_to_stdout(*encoder);
Tag tag;
- tag.AddItem(TAG_ARTIST, "Foo");
- tag.AddItem(TAG_TITLE, "Bar");
+
+ {
+ TagBuilder tag_builder;
+ tag_builder.AddItem(TAG_ARTIST, "Foo");
+ tag_builder.AddItem(TAG_TITLE, "Bar");
+ tag_builder.Commit(tag);
+ }
success = encoder_tag(encoder, &tag, IgnoreError());
assert(success);
diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx
index 6e66c4696..bd890de4f 100644
--- a/test/visit_archive.cxx
+++ b/test/visit_archive.cxx
@@ -35,16 +35,6 @@
#include <unistd.h>
#include <stdlib.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
class MyArchiveVisitor final : public ArchiveVisitor {
public:
virtual void VisitArchiveEntry(const char *path_utf8) override {
@@ -71,8 +61,6 @@ main(int argc, char **argv)
g_thread_init(NULL);
#endif
- g_log_set_default_handler(my_log_func, NULL);
-
/* initialize MPD */
config_global_init();
diff --git a/valgrind.suppressions b/valgrind.suppressions
index 03f2025ee..8d1d86c41 100644
--- a/valgrind.suppressions
+++ b/valgrind.suppressions
@@ -485,3 +485,25 @@
fun:call_init
fun:_dl_init
}
+
+#
+# libsmbclient
+#
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:smbc_*_context
+ fun:smbc_init
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:smbc_setDebug
+ fun:smbc_init
+}